著名的,人手一本的西瓜书(就是这本)的作者周志华老师,于2008年在第八届IEEE数据挖掘国际会议上提出孤立森林(Isolation Forest) 算法,
先简单解释一下什么是孤立森林: 「假设我们用一个随机超平面来切割(split)数据空间(data space), 切一次可以生成两个子空间(想象拿刀切蛋糕一分为二)。之后我们再继续用一个随机超平面来切割每个子空间,循环下去,直到每子空间里面只有一个数据点为止。直观上来讲,我们可以发现那些密度很高的簇是可以被切很多次才会停止切割,但是那些密度很低的点很容易很早的就停到一个子空间里了
和随机森林一样,孤立森林由 iTree(isolation tree) 组成,iTree树和随机森林的决策树不太一样,构建过程只是一个完全随机的过程,并且是不放回抽样。下面详细解释一下构建过程:
现有数据集中有n条数据,先从这n条数据中抽取一批样本(一般是无放回抽样),假设样本个数 ψ 。随机选择一个特征作为起始节点,并在该特征的值域里随机选择一个值,对ψ个样本进行二叉划分,将样本中小于该取值的样本划到左分支,样本中大于该取值的划到右分支。然后在左右两个分支重复这样的二叉划分操作。直到达到满足如下条件
(1)树达到了限制的高度;
(2)节点上只有一个样本;
(3)节点上的样本所有特征都相同。
算法简介
孤立森林算法属非监督学习算法,不需要定义参数模型和进行历史训练样本,通过采用多次迭代的方式构建二叉搜索树(Binary Search Tree),然后将这些二叉树组成森林,默认二叉搜索树的高度为 8,树的高度限制 l 与子样本数量ψ的关系为 l=ceiling(log2(ψ)),它近似等于树的平均高度。每 t=100 棵树组成一个森林,每次最多生成 ψ= 256 个森林。算法主要构建思想如下:
- 构建二叉树 iTree,首先从训练数据中随机选择 X 个样本,若 iTree 已经 达到限定高度或者仅剩一个样本,则算法收敛。否则,递归构建二叉搜索树,将小于当前根结点的样本放入左子结点,将大于当前根结点的样本放入右子结点。
Algorithm iTree(X,e,h): Input: X-input data; e-current height; h-height limit; Output: an iTree; if e >= h OR |X| <= 1 then return exNode{Size <- |X|} else //随机选择一个样本 q l <- filter(X, q<p) r <- filter(X, q>p) return inNode{Left <- iTree(l, e+1, h), Right <- iTree(r, e+1, h), SplitAttr q, SplitValue p} end if
-
构建二叉树森林 iForest,根据样本数据容量迭代重复步骤(1)过程创建二叉搜索树 iTree,并将生成的 iTree 组成二叉树森林。
-
计算森林中二叉树的路径长度,当二叉树森林 iForest 构建完成后,就可以对样本进行预测了,预测过程就是对二叉搜索树进行递归中序遍历,记录从根结点到叶子结点的路径长度 h(x)。
Algorithm pathLength(x,T,e): Input: x-an instance; T-an iTree, e-current path length; Output: path length of x; if T is an external node then return e+c(T.size) //c(n) 为二叉树森林平均路径长度 end if //递归中序遍历二叉搜索树 iTree a <- T.splitAttr if x.a < T.splitValue then return pathLength(x, T.left, e+1) else {x.a >= T.splitValue} return pathLength(x, T.right, e+1) end if
- 计算离群点偏离值,当森林中所有样本路径长度 h(x) 计算完毕后,通过运用统计学的方法计算得出所有数据样本期望值 E(h(x)) 和方差 S(h(x)),进而得到偏离期望和方差的异常数据点。
常见机器学习聚类算法通常根据空间距离或者密度来寻找异常数据,孤立森林算法独辟蹊径,采用构建二叉树森林再进行中序遍历计算叶子结点平均高度的方式来寻找异常数据,算法实现了对于海量数据的异常检测仅需 O(n) 的线性时间复杂度,能够在短暂的批处理时间间隔内有效检测出离群数据点。
假设X中有n个点,那这n个点如果用一个二分树来搜索,平均搜索不成功的路径就等于这n个点的平均路径长度c(n)=2H(n−1)−(2(n−1)/n)。所以用c(n)来归一化E(h(x))。那么这里存在一个疑问,为什么用c(n)来归一化,而不是这么多树真实的,每个点的平均路径长度来归一化呢?难道是为了节省计算力?不知道有没有小伙伴明白?请赐教。
异常程度便可表示为:
下图给出了s和E(h(x))的关系

由上图可以得到一些结论:
- 当E(h(x))→c(n)时,s→0.5,即样本x的路径平均长度与树的平均路径长度相近时,则不能区分是不是异常。
- 当E(h(x))→0 时,s→1,即x的异常分数接近1时,被判定为异常。
- 当E(h(x))→n−1 时,s→0,被判定为正常。
可以看出当其趋于0.5时,表示很可能是正常值,当其趋于1时,很可能是异常值。这里意思是如果某个点的平均路径和n个点(数据集中的点数)的平均路径趋于相同,则不太可能是异常点,只有当其大大小于n个点的平均路径时,才可能是异常值。
注意:
* 训练样本中异常样本的比例比较高,可能最终的效果会受影响 * 异常检测跟具体的应用场景紧密相关,算法检测出的“异常”不一定是我们实际想要的,所以,在特征选择时,要注意过滤不太相关的特征。 个人觉得较好的实践是,在预处理时把不关注的特征normalize。
参考资料: