惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
Security Latest
Security Latest
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
C
Cybersecurity and Infrastructure Security Agency CISA
Cloudbric
Cloudbric
T
Threat Research - Cisco Blogs
大猫的无限游戏
大猫的无限游戏
C
CXSECURITY Database RSS Feed - CXSecurity.com
阮一峰的网络日志
阮一峰的网络日志
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
雷峰网
雷峰网
C
Cisco Blogs
V
Vulnerabilities – Threatpost
S
Security Archives - TechRepublic
V
Visual Studio Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
J
Java Code Geeks
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Know Your Adversary
Know Your Adversary
博客园 - 叶小钗
腾讯CDC
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Privacy International News Feed
P
Palo Alto Networks Blog
博客园_首页
V
V2EX
WordPress大学
WordPress大学
Schneier on Security
Schneier on Security
月光博客
月光博客
博客园 - 司徒正美
Google DeepMind News
Google DeepMind News
TaoSecurity Blog
TaoSecurity Blog
博客园 - 聂微东
酷 壳 – CoolShell
酷 壳 – CoolShell
人人都是产品经理
人人都是产品经理
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 【当耐特】
The Cloudflare Blog
罗磊的独立博客
美团技术团队
N
News | PayPal Newsroom
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Last Week in AI
Last Week in AI
K
Kaspersky official blog
Google Online Security Blog
Google Online Security Blog
S
SegmentFault 最新的问题
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
Tailwind CSS Blog

又见苍岚

COLMAP PatchMatch Stereo 算法详解 事件驱动的状态机框架:从理论到工程实践 Git 在国内网络环境下无法 Push 的排查与修复 —— 配置 Clash 代理 分段五次多项式插值原理详解 路径插值方法深度对比研究 Claude Code 使用指南 OpenClaw 记忆管理与技能创建指南 CBS(Conflict-Based Search)算法详解 A* 算法及其变种详解 OpenClaw 配置多 Agents Windows Powershell 无法加载文件,因为在此系统上禁止运行脚本问题的解决方案 MaxClaw 安装流程 大模型 AI 名词介绍 AList 网盘聚合工具简介 Protobuf 简介与测试 Claude Code 简介以及 GLM 4.7 模型接入 Github 歌词下载工具 163MusicLyrics Python __getattr__ 懒加载 Python TypedDict 机器人仿真平台 Gazebo 安装记录 机器人仿真平台 Gazebo 简介 多机器人路径规划问题(Multi-Agent Path Finding, MAPF)简介 Python exifread 读取修改过的 jpeg 信息错误问题修复 3D 坐标系变换的理解 3D 旋转矩阵基本概念 MongoDB Compass 介绍 Python 环境管理工具 uv Flutter 开发指南 Snipaste 安装下载与黑屏问题解决方案 全局路径规划算法记录 2025 Python 版本性能测试 Flutter Hello World Flutter 安装环境配置 Ubuntu VMware 硬盘扩容后 SMBus Host controller not enabled 报错问题解决 Python NetworkX 教程 Docker GPU 报错 - Failed to initialize NVML Unknown Error 解决方案 Python matplotlib 图表绘制 cuda-toolkit 安装替代 Cuda 与 Cudnn Jinja2 Python 利用 docxtpl 和 Jinja2 生成基于模板的 Word 文档 Docker 实现 CPU 核心隔离 LoFTR 基于 Transformer 的特征提取匹配算法 OmniGlue 特征匹配 SuperGlue 使用图神经网络学习特征匹配 Ubuntu 下将 xlsx 文件按照 sheet 转换为 图片 Python 使用 SQLAlchemy Python FastAPI 教程 openwrt 软路由配置安装 Nav2 地图文件(PGM/YAML)规范标准 3D OBJ 模型转换为 glb 瓦片格式 Python 源码 Redis 数据库介绍 Ubuntu 22.04 内核自动升级导致 MongoDB 7.0.12 错误记录 ubuntu 20.04 安装 ROS Noetic ubuntu 18.04 安装 ROS Melodic VMware Workstation Pro 个人免费版下载、安装、使用指南 Hybrid A-star 路径规划 Reeds-Shepp 曲线 Dubins 曲线 Linux kvm 虚拟机网络不通的问题解决方法 Ubuntu 自动内存清理 BiliBili 缓存视频转 mp4 Python 求解线性规划 3D Gaussian Splatting 官方源码实践记录 ImageMagick 教程 Ubuntu 22.04 安装 Colmap 对数几率 odds Ubuntu nmcli 网络管理工具使用指南 SuperPoint 自监督深度学习特征点提取 SyncTV Music Tag Web 在线音乐信息整理工具 ncm 格式转 mp3 MusicBrainz 音乐元数据百科数据库 Ubuntu 网络流量监控工具 私人云音乐平台 Navidrome 入门 手眼标定 四元数(Quaternions) OHTTPS 实现免费自动 https 证书申请、更新、部署 ubuntu 22.04 安装 CloudCompare 单机 KVM 虚拟机冷迁移 Ubuntu 22.04 使用 mdadm 实现软 raid 小鱼 一键安装 ROS-humble Fluid -46- 基于 Simpletex API 构建公式识别页面 公式识别 API 简介 -- Simpletex 使用 Python web 部署库 waitress 3D Gaussian Splatting for Real-Time Radiance Field Rendering Ubuntu Swap 简介与空间扩展 Ubuntu 24.04 安装 forticlient Clash Verge 使用 MongoDB 7.0.17 集群 Docker 构建源码 Error code - 2013. Lost connection to MySQL server during query 问题解决 Python 日志记录库 loguru 使用指北 Python 实现 Web 日志查看服务 MySQL LOAD DATA LOCAL INFILE 极速数据加载 Image size exceeds limit of 89478485 pixels 解决方案 Docker 使用 NVIDIA GPU 驱动错误解决 阿里云 docker 镜像仓库 Ubuntu中没有wired connected的解决方案 MinIO 简介 subconverter 代理订阅格式转换 修复 node –openssl-legacy-provider is not allowed in NODE_OPTIONS 错误
孤立森林
Yiwei Zhang · 2023-07-13 · via 又见苍岚

孤立森林是一种超脱的异常检测算法,本文记录原理和实现。

简介

孤立森林(isolation Forest)算法,2008年由刘飞、周志华等提出,算法不借助类似距离、密度等指标去描述样本与其他样本的差异,而是直接去刻画所谓的疏离程度(isolation),因此该算法简单、高效,在工业界应用较多。

Isolation Forest算法的逻辑很直观,算法采用二叉树对数据进行分裂,样本选取、特征选取、分裂点选取都采用随机化的方式。

基本假设是异常点的特征距离正常数据比较远,而且数量不多

基于这一假设,如果有个人随机生成特征空间中的平面来切分所有数据点的话,那么异常的点应该会以更大的概率被很快单独分割到某个子空间去。这也就是孤立森林的核心思想了。

形象说明

给你根棍子,还把你眼睛蒙上,要把上图的白棋扒拉出来,找个人在边上给你计数,看多少次能分出来,A这一堆,需要的次数非常多,可能一早上你都扒拉不出来;而B这一堆,可能一次就扒拉出来了,需要的次数非常少,那我们就可以通过计算扒拉的次数来衡量一个点的异常程度了,扒拉的次数越少,越不合群,也就越异常。一个人扒拉可能存在随机性,不大准,那我们找100个人来扒拉,然后将每个人扒拉的次数取的平均,那不就准了,孤立森林,大概也就是这个思想了。

论文示例

论文中给了图示,在一堆二维数据中,考虑孤立点 $x_0$ 和正常点 $x_i$

在二维空间中随机划分,将二者分到独立子空间中,多次组织划分,记录每次达到目的的次数,绘制统计图:

可以看到 $x_0$ 需要的划分次数要明显少很多,依此判断 $x_0$ 更为异常。

数据说明

假设现在有一组一维数据(如下图),我们要对这组数据进行切分,目的是把点A和 B单独切分出来,先在最大,值和最小值之间随机选择一个值 X,然后按照 <X 和 >=X 可以把数据分成左右两组,在这两组数据中分别重复这个步骤,直到数据不可再分。

点B跟其他数据比较疏离,可能用很少的次数就可以把它切分出来,点 A 跟其他数据点聚在一起,可能需要更多的次数才能把它切分出来。

那么从统计意义上来说,相对聚集的点需要分割的次数较多,比较孤立的点需要的分割次数少,孤立森林就是利用分割的次数来度量一个点是聚集的(正常)还是孤立的(异常)。

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

: 所谓单独切分是指在现有的空间分割方案中的一个子空间内随机切分,切分的结果子空间中,如果只包含一个元素,那么我们称该元素被单独切分了出来,之后这个空间不会再次进行切分。

原理

孤立森林算法具体实现时,需要为样本数据维护一棵棵决策树,每个决策就是在切分特征空间,直到达到了切分次数极限或者所有样本都单独待在一个子空间之内。

随机选择m个特征,通过在所选特征的最大值和最小值之间随机选择一个值来分割数据点。观察值的划分递归地重复,直到所有的观察值被孤立(或达到最大分割次数)。

此时每个数据点记录自己所在决策树的层数,我们倾向于相信层数越低的数据越异常,但是一棵树不足以说明问题,于是同样的方法多生成一些树:

训练

1)训练逻辑

从原始数据中,有放回或者不放回的抽取部分样本,选取部分特征,构建一颗二叉树(iTree即Isolation Tree),利用集成学习的思想,多次抽取样本和特征,完成多棵iTree的构建。

2)iTree停止条件

树达到指定的高度/深度,或者数据不可再分。

3)算法伪代码如下

完成训练或,我们获得了 $t$ 棵完成训练的决策树。

  • 树的最大深度=ceiling(log(subsimpleSize)),paper里说自动指定,sklearn也是在代码中写死:max_depth = int(np.ceil(np.log2(max(max_samples, 2))))这个值接近树的平均深度,我们只关注那些小于平均深度的异常值,所以无需让树完全生长
  • Sub-sampling size,每次抽取的样本数,建议256即可,大于256,性能上不会有大的提升
  • Number of tree,建议100,如果特征和样本规模比较大,可以适当增加

预测

树已经有了,接下来需要评价数据样本的异常程度,按照之前描述的思想, 很容易想到用该样本落入叶子结点时,split的次数(即样本落入叶子结点经过的边)作为衡量指标,对于 $t$ 棵树,取平均即可。

对于比较异常的样本,一般没有到达切分最大次数就到达叶子节点了,对于落入树中叶子节点位置的样本,直接统计其树的高度即可;但是当样本众多,叶子节点不是一个样本(经常发生)时,就没有直观的拆分次数用于评估了。

PathLength计算

此时如果直接用该节点的高度其实可能会出问题,因为既然该节点里有不止一个样本,那么肯定达到了最大拆分次数,如果直接使用高度,可能会导致大量的样本在此处评价异常程度时具有相同的权重,很可能导致本该有所区分的样本没有分开;那节点里不止一个样本该怎么办呢,既然统一太粗鲁,我们就尝试细分节点直接的区别,考虑叶子节点的情况,我们倾向于认为比较异常的样本落在的叶子节点里的节点数应该会比很正常的样本落在的叶子节点里的样本数少(这句话有点绕,但是道理就是这样的),那么就需要将这部分差异表现出来。

最暴力的差异表现手段当然是接着划分子空间啦,直到将所有数据分开,但是运算量巨大(不然为啥设置拆分数量上限),那么在不往下拆分的情况下还得估计节点所在数的高度,就需要做估计了。
$$
h(x)=e+c( T.size )
$$
其中e为样本 $x$ 从树的根节点到叶节点的过程中经历的边的个数,即 split 次数。$T.size$ 表示和样本 $x$ 同在一个叶子结点样本的个数,$C(T.size)$ 可以理解为在此处建立一棵节点数为 $T.size$ 的树中节点的平均高度,表示 $T.size$个样本构建一个二叉树的平均路径长度,也就是说这里的节点为了节约时间就不算了,但是每个节点咱们估计一个高度大家拿去用就行了,$c(n)$ 计算公式如下:

$$ \begin{aligned}c(n) & =2 H(n-1)-\frac{2(n-1)}{n} \\ &=2[\ln (n-1)+0.5772156649]-\frac{2(n-1)}{n}\end{aligned} $$

其中,0.5772156649为欧拉常数

计算异常分

样本落入叶子结点经过的边数(split次数),除了和样本本身有关,也和 limit length 和抽样的样本子集有关系。这里,作者采用归一化的方式,把 split length 的值域映射到 0-1 之间。具体公式如下:
$$
s(x, n)=2^{-\frac{E(h(x))}{c(n)}}
$$
其 中:

h(x): 为样本在iTree上的PathLength

E(h(x)): 为样本在t棵iTree的PathLength的均值

c(n): 为n个样本构建一个BST二叉树的平均路径长度,为什么要算这个,因为iTree和BST的结构的等价性, 标准化借鉴BST(Binary Search Tree)去估计路径的平均长度c(n)。

上述公式,指数部分值域为(−∞,0),因此s值域为(0,1)。当PathLength越小,s越接近1,此时样本为异常值的概率越大。

当观测的得分接近1时,路径长度非常小,那么数据点很容易被孤立,我们有一个异常。当观测值小于0.5时,路径长度就会变大,然后我们就得到了一个正常的数据点。如果所有的观察结果都有0.5左右的异常值,那么整个样本就没有任何异常。

实现

异常检测的工具还是很多的,主要有以下几个,我们这次选用的是Scikit-Learn进行实验。

  1. PyOD: 超过30种算法,从经典模型到深度学习模型一应俱全,和sklearn的用法一致
  2. Scikit-Learn: 包含了4种常见的算法,简单易用
  3. TODS: 与PyOD类似,包含多种时间序列上的异常检测算法

官方文档:https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.IsolationForest.html#sklearn.ensemble.IsolationForest

1
2
3
4
5
6
7
8
9
10
sklearn.ensemble.IsolationForest(
*,
n_estimators=100,
max_samples='auto',
contamination='auto',
max_features=1.0,
bootstrap=False,
n_jobs=None,
random_state=None,
verbose=0, warm_start=False)

参数详解

n_estimators : int, optional (default=100)

iTree的个数,指定该森林中生成的随机树数量,默认为100个

max_samples : int or float, optional (default=”auto”)

构建子树的样本数,整数为个数,小数为占全集的比例,用来训练随机数的样本数量,即子采样的大小

如果设置的是一个int常数,那么就会从总样本X拉取max_samples个样本来生成一棵树iTree

如果设置的是一个float浮点数,那么就会从总样本X拉取max_samples * X.shape[0]个样本,X.shape[0]表示总样本个数

如果设置的是"auto",则max_samples=min(256, n_samples),n_samples即总样本的数量

如果max_samples值比提供的总样本数量还大的话,所有的样本都会用来构造数,意思就是没有采样了,构造的n_estimators棵iTree使用的样本都是一样的,即所有的样本

contamination : float in (0., 0.5), optional (default=0.1)

取值范围为(0., 0.5),表示异常数据占给定的数据集的比例,数据集中污染的数量,其实就是训练数据中异常数据的数量,比如数据集异常数据的比例。定义该参数值的作用是在决策函数中定义阈值。如果设置为’auto’,则决策函数的阈值就和论文中定义的一样

max_features : int or float, optional (default=1.0)

构建每个子树的特征数,整数位个数,小数为占全特征的比例,指定从总样本X中抽取来训练每棵树iTree的属性的数量,默认只使用一个属性

如果设置为int整数,则抽取max_features个属性

如果是float浮点数,则抽取max_features * X.shape[1]个属性

bootstrap : boolean, optional (default=False)
采样是有放回还是无放回,如果为True,则各个树可放回地对训练数据进行采样。如果为False,则执行不放回的采样。

n_jobs : int or None, optional (default=None)在运行fit()和predict()函数时并行运行的作业数量。除了在joblib.parallel_backend上下文的情况下,None表示为1。设置为-1则表示使用所有可用的处理器

random_state : int, RandomState instance or None, optional (default=None)

每次训练的随机性

如果设置为int常数,则该random_state参数值是用于随机数生成器的种子

如果设置为RandomState实例,则该random_state就是一个随机数生成器

如果设置为None,该随机数生成器就是使用在np.random中的RandomState实例

verbose : int, optional (default=0)

训练中打印日志的详细程度,数值越大越详细

warm_start : bool, optional (default=False)当设置为True时,重用上一次调用的结果去fit,添加更多的树到上一次的森林1集合中;否则就fit一整个新的森林

方法

fit(X[, y, sample_weight]):训练模型

decision_function(X):返回平均异常分数

predict(X):预测模型返回1或者-1

fit_predict(X[, y]):训练-预测模型一起完成

get_params([deep]):Get parameters for this estimator.

score_samples(X):Opposite of the anomaly score defined in the original paper.

set_params(**params):Set the parameters of this estimator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 加载模型所需要的的包
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.ensemble import IsolationForest

# 构造一个数据集,只包含一列数据,假如都是月薪数据,有些可能是错的
df = pd.DataFrame({'salary':[4,1,4,5,3,6,2,5,6,2,5,7,1,8,12,33,4,7,6,7,8,55]})

#构建模型 ,n_estimators=100 ,构建100颗树
model = IsolationForest(n_estimators=100,
max_samples='auto',
contamination=float(0.1),
max_features=1.0)
# 训练模型
model.fit(df[['salary']])

# 预测 decision_function 可以得出 异常评分
df['scores'] = model.decision_function(df[['salary']])

# predict() 函数 可以得到模型是否异常的判断,-1为异常,1为正常
df['anomaly'] = model.predict(df[['salary']])
print(df)
salary scores anomaly
0 4 0.212235 1
1 1 0.095133 1
2 4 0.212235 1
3 5 0.225169 1
4 3 0.156926 1
5 6 0.227608 1
6 2 0.153989 1
7 5 0.225169 1
8 6 0.227608 1
9 2 0.153989 1
10 5 0.225169 1
11 7 0.212329 1
12 1 0.095133 1
13 8 0.170615 1
14 12 -0.010570 -1
15 33 -0.116637 -1
16 4 0.212235 1
17 7 0.212329 1
18 6 0.227608 1
19 7 0.212329 1
20 8 0.170615 1
21 55 -0.183576 -1

我们可以看到,发现了三个异常的数据,和我们认知差不多,都是比较高的,并且异常值越大,异常分scores就越大

原始论文

参考资料

文章链接:
https://www.zywvvd.com/notes/study/machine-learning/isolation-forest/isolation-forest/