异常检测 Task 4

本章内容为异常检测–基于相似度的方法

1、概述

基于相似度的技术在数据点稀疏或与其他数据点相似度低时将其定义为异常值。在普通的数据处理中,我们常常需要保留正常数据,而对噪声和异常值的特性则基本忽略。但在异常检测中,我们弱化了“噪声”和“正常数据”之间的区别,专注于那些具有有价值特性的异常值。离群值分析中定义相似度的最常用方法如下:

  • 基于距离的方法:数据点到其 k- 最近邻(或其他变量)的距离用于定义邻近度。具有大的 k- 近邻距离的数据点被定义为离群点。基于距离的算法通常比其他两种方法在更细的粒度上执行分析。另一方面,这种更大的粒度往往需要大量的计算代价。
  • 基于密度的方法: 数据点在指定的局部区域(网格区域或基于距离的区域)内的其他点的数量,用于定义局部密度。这些局部密度值可以转换为离群值得分。其他基于核的方法或密度估计的统计方法也可以使用。
  • 基于聚类的方法: 任何群集中的数据点的非隶属性、它与其他群集的距离、最近群集的大小或这些因素的组合被用来量化离群值得分。聚类问题与异常检测问题有着互补关系,在这个问题中,点要么属于聚类,要么应该被认为是离群值。聚类方法和基于密度的方法的主要区别在于聚类方法对数据点进行划分,而基于密度的方法对数据空间进行划分。

2、基于距离的度量

基于距离的方法是一种常见的适用于各种数据域的异常检测算法,它基于最近邻距离来定义异常值。 此类方法不仅适用于多维数值数据,在其他许多领域,例如分类数据,文本数据,时间序列数据和序列数据等方面也有广泛的应用。

基于距离的异常检测有这样一个前提假设,即异常点的 k 近邻距离要远大于正常点。解决问题的最简单方法是使用嵌套循环。第一层循环遍历每个数据,第二层循环进行异常判断,需要计算当前点与其他点的距离,一旦已识别出多于 k 个数据点与当前点的距离在 D 之内,则将该点自动标记为非异常值。 这样计算的时间复杂度为 O ( N 2 ) O(N^{2}) O(N2),当数据量比较大时,这样计算是及不划算的。 因此,需要修剪方法以加快距离计算。

2.1 基于单元的算法

该算法的具有较小的计算复杂度,为 O ( c k + N ) O(ck+N) O(ck+N) ,其中 c 为取决于单元数的常数,k 为数据集的维数,N 为数据集中对象的个数。该算法适用于大规模数据集的异常值检测,但当数据集的维数增加时,它的可扩展性较差。

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

在这里插入图片描述
以二维情况为例,此时网格间的距离为 D 2 ⋅ d \frac{D}{{2 \cdot \sqrt d }} 2d D,需要记住的一点是,网格单元的数量基于数据空间的分区,并且与数据点的数量无关。这是决定该方法在低维数据上的效率的重要因素,在这种情况下,网格单元的数量可能不多。 另一方面,此方法不适用于更高维度的数据。对于给定的单元格,其 L 1 L_1 L1 邻居被定义为通过最多1个单元间的边界可从该单元到达的单元格的集合。请注意,在一个角上接触的两个单元格也是 L 1 L_1 L1 邻居。 L 2 L_2 L2 邻居是通过跨越2个或3个边界而获得的那些单元格。 上图中显示了标记为 X的特定单元格及其 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/2 D/2
  • 一个点与 L 1 L_1 L1 邻接点之间的距离最大为 D D D
  • 一个点与它的 L r L_r Lr 邻居(其中r > 2)中的一个点之间的距离至少为 D D D
    在这里插入图片描述

定义规则,以便立即将部分数据点确定为异常值或非异常值。 规则如下:

  • 如果一个单元格中包含超过 k 个数据点及其 L 1 L_1 L1 邻居,那么这些数据点都不是异常值。
  • 如果单元 A 及其相邻 L 1 L_1 L1 L 2 L_2 L2 中包含少于 k 个数据点,则单元A中的所有点都是异常值。

此过程的第一步是将部分数据点直接标记为非异常值(如果由于第一个规则而导致它们的单元格包含 k 个点以上)。 此外,此类单元格的所有相邻单元格仅包含非异常值。 为了充分利用第一条规则的修剪能力,确定每个单元格及其 L 1 L_1 L1 邻居中点的总和。 如果总数大于 k ,则所有这些点也都标记为非离群值。

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

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

2.2 基于索引的方法

在这里插入图片描述
基于距离的方法主要存在三个不足之处:
(1) 基于索引的算法和嵌套-循环算法的时间复杂度都为 O ( k N 2 ) O(kN^2) O(kN2),与数据集维数 k 是线性关系,与数据集中的对象个数 N 呈平方关系,现实中的数据集规模往往比较大,使得这两个算法的使用受到限制。
(2) DB(p, d)算法的检测结果对参数p和d具有较强敏感性,不同的p、d参数值往往导致差异比较大的结果,而参数 p、d 需要用户设置,为寻找合适的参数一般会涉及多次的尝试和错误。
(3) 基于距离的异常值检测方法将异常值看作是二元属性,对检测出的每个异常值不能给出异常的程度。

3、基于密度的度量

上述异常值检测算法对整个数据集都带有一种“全局”的观念,因此检测出的异常值也是“全局”异常值。然而,当数据集的密度不均匀时,这些检测算法往往会漏掉一部分异常值。现实中,多维数据集的数据结构通常都很复杂,往往有一些数据对象对于其相邻点来说可能是异常值,但在整个数据集中却不是异常值,任何一个“全局”异常值检测算法都不能检测出这些异常值。

基于密度的检测方法认为数据对象是否为异常值不仅仅取决于它与周围相邻点的距离远近,而且还跟其周围相邻点的稀疏程度密切相关,与以往大多数方法认为异常值仅是“非此即彼”的二元属性不同,认为数据对象不能简单地被划分为异常值和正常值,而是给每个对象赋予了一个局部异常因子 LOF 来表示其异常程度,然后根据实际研究要求选取前 n 个 LOF 最大的对象判定为异常值。基于密度的异常值检测方法可以有效检测出其它“全局”检测算法容易遗漏的这部分异常值。
在这里插入图片描述
以上图中一个二维数据集为例来进行说明,很明显图中 C2簇中的对象个数比 C1簇中的多,而 C1簇的密度比 C2簇的密度大,对于 C2中的某一对象 o,它的最近邻距离比 p1与 C1中的最近邻的距离要大,因此采用基于距离的异常值检测算法不会把 p1判定为异常值,而只能检测出 p2为异常值,而基于密度的检测算法可以同时准确判定 p1与 p2都为异常值。

基于密度的异常值检测算法的有关定义:

3.1 k-距离 k-distance(p)
在这里插入图片描述
直观一些理解,就是以对象o为中心,对数据集D中的所有点到o的距离进行排序,距离对象o第k近的点p与o的距离就是k-距离。

在这里插入图片描述

3.2 k-邻域(k-distance neighborhood)

在这里插入图片描述
k-邻域包含对象 p 的第 k 距离以内的所有点,包括第 k 距离点。
对象 p 的第 k 邻域点的个数 ∣ N k ( p ) ∣ ≥ k ∣ N_k(p)∣ ≥ k Nk(p)k
在这里插入图片描述

3.3 可达距离(reachability distance):

在这里插入图片描述
也就是说,如果对象 p 远离对象 o,则两者之间的可达距离就是它们之间的实际距离,但是如果它们足够近,则实际距离用 o 的k-距离代替。点 o 到点 p 的第 k 可达距离,至少是 o 的第 k 距离,或者为 o、p 间的真实距离。这也意味着,离点 o 最近的 k 个点,o 到它们的可达距离被认为相等,且都等于 d k ( o ) d_k(o) dk(o)
如下图, o 1 o_1 o1 到 p 的第 5 可达距离为 d ( p , o 1 ) d(p,o1) d(p,o1) o 2 o_2 o2 到 p 的第5可达距离为 d 5 ( o 2 ) d_5(o_2) d5(o2)

在这里插入图片描述

注意:这里用的是 p k p_k pk 与 o 的距离 d ( p k , o ) d(p_k,o) d(pk,o) 与 o 的 k-距离 k − d i s t a n c e ( o ) k−distance(o) kdistance(o) 来进行比较,不是与 k − d i s t a n c e ( p ) k−distance(p) kdistance(p) 进行比较!

可达距离的设计是为了减少距离的计算开销,o的k-邻域内的所有对象p的k-距离计算量可以被显著降低,相当于使用一个阈值把需要计算的部分“截断”了。这种“截断”对计算量的降低效果可以通过参数k来控制,k的值越高,无需计算的邻近点越多,计算开销越小。但是另一方面,k的值变高,可能意味着可达距离变远,对集群点和离群点的区分度可能变低。因此,如何选择k值,是LOF算法能否达到效率与效果平衡的重要因素。

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

在这里插入图片描述
我们可以将“密度”直观地理解为点的聚集程度,就是说,点与点之间距离越短,则密度越大。在这里,我们使用数据集D中对象p与对象o的k-邻域内所有点的可达距离平均值的倒数(注意,不是导数)来定义局部可达密度。

在进行局部可达密度的计算的时候,我们需要避免数据集内所有数据落在同一点上,即所有可达距离之和为0的情况:此时局部密度为 ∞ \infty ,后续计算将无法进行。LOF算法中针对这一问题进行了如下的定义:对于数据集D内的给定对象p,存在至少 M i n P t s ( p ) ≥ 1 MinPts(p)≥1 MinPts(p)1个不同于p的点。因此,我们使用对象p到 o ∈ N M i n P t s ( p ) o \in NMinPts(p) oNMinPts(p) 的可达距离 r e a c h − d i s t M i n P t s ( p , o ) reach−distMinPts(p,o) reachdistMinPts(p,o) 作为度量对象p邻域的密度的值。

这个值的含义可以这样理解,首先这代表一个密度,密度越高,我们认为越可能属于同一簇,密度越低,越可能是离群点。如果p和周围邻域点是同一簇,那么可达距离越可能为较小的 d k ( o ) d_k(o) dk(o),导致可达距离之和较小,密度值较高;如果 p 和周围邻居点较远,那么可达距离可能都会取较大值 d ( p , o ) d(p,o) d(p,o),导致密度较小,越可能是离群点。

3.5 局部异常因子(local outlier factor)

得到lrd(局部可达密度)以后就可以将每个点的lrd将与它们的k个邻点的lrd进行比较,得到局部异常因子LOF。更具体地说,LOF在数学上是对象p的邻居点o( o ∈ N M i n P t s ( p ) o∈NMinPts(p) oNMinPts(p))的lrd平均值与p的lrd的比值。
在这里插入图片描述
表示点p的邻域点的局部可达密度与点p的局部可达密度之比的平均数,其表示p是离群点的程度。如果这个比值越接近1,说明o的邻域点密度差不多,o可能和邻域同属一簇;如果这个比值小于1,说明o的密度高于其邻域点密度,o为密集点;如果这个比值大于1,说明o的密度小于其邻域点密度,o可能是异常点。

3.6 代码

3.6.1 sklearn

import numpy as np
import pandas as pd
from sklearn.neighbors import LocalOutlierFactor
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus']=False
    
np.random.seed(42)

# 生成训练集
X = 0.3 * np.random.randn(100, 2)

n_inliers = 2 * len(X)

# 生成异常数据
X_outliers = np.random.uniform(low=-4, high=4, size=(20, 2))

n_outliers = len(X_outliers)

# np.r_是将一系列的序列合并到一个数组中
X = np.r_[X-2, X+2, X_outliers]

# 打标签
ground_truth = np.ones(len(X), dtype=int)
ground_truth[-n_outliers:] = -1

# 然后使⽤LocalOutlierFactor库对构造数据集进⾏训练,得到训练的标签和训练分数(局部离群值)。为了便于图形化展⽰,这⾥对训练分数进⾏了⼀些转换。
# fit
clf = LocalOutlierFactor(n_neighbors=20, contamination=0.1)  #n_neighbors临近距离,默认20
clf.fit(X)

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]

#以标准化之后的局部离群值为半径画圆,以圆的⼤小直观表⽰出每个数据点的离群程度

a = plt.scatter(X[:n_inliers+1, 0], X[:n_inliers+1, 1], c='b', s=10)

b = plt.scatter(X[-n_outliers:, 0], X[-n_outliers:, 1], c='r', s=10)

c = plt.scatter(X_pred_same.values[:,0], X_pred_same.values[:,1], s=1000 *  X_pred_same.values[:,3], edgecolors='c',facecolors='none', label='标签一致')
d = 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))
plt.legend([a, b, c, d],
       ["集群点",
        "离群点",
        "标签一致",
        "标签不同"],
       loc="upper left")
plt.show()

在这里插入图片描述
3.6.2 PyOD

# PyOD
from pyod.models.lof import LOF

# 训练模型(找出每个数据的实际离群值)
clf = LOF()
clf.fit(X)

# 对单个数据集进行无监督检测时,以0和1分别表示非离群点与离群点,为了和ground_truth匹配,分别改成1和-1
y_pred = clf.labels_
y_pred[y_pred==1] = -1
y_pred[y_pred==0] = 1

# 找出构造离群值与实际离群值不同的点
n_errors = y_pred != ground_truth
X_pred = np.c_[X, n_errors]

X_scores = clf.decision_scores_

# 归一化离群值
X_scores_nor = 1 - (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]

#以标准化之后的局部离群值为半径画圆,以圆的⼤小直观表⽰出每个数据点的离群程度

a = plt.scatter(X[:n_inliers+1, 0], X[:n_inliers+1, 1], c='b', s=10)

b = plt.scatter(X[-n_outliers:, 0], X[-n_outliers:, 1], c='r', s=10)

c = plt.scatter(X_pred_same.values[:,0], X_pred_same.values[:,1], s=1000 *  X_pred_same.values[:,3], edgecolors='c',facecolors='none', label='标签一致')
d = 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))
plt.legend([a, b, c, d],
       ["集群点",
        "离群点",
           "标签一致",
        "标签不同"],
       loc="upper left")
plt.show()

在这里插入图片描述
使用sklearn和PyOD包的LOF算法,结果基本一样。

3.6.3 新奇点检测

异常检测(outlier detection)新奇点检测 novelty detection
训练集中包含异常数据,这些异常数据的值远离了正常数据因此,孤立点检测估计器试图拟合训练数据最集中的区域,而忽略了偏差观测。训练数据不受异常值的污染,我们感兴趣的是检测一个新的观测值是否是异常值。在这种情况下,离群值也称为新奇值。
# 新奇点检测

import numpy as np
import pandas as pd
from sklearn.neighbors import LocalOutlierFactor
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus']=False
    
np.random.seed(42)

# 生成训练集
X = 0.3 * np.random.randn(100, 2)

n_inliers = 2 * len(X)

# 生成异常数据
X_outliers = np.random.uniform(low=-4, high=4, size=(20, 2))

n_outliers = len(X_outliers)

# np.r_是将一系列的序列合并到一个数组中
X_train = np.r_[X-2, X+2]

X = 0.3 * np.random.randn(20, 2)
X_test = np.r_[X-2, X+2]

clf = LocalOutlierFactor(n_neighbors=20, novelty=True, contamination=0.1)
clf.fit(X_train)

y_pred_test = clf.predict(X_test)
y_pred_outliers = clf.predict(X_outliers)

n_error_test = y_pred_test[y_pred_test == -1].size
n_error_outliers = y_pred_outliers[y_pred_outliers == 1].size

xx, yy = np.meshgrid(np.linspace(-5, 5, 500), np.linspace(-5, 5, 500))
Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.title("Novelty Detection with LOF")
plt.contourf(xx, yy, Z, levels=np.linspace(Z.min(), 0, 7), cmap=plt.cm.PuBu)
a = plt.contour(xx, yy, Z, levels=[0], linewidths=2, colors='darkred')
plt.contourf(xx, yy, Z, levels=[0, Z.max()], colors='palevioletred')

s = 40
b1 = plt.scatter(X_train[:, 0], X_train[:, 1], c='white', s=s, edgecolors='k')
b2 = plt.scatter(X_test[:, 0], X_test[:, 1], c='blueviolet', s=s,
                 edgecolors='k')
c = plt.scatter(X_outliers[:, 0], X_outliers[:, 1], c='gold', s=s,
                edgecolors='k')
plt.axis('tight')
plt.xlim((-5, 5))
plt.ylim((-5, 5))
plt.legend([a.collections[0], b1, b2, c],
           ["learned frontier", "training observations",
            "new regular observations", "new abnormal observations"],
           loc="upper left")
plt.xlabel(
    "errors novel regular: %d/40 ; errors novel abnormal: %d/40"
    % (n_error_test, n_error_outliers))
plt.show()

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值