PyOD主要算法(KNN、IForest 和 MCD)的原理及使用

Python031

PyOD主要算法(KNN、IForest 和 MCD)的原理及使用,第1张

https://pyod.readthedocs.io/en/latest/pyod.models.html

Python Outlier Detection(PyOD)是当下最流行的Python异常检测工具库(toolkit)。该工具库的主要亮点包括:

对于特征空间中的一个样本,如果与之最相似的(即特征空间中距离最近的)k个样本中的大多数都属于某一类别,则该样本的分类结果也是这个类别。

https://www.cnblogs.com/lesleysbw/p/6074662.html

① 什么叫做KD_tree

K:K邻近查询中的k;D:空间是D维空间(Demension)tree:二叉树

② 建树过程

K-D tree的建立就是分裂空间的过程

首先,我们对整个区间 [1 , 15] 建树:先计算区间中所有点在第一维(也就是 x 坐标)上的方差:

平均值 : ave_1 =5.4

方差 : varance_1 =9.04

再计算区间中所有点在第二维(也就是 y 坐标)上的方差:

平均值:ave_2 =6.8

方差:varance_2 =10.96

明显看见,varance_2 >varance_1 ,那么我们在本次建树中, 分裂方式 :split_method =2 , 再将所有的点按照第2维的大小 从小到大排序 ,得到了新的点的一个排列:

(4,2) (1,4) (5,8) (7,9) (10,11)

取中间的点作为分裂点 sorted_mid =(5,8)作为根节点,再把区间 [1 , 2] 建成左子树 , [4 , 5] 建成右子树,此时,直线 : y = 8 将平面分裂成了两半,前面一半给左儿子,后面一半给了右儿子,如图:

建左子树 [1, 3] 的时候可以发现,这时候 第一维的方差大 ,分裂方式就是1 ,把区间 [ 1, 2 ] 中的点按照 第一维 的大小,从小到大排序 ,取 中间点(1,4) 根节点,再以区间 [ 2, 2] 建立右子树 得到节点 (4,2)

建右子树 [4 , 5] 的时候可以发现,这时还是第一维的方差大, 于是,我们便得到了这样的一颗二叉树 也就是 K-D tree,它把平面分成了如下的小平面, 使得每个小平面中最多有一个点

③ 查询过程:

查询,其实相当于我们要将一个点“添加”到已经建好的 K-D tree 中,但并不是真的添加进去,只是找到他应该 处于的子空间 即可,所以查询就显得简单的。

每次在一个区间中查询的时候,先看这个区间的 分裂方式 是什么,也就是说,先看这个区间是按照哪一维来分裂的,这样如果这个点对应的那一维上面的值比根节点的小,就在根节点的左子树上进行查询操作,如果是大的话,就在右子树上进查询操作。

每次回溯到了根节点(也就是说,对他的一个子树的查找已经完成了)的时候,判断一下,以该点为圆心,目前 找到的最小距离为半径 ,看是否和分裂区间的那一维所构成的平面相交,要是相交的话,最近点可能还在另一个子树上,所以还要再查询另一个子树,同时,还要看能否用根节点到该点的距离来更新我们的最近距离。为什么是这样的,我们可以用一幅图来说明:

https://github.com/YinghongZhang/BallTree-MIPS

① 原理

为了改进KDtree的二叉树树形结构,并且沿着笛卡尔坐标进行划分的低效率,ball tree将在一系列嵌套的超球体上分割数据。也就是说: 使用超球面而不是超矩形划分区域 。虽然在构建数据结构的花费上大过于KDtree,但是在 高维 甚至很高维的数据上都表现的很高效。

球树递归地将数据划分为 由质心C和半径r定义的节点 ,使得节点中的每个点都位于由r和C定义的超球内。通过使用三角不等式来减少邻居搜索的候选点数量。

② 建树过程

选择一个距离当前圆心最远的观测点A,和距离A最远的观测点B,将圆中所有离这两个点最近的观测点都赋给这两个簇的中心,然后计算每一个簇的中心点和包含所有其所属观测点的最小半径。对包含n个观测点的超圆进行分割,只需要线性的时间。

③ 查询

使用ball tree时,先自上而下找到包含target的叶子结点(c, r),从此结点中找到离它最近的观测点。这个距离就是 最近邻的距离的上界 。检查它的 兄弟结点 中是否包含比这个上界更小的观测点。方法是: 如果目标点距离兄弟结点的圆心的距离d >兄弟节点所在的圆半径R + 前面的上界r,则这个兄弟结点不可能包含所要的观测点 。否则,检查这个兄弟结点是否包含符合条件的观测点。

用一个随机超平面来切割数据空间, 直到每个子空间里面只有一个数据点为止。切割次数的多少可用来区分异常。

https://www.jianshu.com/p/5af3c66e0410

iForest 由t个iTree孤立树组成,每个iTree是一个二叉树,其实现步骤如下:

可以看到d最有可能是异常,因为其最早就被孤立(isolated)了。

获得t个iTree之后,iForest 训练就结束,然后我们可以用生成的iForest来评估测试数据了。对于一个训练数据x,我们令其遍历每一棵iTree,然后计算x最终落在每个树第几层(x在树的高度),得到x在每棵树的高度平均值。获得每个测试数据的average path length后,我们可以设置一个阈值,低于此阈值的测试数据即为异常。

IForest具有线性时间复杂度。

IForest不适用于特别高维的数据。

最小协方差行列式(Minimum Covariance Determinant)

https://max.book118.com/html/2017/1217/144650709.shtm

论文《Minimum covariance determinant and extensions》中有更详细描述。

论文《A Fast Algorithm for the Minimum Covariance Determinant Estimator》有更详细描述。

可能触发异常产生的代码会放到try语句块里,而处理异常的代码会在except语句块里实现。例如:

我们可以使用三种方法来处理多个异常。

第一种方法需要把所有可能发生的异常放到一个元组里。像这样:

另外一种方式是对每个单独的异常在单独的except语句块中处理。我们想要多少个except语句块都可以:

最后一种方式会捕获 所有 异常:

注意,捕获所有异常可能会造成意外的结果,比如,通常我们使用CTRL+C来终止程序,但如果程序中捕获了所有异常,CTRL+C就无法终止程序了。

包裹到finally从句中的代码不管异常是否触发都将会被执行。这可以被用来在脚本执行之后做清理工作:

如果想在没有触发异常的时候执行一些代码,可以使用else从句。

有人也许问了:如果你只是想让一些代码在没有触发异常的情况下执行,为啥你不直接把代码放在try里面呢?回答是,那样的话这段代码中的任意异常都还是会被try捕获,而你并不一定想要那样。

else从句只会在没有异常的情况下执行,而且它会在finally语句之前执行。