监督学习-分类-决策树
决策树使用树形分支结构分类事物
例:
- 小丽找对象,要求:高、帅、富
- 小明找对象,要求:美美美
if height >= 172:
if hansom = '帅':
if rich >= 5000000:
print('小哥哥我晚上有空!')
else:
print('加个微信吧!')
else:
print('孩子放学了,我该做饭去了!')
else:
print('呵呵,洗澡去了!')
构造二叉树结构的两个指标:
- 在什么地方分支(特征的先后次序,执行效率不同)
- 分支的阈值定多数合适?
所谓的决策树算法,就是通过数据自动学习出二叉树的分支维度顺序和每个分支的阈值
与一般的分支结构不同,决策树的分支条件(特征维度、阈值)通过训练得到,而非手动构造
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
决策树skicit-learn实现
1:获取数据,特征工程,样本数据(特征和标签)
iris = load_iris()
# iris
X = iris['data'][:, 2:]
y = iris['target']
# X
2:将样本数据分为训练集和测试集
为方便可视化理解,只取第2和第3列两维特征
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.3, random_state=3)
3:算法:创建分类器
- 调包,调用不同分类器
- 调参,调节超参数
from sklearn.tree import DecisionTreeClassifier
# 创建空分类器
# my_classifier = DecisionTreeClassifier()
my_classifier = DecisionTreeClassifier(max_depth=2, criterion='entropy', random_state=42)
# # max_depth最大深度,entropy信息熵算法,随机种子
my_classifier
DecisionTreeClassifier(class_weight=None, criterion='entropy', max_depth=2,
max_features=None, max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort=False,
random_state=42, splitter='best')
4:训练:用训练集(特征和标签)训练分类器
my_classifier.fit(X_train, y_train)
DecisionTreeClassifier(class_weight=None, criterion='entropy', max_depth=2,
max_features=None, max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort=False,
random_state=42, splitter='best')
5:预测:使用测试集(特征)预测标签
predictions = my_classifier.predict(X_test)
predictions
array([0, 0, 0, 0, 0, 2, 1, 0, 2, 1, 1, 0, 1, 1, 2, 0, 1, 2, 2, 0, 2, 2,
2, 1, 0, 2, 1, 1, 1, 1, 0, 0, 2, 1, 0, 0, 2, 0, 2, 1, 2, 1, 0, 0,
2])
predictions.shape
(45,)
6:用测试集自带的标签和预测的标签比较,评价分类器的准确率
from sklearn.metrics import accuracy_score
accuracy_score(predictions, y_test) # 正确率
0.9555555555555556
决策树原理可视化
# 绘图函数,不用写,直接调用
def plot_decision_boundary(model, axis):
x0, x1 = np.meshgrid(
np.linspace(axis[0], axis[1], int((axis[1]-axis[0])*100)).reshape(-1, 1),
np.linspace(axis[2], axis[3], int((axis[3]-axis[2])*100)).reshape(-1, 1),
)
X_new = np.c_[x0.ravel(), x1.ravel()]
y_predict = model.predict(X_new)
zz = y_predict.reshape(x0.shape)
from matplotlib.colors import ListedColormap
custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
plt.contourf(x0, x1, zz, cmap=custom_cmap)
plot_decision_boundary(my_classifier, axis=[0.5, 7.5, 0, 3])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.scatter(X[y==2, 0], X[y==2, 1])
<matplotlib.collections.PathCollection at 0x134be1d0>
[外链图片转存失败(img-RkKTIih7-1566998078176)(output_21_1.png)]
下面代码,生成可视化文件的库:graphviz需要安装,不同环境安装方式不同
- 可以使用 export_graphviz 导出器以 Graphviz 格式导出决策树
- 如果你是用 conda 来管理包,那么安装 graphviz 二进制文件和 python 包可以用以下指令安装:
# Conda安装库命令
conda install python-graphviz
注:网络问题可能需要翻墙
pdf文件是在整个 iris 数据集上训练的决策树树的 graphviz 导出示例,其结果被保存在 iris_xxx.pdf 中:
- iris_entropy.pdf
- iris_gini.pdf
生成文档中:
- gini值为当前节点系统基尼系数
- entropy值为当前节点系统熵值
- samples值为此分支节点下还有多少值待拆分
- values值为待拆分值的分类
import graphviz
from sklearn.tree import export_graphviz
# # 基尼系数算法
# dot_data = export_graphviz(my_classifier, out_file=None)
# graph = graphviz.Source(dot_data)
# graph.render("iris_gini")
# # 信息熵算法
# dot_data = export_graphviz(my_classifier, out_file=None)
# graph = graphviz.Source(dot_data)
# graph.render("iris_entropy")
用下面测试数据人工模拟决策树分类过程
模拟时,data的下标是0,1,2,3,target的结果是0,1,2
X[0], X[51], X[102]
(array([1.4, 0.2]), array([4.5, 1.5]), array([5.9, 2.1]))
y[0], y[51], y[102]
(0, 1, 2)
决策树原理
- 每个分支节点在哪个特征维度划分?
- 某个特征维度在什么值划分?
根据某一特征维度下的某一阈值进行二分,划分后使得整个系统信息熵降低
信息熵
信息熵:不确定性的度量
- 封闭系统,不确定性越高,熵值越大,越混乱
- 封闭系统,不确定性越低,熵值越小,越规律
信息熵公式:$ H=-\sum ^{k}{i=1}p{i}\log \left( p_{i}\right) $
- 解释:一个封闭系统中,有k类信息,每类信息的比例为p
信息、能量、物质三者是可以相互转换的
例:鸢尾花数据集,共有3类鸢尾花,每类鸢尾花所占比例为1/3,p1/p2/p3都等于1/3,本系统熵值为:
$ H= -\dfrac {1}{3}\cdot \log \left( \dfrac {1}{3}\right) -\dfrac {1}{3}\cdot \log \left( \dfrac {1}{3}\right) -\dfrac {1}{3}\cdot \log \left( \dfrac {1}{3}\right) =1.0986 $
-(1/3) * np.log2(1/3) - (1/3) * np.log2(1/3) - (1/3) * np.log2(1/3) # byte 比特
1.584962500721156
例:一个封闭系统,三种花所占比例为:1/10、2/10、7/10,本系统熵值为:
-(1/10) * np.log2(1/10) - (2/10) * np.log2(2/10) - (7/10) * np.log2(7/10)
1.1567796494470395
例:一个封闭系统,三种花所占比例为:1, 0, 0,本系统熵值为:
-1/1 * np.log2(1/1) - 0 - 0 # 完全确定的系统,没有不确定性
-0.0
1.58 - 1.15 # 信息增益,传入一条信息到封闭空间,导致空间熵值下降,下降数就是信息量
# 原封闭系统,熵值最大就是1.58,最小是0
0.43000000000000016
信息熵图像
- 设系统分两类,一类比例为x,则另一类比例为1-x
- 代入信息熵公式:H = -x*log(x)-(1-x)*log(1-x)
def en(x):
return -x * np.log2(x) - (1-x) * np.log2(1-x)
x = np.linspace(0.01, 0.99, 200) # 对数的底大于0且不等于1
x
plt.plot(x, en(x)) # x轴为分类的比例,y轴为熵值
plt.xticks(np.arange(10)*0.1)
plt.grid(linewidth=0.2)
plt.show()
[外链图片转存失败(img-112FJKKi-1566998078184)(output_38_0.png)]
结论:2分类系统中,类别比例0.5的,熵值最大,不确定性最高
使用信息熵寻找最优划分
- 遍历特征数据下所有可划分特征维度和所有取值
- 当划分一种特征一个值后,系统熵值降到最低,此特征下的此值就是本分支节点
- 继续用分割后熵值不为0的特征数据划分下一分支节点,循环往复
- 直到整个系统熵降到最低
手写决策树算法
使用信息熵作为指标划分数据为不同类别
from collections import Counter
# 计算系统总信息熵的流程
y
Counter(y)
res = 0
for i in Counter(y).values():
print(i)
p = i / len(y) # 每类值出现的概率
res += -p * np.log2(p)
res
50
50
50
1.584962500721156
# 划分类别函数
def split(X, y, d, value): # 特征,标签,维度,阈值
index_a = X[:, d] <= value # 布尔索引
index_b = X[:, d] > value
# 返回被划分后的两类数据(特征和标签)
return X[index_a], X[index_b], y[index_a], y[index_b]
# 计算信息熵的函数
def entropy(y):
counter = Counter(y)
res = 0.0
for i in counter.values():
p = i / len(y) # 每类值出现的概率
res += -p * np.log2(p)
return res
# 寻找最佳维度和阈值,让每次划分后系统信息熵下降最大(信息熵最低)
def try_split(X, y):
best_entropy = float('inf') # 系统拆分后的总信息熵,设为正无穷大
best_d, best_v = -1, -1 # 划分维度和本维度下的划分阈值
# 遍历特征列
# d的取值为0, 1,总共只有2两列,0代表花瓣长1代表花瓣宽
for d in range(X.shape[1]): # 遍历特征数据X最外维的值(遍历所有特征列)
# 遍历特征行(取每两行 特征值的中间值)
sorted_index = np.argsort(X[:, d]) # 排序特征行(d列的所有行)
for i in range(1, len(X)): #
x1 = X[sorted_index[i - 1], d] # 本列特征值i- 1
x2 = X[sorted_index[i], d]
if x1 != x2: # 如果相等则没有划分价值,跳过代码
v = (x1 + x2) / 2 # 候选阈值
# 数据用本维度和阈值划分为left和right两部分
X_l, X_r, y_l, y_r = split(X, y, d, v)
# 本阈值划分后的系统熵值
e = entropy(y_l) + entropy(y_r)
if e < best_entropy: # 如果切分后的新系统熵值更小,更新参数
best_entropy, best_d, best_v = e, d, v
return best_entropy, best_d, best_v
try_split(X, y)
(1.0, 0, 2.45)
best_entropy, best_d, best_v = try_split(X_train, y_train)
print('最低系统信息熵为:', best_entropy)
print('最佳划分维度为:', best_d)
print('本维度最佳划分阈值为:', best_v)
最低系统信息熵为: 1.0
最佳划分维度为: 0
本维度最佳划分阈值为: 2.45
# 第一次划分后的结果
X1_l, X1_r, y1_l, y1_r = split(X_train, y_train, best_d, best_v)
X1_l.shape, X1_r.shape, y1_l.shape, y1_r.shape
((33, 2), (72, 2), (33,), (72,))
# 查看划分后的左侧标签,信息熵为0,说明完全划分完毕
entropy(y1_l), entropy(y1_r) # 右侧熵不为0,有待继续划分
(0.0, 1.0)
# 继续划分右侧数据
best_entropy2, best_d2, best_v2 = try_split(X1_r, y1_r)
print('最低系统信息熵为:', best_entropy2)
print('最佳划分维度为:', best_d2)
print('本维度最佳划分阈值为:', best_v2)
最低系统信息熵为: 0.5898925289094291
最佳划分维度为: 0
本维度最佳划分阈值为: 4.75
# 第二次划分
X2_l, X2_r, y2_l, y2_r = split(X1_r, y1_r, best_d2, best_v2)
X2_l.shape, X2_r.shape, y2_l.shape, y2_r.shape
((34, 2), (38, 2), (34,), (38,))
entropy(y2_l), entropy(y2_r)
(0.19143325481419343, 0.3984592740952357)
系统分支熵值仍不为0,还可继续划分,略
刚才的运算构造的决策树为:
if X[0] <= 2.45:
X_left_en = 0.0
print('第一种鸢尾花,分类完毕')
else:
if X[0] <= 4.75:
left_en = 0.191
print('第二种鸢尾花')
else:
right_en = 0.398
print('第三种鸢尾花')
基尼系数
基尼系数公式: G = 1 − ∑ i = 1 k p i 2 G=1-\sum_{i=1}^{k} p_{i} 2 G=1−∑i=1kpi2
例:三种鸢尾花,比例都为1/3,基尼系数为: G = 1 − ( 1 3 ) 2 − ( 1 3 ) 2 − ( 1 3 ) 2 = 0.6666 G=1-\left(\frac{1}{3}\right)^{2}-\left(\frac{1}{3}\right)^{2}-\left(\frac{1}{3}\right)^{2}=0.6666 G=1−(31)2−(31)2−(31)2=0.6666
1 - (1/3) ** 2 - (1/3) ** 2 - (1/3) ** 2
0.6666666666666665
例:三种花,比例为:1/10,2/10,7/10,基尼系数为:
1 - ((1/10)**2 + (2/10)**2 + (7/10)**2)
0.4600000000000001
例:三种花,比例为:1,0,0,基尼系数为:
1 - (1**2 + 0 + 0)
0
基尼系数图像
- 设系统分两类,一类比例为x,则另一类比例为1-x
- 代入信息熵公式: G = 1 − ( x 2 + ( 1 − x ) ∗ 2 ) = − 2 x 2 + 2 x G=1-\left(x^2+(1-x)* 2\right)=-2x^2+2x G=1−(x2+(1−x)∗2)=−2x2+2x# 抛物线
def gini(x):
return 1 - (x ** 2 + (1-x) ** 2)
x = np.linspace(0.01, 0.99, 200) # 对数的底大于0且不等于1
x
plt.plot(x, gini(x)) # x轴为分类的比例,y轴为熵值
plt.xticks(np.arange(10)*0.1)
plt.grid(linewidth=0.2)
plt.show()
[外链图片转存失败(img-lOdBZ28k-1566998078187)(output_61_0.png)]
def en(x):
return -x * np.log2(x) - (1-x) * np.log2(1-x)
def gini(x):
return 1 - (x ** 2 + (1-x) ** 2)
x = np.linspace(0.01, 0.99, 200) # 对数的底大于0且不等于1
plt.plot(x, en(x), color='r', alpha=0.8, label='en') # x轴为分类的比例,y轴为熵值
plt.plot(x, gini(x)+0.5, color='g', alpha=0.8, label='gini')
plt.xticks(np.arange(10)*0.1)
plt.grid(linewidth=0.2)
plt.legend()
<matplotlib.legend.Legend at 0x531c748>
[外链图片转存失败(img-Yh21dqip-1566998078191)(output_62_1.png)]
基尼系数和信息熵比较
- 信息熵计算比基尼系数更慢(对数运算)
- sklearn默认基尼系数
- 二者差异较小,大部分数据下优劣相当
决策树作为一个算法类别,有很多不同实现
- ID3
- 只能做分类,分支为二分或多分
- 使用信息增益做分隔
- C4.5
- 只能做分类,分支为二分或多分
- 使用信息增益比来分特征(惩罚了值类别特别多的特征)
- C5.0
- C4.5的性能提升版
- CART
- Classification And Regression Tree,分类和回归树
- 能做分类也能做回归(可以用分类后值的概率作为回归结果)
- 分支为二叉树
- 使用基尼系数来分特征
sklearn内的决策树算法实现为CART算法的最优版本
决策树剪枝:对一些参数进行平衡
剪去枝干,让树变小,目的是:
- 降低复杂度
- 解决过拟合
过拟合和模型精确度是一对互相矛盾的指标:
- 拟合程度越低,模型预测越不精确
- 精确度越高,越容易过拟合
- 二者需要取得平衡,在适度拟合的前提下,模型预测达到最精确
选择模型的标准:如果两个模型精度一样,效率也一样,选择更简单那个
- 奥卡姆剃刀原理
- 如无必要,勿增实体
科学思维:车库里的龙
import numpy as np
import matplotlib.pyplot as plt
# 生成非线性测试数据
from sklearn import datasets
X, y = datasets.make_moons(noise=0.25, random_state=666) # 噪点参数0.25
X[:5]
array([[ 1.09760106, 0.85766164],
[-0.01322635, 0.07455881],
[-0.48210848, 0.86080586],
[-0.91727991, 0.82195148],
[-0.84571072, 0.61547083]])
y
array([1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1,
1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0,
0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0,
0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0], dtype=int64)
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
<matplotlib.collections.PathCollection at 0x53864e0>
[外链图片转存失败(img-Lp7ACHtZ-1566998078196)(output_67_1.png)]
默认参数训练模型
from sklearn.tree import DecisionTreeClassifier
dt_clf = DecisionTreeClassifier()
dt_clf.fit(X, y)
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
max_features=None, max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort=False,
random_state=None, splitter='best')
# 默认参数训练模型可视化
plot_decision_boundary(dt_clf, axis=[-1.5, 2.5, -1.0, 1.5])
# axis为横轴和众轴的范围
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
# 分支规则太细,过拟合明显
<matplotlib.collections.PathCollection at 0x5468128>
[外链图片转存失败(img-8LB320kK-1566998078208)(output_70_1.png)]
# 设分支最大深度为2(值越小越不容易过拟合)
# 分割两次,每次都限制最大和最小范围
dt_clf2 = DecisionTreeClassifier(max_depth=2)
dt_clf2.fit(X, y)
plot_decision_boundary(dt_clf2, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
<matplotlib.collections.PathCollection at 0xfd38be0>
[外链图片转存失败(img-7iaWO4DJ-1566998078215)(output_71_1.png)]
# 节点至少有x个样本数据才会继续分支(值越高越不容易过拟合)
dt_clf3 = DecisionTreeClassifier(min_samples_split=10)
dt_clf3.fit(X, y)
plot_decision_boundary(dt_clf3, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
<matplotlib.collections.PathCollection at 0xfbe3198>
[外链图片转存失败(img-wBIJ2scR-1566998078218)(output_72_1.png)]
# 叶子节点至少应该有x个样本数据(最终节点内最少样本数,越大越不容易过拟合)
dt_clf4 = DecisionTreeClassifier(min_samples_leaf=6)
dt_clf4.fit(X, y)
plot_decision_boundary(dt_clf4, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
<matplotlib.collections.PathCollection at 0xfc56710>
[外链图片转存失败(img-KcplM0NA-1566998078223)(output_73_1.png)]
# 最多有多少个叶子节点(越少越不容易过拟合)
dt_clf5 = DecisionTreeClassifier(max_leaf_nodes=4)
dt_clf5.fit(X, y)
plot_decision_boundary(dt_clf5, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
<matplotlib.collections.PathCollection at 0xfcc6c88>
[外链图片转存失败(img-B1ps3gwm-1566998078228)(output_74_1.png)]
# # 更多参数见文档
# help(DecisionTreeClassifier)
决策树解决回归问题
from sklearn import datasets
boston = datasets.load_boston()
X = boston.data
y = boston.target
# X
X.shape
(506, 13)
y[:10]
array([24. , 21.6, 34.7, 33.4, 36.2, 28.7, 22.9, 27.1, 16.5, 18.9])
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
from sklearn.tree import DecisionTreeRegressor
dt_reg = DecisionTreeRegressor()
dt_reg.fit(X_train, y_train) # 训练
DecisionTreeRegressor(criterion='mse', max_depth=None, max_features=None,
max_leaf_nodes=None, min_impurity_decrease=0.0,
min_impurity_split=None, min_samples_leaf=1,
min_samples_split=2, min_weight_fraction_leaf=0.0,
presort=False, random_state=None, splitter='best')
dt_reg.predict(X_test) # 预测
array([27.1, 50. , 20.4, 34.9, 21.6, 21.1, 17.8, 20.5, 28.5, 16.7, 8.8,
23.9, 22.9, 16.7, 22.9, 26.4, 32.5, 18.4, 14.1, 30.1, 23.3, 10.4,
10.2, 48.3, 27.5, 15. , 21.2, 13.4, 36.1, 32.2, 43.1, 9.5, 31.5,
17.8, 22.6, 22.4, 36. , 32.5, 23.1, 13.3, 24. , 20.6, 26.6, 16.8,
24.1, 10.2, 30.1, 34.9, 24.4, 22.7, 24.6, 23.3, 50. , 16.8, 21.4,
18.8, 21.7, 15.2, 14.1, 15.6, 20.9, 20.4, 28. , 14.9, 14.3, 10.4,
22.4, 24. , 23.8, 20.3, 5.6, 31.5, 18.2, 14.9, 46. , 22.4, 5.6,
23.8, 19. , 32.7, 28.4, 22. , 10.2, 30.1, 36.1, 19.4, 23.3, 24.3,
19.7, 50. , 15.6, 34.6, 24.6, 32.5, 19.5, 13.3, 29.8, 23.3, 13.3,
16.3, 17.8, 16.1, 24.8, 14.1, 17.7, 23.1, 16.1, 21.7, 22.6, 23.1,
48.3, 20.5, 22.4, 5.6, 22.2, 24.4, 17.8, 13.3, 33.2, 16.8, 21.9,
20.3, 25.1, 12.3, 10.4, 33.4, 12.7])
dt_reg.score(X_train, y_train) # 训练集特征预测效果100%
1.0
dt_reg.score(X_test, y_test) # 测试集特征预测过小(58%),说明模型过拟合比较严重,需要调参
0.5883659617972887
# help(DecisionTreeRegressor)
决策树的局限
- 分类边界横平竖直,不能如实反映数据真实分隔情况
- 对某些边界值很敏感,数据一点小改变会导致结果发生很大变化
决策树的优势:
- 结果具有可解释性
- 决策树模型之前的差异性大,模型多样化,最适合做集成学习
实际工作中,决策树一般不单独使用,而是和转为集成算法中的随机森林,更好的应用