ML–决策树与随机森林
在生活中,我们经常遇到一些事情需要作出决策来应对。说到决策,自然想到决策树算法,而说到决策树算法,又自然会想到随机森林
主要涉及的知识点有:
- 决策树的基本原理和构造
- 决策树的优势和不足
- 随机森林的基本原理和构造
- 随机森林的优势和不足
- 实例演示:相亲事件
一.决策树
决策树是一种在分类与回归中都有非常广泛应用的算法,它的原理是通过对一系列问题进行if/else
的推导,最终实现决策
1.决策树的基本原理
举个例子:假设出题者心理想的是Google,百度,Facebook,阿里
,四个公司中的一个,则提问决策树:
最终的4个节点,也就是4个公司的名字,被称为决策树的树叶。例子中的这棵决策树只有4片树叶,所以通过手动的方式就可以进行建模。但是如果样本的特征特别多,就不得不使用机器学习的办法来进行建模了
2.决策树的构建
下面我们先载入酒的数据集,然后将它做成训练集和测试集,输入代码如下:
# 导入numpy
import numpy as np
# 导入画图工具
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
# 导入tree模型和数据集加载工具
from sklearn import tree,datasets
# 导入数据集拆分工具
from sklearn.model_selection import train_test_split
wine=datasets.load_wine()
# 只选取数据集的前两个特征
X=wine.data[:,:2]
y=wine.target
# 将数据集拆分为训练集和测试集
X_train,X_test,y_train,y_test=train_test_split(X,y)
现在完成了数据集的准备,开始用决策树分类器进行分类
注意 :为了便于图形进行演示,我们仍然只选取了数据集中样本的前两个特征
# 设定决策树分类器最大深度为1
clf=tree.DecisionTreeClassifier(max_depth=1)
# 拟合训练数据集
clf.fit(X_train,y_train)
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=1,
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')
[结果分析] 把分类器的参数返回,这些参数中,我们先关注其中之一,就是max_depth
参数。这个参数指的是决策树的深度,也就是所问问题的数量,问题数量越多,就代表决策树的深度
现在我们看看分类器的表现如何,我们把图形画出来:
# 定义图像中分区的颜色和散点的颜色
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=clf.predict(np.c_[xx.ravel(),yy.ravel()])
# 给每个分类中的样本分配不同的颜色
Z=z.reshape(xx.shape)
plt.figure()
plt.pcolormesh(xx,yy,Z,cmap=cmap_light)
# 用散点图把样本表示出来
plt.scatter(X[:,0],X[:,1],c=y,cmap=cmap_bold,edgecolors='k',s=20)
plt.xlim(xx.min(),xx.max())
plt.ylim(yy.min(),yy.max())
plt.title("Classifier:(max_depth=1)")
plt.show()
[结果分析] 很显然,最大深度等于1时分类器的表现肯定不会太好,分类器只分了两类。我们需要加大深度试试看
# 设定决策树最大深度为3
clf2=tree.DecisionTreeClassifier(max_depth=3)
# 重新拟合数据
clf2.fit(X_train,y_train)
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
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')
# 定义图像中分区的颜色和散点的颜色
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=clf2.predict(np.c_[xx.ravel(),yy.ravel()])
# 给每个分类中的样本分配不同的颜色
Z=z.reshape(xx.shape)
plt.figure()
plt.pcolormesh(xx,yy,Z,cmap=cmap_light)
# 用散点图把样本表示出来
plt.scatter(X[:,0],X[:,1],c=y,cmap=cmap_bold,edgecolors='k',s=20)
plt.xlim(xx.min(),xx.max())
plt.ylim(yy.min(),yy.max())
plt.title("Classifier:(max_depth=3)")
plt.show()
[结果分析] 当决策树最大深度设为3的时候,分类器能够进行3个分类的识别,而且大部分数据点都进入了正确的分类,当然还有一小部分数据点的分类是错误的
接下来进一步调整max_depth
的值
# 设定决策树最大深度为5
clf3=tree.DecisionTreeClassifier(max_depth=5)
# 重新拟合数据
clf3.fit(X_train,y_train)
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=5,
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')
# 定义图像中分区的颜色和散点的颜色
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=clf3.predict(np.c_[xx.ravel(),yy.ravel()])
# 给每个分类中的样本分配不同的颜色
Z=z.reshape(xx.shape)
plt.figure()
plt.pcolormesh(xx,yy,Z,cmap=cmap_light)
# 用散点图把样本表示出来
plt.scatter(X[:,0],X[:,1],c=y,cmap=cmap_bold,edgecolors='k',s=20)
plt.xlim(xx.min(),xx.max())
plt.ylim(yy.min(),yy.max())
plt.title("Classifier:(max_depth=5)")
plt.show()
我们可能会感到好奇,在这个过程中,决策树在每一层当中都做了哪些事情呢?我们可以用一个名叫graphviz
的库来展示一下这个过程。首先需要安装这个库:
pip install graphviz
注意 graphviz
只是帮助我们演示决策树的工作过程,对于读者来说,安装他并不是必须的
# 导入graphviz工具
import graphviz
# 导入决策树中输出graphviz的接口
from sklearn.tree import export_graphviz
# 选择最大深度为3的分类模型
export_graphviz(clf2,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()
# 显示dot文件中的图形
graphviz.Source(dot_graph)
注意 为了控制图片不要太大,我们选择用max_depth=3
的分类器来绘制图形,这样可以方便观看
3.决策树的优势和不足
相比其他算法,决策树有一个非常大的优势,就是可以很容易地将模型进行可视化,就像我们在上图中所做的一样。另外,由于决策树算法对每个样本特征进行单独处理,因此并不需要对数据进行转换。这样一来,如果使用决策树算法的话,我们几乎不需要对数据进行预处理。这也是决策树算法的一个优点
当然,决策树算法也有它的不足之处—即便我们在建模的时候可以使用类似max_depth
或是max_leaf_nodes
等参数来对决决策树进行预剪枝处理,但它还是不可避免会出现多拟合的问题,也就让模型的泛化性能大打折扣了
为了避免过拟合的问题出现,可以使用集合学习的方法,也就是我们下面要介绍的—随机森林算法
二.随机森林
常言道,不要为了一棵树放弃一片森林。这句话在机器学习算法方面也是非常正确的
1.随机森林的基本概念
随机森林有的时候也被称为是随机决策森林,是一种集合学习方法,既可以用于分类,也可以用于回归。而所谓集合学习算法,其实就是把多个机器学习算法综合在一起,制造出一个更加大模型的意思。这也就很好地解释了为什么这种算法称为随机森林
在机器学习的领域,其实有很多中集合算法,目前应用比较广泛的就包括随机森林(Random Forests)
和梯度上升决策树(Gradient Boosted Decision Trees,GBDT)
前面我们提到,决策树算法很容易出现过拟合的现象。那么为什么随机森林可以解决这个问题呢?因为随机森林是把不同的几棵决策树打包到一起,每棵树的参数都不相同,然后我们把每棵树预测的结果取平均值,这样即可以保留决策树们的工作成效,又可以降低过拟合的风险
2.随机森林的构建
这次我们继续用在决策树中来展示酒的数据集,输入代码如下:
# 导入随机森林模型
from sklearn.ensemble import RandomForestClassifier
# 载人红酒数据集
wine=datasets.load_wine()
# 选择数据集前两个特征
X=wine.data[:,:2]
y=wine.target
# 将数据集拆分为训练集和测试集
X_train,X_test,y_train,y_test=train_test_split(X,y)
# 设定随机森林中有6颗树
forest=RandomForestClassifier(n_estimators=6,random_state=3)
# 使用模型拟合数据
forest.fit(X_train,y_train)
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
max_depth=None, max_features='auto', 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, n_estimators=6, n_jobs=None,
oob_score=False, random_state=3, verbose=0, warm_start=False)
[结果分析] 可以看到,随机森林向我们返回了包含其自身全部参数的信息,让我们重点看一下其中几个必要重要的参数
首先是bootstrap
参数,代表的是bootstrap sample
,也就是"有放回抽样"的意思,指每次从样本空间中可以重复抽取同一个样本(因为样本在第一次被抽取之后又被放回去了),形象一点来说,如原始样本是[‘苹果’,‘西瓜’,‘香蕉’,‘桃子’],那么经过bootstrap sample
重构的样本就可能是[‘西瓜’,‘西瓜’,‘香蕉’,‘桃子’],还有可能是[‘苹果’,‘西瓜’,‘桃子’,‘桃子’]。Bootstrap sample
生成的数据集和原始数据集在数据量上是完全一样的,D但由于进行了重复采样,因此其中有一些数据点会丢失
看到这里,可能会问为什么要生成bootstrap sample
数据集。这是因为通过重新生成数据集,可以让随机森林中的每一棵决策树在构建的时候,会彼此之间有些差异。再加上每棵树的节点都会去选择不同的样本特征,经过这两步动作之后,可以完全肯定随机森林中的每棵树都不一样,这也符合我们使用随机森林的初衷
另外还有一个要强调的参数,是n_estimators
,这个参数控制的是随机森林中决策树的数量。在随机森林构建完成之后,每棵决策树都会单独进行预测。如果是用来进行回归分析的话,随机森林会把所有决策树预测的值取平均数;如果是用来进行分类的话,在森林内部会进行"投票",每棵树预测出数据类别的概率,比如其中一棵树说,“这瓶酒80%数据class_1”,另外一棵树说,“这瓶酒60%属于class_2”,随机森林会把这些概率取平均值,然后把样本放入概率最高的分类当中
下面我们用图像直观地看一下随机森林分类的表现,输入代码如下:
# 定义图像中分区的颜色和散点的颜色
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.figure()
plt.pcolormesh(xx,yy,Z,cmap=cmap_light)
# 用散点图把样本表示出来
plt.scatter(X[:,0],X[:,1],c=y,cmap=cmap_bold,edgecolors='k',s=20)
plt.xlim(xx.min(),xx.max())
plt.ylim(yy.min(),yy.max())
plt.title("Classifier:(max_depth=5)")
plt.show()
3.随机森林的优势和不足
从优势的角度来说,随机森林集成了决策树的所有优点,而且能够弥补决策树的不足。但也不是说决策树算法就被彻底抛弃了。从便于展示决策过程的角度来说,决策树依旧表现强悍。尤其是随机森林中每棵决策树的层级要比单独的决策树更深,所以如果需要向非专业人士展示模型工作过程的话,还是需要用到决策树的
还有,随机森林算法支持并行处理。对于超大数据集来说,随机森林会比较耗时,不过我们可以用多进程并行处理的方式来解决这个问题。实现方式是调节随机森林的n_jobs
参数,记得把n_jobs
参数数值设为和CPU内核数一致。如果你搞不清楚自己的CPU到底就多少内核,可以设置n_jobs=-1
,这样随机森林会使用CPU的全部内核,速度就会极大提升了
需要注意的是,因为随机森林生成每棵决策树的方法是随机的,那么不同的random_state
参数会导致模型完全不同,所以如果不希望建模的结果太过于不稳定,一定要固化random_state
这个参数的数值
缺点是,对于超高维数据集,稀疏数据集等来说,随机森林就有点捉襟见肘了,在这种情况下,线性模型要比随机森林的表现更好一些
三.随机森林实例—相亲事件
小花的同事给她介绍了一个对象Mr-L,现在Mr-L现年37岁,在某省机关做文员工作。但是小花的择偶标准是需要对方月薪在5万以上(只是为了引入后面的内容),但是又不好直接问Mr-L,所以拿不定主意,这时我们用决策树和随机森林
1.数据集的准备
在网站下载数据集:成年人数据集
下载好的数据集是.data格式的文件,不过不用担心,它其实就是一个csv文件,我们把它重命名为adult.csv
下面我们载人这个数据集,输入代码如下:
# 导入pandas库
import pandas as pd
# 用pd打开csv文件
data=pd.read_csv('adult.csv',header=None,index_col=False,
names=['年龄','单位性质','权重','学历','受教育时长',
'婚姻状况','职业','家庭情况','种族','性别',
'资产所得','资产损失','周工作时长','原籍',
'收入'
]
)
#为了方便展示,我们选取其中一部分数据
data_list=data[['年龄','单位性质','学历','性别','周工作时长','职业','收入']]
display(data_list.head())
年龄 | 单位性质 | 学历 | 性别 | 周工作时长 | 职业 | 收入 | |
---|---|---|---|---|---|---|---|
0 | 39 | State-gov | Bachelors | Male | 40 | Adm-clerical | <=50K |
1 | 50 | Self-emp-not-inc | Bachelors | Male | 13 | Exec-managerial | <=50K |
2 | 38 | Private | HS-grad | Male | 40 | Handlers-cleaners | <=50K |
3 | 53 | Private | 11th | Male | 40 | Handlers-cleaners | <=50K |
4 | 28 | Private | Bachelors | Female | 40 | Prof-specialty | <=50K |
注意 为了方便演示,我们只选了年龄,单位性质,学历,性别,周工作时长,职业和收入等特征
2.用get_dummies处理数据
在这个数据集中,单位性质,学历,性别,职业还有收入都不是整型数值,而是字符串,怎么使用我们现在所学的知识进行建模呢?这里我们用到pandas
的一个功能,叫作get_dummies
,它可以在现有的数据集上添加虚拟变量,让数据集变成可以用的格式
# 使用get_dummies将文本数据转化为数值
data_dummies=pd.get_dummies(data_list)
# 对比样本原始特征和虚拟变量特征
print("样本原始特征:
",list(data_list.columns),'
')
print("虚拟变量特征:
",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']
下面我们看下进行get_dummies
后数据集的样子,显示前5行数据:
# 显示数据集中的前5行
data_dummies.head()
年龄 | 周工作时长 | 单位性质_ ? | 单位性质_ Federal-gov | 单位性质_ Local-gov | 单位性质_ Never-worked | 单位性质_ Private | 单位性质_ Self-emp-inc | 单位性质_ Self-emp-not-inc | 单位性质_ State-gov | … | 职业_ Machine-op-inspct | 职业_ Other-service | 职业_ Priv-house-serv | 职业_ Prof-specialty | 职业_ Protective-serv | 职业_ Sales | 职业_ Tech-support | 职业_ Transport-moving | 收入_ <=50K | 收入_ >50K | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 39 | 40 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | … | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
1 | 50 | 13 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | … | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
2 | 38 | 40 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | … | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
3 | 53 | 40 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | … | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
4 | 28 | 40 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | … | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 |
5 rows × 46 columns
现在我们各列分配给特征向量X和分类标签y,输入代码如下:
# 定义数据集的特征值
features=data_dummies.loc[:,'年龄':'职业_ Transport-moving']
# 将特征数值赋值为x
X=features.values
# 将收入大于50k作为预测目标
y=data_dummies['收入_ >50K'].values
print("特征形态:{},标签形态:{}".format(X.shape,y.shape))
特征形态:(32561, 44),标签形态:(32561,)
3.用决策树建模并作出预测
数据集共有32561条样本数据,每条数据有44个特征值,接下来将数据集拆分成训练集和测试集,然后用决策树算法进行建模,并对模型进行评估:
# 将数据集拆分为训练集和测试集
X_train,X_test,y_train,y_test=train_test_split(X,y,random_state=0)
# 用最大深度为5的随机森林拟合数据
go_dating_tree=tree.DecisionTreeClassifier(max_depth=5)
go_dating_tree.fit(X_train,y_train)
print("模型得分:{:.2f}".format(go_dating_tree.score(X_test,y_test)))
模型得分:0.80
使用模型对Mr-L的收入进行预测,输入代码如下:
# 将Mr Z的数据输入给模型
Mr=[[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]]
# 使用模型做出预测
dating_dec=go_dating_tree.predict(Mr)
if dating_dec==1:
print("大胆追求真爱,这人月薪过5万")
else:
print("不用去了,不满足")
不用去了,不满足