异常检测¶
设置¶
pip install ydf ucimlrepo scikit-learn umap-learn plotly -U -q
什么是异常检测?¶
异常检测 技术是非监督学习算法,用于识别在数据中显著偏离常规的稀有和不寻常的模式。例如,异常检测可以用于欺诈检测、网络入侵检测和故障诊断,而无需定义异常实例。
使用决策森林进行异常检测是一种简单但有效的技术,适用于表格数据。模型为每个数据点分配一个异常分数,范围从 0(正常)到 1(异常)。决策森林还提供可解释性工具和属性,使得更容易理解和描述检测到的异常。
在异常检测中,标记的示例用于评估模型,而不是用于训练。这些标签确保模型能够检测已知的异常。
我们将在 UCI Covertype 数据集上训练和评估两个异常检测模型,该数据集描述了森林覆盖类型及土地单元的其他地理属性。第一个模型在松树和柳树数据上进行训练。鉴于柳树比松树稀有,该模型在没有标签的情况下区分它们。然后将对第一个模型进行解释,并描述什么构成了松树覆盖类型。
# 加载库
import ydf # 学习异常检测模型
import pandas as pd # 我们使用 Pandas 加载小型数据集。
from sklearn import metrics # 使用sklearn计算AUC
from ucimlrepo import fetch_ucirepo # 下载数据集
import matplotlib.pyplot as plt # 用于绘图
import seaborn as sns # 用于绘图
import umap # 用于二维投影距离
# 对于交互式图表
import plotly.graph_objs as go
from plotly.offline import iplot
import plotly.io as pio
pio.renderers.default="colab"
# 禁用 Pandas 警告
pd.options.mode.chained_assignment = None
/usr/local/google/home/gbm/my_venv/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html from .autonotebook import tqdm as notebook_tqdm 2024-06-17 13:06:13.648825: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations. To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags. 2024-06-17 13:06:14.292005: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT
我们从UCI下载Covertype数据集。
准备数据集¶
# https://archive.ics.uci.edu/数据集/31/植被类型
covertype_repo = fetch_ucirepo(id=31)
raw_dataset = pd.concat([covertype_repo.data.features, covertype_repo.data.targets], axis=1)
选择感兴趣的列并清理标签。
dataset = raw_dataset.copy()
# 感兴趣的特征
features = ["Elevation", "Aspect", "Slope", "Horizontal_Distance_To_Hydrology",
"Vertical_Distance_To_Hydrology", "Horizontal_Distance_To_Roadways",
"Hillshade_9am", "Hillshade_Noon", "Hillshade_3pm",
"Horizontal_Distance_To_Fire_Points"]
dataset = dataset[features + ["Cover_Type"]]
# 隐藏类型为文本
dataset["Cover_Type"] = dataset["Cover_Type"].map({
1: "Spruce/Fir",
2: "Lodgepole Pine",
3: "Ponderosa Pine",
4: "Cottonwood/Willow",
5: "Aspen",
6: "Douglas-fir",
7: "Krummholz"
})
dataset.head()
Elevation | Aspect | Slope | Horizontal_Distance_To_Hydrology | Vertical_Distance_To_Hydrology | Horizontal_Distance_To_Roadways | Hillshade_9am | Hillshade_Noon | Hillshade_3pm | Horizontal_Distance_To_Fire_Points | Cover_Type | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2596 | 51 | 3 | 258 | 0 | 510 | 221 | 232 | 148 | 6279 | Aspen |
1 | 2590 | 56 | 2 | 212 | -6 | 390 | 220 | 235 | 151 | 6225 | Aspen |
2 | 2804 | 139 | 9 | 268 | 65 | 3180 | 234 | 238 | 135 | 6121 | Lodgepole Pine |
3 | 2785 | 155 | 18 | 242 | 118 | 3090 | 238 | 238 | 122 | 6211 | Lodgepole Pine |
4 | 2595 | 45 | 2 | 153 | -1 | 391 | 220 | 234 | 150 | 6172 | Aspen |
第一个模型是在“过滤数据集”上训练的,该数据集仅包含云杉/冷杉和棉木/柳树的示例。
filtered_dataset = dataset[dataset["Cover_Type"].isin(["Spruce/Fir", "Cottonwood/Willow"])]
如您所见,云杉/冷杉覆盖区比棉木/柳树覆盖区更为常见:
filtered_dataset["Cover_Type"].value_counts()
Cover_Type Spruce/Fir 211840 Cottonwood/Willow 2747 Name: count, dtype: int64
我们训练了一种流行的异常检测决策森林算法,称为孤立森林。
异常检测模型¶
model = ydf.IsolationForestLearner(features=features).train(filtered_dataset)
Train model on 214587 examples Model trained in 0:00:00.074241
我们可以生成“预测”,即异常分数。
predictions = model.predict(filtered_dataset)
predictions[:5]
array([0.57844853, 0.609949 , 0.5433627 , 0.6099571 , 0.48067462], dtype=float32)
接下来,我们绘制了云杉/冷杉和白杨/柳树覆盖的模型异常分数分布。我们看到这两种分布是“分开的”,这表明模型在区分这两种覆盖方面的能力。
注意: 需要注意的是,由于白杨/柳树覆盖的频率较低,这两种分布是单独标准化的。否则,白杨/柳树的分布会显得平坦。
sns.kdeplot(predictions[filtered_dataset["Cover_Type"] == "Spruce/Fir"], label="Spruce/Fir")
sns.kdeplot(predictions[filtered_dataset["Cover_Type"] == "Cottonwood/Willow"], label="Cottonwood/Willow")
plt.xlabel("predicted anomaly score")
plt.ylabel("distribution")
plt.legend()
None
AUC是用于评估分类模型的一项指标。它还可以用于量化任何信号在分离两个不同类别上的区分能力。在异常检测的上下文中,我们可以使用AUC来量化我们的异常检测模型在多大程度上能够孤立少数类。
覆盖类型信息不用于训练模型,而数据集被认为是静态的(即覆盖类型不会随时间而改变)。因此,我们不需要在训练和测试之间拆分数据集,可以使用所有数据同时进行模型训练和通过AUC进行评估。
metrics.roc_auc_score(filtered_dataset["Cover_Type"] == "Cottonwood/Willow", predictions)
0.9427246186652949
这个高AUC值确认了模型能够很好地区分这两种覆盖类型。
我们还可以分析模型以理解它:例如,我们可以看到在海拔的部分依赖图中,“正常”覆盖范围大约在2900到3300米的高度。通过查看其他属性,还可以得出类似的结论。
model.analyze(filtered_dataset, sampling=0.001) # 使用更大的样本以获得更好的结果
我们还可以解释单个模型的预测。例如,让我们选择第一个棉木/柳树的示例并生成一个预测:
first_willow_example = filtered_dataset[filtered_dataset["Cover_Type"] == "Cottonwood/Willow"][:1]
first_willow_example
Elevation | Aspect | Slope | Horizontal_Distance_To_Hydrology | Vertical_Distance_To_Hydrology | Horizontal_Distance_To_Roadways | Hillshade_9am | Hillshade_Noon | Hillshade_3pm | Horizontal_Distance_To_Fire_Points | Cover_Type | |
---|---|---|---|---|---|---|---|---|---|---|---|
1988 | 2000 | 318 | 7 | 30 | 4 | 108 | 201 | 234 | 172 | 268 | Cottonwood/Willow |
model.predict(first_willow_example)
array([0.5474113], dtype=float32)
现在,让我们看看这个示例的特征值如何影响模型预测的结果:
我们看到,样本的海拔值2000是不常见的,这解释了一些高预测值。另一方面,示例中的“朝向”和“坡度”相对正常。
model.analyze_prediction(first_willow_example)
列出所有决策森林算法,我们的孤立森林模型定义了一个示例之间的隐式距离。这个距离可以用来聚类示例或进行可解释的映射。
让我们计算每对示例之间的距离。为了使代码运行得更快,我们只选择前10000个示例。
distances = model.distance(filtered_dataset[:10000]) # 使用更多示例以获得更佳效果
distances[:4, :4]
array([[0. , 0.86 , 0.6766667, 0.85 ], [0.86 , 0. , 0.9066667, 0.31 ], [0.6766667, 0.9066667, 0. , 0.8833333], [0.85 , 0.31 , 0.8833333, 0. ]], dtype=float32)
我们可以使用UMAP(或任何其他流形学习算法,如T-SNE)将示例投影到2D图中。
请注意,尽管模型从未见过这些覆盖类型,它们在图中仍然很好地分开。
manifold = umap.UMAP(n_components=2, n_neighbors=10, metric="precomputed").fit_transform(distances)
sns.scatterplot(x=manifold[:, 0],
y=manifold[:, 1],
hue=filtered_dataset["Cover_Type"][:manifold.shape[0]])
plt.legend()
/usr/local/google/home/gbm/my_venv/lib/python3.11/site-packages/umap/umap_.py:1858: UserWarning: using precomputed metric; inverse_transform will be unavailable
<matplotlib.legend.Legend at 0x7faa84413490>