实数评估简介
在开发异常检测系统时,若能有一种方法在系统开发过程中对其进行评估,就能更快速地做出决策、调整系统并实现改进。比如在选择不同特征、尝试不同参数时,若有评估方法,就能更轻松地决定是否对算法进行更改,这种评估有时被称为实数评估,即通过计算一个数值来判断算法改进与否。
数据假设与应用
在实际的异常检测中,虽然主要讨论无标签数据,但假设存在一些标签数据(包含少量先前观察到的异常)是很有帮助的。以制造飞机引擎为例,多年来可能会收集到大量正常引擎数据和少量异常引擎数据,将异常标记为 y = 1,正常标记为 y = 0。训练异常检测算法的训练集通常被视为正常数据(y = 0),即使少量异常数据混入训练集,一般也不影响算法评估。
数据集的划分方法
- 当有一定数量的数据时,可将数据集划分为训练集、交叉验证集和测试集。例如,假设有 10000 个正常引擎数据和 20 个异常引擎数据,可将 6000 个正常引擎放入训练集;2000 个正常引擎和 10 个异常引擎放入交叉验证集;另外 2000 个正常引擎和 10 个异常引擎放入测试集。在训练集上训练算法并拟合高斯分布后,在交叉验证集上观察算法正确标记异常引擎的数量,还可通过交叉验证集调整参数 absalon,使其在可靠检测异常的同时,避免将过多正常引擎误标记为异常。最后在测试集上评估算法性能,查看找到的异常引擎数量以及误判的情况。
- 另一种方法是不使用测试集,仅使用训练集和交叉验证集。如在上述例子中,仍在 6000 个正常引擎上训练,将剩余 4000 个正常引擎和所有异常引擎放入交叉验证集,然后调整参数和特征以优化算法在该交叉验证集上的表现。当异常数据极少(如仅有 2 个异常引擎)时,这种方法比较合理,因为没有足够数据创建独立的测试集。但该方法的缺点是无法公平判断算法在未来数据上的表现,且存在过度拟合交叉验证集的风险,导致实际性能可能不如预期。
在交叉验证集或测试集上评估算法
先在训练集(如 6000 个好引擎的例子)上拟合模型 PFX,然后对于交叉验证或测试的例子 X,计算 PMX。若 PMX 小于某个值(如 E),预测 y = 1(异常);若 PMX 大于等于参数,预测 y = 0(正常)。通过比较算法在交叉验证或测试集上的预测结果与实际标签,可评估算法的准确性。在异常检测应用中,数据分布通常是偏斜的,即异常数量(正例)远小于正常数量(负例)。此时,可参考之前课程中关于处理高偏斜数据分布的内容,计算真阳性、假阴性、真阴性等指标,或计算 F1 分数等分类准确性的替代指标,以更好地评估算法在大量正常数据中检测少量异常数据的性能。
算法选择问题
如果数据集中有一些有标签的例子,可以使用无监督学习算法,也可以将这些有标签的例子用于监督学习,那么如何选择异常检测算法或监督学习算法
笔者注:也就是说异常检测算法并非一定是非监督学习算法。根据工作流的不同,算法也可以是监督算法,半监督算法。
异常检测算法适用情况
- 当正例数量极少(0 到 20 个)且围绕着大量负例时,异常检测算法通常更为合适。因为在构建模型(如 pfx 模型)时,其参数仅从负例中学习,正例仅用于交叉验证集和测试集的评估。
- 若认为存在多种不同类型的异常(正例),例如飞机引擎可能有多种出错方式,且未来可能出现全新的故障情况,而少量正例无法涵盖所有可能的异常,此时异常检测算法更适用。因为异常检测通过观察正常例子(y = 0 的负例)并对其建模,任何与正常情况有较大偏差的都将被标记为异常,包括从未见过的新故障方式。
监督学习算法适用情况
- 如果正例和负例数量都较多,监督学习可能更适用。尽管即使只有 20 个正训练样例也可尝试应用监督学习算法,但异常检测和监督学习看待数据集的方式有很大差异。
- 应用监督学习时,理想状态是有足够多的正例让算法了解正例的特征,并且通常假设未来的正例与训练集中的样例相似。
常见案例
- 金融欺诈检测:存在多种不同的金融欺诈方式,且新的欺诈形式不断涌现。异常检测常用于寻找与过去交易不同的情况,因为难以通过少量正例学习所有可能的欺诈模式;而监督学习可用于发现先前观察到的欺诈形式。
- 垃圾邮件检测:虽然有多种类型的垃圾邮件,但多年来垃圾邮件的内容(如推销类似的东西、引导访问类似网站等)较为相似,未来收到的垃圾邮件也很可能与过去见过的类似。因此,监督学习在检测这类垃圾邮件时效果较好,因为它能检测出与训练集中相似的垃圾邮件类型。
- 制造业缺陷检测:
- 对于发现新的、以前未见过的缺陷(如未来飞机引擎可能出现的全新故障方式),即使训练集中没有此类正例,异常检测可能更有效。
- 若要检测已知的、常见的缺陷(如智能手机制造中外壳偶尔会被拉伸),且能获取足够多的有缺陷样本作为训练例子(标记为正例),则监督学习可用于训练系统判断新手机是否有划痕等缺陷。
- 数据中心机器监控:当机器被黑客入侵时,可能会以全新的、与以往不同的方式表现,这种情况更适合用异常检测来发现。实际上,许多与安全相关的应用,由于黑客不断找到新的入侵方式,常使用异常检测而非监督学习。
- 天气预测:通常只会遇到几种常见的天气情况(如晴天、雨天、下雪等),由于能反复看到相同的标签,所以天气预测往往是一个监督学习任务。
- 疾病诊断:利用病人的症状判断其是否患有某种见过的特定疾病,也倾向于使用监督学习应用。
两种算法的核心区别
异常检测试图找到与以往见过的正例都不同的全新正例;而监督学习是基于已有的正例,判断未来的例子是否与之相似。在构建异常检测系统时,特征的选择非常重要。
特征的选择
特征选择的重要性
- 在构建异常检测算法和监督学习算法时,特征选择都很关键。在监督学习中,即使特征不太准确或包含一些无关特征,由于算法有监督信号(足够的标签),它能够判断忽略哪些特征,以及如何重新调整特征以充分利用输入的特征。
- 对于仅从无标签数据中学习的异常检测算法来说,算法难以自行判断应忽略哪些特征,因此仔细选择特征对异常检测算法而言比监督学习更为重要。
使特征符合高斯分布的方法
- 为了帮助异常检测算法更好地运行,应尽量确保输入的特征或多或少符合高斯分布。如果特征不符合高斯分布,可以对其进行变换使其更接近高斯分布。
- 具体操作是,对于特征X,可以通过绘制直方图(使用 Python 命令
PLT
)来观察其分布情况。若分布近似高斯分布(呈现对称的钟形曲线),则该特征是区分异常和正常例子的良好候选特征;若分布不像高斯分布,可以尝试对特征进行变换。 - 例如,计算
,若
的直方图更接近高斯分布,就可以用
代替原来的特征X。还可以使用
(C为常数)、取平方根(
)、取立方根(
)等公式对特征进行变换。在实践中,通过尝试不同的C值,选择使分布更接近高斯分布的值。
特征变换的具体示例及操作
- 以一个特征X为例,先绘制其直方图,根据情况增加直方图的箱子数(如增加到 50 个),还可以改变颜色。然后尝试不同的变换,如绘制
(X的平方根)、
、
的直方图,观察其分布情况,若某个变换后的分布更接近高斯分布,就可以用变换后的特征(如
)代替原来的X,并在训练过程中使用变换后的值。
- 当对X取对数(
)时,如果X存在等于0的值,会出现错误,因为
无定义。此时常见的技巧是给X加上一个非常小的数(如0.01)使其变为非负数,再绘制
的直方图,并通过调整参数使分布更对称、更接近高斯分布。
- 虽然机器学习文献中有自动测量分布与高斯分布接近程度的方法,但在实践中,通过尝试几个值并选择看起来合适的值,通常就能满足实际需求。可以在 Jupiter Notebook 中进行尝试,选择使数据更符合高斯分布的变换。
特征变换的注意事项及错误分析
- 无论对训练集应用何种变换,都要记得对交叉验证集和测试集的数据应用相同的变换。在训练异常检测算法后,确保数据近似高斯分布。如果算法在交叉验证集上表现不佳,可以进行错误分析。
- 异常检测算法期望正常例子的P(X)大于等于阈值,异常例子的P(X)小于。常见问题是对于正常和异常例子,P(X)的值都较大,例如在数据集中,一个异常例子在交叉验证集或测试集中,但由于其与训练集中的其他例子相似,导致P(X)较大,算法无法将其标记为异常。
- 遇到这种情况,可以查看该例子,找出使其被认为是异常的原因,即使某个特征(如
)的值与其他训练样本相似,若能识别出有助于区分该例子与正常例子的新特征(如
),添加这个特征可以提高算法性能。比如在检测欺诈行为时,若
是交易数量,某个用户的交易数量与其他人相似,但发现其打字速度异常快(新特征
),使用
和
绘制数据,能使异常检测算法更容易识别出该异常用户。
异常检测系统的开发过程及示例
- 异常检测系统的开发过程通常是先训练一个模型,然后查看交叉验证集中算法未能检测到的异常例子,分析这些例子以激发新特征的创建,使算法能够识别出这些例子在新特征上的异常值,从而成功将其标记为异常。
- 以监控数据中心计算机的异常检测系统为例,初始特征可以选择内存使用(
)、每秒磁盘操作数量(
)、CPU 负载(CPLO)和网络流量等。训练算法后,可能发现它能检测到一些异常,但无法检测到其他异常。此时,可以通过组合旧特征创建新特征,例如当发现某台计算机行为奇怪,其 CPU 负载高但网络流量低(与正常情况不同,正常情况下流视频的数据中心计算机可能是高 CPU 负载和高网络流量,或低 CPU 负载和无网络流量),可以创建新特征X(如CPLO与网络流量的比率,或CPLO的平方除以网络流量),帮助算法检测出类似的异常机器。通过尝试不同的特征选择,使正常例子的(P(X))较大,而交叉验证集中异常例子的(P(X))较小。
python代码简单示例
import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.svm import OneClassSVM
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt# 生成示例数据
X, _ = make_blobs(n_samples=300, centers=1, random_state=42)
# 随机添加一些异常点
anomaly_indices = np.random.choice(len(X), 20, replace=False)
X[anomaly_indices] += np.random.normal(loc=10, scale=2, size=X[anomaly_indices].shape)# 无监督异常检测:孤立森林
# 初始化孤立森林模型
clf_isolation = IsolationForest(contamination=0.1)
# 训练模型
clf_isolation.fit(X)
# 预测
y_pred_isolation = clf_isolation.predict(X)# 监督异常检测:单类支持向量机
# 划分训练集和测试集
X_train, X_test = train_test_split(X, test_size=0.2, random_state=42)
# 初始化单类支持向量机模型
clf_svm = OneClassSVM(nu=0.1)
# 训练模型
clf_svm.fit(X_train)
# 预测
y_pred_svm = clf_svm.predict(X_test)# 可视化孤立森林的结果
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.scatter(X[:, 0], X[:, 1], c=y_pred_isolation, cmap='viridis')
plt.title('Isolation Forest Anomaly Detection')# 可视化单类支持向量机的结果
plt.subplot(1, 2, 2)
plt.scatter(X_test[:, 0], X_test[:, 1], c=y_pred_svm, cmap='viridis')
plt.title('One - Class SVM Anomaly Detection')plt.show()
代码中需要注意的点
孤立森林基本原理
孤立森林基于这样一个假设:异常数据点在数据空间中是稀疏的,因此与正常数据点相比,它们更容易被孤立出来。该算法通过构建多个随机的二叉树(即孤立树)来对数据进行划分,每个二叉树都是独立地对数据进行随机划分,直到每个数据点都被孤立在一个叶子节点上。在这个过程中,异常数据点由于其稀疏性,通常会更快地被孤立,即它们在树中的路径长度会更短。
算法流程
- 样本采样:从原始数据集中随机抽取一部分样本作为训练集,用于构建孤立树。采样的目的是为了减少计算量,同时也能保证算法的效率。
- 构建孤立树:对于每个采样得到的训练集,构建一棵孤立树。在构建树的过程中,随机选择一个特征和该特征上的一个分割点,将数据划分为两部分,分别递归地构建左右子树,直到每个数据点都被孤立在一个叶子节点上。
- 计算路径长度:对于每个数据点,计算它在所有孤立树中的平均路径长度。路径长度越短,说明该数据点越容易被孤立,越有可能是异常点。
- 异常评分:根据数据点的平均路径长度,计算其异常评分。通常,异常评分的范围在 0 到 1 之间,评分越接近 1,表示该数据点越有可能是异常点;评分越接近 0,表示该数据点越有可能是正常点。
特点
- 计算效率高:孤立森林的时间复杂度为
,其中 n 是数据点的数量。这使得它在处理大规模数据集时具有较高的效率。
- 不需要标记数据:作为一种无监督学习算法,孤立森林不需要事先知道数据集中哪些是正常点,哪些是异常点,只需要输入原始数据即可进行异常检测。
- 对异常数据敏感:由于其基于孤立数据点的思想,孤立森林对异常数据非常敏感,能够有效地识别出数据集中的异常点。
- 可扩展性强:孤立森林可以很容易地扩展到高维数据集,并且在处理高维数据时也能保持较好的性能。