随机森林
随机森林是一种以决策树(常用CART树)为基学习器的bagging算法。
- 回归问题结果:各学习器的均值
- 分类问题结果:
- 硬投票:基学习器预测频率最高的类别为最终结果(原论文采用方法)
- 软投票:通过各基学习器的结果概率分布计算样本属于某个类别的平均概率,然后选择概率分布最高的类别结果(sklearn.ensemble .RandomForestClassifier采用方法)
随机性
- 样本随机:bootstrap抽样导致的训练集随机性
- 特征随机:每个节点随机选取特征子集进行不纯度计算的随机性
- 节点分割随机(可选):使用随机分割点选取时产生的随机性(此时的随机森林又被称为**Extremely Randomized Trees**),这种方法可以进一步减少方差,但是会增大偏差
特征重要性
- Gini系数:随机森林可以通过计算各特征gini系数均值对特征重要性进行排序。但这种方法得出的重要性排序与特征取值范围以及预测类别数等因素相关[Strobl 2007],例如取值范围大的连续变量较离散变量可能会具有更高的得分。
- permutation importance:sklearn中还可以用sklearn.inspection.permutation_importance计算模型中变量重要性,评价指标选择参考Metrics and scoring。
out-of-bag(oob) score
sklearn随机森林模型中有一个oob_score参数,用于计算oob样本(没有被采样到的样本)在不包含该样本基学习器上的预测结果平均得分。
- 回归问题:r2_score
R 2 = 1 − ∑ i ( y i − y ^ i ) 2 ∑ i ( y i − y ‾ ) 2 R^2=1-\frac{\sum \limits_i(y_i-\hat y_i)^2}{\sum \limits_i(y_i-\overline{y})^2} R2=1−i∑(yi−y)2i∑(yi−y^i)2
M S E = ∑ i m ( y i − y ^ i ) 2 m MSE=\frac{\sum \limits_i^m(y_i-\hat y_i)^2}{m} MSE=mi∑m(yi−y^i)2
和MSE相比, R 2 R^2 R2无量纲,而且消除了原始数据离散程度的影响。 - 分类问题:accuracy_score
TRTE
- 定义:Totally Random Trees Embedding(TRTE)是一种非监督数据升维方法。它能够基于每个样本在各个决策树上的叶节点位置,得到一种基于森林的样本特征嵌入。
- 举例:假设现在有4棵树且每棵树有4个叶子节点,我们依次对它们进行从0至15的编号,记样本𝑖在4棵树叶子节点的位置编号为[0,7,8,14],样本𝑗的编号为[1,7,9,13],此时这两个样本的嵌入向量即为[1,0,0,0,0,0,0,1,1,0,0,0,0,0,1,0]和[0,1,0,0,0,0,0,1,0,1,0,0,0,1,0,0]。假设样本𝑘对应的编号为[0,6,8,14],那么其对应嵌入向量([1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0])的距离应当和样本𝑖较近,而离样本𝑗较远,即两个样本在不同树上分配到相同的叶子结点次数越多,则越接近。
- 应用:提取样本隐式特征,度量样本间距离
- 由于闵氏距离度量两个嵌入向量之间的距离时,计算同一位置变量差异,故与叶子节点的编号顺序无关。
代码
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
RANDOM_STATE = 42
X, y = make_classification(n_samples=1000, n_features=25,
n_clusters_per_class=1, n_informative=15,
random_state=RANDOM_STATE)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=RANDOM_STATE)
clf = RandomForestClassifier(n_estimators=3,
max_depth=10,
random_state=RANDOM_STATE)
clf.fit(X_train, y_train)
results = clf.predict(X_test)
孤立森林
目的
用于连续特征数据的异常检测
基本原理及步骤
-
基本思想
多次随机选取特征和对应的分割点以分开空间中样本点,那么异常点很容易在较早的几次分割中就已经与其他样本隔开,正常点由于较为紧密故需要更多的分割次数才能将其分开。 -
异常值判断指标
假设样本数为n,- 树中的平均路径长度: c ( n ) = 2 H n − 1 − 2 ( n − 1 ) n c(n) = 2H_{n-1}-\frac{2(n-1)}{n} c(n)=2Hn−1−n2(n−1)
- 样本x分派到叶子节点的路径长度: h x = ∑ p = 1 x 1 p h_x=\sum_{p=1}^x\frac{1}{p} hx=∑p=1xp1
- 异常程度指标: s ( x , n ) = 2 − E h x c ( n ) s(x,n)=2^{-\frac{\mathbb{E}h_x}{c(n)}} s(x,n)=2−c(n)Ehx
当 E h x → 0 \mathbb{E}h_x\rightarrow0 Ehx→0时, s ( x , n ) → 1 s(x,n)\rightarrow1 s(x,n)→1;
当 E h x → n − 1 \mathbb{E}h_x\rightarrow n-1 Ehx→n−1时, s ( x , n ) → 0 s(x,n)\rightarrow0 s(x,n)→0;
当 E h x → c ( n ) \mathbb{E}h_x\rightarrow c(n) Ehx→c(n)时, s ( x , n ) → 1 2 s(x,n)\rightarrow\frac{1}{2} s(x,n)→21。 -
最大树高
因为
lim n → ∞ H n − log n = γ \lim_{n\to\infty} H_n-\log n = \gamma n→∞limHn−logn=γ
其中, γ ≈ 0.5772 \gamma\approx0.5772 γ≈0.5772为欧拉常数, log n \log n logn与 c ( n ) c(n) c(n)数量级相同。
故,最大树高可设为 log n \log n logn。 -
算法步骤
- step1:样本、特征随机采样;
- step2:对每个特征值随机确定分割点并进行样本分割;
- step3:重复step2直至达到最大树高或者该节点只有1个样本;
- step4:计算样本在每棵树上被分配到叶子的路径+ c ( T . s i z e ) c(T.size) c(T.size)(由于限制了最大树高,故需补充计算当前叶子节点上样本数对应可生长的平均路径值);
- step5:计算 s ( x , n ) s(x,n) s(x,n)。
代码
- python库:[sklearn.ensemble.IsolationForest]https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.IsolationForest.html#sklearn.ensemble.IsolationForest)
- 示例
import matplotlib.pyplot as plt
import numpy as np
from sklearn.ensemble import IsolationForest as sklearnIF
def cal_h(n):
if n>=10000:
hn = (np.log(n) + 0.5772)
else:
hn = 0
for i in range(1,n+1):
hn += 1/i
return hn
class Node:
def __init__(self, depth):
self.depth = depth
self.left = None
self.right = None
self.feature = None
self.pivot = None
class Tree:
def __init__(self, max_height):
self.root = Node(0)
self.max_height = max_height
self.c = None
def _build(self, node, X,):
if X.shape[0] == 1:
return
if node.depth+1 > self.max_height:
node.depth += self._c(X.shape[0])
return
node.feature = np.random.randint(X.shape[1])
pivot_min = X[:, node.feature].min()
pivot_max = X[:, node.feature].max()
node.pivot = np.random.uniform(pivot_min, pivot_max)
node.left, node.right = Node(node.depth+1), Node(node.depth+1)
self._build(node.left, X[X[:, node.feature]<node.pivot])
self._build(node.right, X[X[:, node.feature]>=node.pivot])
def build(self, X):
self.c = self._c(X.shape[0])
self._build(self.root, X)
def _c(self, n):
if n == 1:
return 0
else:
return 2 * (cal_h(n-1) - (n-1)/n)
def _get_h_score(self, node, x):
if node.left is None and node.right is None:
return node.depth
if x[node.feature] < node.pivot:
return self._get_h_score(node.left, x)
else:
return self._get_h_score(node.right, x)
def get_h_score(self, x):
return self._get_h_score(self.root, x)
class IsolationForest:
def __init__(self, n_estimators=100, max_samples=256):
self.n_estimator = n_estimators
self.max_samples = max_samples
self.trees = []
def fit(self, X):
for tree_id in range(self.n_estimator):
random_X = X[np.random.randint(0, X.shape[0], self.max_samples)]
tree = Tree(np.log(random_X.shape[0]))
tree.build(X)
self.trees.append(tree)
def predict(self, X):
result = []
for x in X:
h = 0
for tree in self.trees:
h += tree.get_h_score(x) / tree.c
score = np.power(2, - h/len(self.trees))
result.append(score)
return np.array(result)
if __name__ == "__main__":
rng = np.random.RandomState(42)
# Generate train data
X = 0.3 * rng.randn(1000, 2)
X_train = np.r_[X + 2, X - 2]
# Generate some regular novel observations
X = 0.3 * rng.randn(50, 2)
X_normal = np.r_[X + 2, X - 2]
# Generate some abnormal novel observations
X_outliers = rng.uniform(low=-4, high=4, size=(20, 2))
X_test = np.vstack([X_normal,X_outliers])
IF = IsolationForest()
IF.fit(X_train)
res = IF.predict(X_test)
result = sklearnIF(max_samples=100, random_state=rng).fit(X_train).predict(X_test)
abnormal_X = X_test[res > np.quantile(res, 0.90)]
abnormal_X2 = X_test[result ==-1]
b1 = plt.scatter(X_test[:, 0], X_test[:, 1], s=5)
b2 = plt.scatter(
abnormal_X[:, 0], abnormal_X[:, 1],
s=50, edgecolors="Red", facecolor="none"
)
b3 = plt.scatter(
abnormal_X2[:, 0], abnormal_X2[:, 1],
s=30, edgecolors="green", facecolor="none"
)
plt.legend([b1, b2, b3],
["test observations",
"class IsolationForest outliers", "sklearnIF outliers"],
loc="upper left")
plt.show()
- 结果
选取0.95分位数作为异常值判断时,自己建立的孤立森林结果(红圈)和sklearn库结果(绿圈)如下图所示:
[参考]:
DataWhale树模型与集成学习