1决策树模型
1.1基本概念
决策树类似于我们玩的读心游戏,一个人问问题,另一个人只能回答yes或no。比如:
问:这是个人吗?回答:是
问:是女生吗?回答:不是
问:他戴眼镜吗?回答:是
…
一直猜出回答者的正确答案。如下图所示。
1.2 信息增益与基尼不纯度
在介绍决策树之前我们先来介绍下信息熵,信息熵是约翰·香农根据热力学第二定律,在 1948《通信的数学原理》一书中提出,主要思想是:一个问题不确定性越大,需要获取的信息就越多,信息熵就越大;一个问题不确定性越小,需要获取的信息就越少,信息熵就越小。比如“小张今天会不会在9:00之前到公司”的信息熵就比“小张今天会不会吃早饭”的信息熵要高,因为小张长久以来没有不吃早饭的习惯。
信息熵是这样定义的:假设集合D中第k类样本的比率为pk,(k=1,2,…|y|)
Ent(D)=
在决策树中有两个概念:信息增益(Information Gain)和基尼不纯度(Gini impurity)。
信息增益(Information Gain):划分数据前后数据信息熵的差值。
信息增益纯度越高,纯度提升越大;信息增益纯度越低,纯度提升越小。
信息增益使用的是ID3算法(改进后为C4.5算法)。决策树在选取节点的时候,计算每个特征值划分后的信息增益,选取信息增益最大的节点。
基尼不纯度:反映从集合D中随机取两个样本后,其类别不一致性的概率。
基尼不纯度使用的是CART算法。决策树在集合中要选取基尼不纯度最小的那个节点。
1.3 Sklearn中的决策树构建
Sklearn中使用sklearn.tree.DecisionTreeClassifier构建决策树分类;from sklearn.tree import DecisionTreeRegressor构建决策树回归。
代码语言:javascript复制import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn import tree,datasets
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
def base_of_decision_tree(max_depth):
wine = datasets.load_wine()
# 仅选前两个特征
X = wine.data[:,:2]
y = wine.target
X_train,X_test,y_train,y_test = train_test_split(X, y)
clf = DecisionTreeClassifier(max_depth=max_depth)
clf.fit(X_train,y_train)
#定义图像中分区的颜色和散点的颜色,允许用户使用十六进制颜色码来定义自己所需的颜色库
cmap_light = ListedColormap(['#FFAAAA','#AAFFAA','#AAAAFF'])
cmap_bold = ListedColormap(['#FF0000','#00FF00','#0000FF']
#分别将样本的两个特征值创建图像的横轴和纵轴
x_min,x_max = X_train[:,0].min()-1,X_train[:,0].max() 1
y_min,y_max = X_train[:,1].min()-1,X_train[:,1].max() 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, .02),np.arange(y_min, y_max, .02))
#给每个样本分配不同的颜色, predict:训练后返回预测结果,显示标签值
Z = clf.predict(np.c_[xx.ravel(),yy.ravel()])
Z = Z.reshape(xx.shape)
plt.figure()
# plt.pcolormesh的作用在于能够直观表现出分类边界
plt.pcolormesh(xx,yy,Z,cmap=cmap_light,shading='auto')
#用散点把样本表示出来
plt.scatter(X[:,0],X[:,1],c=y,cmap=cmap_bold,s=20,edgecolors='k')
plt.xlim(xx.min(),xx.max())
plt.ylim(yy.min(),yy.max())
plt.title("Classifier:(max_depth=" str(max_depth) ")")
print("红酒数据训练集(max_depth=" str(max_depth) "):{:.2f}".format(clf.score(X_train,y_train)))
print("红酒数据测试集(max_depth=" str(max_depth) "):{:.2f}".format(clf.score(X_test,y_test)))
plt.show()
输出
代码语言:javascript复制红酒数据训练集(max_depth=1):0.68
红酒数据测试集(max_depth=1):0.69
红酒数据训练集(max_depth=3):0.88
红酒数据测试集(max_depth=3):0.76
红酒数据训练集(max_depth=5):0.90
红酒数据测试集(max_depth=5):0.84
当为max_depth(即树的深度)为1、3、5的时候,划分如下图:
通过图和输出结果,都可以看出,随着max_depth增加,划分越精确( max_depth=1的时候完全是不正确的)。但是我们会发现所有的数据测试集都低于训练集的值,这就是决策树最致命的一点:容易过拟合。
1.4剪枝
解决过拟合的方法是剪枝,预剪枝(Pre-pruning)和后剪枝(post-pruning)。预剪枝及早停止树的增长;后剪枝先形成树,然后再剪枝。Sklearn仅采用预剪枝的策略,预剪枝可以分以下三种方法中的任意一种来解决。
限制最大深度
限制叶节点数最大数目
规定一个节点数据点的最小数目
我们以Sklearn乳腺癌数据采取限制最大深度来看一下防止过拟合的效果。
代码语言:javascript复制from sklearn.datasets import load_breast_cancer
from sklearn.tree import DecisionTreeClassifier
def decision_tree():
cancer = load_breast_cancer()
x_train,x_test,y_train,y_test = train_test_split(cancer.data,cancer.target,stratify=cancer.target,random_state=42)
tree = DecisionTreeClassifier(random_state=0) # 构件树,不剪枝
tree.fit(x_train,y_train)
print("不剪枝,训练数据集上的精度:{:.3f}".format(tree.score(x_train,y_train)))
print("不剪枝,测试数据集上的精度:{:.3f}".format(tree.score(x_test,y_test)))
print("不剪枝,树的深度:{}".format(tree.get_depth()))
tree = DecisionTreeClassifier(max_depth=4,random_state=0) # 构件树,剪枝
tree.fit(x_train,y_train)
print("剪枝,训练数据集上的精度:{:.3f}".format(tree.score(x_train,y_train)))
print("剪枝,测试数据集上的精度:{:.3f}".format(tree.score(x_test,y_test)))
print("剪枝,树的深度:{}".format(tree.get_depth()))
输出
代码语言:javascript复制不剪枝,训练数据集上的精度:1.000
不剪枝,测试数据集上的精度:0.937
不剪枝,树的深度:7
剪枝,训练数据集上的精度:0.988
剪枝,测试数据集上的精度:0.951
剪枝,树的深度:4
可见树的深度为7的时候,测试数据集的得分<训练数据集的得分。但是当树的深度为4的时候,测试数据集的得分几乎等于训练数据集的得分。
1.5 决策树的可视化
为了看到可视化的决策树,首先需要下载并且安装一个软件Graphviz的软件,Graphviz 是一款由AT&T Research和Lucent Bell实验室开源的可视化图形工具。然后打开命令行,输入pip3 install graphviz。接下来运行下面代码。
代码语言:javascript复制from sklearn.tree import export_graphviz
import graphviz
def show_tree():
wine = datasets.load_wine()
# 仅选前两个特征
X = wine.data[:,:2]
y = wine.target
X_train,X_test,y_train,y_test = train_test_split(X, y)
clf = DecisionTreeClassifier(max_depth=3)#为了图片不太大选择max_depth=3
clf.fit(X_train,y_train) export_graphviz(clf,out_file="wine.dot",class_names=wine.target_names,feature_names=wine.feature_names[:2],impurity=False,filled=True)
#打开dot文件
with open("wine.dot") as f:
dot_graph = f.read()
graphviz.Source(dot_graph)
在本地目录下生成wine.dot文件,用Graphviz软件打开,得到如图图片。
总结一下,决策树的优点是:容易可视化和无需对数据进行预处理;缺点是即使采取剪枝也会造成过拟合。解决这个问题最有利的方法是采用随机森林模型。
2 随机森林模型
2.1基本概念
2001年Breiman把分类树组合成随机森林(Breiman 2001a),即在变量(列)的使用和数据(行)的使用上进行随机化,生成很多分类树,再汇总分类树的结果。随机森林在运算量没有显著提高的前提下提高了预测精度。
算法流程:
构建决策树的个数t,单颗决策树的特征个数f,m个样本,n个特征数据集
1 单颗决策树训练
1.1 采用有放回抽样,从原数据集经过m次抽样,获得有m个样本的数据集(可能有重复样本)
1.2 从n个特征里,采用无放回抽样原则,去除f个特征作为输入特征
1.3 在新的数据集(m个样本, f个特征数据集上)构建决策树
1.4 重复上述过程t次,构建t棵决策树
2 随机森林的预测结果
生成t棵决策树,对于每个新的测试样例,综合多棵决策树预测的结果作为随机森林的预测结果。
回归问题:取t棵决策树预测值的平均值作为随机森林预测结果
分类问题:少数服从多数的原则,取单棵的分类结果作为类别随机森林预测结果
在Sklearn中RandomForestClassifier和RandomForestRegressor分类和回归树算法。
2.2 Sklearn中的构建随机森林
代码语言:javascript复制from sklearn.ensemble import RandomForestClassifier
def base_of_decision_tree_forest(n_estimator,random_state):
wine = datasets.load_wine()
# 仅选前两个特征
X = wine.data[:,:2]
y = wine.target
X_train, X_test, y_train, y_test = train_test_split(X, y)
forest = RandomForestClassifier(n_estimators=n_estimator, random_state=random_state, n_jobs=2)
#n_jobs:设置为CPU个数
# 在训练数据集上进行学习
forest.fit(X_train, y_train)
cmap_light = ListedColormap(['#FFAAAA','#AAFFAA','#AAAAFF’])
cmap_bold = ListedColormap(['#FF0000','#00FF00','#0000FF’])
#分别将样本的两个特征值创建图像的横轴和纵轴
x_min,x_max = X_train[:,0].min()-1,X_train[:,0].max() 1
y_min,y_max = X_train[:,1].min()-1,X_train[:,1].max() 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, .02),np.arange(y_min, y_max, .02))
#给每个样本分配不同的颜色
Z = forest.predict(np.c_[xx.ravel(),yy.ravel()])
Z = Z.reshape(xx.shape)
plt.pcolormesh(xx,yy,Z,cmap=cmap_light,shading='auto’)
#用散点把样本表示出来
plt.scatter(X[:,0],X[:,1],c=y,cmap=cmap_bold,s=20,edgecolors='k’)
plt.xlim(xx.min(),xx.max())
plt.ylim(yy.min(),yy.max())
print("红酒数据随机森林训练集得分(n_estimators:" str(n_estimator) ",random_state:" str(random_state) "):{:.2f}".format(forest.score(X_train,y_train)))
print("红酒数据随机森林测试集得分(n_estimators:" str(n_estimator) ",random_state:" str(random_state) "):{:.2f}".format(forest.score(X_test,y_test)))
def tree_forest():
#定义图像中分区的颜色和散点的颜色
figure,axes = plt.subplots(4,4,figsize =(100,10))
plt.subplots_adjust(hspace=0.95)
i = 0
for n_estimator in range(4,8):
for random_state in range(2,6):
plt.subplot(4,4,i 1)
plt.title("n_estimator:" str(n_estimator) "random_state:" str(random_state))
plt.suptitle("Classifier:RandomForest")
base_of_decision_tree_forest(n_estimator,random_state)
i = i 1
plt.show()
得到的结果总结如下表。
2.3 随机森林避免过拟合
我们以2个月亮数据进行分析。
代码语言:javascript复制import mglearn
def my_RandomForet():
# 生成一个用于模拟的二维数据集
X, y = datasets.make_moons(n_samples=100, noise=0.25, random_state=3)
# 训练集和测试集的划分
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y,random_state=42)
# 初始化一个包含 5 棵决策树的随机森林分类器
forest = RandomForestClassifier(n_estimators=5, random_state=2)
# 在训练数据集上进行学习
forest.fit(X_train, y_train)
# 可视化每棵决策树的决策边界
fig, axes = plt.subplots(2, 3, figsize=(20, 10))
for i, (ax, tree) in enumerate(zip(axes.ravel(), forest.estimators_)):
ax.set_title('Tree {}'.format(i))
mglearn.plots.plot_tree_partition(X_train, y_train, tree, ax=ax)
print("决策树" str(i) "训练集得分:{:.2%}".format(tree.score(X_train,y_train)))
print("决策树" str(i) "测试集得分:{:.2%}".format(tree.score(X_test,y_test)))
# 可视化集成分类器的决策边界
print("随机森林训练集得分:{:.2%}".format(forest.score(X_train,y_train)))
print("随机森林测试集得分:{:.2%}".format(forest.score(X_test,y_test)))
mglearn.plots.plot_2d_separator(forest, X_train, fill=True, ax=axes[-1, -1],alpha=0.4)
axes[-1, -1].set_title('Random Forest')
mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train)
plt.show()
输出
虽然决策树3不存在过拟合,决策树4的差值与随机森林得分一致,但是随机森林得分比他们都要高。
2.4案例
下面我们通过一个案例来介绍一下随机森林的使用,案例的内容是预测某人的收入是否可以>50K。我们到http://archive.ics.uci.edu/ml/machine-learning-databases/adult/网上下载adult.dat文件,它的格式是csv文件的形式,把它改为adult.csv,可用Excel文件打开。
代码语言:javascript复制import pandas as pd
def income_forecast():
data=pd.read_csv('adult.csv', header=None,index_col=False,
names=['年龄','单位性质','权重','学历','受教育时长',
'婚姻状况','职业','家庭情况','种族','性别',
'资产所得','资产损失','周工作时长','原籍',
'收入'])
#为了方便展示,我们选取其中一部分数据
data_title = data[['年龄','单位性质','学历','性别','周工作时长','职业','收入']]
print(data_title.head())
输出
正如我们前面所述,通过pd.read_csv()函数可以把csv文件给出来。
代码语言:javascript复制#利用shape方法获取数据集的大小
data_title.shape
输出
代码语言:javascript复制data_title.shape:
(32561, 7)
说明里面有32561个样本,7个属性。
代码语言:javascript复制print("data_title.shape:n",data_title.shape)
data_title.info()
输出
代码语言:javascript复制Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 年龄 32561 non-null int64
1 单位性质 32561 non-null object
2 学历 32561 non-null object
3 性别 32561 non-null object
4 周工作时长 32561 non-null int64
5 职业 32561 non-null object
6 收入 32561 non-null object
dtypes: int64(2), object(5)
memory usage: 1.7 MB
可以看出,单位性质、学历、性别、职业、收入均不是数值类型,于是使用pd.get_dummies()函数对数据进行处理。
代码语言:javascript复制##1-数据准备
#1.2 数据预处理
#用get_dummies将文本数据转化为数值
data_dummies=pd.get_dummies(data_title)
print("data_dummies.shape:n",data_dummies.shape)
#对比样本原始特征和虚拟变量特征---df.columns获取表头
print('样本原始特征:n',list(data_title.columns),'n')
print('虚拟变量特征:n',list(data_dummies.columns))
输出
样本原始特征:
['年龄', '单位性质', '学历', '性别', '周工作时长', '职业', '收入']
虚拟变量特征:
['年龄', '周工作时长', '单位性质_ ?', '单位性质_ Federal-gov', '单位性质_ Local-gov', '单位性质_ Never-worked', '单位性质_ Private', '单位性质_ Self-emp-inc', '单位性质_ Self-emp-not-inc', '单位性质_ State-gov', '单位性质_ Without-pay', '学历_ 10th', '学历_ 11th', '学历_ 12th', '学历_ 1st-4th', '学历_ 5th-6th', '学历_ 7th-8th', '学历_ 9th', '学历_ Assoc-acdm', '学历_ Assoc-voc', '学历_ Bachelors', '学历_ Doctorate', '学历_ HS-grad', '学历_ Masters', '学历_ Preschool', '学历_ Prof-school', '学历_ Some-college', '性别_ Female', '性别_ Male', '职业_ ?', '职业_ Adm-clerical', '职业_ Armed-Forces', '职业_ Craft-repair', '职业_ Exec-managerial', '职业_ Farming-fishing', '职业_ Handlers-cleaners', '职业_ Machine-op-inspct', '职业_ Other-service', '职业_ Priv-house-serv', '职业_ Prof-specialty', '职业_ Protective-serv', '职业_ Sales', '职业_ Tech-support', '职业_ Transport-moving', '收入_ <=50K', '收入_ >50K']
这样把特性与值进行合并,比如'单位性质'分成了'单位性质_ ?', '单位性质_ Federal-gov', '单位性质_ Local-gov', '单位性质_ Never-worked', '单位性质_ Private', '单位性质_ Self-emp-inc', '单位性质_ Self-emp-not-inc', '单位性质_ State-gov', '单位性质_ Without-pay'几部分。
代码语言:javascript复制print(data_dummies.head())
输出
这里0表示符合这个条件,1表示不符合,比如:单位性质_ Local-gov=1表示地方政府。
代码语言:javascript复制#1.3 选择特征
#按位置选择---位置索引---df.iloc[[行1,行2],[列1,列2]]---行列位置从0开始,多行多列用逗号隔开,用:表示全部(不需要[])
#选择除了收入外的字段作为数值特征并赋值给x---df[].values
x=data_dummies.loc[:,'年龄':'职业_ Transport-moving'].values
#将'收入_ >50K'‘作为预测目标y
y = data_dummies['收入_ >50K'].values
#查看x,y数据集大小情况
print('特征形态:{} 标签形态:{}'.format(x.shape, y.shape))
输出
代码语言:javascript复制 特征形态:(32561, 44) 标签形态:(32561,)
32561条数据,44个属性。
代码语言:javascript复制##2-数据建模---拆分数据集/模型训练/测试
#2.1将数据拆分为训练集和测试集---要用train_test_split模块中的train_test_split()函数,随机将75%数据化为训练集,25%数据为测试集
#导入数据集拆分工具
#拆分数据集---x,y都要拆分,rain_test_split(x,y,random_state=0),random_state=0使得每次生成的伪随机数不同
x_train,x_test,y_train,y_test=train_test_split(x,y,random_state=0)
#查看拆分后的数据集大小情况
print('x_train_shape:{}'.format(x_train.shape))
print('x_test_shape:{}'.format(x_test.shape))
print('y_train_shape:{}'.format(y_train.shape))
print('y_test_shape:{}'.format(y_test.shape))
输出
代码语言:javascript复制x_train_shape:(24420, 44)
x_test_shape:(8141, 44)
y_train_shape:(24420,)
y_test_shape:(8141,)
24420条训练数据,8141条测试数据。
代码语言:javascript复制##2、数据建模---模型训练/测试---决策树算法
#2.2 模型训练---算法.fit(x_train,y_train)
#使用算法
tree = DecisionTreeClassifier(max_depth=5)#这里参数max_depth最大深度设置为5
#算法.fit(x,y)对训练数据进行拟合
tree.fit(x_train, y_train)
##2、数据建模---拆分数据集/模型训练/测试---决策树算法
#2.3 模型测试---算法.score(x_test,y_test)
score_test=tree.score(x_test,y_test)
score_train=tree.score(x_train,y_train)
print('test_score:{:.2%}'.format(score_test))
print('train_score:{:.2%}'.format(score_train))
输出
代码语言:javascript复制test_score:79.62%
train_score:80.34%
测试数据得分为79.62%,我们就用这个模型进行预测,假设小王为37岁,机关工作,硕士,男,每周工作40小时,文员,它对应的数据为=[[37,40,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0]],预测代码如下。
代码语言:javascript复制#3、模型应用---算法.predict(x_new)---决策树算法
#导入要预测数据--可以输入新的数据点,也可以随便取原数据集中某一数据点,但是注意要与原数据结构相同x_new=[[37,40,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0]]
#37岁,机关工作,硕士,男,每周工作40小时,文员
prediction=tree.predict(x_new)
print('预测数据:{}'.format(x_new))
print('预测结果:{}'.format(prediction))
输出
代码语言:javascript复制预测数据:[[37, 40, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
预测结果:[0]
所以小王的薪水>50K。(本数据由1994年美国人口普查数据库抽取出来,仅供学习使用。)
3.用sklearn数据测试所有决策树和随机森林模型
修改文件machinelearn_data_model.py。
代码语言:javascript复制…
def machine_learn(data,model):
#支持的模型
modeltype = "LinearRegression,LogisticRegression,Ridge,Lasso,SVM,sm,KNeighborsClassifier,LinearSVR,SVC_linear,SVC_sigmoid,SVC_poly,SVR_linear,SVR_sigmoid,SVR_poly,"
modeltype = "DecisionTreeClassifie,DecisionTreeRegressor,RandomForestClassifie,RandomForestRegressor"
#决策树的深度
max_depth = 5
#随机森林的n_estimators和random_state
n_estimators = 5
random_state = 2
#n_jobs为CPU的核数
n_jobs=2
…
elif model == "DecisionTreeClassifie":
if data == "boston":
y_train = y_train.astype('int')
y_test = y_test.astype('int')
md = DecisionTreeClassifier(max_depth=max_depth).fit(X_train, y_train)
elif model == "DecisionTreeRegressor":
md = DecisionTreeRegressor(max_depth=max_depth).fit(X_train, y_train)
elif model == "RandomForestClassifier":
if data == "boston":
y_train = y_train.astype('int')
y_test = y_test.astype('int')
md = RandomForestClassifier(n_estimators=n_estimators, random_state=random_state, n_jobs=n_jobs).fit(X_train, y_train)
elif model == "RandomForestRegressor":
md = RandomForestRegressor(n_estimators=n_estimators, random_state=random_state, n_jobs=n_jobs).fit(X_train, y_train)
else:
return "提供的模型错误,包括:" modeltype
…
在这里考虑:
DecisionTreeClassifie算法在波士顿房价下要求目标y必须为int类型,所以做了判断;
决策树的深度:max_depth = 5;
随机森林的n_estimators= 5和random_state= 2;
n_jobs为CPU的核数,设置为变量。
这样,我们就可以对指定模型指定数据进行定量分析
from machinelearn_data_model import data_for_model
def Tree_and_forest_for_all_data_and_model():
datas = ["iris","wine","breast_cancer","diabetes","boston","two_moon"]
models = ["DecisionTreeClassifie","DecisionTreeRegressor","RandomForestClassifie","RandomForestRegressor"]
for data in datas:
for model in models:
data_for_model.machine_learn(data,model)
我们对测试结果进行比较: