异常检测--基于类似度的方法

#异常检测——基于类似度的方法html

主要内容包括:python

  • 基于距离的度量
  • 基于密度的度量

 

文章目录

    • 一、概述
    • 二、基于距离的度量
      • 2.1 基于单元的方法
      • 2.2 基于索引的方法
    • 三、基于密度的度量
      • 3.1 k-距离(k-distance(p)):
      • 3.2 k-邻域(k-distance neighborhood):
      • 3.3 可达距离(reachability distance):
      • 3.4 局部可达密度(local reachability density):
      • 3.5 局部异常因子:
    • 四、练习
    • 参考资料:

 

一、概述

  “异常”一般是一个主观的判断,什么样的数据被认为是“异常”的,须要结合业务背景和环境来具体分析肯定。
  实际上,数据一般嵌入在大量的噪声中,而咱们所说的“异常值”一般指具备特定业务意义的那一类特殊的异常值。噪声能够视做特性较弱的异常值,没有被分析的价值。噪声和异常之间、正常数据和噪声之间的边界都是模糊的。异常值一般具备更高的离群程度分数值,同时也更具备可解释性。
  在普通的数据处理中,咱们经常须要保留正常数据,而对噪声和异常值的特性则基本忽略。但在异常检测中,咱们弱化了“噪声”和“正常数据”之间的区别,专一于那些具备有价值特性的异常值。在基于类似度的方法中,主要思想是异常点的表示与正常点不一样算法

二、基于距离的度量

  基于距离的方法是一种常见的适用于各类数据域的异常检测算法,它基于最近邻距离来定义异常值。 此类方法不只适用于多维数值数据,在其余许多领域,例如分类数据,文本数据,时间序列数据和序列数据等方面也有普遍的应用。
  基于距离的异常检测有这样一个前提假设,即异常点的 k kk 近邻距离要远大于正常点。解决问题的最简单方法是使用嵌套循环。 第一层循环遍历每一个数据,第二层循环进行异常判断,须要计算当前点与其余点的距离,一旦已识别出多于 k kk 个数据点与当前点的距离在 D DD 以内,则将该点自动标记为非异常值。 这样计算的时间复杂度为O ( N 2 ) O(N^{2})O(N2),当数据量比较大时,这样计算是及不划算的。 所以,须要修剪方法以加快距离计算。dom

2.1 基于单元的方法

  在基于单元格的技术中,数据空间被划分为单元格,单元格的宽度是阈值D和数据维数的函数。具体地说,每一个维度被划分红宽度最多为 D 2 ⋅ d \frac{D}{{2 \cdot \sqrt d }}2dD 单元格。在给定的单元以及相邻的单元中存在的数据点知足某些特性,这些特性可让数据被更有效的处理。ide

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PrxOTupS-1611237077193)(img/UWiX5C7kCHx5yX7O9yQm9F1ndg-QgMqS3BAwIWPB40k.original.fullsize-1609839833441.png)]svg

  以二维状况为例,此时网格间的距离为 D 2 ⋅ d \frac{D}{{2 \cdot \sqrt d }}2dD ,须要记住的一点是,网格单元的数量基于数据空间的分区,而且与数据点的数量无关。这是决定该方法在低维数据上的效率的重要因素,在这种状况下,网格单元的数量可能很少。 另外一方面,此方法不适用于更高维度的数据。对于给定的单元格,其 L 1 L_{1}L1 邻居被定义为经过最多1个单元间的边界可从该单元到达的单元格的集合。请注意,在一个角上接触的两个单元格也是 L 1 L_{1}L1 邻居。 L 2 L_{2}L2 邻居是经过跨越2个或3个边界而得到的那些单元格。 上图中显示了标记为 X XX的特定单元格及其 L 1 L_{1}L1 和 L 2 L_{2}L2 邻居集。 显然,内部单元具备8个 L 1 L_{1}L1 邻居和40个 L 2 L_{2}L2 邻居。 而后,能够当即观察到如下性质:函数

  • 单元格中两点之间的距离最多为 D / 2 D/2D/2。
  • 一个点与 L 1 L_{1}L1 邻接点之间的距离最大为 D DD。
  • 一个点与它的 L r LrLr 邻居(其中r rr > 2)中的一个点之间的距离至少为D DD。

  惟一没法直接得出结论的是 L 2 L_{2}L2 中的单元格。 这表示特定单元中数据点的不肯定性区域。 对于这些状况,须要明确执行距离计算。 同时,能够定义许多规则,以便当即将部分数据点肯定为异常值或非异常值。 规则以下:学习

  • 若是一个单元格中包含超过 k kk 个数据点及其 L 1 L_{1}L1 邻居,那么这些数据点都不是异常值。
  • 若是单元 A AA 及其相邻 L 1 L_{1}L1 和 L 2 L_{2}L2 中包含少于 k kk 个数据点,则单元A中的全部点都是异常值。

  此过程的第一步是将部分数据点直接标记为非异常值(若是因为第一个规则而致使它们的单元格包含 k kk 个点以上)。 此外,此类单元格的全部相邻单元格仅包含非异常值。 为了充分利用第一条规则的修剪能力,肯定每一个单元格及其 L 1 L_{1}L1 邻居中点的总和。 若是总数大于 k kk ,则全部这些点也都标记为非离群值。测试

  接下来,利用第二条规则的修剪能力。 对于包含至少一个数据点的每一个单元格 A AA,计算其中的点数及其 L 1 L_{1}L1 和 L 2 L_{2}L2 邻居的总和。 若是该数字不超过 k kk,则将单元格A AA 中的全部点标记为离群值。 此时,许多单元可能被标记为异常值或非异常值。ui

  对于此时仍未标记为异常值或非异常值的单元格中的数据点须要明确计算其 k kk 最近邻距离。即便对于这样的数据点,经过使用单元格结构也能够更快地计算出 k kk 个最近邻的距离。考虑到目前为止还没有被标记为异常值或非异常值的单元格A AA。这样的单元可能同时包含异常值和非异常值。单元格 A AA 中数据点的不肯定性主要存在于该单元格的 L 2 L_{2}L2 邻居中的点集。没法经过规则知道 A AA 的 L 2 L_{2}L2 邻居中的点是否在阈值距离 D DD 内,为了肯定单元 A AA 中数据点与其L 2 L_{2}L2 邻居中的点集在阈值距离 D DD 内的点数,须要进行显式距离计算。对于那些在 L 1 L_{1}L1 和 L 2 L_{2}L2 中不超过 k kk 个且距离小于 D DD 的数据点,则声明为异常值。须要注意,仅须要对单元 A AA 中的点到单元A AA的L 2 L_{2}L2邻居中的点执行显式距离计算。这是由于已知 L 1 L_{1}L1 邻居中的全部点到 A AA 中任何点的距离都小于 D DD,而且已知 L r LrLr 中 ( r > 2 ) (r> 2)(r>2) 的全部点与 A AA上任何点的距离至少为 D DD。所以,能够在距离计算中实现额外的节省。

2.2 基于索引的方法

  对于一个给定数据集,基于索引的方法利用多维索引结构(如 R \mathrm{R}R 树、k − d k-dkd 树)来搜索每一个数据对象 A AA 在半径 D DD 范围 内的相邻点。设 M MM 是一个异常值在其 D DD -邻域内容许含有对象的最多个数,若发现某个数据对象 A AA 的 D DD -邻域内出现 M + 1 M+1M+1 甚至更多个相邻点, 则断定对象 A AA 不是异常值。该算法时间复杂度在最坏状况下为 O ( k N 2 ) , O\left(k N^{2}\right),O(kN2), 其中 k kk 是数据集维数, N NN 是数据集包含对象的个数。该算法在数据集的维数增长时具备较好的扩展性,可是时间复杂度的估算仅考虑了搜索时间,而构造索引的任务自己就须要密集复杂的计算量。

三、基于密度的度量

  基于密度的算法主要有局部离群因子(LocalOutlierFactor,LOF),以及LOCI、CLOF等基于LOF的改进算法。下面咱们以LOF为例来进行详细的介绍和实践。

  基于距离的检测适用于各个集群的密度较为均匀的状况。在下图中,离群点B容易被检出,而若要检测出较为接近集群的离群点A,则可能会将一些集群边缘的点看成离群点丢弃。而LOF等基于密度的算法则能够较好地适应密度不一样的集群状况。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6tzwRYEo-1611237077198)(img/图4.1距离检测的困境-离群点A-1609839836032.png)]

   那么,这个基于密度的度量值是怎么得来的呢?仍是要从距离的计算开始。相似k近邻的思路,首先咱们也须要来定义一个“k-距离”。

3.1 k-距离(k-distance§):

  对于数据集D中的某一个对象o,与其距离最近的k个相邻点的最远距离表示为k-distance§,定义为给定点p和数据集D中对象o之间的距离d(p,o),知足:

  • 在集合D中至少有k个点 o’,其中o ′ ∈ D { p } o'∈D\{p\}oD{p},知足d ( p , o ′ ) ≤ d ( p , o ) d(p,o')≤d(p,o)d(p,o)d(p,o)
  • 在集合D中最多有k-1个点o’,其中o ′ ∈ D { p } o'∈D\{p\}oD{p},知足d ( p , o ; ) < d ( p , o ) d(p,o;)<d(p,o)d(p,o;)<d(p,o)

  直观一些理解,就是以对象o为中心,对数据集D中的全部点到o的距离进行排序,距离对象o第k近的点p与o之间的距离就是k-距离。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q7RjZsK3-1611237077201)(img/定义3-邻域半径-1609839837759.png)]

3.2 k-邻域(k-distance neighborhood):

  由k-距离,咱们扩展到一个点的集合——到对象o的距离小于等于k-距离的全部点的集合,咱们称之为k-邻域:$N_{k − d i s t a n c e ( p )}( P ) = { q ∈ D \backslash{ p } ∣ d ( p , q ) ≤ k − d i s t a n c e ( p )}
$。

  在二维平面上展现出来的话,对象o的k-邻域实际上就是以对象o为圆心、k-距离为半径围成的圆形区域。就是说,k-邻域已经从“距离”这个概念延伸到“空间”了。

3.3 可达距离(reachability distance):

  有了邻域的概念,咱们能够按照到对象o的距离远近,将数据集D内的点按照到o的距离分为两类:

  • p i p_ipi在对象o的k-邻域内,则可达距离就是给定点p关于对象o的k-距离;
  • p i p_ipi在对象o的k-邻域外,则可达距离就是给定点p关于对象o的实际距离。

  给定点p关于对象o的可达距离用数学公式能够表示为:r e a c h − d i s t k ( p , o ) = m a x { k − d i s t a n c e ( o ) , d ( p , o ) } r e a c h−d i s t_ k ( p , o ) = m a x \{k−distance( o ) , d ( p , o )\}reachdistk(p,o)=max{kdistance(o),d(p,o)} 。
  这样的分类处理能够简化后续的计算,同时让获得的数值区分度更高。

3.4 局部可达密度(local reachability density):

  咱们能够将“密度”直观地理解为点的汇集程度,就是说,点与点之间距离越短,则密度越大。在这里,咱们使用数据集D中给定点p与对象o的k-邻域内全部点的可达距离平均值的倒数(注意,不是导数)来定义局部可达密度。
  给定点p的局部可达密度计算公式为:l r d M i n P t s ( p ) = 1 / ( ∑ o ∈ N M i n P t s ( p ) r e a c h − d i s t M i n P t s ( p , o ) ∣ N M i n P t s ( p ) ∣ ) lrd_{MinPts}(p)=1/(\frac {\sum\limits_{o∈N_{MinPts}(p)} reach-dist_{MinPts}(p,o)} {\left\vert N_{MinPts}(p) \right\vert})lrdMinPts(p)=1/(NMinPts(p)oNMinPts(p)reachdistMinPts(p,o))

  由公式能够看出,这里是对给定点p进行度量,计算其邻域内的全部对象o到给定点p的可达距离平均值。给定点p的局部可达密度越高,越可能与其邻域内的点 属于同一簇;密度越低,越多是离群点。

3.5 局部异常因子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gRM2ox1m-1611237077204)(img/局部异常因子公式-1609839840015.png)]

  表示点p的邻域N k ( p ) N_k(p)Nk(p)内其余点的局部可达密度与点p的局部可达密度之比的平均数。若是这个比值越接近1,说明o的邻域点密度差很少,o可能和邻域同属一簇;若是这个比值小于1,说明o的密度高于其邻域点密度,o为密集点;若是这个比值大于1,说明o的密度小于其邻域点密度,o多是异常点。

  最终得出的LOF数值,就是咱们所须要的离群点分数。在sklearn中有LocalOutlierFactor库,能够直接调用。下面来直观感觉一下LOF的图像呈现效果。

  LocalOutlierFactor库能够用于对单个数据集进行无监督的离群检测,也能够基于已有的正常数据集对新数据集进行新颖性检测。在这里咱们进行单个数据集的无监督离群检测。

import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.neighbors import LocalOutlierFactor plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus']=False pd.set_option('display.max_columns', None) pd.set_option('display.max_rows', None) 

  首先构造一个含有集群和离群点的数据集。该数据集包含两个密度不一样的正态分布集群和一些离群点。可是,这里咱们手工对数据点的标注实际上是不许确的,可能有一些随机点会散落在集群内部,而一些集群点因为正态分布的特性,会与其他点的距离相对远一些。在这里咱们没法进行区分,因此按照生成方式统一将它们标记为“集群内部的点”或者“离群点”。

np.random.seed(61) # 构造两个数据点集群 X_inliers1 = 0.2 * np.random.randn(100, 2) X_inliers2 = 0.5 * np.random.randn(100, 2) X_inliers = np.r_[X_inliers1 + 2, X_inliers2 - 2] # 构造一些离群的点 X_outliers = np.random.uniform(low=-4, high=4, size=(20, 2)) # 拼成训练集 X = np.r_[X_inliers, X_outliers] n_outliers = len(X_outliers) ground_truth = np.ones(len(X), dtype=int) # 打标签,群内点构造离群值为1,离群点构造离群值为-1 ground_truth[-n_outliers:] = -1 
plt.title('构造数据集 (LOF)') plt.scatter(X[:-n_outliers, 0], X[:-n_outliers, 1], color='b', s=5, label='集群点') plt.scatter(X[-n_outliers:, 0], X[-n_outliers:, 1], color='orange', s=5, label='离群点') plt.axis('tight') plt.xlim((-5, 5)) plt.ylim((-5, 5)) legend = plt.legend(loc='upper left') legend.legendHandles[0]._sizes = [10] legend.legendHandles[1]._sizes = [20] plt.show() 

在这里插入图片描述
  而后使用LocalOutlierFactor库对构造数据集进行训练,获得训练的标签和训练分数(局部离群值)。为了便于图形化展现,这里对训练分数进行了一些转换。

# 训练模型(找出每一个数据的实际离群值) clf = LocalOutlierFactor(n_neighbors=20, contamination=0.1) # 对单个数据集进行无监督检测时,以1和-1分别表示非离群点与离群点 y_pred = clf.fit_predict(X) # 找出构造离群值与实际离群值不一样的点 n_errors = y_pred != ground_truth X_pred = np.c_[X,n_errors] X_scores = clf.negative_outlier_factor_ # 实际离群值有正有负,转化为正数并保留其差别性(不是直接取绝对值) X_scores_nor = (X_scores.max() - X_scores) / (X_scores.max() - X_scores.min()) X_pred = np.c_[X_pred,X_scores_nor] X_pred = pd.DataFrame(X_pred,columns=['x','y','pred','scores']) X_pred_same = X_pred[X_pred['pred'] == False] X_pred_different = X_pred[X_pred['pred'] == True] # 直观地看一看数据 X_pred.head() 

在这里插入图片描述
  将训练分数(离群程度)用圆直观地表示出来,并对构造标签与训练标签不一致的数据用不一样颜色的圆进行标注。

plt.title('局部离群因子检测 (LOF)') plt.scatter(X[:-n_outliers, 0], X[:-n_outliers, 1], color='b', s=5, label='集群点') plt.scatter(X[-n_outliers:, 0], X[-n_outliers:, 1], color='orange', s=5, label='离群点') # 以标准化以后的局部离群值为半径画圆,以圆的大小直观表示出每一个数据点的离群程度 plt.scatter(X_pred_same.values[:,0], X_pred_same.values[:, 1], s=1000 * X_pred_same.values[:, 3], edgecolors='c', facecolors='none', label='标签一致') plt.scatter(X_pred_different.values[:, 0], X_pred_different.values[:, 1], s=1000 * X_pred_different.values[:, 3], edgecolors='violet', facecolors='none', label='标签不一样') plt.axis('tight') plt.xlim((-5, 5)) plt.ylim((-5, 5)) legend = plt.legend(loc='upper left') legend.legendHandles[0]._sizes = [10] legend.legendHandles[1]._sizes = [20] plt.show() 

在这里插入图片描述

  能够看出,模型成功区分出了大部分的离群点,一些由于随机缘由散落在集群内部的“离群点”也被识别为集群内部的点,可是一些与集群略为分散的“集群点”则被识别为离群点。
  同时能够看出,模型对于不一样密度的集群有着较好的区分度,对于低密度集群与高密度集群使用了不一样的密度阈值来区分是否离群点。
  所以,咱们从直观上能够获得一个印象,即基于LOF模型的离群点识别在某些状况下,可能比基于某种统计学分布规则的识别更加符合实际状况。

四、练习

1.学习使用PyOD库生成toy example并调用LOF算法

import os import sys from pyod.models.lof import LOF from pyod.utils.data import generate_data from pyod.utils.data import evaluate_print from pyod.utils.example import visualize # 使用生成样本数据pyod.utils.data.generate_data(): contamination = 0.1 # percentage of outliers n_train = 200 # number of training points n_test = 100 # number of testing points X_train, y_train, X_test, y_test = generate_data( n_train=n_train, n_test=n_test, contamination=contamination) # 初始化pyod.models.lof.LOF检测器,拟合模型,而后进行预测。 # train LOF detector clf_name = 'LOF' """ LOF()构造器参数列表: n_neighbors=20, algorithm='auto', metric='minkowski', # 计算距离 p=2, # Parameter for the Minkowski metric from sklearn.metrics.pairwise.pairwise_distances. metric_params=None, contamination=0.1, # 污染比例: the proportion(比例) of outliers in the data set. n_jobs=1 # 并行做业数 """ clf = LOF() clf.fit(X_train) # get the prediction labels and outlier scores of the training data y_train_pred = clf.labels_ # binary labels (0: inliers, 1: outliers) y_train_scores = clf.decision_scores_ # raw outlier scores # get the prediction on the test data y_test_pred = clf.predict(X_test) # outlier labels (0 or 1) y_test_scores = clf.decision_function(X_test) # outlier scores # 使用ROC和Precision @ Rank n评估预测pyod.utils.data.evaluate_print()。 from pyod.utils.data import evaluate_print # evaluate and print the results print("\nLOF On Training Data:") evaluate_print(clf_name, y_train, y_train_scores) print("\nLOF On Test Data:") evaluate_print(clf_name, y_test, y_test_scores) # 在培训和测试数据上查看示例输出。 # 经过可视化全部示例中包含的功能来生成可视化。 visualize(clf_name, X_train, y_train, X_test, y_test, y_train_pred, y_test_pred, show_figure=True, save_figure=True) 

在这里插入图片描述
在这里插入图片描述

参考资料:

  1. LOF: Identifying Density-Based Local Outliers
  2. https://scikit-learn.org/stable/auto_examples/neighbors/plot_lof_outlier_detection.html?highlight=lof

关于Datawhale:

Datawhale是一个专一于数据科学与AI领域的开源组织,聚集了众多领域院校和知名企业的优秀学习者,聚合了一群有开源精神和探索精神的团队成员。Datawhale以“for the learner,和学习者一块儿成长”为愿景,鼓励真实地展示自我、开放包容、互信互助、勇于试错和敢于担当。同时Datawhale 用开源的理念去探索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,创建起人与人,人与知识,人与企业和人与将来的联结。

相关文章
相关标签/搜索