机器学习测试笔记(13)——决策树与随机森林

2021-01-18 11:16:17 浏览数 (1)

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中随机取两个样本后,其类别不一致性的概率。

Gini(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)   

我们对测试结果进行比较:

0 人点赞