天天看点

sklearn 决策树_Sklearn学习笔记(1)-决策树

sklearn 决策树_Sklearn学习笔记(1)-决策树

前言

大家可以先看看《机器学习理论基础》这个系列,就会发现自己编写一个学习算法还是挺麻烦的,特别是稍微复杂一点的,在某些文章中我也提到过sklearn,这个库我个人觉得是机器学习必备,使用简单但功能强大,堪称神库。sklearn全称是scikit-learn,网址在https://scikit-learn.org/stable/index.html。这个系列就结合一些实例,介绍一下这个库的用法。

Sklearn的基本建模流程

Sklearn对所有模型的建模流程都是通用的,这是它非常了不起的地方,总共只有三步。

1.实例化,建立评估模型对象;

2.通过模型接口训练模型;

3.通过模型结果提取需要的信息。

相应的,第一步,需要了解实例化时需要使用的参数。第二第三步需要了解数据属性和数据接口。

下面就是示例,其实也就是三步而已。

from sklearn import tree                #导入需要的模块
​
clf = tree.DecisionTreeClassifier()     #实例化
clf = clf.fit(X_train,y_train)          #用训练集数据训练模型
result = clf.score(X_test,y_test)       #导入测试集,从接口中调用需要的信息(正确率)或者predict
           

先导知识

如果大家不了解决策树的话,可以看看这篇文章。机器学习理论基础(5)-决策树

决策树模型参数

Sklearn中决策树的类都在”tree“这个模块下,决策树有分类树(tree.DecisionTreeClassifier)也有回归树(tree.DecisionTreeRegressor)。这里只讲分类树。

class sklearn.tree.DecisionTreeClassifier(criterion='gini', splitter='best', 
max_depth=None, min_samples_split=2, min_samples_leaf=1, 
min_weight_fraction_leaf=0.0, max_features=None, random_state=None, 
max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, 
class_weight=None, presort='deprecated', ccp_alpha=0.0)
           

习惯就好,sklearn的类参数都是非常多。需要了解的参数我们只讲8个。

criterion

两个参数,其实就是ID3和CART算法的区别。

  1. 输入”entropy“,使用信息熵(Entropy)
  2. 输入”gini“,使用基尼系数(Gini Impurity)

random_state & splitter

决策树在建树时,是靠优化节点来追求一棵优化的树,但最优的节点能够保证最优的树吗?集成算法被用来解决这个问题:既然一棵树不能保证最优,那就建更多的不同的树,然后从中取最好的。怎样从一组数据集中建不同的树?在每次分枝时,不从使用全部特征,而是随机选取一部分特征,从中选取不纯度相关指标最优的作为分枝用的节点。这样,每次生成的树也就不同了。

random_state 用来设置分枝中的随机模式的参数,默认None。也可以输入任意整数作为随机数生成种子。设置的话,是为了保证每次随机生成的数是一致的(即使是随机的);如果不设置,那么每次生成的随机数都是不同的,

也就是说决策树其实是随机的

(可以通过多运行几次score测试下)。

splitter也是用来控制决策树中的随机选项的,有两种输入值,输入”best",决策树在分枝时虽然随机,但是还是会优先选择更重要的特征进行分枝(重要性可以通过属性feature_importances_查看)

#特征重要性
clf.feature_importances_
[*zip(feature_name,clf.feature_importances_)]
           

输入“random",决策树在分枝时会更加随机,树会因为含有更多的不必要信息而更深更大,并因这些不必要信息而降低对训练集的拟合。这也是防止过拟合的一种方式。当你预测到你的模型会过拟合,用这两个参数来帮助你降低树建成之后过拟合的可能性。当然,树一旦建成,我们依然是使用剪枝参数来防止过拟合。

剪枝参数

为了让决策树有更好的泛化性,我们要对决策树进行剪枝。剪枝策略对决策树的影响巨大,正确的剪枝策略是优化决策树算法的核心。sklearn为我们提供了不同的剪枝策略:

max_depth

限制树的最大深度,超过设定深度的树枝全部剪掉。

这是用得最广泛的剪枝参数,在高维度低样本量时非常有效。决策树多生长一层,对样本量的需求会增加一倍,所以限制树深度能够有效地限制过拟合。实际使用时,建议从=3开始尝试,看看拟合的效果再决定是否增加设定深度。

min_samples_leaf & min_samples_split

min_samples_leaf限定,一个节点在分枝后的每个子节点都必须包含至少min_samples_leaf个训练样本,否则分枝就不会发生。一般来说,建议从=5开始使用。如果叶节点中含有的样本量变化很 大,建议输入浮点数作为样本量的百分比来使用。

min_samples_split限定,一个节点必须要包含至少min_samples_split个训练样本,这个节点才允许被分枝,否则分枝就不会发生。

这两个参数就是限定叶子和节点的训练样本个数,如果数目太少,就没必要分支,防止过拟合。

max_features & min_impurity_decrease

一般配合max_depth使用,用作树的”精修“。

max_features限制分枝时考虑的特征个数,超过限制个数的特征都会被舍弃。max_features是用来限制高维度数据的过拟合的剪枝参数,但其方法比较暴力,是直接限制可以使用的特征数量而强行使决策树停下的参数,在不知道决策树中的各个特征的重要性的情况下,强行设定这个参数可能会导致模型学习不足。如果希望通过降维的方式防止过拟合,建议使用PCA,ICA或者特征选择模块中的降维算法。 min_impurity_decrease限制信息增益的大小,信息增益小于设定数值的分枝不会发生。

目标权重参数

class_weight & min_weight_fraction_leaf

完成样本标签平衡的参数。样本不平衡是指在一组数据集中,标签的一类天生占有很大的比例。因此我们要使用class_weight参数对样本标签进行一定的均衡,给少量的标签更多的权重,让模型更偏向少数类,向捕获少数类的方向建模。该参数默认None,此模式表示自动给与数据集中的所有标签相同的权重。

有了权重之后,样本量就不再是单纯地记录数目,而是受输入的权重影响了,因此这时候剪枝,就需要搭配min_weight_fraction_leaf这个基于权重的剪枝参数来使用。

重要属性和接口

属性是在模型训练之后,能够调用查看的模型的各种性质。对决策树来说,最重要的是feature_importances_,能够查看各个特征对模型的重要性。 sklearn中许多算法的接口都是相似的,比如说我们之前已经用到的fit和score,几乎对每个算法都可以使用。除了这两个接口之外,决策树最常用的接口还有

apply和predict

。apply中输入测试集返回每个测试样本所在的叶子节点的索引,predict输入测试集返回每个测试样本的标签。

#apply返回每个测试样本所在的叶子节点的索引
clf.apply(Xtest)
#predict返回每个测试样本的分类/回归结果
clf.predict(Xtest)
           

在这里不得不提的是,所有接口中要求输入X_train和X_test的部分,

输入的特征矩阵必须至少是一个二维矩阵

。sklearn不接受任何一维矩阵作为特征矩阵被输入。

实例:泰坦尼克号幸存者预测

数据集来自: https://www.kaggle.com/c/titanic。

如何探索数据

import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
​
data = pd.read_csv(r"./data.csv",index_col = 0)
           
探索数据常用命令:head

其实就是简单看看前几行的数据,看看表格到底长什么样,直接用data的话就是展示的太多了。

data.head()
           
sklearn 决策树_Sklearn学习笔记(1)-决策树
探索数据常用命令:info
data.info()
           

看到输出为:

<class 'pandas.core.frame.DataFrame'>
Int64Index: 891 entries, 1 to 891
Data columns (total 11 columns):
Survived    891 non-null int64
Pclass      891 non-null int64
Name        891 non-null object     #object代表不是数字,但决策树分类器只能处理数字
Sex         891 non-null object
Age         714 non-null float64    #有部分缺失值
SibSp       891 non-null int64
Parch       891 non-null int64
Ticket      891 non-null object
Fare        891 non-null float64
Cabin       204 non-null object     #有超多缺失值
Embarked    889 non-null object
dtypes: float64(2), int64(4), object(5)
memory usage: 83.5+ KB
           

数据预处理

筛选特征

我们预测的是存活的几率,那么Name就不重要了。其次,Cabin并没有告诉我们有用的信息,比如头等舱还是经济舱,而且缺失值实在太多了,这里就去掉。最后,Ticket同样可以删掉。

data.drop(["Cabin","Name","Ticket"],inplace=True,axis=1)
data.head()
           
sklearn 决策树_Sklearn学习笔记(1)-决策树

处理缺失值

年龄和是否幸存有较强的相关性,所以缺失值我们需要填补,这里使用平均年龄填补。顺便把有缺失值的行删除。

data["Age"] = data["Age"].fillna(data["Age"].mean())
data = data.dropna()
           

处理非数字

#将二分类变量转换为数值型变量
data["Sex"] = (data["Sex"]== "male").astype("int")
​
#将三分类变量转换为数值型变量
labels = data["Embarked"].unique().tolist()
data["Embarked"] = data["Embarked"].apply(lambda x: labels.index(x))
           

当然这样直接的转成0,1,2是有前提的:就是特征的不同取值是没有联系的。比如说有一列是“教育水平”,分为“初中,高中,大学”,就不能单纯的换成0,1,2,因为不同学历之间的关系,没办法通过这种方式表达出来。大家要深入了解的话,可搜索“one-hot”编码。

拆分数据

先把数据和标签拆分,然后按经典的三七分,把数据拆分。由于是随机抽取的,所以索引是乱的。

X = data.iloc[:,data.columns != "Survived"]
y = data.iloc[:,data.columns == "Survived"]
​
from sklearn.model_selection import train_test_split
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y,test_size=0.3)
​
Xtrain.head(10)
           
sklearn 决策树_Sklearn学习笔记(1)-决策树

修正测试集和训练集的索引。

for i in [Xtrain, Xtest, Ytrain, Ytest]:
    i.index = range(i.shape[0])
           

训练模型

按照基本流程,三部曲,训练模型。得到分数后,再做十次交叉验证,看看模型的稳定性。

clf = DecisionTreeClassifier(random_state=25)
clf = clf.fit(Xtrain, Ytrain)
score_ = clf.score(Xtest, Ytest)
print(score_)
​
#进行十次交叉验证
score = cross_val_score(clf,X,y,cv=10).mean()
print(score)
           

结果一般般:

0.7865168539325843
0.7739274770173645
           

调整参数

先从max_depth开始调,为了看看到底是过拟合还是欠拟合,最好还是把训练集和测试集的表现都比较一下。

tr = []
te = []
for i in range(10):
    clf = DecisionTreeClassifier(random_state=25
                                 ,max_depth=i+1
                                 ,criterion="entropy"
                                )
    clf = clf.fit(Xtrain, Ytrain)
    score_tr = clf.score(Xtrain,Ytrain)
    score_te = cross_val_score(clf,X,y,cv=10).mean()
    tr.append(score_tr)
    te.append(score_te)
print(max(te))
plt.plot(range(1,11),tr,color="red",label="train")
plt.plot(range(1,11),te,color="blue",label="test")
plt.xticks(range(1,11))
plt.legend()
plt.show()
           

输出:

0.8177860061287026
           
sklearn 决策树_Sklearn学习笔记(1)-决策树

结果还是只有81%,可以看到是有过拟合的倾向,我们还得继续调整参数。

网格搜索

网格搜索是帮助我们同时调整多个参数的技术,本质是枚举各个参数,一个一个尝试,看看哪个效果最好。我们先会给网格一个字典,对应的是调整的参数和调整参数的范围。所以网格搜索的时间是很长的,大家不要随便设置参数范围。

import numpy as np
​
parameters = {'splitter':('best','random')
              ,'criterion':("gini","entropy")
              ,"max_depth":[*range(1,10)]
              ,'min_samples_leaf':[*range(1,50,5)]
              ,'min_impurity_decrease':[*np.linspace(0,0.5,20)]
             }
​
clf = DecisionTreeClassifier(random_state=25)
#交叉验证的次数依然是10次
GS = GridSearchCV(clf, parameters, cv=10)
GS.fit(Xtrain,Ytrain)
​
print(GS.best_params_)
print(GS.best_score_)
           

输出为:

{'criterion': 'gini',
 'max_depth': 5,
 'min_impurity_decrease': 0.0,
 'min_samples_leaf': 1,
 'splitter': 'random'}
 
0.8344051446945338
           

相比之前的分数提高了,可见网格产生了效果,可以找到较好的参数。为什么不是最好的参数?那是因为网格搜索也是有问题的,比如说它有四个参数,但用了其中三个参数,就能达到一个很好的效果,网格也不能舍弃最后一个参数,必须要使用完。所以有时候使用网格得到更差的分数,也是很正常的。

这里我们就简单学习了决策树的重要参数和实例,大家要深入了解还需多写多练。