阿喽哈~小伙伴们,今天我们来唠一唠决策树 ♣ ♣ ♣
决策树应该是很多小伙伴入门机器学习的时候最先接触到的分类算法之一,决策树分为分类树和回归树,今天我们只说分类树~
简单回顾一下分类树的算法原理:分类树的底层算法分为三种,分别是ID3, C4.5和CART树。
具体更深入的算法原理这里就不多说啦,本篇的重点是演示如何使用Sklearn实现分类树以及简单的调参,上代码~
一般在敲代码之前可以先导入需要用到的包,这样代码比较好看,而且也方便管理,是一个良好的编码习惯哦
import pandas as pd
import numpy as np
import seaborn as sns
from sklearn import tree # 导入模型
from sklearn.model_selection import train_test_split # 制作数据集和测试集
from sklearn.preprocessing import OrdinalEncoder
import graphviz
from sklearn.metrics import roc_curve, auc
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['font.size'] = 24
为了方便大家代码复现,本次使用的是python自带的泰坦尼克号生还数据集,共981个样本,特征涉及性别、年龄、船票价格、是否有同伴等等,标签列有两个,分别是‘survived’和‘alive’,都表示该乘客是否生还,所以我们取一列就可以了
data = sns.load_dataset('titanic') # 导入泰坦尼克号生还数据
data
首先进行数据的预处理,首先可以看到‘deck’列存在缺失值,我们可以看看总体数据缺失的情况
data.replace(to_replace=r'^\s*$', value=np.nan, regex=True, inplace=True) # 把各类缺失类型统一改为NaN的形式
data.isnull().mean()
共4列数据存在缺失值,‘deck’缺失率超过70%,予以删除,剩余特征的缺失值使用其均值或是众数进行填补
细心地童鞋可能发现了有好几列重复的特征,‘embarked’和‘embark_town’都表示出发港口,‘sex’、‘who’、‘adult_male’都表示性别,‘pclass’和‘class’都是船票类型,‘sibsp’和‘alone’都表示是否有同伴,对于这几个特征,所以我们保留其中一个就可以了
del data['deck'] # 删除‘deck’列
del data['who']
del data['adult_male']
del data['class']
del data['alone']
data['age'].fillna(np.mean(data.age), inplace=True) # 年龄特征使用均值对缺失值进行填补
data['embarked'].fillna(data['embarked'].mode(dropna=False)[0], inplace=True) # 文本型特征视同众数进行缺失值填补
x = data.drop(['alive', 'survived', 'embark_town'], axis=1) # 取出用于建模的特征列X
label = data['survived'] # 取出标签列Y
sklean中的决策树算法是无法进行字符串的处理的,所以要先进行数据编码,这里我们就使用最简单的特征编码,转化完毕后特征全部变为数值型
oe = OrdinalEncoder() # 定义特征转化函数
# 把需要转化的特征都写进去
x[['sex', 'embarked']] = oe.fit_transform(x[['sex', 'embarked']])
x.head()
# 划分训练集、测试集
xtrain, xtest, ytrain, ytest = train_test_split(x, label, test_size=0.3)
xtrain.head()
ok,现在可以把我们的数据集扔进决策树算法去跑一跑啦
"""
# sklearn封装的决策树算法及所有超参数
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=False)
"""
决策树的剪枝操作可以很好地防止模型过拟合,因为在用训练集训练模型时,如果不控制决策树的深度或叶子结点个数等参数,树就会一直生长下去,直到每个结点都无法再划分下去,算法才会停止,这时候模型在训练集上的预测精度会很高,但是会把训练集中的随机因素或是噪音点都当做信息学习到模型里,这样我们的模型就会过于片面,面对其他数据集的时候可能预测能力会很差。
接下来我会训练两个模型,同样的数据集,第一个不剪枝,第二个进行剪枝处理,然后在测试集上测试预测精度,让我看一看两个模型的精度如何吧~
lf = tree.DecisionTreeClassifier(class_weight='balanced', max_depth=None)# 载入决策树分类模型
clf = clf.fit(xtrain, ytrain) # 决策树拟合,得到模型
score = clf.score(xtest, ytest) #返回预测的准确度
clf_new = tree.DecisionTreeClassifier(class_weight='balanced', max_depth=10) # 载入决策树分类模型
clf_new = clf_new.fit(xtrain, ytrain) # 决策树拟合,得到模型
score_new = clf_new.score(xtest, ytest) #返回预测的准确度
print(" 决策树_未剪枝:{} \n".format(score), "决策树_剪枝:{}".format(score_new))
看一下结果,未剪枝的决策树在测试集上的准确度反而更高,别慌,遇到事情不要慌哈哈哈,我们来想一想:有时模型准确度会受到原数据的label分布影响,尤其像信贷数据这种,逾期样本(label为1)的个数往往只占数据集十分之一,甚至更少。所以我们往往对于分类模型来说,会使用AUC值来判断模型的预测能力,AUC可以很好地解决数据不平衡带来的问题,那我们下面再来看一看两个模型的AUC值吧
# 未剪枝决策树 预测测试集
y_test_proba = clf.predict_proba(xtest)
false_positive_rate, recall, thresholds = roc_curve(ytest, y_test_proba[:, 1])
# 未剪枝决策树AUC
roc_auc = auc(false_positive_rate, recall)
# 剪枝决策树 预测测试集
y_test_proba_new = clf_new.predict_proba(xtest)
false_positive_rate_new, recall_new, thresholds_new = roc_curve(ytest, y_test_proba_new[:, 1])
# 剪枝决策树AUC
roc_auc_new = auc(false_positive_rate_new, recall_new)
# 画出两个模型ROC曲线
plt.plot(false_positive_rate, recall, color='blue', label='AUC_orig=%0.3f' % roc_auc)
plt.plot(false_positive_rate_new, recall_new, color='orange', label='AUC_jianzhi=%0.3f' % roc_auc_new)
plt.legend(loc='best', fontsize=15, frameon=False)
plt.plot([0, 1], [0, 1], 'r--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.0])
plt.ylabel('Recall')
plt.xlabel('Fall-out')
plt.show()
从图上就可以看出,黄色实线代表剪枝决策树,AUC值为0.764,大于未剪枝的决策树,而且ROC面积也明显高于未剪枝的决策树,和我们预想的是一样的,小伙伴们在用决策树的时候一定要记得剪枝哦~
sklearn中的决策树算法有很多超参数,通过对超参数的可以在一定程度上提高模型的性能,我们这里尝试调整max_depth最大深度这个参数
# 定义空列表,用来存放每一个树的深度所对应的AUC值
auc_test = []
for i in range(20):
clf = tree.DecisionTreeClassifier(class_weight='balanced', max_depth = i + 1)
clf = clf.fit(xtrain, ytrain)
y_test_proba = clf.predict_proba(xtest)
false_positive_rate, recall, thresholds = roc_curve(ytest, y_test_proba[:, 1])
roc_auc = auc(false_positive_rate, recall)
auc_test.append(roc_auc)
plt.plot(range(1,21),auc_test,color="red",label="max_depth")
plt.legend()
plt.show()
随着树的增加,AUC值是逐渐下降的,根据这个图就可以选择最合适的max_depth
clf = tree.DecisionTreeClassifier(class_weight='balanced', max_depth=5)
clf = clf.fit(xtrain, ytrain) # 决策树拟合,得到模型
#特征重要性
feature_name = ['ALLMONEY', 'CAPARTY01', 'DJK5NNSYYFS', 'FINAL_SCORE', 'LOANCARDLEDGER', 'MARRIAGESTATUS']
clf.feature_importances_
[*zip(feature_name,clf.feature_importances_)]
# 横向柱状图
plt.barh(np.arange(len(feature_name)), clf.feature_importances_, align='center')
plt.yticks(np.arange(len(feature_name)), feature_name, fontsize=15)
plt.xticks(fontsize=10)
plt.xlabel('Importances', fontsize=15)
plt.xlim(0,1)
plt.show()
可以看到,所有的特征都有特征重要性,但在实际应用中,并不是所有的特征都会被用到,这也是由于我们剪枝的原因,一些不重要的特征可能就不会出现在决策树中,也就是说在整颗树中没有这个结点,所以如果碰到特征重要性为0的情况,也是正常的
dot_data = tree.export_graphviz(clf, feature_names=feature_name, class_names=['good', 'bad'], filled=True, rounded=True)
graph = graphviz.Source(dot_data)
graph
树有点大哈哈哈放不下了,这时候我们可以以图片或者pdf的形式把树保存到电脑里,便于观察~
mytree = graphviz.Source(dot_data, format='pdf')
mytree.render('D:/weiyu/汇报/早会分享/decision_tree')
本人才疏学浅,若有理解有误的地方,还请各路大佬批评指正♡♡♡
ok!感恩的心~