第05章 决策树模型¶

5.1 决策树模型的基本原理¶

5.1.1 决策树模型简介¶

  • 基本原理: 通过对一系列问题的if/else的推到, 最终实现相关决策

  • 父节点和子节点

    • 子节点由父节点根据某一规则分裂而来, 然后子节点作为新的父节点继续分裂
  • 根节点和叶子节点

    • 根节点是没有父节点的节点, 即初始节点, 叶子节点则是没有子节点的节点, 即最终节点
  • 决策数模型的关键: 如何选择合适的节点进行分裂

    • 如何建立这样一棵 "树"

5.1.2 决策树模型的建树依据¶

  • 主要用到的是基尼系数的概念

    • 基尼系数用于计算一个系统中的失序现象, 即系统的混乱程度: 基尼系数越高, 系统的混乱程度就越高, 建立决策树模型的目的就是降低系统的混乱程度, 从而得到合适的数据分类效果

    • 基尼系数的计算公式$$gini(T) = 1 - \sum p_i^2$$ 其中$p_i$为类别$i$在样本$T$中出现的频率, 即类别为$i$的样本占总样本个数的比率

  • 当引入某个分类的变量(如 "性别 = 男" )时, 分类后的基尼系数公式为$$gini(T) = \frac{S_1}{S_1 + S_2}gini(T_1)+\frac{S_2}{S_1 + S_2}gini(T_2)$$ 其中$S_1$ , $S_2$为两类各自的样本量, $gini(T_1)$, $gini(T_2)$为两类各自的基尼系数

补充知识点: 信息熵¶

  • 另一种衡量系统混乱程度的经典手段, 在搭建决策树模型时, 信息熵的作用和基尼系数是基本一致的, 用以帮助合理地划分节点

  • 计算公式$$H(X) = - \sum p_i log_2(p_i) \hspace{2em} (i = 1, 2, \cdots, n)$$ 其中$X$表示随机变量, 随机变量的取值为$X_1, X_2, ..., $在$n$分类问题中就有$n$个取值; $p_i$表示随机变量$X$取值为$X_i$的发生频率, 且有$\sum p_i = 1$; 对数函数以2为底

  • 当引入某个用于进行分类的变量A, 则根据变量A划分后的信息熵又称为条件熵, 计算公式为$$H_A(X) = \frac{S_1}{S_1 + S_2}H(X_1)+\frac{S_2}{S_1 + S_2}H(X_2)$$

  • 为了衡量不同划分方式降低信息熵的效果, 还需要计算分类后信息熵的减少值(原系统的信息熵与分类后的信息熵之差, 改减少值成为熵增益或信息增益), 计算公式为$$Gain(A) = H(X) - H_A(X)$$

  • 基尼系数涉及平方运算, 而信息熵涉及相对复杂的对数函数运算, 因此, 目前决策树模型默认使用基尼系数作为建树依据, 运算速度会比较快

  • 在Python中, 使用Scikit-Learn库来建立决策树模型

5.1.3 决策树模型的代码实现¶

  • 决策树模型技能做分类分析(即预测分类变量值), 又能做回归分析(即预测连续变量值), 对应的模块分别为分类决策树模型(DecisionTreeClassifier)和回归决策树模型(DecisionTreeRegressor)

分类决策树模型¶

In [1]:
import warnings

warnings.filterwarnings('ignore')
In [2]:
from sklearn.tree import DecisionTreeClassifier
X = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
y = [1, 0, 0, 1, 1]

model = DecisionTreeClassifier(random_state=0)
model.fit(X, y)

print(model.predict([[5, 5]]))
[0]
In [3]:
print(model.predict([[5, 5], [7, 7], [9, 9]]))
[0 0 1]
补充知识点: 决策树可视化¶

通过graphviz插件进行决策树可视化,graphviz插件的使用教程可以查看该文档:https://shimo.im/docs/Dcgw8H6WxgWrc8hq/

In [4]:
import os  # 这两行是手动进行环境变量配置,防止在本机环境的变量部署失败
os.environ['PATH'] = os.pathsep + r'D:\softwares\Graphviz\bin'
In [5]:
# 1.如果不用显示中文,那么通过如下代码即可。安装graphviz稍微有些麻烦,可以参考上面的参考链接。
from sklearn.tree import export_graphviz
import graphviz

dot_data = export_graphviz(model, out_file=None, class_names=['0', '1'])
graph = graphviz.Source(dot_data)
graph  # 通过graph.render('决策树可视化')可在代码所在文件夹生成决策树可视化PDF文件
Out[5]:
No description has been provided for this image
补充知识点: random_state参数的作用解释¶
In [6]:
from sklearn.tree import DecisionTreeClassifier
X = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
y = [1, 0, 0, 1, 1]

model = DecisionTreeClassifier()  # 不设置random_state参数
model.fit(X, y)

# 生成可视化结果
dot_data = export_graphviz(model, out_file=None, class_names=['0', '1'])
graph = graphviz.Source(dot_data)
graph
Out[6]:
No description has been provided for this image
In [7]:
from sklearn.tree import DecisionTreeClassifier
X = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
y = [1, 0, 0, 1, 1]

model = DecisionTreeClassifier()  # 不设置random_state参数
model.fit(X, y)

# 生成可视化结果
dot_data = export_graphviz(model, out_file=None, class_names=['0', '1'])
graph = graphviz.Source(dot_data)
graph
Out[7]:
No description has been provided for this image

可以看到它们的节点划分方式是不同的,这样会导致同样的数据的预测结果有所不同,例如数据[7, 7]在左边的决策树中会被预测为类别0,而在右边的决策树中会被预测为类别1。

此时有的读者就会有疑问了,为什么模型训练后会产生两颗不同的树呢,哪棵树是正确的呢?其实两颗树都是正确的,出现这种情况的原因,是因为根据“X[1]<=7”或者“X[0]<=6”进行节点划分时产生的基尼系数下降是一样的(都是0.48 - (0.60.444 + 0.40) = 0.2136),所以无论以哪种形式进行节点划分都是合理的。产生这一现象的原因大程度是因为数据量较少所以容易产生不同划分方式产生的基尼系数下降是一样的情况,当数据量较大时出现该现象的几率则较小。

总的来说,对于同一个模型,不同的划分方式可能会导致最后的预测结果会有所不同,设置random_state参数(可以设置成0,也可以设置成1或123等任意数字)则能保证每次的划分方式都是一致的,使得每次运行的结果相同。这个概念对于初学者来说还是挺重要的,因为初学者往往会发现怎么每次运行同一个模型出来的结果都不一样,从而一头雾水,如果出现这种情况,那么设置一下random_state参数即可。

回归决策树模型¶

In [8]:
from sklearn.tree import DecisionTreeRegressor
X = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
y = [1, 2, 3, 4, 5]

model = DecisionTreeRegressor(max_depth=2, random_state=0)
model.fit(X, y)

print(model.predict([[9, 9]]))
[4.5]
In [9]:
# 生成可视化结果
dot_data = export_graphviz(model, out_file=None)  # 回归决策树就没有class分类参数了
graph = graphviz.Source(dot_data)
graph
Out[9]:
No description has been provided for this image

5.2 案例实战: 员工离职预测模型¶

5.2.1 模型搭建¶

In [10]:
import pandas as pd
df = pd.read_excel('员工离职预测模型.xlsx')
df.head()
Out[10]:
工资 满意度 考核得分 工程数量 月工时 工龄 离职
0 低 3.8 0.53 2 157 3 1
1 中 8.0 0.86 5 262 6 1
2 中 1.1 0.88 7 272 4 1
3 低 7.2 0.87 5 223 5 1
4 低 3.7 0.52 2 159 3 1
In [11]:
df = df.replace({'工资': {'低': 0, '中': 1, '高': 2}})
df.head()
Out[11]:
工资 满意度 考核得分 工程数量 月工时 工龄 离职
0 0 3.8 0.53 2 157 3 1
1 1 8.0 0.86 5 262 6 1
2 1 1.1 0.88 7 272 4 1
3 0 7.2 0.87 5 223 5 1
4 0 3.7 0.52 2 159 3 1
In [12]:
X = df.drop(columns='离职') 
y = df['离职']
In [13]:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123)
In [14]:
from sklearn.tree import DecisionTreeClassifier
model = DecisionTreeClassifier(max_depth=3, random_state=123) 
model.fit(X_train, y_train)
Out[14]:
DecisionTreeClassifier(max_depth=3, random_state=123)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
DecisionTreeClassifier(max_depth=3, random_state=123)

5.2.2 模型预测及评估¶

In [15]:
y_pred = model.predict(X_test)
print(y_pred[0:100])
[0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 1 1 0 1 0 1 0 0 1 0
 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0 0 0 1 0 1 1 0 0 0 0 0 0 0 0 0
 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 1 1 1 0 0 0]
In [16]:
# 通过构造DataFrame进行对比
a = pd.DataFrame()  # 创建一个空DataFrame 
a['预测值'] = list(y_pred)
a['实际值'] = list(y_test)
a.head()
Out[16]:
预测值 实际值
0 0 0
1 0 0
2 1 1
3 0 0
4 0 0
In [17]:
# 如果要查看整体的预测准确度
from sklearn.metrics import accuracy_score
score = accuracy_score(y_pred, y_test)
print(score)
0.9573333333333334
In [18]:
# 或者用模型自带的score函数查看预测准确度
model.score(X_test, y_test)
Out[18]:
0.9573333333333334

其实分类决策树模型本质预测的并不是准确的0或1的分类,而是预测其属于某一分类的概率,可以通过如下代码查看预测属于各个分类的概率:

In [19]:
y_pred_proba = model.predict_proba(X_test)
print(y_pred_proba[0:5])
[[0.98526077 0.01473923]
 [0.98526077 0.01473923]
 [0.28600613 0.71399387]
 [0.98526077 0.01473923]
 [0.92283214 0.07716786]]
In [20]:
b = pd.DataFrame(y_pred_proba, columns=['不离职概率', '离职概率']) 
b.head()
Out[20]:
不离职概率 离职概率
0 0.985261 0.014739
1 0.985261 0.014739
2 0.286006 0.713994
3 0.985261 0.014739
4 0.922832 0.077168

如果想查看离职概率,即查看y_pred_proba的第二列,可以采用如下代码,这个是二维数组选取列的方法,其中逗号前的“:”表示所有行,逗号后面的数字1则表示第二列,如果把数字1改成数字0,则提取第一列不离职概率。

In [21]:
y_pred_proba[:,1]
Out[21]:
array([0.01473923, 0.01473923, 0.71399387, ..., 0.01473923, 0.94594595,
       0.01473923])

在Python实现上,通过4.3节讲过的代码就可以求出在不同阈值下的命中率(TPR)以及假警报率(FPR)的值,从而可以绘制ROC曲线。

In [22]:
from sklearn.metrics import roc_curve
fpr, tpr, thres = roc_curve(y_test, y_pred_proba[:,1])
In [23]:
a = pd.DataFrame()  # 创建一个空DataFrame 
a['阈值'] = list(thres)
a['假警报率'] = list(fpr)
a['命中率'] = list(tpr)
a
Out[23]:
阈值 假警报率 命中率
0 inf 0.000000 0.000000
1 1.000000 0.000000 0.247110
2 0.945946 0.008232 0.677746
3 0.713994 0.038128 0.942197
4 0.077168 0.159879 0.969653
5 0.059406 0.171577 0.972543
6 0.045763 0.240035 0.976879
7 0.014739 1.000000 1.000000
In [24]:
import matplotlib.pyplot as plt
plt.plot(fpr, tpr)
plt.show()
No description has been provided for this image
In [25]:
#求AUC值
from sklearn.metrics import roc_auc_score
score = roc_auc_score(y_test, y_pred_proba[:,1])
print(score)
0.9736722483245008
In [26]:
model.feature_importances_
Out[26]:
array([0.        , 0.59810862, 0.14007392, 0.10638659, 0.00456495,
       0.15086592])
In [27]:
# 通过DataFrame进行展示,并根据重要性进行倒序排列
features = X.columns  # 获取特征名称
importances = model.feature_importances_  # 获取特征重要性

# 通过二维表格形式显示
importances_df = pd.DataFrame()
importances_df['特征名称'] = features
importances_df['特征重要性'] = importances
importances_df.sort_values('特征重要性', ascending=False)
Out[27]:
特征名称 特征重要性
1 满意度 0.598109
5 工龄 0.150866
2 考核得分 0.140074
3 工程数量 0.106387
4 月工时 0.004565
0 工资 0.000000

5.2.3 决策树模型可视化呈现及决策树要点理解¶

In [28]:
# 1.如果不用显示中文,那么通过如下代码即可:
# !pip3 install pygraphviz
from sklearn.tree import export_graphviz
import graphviz

dot_data = export_graphviz(model, out_file=None, class_names=['0', '1'])
graph = graphviz.Source(dot_data)

# graph.render("result")  # 导出成PDF文件
# print('可视化文件result.pdf已经保存在代码所在文件夹!')

graph
Out[28]:
No description has been provided for this image
In [29]:
# 添加名称(feature_names)和填充颜色(filled=True)
dot_data = export_graphviz(model, out_file=None, feature_names=['income', 'satisfication', 'score', 'project_num', 'hours', 'year'], class_names=['0', '1'], filled=True)  
graph = graphviz.Source(dot_data)

graph
Out[29]:
No description has been provided for this image
In [30]:
# 2.如果想显示中文,需要使用如下代码
from sklearn.tree import export_graphviz
import graphviz

# 生成dot_data
dot_data = export_graphviz(model, out_file=None, feature_names=X_train.columns, class_names=['不离职', '离职'], rounded=True, filled=True)

# # 将生成的dot_data内容导入到txt文件中
# f = open('dot_data.txt', 'w')
# f.write(dot_data)
# f.close()

# 修改字体设置,避免中文乱码!
# import re
# f_old = open('dot_data.txt', 'r')
# f_new = open('dot_data_new.txt', 'w', encoding='utf-8')
# for line in f_old:
#     if 'fontname' in line:
#         font_re = 'fontname=(.*?)]'
#         old_font = re.findall(font_re, line)[0]
#         line = line.replace(old_font, 'SimHei')
#     f_new.write(line)
# f_old.close()
# f_new.close()

graph = graphviz.Source(dot_data)

graph

# # 以PNG的图片形式存储生成的可视化文件
# os.system('dot -Tpng dot_data_new.txt -o 决策树模型.png')  
# print('决策树模型.png已经保存在代码所在文件夹!')

# # 以PDF的形式存储生成的可视化文件
# os.system('dot -Tpdf dot_data_new.txt -o 决策树模型.pdf')  
# print('决策树模型.pdf已经保存在代码所在文件夹!')
Out[30]:
No description has been provided for this image

节点各元素的含义

  • 除了叶子节点外,每个节点都有5个元素:分裂依据、gini(当前基尼系数)、samples(当前样本数)、value(样本中各类别的数量)、class(分类类别)

  • 以根节点为例,其分裂依据为满意度是否小于等于4.65;当前基尼系数gini为0.365;当前样本数samples为12000;value后中括号中左边的数值9120表示“离职”列中的0的数量,即不离职员工的数量, 右边的数值2880表示“离职”列中的1的数量,即离职员工的数量;分类类别class为“不离职”,这是因为这个节点中不离职员工的数量9120多于离职员工的数量2880,不过根节点的class没有什么意义,模型的预测结果取决于最后叶子节点的class。此外,最后的叶子节点因为已经分裂完毕,所以不再有分裂依据这一项。

In [31]:
# 通过DataFrame进行展示,并根据重要性进行倒序排列
features = X.columns  # 获取特征名称
importances = model.feature_importances_  # 获取特征重要性

# 通过二维表格形式显示
importances_df = pd.DataFrame()
importances_df['特征名称'] = features
importances_df['特征重要性'] = importances
importances_df.sort_values('特征重要性', ascending=False)
Out[31]:
特征名称 特征重要性
1 满意度 0.598109
5 工龄 0.150866
2 考核得分 0.140074
3 工程数量 0.106387
4 月工时 0.004565
0 工资 0.000000

在决策树模型中, 特征重要性的大小取决于该特征变量对整体的基尼系数下降的贡献大小

比如 "满意度" 的特征重要性0.598是如何计算出来的

  • 首先, 需要计算整体的基尼系数的下降值: 根据最终8个叶子节点各自的基尼系数和样本数进行权重求和, 得到新系统的基尼系数, 然后用根节点的基尼系数减去新系统的基尼系数

    $\frac{1295}{12000} \times 0.102 + \frac{101}{12000} \times 0.112 + \frac{714}{12000} \times 0.0 + \frac{1257}{12000} \times 0.142 + \frac{7056}{12000} \times 0.029 + \frac{8}{12000} \times 0.0 + \frac{590}{12000} \times 0.087 + \frac{979}{12000} \times 0.408 = 0.0814$

    根节点的基尼系数为0.365, 因此整体基尼系数下降值为0.365 - 0.0814 = 0.2836

  • 其次, 计算特征所发挥的作用: "满意度"在决策树的根节点和第2层的第2个节点中都发挥了作用, 系统产生的基尼系数的下降值分别计算如下

    $$0.365 - (\frac{3367}{12000} \times 0.477 + \frac{8633}{12000} \times 0.175) = 0.105$$

    $$\frac{1971}{12000} \times 0.484 - (\frac{714}{12000} \times 0 + \frac{1257}{12000} \times 0.142) = 0.0646$$

    两者之和为0.1696

  • 最后, 用特征发挥的作用除以整体基尼系数的下降:

    $0.1696 \div 0.2836 = 0.598$

叶子节点停止分裂: 已经无法再分裂, 或达到限定的条件(决策树的最大深度)

5.3 参数调优: K折交叉验证与GridSearch网格搜索¶

  • 超参数

    • 机器学习模型的内置参数, 如决策树-max_depth(树的最大深度)
  • 调节模型参数的常用方法

    • GridSearch网格搜索

    • K折交叉验证

5.3.1 K折交叉验证¶

  • 交叉验证

    因为训练集和测试集的数据划分是随机的, 所以有时会重复地使用数据, 以便更好地评估模型的有效性, 并选出最好的模型

    • 方法有简单交叉验证, K折交叉验证和留一交叉验证3种

    • 其中K折交叉验证应用较为广泛: 将数据集随机等分为K份, 每次选取K-1份作为训练集, 剩下的1份作为测试集, 得到K个模型后将这K个模型的平均测试效果作为最终的模型效果

      • 如果训练集相对较小, 则增大K值, 这样在每次迭代过程中将会有更多数据用于模型训练, 同时算法时间延长; 如果训练集相对较大, 则减小K值, 这样可以降低模型在不同的数据块上进行重复拟合性能评估的计算成本, 在平均性能的基础上获得模型的准确评估
In [32]:
#5.2的模型搭建代码

# 1.读取数据与简单预处理
import pandas as pd
df = pd.read_excel('员工离职预测模型.xlsx')
df = df.replace({'工资': {'低': 0, '中': 1, '高': 2}})

# 2.提取特征变量和目标变量
X = df.drop(columns='离职') 
y = df['离职']

# 3.划分训练集和测试集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123)

# 4.模型训练及搭建
from sklearn.tree import DecisionTreeClassifier
model = DecisionTreeClassifier(max_depth=3, random_state=123)
model.fit(X_train, y_train)
Out[32]:
DecisionTreeClassifier(max_depth=3, random_state=123)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
DecisionTreeClassifier(max_depth=3, random_state=123)
In [33]:
from sklearn.model_selection import cross_val_score
acc = cross_val_score(model, X, y, cv=5, scoring=None) #cv=5, 交叉验证5次, 每次随机取4/5的数据用于训练, 1/5的数据用于测试, 默认为3, scoring不设置参数默认为accuracy(准确度)作为评估指标

#5次交叉验证的打分
print(acc)

acc.mean()
[0.96666667 0.96066667 0.959      0.96233333 0.91366667]
Out[33]:
np.float64(0.9524666666666667)
In [34]:
from sklearn.model_selection import cross_val_score
acc = cross_val_score(model, X, y, scoring='roc_auc', cv=5)#以ROC曲线的AUC值作为评估标准

print(acc)

acc.mean()
[0.97146884 0.9674637  0.96641351 0.97047305 0.95030156]
Out[34]:
np.float64(0.9652241309284616)

5.3.2 GridSearch网格搜索¶

单参数调优¶

In [35]:
from sklearn.model_selection import GridSearchCV  # 网格搜索合适的超参数

# 指定参数k的范围
parameters = {'max_depth': [3, 5, 7, 9, 11]}
# 构建决策树分类器
model = DecisionTreeClassifier()  # 这里因为要进行参数调优,所以不需要传入固定的参数了

# 网格搜索
grid_search = GridSearchCV(model, parameters, scoring='roc_auc', cv=5)   # cv=5表示交叉验证5次,默认值为3;scoring='roc_auc'表示通过ROC曲线的AUC值来进行评分,默认通过准确度评分
grid_search.fit(X_train, y_train)

# 输出参数的最优值
grid_search.best_params_
Out[35]:
{'max_depth': 7}
In [36]:
# 通过如下代码可以查看GridSearchCV函数的官方介绍
# GridSearchCV?

也可以批量生成调参所需数据

In [37]:
import numpy as np
parameters = {'max_depth': np.arange(1, 10, 2)}
In [38]:
# 根据max_depth=7来重新搭建模型
model = DecisionTreeClassifier(max_depth=7)  # 这个max_depth参数是可以调节的,之后讲
model.fit(X_train, y_train) 

# 查看整体预测准确度
y_pred = model.predict(X_test)
from sklearn.metrics import accuracy_score
score = accuracy_score(y_pred, y_test)
print(score)
0.982

原来准确度评分score为0.957,现在为0.982,的确有所提升

In [39]:
# 查看新的AUC值
# 预测不违约&违约概率
y_pred_proba = model.predict_proba(X_test)
y_pred_proba[:,1]  # 如果想单纯的查看违约概率,即查看y_pred_proba的第二列

# 绘制ROC曲线,计算AUC值
from sklearn.metrics import roc_curve
fpr, tpr, thres = roc_curve(y_test, y_pred_proba[:,1])

# 绘制ROC曲线
import matplotlib.pyplot as plt
plt.plot(fpr, tpr)
plt.show()
No description has been provided for this image
In [40]:
# 计算AUC值
from sklearn.metrics import roc_auc_score
score = roc_auc_score(y_test, y_pred_proba[:,1])
print(score)
0.9878194468097895

总结:原来获得的AUC值为0.9736,现在获得的AUC值为0.9877,的确提高了模型的预测水平

In [41]:
# 查看此时的变量重要性
model.feature_importances_
Out[41]:
array([0.00059222, 0.52722508, 0.13163818, 0.1116004 , 0.07764762,
       0.1512965 ])
In [42]:
# 一一对应
features = X.columns
importances = model.feature_importances_

# 通过表格形式显示
importances_df = pd.DataFrame()  # 创建空二维表格,为之后准备
importances_df['特征名称'] = features
importances_df['特征重要性'] = importances

importances_df.sort_values('特征重要性', ascending=False)
Out[42]:
特征名称 特征重要性
1 满意度 0.527225
5 工龄 0.151297
2 考核得分 0.131638
3 工程数量 0.111600
4 月工时 0.077648
0 工资 0.000592

决策树模型还有些别的超参数,如下所示:

下面是分类决策树模型DecisionTreeClassifier()模型常用的一些超参数及它们的解释:

  1. criterion:特征选择标准,取值为"entropy"信息熵和"gini"基尼系数,默认选择"gini"。
  2. splitter:取值为"best"和"random","best"在特征的所有划分点中找出最优的划分点,适合样本量不大的情况,"random"随机地在部分划分点中找局部最优的划分点,适合样本量非常大的情况,默认选择"best"。
  3. max_depth:决策树最大深度,取值为int或None,一般数据或特征比较少的时候可以不设置,如果数据或特征比较多时,可以设置最大深度进行限制。默认取‘None’。
  4. min_samples_split:子节点往下划分所需的最小样本数,默认取2,如果子节点中的样本数小于该值则停止分裂。
  5. min_samples_leaf:叶子节点的最少样本数,默认取1,如果小于该数值,该叶子节点会和兄弟节点一起被剪枝(即剔除该叶子节点和其兄弟节点,并停止分裂)。
  6. min_weight_fraction_leaf:叶子节点最小的样本权重和,默认取0,即不考虑权重问题,如果小于该数值,该叶子节点会和兄弟节点一起被剪枝(即剔除该叶子节点和其兄弟节点,并停止分裂)。如果较多样本有缺失值或者样本的分布类别偏差很大,则需考虑样本权重问题。
  7. max_features:在划分节点时所考虑的特征值数量的最大值,默认取None,可以传入int型或float型数据。如果是float型数据,表示百分数。
  8. max_leaf_nodes:最大叶子节点数,默认取None,可以传入int型数据。
  9. class_weight:指定类别权重,默认取None,可以取"balanced",代表样本量少的类别所对应的样本权重更高,也可以传入字典指定权重。该参数主要是为防止训练集某些类别的样本过多,导致训练的决策树过于偏向这些类别。除了此处指定class_weight,还可以使用过采样和欠采样的方法处理样本类别不平衡的问题,过采样和欠采样将在第十一章:数据预处理讲解。
  10. random_state:当数据量较大,或特征变量较多时,可能在某个节点划分时,会碰上两个特征变量的信息熵增益或者基尼系数减少量是一样的情况,那么此时决策树模型默认是随机从中选一个特征变量进行划分,这样可能会导致每次运行程序后生成的决策树不太一致。如果设定random_state参数(如设置为123)可以保证每次运行代码时,各个节点的分裂结果都是一致的,这在特征变量较多,树的深度较深的时候较为重要。

多参数调优¶

In [43]:
from sklearn.model_selection import GridSearchCV

# 指定决策树分类器中各个参数的范围
parameters = {'max_depth': [5, 7, 9, 11, 13], 'criterion':['gini', 'entropy'], 'min_samples_split':[5, 7, 9, 11, 13, 15]}
# 构建决策树分类器
model = DecisionTreeClassifier()  # 这里因为要进行参数调优,所以不需要传入固定的参数了

# 网格搜索
grid_search = GridSearchCV(model, parameters, scoring='roc_auc', cv=5)
grid_search.fit(X_train, y_train)

# 获得参数的最优值
grid_search.best_params_
Out[43]:
{'criterion': 'entropy', 'max_depth': 11, 'min_samples_split': 13}
In [44]:
# 根据多参数调优的结果来重新搭建模型
model = DecisionTreeClassifier(criterion='entropy', max_depth=11, min_samples_split=13)
model.fit(X_train, y_train) 

# 查看整体预测准确度
y_pred = model.predict(X_test)
from sklearn.metrics import accuracy_score
score = accuracy_score(y_pred, y_test)
print(score)
0.9823333333333333
In [45]:
# 查看新的AUC值
# 预测不违约&违约概率
y_pred_proba = model.predict_proba(X_test)
y_pred_proba[:,1]  # 如果想单纯的查看违约概率,即查看y_pred_proba的第二列

score = roc_auc_score(y_test, y_pred_proba[:,1])
print(score)
0.9880266927800764

这里多参数调优后发现,模型效果的确有所优化

注意点1:多参数调优和分别单参数调优的区别

多参数调优和单参数分别调优是有区别的,比如有的读者为了省事,对上面的3个参数进行3次单独的单参数调优,然后将结果汇总,这样的做法其实是不严谨的。因为在进行单参数调优的时候,是默认其他参数取默认值的,那么该参数和其他参数都不取默认值的情况就没有考虑进来,也即忽略了多个参数对模型的组合影响。以上面的代码示例来说,使用多参数调优时,它是526=60种组合可能,而如果是进行3次单参数调优,则只是5+2+6=13种组合可能。 因此,如果只需要调节一个参数,那么可以使用单参数调优,如果需要调节多个参数,则推荐使用多参数调优。

注意点2:参数取值是给定范围的边界

另外一点需要需要注意的是,如果使用GridSearchCV()方法所得到的参数取值是给定范围的边界,那么有可能存在范围以外的取值使得模型效果更好,因此需要我们额外增加范围,继续调参。举例来说,倘若上述代码中获得的最佳max_depth值为设定的最大值13,那么实际真正合适的max_depth可能更大,此时便需要将搜索网格重新调整,如将max_depth的搜索范围变成[9, 11, 13, 15, 17],再重新参数调优。