Isolation, 意为孤立 / 隔离, 是名词, 其动词为 isolate,forest 是森林, 合起来就是 "孤立森林" 了, 也有叫 "独异森林", 好像并没有统一的中文叫法. 可能大家都习惯用其英文的名字 isolation forest, 简称 iForest .
iForest 适用于连续数据 (Continuous numerical data) 的异常检测, 将异常定义为 "容易被孤立的离群点(more likely to be separated)"-- 可以理解为分布稀疏且离密度高的群体较远的点. 用统计学来解释, 在数据空间里面, 分布稀疏的区域表示数据发生在此区域的概率很低, 因此可以认为落在这些区域里的数据是异常的. 通常用于网络安全中的攻击检测和流量异常等分析, 金融机构则用于挖掘出欺诈行为. 对于找出的异常数据, 然后要么直接清除异常数据, 如数据清理中的去噪数据, 要么深入分析异常数据, 比如分析攻击, 欺诈的行为特征.
Isolation Forest 算法原理
iForest 属于 Non-parametric 和 unsupervised 的方法, 即不用定义数学模型也不需要有标记的训练. 对于如何查找哪些点是否容易被孤立 (isolated),iForest 使用了一套非常高效的策略. 假设我们用一个随机超平面来切割(split) 数据空间(data space), 切一次可以生成两个子空间(详细拿刀切蛋糕一分为二). 之后我们再继续用一个随机超平面来切割每个子空间, 循环下去, 直到每个子空间里面只有一个数据点为止. 直观上来讲, 我们可以发现那些密度很高的簇是被切分很多次才会停止切割, 但是那些密度很低的点很容易很早就停到一个子空间看了.
怎么来切这个数据空间是 iForest 的设计核心思想, 本文仅学习最基本的方法, 由于切割是随机的, 所以需要用 ensemble 的方法来得到一个收敛值 (蒙特卡洛方法), 即反复从头开始切, 然后平均每次切的结果. IForest 由 t 个 iTree(Isolation Tree) 孤立树组成, 每个 iTree 是一个二叉树结构, 所以下面我们先说一下 iTree 树的构建, 然后再看 iForest 树的构建.
Isolation Forest 算法步骤
1,iTree 的构建
提到森林, 自然少不了树, 毕竟森林都是树构建的, 看 Isolation Forest(简称 iForest)前, 我们先来看看 Isolation Tree(简称 iTree)是怎么构成的. iTree 是一种随机二叉树, 每个节点要么有两个女儿, 要么就是叶子节点, 一个孩子都没有. 给定一堆数据集 D, 这里 D 的所有属性都是连续型的变量, iTree 的构成过程如下:
1, 随机选择一个属性 Attr
2, 随机选择该属性的一个值 Value
3, 根据 Attr 对每条记录进行分类, 把 Attr 小于 Value 的记录放在左女儿, 把大于等于 Value 的记录放在右孩子.
4, 然后递归的构造左女儿和右女儿, 直到满足以下条件:
1, 传入的数据集只有一条记录或者多条一样的记录
2, 树的高度达到了限定高度
iTree 构建好之后, 就可以对数据进行预测了. 预测的过程就是把测试记录在 ITree 上走一下, 看看测试记录在哪个叶子节点. iTree 能有效检测异常的假设是: 异常点一般都是非常稀有的, 在 iTree 中会很快被划分到叶子节点.
(注意: 异常点一般来说是稀疏的, 因此可以用较小次划分把他们归结到单独的区域中, 或者说只包含它的空间的面积较大)
因此可以把叶子节点到根节点的路径 h(x)长度来判断一条记录 x 是否是异常点 (也就是根据 h(x) 判断 x 是否是异常点). 对于一个包含 n 条记录的数据集, 其构造的树的高度最小值为 log(n), 最大值为 n-1, 论文中提到说用 log(n)和 n-1 归一化不能保证有界和不方便比较, 所以下面用一个稍微复杂一点的归一化公式:
S(x,n) 就是记录 x 在 n 个样本的训练数据构成的 iTree 的异常指数, S(x,n)取值范围为[0,1].
异常情况的判断分以下几种情况
1, 越接近 1 表示是异常点的可能性高
2, 越接近 0 表示是正常点的可能性高
3, 如果大部分的训练样本的 S(x,n)都接近于 0.5, 说明整个数据集都没有明显的异常值
如果是随机选属性, 随机选属性值, 一棵树这么随机选肯定不行, 但是把多棵树结合起来就变的强大了.
2,iForest 的构建
iTree 明白了, 下面我们看看 IForest 是怎么构造的, 给定一个包含 n 条记录的数据集 D, 如何构造一个 iForest,iForest 和 Random Forest 的方法有点类似, 都是随机采样一部分数据集去构造一棵树, 保证不同树之间的差异性, 不过 iForest 与 RF 不同, 采样的数据量 Psi 不需要等于 n, 可以远远小于 n, 论文提到采样大小超过 256 效果就提升不大了, 并且越大还会造成计算时间上的浪费, 为什么不像其他算法一样, 数据越多效果越好呢? 可以看看下面这两个图:
左边是原始数据, 右边是采样了数据, 蓝色是正常样本, 红色是异常样本. 可以看到, 在采样之前, 正常样本和异常样本出现重叠, 因此很难分开, 但我们采样之和, 异常样本和正常样本可以明显的分开.
其构造 iForest 的步骤如下:
1, 从训练数据中随机选择 n 个点样本作为 subsample, 放入树的根节点.
2, 随机指定一个维度(attribute), 在当前节点数据中随机产生一个切割点 p-- 切割点产生于当前节点数据中指定维度的最大值和最小值之间.
3, 以此切割点生成了一个超平面, 然后将当前节点数据空间划分为 2 个子空间: 把指定维度里面小于 p 的数据放在当前节点的左孩子, 把大于等于 p 的数据放在当前节点的右孩子.
4, 在孩子节点中递归步骤 2 和 3, 不断构造新的孩子节点, 知道孩子节点中只有一个数据 (无法再继续切割) 或者孩子节点已达限定高度.
获得 t 个 iTree 之后, iForest 训练就结束, 然后我们可以用生成的 iForest 来评估测试数据了. 对于一个训练数据 X, 我们令其遍历每一颗 iTree, 然后计算 X 最终落在每个树第几层(X 在树的高度). 然后我们可以得到 X 在每棵树的高度平均值, 即 the average path length over t iTrees.
Isolation Forest 算法总结
1, 异常点检测算法小结
IForest 目前是异常点检测最常用的算法之一, 它的优点非常突出, 他具有线性时间复杂度. 因为是随机森林的方法, 所以可以用在含有海量数据的数据集上, 通常树的数量越多, 算法越稳定. 由于每棵树都是互相独立生成的, 因此可以部署在大规模分布式系统上来加速运算. 对于目前大数据分析的趋势来说, 它的好用是由原因的.
但是 IForest 也有一些缺点, 比如不适用于特别高维的数据. 由于每次切数据空间都是随机选取一个维度和该维度的随机一个特征, 建完树后仍然有大量的维度没有被使用, 导致算法可靠性降低. 此推荐降维后使用, 或者考虑使用 One Class SVM .
另外 IForest 仅对即全局稀疏点敏感, 不擅长处理局部的相对稀疏点, 这样在某些局部的异常点较多的时候检测可能不是很准.
而 One Class SVM 对于中小型数据分析, 尤其是训练样本不是特别海量的时候用起来经常会比 IForest 顺手, 因此比较适合做原型分析.
2,Isolation Forest 算法小结
1,iForest 具有线性时间复杂度, 因为是 ensemble 的方法, 所以可以用在含有海量数据的数据集上面, 通常树的数量越多, 算法越稳定. 由于每棵树都是相互独立生成的, 因此可以部署在大规模分布式系统上来加速运算.
2,iForest 不适用于特别高维的数据. 由于每次切数据空间都是随机选取一个维度, 建完树后仍然有大量的维度信息没有被使用, 导致算法可靠性降低. 高维空间还可能存在大量噪音维度或者无关维度 (irrelevant attributes), 影响树的构建. 对这类数据, 建议使用子空间异常检测(Subspace Anomaly Detection) 技术. 此外, 切割平面默认是 axis-parallel 的, 也可以随机生成各种角度的切割平面.
3,IForest 仅对 Global Anomaly 敏感, 即全局稀疏点敏感, 不擅长处理局部的相对稀疏点(Local Anomaly).
4,iForest 推动了重心估计 (Mass Estimation) 理论, 目前在分类聚类和异常检测中都取得显著效果.
异常点检测算法使用场景
什么时候我们需要异常点检测算法呢? 常用的有三种情况.
1. 做数据预处理的时候需要对异常的数据做过滤, 防止对归一化等处理的结果.
2. 对没有标记输出的特征数据做筛选, 找出异常的数据.
3. 对有标记输出的特征数据做二分类时, 由于某些类别的训练样本非常少, 类别严重不平衡, 此时也可以考虑用非监督的异常点检测算法来做.
在以上场景中, 异常的数据量都是很少的一部分, 因此诸如: SVM, 逻辑回归等分类算法, 都不适用, 因为监督学习算法适用于有大量的正向样本, 也有大量的负向样本, 有足够的样本让算法去学习其特征, 且未来新出现的样本与训练样本分布一致.
下面是异常检测和监督学习相关算法的适用范围:
异常检测: 信用卡诈骗, 制造业产品异常检测, 数据中心机器异常检测, 入侵检测
监督学习: 垃圾邮件识别, 新闻分类
异常点检测算法常见类别
异常点检测的目的是找到数据集中和大多数数据不同的数据, 常用的异常点检测算法一般分为三类.
第一类是基于统计学的方法来处理异常数据, 这种方法一般会构建一个概率分布模型, 并计算对象符合该模型的概率, 把具有低概率的对象视为异常点. 比如特征工程中的 RobustScaler 方法, 在做数据特征值缩放的时候, 它会利用数据特征的分位数分布, 将数据根据分位数划分为多段, 只取中间段来做缩放, 比如只取 25% 分位数到 75% 分位数的数据做缩放, 这样减少了异常数据的影响.
第二类是基于聚类的方法来做异常点检测. 这个很好理解, 由于大部分聚类算法是基于数据特征的分布来做的, 通常我们聚类后发现某些聚类簇的数据样本量比其他簇少很多, 而且这个簇里的数据特征均值分布之类的值和其他簇也差异很大, 这些簇里的样本点大部分时候都是异常点. 比如 BIRCH 聚类算法原理和 DBSCAN 密度聚类算法都可以在聚类的同时做异常点的检测.
第三类是基于专门的异常点检测算法来做. 这些算法不像聚类算法, 检测异常点只是一个赠品, 他们的目的就是专门检测异常点的, 这类算法的代表是 One Class SVM 和 Isolation Forest.
scikit-learn Isolation Forest 算法库概述
在 sklearn 中, 我们可以用 ensemble 包里面的 IsolationForest 来做异常点检测
1, 知识储备(np.random.RandomState 的用法)
numpy.random.RandomState(): 获取随机数生成器
是计算机实现的随机数生成通常为伪随机数生成器, 为了使得具备随机性的代码最终的结果可复现, 需要设置相同的种子值.
- rng = numpy.random.RandomState(123465)
- arrayA = rng.uniform(0,1,(2,3))
该段代码的目的是产生一个 2 行 3 列的 assarray, 其中的每个元素都是 [0,1] 区间的均匀分布的随机数.
这里可以看到 123456 这个数字, 其实, 他是伪随机数产生器的种子. 也就是 "the starting point for a sequence of pseudorandom number"
对于某一个伪随机数发生器, 只要该种子 (seed) 相同, 产生的随机数序列就是相同的.
2,iForest 常用参数解释
n_estimators: 构建多少个 itree
max_samples: 采样数, 自动是 256
contamination:c(n)默认是 0.1
max_features: 最大特征数, 默认为 1
Bootstrap: 构建 Tree 时, 下次是否替换采样, 为 True 为替换, 为 False 为不替换
n_jobs:fit 和 prdict 执行时的并行数
完整的参数, 请参考 scikit-learn 官网文档, 这里方便自己学习, 就复制到这里.
3,iForest 常用属性说明
4,iForest 常用方法介绍
5, 实例一(iForest 算法检验数据)
isolation Forest 通过随机选择一个特征然后随机选择所选特征的最大值和最小值之间的分割值来 "隔离" 观察.
代码:
- #_*_coding:utf-8_*_
- import numpy as np
- import matplotlib.pyplot as plt
- from sklearn.ensemble import IsolationForest
- rng = np.random.RandomState(42)
- # Generate train data
- X = 0.3 * rng.randn(100, 2)
- X_train = np.r_[X + 2, X - 2]
- X = 0.3 * rng.randn(20, 2)
- X_test = np.r_[X + 2, X - 2]
- X_outliers = rng.uniform(low=-4, high=4, size=(20, 2))
- # fit the model
- clf = IsolationForest(behaviour='new', max_samples=100,
- random_state=rng, contamination='auto')
- clf.fit(X_train)
- y_pred_train = clf.predict(X_train)
- y_pred_test = clf.predict(X_outliers)
- xx, yy = np.meshgrid(np.linspace(-5, 5, 50), np.linspace(-5, 5, 50))
- Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()])
- Z = Z.reshape(xx.shape)
- plt.title("IsolationForest")
- plt.contourf(xx, yy, Z, camp=plt.cm.Blues_r)
- b1 = plt.scatter(X_train[:, 0], X_train[:, 1], c='white',
- s=20, edgecolor='k')
- b2 = plt.scatter(X_test[:, 0], X_test[:, 1], c='green',
- s=20, edgecolor='k')
- c = plt.scatter(X_outliers[:, 0], X_outliers[:, 1], c='red',
- s=20, edgecolor='k')
- plt.axis('tight')
- plt.xlim((-5, 5))
- plt.ylim((-5, 5))
- plt.legend([b1, b2, c],
- ["training observations",
- "new regular observations", "new abnormal observations"],
- loc="upper left")
- plt.show()
结果展示:
6, 实例二(多种异常检测算法比较)
此示例显示了 2D 数据集上不同异常检测算法的特征. 数据集包含一种或两种模式(高密度区域), 以说明算法处理多模态数据的能力.
对于每个数据集, 15% 的样本被生成为随机均匀噪声. 该比例是给予 OneClassSVM 的 nu 参数的值和其他异常值检测算法的污染参数. 除了局部异常因子 (LOF) 之外, 内部和异常值之间的决策边界以黑色显示, 因为当用于异常值检测时, 它没有预测方法应用于新数据.
svm.OneClassSVM 被称为是对异常值敏感, 并因此对异常值检测不执行的非常好. 当训练集未被异常值污染时, 该估计器最适合于新颖性检测. 也就是说, 高维中的离群检测, 或者对上层数据的分布没有任何假设是非常具有挑战性的, 并且单类 SVM 可能在这些情况下根据其超参数的值给出有用的结果.
covariance.EllipticEnvelope 假设数据是高斯数据并学习椭圆. 因此, 当数据不是单峰时, 它会降级. 但请注意, 此估算器对异常值很稳健.
ensemble.IsolationForest 并且 neighbors.LocalOutlierFactor 似乎对多模态数据集表现得相当好. neighbors.LocalOutlierFactor 对于第三数据集示出了优于其他估计器的优点 , 其中两种模式具有不同的密度. 这一优势可以通过 LOF 的局部方面来解释, 这意味着它只将一个样本的异常得分与其邻居的得分进行比较.
最后, 对于最后一个数据集, 很难说一个样本比另一个样本更异常, 因为它们均匀分布在超立方体中. 除了 svm.OneClassSVM 稍微过度拟合之外, 所有估算者都为这种情况提供了不错的解决方案. 在这种情况下, 更仔细地观察样本的异常分数是明智的, 因为良好的估计器应该为所有样本分配相似的分数.
虽然这些例子给出了一些关于算法的直觉, 但这种直觉可能不适用于非常高维的数据.
最后, 请注意模型的参数已经在这里精心挑选, 但实际上它们需要进行调整. 在没有标记数据的情况下, 问题完全没有监督, 因此模型选择可能是一个挑战.
代码:
- import time
- import numpy as np
- import matplotlib
- import matplotlib.pyplot as plt
- from sklearn import svm
- from sklearn.datasets import make_blobs, make_moons
- from sklearn.covariance import EllipticEnvelope
- from sklearn.ensemble import IsolationForest
- from sklearn.neighbors import LocalOutlierFactor
- matplotlib.rcParams['contour.negative_linestyle'] = 'solid'
- # Example settings
- n_samples = 300
- outliers_fraction = 0.15
- n_outliers = int(outliers_fraction * n_samples)
- n_inliers = n_samples - n_outliers
- # define outlier/ anomaly detection methods to be compared
- anomaly_algorithms = [
- ("Robust covariance", EllipticEnvelope(contamination=outliers_fraction)),
- ("One-Class SVM", svm.OneClassSVM(nu=outliers_fraction, kernel='rbf',gamma=0.1)),
- ("Isolation Forest", IsolationForest(behaviour='new', contamination=outliers_fraction, random_state=42)),
- ("Local Outlier Factor", LocalOutlierFactor(n_neighbors=35, contamination=outliers_fraction))
- ]
- # define datasets
- blobs_params = dict(random_state=0, n_samples=n_inliers, n_features=2)
- datasets = [
- make_blobs(centers=[[0, 0], [0, 0]], cluster_std=0.5, **blobs_params)[0],
- make_blobs(centers=[[2, 2], [-2, -2]], cluster_std=[0.5, 0.5], **blobs_params)[0],
- make_blobs(centers=[[2, 2], [-2, -2]], cluster_std=[1.5, 0.3], **blobs_params)[0],
- 4. * (make_moons(n_samples=n_samples, noise=0.05, random_state=0)[0] - np.array([0.5, 0.25])),
- 14. * (np.random.RandomState(42).rand(n_samples, 2) - 0.5)
- ]
- # Compare given classifiers under given settings
- xx, yy = np.meshgrid(np.linspace(-7, 7, 150), np.linspace(-7, 7, 150))
- plt.figure(figsize=(len(anomaly_algorithms) * 2 + 3, 12.5))
- plt.subplots_adjust(left=0.02, right=0.98, bottom=0.001, top=0.96, wspace=0.05, hspace=0.01)
- plot_num = 1
- rng = np.random.RandomState(42)
- for i_dataset, X in enumerate(datasets):
- # add outliers
- X = np.concatenate([X, rng.uniform(low=-6, high=6, size=(n_outliers, 2))], axis=0)
- for name, algorithm in anomaly_algorithms:
- print(name , algorithm)
- t0 = time.time()
- algorithm.fit(X)
- t1 = time.time()
- plt.subplot(len(datasets), len(anomaly_algorithms), plot_num)
- if i_dataset == 0:
- plt.title(name, size=18)
- # fit the data and tag outliers
- if name == 'Local Outlier Factor':
- y_pred = algorithm.fit_predict(X)
- else:
- y_pred = algorithm.fit(X).predict(X)
- # plot the levels lines and the points
- if name != "Local Outlier Factor":
- Z = algorithm.predict(np.c_[xx.ravel(), yy.ravel()])
- Z = Z.reshape(xx.shape)
- plt.contour(xx, yy, Z, levels=[0], linewidths=2, colors='black')
- colors = np.array(["#377eb8", '#ff7f00'])
- plt.scatter(X[:, 0], X[:, 1], s=10, color=colors[(y_pred + 1) // 2])
- plt.xlim(-7, 7)
- plt.ylim(-7, 7)
- plt.xticks(())
- plt.yticks(())
- plt.text(0.99, 0.01, ('%.2fs' % (t1 - t0)).lstrip('0'),
- transform=plt.gca().transAxes, size=15,
- horizontalalignment='right')
- plot_num += 1
- plt.show()
结果
参考文献: https://www.cnblogs.com/pinard/p/9314198.html
https://www.jianshu.com/p/5af3c66e0410
来源: https://www.cnblogs.com/wj-1314/p/10461816.html