孤立森林是一种旨在通过构建随机化的决策树(Decision Tree)来识别数据集中的异常点的算法。其特点如下:
- 随机森林构建:通过设定的孤立树数量和子采样大小,随机构建多棵孤立树。每棵树都是通过对数据进行随机抽样并在随机选择的特征上进行切割来构建,形成一种随机森林结构。
- 递归划分空间:在每一棵树中,通过随机选取的特征和切割点递归地划分数据空间,将数据集分到树的不同节点上。这一过程持续进行,直到达到了树的高度限制或者数据无法进一步分割(即被孤立)。
- 异常检测评分:在测试阶段,每个数据点的异常程度是通过其在树中被孤立的速度来评估的。简单来说,一个样本在树中路径越短,被孤立得越快,就越可能被视为异常。整个森林中所有树对同一个数据点的平均路径长度形成了该点的异常评分。
如下是代码示例:
import numpy as np
import scipy.io
data = scipy.io.loadmat('lympho.mat')['X']
labels = scipy.io.loadmat('lympho.mat')['y']
# 定义孤立森林类
class Isolation_Forest:
# 定义初始化森林的构造函数
def __init__(self, num_trees, subsample_size):
# 孤立树的个数
self.num_trees = num_trees
# 子采样的大小
self.subsample_size = subsample_size
# 存储孤立树的列表
self.trees = []
# 定义训练孤立森林的函数
def fit_ioslayion_forest(self, x):
num_samples = x.shape[0]
subsample_size = min(self.subsample_size, num_samples)
# 对于每一棵树, 将输入数据进行采样得到新的数据, 放入树的根节点
for _ in range(self.num_trees):
# 随机选择子样本
indices = np.random.choice(num_samples, size=subsample_size,
replace=False)
# 设置树的高度限制为 ceiling(log2M),M 为子样本大小
tree = Isolation_Tree(height_limit=np.ceil(np.log2(subsample_size)))
# 构建孤立树
tree.fit_ioslayion_forest(x[indices])
# 存储孤立树
self.trees.append(tree)
# 定义计算孤立森林中异常分数的函数
def anomaly_score(self, x):
# 计算样本 x 在每棵树上的路径长度
path_lengths = np.array([tree.path_length(x) for tree in self.trees])
# 计算异常分数
return 2.0 ** (-np.mean(path_lengths / np.log2(self.subsample_size)))
# 定义计算数据集 X 中每个样本的异常分数的函数
def anomaly_scores(self, x):
return np.array([self.anomaly_score(sample) for sample in x])
# 定义孤立森立树的类
class Isolation_Tree:
# 定义孤立树的构造函数
def __init__(self, height_limit):
# 设置树的高度限制
self.height_limit = height_limit
# 存储切割特征的索引
self.split_attribute = None
# 存储切割点的值
self.split_value = None
# 左子树
self.left_child = None
# 右子树
self.right_child = None
# 定义孤立树的训练函数
def fit_ioslayion_forest(self, x, height=0):
if height >= self.height_limit or len(x) <= 1:
return
# 随机指定一个维度,根据指定数据的极值,在当前数据中随机生成切割点 p
self.split_attribute = np.random.choice(x.shape[1])
min_val = np.min(x[:, self.split_attribute])
max_val = np.max(x[:, self.split_attribute])
# 在切割特征的范围内随机选择一个值作为切割点 p
self.split_value = np.random.uniform(min_val, max_val)
# 根据切割点生产的超平面,将当前节点数据空间划分为两个不同的子空间,则该维度空间中小于 p 的数据作为当前节点的左孩子,大于 p 的数据作为当前节点的右孩子
left_mask = x[:, self.split_attribute] < self.split_value
right_mask = ~left_mask
# 在每个孩子节点中递归进行步骤 4 和步骤 5,重复构造新的孩子节点以递归构建左子树,直到所有的样本都已经被孤立,或者孤立树达到指定高度
self.left_child = Isolation_Tree(height_limit=self.height_limit)
self.left_child.fit_ioslayion_forest(x[left_mask], height + 1)
# 左子树构建达到限制条件,再递归构建右子树,同样直到达到限制条件为止
self.right_child = Isolation_Tree(height_limit=self.height_limit)
self.right_child.fit_ioslayion_forest(x[right_mask], height + 1)
# 定义计算路径长度的函数
def path_length(self, x, height=0):
if self.split_attribute is None or height >= self.height_limit:
return height
if x[self.split_attribute] < self.split_value:
# 继续沿左子树递归计算路径长度
return self.left_child.path_length(x, height + 1)
else:
# 继续沿右子树递归计算路径长度
return self.right_child.path_length(x, height + 1)
你可以使用以下方式调用:
# 给定孤立树的个数和子采样的大小
t = 100
m = 256
# 初始化创建一个 IsolationForest 实例,设置孤立树的个数为 t,子采样的大小为 m
forest = Isolation_Forest(num_trees=t, subsample_size=m)
# 进行模型训练
forest.fit_ioslayion_forest(data)
# 随机生成一个样本,使用训练完毕的模型计算单个样本的异常分数
sample = np.random.rand(m)
score = forest.anomaly_score(sample)
print("异常分数值:", score)
以上代码使用孤立森林算法对给定数据集进行异常点检测,并最终输出异常分数。代码中使用了两个参数,分别是孤立树的个数(T
)和子采样的大小(M
)。根据具体的需求,你可以调整这些参数的数值,以获得更好的异常点检测结果。如果你想复现该示例,你需要自行准备 Outlier Detection DataSets (ODDS)-Lympho 数据集,并使用你系统中的数据集路径替换代码中的数据集路径。当然,你也可以使用任何你想要进行检测的数据集来替换示例中的数据,此时你需要确保文件的尺寸、维度、数据集键名等内容符合代码的需求,否则可能导致数据读取错误。
此外,在使用这段代码时,你需要注意以下事项:
- 内存消耗:如果数据集非常大,例如具有数百万个数据点,孤立森林算法可能会占用大量的内存。请确保系统具有足够的内存来处理数据集,以避免内存溢出或性能下降的问题。
- 过拟合:孤立森林算法对于异常点的检测非常敏感,但在某些情况下可能会过度拟合。请注意监控异常点的数量和模型的性能,并根据需要进行调整和优化。
- 数据偏差:如果数据集中的异常点数量相对较少,或者异常点与正常点的分布模式相似,孤立森林算法可能无法有效地检测异常点。在这种情况下,需要考虑其他异常检测算法或调整算法参数。
- 结果解释:预测得出的异常分数并不一定完全准确,仅表示与其他数据点的差异程度。因此,在分析结果时,建议结合领域知识和其他辅助方法对异常点进行验证和解释。