iTree visualization for a toy dataset

This notebook shows how iTree, a decision tree used in Isolation Forest, finds outliers. We show it for a 2-D toy dataset and three different anomaly detection models: Isolation Forest, Pine Forest, and AAD.

To run this notebook you need graphviz installed and dot command to be in your $PATH environment variable. You can install it to your system with apt install graphviz or brew install graphviz or the equivalent command for your system.

[1]:
%matplotlib inline

import subprocess

import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['figure.figsize'] = figsize = (8, 6)
plt.rcParams['figure.dpi'] = 90
[2]:
class TreeViz:
    """
    Tree vizualization with matplotlib or graphviz.
    """
    def __init__(self, tree, known_data=None, known_labels=None, data=None, labels=None):
        self.tree = tree
        self.known_data = known_data
        self.known_labels = known_labels
        self.data = data
        self.labels = labels

        if known_data is None:
            self.known_data = np.empty((0, tree.n_features))
            self.known_labels = np.empty((0,))

    @staticmethod
    def _node_label(reds, blues):
        if reds + blues == 0:
            return '""'

        n_float = np.sqrt((reds + blues) / 12)
        columns = int(np.ceil(n_float * 4))

        labels = ['<font color="red">&#x2605;</font>'] * reds + ['<font color="blue">&#x2605;</font>'] * blues
        text = []
        while len(labels) > 0:
            text.append(''.join(labels[:columns]))
            labels = labels[columns:]

        return '<' + '<br/>'.join(text) + '>'

    def _dot_walker(self, current, known_data, known_labels):
        "Walk trough the tree recursively and make a dot file for graphviz"

        tree = self.tree
        text = []
        if tree.children_left[current] != -1:
            text.append(f' n{current} [label=""];')
            # If there are children nodes, render them too.

            # We need to split data and labels
            index = known_data[:, tree.feature[current]] <= tree.threshold[current]

            # Left one
            left = tree.children_left[current]
            text.append(f' n{current} -- n{left};')
            text.extend(self._dot_walker(left, known_data[index, :], known_labels[index]))

            # And right one
            right = tree.children_right[current]
            text.append(f' n{current} -- n{right};')
            text.extend(self._dot_walker(right, known_data[~index, :], known_labels[~index]))
        else:
            reds = np.sum(known_labels == Label.ANOMALY)
            blacks = np.sum(known_labels == Label.REGULAR)
            label = self._node_label(reds, blacks)
            text.append(f' n{current} [label={label}];')

        return text

    def generate_dot(self):
        text = []
        text.append('graph ""')
        text.append('{')
        text.extend(self._dot_walker(0, self.known_data, self.known_labels))
        text.append('}')

        return '\n'.join(text)

    def draw_graph(self):
        p = subprocess.Popen(['dot', '-Tpng'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        (image, _) = p.communicate(input=self.generate_dot().encode('utf-8'), timeout=10)
        return image

    def display_graph(self):
        'Display tree as a graph'
        image = self.draw_graph()

        import IPython.display
        return IPython.display.Image(image)

    def display_2d(self, full=False):
        'Display tree like a k2-tree'
        data = self.data
        labels = self.labels
        known_data = self.known_data
        known_labels = self.known_labels
        if data is None:
            raise ValueError('no data to plot')

        if data.shape[1] != 2:
            raise ValueError('only 2d plots are supported')

        fig, ax = plt.subplots()

        frame = np.empty((2, 2))
        frame[0, :] = data.min(axis=0)
        frame[1, :] = data.max(axis=0)

        self._draw_subsets(ax, frame, 0)

        if full:
            ax.scatter(*data[labels == Label.R, :].T, color='blue', s=10, label='Normal')
            ax.scatter(*data[labels == Label.A, :].T, color='red', s=10, label='Anomaluous')
            ax.scatter(*known_data[known_labels == Label.R, :].T, color='blue', marker='*', s=80)
            ax.scatter(*known_data[known_labels == Label.A, :].T, color='red', marker='*', s=80)
        else:
            ax.scatter(*known_data[known_labels == Label.R, :].T, color='blue', marker='*', s=80, label='Normal')
            ax.scatter(*known_data[known_labels == Label.A, :].T, color='red', marker='*', s=80, label='Anomaluous')

        ax.set(xlabel='x1', ylabel='x2', xlim=frame[:, 0], ylim=frame[:, 1])
        ax.legend()

        return fig, ax

    def _draw_subsets(self, ax, frame, current):
        tree = self.tree
        if tree.feature[current] < 0:
            return

        threshold = tree.threshold[current]
        feature = tree.feature[current]
        if feature == 0:
            ax.plot([threshold, threshold], [frame[0, 1], frame[1, 1]], color='gray', zorder=1)
        else:
            ax.plot([frame[0, 0], frame[1, 0]], [threshold, threshold], color='gray', zorder=1)

        left_frame = frame.copy()
        left_frame[1, feature] = threshold
        self._draw_subsets(ax, left_frame, tree.children_left[current])

        right_frame = frame.copy()
        right_frame[0, feature] = threshold
        self._draw_subsets(ax, right_frame, tree.children_right[current])
[3]:
from coniferest.datasets import Label, non_anomalous_outliers

data, labels = non_anomalous_outliers(inliers=1000, outliers=50)

plt.figure()
plt.title('Data overview')
plt.scatter(*data[labels == Label.R, :].T, color='blue', s=10, label='Normal')
plt.scatter(*data[labels == Label.A, :].T, color='red', s=10, label='Anomaluous')
plt.xlabel('x1')
plt.ylabel('x2')
plt.legend()
pass
../_images/notebooks_itree-visualisation_3_0.png

Get some experiment data

[4]:
from coniferest.session.oracle import create_oracle_session
[5]:
from coniferest.isoforest import IsolationForest

isoforest = IsolationForest(n_trees=30, n_subsamples=64, max_depth=5, random_seed=0)

session_isoforest = create_oracle_session(
    data=data,
    labels=labels,
    model=isoforest,
).run()
[6]:
from coniferest.pineforest import PineForest

pineforest = PineForest(
    n_trees=30,
    n_spare_trees=100,
    n_subsamples=64,
    max_depth=5,
    random_seed=0,
)

session_pineforest = create_oracle_session(
    data=data,
    labels=labels,
    model=pineforest,
).run()

Isoforest trees by themselves

[7]:
for t in isoforest.trees:
    viz = TreeViz(t, None, None, data=data, labels=np.full_like(labels, Label.R))
    display(viz.display_graph())
    fig, ax = viz.display_2d(full=True)
    ax.legend().remove()
    display(fig)
    plt.close(fig)
../_images/notebooks_itree-visualisation_9_0.png
../_images/notebooks_itree-visualisation_9_1.png
../_images/notebooks_itree-visualisation_9_2.png
../_images/notebooks_itree-visualisation_9_3.png
../_images/notebooks_itree-visualisation_9_4.png
../_images/notebooks_itree-visualisation_9_5.png
../_images/notebooks_itree-visualisation_9_6.png
../_images/notebooks_itree-visualisation_9_7.png
../_images/notebooks_itree-visualisation_9_8.png
../_images/notebooks_itree-visualisation_9_9.png
../_images/notebooks_itree-visualisation_9_10.png
../_images/notebooks_itree-visualisation_9_11.png
../_images/notebooks_itree-visualisation_9_12.png
../_images/notebooks_itree-visualisation_9_13.png
../_images/notebooks_itree-visualisation_9_14.png
../_images/notebooks_itree-visualisation_9_15.png
../_images/notebooks_itree-visualisation_9_16.png
../_images/notebooks_itree-visualisation_9_17.png
../_images/notebooks_itree-visualisation_9_18.png
../_images/notebooks_itree-visualisation_9_19.png
../_images/notebooks_itree-visualisation_9_20.png
../_images/notebooks_itree-visualisation_9_21.png
../_images/notebooks_itree-visualisation_9_22.png
../_images/notebooks_itree-visualisation_9_23.png
../_images/notebooks_itree-visualisation_9_24.png
../_images/notebooks_itree-visualisation_9_25.png
../_images/notebooks_itree-visualisation_9_26.png
../_images/notebooks_itree-visualisation_9_27.png
../_images/notebooks_itree-visualisation_9_28.png
../_images/notebooks_itree-visualisation_9_29.png
../_images/notebooks_itree-visualisation_9_30.png
../_images/notebooks_itree-visualisation_9_31.png
../_images/notebooks_itree-visualisation_9_32.png
../_images/notebooks_itree-visualisation_9_33.png
../_images/notebooks_itree-visualisation_9_34.png
../_images/notebooks_itree-visualisation_9_35.png
../_images/notebooks_itree-visualisation_9_36.png
../_images/notebooks_itree-visualisation_9_37.png
../_images/notebooks_itree-visualisation_9_38.png
../_images/notebooks_itree-visualisation_9_39.png
../_images/notebooks_itree-visualisation_9_40.png
../_images/notebooks_itree-visualisation_9_41.png
../_images/notebooks_itree-visualisation_9_42.png
../_images/notebooks_itree-visualisation_9_43.png
../_images/notebooks_itree-visualisation_9_44.png
../_images/notebooks_itree-visualisation_9_45.png
../_images/notebooks_itree-visualisation_9_46.png
../_images/notebooks_itree-visualisation_9_47.png
../_images/notebooks_itree-visualisation_9_48.png
../_images/notebooks_itree-visualisation_9_49.png
../_images/notebooks_itree-visualisation_9_50.png
../_images/notebooks_itree-visualisation_9_51.png
../_images/notebooks_itree-visualisation_9_52.png
../_images/notebooks_itree-visualisation_9_53.png
../_images/notebooks_itree-visualisation_9_54.png
../_images/notebooks_itree-visualisation_9_55.png
../_images/notebooks_itree-visualisation_9_56.png
../_images/notebooks_itree-visualisation_9_57.png
../_images/notebooks_itree-visualisation_9_58.png
../_images/notebooks_itree-visualisation_9_59.png
[8]:
def plot_trees_with_data(session, model):
    for t in model.trees:
        viz = TreeViz(
            t,
            known_data=session._data[np.array(list(session.known_labels.keys()))],
            known_labels=np.array(list(session.known_labels.values())),
            data=session._data,
            labels=session._metadata,
        )
        display(viz.display_graph())
        fig, _ = viz.display_2d()
        display(fig)
        plt.close(fig)

Pineforest trees on data seen by Pineforest

[9]:
plot_trees_with_data(session_pineforest, pineforest)
../_images/notebooks_itree-visualisation_12_0.png
../_images/notebooks_itree-visualisation_12_1.png
../_images/notebooks_itree-visualisation_12_2.png
../_images/notebooks_itree-visualisation_12_3.png
../_images/notebooks_itree-visualisation_12_4.png
../_images/notebooks_itree-visualisation_12_5.png
../_images/notebooks_itree-visualisation_12_6.png
../_images/notebooks_itree-visualisation_12_7.png
../_images/notebooks_itree-visualisation_12_8.png
../_images/notebooks_itree-visualisation_12_9.png
../_images/notebooks_itree-visualisation_12_10.png
../_images/notebooks_itree-visualisation_12_11.png
../_images/notebooks_itree-visualisation_12_12.png
../_images/notebooks_itree-visualisation_12_13.png
../_images/notebooks_itree-visualisation_12_14.png
../_images/notebooks_itree-visualisation_12_15.png
../_images/notebooks_itree-visualisation_12_16.png
../_images/notebooks_itree-visualisation_12_17.png
../_images/notebooks_itree-visualisation_12_18.png
../_images/notebooks_itree-visualisation_12_19.png
../_images/notebooks_itree-visualisation_12_20.png
../_images/notebooks_itree-visualisation_12_21.png
../_images/notebooks_itree-visualisation_12_22.png
../_images/notebooks_itree-visualisation_12_23.png
../_images/notebooks_itree-visualisation_12_24.png
../_images/notebooks_itree-visualisation_12_25.png
../_images/notebooks_itree-visualisation_12_26.png
../_images/notebooks_itree-visualisation_12_27.png
../_images/notebooks_itree-visualisation_12_28.png
../_images/notebooks_itree-visualisation_12_29.png
../_images/notebooks_itree-visualisation_12_30.png
../_images/notebooks_itree-visualisation_12_31.png
../_images/notebooks_itree-visualisation_12_32.png
../_images/notebooks_itree-visualisation_12_33.png
../_images/notebooks_itree-visualisation_12_34.png
../_images/notebooks_itree-visualisation_12_35.png
../_images/notebooks_itree-visualisation_12_36.png
../_images/notebooks_itree-visualisation_12_37.png
../_images/notebooks_itree-visualisation_12_38.png
../_images/notebooks_itree-visualisation_12_39.png
../_images/notebooks_itree-visualisation_12_40.png
../_images/notebooks_itree-visualisation_12_41.png
../_images/notebooks_itree-visualisation_12_42.png
../_images/notebooks_itree-visualisation_12_43.png
../_images/notebooks_itree-visualisation_12_44.png
../_images/notebooks_itree-visualisation_12_45.png
../_images/notebooks_itree-visualisation_12_46.png
../_images/notebooks_itree-visualisation_12_47.png
../_images/notebooks_itree-visualisation_12_48.png
../_images/notebooks_itree-visualisation_12_49.png
../_images/notebooks_itree-visualisation_12_50.png
../_images/notebooks_itree-visualisation_12_51.png
../_images/notebooks_itree-visualisation_12_52.png
../_images/notebooks_itree-visualisation_12_53.png
../_images/notebooks_itree-visualisation_12_54.png
../_images/notebooks_itree-visualisation_12_55.png
../_images/notebooks_itree-visualisation_12_56.png
../_images/notebooks_itree-visualisation_12_57.png
../_images/notebooks_itree-visualisation_12_58.png
../_images/notebooks_itree-visualisation_12_59.png

Pineforest trees on data seen by Isoforest

[10]:
plot_trees_with_data(session_isoforest, pineforest)
../_images/notebooks_itree-visualisation_14_0.png
../_images/notebooks_itree-visualisation_14_1.png
../_images/notebooks_itree-visualisation_14_2.png
../_images/notebooks_itree-visualisation_14_3.png
../_images/notebooks_itree-visualisation_14_4.png
../_images/notebooks_itree-visualisation_14_5.png
../_images/notebooks_itree-visualisation_14_6.png
../_images/notebooks_itree-visualisation_14_7.png
../_images/notebooks_itree-visualisation_14_8.png
../_images/notebooks_itree-visualisation_14_9.png
../_images/notebooks_itree-visualisation_14_10.png
../_images/notebooks_itree-visualisation_14_11.png
../_images/notebooks_itree-visualisation_14_12.png
../_images/notebooks_itree-visualisation_14_13.png
../_images/notebooks_itree-visualisation_14_14.png
../_images/notebooks_itree-visualisation_14_15.png
../_images/notebooks_itree-visualisation_14_16.png
../_images/notebooks_itree-visualisation_14_17.png
../_images/notebooks_itree-visualisation_14_18.png
../_images/notebooks_itree-visualisation_14_19.png
../_images/notebooks_itree-visualisation_14_20.png
../_images/notebooks_itree-visualisation_14_21.png
../_images/notebooks_itree-visualisation_14_22.png
../_images/notebooks_itree-visualisation_14_23.png
../_images/notebooks_itree-visualisation_14_24.png
../_images/notebooks_itree-visualisation_14_25.png
../_images/notebooks_itree-visualisation_14_26.png
../_images/notebooks_itree-visualisation_14_27.png
../_images/notebooks_itree-visualisation_14_28.png
../_images/notebooks_itree-visualisation_14_29.png
../_images/notebooks_itree-visualisation_14_30.png
../_images/notebooks_itree-visualisation_14_31.png
../_images/notebooks_itree-visualisation_14_32.png
../_images/notebooks_itree-visualisation_14_33.png
../_images/notebooks_itree-visualisation_14_34.png
../_images/notebooks_itree-visualisation_14_35.png
../_images/notebooks_itree-visualisation_14_36.png
../_images/notebooks_itree-visualisation_14_37.png
../_images/notebooks_itree-visualisation_14_38.png
../_images/notebooks_itree-visualisation_14_39.png
../_images/notebooks_itree-visualisation_14_40.png
../_images/notebooks_itree-visualisation_14_41.png
../_images/notebooks_itree-visualisation_14_42.png
../_images/notebooks_itree-visualisation_14_43.png
../_images/notebooks_itree-visualisation_14_44.png
../_images/notebooks_itree-visualisation_14_45.png
../_images/notebooks_itree-visualisation_14_46.png
../_images/notebooks_itree-visualisation_14_47.png
../_images/notebooks_itree-visualisation_14_48.png
../_images/notebooks_itree-visualisation_14_49.png
../_images/notebooks_itree-visualisation_14_50.png
../_images/notebooks_itree-visualisation_14_51.png
../_images/notebooks_itree-visualisation_14_52.png
../_images/notebooks_itree-visualisation_14_53.png
../_images/notebooks_itree-visualisation_14_54.png
../_images/notebooks_itree-visualisation_14_55.png
../_images/notebooks_itree-visualisation_14_56.png
../_images/notebooks_itree-visualisation_14_57.png
../_images/notebooks_itree-visualisation_14_58.png
../_images/notebooks_itree-visualisation_14_59.png

Isoforest trees on data seen by Isoforest

[11]:
plot_trees_with_data(session_isoforest, isoforest)
../_images/notebooks_itree-visualisation_16_0.png
../_images/notebooks_itree-visualisation_16_1.png
../_images/notebooks_itree-visualisation_16_2.png
../_images/notebooks_itree-visualisation_16_3.png
../_images/notebooks_itree-visualisation_16_4.png
../_images/notebooks_itree-visualisation_16_5.png
../_images/notebooks_itree-visualisation_16_6.png
../_images/notebooks_itree-visualisation_16_7.png
../_images/notebooks_itree-visualisation_16_8.png
../_images/notebooks_itree-visualisation_16_9.png
../_images/notebooks_itree-visualisation_16_10.png
../_images/notebooks_itree-visualisation_16_11.png
../_images/notebooks_itree-visualisation_16_12.png
../_images/notebooks_itree-visualisation_16_13.png
../_images/notebooks_itree-visualisation_16_14.png
../_images/notebooks_itree-visualisation_16_15.png
../_images/notebooks_itree-visualisation_16_16.png
../_images/notebooks_itree-visualisation_16_17.png
../_images/notebooks_itree-visualisation_16_18.png
../_images/notebooks_itree-visualisation_16_19.png
../_images/notebooks_itree-visualisation_16_20.png
../_images/notebooks_itree-visualisation_16_21.png
../_images/notebooks_itree-visualisation_16_22.png
../_images/notebooks_itree-visualisation_16_23.png
../_images/notebooks_itree-visualisation_16_24.png
../_images/notebooks_itree-visualisation_16_25.png
../_images/notebooks_itree-visualisation_16_26.png
../_images/notebooks_itree-visualisation_16_27.png
../_images/notebooks_itree-visualisation_16_28.png
../_images/notebooks_itree-visualisation_16_29.png
../_images/notebooks_itree-visualisation_16_30.png
../_images/notebooks_itree-visualisation_16_31.png
../_images/notebooks_itree-visualisation_16_32.png
../_images/notebooks_itree-visualisation_16_33.png
../_images/notebooks_itree-visualisation_16_34.png
../_images/notebooks_itree-visualisation_16_35.png
../_images/notebooks_itree-visualisation_16_36.png
../_images/notebooks_itree-visualisation_16_37.png
../_images/notebooks_itree-visualisation_16_38.png
../_images/notebooks_itree-visualisation_16_39.png
../_images/notebooks_itree-visualisation_16_40.png
../_images/notebooks_itree-visualisation_16_41.png
../_images/notebooks_itree-visualisation_16_42.png
../_images/notebooks_itree-visualisation_16_43.png
../_images/notebooks_itree-visualisation_16_44.png
../_images/notebooks_itree-visualisation_16_45.png
../_images/notebooks_itree-visualisation_16_46.png
../_images/notebooks_itree-visualisation_16_47.png
../_images/notebooks_itree-visualisation_16_48.png
../_images/notebooks_itree-visualisation_16_49.png
../_images/notebooks_itree-visualisation_16_50.png
../_images/notebooks_itree-visualisation_16_51.png
../_images/notebooks_itree-visualisation_16_52.png
../_images/notebooks_itree-visualisation_16_53.png
../_images/notebooks_itree-visualisation_16_54.png
../_images/notebooks_itree-visualisation_16_55.png
../_images/notebooks_itree-visualisation_16_56.png
../_images/notebooks_itree-visualisation_16_57.png
../_images/notebooks_itree-visualisation_16_58.png
../_images/notebooks_itree-visualisation_16_59.png