Isolation Forest 简介

1. 简介

        孤立森林 iForest(Isolation Forest)是一种无监督的异常检测算法,能处理大规模的多维数据。其基本原理是:异常数据由于数量较少且与正常数据差异较大,因此在被隔离时需要较少的步骤(异常样本更容易快速落入叶子结点,或者说异常样本在DT上,距离根节点更近)

两个假设:

1. 异常样本占比很小(如果占比太高,可能被识别为正常的);
2. 异常样本与正常样本差异较大(主要是全局上都为异常的异常,局部小异常可能发现不了,因为差异并不大)。

        IF适用于连续数据的异常检测,将异常定义为容易被孤立的离群点(可以理解为分布稀疏且离密度高的群体较远的点)。直观上,异常数据较少次切分就可以将它们单独划分出来,而正常数据恰恰相反。用统计学来解释,在数据空间里面,分布稀疏的区域表示数据发生在此区域的概率很低,因而可以认为落在这些区域里的数据是异常的。

        IF通过构建二叉树的方法孤立每一个异常样本——因为异常样本容易被孤立的特征,异常样本更靠近根节点。相对聚集的点(正常)需要分割的次数较多,比较孤立的点(异常)需要分割的次数较少。IF就是利用分割次数来度量一个点是聚集的还是孤立的。

        IF不借助类似距离、密度等指标去描述样本与其他样本的差异,而是直接去刻画所谓的疏离程度,因此该算法简单、高效,在工业界应用较多。算法采用二叉树对数据进行分裂,样本选取、特征选取、分裂点选取都采用随机化的方式。如果某个样本是异常值,可能需要很少次数就可以切分出来。

基本思想:

        对数据子采样后选择特征孤立数据到一颗树的叶节点,直到终止条件发生,孤立树建立完成。再循环建立新的孤立树,直至形成一片孤立森林。然后数据放入计算此数据在对于叶节点的路径长度,路径长度越短表示越先被孤立出来,越可能是异常值。

      IF思想是通过不断地分割数据集,从而把异常点给孤立出来。分割数据集的依据是反复随机选取样本特征,不断地分割数据集直到每个样本点都是孤立的。在此情况下,异常点因为具有不同或者特殊的特征值,因此异常点的路径通常很短,也会比较早被分离出来。

2. 具体流程

2.1 训练森林

    1. 子采样: 首先从整个数据集中随机抽取一定数量的样本(无放回)来为构建树做准备。这些抽样的子集大小通常远小于原始数据集的大小,这样可以限制树的大小,并且减少计算复杂度
    2. 构建孤立树 (iTrees): 对于每个子采样集,算法构建一棵孤立树(完全二叉树)。构建孤立树的过程是递归的。在每个节点,算法随机选择一个特征,并在该特征最大值和最小值之间随机选择一个分割值。然后,数据根据这个分割值将样本分到左子树或右子树(这里其实就是简单的将样本中特征小于这个分割点的样本分到左边,其次分到右边)。然后再随机的按某个特征维度的取值把数据进行细分,重复上述步骤,直到数据不可再分。

孤立树iTree生长终止条件:

1. 树达到指定的高度;

2. 数据不可再分。

     (1)当前节点只有一个数据点;

       (2) 节点上的样本的所有特征都相同。

    (1)节点中的样本数量到一定的数目;???

    (2)所有样本的所选特征值都是同一个值。???

3. 森林构建: 重复1-2构建完特定数量的孤立树,集合为孤立森林。(利用集成学习的思想,多次抽取样本和特征,完成多棵iTree的构建)。

流程汇总:-- 单棵树的训练

1. 从训练数据中随机选择N个点作为子样本,放入一棵孤立树的根节点;
2. 随机选择一个特征,在当前节点数据范围内,随机产生一个分割点x (x产生于当前节点数据中指定维度的最大值与最小值之间);
3. 此切割点的选取生成了一个超平面,将当前节点数据空间切分为2个子空间:把当前所选维度下 <x 的点放在当前节点的左分支,把 >=x 的点放在当前节点的右分支;
4. 在节点的左分支和右分支节点递归步骤 2、3,不断构造新的叶子节点,直到叶子节点上只有一个数据(无法再继续切割)或树已经生长到了所设定的高度

整合全部孤立树的结果:

        由于切割过程是完全随机的,所以需要用 ensemble 的方法来使结果收敛,即反复从头开始切,然后计算每次切分结果的平均值(即通过对多棵树进行训练,来去除这种随机性,让结果尽量收敛)。

        获得 t 个孤立树后,单棵树的训练就结束了。接下来就可以用生成的孤立树来评估测试数据了,即计算异常分数 s。 对于每个样本 x,需要对其综合计算每棵树的结果,通过下面的公式计算异常得分: s(x, n)= 2^{-\frac{E(h(x))}{c(n)}}

2.2 相关概念

    1. 路径长度h(x) :  样本通过该孤立树构建阶段的特征选择方式,从根节点到达该样本被孤立的节点(被孤立就是意味着这个样本最终到达的树的叶子节点)所需要的边数(也即split的次数)。

h(x)=e+c(T.size)

其中e为样本x 从树的根节点到叶节点经历的边的个数,即split次数。T.size表示和样本x同在一个叶子结点样本的个数,C(T.size)可以看做一个修正值,(表示T.size个样本构建一个二叉树的平均路径长度),因为有一些样本点还没有被孤立出来,树就停止生长了,该项对其高度给出修正。

c(n)=2H(n-1)-\frac{2(n-1)}{n} =2[ln(n-1)+0.5772156649]-\frac{2(n-1)}{n}

H(i) 是调和数,可以近似为 ln(i) + 0.5772156649(欧拉常数),其中 n 为样本个数,对于给定的数据集大小 n,平均路径长度的期望是一个常数,该公式提供了一个标准化的基准,用于将路径长度标准化。

为何加入修正值?

        由于树的深度设置为ceiling(log(subsimpleSize)) ,所以可能大部分样本的PathLength都比较接近,而如果叶子结点的样本数越多,该样本是异常值的概率也较低(基于fewAndDifferent的假设)。另外c(n)是单调递增的,总之,加了修正,使得异常和正常样本的PathLength差异更大,可以简单的理解,如果样本快速落入叶子结点,并且该叶子结点的样本数较少,那么其为异常样本的可能性较大。

2. 平均路径长度 E(h(x)): 该样本在森林中所有树的路径长度的平均值。

2.3 异常分数

        对于每个样本 x,其异常分数 s(x, n)= 2^{-\frac{E(h(x))}{c(n)}}。其中h(x) 为 x 在每棵树的高度, E(h(x)) 是数据点 x 在所有树中路径长度的平均值, n 是训练数据的样本量,c(n) 是树的平均路径长度,用来对样本 x 的路径长度 h(x) 进行标准化处理。score的范围为[0, 1]。

其中E(h(x))=\frac{\sum_{x=1}^{n}h(x)}{n},x为第x棵孤立树,n为孤立数的总数。

异常分数判断:

1. E(h(x)) -> 0,s(x,n)=1,异常分数越接近1,表明数据异常的可能性越大(此时路径长度非常小,说明样本很容易被孤立);
2. E(h(x)) -> n−1,s(x,n)=0,不容易被孤立,也看不出是否为异常。

3. E(h(x)) 约等于 c(n), s(x,n)=1/2,样本点的路径长度和平均路径长度没啥差别,看不出是否有异常;

        当观测值小于0.5时,路径长度就会变大,得到了一个正常的数据点。如果所有的观察结果都有0.5左右的异常值,那么整个样本就没有任何异常。

(1)如果异常得分接近 1,那么一定是异常点

(2)如果异常得分远小于 0.5,那么一定不是异常点;

(3)如果异常得分所有点的得分都在 0.5 左右,那么样本中很可能不存在异常点。

备注:样本落入叶子结点经过的边数(split次数),除了和样本本身有关,也和limit length和抽样的样本子集有关系。采用归一化的方式,把split length的值域映射到0-1之间(指数部分值域为(−∞,0))。当PathLength越小,s越接近1,此时样本为异常值的概率越大。

      IF可以通过计算每棵树的异常得分,并在孤立树之间进行平均,从而在比正常观测更少的步骤中隔离异常。事实上,得分较高的异常值路径长度较低。

注:scikit-learn的隔离森林引入了异常分数的修改,异常值由负的分数表示,而正的分数意味着是正常的。

2.4 检测过程

    1. 路径长度计算: 对于新的测试样本计算它们在每棵孤立树中的路径长度, 并算平均路径长度;
    2. 计算异常分数: 利用上述计算,IF会计算每个数据点的异常分数;
    3. 判定异常: 根据计算出的异常分数,可以设置一个阈值来判定哪些数据点是异常的。

        那些密度很高的簇要被切很多次才会停止切割,即每个点都单独存在于一个子空间内,但那些分布稀疏的点,大都很早就停到一个子空间内了。

IF算法总共分两步:

1. 训练 iForest:从训练集中进行采样,构建孤立树,对森林中的每棵孤立树进行测试,记录路径长度;
2. 计算异常分数:根据异常分数计算公式,计算每个样本点的 anomaly score。

3. 算法优缺点

3.1 优点

    1. 高效性:IF每棵树随机采样独立生成,具有很好的处理大数据的能力和速度。它具有线性的时间复杂度,并且由于使用了子采样,使得在计算上更加高效。
    2. 可进行并行化处理: 和RF一样,构建孤立树是独立的过程,构建森林可以并行化(可部署在大规模分布式系统上来加速运算)。

        “孤立” 指的是 “把异常点从所有样本中孤立出来”。大多数基于模型的异常检测算法会先 ”规定“ 正常点的范围或模式,如果某个点不符合这个模式,或者说不在正常范围内,那么模型会将其判定为异常点。

IF的创新点:

1. Partial models:在训练过程中,每棵孤立树都是随机选取部分样本;
2. No distance or density measures:不同于 KMeans、DBSCAN 等算法,IF不需要计算有关距离、密度的指标,可大幅度提升速度,减小系统开销;
3. Linear time complexity:因为基于 ensemble,所以有线性时间复杂度。通常树的数量越多,算法越稳定;
4. Handle extremely large data size:由于每棵树都是独立生成的,因此可部署在大规模分布式系统上来加速运算。

3.2 缺点

    1. 异常值比例敏感性: 如果数据集中异常值的比例相对较高,其效果可能就会下降,因为它是基于异常值“少而不同”的假设。
    2. 对局部异常检测不敏感:因为 “少而不同的” 前提条件决定主要解决全局异常的特点,对在局部区域表现出轻微异常特征的点检测不是很敏感。
    3. 不适用于特别高维的数据:IF不会因为特征的多少而降低算法的效率,但也正因为每次只随机用其中一个特征作为分割的特征,如果特征维度很高,就会有很多特征没有用到。

4. demo

4.1 API

1. 构造函数

sklearn.ensemble.IsolationForest(*, 
   n_estimators=100,  # iTree的个数
   max_samples='auto',  # 构建子树的样本数,即子采样的大小,小数为占全集的比例,"auto"-> min(256,n_samples)
   contamination='auto',  # 异常数据占比
   max_features=1.0,  # 构建每个子树的特征数,默认只使用一个属性
   bootstrap=False,  # 采样是否有放回
   n_jobs=None, 
   random_state=None, 
   verbose=0,               
   warm_start=False)  # 为True时,重用上一次调用的结果去fit,添加更多的树到上一次的森林集合中;否则就fit一整个新的森林

2. 其他

fit(X[, y, sample_weight])  # 训练模型
decision_function(X)  # 返回平均异常分数
predict(X)  # 预测模型返回1或者-1
fit_predict(X[, y])  # 训练-预测模型一起完成
get_params([deep])
score_samples(X)
set_params(**params)

4.2 数据准备

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

n_samples, n_outliers = 120, 10
rng = np.random.RandomState(0)
cluster_1 = 0.4 * rng.randn(n_samples, 2) + np.array([2, 2])
cluster_2 = 0.3 * rng.randn(n_samples, 2) + np.array([-2, -2])
outliers = rng.uniform(low=-4, high=4, size=(n_outliers, 2))
X = np.concatenate([cluster_1, cluster_2, outliers])
y = np.concatenate(
    [np.ones((2 * n_samples), dtype=int), -np.ones(n_outliers, dtype=int)])

scatter = plt.scatter(X[:, 0], X[:, 1], c=y, s=20, edgecolor="k")
handles, labels = scatter.legend_elements()
plt.axis("square")
plt.legend(handles=handles, labels=["outliers", "inliers"], title="true class")
plt.title("data distribution")
plt.show()

4.3 模型预测&可视化

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

n_samples, n_outliers = 120, 10
rng = np.random.RandomState(0)
cluster_1 = 0.4 * rng.randn(n_samples, 2) + np.array([2, 2])
cluster_2 = 0.3 * rng.randn(n_samples, 2) + np.array([-2, -2])
outliers = rng.uniform(low=-4, high=4, size=(n_outliers, 2))
X = np.concatenate([cluster_1, cluster_2, outliers])
y = np.concatenate(
    [np.ones((2 * n_samples), dtype=int), -np.ones(n_outliers, dtype=int)])

X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=42)
from sklearn.ensemble import IsolationForest
clf = IsolationForest(max_samples=100, random_state=0)
clf.fit(X_train)
y_pre_score_test = clf.decision_function(cluster_1)  # -1为异常, 1为正常,
y_pre_label_test = clf.predict(cluster_1)

# ---------结果可视化--------------
# 通过网格的方式得到location的x和y坐标
xx, yy = np.meshgrid(np.linspace(-6, 6, 60), np.linspace(-6, 6, 60))
# concat x和y 得到输入的坐标
input_location = np.c_[xx.ravel(), yy.ravel()]
Z = clf.decision_function(input_location)
Z = Z.reshape(xx.shape)

plt.title("IsolationForest")
plt.contourf(xx, yy, Z, camp=plt.cm.Blues_r)
b1 = plt.scatter(X_train[:, 0], X_train[:, 1], c='white', s=20, edgecolor='k')
b2 = plt.scatter(X_test[:, 0], X_test[:, 1], c='green', s=20, edgecolor='k')
c = plt.scatter(outliers[:, 0], outliers[:, 1], c='red', s=20, edgecolor='k')
plt.axis('tight')
plt.xlim((-6, 6))
plt.ylim((-6, 6))
plt.legend([b1, b2, c], ["train data", "test data", "outlier"], loc="best")
plt.show()

5. 一些问题

1. 树的最大深度

        ceiling(log(subsimpleSize)),paper里说自动指定,sklearn也是在代码中写死:max_depth = int(np.ceil(np.log2(max(max_samples, 2))))这个值接近树的平均深度,我们只关注那些小于平均深度的异常值,所以无需让树完全生长。

2. Sub-sampling size

        每次抽取的样本数,建议256即可,大于256,性能上不会有大的提升;

3. Number of tree,建议100,如果特征和样本规模比较大,可以适当增加。

        通常树的数量越多,算法越稳定,但不是越多越好,根据经验当t>=100时,平均路径长度就已经收敛了,因此论文中推荐t设置为100,同时树的深度不易过深。

        当树数量过小时,会导致路径平均值波动较大(结果可能不稳定),当数量过大时,会白白浪费计算资源。还有一点,由于孤立森林的思想是孤立异常值,那么当样本数据量过大时,可能异常值也可能会挤在一起,就会导致孤立的思路失效了。

对于这个问题,可以通过子采样来解决。

        子采样之后可见异常值又重新被明显的区分开来了,论文中也通过多次实验发现子采样为256时,效果是最好的。

4. 为什么需要对树的高度做限制?

        因为我们只关心路径长度较短的点,它们更可能是异常点,而并不关心那些路径很长的正常点。

5. 在使用IF进行实际异常检测的过程中,可能可能存在的坑

1. 若训练样本中异常样本的比例较高,可能会导致最终结果不理想,因为这违背了该算法的理论基础;
2. 异常检测跟具体的应用场景紧密相关,因此算法检测出的 “异常” 不一定是实际场景中的真正异常,所以在特征选择时,要尽量过滤不相关的特征。

6. 模型复杂度

训练时间是O(1),预测时间是O(N),空间同理。

        IF是基于 Ensemble 的异常检测方法,因此具有线性的时间复杂度,且精准度较高,在处理大数据时速度快,所以目前在工业界的应用范围比较广。

常见的场景包括:

1. 网络安全中的攻击检测;

2. 金融交易欺诈检测;

3. 疾病侦测;

4. 噪声数据过滤(数据清洗)等。

参考:异常检测算法 -- 孤立森林(Isolation Forest)剖析 - 知乎

Isolation Forest是一种异常检测算法,可用于识别数据集中的异常值。Sklearn是一个Python机器学习库,提供了Isolation Forest算法的实现。 Isolation Forest基于以下两个概念来检测异常值:孤立实例和孤立树。孤立实例是数据集中的少数异常点,而孤立树是通过随机选择和分割特征来建立的二叉树。算法通过计算数据实例在树中的深度来评估其异常程度。异常点被认为是通过较少的路径被孤立,而正常点通常需要更多的路径来被孤立。 使用sklearn库中的Isolation Forest算法,我们可以按照以下步骤来进行异常检测。 首先,导入必要的库和数据集。使用sklearn.ensemble模块中的IsolationForest类来创建模型。 ``` import numpy as np from sklearn.ensemble import IsolationForest # 创建模型 model = IsolationForest(n_estimators=100, contamination=0.05, random_state=42) ``` 然后,我们可以使用模型的fit()方法来训练 Isolation Forest模型。 ``` # 训练模型 model.fit(data) ``` 在训练完成后,我们可以使用predict()方法来预测数据中的异常值。预测结果是-1表示异常值,1表示正常值。 ``` # 预测异常值 predictions = model.predict(data) ``` 最后,我们可以根据预测结果来标记和分析数据中的异常值。 需要注意的是,在使用Isolation Forest算法时,需要调整一些重要参数。例如,n_estimators参数表示建立孤立树的数量,contamination参数表示数据集中异常值的比例,我们可能需要根据实际情况进行调整。 总的来说,通过使用sklearn中的Isolation Forest算法,我们可以简单方便地进行异常检测,对于发现数据集中的异常值具有较好的效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值