Anomaly Detection with Isolation
简介
异常检测是一种常见的数据科学问题,其目标是识别我们数据中的奇怪或可疑观察,事件或项目,这些可能表明我们的数据收集过程中存在某些问题(例如传感器损坏,收集表格中的拼写错误等或安全漏洞,服务器故障等意外事件。
可以以监督,半监督和无监督的方式执行异常检测。
对于监督方法,我们需要知道每个观察,事件或项目是否异常或真实,我们在训练期间使用此信息。但是为每次观察获取标签可能通常是不现实的,甚至需要消耗大量的人力物力对样本尽心打标签。
半监督方法使用的假设是我们只知道哪些观测是真实的,非异常的,并且我们没有关于异常观测的任何信息。因此,我们只使用真实数据进行训练。在预测期间,模型评估新观察与训练数据的相似程度以及它与模型的拟合程度。
无监督方法假定训练集包含真实和异常观察。由于标记数据或仅拥有干净的数据通常很难且耗时,因此我想更多地关注使用Isolation Forests进行异常检测的无监督方法之一。
加载数据
在我们进入异常检测之前,让我们初始化h2o集群并加载我们的数据。我们将使用信用卡数据集,其中包含有关信用卡交易的各种属性的信息。有492个欺诈性交易和284,807个真实交易,这使得目标类别高度不平衡。我们不会在异常检测建模过程中使用标签,但我们会在评估异常检测时使用它。
import h2o
h2o.init()
df = h2o.import_file(“creditcard.csv”)
Isolation Forests
有一种无监督异常检测问题的方法,试图利用共同和独特观察的属性之间的差异。Isolation Forests背后的想法如下。
- 我们首先构建多个决策树,以便树木隔离树叶中的观察结果。理想情况下,树的每个叶子都会从您的数据集中精确隔离一个观察值。树木随机分裂。我们假设如果一个观察结果与我们的数据集中的其他观察结果相似,则需要更多的随机分割以完全隔离此观察结果,而不是隔离异常值。
- 对于具有与其他观察值显着不同的某些特征值的异常值,随机找到分离它的分裂应该不会太难。当我们构建多个隔离树,因此隔离林时,对于每个观察,我们可以计算隔离观察的所有树的平均分裂数。然后将平均分裂数用作分数,其中观察需要的分裂越少,异常的可能性越大。
让我们训练我们的isoforest,看看预测的样子。数据的最后一列(索引30)包含类标签,因此我们将其从培训过程中排除。
seed = 12345
ntrees = 100
isoforest = h2o.estimators.H2OIsolationForestEstimator(
ntrees=ntrees, seed=seed)
isoforest.train(x=df.col_names[0:31], training_frame=df)
predictions = isoforest.predict(df)
predictions
检查预测
我们可以看到预测 结果包含两列:预测显示标准化的异常分数,mean_length显示所有树的平均分裂数以隔离观察。
根据它们的定义,这两列应该具有反比例的属性,因为隔离观察所需的随机分裂越少,它就越不正常。我们可以轻松检查一下。
predictions.cor()
使用分位数预测异常
当我们以无人监督的方式制定这个问题时,我们如何从平均分裂/异常分数到实际预测? 如果我们对数据集中异常值的相对数量有所了解,我们可以找到分数的相应分位数值,并将其用作我们预测的阈值。
quantile = 0.95
quantile_frame = predictions.quantile([quantile])
quantile_frame
我们可以使用阈值来预测异常类。
threshold = quantile_frame[0, "predictQuantiles"]
predictions["predicted_class"] = predictions["predict"] > threshold
predictions["class"] = df["Class"]
predictions
评估
由于隔离林是一种无监督的方法,因此有必要查看不依赖于预测阈值的分类度量,并估计评分质量。两个这样的度量是接收器操作特性曲线下面积(AUC)和精确调用曲线下面积(AUCPR)。
AUC是评估二元分类模型如何区分真阳性和假阳性的度量。完美的AUC分数是1; 随机猜测的基线得分为0.5。
AUCPR是使用连续预测分数的不同阈值来评估二元分类的精确回忆权衡的度量。完美的AUCPR得分是1; 基线分数是阳性分类的相对数。
对于高度不平衡的数据,建议AUCPR优于AUC,因为AUCPR对真阳性,假阳性和假阴性更敏感,而不关心真阴性,其中大量阴性通常会掩盖其他指标的影响。
%matplotlib notebook
from sklearn.metrics import roc_curve, precision_recall_curve, auc
import matplotlib.pyplot as plt
import numpy as np
def get_auc(labels, scores):
fpr, tpr, thresholds = roc_curve(labels, scores)
auc_score = auc(fpr, tpr)
return fpr, tpr, auc_score
def get_aucpr(labels, scores):
precision, recall, th = precision_recall_curve(labels, scores)
aucpr_score = np.trapz(recall, precision)
return precision, recall, aucpr_score
def plot_metric(ax, x, y, x_label, y_label, plot_label, style="-"):
ax.plot(x, y, style, label=plot_label)
ax.legend()
ax.set_ylabel(x_label)
ax.set_xlabel(y_label)
def prediction_summary(labels, predicted_score, predicted_class, info, plot_baseline=True, axes=None):
if axes is None:
axes = [plt.subplot(1, 2, 1), plt.subplot(1, 2, 2)]
fpr, tpr, auc_score = get_auc(labels, predicted_score)
plot_metric(axes[0], fpr, tpr, "False positive rate",
"True positive rate", "{} AUC = {:.4f}".format(info, auc_score))
if plot_baseline:
plot_metric(axes[0], [0, 1], [0, 1], "False positive rate",
"True positive rate", "baseline AUC = 0.5", "r--")
precision, recall, aucpr_score = get_aucpr(labels, predicted_score)
plot_metric(axes[1], recall, precision, "Recall",
"Precision", "{} AUCPR = {:.4f}".format(info, aucpr_score))
if plot_baseline:
thr = sum(labels)/len(labels)
plot_metric(axes[1], [0, 1], [thr, thr], "Recall",
"Precision", "baseline AUCPR = {:.4f}".format(thr), "r--")
plt.show()
return axes
def figure():
fig_size = 4.5
f = plt.figure()
f.set_figheight(fig_size)
f.set_figwidth(fig_size*2)
h2o_predictions = predictions.as_data_frame()
figure()
axes = prediction_summary(
h2o_predictions["class"], h2o_predictions["predict"], h2o_predictions["predicted_class"], "h2o")
Isolation Forests in scikit-learn
我们可以使用scikit-learn执行相同的异常检测。此示例中使用的scikit-learn版本为0.20。某些行为在其他版本中可能有所不同。该score_samples方法返回与异常分数相反的方法; 因此它是倒置的。Scikit-learn还接受一个contamination参数,即数据集中异常值的比例。
from sklearn.ensemble import IsolationForest
import pandas as pd
df_pandas = df.as_data_frame()
df_train_pandas = df_pandas.iloc[:, :30]
x = IsolationForest(random_state=seed, contamination=(1-quantile),
n_estimators=ntrees, behaviour="new").fit(df_train_pandas)
iso_predictions = x.predict(df_train_pandas)
iso_score = x.score_samples(df_train_pandas)
sk_predictions = pd.DataFrame({
"predicted_class": list(map(lambda x: 1*(x == -1), iso_predictions)),
"class": h2o_predictions["class"],
"predict": -iso_score
})
sk_predictions.head()
评估scikit-learn
figure()
axes = prediction_summary(
sk_predictions [ “class” ],sk_predictions [ “predict” ],sk_predictions [ “ predict_class ” ],“sklearn”)