异常检测系列:基于聚类的局部异常因子(CBLOF)

img
在介绍了局部离群因子(LOF)之后,我们可以介绍基于聚类的局部离群因子(CBLOF)。它将异常定义为与附近聚类的局部距离和数据点所属聚类的大小的组合。它首先将数据点聚类为大型或小型聚类。靠近附近大型聚类的小型聚类的数据点被识别为离群值。局部离群值可能不是一个单独的点,而是一个小组孤立点。CBLOF是由He、Xu和Deng(2002)[1]提出的,因为它在捕捉离群值方面具有独特的优点。

(A) CBLOF的工作原理是什么?

在图(A)中,C2和C4是大型聚类,C1和C3是小型聚类。聚类C1和C3是离群值,因为它们不属于大型聚类C2和C4。按照局部邻域的精神,聚类C1中的数据是聚类C2的局部离群值,聚类C3中的数据是聚类C4的局部离群值。请注意CBLOF和LOF之间的区别。CBLOF在“聚类”级别上构建,而LOF在单个数据点级别上构建。

离群值的定义是任何靠近较大聚类的小型聚类的数据点。该定义捕捉了Breunig(2000)提出的“局部离群值”的概念,以及任何聚类方法中的聚类概念。CBLOF考虑了数据点所属聚类的大小和数据点与其最近聚类之间的距离。
img
图(A):CBLOF(图片来源:[1])

让我用三个步骤来描述CBLOF的过程。第一步将数据点分配给一个且仅一个簇。任何能产生良好聚类结果的聚类算法都是合适的。因此,K-means是CBLOF常用的聚类算法。

第二步根据簇的大小从大到小对簇进行排名,并获取累积数据计数。那些包含90%数据的簇被称为“大”簇,而包含剩余10%数据的簇被称为“小”簇。90%的阈值可以在建模过程中进行微调。

第三步根据以下规则计算数据点到质心的距离和异常值得分:

  • 如果一个数据点属于一个大簇,则距离是到其簇质心的距离。异常值得分是距离乘以簇中数据点的数量。

  • 如果一个数据点属于一个小簇,则距离是到最近的大簇质心的距离。异常值得分是距离乘以数据点所属小簇的数据量。

(B) CBLOF和LOF的区别

LOF使用基于密度的方法,计算局部第K个最近邻居的数据点之间的距离。在图(B)中,LOF可能将点a1识别为异常值,因为它从局部角度来看与C1距离较远。C1或C3可能不被认为是异常值。相反,CBLOF将考虑簇C1中的所有数据点都是异常值,因为C1与最近的大簇C2相距较远。
img
(C)建模过程

在本书中,我采用以下建模过程进行模型的开发、评估和结果的解释。

  1. 模型开发

  2. 阈值确定

  3. 正常组和异常组的描述性统计

使用CBLOF异常值得分,您将选择一个阈值来将具有高异常值得分的异常观察结果与正常观察结果分开。如果有任何先前的知识表明异常值的百分比不应超过1%,您可以选择一个阈值,使异常值约占1%。

两个组之间特征的描述性统计(例如均值和标准差)对于传达模型的可靠性非常重要。如果预期异常组中某个特征的均值高于正常组的均值,而结果却相反,这将是违反直觉的。在这种情况下,您应该调查、修改或删除该特征,并重新进行建模。
img
(C.1) 步骤1 — 构建CBLOF模型

与之前一样,我将使用PyOD的实用函数generate_data()生成异常值。虽然目标变量Y可用,但无监督模型仅使用X变量。Y变量仅用于验证。我将异常值的百分比设置为5%,使用“contamination=0.05”。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pyod.utils.data import generate_data
contamination = 0.05 # percentage of outliers
n_train = 500       # number of training points
n_test = 500        # number of testing points
n_features = 6      # number of features
X_train, X_test, y_train, y_test = generate_data(
    n_train=n_train, 
    n_test=n_test, 
    n_features= n_features, 
    contamination=contamination, 
    random_state=123)

# Make the 2d numpy array a pandas dataframe for each manipulation 
X_train_pd = pd.DataFrame(X_train)
    
# Plot
plt.scatter(X_train_pd[0], X_train_pd[1], c=y_train, alpha=0.8)
plt.title('Scatter plot')
plt.xlabel('x0')
plt.ylabel('x1')
plt.show()

让我在散点图中仅绘制前两个变量。散点图中的黄色点是异常值,紫色点是正常数据点。
img
图(C.1): (作者提供的图片)

下面的代码指定了模型。因为CBLOF是一种基于聚类的算法,一个关键参数是聚类的数量。我将聚类的数量设置为10。之后我们将测试一系列其他聚类数量的值。参数contamination=0.05声明了一个5%的污染率。污染率是异常值的百分比。在大多数情况下,我们不知道异常值的百分比,所以可以根据任何先前的知识来赋值。PyOD默认的污染率为10%。

函数decision_functions()生成训练数据和测试数据的异常值分数。当训练数据的异常值分数按升序排序时,前5%的数据是根据5%的污染率来确定的异常值。将前5%和剩下的95%分开的异常值分数是阈值,并存储在threshold_中。

函数predict()通过将异常值分数与阈值进行比较,将其分配为“1”或“0”。如果分数高于阈值,则分配为“1”,否则为“0”。我在下面的代码中编写了一个简短的函数count_stat()来显示预测的“1”和“0”的计数。


from pyod.models.cblof import CBLOF
cblof = CBLOF(n_clusters=10, contamination = 0.05) 
cblof.fit(X_train)

# Training data
y_train_scores = cblof.decision_function(X_train)
y_train_pred = cblof.predict(X_train)

# Test data
y_test_scores = cblof.decision_function(X_test)
y_test_pred = cblof.predict(X_test) # outlier labels (0 or 1)

def count_stat(vector):
    # Because it is '0' and '1', we can run a count statistic. 
    unique, counts = np.unique(vector, return_counts=True)
    return dict(zip(unique, counts))

print("The training data:", count_stat(y_train_pred))
print("The training data:", count_stat(y_test_pred))
# Threshold for the defined comtanimation rate
print("The threshold for the defined comtanimation rate:" , cblof.threshold_)

img
CBLOF特有的两个参数值得一提。我们可以使用get_params()来打印出模型参数。这两个参数是“alpha”和“beta”。首先,让我解释一下“alpha”。它是大簇的计数百分比。正如第(A)节所述,CBLOF通过大小对簇进行排序,那些包含90%数据的前几个簇被称为大簇。这里的“alpha”默认为90%。其次,让我解释一下“beta”。它是决定小簇和大簇的系数。对于按照大小降序排列的簇,|C1|, |C2|, …, |Cn|,“beta”是任意相邻簇k和k-1之间的比率:|Ck| / |Ck-1|。默认值为5.0。这意味着如果前一个簇的大小|Ck|是下一个簇的五倍,簇k及以上被定义为大簇,簇k-1及以下被定义为小簇。只要满足“alpha”或“beta”,算法就会定义簇。

cblof.get_params()

img
(C.2) 第二步 — 确定CBLOF模型的合理阈值

PyOD有一个内置函数threshold_,它根据污染率contamination计算训练数据的阈值。由于默认的污染率是0.10,训练数据的阈值为1.2311,如下所示。它表示任何具有异常分数大于1.2311的观测值都被视为异常值。

确定阈值的第二种方法是使用PCA异常分数的直方图,这使我们能够根据业务情况选择阈值。图(C.2)显示了分数的直方图。我们可以选择一种更保守的方法,通过选择一个较高的阈值,这将导致异常组中的异常值更少但更精细。

import matplotlib.pyplot as plt
plt.hist(y_train_scores, bins='auto')  # arguments are passed to np.histogram
plt.title("Histogram with 'auto' bins")
plt.xlabel('CBLOF outlier score')
plt.show()

img
(C.3) 第三步 — 展示正常组和异常组的汇总统计信息

通过选择的阈值,我们可以识别出正常组和异常组,并展示这些组的特征。下面我使用用户定义的实用函数来展示这两个组。


threshold = cblof.threshold_ # Or other value from the above histogram

def descriptive_stat_threshold(df,pred_score, threshold):
    # Let's see how many '0's and '1's.
    df = pd.DataFrame(df)
    df['Anomaly_Score'] = pred_score
    df['Group'] = np.where(df['Anomaly_Score']< threshold, 'Normal', 'Outlier')

    # Now let's show the summary statistics:
    cnt = df.groupby('Group')['Anomaly_Score'].count().reset_index().rename(columns={'Anomaly_Score':'Count'})
    cnt['Count %'] = (cnt['Count'] / cnt['Count'].sum()) * 100 # The count and count %
    stat = df.groupby('Group').mean().round(2).reset_index() # The avg.
    stat = cnt.merge(stat, left_on='Group',right_on='Group') # Put the count and the avg. together
    return (stat)

descriptive_stat_threshold(X_train,y_train_scores, threshold)

img
表格(C.3)

上表展示了正常组和异常组的特征。它显示了正常组和异常组的计数和计数百分比。您需要使用特征名称标记特征,以便有效地展示。该表格告诉我们几个重要的结果。

首先,异常组约占总体的5%。请记住,异常组的大小取决于阈值。如果您选择较高的阈值,异常组的大小将会缩小。

其次,在我们的情况下,异常组中特征的均值小于正常组。如果某个特征的均值在异常组中被认为是较高或较低的,而结果与直觉相悖,建议您修改或删除该特征并重新运行模型。一个良好的模型应该具有与任何先前知识一致的特征均值。显然,如果数据结果强有力,它将提供新的见解来挑战任何先前的简要。

第三,“异常分数”是平均异常分数。我们只需要验证异常分数在异常组中是否高于正常组。您不需要对分数进行过多解释。

因为我们的数据生成中有真实值,所以我们可以生成一个混淆矩阵来了解模型的性能。该模型表现良好,并成功识别出了所有的25个异常值。


Actual_pred = pd.DataFrame({'Actual': y_test, 'Anomaly_Score': y_test_scores})
Actual_pred['Pred'] = np.where(Actual_pred['Anomaly_Score']< threshold,0,1)
pd.crosstab(Actual_pred['Actual'],Actual_pred['Pred'])

img
(D) 通过聚合多个模型实现模型稳定性

许多异常检测算法,特别是基于邻近性和基于分布的算法,对异常值非常敏感,并容易过拟合。我们如何生成一个具有稳定结果的模型呢?解决方案是使用一系列超参数训练模型,然后聚合得分。这样可以大大降低过拟合的可能性,并提高预测准确性。PyOD模块提供了四种方法来聚合结果。请记得使用pip install combo安装相关函数。您只需要使用一种方法来生成聚合结果。

与LOF类似,任何基于密度的模型都可能对训练数据过拟合,仅依赖一个模型并不是一个好主意。我将生成5个CBLOF模型,分别使用10、20、30、40和50个最近邻。这些模型的平均预测将作为最终模型的预测结果。

下面的“y_by_average”是对训练数据的5个模型预测结果的平均值,这些结果存储在数据框“train_scores_norm”中。我在图(F)中创建了它的直方图。


from pyod.models.combination import aom, moa, average, maximization
from pyod.utils.utility import standardizer
from pyod.models.cblof import CBLOF

# Standardize data
X_train_norm, X_test_norm = standardizer(X_train, X_test)
# Test a range of clusters from 10 to 50. There will be 5 models.
n_clf = 5
k_list = [10, 20, 30, 40, 50]
# Just prepare data frames so we can store the model results
train_scores = np.zeros([X_train.shape[0], n_clf])
test_scores = np.zeros([X_test.shape[0], n_clf])
train_scores.shape
# Modeling
for i in range(n_clf):
    k = k_list[i]
    cblof = CBLOF(n_clusters = k, contamination=0.05)  
    cblof.fit(X_train_norm)

    # Store the results in each column:
    train_scores[:, i] = cblof.decision_function(X_train_norm) 
    test_scores[:, i] = cblof.decision_function(X_test_norm) 
# Decision scores have to be normalized before combination
train_scores_norm, test_scores_norm = standardizer(train_scores,test_scores)

下面的“Combination”是对训练数据的5个模型预测结果的平均值,这些结果存储在数据框“train_scores_norm”中。我在图(F)中创建了它的直方图。


# Combination by average
# The test_scores_norm is 500 x 5. The "average" function will take the average of the 5 columns. The result "y_by_average" is a single column: 
y_train_by_average = average(train_scores_norm)
y_test_by_average = average(test_scores_norm)
import matplotlib.pyplot as plt
plt.hist(y_train_by_average, bins='auto') # arguments are passed to np.histogram
plt.title("Combination by average")
plt.show()

img
图(D): 训练数据平均预测值的直方图

图(D)显示大部分分数都低于0.2。如果我将阈值设为1.0,它仍然可以很好地将异常值与正常数据分离。我在表(D)中生成了摘要统计表。它确定了25个数据点为异常值。与表(C.3)类似,异常值组的特征均值都小于正常组的特征均值。

descriptive_stat_threshold(X_train,y_train_by_average, 1)

img
表格(D)

(F) CBLOF摘要

  • 基于聚类的局部异常因子(CBLOF)将异常定义为与附近聚类的局部距离的组合,以及数据点所属聚类的大小。

  • CBLOF首先将数据点聚类为大型或小型聚类。然后识别小型聚类中的数据点作为局部异常值。局部异常值可能不是一个单独的点,而是一个小组孤立点。

  • 如果数据点属于大型聚类,则异常值分数是其聚类质心的距离乘以聚类中的数据点数。

  • 如果数据点属于小型聚类,则异常值分数是其最近大型聚类质心的距离乘以数据点所属小型聚类的数据数。

参考文献

  • [1] He, Z., Xu, X., & Deng, S. (2003). Discovering cluster-based local outliers. Pattern Recognit. Lett., 24, 1641–1650.

异常检测系列文章导航

异常检测系列:异常检测基本介绍

异常检测系列:Histogram-based Outlier Score_HBOS算法

异常检测系列:基于经验累积分布的异常检测(ECOD)

异常检测系列:孤立森林(Isolation Forest)模型介绍

异常检测系列:主成分分析PCA异常值分数检测

异常检测系列:支持向量机(SVM)异常检测介绍

异常检测系列:单类支持向量机(OCSVM)

异常检测系列:高斯混合模型(GMM)

异常检测系列:局部异常因子(LOF)

异常检测系列:K最近邻算法KNN

异常检测系列:基于聚类的局部异常因子(CBLOF)

异常检测系列:基于极限梯度提升的异常检测(XGBOD)

异常检测系列:AutoEncoder模型介绍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数智笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值