异常检测系列:孤立森林(Isolation Forest)模型介绍

本文介绍了孤立森林(IsolationForest,IForest)这种无监督的离群值检测方法,它通过构建树结构直接识别异常值,特别适合大数据和高维问题。IForest利用异常值更容易被隔离的直觉,通过计算观测值在树中的路径长度确定其异常程度。文章还探讨了随机森林的原理以及IForest在实际建模过程中的应用和参数调整。
摘要由CSDN通过智能技术生成

img

如果让你逐个分离上面的树,你会从哪棵树开始?你可以选择左边的那棵,因为它独立存在。移除了那棵树之后,下一个最容易分离的是哪棵树?可能是大簇中左下方的那棵。移除了那棵树之后,下一个是哪棵?可能是左上方的那棵,依此类推。这里我提出一个非常重要的直觉:离群值应该是最容易被隔离的。就像剥洋葱一样,离群值位于外层。这就是孤立森林寻找离群值的直觉。

孤立森林快速且不消耗太多内存,因为它不使用任何距离度量来检测异常值。这个优势使其适用于大数据量和高维问题。

(A)什么是孤立森林?

许多离群值检测方法首先对正常数据点进行建模,然后识别那些不符合正常数据模式的观测值。孤立森林(Isolation Forest)或IForest,由Liu, Ting, and Zhou (2008)提出,与这些方法不同。孤立森林不是通过对正常数据点进行建模来寻找离群值,而是直接识别异常值。它应用了一种树结构来隔离每个观测值。离群值将首先被单独分离出来,而正常点往往隐藏在树的深处。他们将每棵树称为孤立树(Isolation Tree)或iTree。他们的算法构建了一组iTree。在iTree上,路径长度较短的观测值被视为异常值。

图(A)使用分区图和树来解释iTree如何隔离数据点。红点是离其他点最远的,然后是绿点,然后是蓝点。在分区图中,只需要一次“切割”就可以将红点与其他点分离。第二次切割是为了绿点,第三次切割是为了蓝点,依此类推。分离一个点所需的切割次数越多,它在树中的深度越深。切割次数的倒数就是异常得分。图(A)右侧的树结构讲述了同样的故事。它需要一次分割来单独分离红点,然后是第二次分割来触及绿点,然后是第三次分割来到达蓝点,依此类推。深度的数量成为了异常得分的良好代理。为了与异常值与高得分相关的传统约定一致,异常得分被定义为深度数量的倒数。
img
图(A):iTree(作者提供的图片)

iTree是一棵二叉树,树中的每个节点都有零个或两个子节点。iTree开始生长,直到满足以下条件之一:(i)末节点只有一个数据点,(ii)节点中的所有数据具有相同的值,或者(iii)树达到研究者设置的高度限制。iTree不需要完全发展,直到所有末节点都有一个数据点。通常,当深度达到设置的限制时,它停止生长。这是因为我们对根节点附近的异常值感兴趣。因此,构建一个大的iTree并不是必要的,因为iTree中的大部分数据都是正常数据点。小样本量会产生更好的iTree,因为它可以减少淹没和掩盖效应。请注意,这个iTree算法与决策树算法不同,因为iTree不使用目标变量来训练树。它是一种无监督学习方法。

(B)为什么叫“Forest”?

你可能更常听到“随机森林”而不是“孤立森林”。"Forest"指的是构建树的集成学习。为什么需要这样做?我们都知道单个决策树的缺点是过拟合,这意味着模型在训练数据上预测得非常好,但在新数据上预测得很差。集成策略通过构建多棵树并对树的预测进行平均来克服这个问题。
img
图(B):孤立森林

图(B)显示了一个数据矩阵,其中每一行都是一个具有多维值的观测。IForest的目标是为每个观测分配一个异常值分数。首先,它随机选择任意数量的行任意数量的列来创建表格,如(1),(2)和(3)。一个观测将出现在至少一个表格中。为每个表格构建一个iTree来计算异常值分数。表格(1)有6行和3列。表格(1)的第一次切割可能是第6个观测,因为它的值与其他观测非常不同。之后,表格(1)的第二次切割可能是第4个观测。类似地,在表格(3)中,第一次切割可能是第6个观测(即第三个记录)。第二次切割是第4个观测(即表格中的第一个记录)。简而言之,如果有N个表格,就会有N个iTree。一个观测可以有多达N个分数。IForest计算分数的算术平均值以获得最终分数。

© 建模过程

与其他章节一样,我使用以下建模过程进行模型开发、评估和结果解释。

  1. 模型开发

  2. 阈值确定

  3. 对正常组和异常组进行分析

对两个组的分析对于传达模型的合理性非常重要。您的业务知识应该告诉您,如果一个特征的平均值在异常组中应该更高还是更低。如果这与直觉相悖,建议您进行调查、修改或删除该特征。您应该迭代建模过程,直到所有特征都合理。
img
(C.1) 第一步:构建模型

我生成了一个包含六个变量和500个观测值的模拟数据集。尽管这个模拟数据集有目标变量Y,但无监督模型只使用X变量。Y变量仅用于验证。异常值的百分比设置为5%,即“contamination=0.05”。我们可以绘制前两个变量的散点图。黄色点是异常值,紫色点是正常数据点。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pyod.utils.data import generate_data
contamination = 0.05 # percentage of outliers
n_train = 500       # number of training points
n_test = 500        # number of testing points
n_features = 6      # number of features
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()

# Plot
plt.scatter(X_train_pd[0], X_train_pd[1], c=y_train, alpha=0.8)
plt.title('Scatter plot')
plt.xlabel('x0')
plt.ylabel('x1')
plt.show()

img
img

图(C.1)

下面我们声明并拟合模型。树的大小“max_samples”设置为40个观测值。在IForest中,不需要分配一个大的树大小,小的样本大小可以产生更好的iTrees。


from pyod.models.iforest import IForest
isft = IForest(contamination=0.05, max_samples=40, behaviour='new') 
isft.fit(X_train)

# Training data
y_train_scores = isft.decision_function(X_train)
y_train_pred = isft.predict(X_train)

# Test data
y_test_scores = isft.decision_function(X_test)
y_test_pred = isft.predict(X_test) # outlier labels (0 or 1)

# Threshold for the defined comtanimation rate
print("The threshold for the defined contamination rate:" , isft.threshold_)

def count_stat(vector):
    # Because it is '0' and '1', we can run a count statistic. 
    unique, counts = np.unique(vector, return_counts=True)
    return dict(zip(unique, counts))

print("The training data:", count_stat(y_train_pred))
print("The training data:", count_stat(y_test_pred))

img
让我更详细地解释一下污染率。在大多数实际应用中,我们并不知道异常值的百分比。当没有关于异常值百分比的先验知识时,我将在第(C.2)节中展示如何确定一个合理的阈值。PyOD默认的污染率为10%。在这里,我将污染率设置为5%,因为训练样本中有5%的异常值。这个参数不影响异常值得分的计算。内置函数threshold_计算训练数据在污染率下的阈值。在这种情况下,当污染率为0.05时,阈值为-5.082e-15。函数decision_functions()生成异常值得分,函数predict()根据阈值分配标签(1或0)。

(C.1.1) 超参数

我将使用.get_params()来解释一些重要的参数:


isft.get_params()

img

  • “max_samples”: 从X中抽取的样本数,用于训练每个基本估计器。这是树的大小,是一个重要的参数。

  • “n_estimators”: 集成中树的数量。默认值为100棵树。

  • “max_features”: 从X中抽取的特征数,用于训练每个基本估计器。默认值为1.0。

  • “n_jobs”: 并行运行“fit”和“predict”的作业数。默认值为1.0。如果将其设置为-1,则作业数设置为核心数。

(C.1.2) 变量重要性

因为IForest应用了树框架,我们将能够了解特征在确定异常值方面的相对重要性。特征重要性由基尼不纯度指数衡量。这些值总和为1.0。

isft_vi = isft.feature_importances_
isft_vi

img
我们可以像树模型一样绘制特征重要性。图(C.1.2)显示了确定异常值的特征的相对强度。


from matplotlib import pyplot as plt
for_plot = pd.DataFrame({'x_axis':X_train_pd.columns,
              'y_axis':isft_vi}).sort_values(by='y_axis',ascending=True)
for_plot['y_axis'].plot.barh()

img
(C.2)第二步-确定模型的合理阈值

阈值将由异常值分数的直方图确定。图(C.2)建议将阈值设置在0.0左右。这意味着大多数正常数据的异常值分数小于0.0。异常数据的异常值分数处于较高的范围内。


import matplotlib.pyplot as plt
plt.hist(y_train_scores, bins='auto') # arguments are passed to np.histogram
plt.title("Outlier score")
plt.show()

img
图(C.2)

(C.3) 第三步 — 展示正常组和异常组的描述统计

对正常组和异常组进行分析是展示模型的可靠性的关键步骤。我创建了一个名为descriptive_stat_threshold()的简短函数,用于展示正常组和异常组的特征大小和描述统计信息。下面我只是采用了设置的异常率作为阈值。您可以测试一系列阈值,以找到合理的异常组大小。


threshold = isft.threshold_ # Or other value from the above histogram

def descriptive_stat_threshold(df,pred_score, threshold):
    # Let's see how many '0's and '1's.
    df = pd.DataFrame(df)
    df['Anomaly_Score'] = pred_score
    df['Group'] = np.where(df['Anomaly_Score']< threshold, 'Normal', 'Outlier')

    # Now let's show the summary statistics:
    cnt = df.groupby('Group')['Anomaly_Score'].count().reset_index().rename(columns={'Anomaly_Score':'Count'})
    cnt['Count %'] = (cnt['Count'] / cnt['Count'].sum()) * 100 # The count and count %
    stat = df.groupby('Group').mean().round(2).reset_index() # The avg.
    stat = cnt.merge(stat, left_on='Group',right_on='Group') # Put the count and the avg. together
    return (stat)

descriptive_stat_threshold(X_train,y_train_scores, threshold)

img
表格(C.3)

上述表格包括模型评估和模型结果的基本要素。请记得使用特征名称标记特征,以便有效展示。

  • **异常值组的大小:**请记住,异常值组的大小由阈值决定。如果选择较高的阈值,大小将会缩小。

  • **每个组中的特征统计:**特征统计应与任何先前的业务知识一致。如果任何特征显示出违反直觉的结果,则应重新检查或删除该特征。直到所有特征都合理为止,模型应进行迭代。

  • **平均异常分数:**异常值组的平均异常分数远高于正常组(0.18 > -0.10)。您不需要过多解释分数。

因为我们在数据生成中有真实值,所以我们可以生成混淆矩阵来了解模型的性能。该模型表现良好,并识别出了所有的25个异常值。


def confusion_matrix(actual,score, threshold):
    Actual_pred = pd.DataFrame({'Actual': actual, 'Pred': score})
    Actual_pred['Pred'] = np.where(Actual_pred['Pred']<=threshold,0,1)
    cm = pd.crosstab(Actual_pred['Actual'],Actual_pred['Pred'])
    return (cm)
confusion_matrix(y_train,y_train_scores,threshold)

img
(D) 通过聚合多个模型实现模型稳定性

由于IForest是一种基于相似度的算法,它对异常值非常敏感,并且容易过拟合。为了产生稳定的预测结果,我们可以聚合多个模型产生的分数。在所有超参数中,树的数量n_estimators可能是最重要的一个。我将为不同数量的树生成5个模型。这些模型的平均预测结果将作为最终的模型预测结果。PyOD模块提供了四种方法来聚合结果。记得使用pip install combo安装相关函数。你只需要使用一种方法来产生聚合结果。


from pyod.models.combination import aom, moa, average, maximization
from pyod.utils.utility import standardizer
from pyod.models.iforest import IForest

# Standardize data
X_train_norm, X_test_norm = standardizer(X_train, X_test)

# Test a range of the number of trees
k_list = [100, 200, 300, 400, 500]
n_clf = len(k_list)
# Just prepare data frames so we can store the model results
train_scores = np.zeros([X_train.shape[0], n_clf])
test_scores = np.zeros([X_test.shape[0], n_clf])

# Modeling
for i in range(n_clf):
    k = k_list[i]
    #isft = IForest(contamination=0.05, max_samples=k) 
    isft = IForest(contamination=0.05, n_estimators=k) 
    isft.fit(X_train_norm)
    
    # Store the results in each column:
    train_scores[:, i] = isft.decision_function(X_train_norm) 
    test_scores[:, i] = isft.decision_function(X_test_norm) 
# Decision scores have to be normalized before combination
train_scores_norm, test_scores_norm = standardizer(train_scores,test_scores)

这5个模型的预测结果取平均得到平均异常值分数(“y_by_average”)。我在图(D)中创建了它的直方图。


# Combination by average
# The test_scores_norm is 500 x 10. The "average" function will take the average of the 10 columns. The result "y_by_average" is a single column: 
y_train_by_average = average(train_scores_norm)
y_test_by_average = average(test_scores_norm)
import matplotlib.pyplot as plt
plt.hist(y_train_by_average, bins='auto') # arguments are passed to np.histogram
plt.title("Combination by average")
plt.show()

img
图(D):平均分数的直方图

图(D)表明阈值等于1.0。根据此,我在表(D)中对正常组和异常组的特征进行了分析。它确定了25个数据点为异常值。读者应该对表(C.3)应用类似的解释。


descriptive_stat_threshold(X_train,y_train_by_average, 1.0)

img
表格(D)

(E) 概述

  • 大多数现有的基于模型的异常检测方法会构建一个正常实例的概要,然后将不符合正常概要的实例标识为异常值。但是,Isolation Forest直接且明确地隔离异常值。

  • IForest采用树结构来隔离每个单独的数据点。异常值是首先被单独分离出来的单个数据点;而正常点往往在树中聚集在一起。

  • 由于Isolation Forest不使用任何距离度量来检测异常值,因此它对于大数据量和高维问题是快速且适用的。

参考文献

  • Liu, F. T., Ting, K. M. & Zhou, Z.-H. (2008). Isolation forest. 2008 Eighth IEEE International Conference on Data Mining (p./pp. 413–422),.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数智笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值