乐趣区

关于人工智能:基于Python的决策树分类器与剪枝

作者 |Angel Das
编译 |VK
起源 |Towards Data Science

介绍

  • 决策树分类器是一种有监督的学习模型,在咱们关怀可解释性时十分有用。
  • 决策树通过基于每个档次的多个问题做出决策来合成数据
  • 决策树是解决分类问题的罕用算法之一。

为了更好地了解它,让咱们看看上面的例子。

决策树通常包含:

  • 根节点- 示意被进一步划分为同质组的样本或总体
  • 拆分- 将节点分为两个子节点的过程
  • 决策节点- 当一个子节点依据某个条件拆分为其余子节点时,称为决策节点
  • 叶节点或终端节点- 不进一步拆分的子节点
  • 信息增益 - 要应用一个条件(比如说信息最丰盛的特色) 来宰割节点,咱们须要定义一个能够优化的指标函数。在决策树算法中,咱们偏向于在每次宰割时最大化信息增益。在测量信息增益时,通常应用三种度量。它们是基尼不纯度、熵和分类误差

数学了解

为了了解决策树是如何倒退的,咱们须要更深刻地理解在每一步中如何应用度量使信息增益最大化。

让咱们举一个例子,其中咱们有蕴含学生信息的训练数据,如性别、年级、因变量或分类变量,这些变量能够辨认学生是否是美食家。咱们有以下概述的信息。

  1. 学生总数 -20 人
  2. 被归为美食家的学生总数 -10
  3. 不属于美食家的学生总数 -10
  4. P(美食家),即学生成为美食家的概率 =(10/20)=0.5
  5. Q(非美食家),学生不是美食家的概率 =(10/20)=0.5

让咱们依据学生的性别将他们分成两个节点,并从新计算上述指标。

男学生(节点 A)

  1. 学生总数 -10 人
  2. 被归为美食家的学生总数 -8
  3. 不属于美食家的学生总数 -2
  4. P(美食家),学生成为美食家的概率 =(8/10)=0.8
  5. Q(非美食家),学生不是美食家的概率 =(2/10)=0.2

女生(节点 B)

  1. 学生总数 -10 人
  2. 被归为美食家的学生总数 -4
  3. 不属于美食家的学生总数 -6
  4. P(美食家),学生成为美食家的概率 =(4/10)=0.4
  5. Q(非美食家),学生不成为美食家的概率 =(6/10)=0.6

节点 A 的 基尼指数 (GIn)=P²+Q²,其中 P 和 Q 是学生成为美食家和非美食家的概率。GIn(节点 A)=0.8²+0.2²=0.68

节点 A 的 基尼不纯度(GIp)=1- 基尼指数 =1–0.68=0.32

节点 B 或女生的 基尼指数(GIn)=P²+Q²,其中 P 和 Q 是学生成为美食家和非美食家的概率。GIn(节点 B)=0.4²+0.6²=0.52

节点 B 的 基尼不纯度(GIp)=1- 基尼指数 =1–0.52=0.48

咱们察看到的是,当咱们将学生按性别 (男性和女性) 别离划分为 A 和 B 节点时,咱们别离失去了两个节点的基尼不纯度。当初,为了确定性别是否是将学生分为美食家和非美食家的正确变量,咱们须要一个加权基尼不纯度分数,该分数应用以下公式计算。

加权基尼不纯度 =(A 节点总样本数 / 数据集中总样本数)基尼不纯度 (A 节点)+(B 节点总样本数 / 数据集中样本数) 基尼不纯度(B 节点)

用此公式计算上例的加权基尼不纯度分数,按性别划分学生时加权基尼不纯度分数 =(10/20)0.32 + (10/20)0.48 = 0.4

一个分类问题波及多个自变量。变量能够是名义变量,也能够是连续变量。决策树很适宜解决不同数据类型的变量。

决策树算法在决定每个节点的拆分时思考了所有可能的变量,能够取得最大加权不纯度增益的变量被用作特定节点的决策变量。

在下面的例子中,应用“性别”作为决策变量的加权不纯度增益是 0.4,然而,假如应用“年级”作为决策变量,加权不纯度增益 0.56,算法将应用“年级”作为创立第一个宰割的决策变量。所有后续步骤都遵循相似的办法,直到每个节点都是同构的。

决策树算法简介

  1. 决策树容易适度拟合,因为算法持续将节点宰割为子节点,直到每个节点变得平均
  2. 与测试集相比,训练数据的精度要高得多,因而须要对决策树进行剪枝,以避免模型适度拟合。剪枝能够通过管制树的深度、每个节点的最大 / 最小样本数、要拆分的节点的最小不纯度增益和最大叶节点来实现
  3. Python 容许用户应用基尼不纯度或熵作为信息增益准则来开发决策树
  4. 能够应用网格搜寻或随机搜寻 CV 对决策树进行微调。CV 代表穿插验证

三种不同不纯度规范的比拟

上面概述的代码片段提供了不同不纯度规范的直观比拟,以及它们如何随不同的概率值而变动。留神上面的代码改编自Deeper Insights into Machine Learning by S.Raschka, D.Julian, and J.Hearty, 2016

import matplotlib.pyplot as plt

import numpy as np

#----- 计算基尼指数
def gini(p):
    return (p)*(1 - (p)) + (1 - p)*(1 - (1-p))

#----- 计算熵
def entropy(p):
    return - p*np.log2(p) - (1 - p)*np.log2((1 - p))

#----- 计算分类误差
def classification_error(p):
    return 1 - np.max([p, 1 - p])

#----- 创立一个从 0 到 1 的概率值 Numpy 数组,增量为 0.01
x = np.arange(0.0, 1.0, 0.01)

#--- 不同 p 值的熵
ent = [entropy(p) if p != 0 else None for p in x]

#---- 取得缩放后的熵
sc_ent = [e*0.5 if e else None for e in ent]

#-- 分类谬误
err = [classification_error(i) for i in x]

#-- 绘图

fig = plt.figure();
plt.figure(figsize=(10,8));
ax = plt.subplot(111);

for i, lab, ls, c, in zip([ent, sc_ent, gini(x), err], ['Entropy', 'Entropy (scaled)','Gini Impurity',
                                                        'Misclassification Error'],['-', '-', '--', '-.'],
                          ['black', 'darkgray','blue', 'brown', 'cyan']):
    line = ax.plot(x, i, label=lab,
    linestyle=ls, lw=2, color=c)
    
ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=3, fancybox=True, shadow=False)
ax.axhline(y=0.5, linewidth=1, color='k', linestyle='--')
ax.axhline(y=1.0, linewidth=1, color='k', linestyle='--')
plt.ylim([0, 1.1])
plt.xlabel('p(i=1)')
plt.ylabel('Impurity Index')
plt.show()

练习

问题陈说旨在建设一个分类模型来预测红酒的品质。

这是一个典型的多类分类问题。留神,所有的机器学习模型都对异样值敏感,因而在构建树之前,应该解决由异样值组成的特色 / 独立变量。

不同个性 / 独立变量的一个重要方面是它们如何相互作用。皮尔逊相干能够用来确定数据集中两个特色之间的关联水平。然而,对于像决策树这样的基于决策的算法,咱们不会抛弃高度相干的变量。

# 导入所需的库 -
%matplotlib inline

import numpy as np
import pandas as pd

from sklearn.tree import DecisionTreeClassifier

import numpy as np
import pandas as pd
import seaborn as sns

sns.set(color_codes=True)

from matplotlib import pyplot as plt
from sklearn.model_selection import train_test_split #分为训练集和测试集
from sklearn.tree import DecisionTreeClassifier #构建决策树模型

from sklearn import metrics
from sklearn.metrics import accuracy_score,f1_score,recall_score,precision_score, confusion_matrix #模型验证
%matplotlib inline

from IPython.display import display #用于在一个输入中显示多个数据帧

from sklearn.feature_extraction.text import CountVectorizer  #DT 不承受字符串作为模型拟合步骤的输出

import missingno as msno_plot #缺失值绘图

wine_df = pd.read_csv('winequality-red.csv',sep=';')

数据的疾速描述性统计

wine_df.describe().transpose().round(2)

查看缺失值

# 非缺失值的条形图
plt.title('#Non-missing Values by Columns')
msno_plot.bar(wine_df);

异样值检查和解决

# 查看异样值
plt.figure(figsize=(15,15))
pos = 1
for i in wine_df.columns:
    plt.subplot(3, 4, pos)
    sns.boxplot(wine_df[i])
    pos += 1

col_names=['fixed acidity', 'volatile acidity', 'citric acid', 'residual sugar',
       'chlorides', 'free sulfur dioxide', 'total sulfur dioxide', 'density',
       'pH', 'sulphates', 'alcohol']

display(col_names)

for i in col_names:
    q1, q2, q3 = wine_df[i].quantile([0.25,0.5,0.75])
    IQR = q3 - q1
    lower_cap=q1-1.5*IQR
    upper_cap=q3+1.5*IQR
    wine_df[i]=wine_df[i].apply(lambda x: upper_cap if x>(upper_cap) else (lower_cap if x<(lower_cap) else x))

下面的异样值应用 Q1–1.5*IQR 和 Q3+1.5*IQR 值进行提取。Q1、Q3 和 IQR 别离代表第一四分位数、第三四分位数和四分位数间的范畴。

sns.pairplot(wine_df);

了解不同变量之间的关系。留神。在决策树中,咱们不须要删除高度相干的变量,因为节点只应用一个独立变量被划分为子节点,因而,即便两个或多个变量高度相干,产生最高信息增益的变量也将用于剖析。

plt.figure(figsize=(10,8))
sns.heatmap(wine_df.corr(),
            annot=True,
            linewidths=.5,
            center=0,
            cbar=False,
            cmap="YlGnBu")
plt.show()

分类问题对类别不均衡很敏感。当一个类值所占比例较大时,就会呈现类不均衡。类别均衡是通过将因变量“quality”属性的值组合而产生的。

plt.figure(figsize=(10,8))
sns.countplot(wine_df['quality']);

wine_df['quality'] = wine_df['quality'].replace(8,7)
wine_df['quality'] = wine_df['quality'].replace(3,5)
wine_df['quality'] = wine_df['quality'].replace(4,5)
wine_df['quality'].value_counts(normalize=True)

将数据分为训练集和测试集,以查看模型的准确性,并查找是否存在过拟合或欠拟合。

# 将数据合成为训练集和测试集

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test =train_test_split(wine_df.drop('quality',axis=1), wine_df['quality'], test_size=.3, random_state=22)
X_train.shape,X_test.shape

利用基尼准则建设了决策树模型。请留神,为了简略起见,咱们将树剪枝到最大深度 3。这将有助于咱们将树可视化,并将其与咱们在初始局部中探讨的概念分割起来。

clf_pruned = DecisionTreeClassifier(criterion = "gini", random_state = 100,
                               max_depth=3, min_samples_leaf=5)
clf_pruned.fit(X_train, y_train)

请留神,能够调整以下参数以改良模型输入(Scikit Learn,2019)。

  1. criterion — 应用的度量,例如基尼不纯度
  2. class_weight — None,代表所有类权重为 1
  3. max_depth — 3; 剪枝。当“None”示意节点将开展,直到所有叶子都是同构的
  4. max_features — None; 在决定节点的宰割时,要思考所有的特色或自变量
  5. max_leaf_nodes — None;
  6. min_impurity_decrease — 0.0; 只有当宰割确保不纯度的缩小大于或等于零时,节点才被宰割
  7. min_impurity_split — None;
  8. min_samples_leaf — 1; 一个叶子存在所需的最小样本数
  9. min_samples_split — 2; 如果 min_samples_leaf =1,则示意右节点和左节点应该各有一个样本,即父节点或根节点应该至多有两个样本
  10. splitter —‘best’; 用于在每个节点抉择宰割的策略。最好确保在决定宰割时思考到所有的特色
from sklearn.tree import export_graphviz
from sklearn.externals.six import StringIO  
from IPython.display import Image  
import pydotplus
import graphviz

xvar = wine_df.drop('quality', axis=1)
feature_cols = xvar.columns

dot_data = StringIO()
export_graphviz(clf_pruned, out_file=dot_data,  
                filled=True, rounded=True,
                special_characters=True,feature_names = feature_cols,class_names=['0','1','2'])

from pydot import graph_from_dot_data
(graph,) = graph_from_dot_data(dot_data.getvalue())
Image(graph.create_png())

preds_pruned = clf_pruned.predict(X_test)
preds_pruned_train = clf_pruned.predict(X_train)
print(accuracy_score(y_test,preds_pruned))
print(accuracy_score(y_train,preds_pruned_train))

模型对训练数据和测试数据的准确度得分别离为 0.60 和 0.62。

特色重要性是指一类将分数调配给预测模型的输出特色的技术,该技术批示在进行预测时每个特色的绝对重要性。

## 计算特色重要性

feat_importance = clf_pruned.tree_.compute_feature_importances(normalize=False)

feat_imp_dict = dict(zip(feature_cols, clf_pruned.feature_importances_))
feat_imp = pd.DataFrame.from_dict(feat_imp_dict, orient='index')
feat_imp.rename(columns = {0:'FeatureImportance'}, inplace = True)
feat_imp.sort_values(by=['FeatureImportance'], ascending=False).head()

DecisionTreeClassifier()提供诸如 min_samples_leaf 和 max_depth 等参数,以避免树适度拟合。

能够看成是如下场景,在这个场景中,咱们明确定义树的深度和最大叶子数。然而,最大的挑战是如何确定一棵树应该蕴含的最佳深度和叶子。

在下面的例子中,咱们应用 max_depth=3,min_samples_leaf=5。这些数字只是用来察看树的行为的示例图。然而,如果在事实中,咱们被要求钻研这个模型并为模型参数找到一个最佳值,这是一个挑战,但并非不可能(决策树模型能够应用 GridSearchCV 算法进行微调)。

另一种办法是应用老本复杂性剪枝(CCP)。

老本复杂性剪枝为管制树的大小提供了另一种抉择。在 DecisionTreeClassifier 中,这种剪枝技术是由代价复杂性参数 ccp_alpha 来参数化的。ccp_alpha 值越大,剪枝的节点数就越多。

简略地说,老本复杂性是一个阈值。只有当模型的整体不纯度改善了一个大于该阈值的值时,该模型才会将一个节点进一步拆分为其子节点,否则将进行。

当 CCP 值较低时,即便不纯度缩小不多,该模型也会将一个节点宰割成子节点。随着树的深度减少,这一点很显著,也就是说,当咱们沿着决策树往下走时,咱们会发现宰割对模型整体不纯度的变动没有太大奉献。然而,更高的宰割保障了类的正确分类,即准确度更高。

当 CCP 值较低时,会创立更多的节点。节点越高,树的深度也越高。

上面的代码 (Scikit Learn) 阐明了如何对 alpha 进行调整,以取得更高精度分数的模型。

path = model_gini.cost_complexity_pruning_path(X_train, y_train)
ccp_alphas, impurities = path.ccp_alphas, path.impurities

fig, ax = plt.subplots(figsize=(16,8));
ax.plot(ccp_alphas[:-1], impurities[:-1], marker='o', drawstyle="steps-post");
ax.set_xlabel("effective alpha");
ax.set_ylabel("total impurity of leaves");
ax.set_title("Total Impurity vs effective alpha for training set");

让咱们理解随着 alpha 的变动深度和节点数的变动。

clfs = clfs[:-1]

ccp_alphas = ccp_alphas[:-1]

node_counts = [clf.tree_.node_count for clf in clfs]

depth = [clf.tree_.max_depth for clf in clfs]

fig, ax = plt.subplots(2, 1,figsize=(16,8))

ax[0].plot(ccp_alphas, node_counts, marker='o', drawstyle="steps-post")
ax[0].set_xlabel("alpha")
ax[0].set_ylabel("number of nodes")
ax[0].set_title("Number of nodes vs alpha")
ax[1].plot(ccp_alphas, depth, marker='o', drawstyle="steps-post")
ax[1].set_xlabel("alpha")
ax[1].set_ylabel("depth of tree")
ax[1].set_title("Depth vs alpha")
fig.tight_layout()

理解 α 减少时精度的变动。

fig, ax = plt.subplots(figsize=(16,8)); #设置大小
train_scores = [clf.score(X_train, y_train) for clf in clfs]
test_scores = [clf.score(X_test, y_test) for clf in clfs]
ax.set_xlabel("alpha")
ax.set_ylabel("accuracy")
ax.set_title("Accuracy vs alpha for training and testing sets")
ax.plot(ccp_alphas, train_scores, marker='o', label="train",
        drawstyle="steps-post")
ax.plot(ccp_alphas, test_scores, marker='o', label="test",
        drawstyle="steps-post")
ax.legend()
plt.show()

i = np.arange(len(ccp_alphas))
ccp = pd.DataFrame({'Depth': pd.Series(depth,index=i),'Node' : pd.Series(node_counts, index=i),\
                    'ccp' : pd.Series(ccp_alphas, index = i),'train_scores' : pd.Series(train_scores, index = i),
                   'test_scores' : pd.Series(test_scores, index = i)})
ccp.tail()
ccp[ccp['test_scores']==ccp['test_scores'].max()]

下面的代码提供了在测试数据中产生最高精度的成本计算剪枝值。

参考文献

  1. Raschka, S., Julian, D. and Hearty, J. (2016). Python : deeper insights into machine learning : leverage benefits of machine learning techniques using Python : a course in three modules. Birmingham, Uk: Packt Publishing, pp.83, 88, 89.
  2. ‌Scikit-learn: Machine Learning in Python, Pedregosa et al., JMLR 12, pp. 2825–2830, 2011.
  3. Scikit Learn (2019). sklearn.tree.DecisionTreeClassifier — scikit-learn 0.22.1 documentation. [online] Scikit-learn.org. Available at: https://scikit-learn.org/stab…
  4. Scikit Learn (n.d.). Post pruning decision trees with cost complexity pruning. [online] Available at: https://scikit-learn.org/stab…

原文链接:https://towardsdatascience.co…

欢送关注磐创 AI 博客站:
http://panchuang.net/

sklearn 机器学习中文官网文档:
http://sklearn123.com/

欢送关注磐创博客资源汇总站:
http://docs.panchuang.net/

退出移动版