【Educoder数据挖掘实训】异常值检测-值域法

开挖!

这个题中 l o floflof算法给的很抽象,先用比较通俗的方式说一下:
首要想法是找到不合群的点,也就是异常点。采用的方法是对局部可达密度进行判断。相较于其他普通的简单基于聚类的算法,这个算法有两个优点:

  1. 可以应对下列问题:

    在上图中,显然pp p是一个异常点。但是可能根据常规的聚类算法很难排除点pp p。原因是点pp p是相较于 C 2C_2 C2来说的异常点,可是pp p C 2C_2 C2中点的距离和 C 1C_1 C1中点的平均距离差不多,所以常规的算法无法处理。但是pp ploflof lof算法中密度显然很低,可以被标记出来。
  2. loflof lof算法中,不会像传统异常点检测算法一样直接给出哪些点是异常点,二是会给出每个点的密度。这样可以自己更新阈值更方便后续处理,或者说loflof lof算法能更好的处理特殊情况。

那么什么是 l o floflof算法呢?先定义几个函数:
d ( p , q )d(p,q)d(p,q)表示点到点的距离;
dk( p )d_k(p)dk(p):第 kkk距离,表示所有点到 ppp的距离里,从小到大排序的第 kkk个;
Nk( p )N_k(p)Nk(p):第 kkk距离邻域:表示所有点到 ppp的距离里,不大于 dk( p )d_k(p)dk(p)的,不难看出 ∣ Nk( p ) ∣ ≥ k|N_k(p)|\ge kNk(p)k
r e a c h _ d i s tk( o , p ) = m a x ( dk( o ) , d ( o , p ) )reach\_dist_k(o,p)=max(d_k(o), d(o,p))reach_distk(o,p)=max(dk(o),d(o,p)):第 kkk可达距离,显然在 ooo的第 kkk邻域里的点,点 ooo到这些点的第 kkk可达距离都为第 kkk距离。
l r dk( p ) = 1 / (∑ o ∈ Nk( p )r e a c h _ d i s tk( o , p ) ∣ Nk( p ) ∣)lrd_k(p) = 1/(\frac{\sum_{o\in N_k(p)} reach\_dist_k(o,p)}{|N_k(p)|})lrdk(p)=1/(Nk(p)oNk(p)reach_distk(o,p)):点 ppp的第 kkk局部可达密度;
L O Fk( p ) =∑ o ∈ Nk( p ) l r dk( o ) l r dk( p )∣ Nk( p ) ∣=∑ o ∈ Nk( p )l r dk( o ) ∣ Nk( p ) ∣/ l r dk( p )LOF_k(p) = \frac{\sum_{o\in N_k(p)}\frac{lrd_k(o)}{lrd_k(p)}}{|N_k(p)|} = \frac{\sum_{o\in N_k(p)}lrd_k(o)}{|N_k(p)|} /lrd_k(p)LOFk(p)=Nk(p)oNk(p)lrdk(p)lrdk(o)=Nk(p)oNk(p)lrdk(o)/lrdk(p):局部离群因子,即将点 ppp Nk( p )N_k(p)Nk(p)邻域内所有点的平均局部可达密度与点的局部可达密度做比较,通过这个值来反应点 ppp是不是异常点。

所以其实我们要做的就是求出所有点的 L O Fk( p )LOF_k(p)LOFk(p)
显然有一种做法是 n3 n^3n3,即暴力枚举所有点和 kkk,这样当然是没问题的。
而且在数据挖掘中往往时间并不占据主要考虑对象,所以时间复杂度显得不是很重要。
但是显然有更优化的方法,比如用 K D T r e eKDTreeKDTree来优化这个过程或者 B a l lTr e eBall_TreeBallTree来优化,效果都是很好的。


当然这都不是我们考虑的范围, P y t h o nPythonPython已经给出了相应的函数,我们只需要拿来用即可。
但是可能有一个问题,就是上述的 kkk到底取多少,题目里也并没有明确强调。经过实验取 101010即可, P y t h o nPythonPython函数中默认是 202020
在求出所有密度之后我们在用 f i t _ p r e d i c tfit\_predictfit_predict函数进行预测即可,其中为 − 1-11的点就是异常点。
代码:

import pandas as pdimport numpy as npimport matplotlib.pyplot as pltfrom sklearn.neighbors import LocalOutlierFactor# 导入数据abc = pd.read_csv('deaths.csv')## 只分析其中的Population和Lat两个变量abc = abc[["Population","Lat"]]###begin###lof = LocalOutlierFactor(n_neighbors = 10)###将lof作用于数据集score = lof.fit_predict(abc)ans = 0for scr in score :if scr == -1 :ans += 1print("检测出的异常值数量为:", ans)###end####

一些问题和思考:

  1. 首先,这些算法PythonPython Python中都应相应的函数,只需要拿来用即可,关键要考虑清楚输入和输出的格式要求和数据类型。
  2. 这里n_neighbors=10n\_neighbors = 10 n_neighbors=10并不是强制要求,而是我们采用fit_predictfit\_predict fit_predict函数进行异常点检测时恰好kk k需要取到1010 10,我们如果换一种阈值可能就需要kk k是另一个值。
  3. 对于kk k值更深层次的理解:这里的kk k并不具备单调属性。很容易被误解成以每个点周围的kk k个点为聚类考虑问题。显然并不是,比如我们将kk k1010 10枚举到2020 20,得到的异常点个数并不是单调的:
    这其中的原因是:kk k并不是一个越大越宽松或者越大越严谨的可操控量,kk k只是一个算法中的变量。对于一个未知的数据我们并不能确定kk k的值来找到最好的异常点检测方案。换句话说,对于不同的数据找到最合适的kk k恰恰是我们应用loflof lof算法的关键。