ML--决策树与随机森林

ML–决策树与随机森林

在生活中,我们经常遇到一些事情需要作出决策来应对。说到决策,自然想到决策树算法,而说到决策树算法,又自然会想到随机森林

主要涉及的知识点有:

  • 决策树的基本原理和构造
  • 决策树的优势和不足
  • 随机森林的基本原理和构造
  • 随机森林的优势和不足
  • 实例演示:相亲事件

一.决策树

决策树是一种在分类与回归中都有非常广泛应用的算法,它的原理是通过对一系列问题进行if/else的推导,最终实现决策

1.决策树的基本原理

举个例子:假设出题者心理想的是Google,百度,Facebook,阿里,四个公司中的一个,则提问决策树:
未命名文件.png
最终的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()

output_10_0.png

[结果分析] 很显然,最大深度等于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()

output_13_0.png

[结果分析] 当决策树最大深度设为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()

output_16_0.png

我们可能会感到好奇,在这个过程中,决策树在每一层当中都做了哪些事情呢?我们可以用一个名叫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)

output_18_0.svg

注意 为了控制图片不要太大,我们选择用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()

output_24_0.png

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("不用去了,不满足")
不用去了,不满足
原文地址:https://www.cnblogs.com/LQ6H/p/10415083.html