异常检测系列:Histogram-based Outlier Score

小明 2025-04-28 04:01:32 4

考虑多���数据,就像Excel电子表格中的数据框一样。列是维度或变量,行是观察结果。一个观察结果有多个值。变量的计数统计称为直方图。如果有N个变量,就会有N个直方图。如果一个观察结果的值落在直方图的尾部,那么这个值就是异常值。如果一个观察结果的许多值都是异常值,那么这个观察结果很可能是异常值。

列也被称为变量。通过所有的观察结果,我们可以为每个变量计算一个称为直方图的计数统计。如果一个观察结果的值落在直方图的尾部,那么这个值就是异常值。通常情况下,某些观察结果的值在相应的变量方面是异常值,但是某些值是正常的。如果一个观察结果的许多值都是异常值,那么这个观察结果很可能是异常值。

基于这种直觉,可以使用变量的直方图来定义变量的单变量异常值分数。一个观察结果应该有N个单变量异常值分数。该技术假设变量之间是独立的,以推导直方图和单变量异常值分数。一个观察结果的N个单变量异常值分数可以加总成为基于直方图的异常值分数(HBOS)。尽管这个假设听起来很强,但HBOS在实际情况中证明了其有效性。

(A) HBOS是如何工作的?

HBOS独立地为所有的N个变量构建直方图。箱子的高度用于衡量“异常性”。大多数观察结果属于高频箱子,而异常值属于低频箱子。单变量异常值分数被定义为箱子高度的倒数。

HBOS被正式定义为N个变量的对数单变量异常值分数的总和:

在上述方程中,hist_i§是变量i所属的柱状图的高度,其中观测值p属于,而*1/hist§*是单变量异常值分数。这个定义将为异常值分配一个较大的值。

如果一个变量是分类变量,那么直方图就是按类别计数。如果一个变量是数值型的,它应该首先被离散化成等宽的箱子,以得到计数统计量。每个直方图的最大高度被归一化为1.0。这样可以确保所有单变量分数可以等量地求和得到HBOS。

(B) 基于分布的算法可以很快

在前面的章节中,我提到异常检测算法可以是基于接近度、基于分布或基于集成的方法。基于分布的方法使用概率分布拟合数据来获取异常值分数。基于分布的方法的计算时间通常比基于接近度或基于集成的方法的时间短。基于接近度的方法可能耗时,因为它需要计算任意两个数据点之间的距离。因此,它是一个适合数据科学项目的建模候选方法。

© 建模过程

在本书中,我应用图©中的过程来帮助您开发模型,评估模型性能并展示模型结果。它们是(1) 模型开发,(2) 阈值确定,和(3) 对正常和异常组进行分析。

图(C):建模过程(作者提供的图像)

在大多数情况下,我们没有已验证的异常值来进行监督学习建模。由于我们没有已知的异常值,我们甚至不知道人群中异常值的百分比。好消息是异常值分数已经衡量了观测值与正常数据之间的偏差。如果我们为异常值分数派生直方图,我们可以发现这些观测值并确定异常值的百分比。因此,在第一步中,我们开发模型并分配异常值分数。在第二步中,我们将异常值分数绘制在直方图中,然后选择一个值,称为阈值,将正常观测值与异常观测值分开。阈值还确定了异常组的大小。

我们如何评估无监督模型的可靠性?如果一个模型能够有效地识别训练数据中的异常值,那么这些异常值应该表现出异常性的特征。它们在这些变量方面应该与正常数据非常不同。在第三步中,我们将对正常组和异常组进行描述性统计,以证明模型的可靠性。两组之间的变量的描述性统计(如均值和标准差)证明了模型的可预测性。

描述性统计表是评估模型是否与任何先前知识一致的合理指标。如果一个变量在异常组中预期较高或较低,但结果与直觉相悖,您应该调查、修改或删除该变量,并重新进行建模。最终版本的模型应该提供与任何先前知识一致的描述性统计表。

(C.1) 第一步 - 构建模型

与之前一样,我将使用PyOD的实用函数generate_data()生成百分之十的异常值。为了使案例更有趣,数据生成过程(DGP)将创建六个变量。虽然这个模拟数据集有目标变量Y,但无监督模型只使用X变量。Y变量仅用于验证。我将异常值的百分比设置为5%,即“contamination=0.05”。

前五条记录如下所示:

让我在散点图中绘制前两个变量。黄色点是异常值,紫色点是正常数据点。

让我们构建我们的第一个HBOS模型。HBOS的一个重要超参数是箱子的数量。HBOS对直方图的箱宽敏感,我们将在后面讨论这个超参数。该模型假设箱子的数量为在这个模型中,我们还假设污染率为5%,因为我们在数据中生成了5%的异常值。如果我们不指定它,那么默认值为10%。

一旦训练完成,该模型还会为训练数据中的观测值计算异常值分数。如果我们按升序对观测值进行排序,那些高于阈值的观测值就是异常值。这个阈值是由参数contamination=0.05确定的,意味着5%的数据将是异常值。模型计算训练数据的阈值并存储在HBOS.threshold_中。

我们将使用PyOD的decision_function()函数为训练和测试数据生成异常值分数。我们还将使用predict()函数来预测观测值是否为异常值。predict()函数将异常值分数与阈值hbos.threshold_进行比较。如果异常值分数高于阈值,函数将为观测值分配“1”,否则为“0”。我编写了一个简短的函数count_stat()来显示预测的“1”和“0”值的计数。

from pyod.models.hbos import HBOS
n_bins = 50
hbos = HBOS(n_bins=n_bins,contamination=0.05)
hbos.fit(X_train)
# Training data
y_train_scores = hbos.decision_function(X_train)
y_train_pred = hbos.predict(X_train)
# Test data
y_test_scores = hbos.decision_function(X_test)
y_test_pred = hbos.predict(X_test) # outlier labels (0 or 1)
# Threshold for the defined comtanimation rate
print("The threshold for the defined comtanimation rate:" , hbos.threshold_)
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))

(C.2) 第二步 — 确定一个合理的阈值

在大多数情况下,我们不知道异常值的百分比。我们如何在无监督学习模型中识别异常值?因为异常值分数已经衡量了数据点与其他数据点的偏差,我们可以按照它们的异常值分数对数据进行排序。那些具有较高异常值分数的数据点可能是异常值。我们可以根据异常值分数确定一个阈值,将异常值与正常数据分开。

图(C.2)展示了异常值分数的直方图。我们可以选择一种更保守的方法,通过选择一个较高的阈值,这将导致异常值组中的异常值更少,但希望更精细。直方图建议选择5.5作为阈值。阈值的选择还决定了人群中异常值的百分比。

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

图(C.2)

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

这一步描述了正常组和异常组的特征。异常值的特征应该与正常数据非常不同。描述性统计表成为证明模型的可靠性的一个好指标。

在表(A)中,我展示了正常组和异常组的计数和计数百分比。在我们的案例中,有两个被标记为“特征0”和“特征1”的特征。 “异常分数”是平均异常分数。提醒您在有效的展示中使用特征名称标记特征。该表告诉我们几个重要的结果:

  • 异常组的大小: 异常组约占总体的8.4%。请记住,异常组的大小由阈值决定。如果您选择更高的阈值,大小将会缩小。

  • 平均异常分数: 异常组的平均HBO分数远高于正常组的分数(5.836 > -0.215)。这个证据只是验证了异常组中的数据是异常值。此时,您不需要对分数进行过多的解释。

  • 每个组中的特征统计: 表(A)显示,异常组的特征‘0’和特征‘1’的值比正常组的值要小。在业务应用中,您可能期望异常组的特征值要么高于正常组,要么低于正常组。因此,特征统计有助于理解模型结果。

    threshold = hbos.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'] 
    

    由于我们在数据生成过程中有真实值y_test,我们可以生成一个混淆矩阵来了解模型的性能。数据生成过程产生了50个异常值(500 x 10% = 50)。该模型在阈值为4.0时识别出了50个中的42个。

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

    descriptive_stat_threshold(X_test,y_test_scores, threshold)
    

    def confusion_matrix(actual,score, threshold):
        Actual_pred = pd.DataFrame({'Actual': actual, 'Pred': score})
        Actual_pred['Pred'] = np.where(Actual_pred['Pred']
The End
微信