决策树与随机森林

决策树

决策树节点字段的选择

即CART算法,它既可以处理离散型的分类问题(分类决策树),也可解决连续型的预测问题(回归决策树)。这两种树分别对应
DecisionTreeClassifier类和DecisionTreeRegressor类

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)

DecisionTreeRegressor(criterion='mse', 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, 
	presort=False)

criterion:用于指定选择节点字段的评价指标,对于分类决策树,默为'gini',表示采用基尼指数选择节点的最佳分割字段;对于回归决策树,默认为'mse',表示使用均方误差选择节点的最佳分割字段。

splitter:用于指定节点中的分割点选择方法,默认为'best',表示从所有的分割点中选择最佳分割点;如果指定为'random',则表示随机选择分割点。

max_depth:用于指定决策树的最大深度,默认为None,表示树的生长过程中对深度不做任何限制。

min_samples_split:用于指定根节点或中间节点能够继续分割的最小样本量,默认为2。

min_samples_leaf:用于指定叶节点的最小样本量,默认为1。

min_weight_fraction_leaf:用于指定叶节点最小的样本权重,默认为None,表示不考虑叶节点的样本权值。

max_features:用于指定决策树包含的最多分割字段数,默认为None,表示分割时使用所有的字段,与指定'auto'效果一致;如果为具体的整数,则考虑使用对应的分割字段数;如果为01的浮点数,则考虑对应百分比的字段个数;如果为'sqrt',则表示最多考虑个字段;如果为'log2',则表示最多使用log2P个字段。

random_state:用于指定随机数生成器的种子,默认为None,表示使用默认的随机数生成器。

max_leaf_nodes:用于指定最大的叶节点个数,默认为None,表示对叶节点个数不做任何限制。

min_impurity_decrease:用于指定节点是否继续分割的最小不纯度值,默认为0。

min_impurity_split:同参数min_impurity_decrease含义一致,该参数已在0.21版本剔除。

class_weight:用于指定因变量中类别之间的权重,默认为None,表示每个类别的权重都相等;如果为balanced,则表示类别权重与原始样本中类别的比例成反比;还可以通过字典传递类别之间的权重差异,其形式为{class_label:weight}。

presort:bool类型参数,是否对数据进行预排序,默认为False。如果数据集的样本量比较小,设置为True可以提高模型的执行速度;如果数据集的样本量比较大,则不易设置为True

决策树的剪枝技术

sklearn模块并没有提供后剪枝的运算函数或“方法”。这并不意味着sklearn模块中的决策树算法没有实际的落地意义,因为它提供了决策树的预剪枝技术,如果预剪枝不够理想,还可以使用集成的随机森林算法,该算法综合了多棵决策树,可以很好地避免单棵决策树过拟合的可能。

随机森林

RandomForestClassifier(n_estimators=10, criterion='gini', max_depth=None, 
	min_samples_split=2, min_samples_leaf=1, 
	min_weight_fraction_leaf=0.0, max_features='auto', 
	max_leaf_nodes=None, min_impurity_decrease=0.0, 
	min_impurity_split=None, bootstrap=True, 
	oob_score=False, n_jobs=1, random_state=None, 
	verbose=0, warm_start=False, class_weight=None)

RandomForestRegressor(n_estimators=10, criterion='gini', max_depth=None, 
	min_samples_split=2, min_samples_leaf=1, 
	min_weight_fraction_leaf=0.0, max_features='auto', 
	max_leaf_nodes=None, min_impurity_decrease=0.0, 
	min_impurity_split=None, bootstrap=True, 
	oob_score=False, n_jobs=1, random_state=None, 
	verbose=0, warm_start=False)

n_estimators:用于指定随机森林所包含的决策树个数。

criterion:用于指定每棵决策树节点的分割字段所使用的度量标准,用于分类的随机森林,默认的criterion值为'gini';用于回归的随机森林,默认的criterion值为'mse'。

max_depth:用于指定每棵决策树的最大深度,默认不限制树的生长深度。

min_samples_split:用于指定每棵决策树根节点或中间节点能够继续分割的最小样本量,默认为2。

min_samples_leaf:用于指定每棵决策树叶节点的最小样本量,默认为1。

min_weight_fraction_leaf:用于指定每棵决策树叶节点最小的样本权重,默认为None,表示不考虑叶节点的样本权值。

max_features:用于指定每棵决策树包含的最多分割字段数,默认为None,表示分割时使用所有的字段。

max_leaf_nodes:用于指定每棵决策树最大的叶节点个数,默认为None,表示对叶节点个数不做任何限制。

min_impurity_decrease:用于指定每棵决策树的节点是否继续分割的最小不纯度值,默认为0。

bootstrap:bool类型参数,是否对原始数据集进行bootstrap抽样,用于子树的构建,默认为True。

oob_score:bool类型参数,是否使用包外样本计算泛化误差,默认为False,包外样本是指每次bootstrap抽样时没有被抽中的样本。

n_jobs:用于指定计算随机森林算法的CPU个数,默认为1。

random_state:用于指定随机数生成器的种子,默认为None,表示使用默认的随机数生成器。

verbose:用于指定随机森林计算过程中是否输出日志信息,默认为0,表示不输出。

warm_start:bool类型参数,是否基于上一次的训练结果进行本次的运算,默认为False。

class_weight:用于指定因变量中类别之间的权重,默认为None,表示每个类别的权重都相等。

应用决策树

利用分类决策树和分类随机森林对Titanic数据集进行拟合,该数据集一共包含891个观测和12个变量,其中变量Survived为因变量,1表示存活,0表示未存活

# 导入第三方模块
import pandas as pd
# 读入数据
Titanic = pd.read_csv(r'Titanic.csv')
Titanic.head()

PassengerId	Survived	Pclass	Name	Sex	Age	SibSp	Parch	Ticket	Fare	Cabin	Embarked
0	1	0	3	Braund, Mr. Owen Harris	male	22.0	1	0	A/5 21171	7.2500	NaN	S
1	2	1	1	Cumings, Mrs. John Bradley (Florence Briggs Th...	female	38.0	1	0	PC 17599	71.2833	C85	C
2	3	1	3	Heikkinen, Miss. Laina	female	26.0	0	0	STON/O2. 3101282	7.9250	NaN	S
3	4	1	1	Futrelle, Mrs. Jacques Heath (Lily May Peel)	female	35.0	1	0	113803	53.1000	C123	S
4	5	0	3	Allen, Mr. William Henry	male	35.0	0	0	373450	8.0500	NaN	S

PassengerId为乘客编号、Name为乘客姓名、Ticket为船票信息、Cabin为客
舱信息,将这四个变量用于建模并没有实际意义,故需要将它们从表中除;Pclass为船舱等级,虽然为数字,但仍然需要做哑变量处理,因为它属于类别变量;变量Sex和Embarked均为离散的字符型变量,在建模之前都需要对其进行重编码,如因子化处理、One-Hot编码或哑变量处理等

# 删除无意义的变量,并检查剩余自字是否含有缺失值
Titanic.drop(['PassengerId','Name','Ticket','Cabin'], axis = 1, inplace = True)
Titanic.isnull().sum(axis = 0)

out:
Survived      0
Pclass        0
Sex           0
Age         177
SibSp         0
Parch         0
Fare          0
Embarked      2
dtype: int64
# 对Sex分组,用各组乘客的平均年龄填充各组中的缺失年龄
fillna_Titanic = []
for i in Titanic.Sex.unique():
    update = Titanic.loc[Titanic.Sex == i,].fillna(value = {'Age': Titanic.Age[Titanic.Sex == i].mean()})
    fillna_Titanic.append(update)
Titanic = pd.concat(fillna_Titanic)
# 使用Embarked变量的众数填充缺失值
Titanic.fillna(value = {'Embarked':Titanic.Embarked.mode()[0]}, inplace=True)
Titanic.head()


Survived	Pclass	Sex	Age	SibSp	Parch	Fare	Embarked
0	0	3	male	22.000000	1	0	7.2500	S
4	0	3	male	35.000000	0	0	8.0500	S
5	0	3	male	30.726645	0	0	8.4583	Q
6	0	1	male	54.000000	0	0	51.8625	S
7	0	3	male	2.000000	3	1	21.0750	S
# 将数值型的Pclass转换为类别型,否则无法对其哑变量处理
Titanic.Pclass = Titanic.Pclass.astype('category')
# 哑变量处理
dummy = pd.get_dummies(Titanic[['Sex','Embarked','Pclass']])
# 水平合并Titanic数据集和哑变量的数据集
Titanic = pd.concat([Titanic,dummy], axis = 1)
# 删除原始的Sex、Embarked和Pclass变量
Titanic.drop(['Sex','Embarked','Pclass'], inplace=True, axis = 1)
Titanic.head()

	Survived	Age	SibSp	Parch	Fare	Sex_female	Sex_male	Embarked_C	Embarked_Q	Embarked_S	Pclass_1	Pclass_2	Pclass_3
0	0	22.000000	1	0	7.2500	0	1	0	0	1	0	0	1
4	0	35.000000	0	0	8.0500	0	1	0	0	1	0	0	1
5	0	30.726645	0	0	8.4583	0	1	0	1	0	0	0	1
6	0	54.000000	0	0	51.8625	0	1	0	0	1	1	0	0
7	0	2.000000	3	1	21.0750	0	1	0	0	1	0	0	1
# 导入第三方包
from sklearn import model_selection
# 取出所有自变量名称
predictors = Titanic.columns[1:]
# 将数据集拆分为训练集和测试集,且测试集的比例为25%
X_train, X_test, y_train, y_test = model_selection.train_test_split(Titanic[predictors], Titanic.Survived, 
                                                                    test_size = 0.25, random_state = 1234)

为了防止构建的决策树产生过拟合,需要对决策树进行预剪枝,如限制树生长的最大深度、设置决策树的中间节点能够继续分支的最小样本量以及叶节点的最小样本量等。为了能够得到比较理想的树,需要不断尝试不同组合的参数值。所幸的是,Python提供了网格搜索法,可以帮助用户快速地进行各参数组合下的试错,网格搜索法的实现需要调用GridSearchCV类,该“类”存储在sklearn的子模块model_selection中。接下来利用GridSearchCV类选择最佳的参数组合,代码如下:

# 导入第三方模块
from sklearn.model_selection import GridSearchCV
from sklearn import tree
# 预设各参数的不同选项值
max_depth = [2,3,4,5,6]
min_samples_split = [2,4,6,8]
min_samples_leaf = [2,4,8,10,12]
# 将各参数值以字典形式组织起来
parameters = {'max_depth':max_depth, 'min_samples_split':min_samples_split, 'min_samples_leaf':min_samples_leaf}
# 网格搜索法,测试不同的参数值
grid_dtcateg = GridSearchCV(estimator = tree.DecisionTreeClassifier(), param_grid = parameters, cv=10)
# 模型拟合
grid_dtcateg.fit(X_train, y_train)
# 返回最佳组合的参数值
grid_dtcateg.best_params_

out:
{'max_depth': 3, 'min_samples_leaf': 10, 'min_samples_split': 2}

经过10重交叉验证的网格搜索,得到各参数的最佳组合值为3,4,2。根据经
验,如果数据量比较小时,树的最大深度可设置在10以内,反之则需设置比较大的树深度,如20左右。接下来利用这个参数值构建分类决策树

# 导入第三方模块
from sklearn import metrics
# 构建分类决策树
CART_Class = tree.DecisionTreeClassifier(max_depth=3, min_samples_leaf = 4, min_samples_split=2)
# 模型拟合
decision_tree = CART_Class.fit(X_train, y_train)
# 模型在测试集上的预测
pred = CART_Class.predict(X_test)
# 模型的准确率
print('模型在测试集的预测准确率:\n',metrics.accuracy_score(y_test, pred))

out:

模型在测试集的预测准确率:
 0.8295964125560538

为了进一步验证模型在测试集上的预测效果,需要绘制ROC曲线

# 导入第三方包
import matplotlib.pyplot as plt
y_score = CART_Class.predict_proba(X_test)[:,1]
fpr,tpr,threshold = metrics.roc_curve(y_test, y_score)
# 计算AUC的值
roc_auc = metrics.auc(fpr,tpr)

# 绘制面积图
plt.stackplot(fpr, tpr, color='steelblue', alpha = 0.5, edgecolor = 'black')
# 添加边际线
plt.plot(fpr, tpr, color='black', lw = 1)
# 添加对角线
plt.plot([0,1],[0,1], color = 'red', linestyle = '--')
# 添加文本信息
plt.text(0.5,0.3,'ROC curve (area = %0.2f)' % roc_auc)
# 添加x轴与y轴标签
plt.xlabel('1-Specificity')
plt.ylabel('Sensitivity')
# 显示图形
plt.show()

决策树实际上就是一个含有IF…THEN…逻辑的判断条件,为了展现决策
树背后逻辑,这里将决策树进行可视化展现

# 需要在电脑中安装Graphviz
# https://graphviz.gitlab.io/_pages/Download/Download_windows.html
# 然后将解压文件中的bin设置到环境变量中
# 导入第三方模块
from sklearn.tree import export_graphviz
from IPython.display import Image
import pydotplus
#from sklearn.externals.six import StringIO
from six import StringIO

# 绘制决策树
dot_data = StringIO()
export_graphviz(
    decision_tree,
    out_file=dot_data,  
    #feature_names=predictors,
    class_names=['Unsurvived','Survived'],  
    filled=True,
    rounded=True,  
    special_characters=True
)
# 决策树展现
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
Image(graph.create_png()) 

决策树与随机森林_第1张图片
通过对决策树的预剪枝,生长成一棵深度为3的树(根节点不算一层深
度),根节点所选的变量为Sex_male,并且以0.5作为分割点,其对应的左分支节点表示女性乘客,右分支节点为男性乘客。以决策树的最左边分支为例,解释背后的IF…THEN逻辑,如果该乘客是乘坐非三等舱的女性,并且票价小于等于29.356,那么她将是一位幸存者。

应用随机森林

对比使用随机森林算法,这样做的目的出于两方面,一方面是为了避免单棵决策树出现过拟合的可能,另一方面在某种程度上可以提高模型的预测准确率

# 导入第三方包
from sklearn import ensemble
# 构建随机森林
RF_class = ensemble.RandomForestClassifier(n_estimators=200, random_state=1234)
# 随机森林的拟合
RF_class.fit(X_train, y_train)
# 模型在测试集上的预测
RFclass_pred = RF_class.predict(X_test)
# 模型的准确率
print('模型在测试集的预测准确率:\n',metrics.accuracy_score(y_test, RFclass_pred))

out:
# 导入第三方包
from sklearn import ensemble
# 构建随机森林
RF_class = ensemble.RandomForestClassifier(n_estimators=200, random_state=1234)
# 随机森林的拟合
RF_class.fit(X_train, y_train)
# 模型在测试集上的预测
RFclass_pred = RF_class.predict(X_test)
# 模型的准确率
print('模型在测试集的预测准确率:\n',metrics.accuracy_score(y_test, RFclass_pred))

也对该模型产生的结果绘制ROC曲线

# 计算绘图数据
y_score = RF_class.predict_proba(X_test)[:,1]
fpr,tpr,threshold = metrics.roc_curve(y_test, y_score)
roc_auc = metrics.auc(fpr,tpr)
# 绘图
plt.stackplot(fpr, tpr, color='steelblue', alpha = 0.5, edgecolor = 'black')
plt.plot(fpr, tpr, color='black', lw = 1)
plt.plot([0,1],[0,1], color = 'red', linestyle = '--')
plt.text(0.5,0.3,'ROC curve (area = %0.2f)' % roc_auc)
plt.xlabel('1-Specificity')
plt.ylabel('Sensitivity')
plt.show()

利用理想的随机森林算法挑选出影响乘客是否幸存的重要因素

# 变量的重要性程度值
importance = RF_class.feature_importances_
# 构建含序列用于绘图
Impt_Series = pd.Series(importance, index = X_train.columns)
# 对序列排序绘图
Impt_Series.sort_values(ascending = True).plot(kind='barh')
plt.show()

决策树与随机森林_第2张图片
对各自变量的重要性做了降序排列,其中最重要的前三个变量分别是乘客
的年龄、票价和是否为女性,从而在一定程度上能够体现危难时机妇女和儿童优先被救援的精神。

预测问题的解决

因变量不再是离散的类别值,而是连续的数值。使用的数据集是关于患者的肾小球滤过率,该指标可以反映患者肾功能的健康状况,该数据集一共包含28 009条记录和10个变量

# 读入数据
NHANES = pd.read_excel(r'NHANES.xlsx')
NHANES.head()
print(NHANES.shape)
# 取出自变量名称
predictors = NHANES.columns[:-1]
# 将数据集拆分为训练集和测试集
X_train, X_test, y_train, y_test = model_selection.train_test_split(NHANES[predictors], NHANES.CKD_epi_eGFR, 
                                                                    test_size = 0.25, random_state = 1234)

# 预设各参数的不同选项值
max_depth = [18,19,20,21,22]
min_samples_split = [2,4,6,8]
min_samples_leaf = [2,4,8]
parameters = {'max_depth':max_depth, 'min_samples_split':min_samples_split, 'min_samples_leaf':min_samples_leaf}
# 网格搜索法,测试不同的参数值
grid_dtreg = GridSearchCV(estimator = tree.DecisionTreeRegressor(), param_grid = parameters, cv=10)
# 模型拟合
grid_dtreg.fit(X_train, y_train)
# 返回最佳组合的参数值
grid_dtreg.best_params_

out:
{'max_depth': 22, 'min_samples_leaf': 2, 'min_samples_split': 4}

由于训练数据集的样本量比较大,因此设置的树深度在20左右。经过10重交
叉验证的网格搜索,得到各参数的最佳组合值为20,2,4。接下来利用这个参数值构建回归决策树

# 构建用于回归的决策树
CART_Reg = tree.DecisionTreeRegressor(max_depth = 20, min_samples_leaf = 2, min_samples_split = 4)
# 回归树拟合
CART_Reg.fit(X_train, y_train)
# 模型在测试集上的预测
pred = CART_Reg.predict(X_test)
# 计算衡量模型好坏的MSE值
metrics.mean_squared_error(y_test, pred)

# 构建用于回归的随机森林
RF = ensemble.RandomForestRegressor(n_estimators=200, random_state=1234)
# 随机森林拟合
RF.fit(X_train, y_train)
# 模型在测试集上的预测
RF_pred = RF.predict(X_test)
# 计算模型的MSE值
metrics.mean_squared_error(y_test, RF_pred)

由于因变量为连续型的数值,因此不能再使用分类模型中的准确率指标进行评估,而是使用均方误差MSE或均方根误差RMSE,该指标越小,说明模型拟合效果越好。通过模型在测试集上的预测,计算得到MSE的值为1.84。由于树的深度高达20层,不便于绘制决策树图

# 构建变量重要性的序列
importance = pd.Series(RF.feature_importances_, index = X_train.columns)
# 排序并绘图
importance.sort_values().plot(kind='barh')
plt.show()

你可能感兴趣的:(机器学习,决策树,随机森林,机器学习)