快速潜入Python代码
在异常检测的领域中,寻找隐藏的非正常情况类似于在广阔的数据景观中寻找隐藏的珍宝。然而,即使你有最先进的异常检测算法和技术,还是需要做出一个关键的决定:在何处划定异常和正常的界限?
欢迎来到迷人的选择异常检测阈值的世界,这是异常检测过程中至关重要的一步。阈值决定了什么被标记为异常,什么保持在正常范围内。找到这种微妙的平衡是发现有价值的见解的关键,从发现欺诈交易到识别设备故障等等。
但是,就像任何冒险一样,它并非没有挑战。选择正确的阈值需要深入了解你的数据、分析的具体目标,并且需要一些直觉。这个决定可以决定是否完全释放异常检测的潜力,或者淹没在一片虚假警报中。
怎样才算正常?这个问题是设置异常检测阈值的核心,它是过程中至关重要的一部分,可以决定异常检测系统的成败。
在本文中,我们将探讨一种更简单的选择合适阈值的方法。我们将介绍一种使用直方图中分数分布的方法,让它为你做繁重的工作。这对于提高无监督异常检测的准确性来说是一种改变游戏规则的方法。
直方图切割阈值检测 (HCTT)
通常,在设置异常检测系统时,您需要决定一个数据点是否是异常点,并给出它的分数为0或1。但如果您不确定呢?
让我们暂时跳过复杂的模型构建部分,集中讨论一个常见的问题:大多数博客假设您已经知道数据中有多少异常点。但如果您不知道呢?
这就是有监督和半监督异常检测方法派上用场的地方。它们就像一个安全网,但是有一个注意事项。数据会随着时间的推移而变化,所以你必须时刻关注你的系统并调整阈值当事情发生变化时。这是许多博客忽视的重要步骤,但在现实应用中是必不可少的。
让我带着你一起简化为异常检测选择合适的阈值的过程。我们将分解其中的复杂问题,并向您展示如何适应数据的变化。找到完美的阈值就像在迷宫中找到出路,而我在这里为您提供指引。
这种方法很简单,一旦你根据相应的数据调整对应的模型,你将始终有一种方法来确定异常分数。对于高斯分布可以是概率的对数,对于K-Means来说可以是与质心的距离,等等。但是在这里,问题是你现在有一个分数,你需要根据整个组来确定这个值是否有意义,这就是我们要处理的。为此,我们将把分布图分为100个箱子,你期望这个直方图将反映每个累积数据的1%的变化,这就是我们要选择相应阈值的地方。

通过分析直方图,你能看出我们的方向吗?你能根据直方图分布建议一个阈值吗?我们可以在分数箱子有明显断裂的时候确定一个阈值。然而,挑战在于将其整合到算法函数中。我们需要定义一个主要参数或两个可选参数,这些参数可以基于你的观察洞察来确定。所选择的参数应该在设置得当的情况下对变化是稳健的。这些参数可以是百分差异和数据累积分布(可选)。

百分比差异切割:这是一个直方图中一个柱状桶与下一个之间的累积比例差异。请注意,顺序可能会因算法的使用而有所不同。例如,如果您正在考虑高斯混合并使用对数概率,则切割应从右向左执行。另一方面,如果您正在使用K均值,并测量到质心的距离,则应从左到右处理柱状桶,将左侧视为负值,右侧视为正值。设置为3-6%之间通常可以获得稳健的结果。这是添加新数据不会显著改变先前累积分布的点,因此我们可以决定进行切割。在此点之后映射的任何数据都不会与大多数数据相对应。
累积数据分布(可选):此参数主要基于直观选择主要用于提醒的值。它可以设置为您允许算法检测到的异常值比例的上限。例如,如果您不希望超过20%的数据被标记为异常值,可以将其作为安全双重检查。
正如您所见,此算法简单直观。我决定分享这种方法,因为我找不到任何其他可转移的检测方法,无论使用的模型如何。我相信其他人也可能会发现这种方法对他们的目的有所帮助。
让我们试试看,看看它的表现如何!
行动!
示例1:高斯混合
让我们从使用高斯混合模型开始。我们将使用PyOD库创建异常观察。最初,我们将生成的数据的异常值比例设置为15%。这是我们希望使用提议的算法来识别的比例。
import numpy as npimport pandas as pdimport matplotlib.pyplot as pltfrom pyod.utils.data import generate_datafrom sklearn.mixture import GaussianMixturecontamination = 0.15 # 异常值的百分比n_train = 1000 # 训练点数n_test = 100 # 测试点数n_features = 6 # 特征数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)X_train_pd = pd.DataFrame(X_train)X_train_pd.head()

通过数据我们已经知道哪些是异常值(y=1),哪些不是(y=0)。有了这个,我们可以测试它是否有效。首先我们调整高斯混合模型
df_model = X_train_pdgm = GaussianMixture(n_components = 1, covariance_type = 'full', random_state=0)gm.fit(df_model)df_model['log'] = gm.score_samples(df_model)
现在我们绘制直方图来检查每个值的对数概率

由于我们观察到-5处的明显偏移,我们可以推断出-5左侧的任何值都可能是异常值。现在,让我们将我们的切割算法付诸实践。我们将将百分比差异切割设置为0.005,将累积数据分布设置为80%。
perc_dif = 0.0005cum_perc = 0.8df_histogram_change = df_model['log'].value_counts(bins=100).sort_index(ascending=False)df_histogram_change = pd.DataFrame(df_histogram_change)df_histogram_change['cumsum'] = df_histogram_change['log'].cumsum()df_histogram_change['index'] = range(len(df_histogram_change))df_histogram_change['cumsum_diff'] = df_histogram_change['cumsum'].pct_change()df_histogram_change['interval'] = df_histogram_change.indexdf_histogram_change['cum_pct'] = df_histogram_change['cumsum']/sum(df_histogram_change['log'])df_histogram_changefilter_df = df_histogram_change[(df_histogram_change['cumsum_diff']<perc_dif) & (df_histogram_change['cum_pct']>cum_perc)]index = filter_df.iloc[0]['index']tresh = df_histogram_change.index[index].leftdf_model = df_model.copy()df_model['outlier'] = np.where(df_model ['log']<tresh,1,0)
包含我们感兴趣的变量的对应的直方图数据帧

注意,“cumsum”表示直方图图表上从右到左的累积和。“cumsum_diff”指的是区间之间的差异,“cum_pct”表示数值占全部数值的百分比。
现在我们想要用这个方法确定阈值为0.15(15%)
df_cases = df_model['outlier'].value_counts()df_cases[1]/sum(df_cases)#0.15
我们可以看到估计值和实际值之间的差异为0。
示例2:时间序列上的K-means
让我们根据相关博客文章中概述的方法改进我们的方法,该方法使用K-means分析时间序列数据中的异常情况。然而,我们可以看出在选择阈值方面还有改进的空间。
from sklearn.cluster import KMeansfrom numpy import sqrt, random, array, argsortfrom sklearn.preprocessing import scaleimport matplotlib.pyplot as plt
让我们生成相应的数据
random.seed(123)def makeData(N): x = [] for i in range(N): a = i/1000 + random.uniform(-3, 2) r = random.uniform(-5, 10) if(r >= 9.9): r = r + 10 elif(r<(-4.8)): r = r +(- 10) x.append([a + r]) return array(x)x = makeData(500)
让我们绘制它
x_ax = range(500)plt.title("时间序列")plt.xlabel('时间')plt.ylabel('值')plt.plot(x_ax, x)plt.show()

让我们应用K-means模型并对数据集进行缩放以使用该方法
x = scale(x)kmeans = KMeans(n_clusters = 1).fit(x)center = kmeans.cluster_centers_distance = sqrt((x - center)**2)df = pd.DataFrame(x)df['distance'] = distance
绘制得分的相应直方图
import seaborn as snsplt.figure( figsize=(10, 6), # 设置图表大小 dpi=100, # 设置dpi(或分辨率))sns.histplot(data=df, x="distance", bins=100)# 设置副标题plt.title( "对数的直方图", # 副标题文本 fontsize=14 # 设置字体大小)

需要注意的是,该方法的设计是从左到右操作的,因为得分的大小对应于距离中心的更大距离。理解这个基本方面对于应用该方法至关重要。
df_histogram_change = df['distance'].value_counts(bins=100).sort_index(ascending=True)df_histogram_change = pd.DataFrame(df_histogram_change)df_histogram_change['cumsum'] = df_histogram_change['distance'].cumsum()df_histogram_change['index'] = range(len(df_histogram_change))df_histogram_change['cumsum_diff'] = df_histogram_change['cumsum'].pct_change()df_histogram_change['interval'] = df_histogram_change.indexdf_histogram_change['cum_pct'] = df_histogram_change['cumsum']/sum(df_histogram_change['distance'])df_histogram_changefilter_df = df_histogram_change[(df_histogram_change['cumsum_diff']<0.0005) & (df_histogram_change['cum_pct']>0.8)]index - filter_df.iloc[0]['index']tresh = df_histogram_change.index[index].leftdf = df.copy()df['outlier'] = np.where(df['distance']>tresh,1,0)#tresh 1.99
阈值为1.99,超过这个值的所有数据将被视为异常值
indexes = df[df['outlier']==1].indexvalues = df.loc[indexes,0]
现在我们来绘制异常值,咔嚓!就像魔法一样

在异常检测的动态领域中,区分正常数据和异常数据至关重要,而异常检测阈值的设置过程则处于中心位置。本篇博客深入剖析了这个决策制定的复杂性,强调了它在发现欺诈交易到识别设备故障等方面发现有价值洞察的关键作用。
为了认识到选择正确阈值所面临的挑战,本文介绍了直方图剪切检测(Histogram Cut Detection,HCT)方法作为一种具有改变游戏规则意义的解决方案。HCT通过利用直方图中的分数分布来简化阈值设置过程,提供了一种更加直观和准确的无监督异常检测方法。
该方法通过高斯混合模型和K-Means模型等示例演示,将分布图分成100个箱,每个箱反映了积累数据的1%变化。这种细粒度的分析允许根据得分箱的明显断开选择阈值。
本文介绍了关键参数,如百分比差异和数据累积分布,以增强算法对变化的适应性和稳健性。这些参数为阈值确定添加了直观感,确保算法随时间保持有效。
在实际应用中,本文展示了该方法的简易性和有效性,并鼓励读者在其特定异常检测场景中测试其性能。所演示的例子说明了该方法适用于各种模型,并凸显了其广泛使用的潜力。
总而言之,直方图剪切检测方法提供了一种简单、直观和可转移的异常检测阈值设置方法。本博客旨在使其他领域的人士受益,并鼓励读者在自己的异常检测工作中探索和实施此方法。