与其他流行的孤立点检测方法不同的主要思想是,孤立森林显式地识别异常,而不是对正常数据点进行分析。 孤立林和任何一种树系算法一样,都是建立在决策树的基础上的。 在这些树中,首先是随机选择一个特征,然后在所选特征的最小值和最大值之间选择一个随机分割值来创建的。
原则上,离群点不像正常点那样频繁,在数值上与它们不同(它们离特征空间中的常规点更远)。 这就是为什么通过使用这样的随机分区,它们更接近树的根(较短的平均路径长度,即观察到的边数必须通过从根到终端节点的树),并且需要更少的分割。
从图1中可以看出正常点(左边)比异常点(右边)需要标识更多的分区。
与其他离群点检测方法一样,决策需要一个异常评分。 在孤立森林的场景中,它被定义为:
其中
h
(
x
)
h(x)
h(x)是观察x的路径长度,
c
(
n
)
c(n)
c(n)是二进制搜索树中搜索失败的平均 路径长度,n是外部节点数。 更多关于异常值及其分量的信息,请参见[1]。
每一次观测都得到一个异常评分,可以在其基础上作出以下决定:
- 得分接近1表示异常
- 得分远小于0.5表示正常观测
- 如果所有分数都接近0.5,那么整个样本似乎没有明显的异常
Python示例
在二维数据集上找到离群点的过程。 首先,我需要产生观察。 我将从观察开始,这些观察将被认为是正常的,并将用于训练模型(Python的Scikit学习实现孤立森林类似于所有其他机器学习算法)。 第二组是新的观察,来自与训练相同的分布。 最后生成异常值。
图2显示了生成的数据集,训练和“正常”观测基本上是相互叠加的,而异常值则分散在一起。 由于异常值的随机性质,其中一些与训练/正常观测重叠。
现在我需要在训练集上训练孤立森林。 我在这里使用默认参数。 值得注意的是超参数,它指定了我们认为是异常值的观测百分比 (scikit-learn的默认值为0.1).
好吧,现在我们有了预测。 如何评估结果? 我们知道测试集只包含来自与正常观测相同分布的观测。 因此,所有的测试集观测都应该被归类为正常的。 对于离群点集,反之亦然。 让我们看看准确性。
起初,这看起来相当不错,特别是考虑到默认设置,然而,还有一个问题有待考虑。 由于异常值数据是随机生成的,一些异常值实际上位于正常观测范围内。 为了更仔细地检查它,我将绘制正常的观测数据集和标记的离群点集。 我们可以看到一些异常值在正常范围内观测集被正确地归类为常规观测,其中一些被错误地分类。 我们可以做的是尝试不同的超参数(contamination, number of estimators, number of samples) 才能更好地适应。目前效果还不错。
总结:
- 孤立森林是一种识别异常而不是正常观测的离群点检测技术
- 与随机森林类似,它是建立在二元(隔离)树的集合上的
- 它可以被放大以处理大型、高维数据集
coding:
# importing libaries ----
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pylab import savefig
from sklearn.ensemble import IsolationForest
# default plot settings
plt.rcParams['figure.dpi'] = 300
plt.rcParams['figure.figsize'] = [15, 10]
# Generating data ----
rng = np.random.RandomState(42)
# Generating training data
X_train = 0.2 * rng.randn(1000, 2)
X_train = np.r_[X_train + 3, X_train]
X_train = pd.DataFrame(X_train, columns = ['x1', 'x2'])
# Generating new, 'normal' observation
X_test = 0.2 * rng.randn(200, 2)
X_test = np.r_[X_test + 3, X_test]
X_test = pd.DataFrame(X_test, columns = ['x1', 'x2'])
# Generating outliers
X_outliers = rng.uniform(low=-1, high=5, size=(50, 2))
X_outliers = pd.DataFrame(X_outliers, columns = ['x1', 'x2'])
# Plotting generated data ----
plt.title("Data")
p1 = plt.scatter(X_train.x1, X_train.x2, c='white',
s=20*4, edgecolor='k')
p2 = plt.scatter(X_test.x1, X_test.x2, c='green',
s=20*4, edgecolor='k')
p3 = plt.scatter(X_outliers.x1, X_outliers.x2, c='red',
s=20*4, edgecolor='k')
plt.axis('tight')
plt.xlim((-2, 5))
plt.ylim((-2, 5))
plt.legend([p1, p2, p3],
["training observations",
"new regular obs.", "new abnormal obs."],
loc="lower right")
# saving the figure
plt.savefig('generated_data.png', dpi=300)
plt.show()
# Isolation Forest ----
# training the model
clf = IsolationForest(max_samples=100, contamination = 0.1, random_state=rng)
clf.fit(X_train)
# predictions
y_pred_train = clf.predict(X_train)
y_pred_test = clf.predict(X_test)
y_pred_outliers = clf.predict(X_outliers)
# new, 'normal' observations
print("Accuracy:", list(y_pred_test).count(1)/y_pred_test.shape[0])
#--------------------Accuracy: 0.93----------------------------
# outliers
print("Accuracy:", list(y_pred_outliers).count(-1)/y_pred_outliers.shape[0])
#--------------------Accuracy: 0.98----------------------------
# Inspecting the outliers ----
# adding the predicted label
X_outliers = X_outliers.assign(y = y_pred_outliers)
plt.title("Outlier Inspection")
p1 = plt.scatter(X_train.x1, X_train.x2, c='white',
s=20*4, edgecolor='k')
p2 = plt.scatter(X_outliers.loc[X_outliers.y == -1, ['x1']],
X_outliers.loc[X_outliers.y == -1, ['x2']],
c='red', s=20*4, edgecolor='k')
p3 = plt.scatter(X_outliers.loc[X_outliers.y == 1, ['x1']],
X_outliers.loc[X_outliers.y == 1, ['x2']],
c='green', s=20*4, edgecolor='k')
plt.axis('tight')
plt.xlim((-2, 5))
plt.ylim((-2, 5))
plt.legend([p1, p2, p3],
["training observations",
"detected outliers",
"detected regular obs."],
loc="lower right")
# saving the figure
plt.savefig('outlier_inspection.png', dpi=300)
plt.show()