目录
一、引言
在数据处理实践中常常遇到有异常值的情况,导致模型预测失败。而传统的异常值识别方法,要么依赖人工经验判断,面对百万的消费数据时,效率低下,要么像Z-score、IQR等方法,对数据分布有严格要求,面对复杂数据时力不从心。但今天要给大家介绍的孤立森林模型,能轻松解决这些问题,它基于随机森林的弱分叉树构建,无需假设数据分布,还能快速处理百万级数据。
文末有代码
二、模型原理
孤立森林的核心思路特别简单,异常值更容易被识别,且其底层架构依托于随机森林的弱分叉树。
正常数据往往聚集在某个区域,而异常数据因为偏离群体,只需要很少的步骤就能把它和其他数据分开。反之,要识别一个正常数据,则需要更多步骤。随机森林的弱分叉树,能通过简单的分割实现快速识别异常值:它不像传统决策树那样追求最优分割点以提升分类精度,而是随机选择特征和分割点,用更简单的结构快速完成数据划分,这种弱的特性,让它在识别异常值时更高效。
具体来说,模型会通过以下 3 个关键步骤工作:
1.构建孤立树:作为随机森林弱分叉树的延伸,孤立树的构建完全依赖随机规则 即随机选择一个特征,再在该特征的取值范围内随机选一个分割点,把数据分成两部分。一直重复这个过程,直到每个子节点里只有一个数据(或达到树的最大深度)。每棵孤立树都是一棵结构简单、分裂随机的弱分叉树,无需复杂计算,构建速度极快。
2.生成森林:借鉴随机森林多树集成的思想,重复构建多棵孤立树(弱分叉树),形成孤立森林。因为单棵弱分叉树的判断可能有偶然性(随机分割可能存在偏差),多棵树投票能抵消随机性,让结果更可靠。
3.计算异常分数:对于每个数据点,统计它在每棵孤立树(弱分叉树)中被识别(孤立) 所需的步数(即从根节点到叶子节点的路径长度)。如果一个数据在多数弱分叉树中都能被快速孤立(路径长度短),说明它是异常值的概率极高,反之则是正常数据。
三、模型代码
接下来我们用Python代码实现孤立森林模型的搭建,基于 sklearn 库。
01、数据导入
这里我们用随机生成的用户消费数据(包含 “消费金额” 和 “消费频次” 两个特征),在实际运用中,可以将这段代码替换成导入需要识别异常值的文件。
# 导入必要库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.ensemble import IsolationForest
from sklearn.datasets import make_blobs # 用于生成模拟数据
# 设置中文字体(避免图表中文乱码)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 生成模拟数据:1000个正常用户,50个异常用户
# 正常数据:围绕两个聚类中心分布
normal_data, _ = make_blobs(n_samples=1000, centers=[[50, 5], [80, 10]], cluster_std=5, random_state=42)
# 异常数据:随机生成偏离正常范围的数据
anomaly_data = np.random.uniform(low=[0, 0], high=[150, 30], size=(50, 2))
# 合并数据
data = np.vstack((normal_data, anomaly_data))
# 转换为DataFrame(方便后续处理)
df = pd.DataFrame(data, columns=['消费金额', '消费频次'])
02、模型参数
sklearn库中的IsolationForest类已经封装好了核心逻辑,其底层会自动生成多棵随机森林风格的弱分叉树,我们只需要调整关键参数即可。这里我们重点解释3个必调参数:
n_estimators:森林中孤立树(弱分叉树)的数量,默认 100。数量越多,集成效果越稳定,但计算速度会变慢。建议根据实际训练的数据量调整,设置更多弱分叉树能抵消随机分割的偏差,提升异常识别的精度。
contamination:异常值比例,即数据中异常值占比的估计值,这是模型判断异常分数阈值的关键,建议根据实际数据设定。如果没有头绪,可先设为0.01-0.1进行尝试。
max_samples:每棵弱分叉树使用的样本数量,默认auto(即样本总数,取较小值)。限制单棵树的样本量,既能减少计算量,又能让每棵弱分叉树的分割视角更独特,提升集成效果。
# 初始化孤立森林模型(底层自动构建弱分叉树)
model = IsolationForest(
n_estimators=100, # 100棵弱分叉树组成森林
contamination=0.05, # 假设异常值占比5%
max_samples='auto', # 每棵弱分叉树使用默认样本数
random_state=42 # 固定随机种子,保证弱分叉树构建规则可复现
)
# 训练模型并预测
# 模型输出:1表示正常数据,-1表示异常数据
df['异常标签'] = model.fit_predict(df[['消费金额', '消费频次']])
# 查看结果:统计正常/异常数据数量
print("正常数据数量:", len(df[df['异常标签'] == 1]))
print("异常数据数量:", len(df[df['异常标签'] == -1]))
# 分离正常数据和异常数据
normal_df = df[df['异常标签'] == 1]
anomaly_df = df[df['异常标签'] == -1]
# 查看前5条异常数据
print("异常数据示例:")
print(anomaly_df[['消费金额', '消费频次']].head())
下面是模型识别出来的部分异常值,可以看出有些异常值是因为消费频数为负数被识别为异常值,有些是因为过低的消费金额,过高的消费频数被识别为异常值。
正常数据数量: 997,异常数据数量: 53
四、可视化图表展示
仅仅有异常值数据结果还不够,下面我们通过一段代码来可视化查看异常值的分布。
如下图,孤立森林模型将正常数据点和异常数据点用不同颜色区分标注出来。其中蓝色为正常的数据,由于正常数据具有相似的特征,所以分布比较紧密。而红色的异常数据,分布较散。根据散点图,可以看出孤立森林模型识别异常值效果优异,分散的点都被正确标注为异常数据(红色点)。
# 创建画布
plt.figure(figsize=(10, 6))
# 绘制正常数据(蓝色)
plt.scatter(
normal_df['消费金额'],
normal_df['消费频次'],
c='skyblue',
label=f'正常数据({len(normal_df)}个)',
alpha=0.6 # 透明度,避免重叠
)
# 绘制异常数据(红色,更大的点)
plt.scatter(
anomaly_df['消费金额'],
anomaly_df['消费频次'],
c='red',
s=80, # 点的大小
label=f'异常数据({len(anomaly_df)}个)',
edgecolors='black' # 黑色边框,突出异常点
)
# 添加图表标签与图例
plt.xlabel('消费金额(元)', fontsize=12)
plt.ylabel('消费频次(次/月)', fontsize=12)
plt.title('孤立森林模型(基于弱分叉树)异常值检测结果', fontsize=14, fontweight='bold')
plt.legend(fontsize=10)
plt.grid(alpha=0.3) # 添加网格,方便读数
# 保存图片
plt.savefig('孤立森林异常值检测.png', dpi=300, bbox_inches='tight')
plt.show()
如下图,设置孤立森林模型的contamination参数为0.05,计算得出数据的异常分数。其中正常数据(蓝色)分布在异常分数0的右边,异常数据(红色)分布在异常分数-1的附近,在异常分数0的左边。
# 计算每个数据点的异常分数(由弱分叉树集成计算得出)
df['异常分数'] = model.decision_function(df[['消费金额', '消费频次']])
# 创建画布
plt.figure(figsize=(10, 6))
# 绘制正常数据的异常分数(蓝色)
plt.hist(
normal_df['异常分数'],
bins=30,
alpha=0.6,
color='skyblue',
label=f'正常数据(分数接近0)'
)
# 绘制异常数据的异常分数(红色)
plt.hist(
anomaly_df['异常分数'],
bins=10,
alpha=0.8,
color='red',
label=f'异常数据(分数接近-1)'
)
# 添加阈值线(模型判断异常的分界线)
threshold = model.decision_function(df).min() + (model.decision_function(df).max() - model.decision_function(df).min()) * (1 - 0.05)
plt.axvline(x=threshold, color='black', linestyle='--', label=f'异常阈值({threshold:.3f})')
# 添加标签与图例
plt.xlabel('异常分数', fontsize=12)
plt.ylabel('数据数量', fontsize=12)
plt.title('孤立森林模型(基于弱分叉树)异常分数分布', fontsize=14, fontweight='bold')
plt.legend(fontsize=10)
plt.grid(alpha=0.3)
# 保存图片
plt.savefig('孤立森林异常分数分布.png', dpi=300, bbox_inches='tight')
plt.show()
五、总结
看到这里,相信大家已经掌握了孤立森林的核心用法,尤其是它基于随机森林弱分叉树的底层逻辑。最后总结一下它的优点:
1.速度快:依托随机森林的弱分叉树,无需复杂的最优分割计算,通过随机分割快速孤立异常值,适合百万级以上的海量数据。
2.无假设:不要求数据满足正态分布等条件,弱分叉树的随机分割特性让它能适应各种复杂数据分布,适用范围广。
3.省内存:每棵弱分叉树只使用部分样本,且结构简单,内存占用低,部署成本低。
关注【小小科研】公众号,了解更多模型哦,感谢支持!