机器学习_2_决策树与随机森林
写在前面:
- 新手入门,有理解不到位的地方欢迎批评指正。
1. 理论
1. 决策树
决策树是一种树型结构,其中每个内部结点表示在一个属性上的测试,每个分支代表一个测试输出,每个叶结点代表一种类别。
决策树学习采用的是自顶向下的递归方法,其基本思想是以信息熵为度量构造一棵熵值下降最快的树,到叶子节点处的熵值为零,此时每个叶节点中的实例都属于同一类 。
我们希望决策树的分支结点所包含的样本尽可能属于同一类别,即结点的"纯度" (purity)越来越高.
建立决策树的关键,即在当前状态下选择哪个属性作为分类依据。根据不同的目标函数,建立决策树主要有一下三种算法。
-
ID3:(信息增益/互信息 g ( D , A ) \mathrm{g}(\mathrm{D}, \mathrm{A}) g(D,A))
g ( D , A ) = H ( D ) − H ( D ∣ A ) \mathrm{g}(\mathrm{D}, \mathrm{A})=\mathrm{H}(\mathrm{D})-\mathrm{H}(\mathrm{D} \mid \mathrm{A}) g(D,A)=H(D)−H(D∣A) -
C4.5:(信息增益率 g r ( D , A ) \mathrm{g}_{\mathrm{r}}(\mathrm{D}, \mathrm{A}) gr(D,A))
g r ( D , A ) = g ( D , A ) / H ( A ) \mathrm{g}_{\mathrm{r}}(\mathrm{D}, \mathrm{A})=\mathrm{g}(\mathrm{D}, \mathrm{A}) / \mathrm{H}(\mathrm{A}) gr(D,A)=g(D,A)/H(A) -
CART:(基尼指数 Gini ( p ) \operatorname{Gini}(p) Gini(p))
Gini ( p ) = ∑ k = 1 K p k ( 1 − p k ) = 1 − ∑ k = 1 K p k 2 = 1 − ∑ k = 1 K ( ∣ C k ∣ ∣ D ∣ ) 2 \begin{array}{l} \operatorname{Gini}(p)=\sum_{k=1}^{K} p_{k}\left(1-p_{k}\right)=1-\sum_{k=1}^{K} p_{k}^{2} \\ =1-\sum_{k=1}^{K}\left(\frac{\left|C_{k}\right|}{|D|}\right)^{2} \end{array} Gini(p)=∑k=1Kpk(1−pk)=1−∑k=1Kpk2=1−∑k=1K(∣D∣∣Ck∣)2
说明:一个属性的信息增益(率)/gini指数越大,表明属性对样本的熵减少的能力更强,这个属性使得数据由不确定性变成确定性的能力越强。
其中:
- 训练数据集为 D D D, ∣ D ∣ |D| ∣D∣表示样本个数。
- 设有 K K K个类 C k , k = 1 , 2 ⋯ K C_{k}, k=1,2 \cdots K Ck,k=1,2⋯K, ∣ C k ∣ \left|C_{k}\right| ∣Ck∣为属于类 C k C_{k} Ck的样本个数,有 ∑ k ∣ C k ∣ = ∣ D ∣ \sum_{k}\left|C_{k}\right|=|D| ∑k∣Ck∣=∣D∣
- 设特征 A A A有 n n n个不同的取值 { a 1 , a 2 ⋯ a n } \left\{a_{1}, a_{2} \cdots a_{n}\right\} {a1,a2⋯an},根据特征 A A A的取值将 D D D划分为 n n n个子集 D 1 , D 2 ⋯ D n D_{1}, D_{2} \cdots D_{n} D1,D2⋯Dn, ∣ D i ∣ \left|D_{i}\right| ∣Di∣ 为 D i D_{i} Di的样本个数,有 ∑ i ∣ D i ∣ = ∣ D ∣ \sum_{i}\left|D_{i}\right|=|D| ∑i∣Di∣=∣D∣
- 记子集 D i D_{i} Di中属于类 C k C_k Ck的样本的集合为 D i k D_{ik} Dik, ∣ D i k ∣ |D_{ik}| ∣Dik∣为 D i k D_{ik} Dik的样本个数
- 数据集 D D D的经验熵 H ( D ) = − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ log ∣ C k ∣ ∣ D ∣ H(D)=-\sum_{k=1}^{K} \frac{\left|C_{k}\right|}{|D|} \log \frac{\left|C_{k}\right|}{|D|} H(D)=−∑k=1K∣D∣∣Ck∣log∣D∣∣Ck∣
- 特征 A A A对数据集 D D D的经验条件熵 H ( D ∣ A ) = H ( A , D ) − H ( A ) \mathrm{H}(\mathrm{D} \mid \mathrm{A})=\mathrm{H}(\mathrm{A}, \mathrm{D})-\mathrm{H}(\mathrm{A}) H(D∣A)=H(A,D)−H(A)
- 联合熵 H ( A , D ) = − ∑ A , D p ( A , D ) log p ( A , D ) \mathrm{H}(\mathrm{A}, \mathrm{D})=-\sum_{A, D} p(A, D) \log p(A, D) H(A,D)=−∑A,Dp(A,D)logp(A,D)
- 熵 H ( A ) = − ∑ A p ( A ) log p ( A ) \mathrm{H}(\mathrm{A})=-\sum_{A} p(A) \log p(A) H(A)=−∑Ap(A)logp(A)
损失函数/评价函数:
C
(
T
)
=
∑
t
∈
l
e
a
f
N
t
⋅
H
(
t
)
C(T)=\sum_{t \in l e a f} N_{t} \cdot H(t)
C(T)=t∈leaf∑Nt⋅H(t)
其中:
- 假定样本的总类别为 K K K个
- 对于决策树的某叶结点 t t t,假定该叶结点含有样本数目为 N t N_{t} Nt,其中第 k k k类的样本点数目为 n k n_k nk, k = 1 , 2 , … , K k=1,2,…,K k=1,2,…,K
过拟合问题:
-
剪枝
由完全树 T 0 T_0 T0开始,剪枝部分结点得到 T 1 T_1 T1,再次剪枝部分结点得到 T 2 T_2 T2…直到仅剩树根的树$T_k $ ,然后在验证数据集上对这 k k k个树分别评价,选择损失函数最小的树 T α T_α Tα
- 预剪枝
- 后剪枝
-
随机森林
2. 随机森林
Bagging(bootstrap aggregation )策略:
从样本集中重采样(
bootstrap
)(有重复的)选出 n n n个样本在所有属性上,对这n个样本建立分类器(ID3、 C4.5、 CART、 SVM、 Logistic回归等)
重复以上两步 m m m次,即获得了 m m m个分类器
将数据放在这 m m m个分类器上,最后根据这 m m m个分类器的投票结果,决定数据属于哪一类
bootstrap
每次约有36.79%的样本不会出现在bootstrap
所采集的样本集合中,将未参与模型训练的数据称为袋外数据OOB(Out Of Bag)。它可以用于取代测试集用于误差估计。袋外数据误差估计与同训练集一样大小的测试集精度相同 。
随机森林在bagging基础上做了修改:
-
从样本中用
bootstrap
采样选出 n n n个样本; -
从所有属性中随机选择 k k k个属性,选择最佳分割属性作为节点建立CART决策树;
-
重复以上两步 m m m次,即建立了 m m m棵CART决策树;
-
这 m m m个CART形成随机森林,通过投票表决结果,决定数据属于哪一类 。
投票机制:
- 简单投票机制
- 一票否决(一致表决)
- 少数服从多数(最多)
- 有效多数(加权)
- 阈值表决
- 贝叶斯投票机制(加入一些先验知识)
随机森林RF应用:
-
计算样本间相似度:
两样本同时出现在相同叶结点的次数越多,则二者越相似 。
-
计算特征重要度:
计算正例经过的结点,使用经过结点的数目、经过结点的gini系数和等指标。或者,随机替换一列数据,重新建立决策树,计算新模型的正确率变化,从而考虑这一列特征的重要性。
-
异常值检测 :
随机选择特征、随机选择分割点,生成一定深度的决策树iTree,若干颗iTree组成iForest ;计算iTree中样本 x x x从根到叶子的长度 f ( x ) f(x) f(x);计算iForest中 f ( x ) f(x) f(x)的总和 F ( x ) F(x) F(x)若样本x为异常值,它应在大多数iTree中很快从根到达叶子,即 F ( x ) F(x) F(x)较小。
样本不均衡的处理办法:
假定样本数目A类比B类多,且严重不平衡:
- A类下采样Undersampling
- 随机欠采样
- A类分成若干子类,分别与B类进入ML模型
- 基于聚类的A类分割
- B类上采样Oversampling
- 避免欠采样造成的信息丢失
- B类数据合成Synthetic Data Generation
- 随机插值得到新样本
- SMOTE(Synthetic Minority Over-sampling Technique)
- 代价敏感学习Cost Sensitive Learning
- 降低A类权值,提高B类权值
2. 代码实践
代码环境:
- win7+PyCharm 2018.3.1 Professional+python3.7
决策树:
# -*- coding:utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
import pydotplus
# 花萼长度、花萼宽度,花瓣长度,花瓣宽度
iris_feature_E = 'sepal length', 'sepal width', 'petal length', 'petal width'
iris_feature = u'花萼长度', u'花萼宽度', u'花瓣长度', u'花瓣宽度'
iris_class = 'Iris-setosa', 'Iris-versicolor', 'Iris-virginica'
if __name__ == "__main__":
mpl.rcParams['font.sans-serif'] = [u'SimHei']
mpl.rcParams['axes.unicode_minus'] = False
path = 'iris.data' # 数据文件路径
data = pd.read_csv(path, header=None)
x = data[range(4)]
y = pd.Categorical(data[4]).codes
# 为了可视化,仅使用前两列特征
x = x.iloc[:, :2]
x_train, x_test, y_train, y_test = train_test_split(x, y, train_size=0.7, random_state=1)
print y_test.shape
# 决策树参数估计
# min_samples_split = 10:如果该结点包含的样本数目大于10,则(有可能)对其分支
# min_samples_leaf = 10:若将某结点分支后,得到的每个子结点样本数目都大于10,则完成分支;否则,不进行分支
model = DecisionTreeClassifier(criterion='entropy')
model.fit(x_train, y_train)
y_test_hat = model.predict(x_test) # 测试数据
# 保存
# dot -Tpng my.dot -o my.png
# 1、输出
# with open('iris.dot', 'w') as f:
# tree.export_graphviz(model, out_file=f)
# 2、给定文件名
# tree.export_graphviz(model, out_file='iris1.dot')
# 3、输出为pdf格式
dot_data = tree.export_graphviz(model, out_file=None, feature_names=iris_feature_E[:2], class_names=iris_class,
filled=True, rounded=True, special_characters=True)
graph = pydotplus.graph_from_dot_data(dot_data)
graph.write_pdf('iris.pdf')
f = open('iris.png', 'wb')
f.write(graph.create_png())
f.close()
# 画图
N, M = 50, 50 # 横纵各采样多少个值
x1_min, x2_min = x.min()
x1_max, x2_max = x.max()
t1 = np.linspace(x1_min, x1_max, N)
t2 = np.linspace(x2_min, x2_max, M)
x1, x2 = np.meshgrid(t1, t2) # 生成网格采样点
x_show = np.stack((x1.flat, x2.flat), axis=1) # 测试点
print x_show.shape
cm_light = mpl.colors.ListedColormap(['#A0FFA0', '#FFA0A0', '#A0A0FF'])
cm_dark = mpl.colors.ListedColormap(['g', 'r', 'b'])
y_show_hat = model.predict(x_show) # 预测值
print y_show_hat.shape
print y_show_hat
y_show_hat = y_show_hat.reshape(x1.shape) # 使之与输入的形状相同
print y_show_hat
plt.figure(facecolor='w')
plt.pcolormesh(x1, x2, y_show_hat, cmap=cm_light) # 预测值的显示
plt.scatter(x_test[0], x_test[1], c=y_test.ravel(), edgecolors='k', s=150, zorder=10, cmap=cm_dark, marker='*') # 测试数据
plt.scatter(x[0], x[1], c=y.ravel(), edgecolors='k', s=40, cmap=cm_dark) # 全部数据
plt.xlabel(iris_feature[0], fontsize=15)
plt.ylabel(iris_feature[1], fontsize=15)
plt.xlim(x1_min, x1_max)
plt.ylim(x2_min, x2_max)
plt.grid(True)
plt.title(u'鸢尾花数据的决策树分类', fontsize=17)
plt.show()
# 训练集上的预测结果
y_test = y_test.reshape(-1)
print y_test_hat
print y_test
result = (y_test_hat == y_test) # True则预测正确,False则预测错误
acc = np.mean(result)
print '准确度: %.2f%%' % (100 * acc)
# 过拟合:错误率
depth = np.arange(1, 15)
err_list = []
for d in depth:
clf = DecisionTreeClassifier(criterion='entropy', max_depth=d)
clf.fit(x_train, y_train)
y_test_hat = clf.predict(x_test) # 测试数据
result = (y_test_hat == y_test) # True则预测正确,False则预测错误
if d == 1:
print result
err = 1 - np.mean(result)
err_list.append(err)
# print d, ' 准确度: %.2f%%' % (100 * err)
print d, ' 错误率: %.2f%%' % (100 * err)
plt.figure(facecolor='w')
plt.plot(depth, err_list, 'ro-', lw=2)
plt.xlabel(u'决策树深度', fontsize=15)
plt.ylabel(u'错误率', fontsize=15)
plt.title(u'决策树深度与过拟合', fontsize=17)
plt.grid(True)
plt.show()
随机森林:
# -*- coding:utf-8 -*-
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
from sklearn.ensemble import RandomForestClassifier
def iris_type(s):
it = {'Iris-setosa': 0, 'Iris-versicolor': 1, 'Iris-virginica': 2}
return it[s]
# 'sepal length', 'sepal width', 'petal length', 'petal width'
iris_feature = u'花萼长度', u'花萼宽度', u'花瓣长度', u'花瓣宽度'
if __name__ == "__main__":
mpl.rcParams['font.sans-serif'] = [u'SimHei'] # 黑体 FangSong/KaiTi
mpl.rcParams['axes.unicode_minus'] = False
path = '..\\8.Regression\\iris.data' # 数据文件路径
data = pd.read_csv(path, header=None)
x_prime = data[range(4)]
y = pd.Categorical(data[4]).codes
feature_pairs = [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]
plt.figure(figsize=(10, 9), facecolor='#FFFFFF')
for i, pair in enumerate(feature_pairs):
# 准备数据
x = x_prime[pair]
# 随机森林
clf = RandomForestClassifier(n_estimators=200, criterion='entropy', max_depth=3)
clf.fit(x, y.ravel())
# 画图
N, M = 50, 50 # 横纵各采样多少个值
x1_min, x2_min = x.min()
x1_max, x2_max = x.max()
t1 = np.linspace(x1_min, x1_max, N)
t2 = np.linspace(x2_min, x2_max, M)
x1, x2 = np.meshgrid(t1, t2) # 生成网格采样点
x_test = np.stack((x1.flat, x2.flat), axis=1) # 测试点
# 训练集上的预测结果
y_hat = clf.predict(x)
y = y.reshape(-1)
c = np.count_nonzero(y_hat == y) # 统计预测正确的个数
print '特征: ', iris_feature[pair[0]], ' + ', iris_feature[pair[1]],
print '\t预测正确数目:', c,
print '\t准确率: %.2f%%' % (100 * float(c) / float(len(y)))
# 显示
cm_light = mpl.colors.ListedColormap(['#A0FFA0', '#FFA0A0', '#A0A0FF'])
cm_dark = mpl.colors.ListedColormap(['g', 'r', 'b'])
y_hat = clf.predict(x_test) # 预测值
y_hat = y_hat.reshape(x1.shape) # 使之与输入的形状相同
plt.subplot(2, 3, i+1)
plt.pcolormesh(x1, x2, y_hat, cmap=cm_light) # 预测值
plt.scatter(x[pair[0]], x[pair[1]], c=y, edgecolors='k', cmap=cm_dark) # 样本
plt.xlabel(iris_feature[pair[0]], fontsize=14)
plt.ylabel(iris_feature[pair[1]], fontsize=14)
plt.xlim(x1_min, x1_max)
plt.ylim(x2_min, x2_max)
plt.grid()
plt.tight_layout(2.5)
plt.subplots_adjust(top=0.92)
plt.suptitle(u'随机森林对鸢尾花数据的两特征组合的分类结果', fontsize=18)
plt.show()
参考资料:
- https://www.bilibili.com/video/BV1Tb411H7uC
- 统计学习方法第二版_李航
- 西瓜书