在数据分析和机器学习中,区分数据中的正常观察值和异常观察值是一项重要任务。这种区分通常用于数据清洗和异常检测,以提高模型的准确性和鲁棒性。
异常值检测:异常值检测旨在识别那些与训练数据集中大多数观察值显著不同的观察点。这些异常值被视为远离数据集中其他点的离群点。异常值检测模型专注于拟合数据的密集区域,同时识别并忽略那些偏离正常范围的异常观察值。
新颖性检测:新颖性检测关注的是识别那些在训练数据中未出现过的新观察值,这些观察值可能代表新的趋势或异常模式。新颖性检测假设训练数据是干净的,目标是发现那些与已知模式显著不同的新观察值。在这种情况下,异常值也被称作新颖性。
异常值检测和新颖性检测都用于异常检测,即对异常或不寻常的观察值进行检测。在这种情况下,异常值检测也被称为无监督异常检测,新颖性检测为半监督异常检测。在异常值检测的背景下,异常值/异常不能形成密集的簇,因为现有的估计器假设异常值/异常位于低密度区域。相反,在新颖性检测的背景下,新颖性/异常可以形成密集的簇,只要它们在训练数据的低密度区域,在这个背景下被视为正常的。
Scikit-learn 提供了多种机器学习工具,可用于新颖性或异常值检测。这些工具通过无监督方式从数据中学习,以识别异常值。
训练模型
estimator.fit(X_train)
预测新观察值
新观察值可以通过预测方法被分类为内部点或异常值:
estimator.predict(X_test)
其中,内部点被标记为 1
,异常值被标记为 -1
。预测方法基于估计器计算的原始评分函数,并使用 score_samples
方法获取这个评分。阈值可以通过 contamination
参数来控制。
决策函数
decision_function
方法也是基于评分函数定义的,以这样一种方式,负值表示异常值,非负值表示内部点:
estimator.decision_function(X_test)
使用 LocalOutlierFactor 进行新颖性检测
estimator.fit_predict(X_train)
neighbors.LocalOutlierFactor
默认不支持predict
、decision_function
和score_samples
方法,只支持fit_predict
方法,因为这个估计器最初是用于异常值检测的。训练样本的异常分数可以通过negative_outlier_factor_
属性访问。如果真的想使用neighbors.LocalOutlierFactor
进行新颖性检测,即预测标签或计算未见数据的异常分数,可以在拟合估计器之前将novelty
参数设置为True来实例化估计器。在这种情况下,fit_predict
是不可用的。
警告
“使用局部异常因子(Local Outlier Factor)进行新颖性检测时,只有当
novelty
设置为True时,才能在未见过的新区数据上使用predict
、decision_function
和score_samples
方法,而不能在训练样本上使用,因为这会导致错误的结果。即,predict
的结果不会与fit_predict
相同。训练样本的异常分数始终可以通过negative_outlier_factor_
属性访问。
局部异常因子(neighbors.LocalOutlierFactor
)的行为总结如下表所示:
方法 | 异常值检测 | 新颖性检测 |
---|---|---|
fit_predict | 可用 | 不可用 |
predict | 不可用 | 仅在新数据上使用 |
decision_function | 不可用 | 仅在新数据上使用 |
score_samples | 使用negative_outlier_factor_ | 仅在新数据上使用 |
negative_outlier_factor_ | 可用 | 可用 |
注意
“当使用局部异常因子进行新颖性检测时,应该仅对新区数据使用
predict
、decision_function
和score_samples
方法。对于训练数据,应该使用negative_outlier_factor_
属性来获取异常分数。如果novelty
参数设置为True,fit_predict
方法将不可用。
在scikit-learn中的异常值检测算法比较。局部异常因子(Local Outlier Factor LOF)没有显示黑色的决策边界,因为它没有用于新数据的预测方法,当它用于异常值检测时。
ensemble.IsolationForest
和neighbors.LocalOutlierFactor
在此处考虑的数据集上表现合理。svm.OneClassSVM
已知对异常值敏感,因此在异常值检测中表现不佳。即便如此,在高维或没有任何关于内部数据分布假设的情况下进行异常值检测是非常具有挑战性的。svm.OneClassSVM
仍可与异常值检测一起使用,但需要微调其超参数以处理异常值并防止过拟合。linear_model.SGDOneClassSVM
提供了一个线性One-Class SVM的实现,其复杂度与样本数量成线性关系。这里使用该实现和核近似技术来获得类似于默认使用高斯核的svm.OneClassSVM
的结果。最后,covariance.EllipticEnvelope
假设数据是高斯分布,并学习一个椭圆。
示例
svm.OneClassSVM
、 ensemble.IsolationForest
、 neighbors.LocalOutlierFactor
和covariance.EllipticEnvelope
。metrics.RocCurveDisplay
的ROC曲线评估异常值检测估计器、neighbors.LocalOutlierFactor
和 ensemble.IsolationForest
。新颖性检测是识别数据集中新观察结果是否与现有数据来自同一分布的过程。这项技术用于确定新观察结果是否足够独特,以至于可以被视为异常,或者它们是否与现有数据集相似到无法区分。
在实践中,新颖性检测通常涉及学习一个近似的决策边界,该边界围绕现有数据的分布。如果新的观察结果落在这个边界定义的子空间内,则被认为是正常的;如果它们落在边界之外,则被认为是异常的。
One-Class SVM 是一种流行的新颖性检测方法,由 Schölkopf 等人提出。在 scikit-learn 中,它通过 svm.OneClassSVM
类实现。这种方法需要选择一个核函数和一个参数来定义决策边界。径向基函数(RBF)核是常用的选择,尽管确定其带宽参数(gamma
)没有确切的公式,通常依赖于经验设置或交叉验证。
One-Class SVM 的 nu
参数定义了边界的宽度,对应于新观察结果落在边界外的概率。在 scikit-learn 中,nu
的默认值通常足以处理大多数情况。
参考文献
示例
svm.OneClassSVM
对象围绕一些数据学习到的边界扩展单类SVM
在linear_model.SGDOneClassSVM
中实现了单类SVM的在线线性版本。这种实现与样本数量成线性关系,允许通过核近似技术来近似解决核化版本的svm.OneClassSVM
,后者在最理想情况下的复杂度是样本数量的二次方。更多详情请参见 Online One-Class SVM 部分。
示例
linear_model.SGDOneClassSVM
和核近似的单类SVM的解决方案。异常值检测的目标是识别并区分出那些偏离正常数据模式的观察值,这些观察值通常被称为异常值。与新颖性检测不同,异常值检测并不依赖于一个已知的正常观察群体的数据集来进行工具的训练。
拟合椭圆包络一种常见的异常值检测方法是假设正常数据遵循某种已知分布(例如,假设数据遵循高斯分布)。基于这个假设,我们可以尝试定义数据的“形状”,并将那些远离这个形状的观察值定义为异常。
Scikit-learn提供了一个对象 covariance.EllipticEnvelope
,它对数据进行鲁棒的协方差估计,因此可以拟合中心数据点的椭圆,忽略中心模式之外的点。
例如,假设内部数据是高斯分布的,它将以鲁棒的方式(即不受异常值影响)估计内部数据的位置和协方差。从这个估计中得到的马氏距离用来导出异常程度的度量。下面展示了这种策略。
示例
covariance.EmpiricalCovariance
)或鲁棒估计(covariance.MinCovDet
)的位置和协方差来评估观察值异常程度的差异。参考文献
隔离森林在高维数据集中执行异常值检测的一种有效方法是使用随机森林。ensemble.IsolationForest
通过随机选择一个特征,然后随机选择该特征的最大值和最小值之间的分割值来“隔离”观察结果。
由于递归分区可以由树结构表示,因此隔离一个样本所需的分割次数等同于从根节点到终止节点的路径长度。这个路径长度,在这些随机树的森林中平均后,是正常性的度量,也是决策函数。
随机分区为异常值产生了明显更短的路径。因此,当一个随机树的森林共同为特定样本产生更短的路径长度时,它们很可能是异常值。
ensemble.IsolationForest
的实现基于 tree.ExtraTreeRegressor
的集成。遵循隔离森林原始论文,每棵树的最大深度设置为 ( ),其中 ( ) 是用于构建树的样本数量(详见Liu等人,2008年的更多细节)。
下面展示了这个算法的示意图。
ensemble.IsolationForest
支持warm_start=True
,允许向已经拟合的模型添加更多树:
from sklearn.ensemble import IsolationForest
import numpy as np
X = np.array([[-1, -1], [-2, -1], [-3, -2], [0, 0], [-20, 50], [3, 5]])
clf = IsolationForest(n_estimators=10, warm_start=True)
clf.fit(X) # 拟合10棵树
clf.set_params(n_estimators=20) # 再添加10棵树
clf.fit(X) # 拟合添加的树
示例
ensemble.IsolationForest
与 neighbors.LocalOutlierFactor
、调整为执行异常值检测方法的 svm.OneClassSVM
、linear_model.SGDOneClassSVM
,以及基于协方差的异常值检测covariance.EllipticEnvelope
。参考文献
局部异常因子在适度高维数据集上执行异常值检测的另一种有效方法是使用局部异常因子(LOF)算法。
neighbors.LocalOutlierFactor
(LOF)算法计算一个分数(称为局部异常因子),反映观察结果的异常程度。它衡量给定数据点相对于其邻居的局部密度偏差。其思想是检测那些密度远低于其邻居的样本。
在实践中,局部密度是从k-最近邻居中获得的。一个观察结果的LOF分数等于其k-最近邻居的平均局部密度与其自身局部密度的比率:正常实例的局部密度应与其邻居相似,而异常数据的局部密度预期要小得多。
考虑的邻居数量k
(别名参数n_neighbors
)通常选择为
n_neighbors=20
通常普遍有效。当异常值的比例很高(即大于10%,如下例所示)时,n_neighbors
应该更大(如下例中的n_neighbors=35
)。LOF算法的优势在于它考虑了数据集的局部和全局属性:即使在异常样本具有不同底层密度的数据集中,它也能表现良好。问题不在于样本的孤立程度,而在于相对于周围邻域的孤立程度。
在应用LOF进行异常值检测时,没有predict
、decision_function
和score_samples
方法,只有fit_predict
方法。训练样本的异常分数可以通过negative_outlier_factor_
属性访问。请注意,当LOF应用于新颖性检测时,即当novelty
参数设置为True时,可以在未见数据上使用predict
、decision_function
和score_samples
,但predict
的结果可能与fit_predict
不同。见 Novelty detection with Local Outlier Factor。
这种策略下面有示意图。
示例
neighbors.LocalOutlierFactor
的局部异常因子( Outlier detection with Local Outlier Factor (LOF))异常值检测示例。参考文献
要使用neighbors.LocalOutlierFactor
进行新颖性检测,即预测标签或计算未见数据的异常分数,您需要在拟合估计器之前将新颖性参数设置为True来实例化估计器:
lof = LocalOutlierFactor(novelty=True)
lof.fit(X_train)
请注意,在这种情况下,为了避免不一致,fit_predict
是不可用的。
警告
使用局部异常因子进行新颖性检测
当新颖性设置为True时,请务必注意,您只能在未见过的新区数据上使用predict
、decision_function
和score_samples
,而不能在训练样本上使用,因为这会导致错误的结果。即,predict
的结果将与fit_predict
不同。训练样本的异常分数始终可以通过negative_outlier_factor_
属性访问。
局部异常因子进行新颖性检测的示例如下。
Python需要安装的包有scikit-learn,OpenCV,NumPy,imutils等
pip install numpy
pip install opencv-contrib-python
pip install imutils
pip install scikit-learn
实践中使用的是8Scenes数据集中的一个子集。8Scenes数据集包含8个户外场景类别:海岸、山脉、森林、开阔乡村、街道、城市内部、高楼大厦和高速公路。共有2600张彩色图像,分辨率为256x256像素。此数据集中的所有对象和区域都已完全标记,有超过29,000个对象。
本教程中使用的示例数据集包含了16张森林图片,每张图片都如上图所述。这些图片将被用来训练一个异常检测算法。当输入一张新的图像时,该异常检测算法将根据其分析返回以下两个值之一:
因此,可以将这个模型视为一个用于区分“森林”与“非森林”图像的检测器。模型是基于森林图像训练的,其任务是判断一个新的输入图像是否符合“森林流形”的特征,或者是否是一个异常值/离群值。
为了评估这个异常检测算法的效果,我们准备了3张测试图片,如下图所示:
在这些图片中,只有一张展示的是森林——其他两张分别是高速公路和海滩海岸的场景。如果异常检测流程运作正常,模型应该能够对森林图片标记为1,而对两张非森林图片标记为-1。
在训练机器学习模型以检测异常和离群值之前,需要一个过程来量化和描述输入图像的内容。颜色直方图是一个简单而有效的方法,用于描述图像的颜色分布。对于区分森林与非森林图像的任务,我们可以假设森林图像包含更多的绿色阴影,这一特征可以通过使用 OpenCV 提取颜色直方图来利用。
features.py
实现如下:from imutils import paths
import numpy as np
import cv2
def quantify_image(image, bins=(4, 6, 3)):
# 计算图像的3D颜色直方图并归一化
hist = cv2.calcHist([image], [0, 1, 2], None, bins, [0, 180, 0, 256, 0, 256])
hist = cv2.normalize(hist, hist).flatten()
# 返回直方图
return hist
def load_dataset(datasetPath, bins):
# 获取数据集目录中所有图像的路径,然后初始化图像列表
imagePaths = list(paths.list_images(datasetPath))
data = []
# 遍历图像路径
for imagePath in imagePaths:
# 加载图像并转换为HSV颜色空间
image = cv2.imread(imagePath)
image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# 量化图像并更新数据列表
features = quantify_image(image, bins)
data.append(features)
# 将数据列表作为NumPy数组返回
return np.array(data)
quantify_image
函数接受两个参数:
image
:由OpenCV加载的图像。bins
:在绘制直方图时,x轴作为“bins”,默认设置指定了 4 个色调 bin、6 个饱和度 bin 和 3 个值 bin。load_dataset
函数接受两个参数:
datasetPath
:接受包含图像数据集的目录的路径。bins
:颜色直方图的箱数,传递给quantify_image
方法。训练代码train_anomaly_detector.py
from features import load_dataset
from sklearn.ensemble import IsolationForest
import argparse
import pickle
# 构建参数解析器并解析参数
ap = argparse.ArgumentParser()
ap.add_argument("-d", "--dataset", required=True, help="path to dataset of images")
ap.add_argument("-m", "--model", required=True, help="path to output anomaly detection model")
args = vars(ap.parse_args())
# 加载和量化图像数据集
print("[INFO] 准备数据集...")
data = load_dataset(args["dataset"], bins=(3, 3, 3))
# 训练异常检测模型
print("[INFO] 拟合异常检测模型...")
model = IsolationForest(n_estimators=100, contamination=0.01, random_state=42)
model.fit(data)
# 将异常检测模型序列化到磁盘
f = open(args["model"], "wb")
f.write(pickle.dumps(model))
f.close()
终端执行训练脚本即可:
$ python train_anomaly_detector.py --dataset forest --model anomaly_detector.model
[INFO] 准备数据集...
[INFO] 拟合异常检测模型...
$ ls *.model
anomaly_detector.model
可以看到已经训练得到异常检测模型
测试代码test_anomaly_detector.py
:
from features import quantify_image
import argparse
import pickle
import cv2
# 构建参数解析器并解析参数
ap = argparse.ArgumentParser()
ap.add_argument("-m", "--model", required=True, help="path to trained anomaly detection model")
ap.add_argument("-i", "--image", required=True, help="path to input image")
args = vars(ap.parse_args())
print("[INFO] 加载异常检测模型...")
model = pickle.loads(open(args["model"], "rb").read())
# 加载输入图像,转换为HSV颜色空间,并以与训练时相同的方式量化图像
image = cv2.imread(args["image"])
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
features = quantify_image(hsv, bins=(3, 3, 3))
# 使用异常检测器模型和提取的特征来确定示例图像是否为异常
preds = model.predict([features])[0]
label = "anomaly" if preds == -1 else "normal"
color = (0, 0, 255) if preds == -1 else (0, 255, 0)
# 在原始图像上绘制预测标签文本
cv2.putText(image, label, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2)
# 显示图像
cv2.imshow("Output", image)
cv2.waitKey(0)
加载在上一步中训练的异常检测模型,加载、预处理和量化查询图像,使用异常检测器进行预测,以确定查询图像是正常值还是异常值(即异常值),最后显示结果。
使用脚本对测试图像进行测试:
$ python test_anomaly_detector.py --model anomaly_detector.model --image examples/forest_cdmc290.jpg
[INFO] 加载异常检测模型...
$ python test_anomaly_detector.py --model anomaly_detector.model \
--image examples/highway_a836030.jpg
[INFO] 加载异常检测模型...
$ python test_anomaly_detector.py --model anomaly_detector.model \
--image examples/coast_osun52.jpg
[INFO] 加载异常检测模型...
在本教程中,深入探讨了如何结合计算机视觉技术和 scikit-learn 机器学习库,对图像数据集中的异常和离群值进行有效检测。
异常检测步骤概述:
其他异常检测算法:
除了隔离森林,还有几种其他算法也适用于离群值和异常检测,包括:
这些算法各有优势,选择合适的算法取决于具体的数据特性和业务需求。通过综合比较和实验,可以找到最适合特定问题的异常检测方法。