二、随机森林
2.1 概述
2.1.1 集成算法
- 概述:它本身不是一个单独的机器学习算法,而是通过在数据上构建多个模型,集成所有模型的建模结果;
- 目标:集成算法会考虑多个评估器的建模结果,汇总之后得到一个综合的结果,以此来获取比单个模型更好的回归或分类表现;
-
模型:
(1)多个模型集成成为的模型叫做集成评估器(ensemble estimator);
(2)组成集成评估器的每个模型都叫做基评估器(base estimator); -
集成算法:
(1)装袋法(Bagging):构建多个相互独立的评估器,然后对其预测进行平均或多数表决原则来决定集成评估器的结果。装袋法的代表模型就是随机森林;
(2)提升法(Boosting):基评估器是相关的,是按顺序一一构建的。其核心思想是结合弱评估器的力量一次次对难以评估的样本进行预测,从而构成一个强评估器。提升法的代表模型有Adaboost和梯度提升树
(3)stacking:我不到啊,一般都不用;
2.1.2 sklearn中的集成算法模块
- sklearn中的集成算法模块ensemble:
集成算法中,有一半以上都是树的集成模型,可以想见决策树在集成中必定是有很好的效果。在这堂课中,我们会以随机森林为例,慢慢为大家揭开集成算法的神秘面纱。
2.2 RandomForestClassifier
class sklearn.ensemble.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=None, random_state=None, verbose=0, warm_start=False, class_weight=None)
随机森林是非常具有代表性的Bagging集成算法,它的所有基评估器都是决策树,分类树 组成的森林就叫做随机森林分类器,回归树 所集成的森林就叫做随机森林回归器。
2.2.1 重要参数
(1) 控制基评估器(决策树)的参数(之前学过了的)
单个决策树的准确率越高,随机森林的准确率也会越高,因为装袋法是依赖于平均值或者少数服从多数原则来决定集成的结果的
(2)n_estimators
- 这是森林中树木的数量,即基评估器的数量。这个参数对随机森林模型的精确性影响是单调的,n_estimators越大,模型的效果往往越好。
1.导入需要的包
#设定运行环境
%matplotlib inline
from sklearn.tree import DecisionTreeClassifier
#导入随机森林分类器
from sklearn.ensemble import RandomForestClassifier
#datasets是一个生成各种数据的模块
from sklearn.datasets import load_wine
2.导入需要的数据集
wine = load_wine()
3.sklearn建模的基本流程
from sklearn.model_selection import train_test_split
Xtrain, Xtest, Ytrain, Ytest = train_test_split(wine.data,wine.target,test_size=0.3)
clf = DecisionTreeClassifier(random_state=0)
rfc = RandomForestClassifier(random_state=0)
clf = clf.fit(Xtrain,Ytrain)
rfc = rfc.fit(Xtrain,Ytrain)
score_c = clf.score(Xtest,Ytest)
score_r = rfc.score(Xtest,Ytest)
print("Single Tree:{}".format(score_c)
,"Random Forest:{}".format(score_r)
)
#Single Tree:0.8888888888888888 Random Forest:0.9814814814814815
4. 画出随机森林和决策树在一组交叉验证下的效果对比
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
rfc = RandomForestClassifier(n_estimators=25)
rfc_s = cross_val_score(rfc,wine.data,wine.target,cv=10)
clf = DecisionTreeClassifier()
clf_s = cross_val_score(clf,wine.data,wine.target,cv=10)
plt.plot(range(1,11),rfc_s,label = "RandomForest")
plt.plot(range(1,11),clf_s,label = "Decision Tree")
plt.legend()
plt.show()
5. 画出随机森林和决策树在十组交叉验证下的效果对比5. 画出随机森林和决策树在十组交叉验证下的效果对比
rfc_l = []
clf_l = []
for i in range(10):
rfc = RandomForestClassifier(n_estimators=25)
rfc_s = cross_val_score(rfc,wine.data,wine.target,cv=10).mean()
rfc_l.append(rfc_s)
clf = DecisionTreeClassifier()
clf_s = cross_val_score(clf,wine.data,wine.target,cv=10).mean()
clf_l.append(clf_s)
plt.plot(range(1,11),rfc_l,label = "Random Forest")
plt.plot(range(1,11),clf_l,label = "Decision Tree")
plt.legend()
plt.show()
6. n_estimators的学习曲线
#用来记录每次的结果
superpa = []
#让随机森林在不同的n_estimators上跑了200次
for i in range(200):
rfc = RandomForestClassifier(n_estimators=i+1,n_jobs=-1)
rfc_s = cross_val_score(rfc,wine.data,wine.target,cv=10).mean()
superpa.append(rfc_s)
#输出最高的准确率以及对应的索引
print(max(superpa),superpa.index(max(superpa)))
plt.figure(figsize=[20,5])
plt.plot(range(1,201),superpa)
plt.show()
(3)random_state
- 随机森林的本质是一种装袋集成算法(bagging),装袋集成算法是对基评估器的预测结果进行平均或用多数表决原则来决定集成评估器的结果。假设一棵树判断错误的可能性为0.2(ε),那13棵树以上都判断错误的可能性是:
i是判断错误的次数,也是判错的树的数量,ε是一棵树判断错误的概率,(1-ε)是判断正确的概率,共判对25-i次。采用组合,是因为25棵树中,有任意i棵都判断错误。
import numpy as np
from scipy.special import comb
np.array([comb(25,i)*(0.2**i)*((1-0.2)**(25-i)) for i in range(13,26)]).sum()
#0.00036904803455582827
- 在随机森林中的random_state控制的是生成森林的模式,而非让一个森林中只有一棵树;
fc = RandomForestClassifier(n_estimators=20,random_state=2)
rfc = rfc.fit(Xtrain, Ytrain)
#随机森林的重要属性之一:estimators,查看森林中树的状况
rfc.estimators_
每棵树的random_state都不一样
rfc.estimators_[0].random_state
for i in range(len(rfc.estimators_)):
print(rfc.estimators_[i].random_state)
当random_state固定时,随机森林中生成是一组固定的树,但每棵树依然是不一致的,这是用”随机挑选特征进行分枝“的方法得到的随机性。用袋装法集成时,基分类器应当是相互独立的,是不相同的。
(4)bootstrap & oob_score
- 要让基分类器尽量都不一样,一种很容易理解的方法是使用不同的训练集来进行训练,而袋装法正是通过有放回的随机抽样技术来形成不同的训练数据,bootstrap就是用来控制抽样技术的参数;
- bootstrap:其实就是有放回的抽样。bootstrap参数默认True,代表采用这种有放回的随机抽样技术;
- 问题:由于是有放回,一些样本可能在同一个自助集中出现多次,而其他一些却可能被忽略,如下图所示:
一般来说,自助集大约平均会包含63%的原始数据。因为每一个样本被抽到某个自助集中的概率为:
- 当n足够大时,这个概率收敛于1-(1/e),约等于0.632。因此,会有约37%的训练数据被浪费掉,没有参与建模,这些数据被称为袋外数据(out of bag data,简写为oob)
——>也就是说,在使用随机森林时,我们可以不划分测试集和训练集,只需要用袋外数据来测试我们的模型即可。当n和n_estimators都不够大的时候,很可能就没有数据掉落在袋外,自然也就无法使用oob数据来测试模型了。
如果希望用袋外数据来测试,则需要在实例化时就将oob_score这个参数调整为True,训练完毕之后,我们可以用随机森林的另一个重要属性:oob_score_来查看我们的在袋外数据上测试的结果:
#无需划分训练集和测试集
rfc = RandomForestClassifier(n_estimators=25,oob_score=True)
rfc = rfc.fit(wine.data,wine.target)
#重要属性oob_score_,用这个来获得测试结果
rfc.oob_score_
2.2.2 重要属性和接口
- 常用接口:apply, fit, predict和score;
- predict_proba:这个接口返回每个测试样本对应的被分到每一类标签的概率,标签有几个分类就返回几个概率;sklearn中的随机森林是平均每个样本对应的predict_proba返回的概率,得到一个平均概率,从而决定测试样本的分类。
2.3 RandomForestRegressor
class sklearn.ensemble.RandomForestRegressor (n_estimators=’warn’, criterion=’mse’, 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=None, random_state=None, verbose=0, warm_start=False)
所有的参数,属性与接口,全部和随机森林分类器一致。仅有的不同就是回归树与分类树的不同,不纯度的指标,参数Criterion不一致;
2.3.1 重要参数,属性与接口
(1)criterion:
-
回归树衡量分枝质量的指标,支持的标准有三种:
(1)输入"mse"使用均方误差mean squared error(MSE),父节点和叶子节点之间的均方误差的差额将被用来作为特征选择的标准,这种方法通过使用叶子节点的均值来最小化L2损失;
(2)输入“friedman_mse”使用费尔德曼均方误差,这种指标使用弗里德曼针对潜在分枝中的问题改进后的均方误差;
(3)输入"mae"使用绝对平均误差MAE(mean absolute error),这种指标使用叶节点的中值来最小化L1损失;
这些和决策树里头的都差不多
注意:在回归树中,MSE不只是我们的分枝质量衡量指标,也是我们最常用的衡
量回归树回归质量的指标。在回归中,我们追求的是,MSE越小越好;
- 重要属性和接口:都与随机森林的分类器相一致,还是apply, fit, predict和score最为核心。值得一提的是,随机森林回归并没有predict_proba这个接口,因为对于回归来说,并不存在一个样本要被分到某个类别的概率问题,因此没有predict_proba这个接口;
- 随机森林回归用法:和决策树完全一致,除了多了参数n_estimators:
from sklearn.datasets import load_boston
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestRegressor
#生成一个字典
boston = load_boston()
regressor = RandomForestRegressor(n_estimators=100,random_state=0)
cross_val_score(regressor, boston.data, boston.target, cv=10
,scoring = "neg_mean_squared_error")
import sklearn
#sklearn当中所有评估指标(打分)列表
sorted(sklearn.metrics.SCORERS.keys())
注意:在这里,如果不填写scoring = "neg_mean_squared_error",交叉验证默认的模型衡量指标是R平方,因此交叉验证的结果可能有正也可能有负。而如果写上scoring,则衡量标准是负MSE,交叉验证的结果只可能为负。
各种评估指标:
2.3.2 实例:用随机森林回归填补缺失值
面对缺失值,很多人选择的方式是直接将含有缺失值的样本删除,这是一种有效的方法,但是有时候填补缺失值会比直接丢弃样本效果更好,即便我们其实并不知道缺失值的真实样貌。在sklearn中,我们可以使用sklearn.impute.SimpleImputer来轻松地将均值,中值,或者其他最常用的数值填补到数据中,在这个案例中,我们将使用均值,0,和随机森林回归来填补缺失值,并验证四种状况下的拟合状况,找出对使用的数据集来说最佳的缺失值填补方法。
1.导入需要的库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_boston
#用来填补缺失值的类
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestRegressor
2. 以波士顿数据集为例,导入完整的数据集并探索
dataset = load_boston()
dataset.data.shape
#总共506*13=6578个数据,对于标签是连续型变量的数据用回归
X_full, y_full = dataset.data, dataset.target
n_samples = X_full.shape[0]
n_features = X_full.shape[1]
3.为完整数据集放入缺失值
#首先确定我们希望放入的缺失数据的比例,在这里我们假设是50%,那总共就要有3289个数据缺失
rng = np.random.RandomState(0)
missing_rate = 0.5
#计算缺失的个数,np.floor向下取整,返回.0格式的浮点数
n_missing_samples = int(np.floor(n_samples * n_features * missing_rate))
#所有数据要随机遍布在数据集的各行各列当中,而一个缺失的数据会需要一个行索引和一个列索引
#如果能够创造一个数组,包含3289个分布在0~506中间的行索引,和3289个分布在0~13之间的列索引
#那我们就可以利用索引来为数据中的任意3289个位置赋空值
#然后我们用0,均值和随机森林来填写这些缺失值,然后查看回归的结果如何
#randint(下限,上限,n)请在上/下限之间取出n个整数(会有重复)
missing_features = rng.randint(0,n_features,n_missing_samples)
missing_samples = rng.randint(0,n_samples,n_missing_samples)
#missing_samples = rng.choice(dataset.data.shape[0],n_missing_samples,replace=False)
#我们现在采样了3289个数据,远远超过我们的样本量506,所以我们使用随机抽取的函数randint。但如果我们需要
#的数据量小于我们的样本量506,那我们可以采用np.random.choice来抽样,choice会随机抽取不重复的随机数,
#因此可以帮助我们让数据更加分散,确保数据不会集中在一些行中
#创建副本,用于创建缺失的数据集
X_missing = X_full.copy()
#y不能为空,不然就变成了无监督学习
y_missing = y_full.copy()
#让其中指定的位置转为空值
X_missing[missing_samples,missing_features] = np.nan
#转换成DataFrame是为了后续方便各种操作,numpy对矩阵的运算速度快到拯救人生,但是在索引等功能上却不如pandas来得好用
X_missing = pd.DataFrame(X_missing)
此时的X_missing和y_missing就变成了:
4.使用0和均值填补缺失值
#使用均值进行填补
from sklearn.impute import SimpleImputer
#第一个参数指明缺失值长啥样,第二个参数指明用什么来填补缺失值
imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean')
#训练+导出——>fit_transform:先训练后转出的接口
X_missing_mean = imp_mean.fit_transform(X_missing)
#使用0进行填补
imp_0 = SimpleImputer(missing_values=np.nan, strategy="constant",fill_value=0)
X_missing_0 = imp_0.fit_transform(X_missing)
5.使用随机森林填补缺失值
对于一个有n个特征的数据来说,其中特征T有缺失值,我们就把特征T当作标签,其他的n-1个特征和原本的标签组成新的特征矩阵。那对于T来说,它没有缺失的部分,就是我们的Y_test,这部分数据既有标签也有特征,而它缺失的部分,只有特征没有标签,就是我们需要预测的部分。
特征T不缺失的值对应的其他n-1个特征 + 本来的标签:X_train
特征T不缺失的值:Y_train
特征T缺失的值对应的其他n-1个特征 + 本来的标签:X_test
特征T缺失的值:未知,我们需要预测的Y_test
这种做法,对于某一个特征大量缺失,其他特征却很完整的情况,非常适用。
那如果数据中除了特征T之外,其他特征也有缺失值怎么办?
答案是遍历所有的特征,从缺失最少的开始进行填补(因为填补缺失最少的特征所需要的准确信息最少)。
填补一个特征时,先将其他特征的缺失值用0代替,每完成一次回归预测,就将预测值放到原本的特征矩阵中,再继续填补下一个特征。每一次填补完毕,有缺失值的特征会减少一个,所以每次循环后,需要用0来填补的特征就越来越少。当
进行到最后一个特征时(这个特征应该是所有特征中缺失值最多的),已经没有任何的其他特征需要用0来进行填补了,而我们已经使用回归为其他特征填补了大量有效信息,可以用来填补缺失最多的特征。
遍历所有的特征后,数据就完整,不再有缺失值了
X_missing_reg = X_missing.copy()
#找出数据集中缺失值从小到大排列的特征们的顺序,agrsort带有索引
#返回:从小到大排序顺序所对应的索引
sortindex = np.argsort(X_missing_reg.isnull().sum(axis=0)).values
X_missing_reg 的值:
#构建我们的新特征矩阵(没被选中去填充的特征 + 原始标签)和新标签(被选中去填充的特征)
#不在原来的矩阵上进行填补,所以用新的矩阵来替代
df = X_missing_reg
#构建新标签,用切片的方式取出对应列
fillc = df.iloc[:,6]
#新特征矩阵,将新标签和新矩阵连接起来,axis=1说明用行进行匹配(下面这个代码只能运行一次,不然会有一大堆矩阵)
df = pd.concat([df.iloc[:,df.columns != 6],pd.DataFrame(y_full)],axis=1)
此时的df:
#在新特征矩阵中,对含有缺失值的列,进行0的填补
df_0 =SimpleImputer(missing_values=np.nan,
strategy='constant',fill_value=0).fit_transform(df)
此时的df_0:
#找出我们的训练集和测试集
#被选中要填充的特征中(现在是我们的标签),存在的那些值,是非空值
Ytrain = fillc[fillc.notnull()]
#被选中要填充的特征中(现在是我们的标签),不存在的那些值,是空值
#我们所需要的是Ytest的索引,用来指明哪些是空值
Ytest = fillc[fillc.isnull()]
#在新特征矩阵上,被选出来的要填充的特征的非空值所对应的记录
Xtrain = df_0[Ytrain.index,:]
#在新特征矩阵上,被选出来的要填充的特征的空值所对应的记录
Xtest = df_0[Ytest.index,:]
#跑模型
#用随机森林回归来填补缺失值
rfc = RandomForestRegressor(n_estimators=100)
rfc = rfc.fit(Xtrain, Ytrain)
#用predict接口将Xtest导入,得到预测结果(回归结果),就是我们要用来填补空值的值
Ypredict = rfc.predict(Xtest)
所得到的Ypredict就是一串标签(随即回归森林预测所得):
#将填补好的特征返回到我们的原始的特征矩阵中
X_missing_reg.loc[X_missing_reg.iloc[:,6].isnull(),6] = Ypredict
最终得到了填补完毕的X_missing_reg:
用上述原理调用for循环:
#for循环来进行填补缺失值,共有五步
for i in sortindex:
#构建我们的新特征矩阵(没被选中去填充的特征 + 原始标签)和新标签(被选中去填充的特征)
#不在原来的矩阵上进行填补,所以用新的矩阵来替代
df = X_missing_reg
#构建新标签,用切片的方式取出对应列
fillc = df.iloc[:,i]
#新特征矩阵,将新标签和新矩阵连接起来,axis=1说明用行进行匹配(下面这个代码只能运行一次,不然会有一大堆矩阵)
df = pd.concat([df.iloc[:,df.columns != i],pd.DataFrame(y_full)],axis=1)
#在新特征矩阵中,对含有缺失值的列,进行0的填补
df_0 =SimpleImputer(missing_values=np.nan,
strategy='constant',fill_value=0).fit_transform(df)
#找出我们的训练集和测试集
#被选中要填充的特征中(现在是我们的标签),存在的那些值,是非空值
Ytrain = fillc[fillc.notnull()]
#被选中要填充的特征中(现在是我们的标签),不存在的那些值,是空值
#我们所需要的是Ytest的索引,用来指明哪些是空值
Ytest = fillc[fillc.isnull()]
#在新特征矩阵上,被选出来的要填充的特征的非空值所对应的记录
Xtrain = df_0[Ytrain.index,:]
#在新特征矩阵上,被选出来的要填充的特征的空值所对应的记录
Xtest = df_0[Ytest.index,:]
#跑模型
#用随机森林回归来填补缺失值
rfc = RandomForestRegressor(n_estimators=100)
rfc = rfc.fit(Xtrain, Ytrain)
#用predict接口将Xtest导入,得到预测结果(回归结果),就是我们要用来填补空值的值
Ypredict = rfc.predict(Xtest)
#将填补好的特征返回到我们的原始的特征矩阵中
X_missing_reg.loc[X_missing_reg.iloc[:,i].isnull(),i] = Ypredict
填完之后的X_missing_reg:
此时我们有了四种数据:完整数据X_full、用回归数据来填的X_missing_reg、用0填的X_missing_0和用均值填的X_missing_mean
6. 对填补好的数据进行建模
对所有数据进行建模,取得MSE结果
X = [X_full,X_missing_mean,X_missing_0,X_missing_reg]
#mse要越小越好
mse = []
for x in X:
estimator = RandomForestRegressor(random_state=0, n_estimators=100)
scores = cross_val_score(estimator,x,y_full,scoring='neg_mean_squared_error', cv=5).mean()
mse.append(scores * -1)
所得到的得分结果为:
7. 用所得结果画出条形图
x_labels = ['Full data',
'Zero Imputation',
'Mean Imputation',
'Regressor Imputation']
colors = ['r', 'g', 'b', 'orange']
#画出画布
plt.figure(figsize=(12, 6))
#添加子图plt.subplot(一般在有多个图标要画的时候用来定义图标位置,这里可以有多个图)
ax = plt.subplot(111)
for i in np.arange(len(mse)): #相当于range(mse)相当于y轴
#画条形图(从左往右)barh(共有i个数,现在画第i个数,alpha是颜色深度,align说明放在哪)
ax.barh(i, mse[i],color=colors[i], alpha=0.6, align='center')
ax.set_title('Imputation Techniques with Boston Data')
#让X轴特征不从0开始
ax.set_xlim(left=np.min(mse) * 0.9,
right=np.max(mse) * 1.1)
ax.set_yticks(np.arange(len(mse)))
ax.set_xlabel('MSE')
#按x的标签分类
ax.set_yticklabels(x_labels)
plt.show()
2.4 机器学习中调参的基本思想
-
泛化误差(Genelization error):用来衡量模型在未知数据上的准确率的指标:
当模型在未知数据(测试集或者袋外数据)上表现糟糕时,我们说模型的泛化程度不够,泛化误差大,模型的效果不好。泛化误差受到模型的结构(复杂度)影响:
当模型太复杂,模型就会过拟合,泛化能力就不够,所以泛化误差大。当模型太简单,模型就会欠拟合,拟合能力就不够,所以误差也会大。只有当模型的复杂度刚刚好的才能够达到泛化误差最小的目标。
——>对树模型来说,树越茂盛,深度越深,枝叶越多,模型就越复杂。所以树模型是天生位于图的右上角的模型,随机森林是以树模型为基础,所以随机森林也是天生复杂度高的模型。随机森林的参数,都是向着一个目标去:减少模型的复杂度,把模型往图像的左边移动,防止过拟合;
- 调参的时候的注意点:
- 模型太复杂或者太简单,都会让泛化误差高,我们追求的是位于中间的平衡点;
- 模型太复杂就会过拟合,模型太简单就会欠拟合;
- 对树模型和树的集成模型来说,树的深度越深,枝叶越多,模型越复杂;
- 树模型和树的集成模型的目标,都是减少模型复杂度,把模型往图像的左边移动。
-
调参顺序:
一般就调2~4就行了
接下来就可以开始实操了:
2.4.1 案例:随机森林在乳腺癌数据上的调参随机森林在乳腺癌数据上的调参
1. 导入需要的库
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
2.导入数据集,探索数据导入数据集,探索数据
data = load_breast_cancer()
#是一个二分类数据,样本量很少,容易发生过拟合
3. 进行一次简单的建模,看看模型本身在数据集上的效果
rfc = RandomForestClassifier(n_estimators=100,random_state=90)
score_pre = cross_val_score(rfc,data.data,data.target,cv=10).mean()
score_pre
#得分为0.9648809523809524
#这里可以看到,随机森林在乳腺癌数据上的表现本就还不错,在现实数据集上,基本上不可能什么都不调就看到95%以上的准确率
4. 随机森林调整的第一步:无论如何先来调n_estimators在这里我们选择学习曲线,可以使用网格搜索吗?可以,但是只有学习曲线,才能看见趋势我个人的倾向是,要看见n_estimators在什么取值开始变得平稳,是否一直推动模型整体准确率的上升等信息第一次的学习曲线,可以先用来帮助我们划定范围,我们取每十个数作为一个阶段,来观察n_estimators的变化如何引起模型整体准确率的变化
scorel = []
for i in range(0,200,10):
rfc = RandomForestClassifier(n_estimators=i+1,
n_jobs=-1,
random_state=90)
score = cross_val_score(rfc,data.data,data.target,cv=10).mean()
scorel.append(score)
#输出最高分以及其对应的n_estimators的值
print(max(scorel),(scorel.index(max(scorel))*10)+1)
plt.figure(figsize=[20,5])
plt.plot(range(1,201,10),scorel)
plt.show()
#list.index([object])
#返回这个object在列表list中的索引
所得结果为:
5. 在确定好的范围内,进一步细化学习曲线
- 跟上面其实都一样,不过范围重新划了一下
scorel = []
for i in range(65,75):
rfc = RandomForestClassifier(n_estimators=i,
n_jobs=-1,
random_state=90)
score = cross_val_score(rfc,data.data,data.target,cv=10).mean()
scorel.append(score)
print(max(scorel),([*range(65,75)][scorel.index(max(scorel))]))
plt.figure(figsize=[20,5])
plt.plot(range(65,75),scorel)
plt.show()
接下来就进入网格搜索,我们将使用网格搜索对参数一个个进行调整
6. 为网格搜索做准备,书写网格搜索的参数
为什么我们不同时调整多个参数呢?原因有两个:
- 1)同时调整多个参数会运行非常缓慢,在课堂上我们没有这么多的时间;
- 2)同时调整多个参数,会让我们无法理解参数的组合是怎么得来的,所以即便网格搜索调出来的结果不好,我们也不知道从哪里去改。在这里,为了使用复杂度-泛化误差方法(方差-偏差方法),我们对参数进行一个个地调整。
"""
有一些参数是没有参照的,很难说清一个范围,这种情况下我们使用学习曲线,看趋势
从曲线跑出的结果中选取一个更小的区间,再跑曲线
param_grid = {'n_estimators':np.arange(0, 200, 10)}
param_grid = {'max_depth':np.arange(1, 20, 1)}
param_grid = {'max_leaf_nodes':np.arange(25,50,1)}
对于大型数据集,可以尝试从1000来构建,先输入1000,每100个叶子一个区间,再逐渐缩小范围
有一些参数是可以找到一个范围的,或者说我们知道他们的取值和随着他们的取值,模型的整体准确率会如何变化,
这样的参数我们就可以直接跑网格搜索
param_grid = {'criterion':['gini', 'entropy']}
param_grid = {'min_samples_split':np.arange(2, 2+20, 1)}
param_grid = {'min_samples_leaf':np.arange(1, 1+10, 1)}
param_grid = {'max_features':np.arange(5,30,1)}
"""
7. 开始按照参数对模型整体准确率的影响程度进行调参,首先调整max_depth
#调整max_depth
param_grid = {'max_depth':np.arange(1, 20, 1)}
# 一般根据数据的大小来进行一个试探,乳腺癌数据很小,所以可以采用1~10,或者1~20这样的试探
# 但对于像digit recognition那样的大型数据来说,我们应该尝试30~50层深度(或许还不足够
# 更应该画出学习曲线,来观察深度对模型的影响
rfc = RandomForestClassifier(n_estimators=73
,random_state=90
)
#第一个参数是模型,第二个是要搜索的参数,第三个就是次数
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(data.data,data.target)
#调整后的最佳参数
GS.best_params_
#{'max_depth': 8}
#调整后的最佳分数
GS.best_score_
#0.9666353383458647
注意:将max_depth设置为有限之后,模型的准确率下降了,这是因为:限制max_depth,是让模型变得简单,把模型向左推,而模型整体的准确率下降了,即整体的泛化误差上升了,这说明模型现在位于图像左边,即泛化误差最低点的左边(偏差为主导的一边);
——>除了max_features,我们没有任何参数可以调整了
因为max_depth,min_samples_leaf和min_samples_split是剪枝参数,是减小复杂度的参数。
8. 调整max_features
#调整max_features
param_grid = {'max_features':np.arange(5,30,1)}
"""
max_features是唯一一个即能够将模型往左(低方差高偏差)推,也能够将模型往右(高方差低偏差)推的参数。我
们需要根据调参前,模型所在的位置(在泛化误差最低点的左边还是右边)来决定我们要将max_features往哪边调。
现在模型位于图像左侧,我们需要的是更高的复杂度,因此我们应该把max_features往更大的方向调整,可用的特征
越多,模型才会越复杂。max_features的默认最小值是sqrt(n_features),因此我们使用这个值作为调参范围的
最小值。
"""
rfc = RandomForestClassifier(n_estimators=73
,random_state=90
)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(data.data,data.target)
GS.best_params_
# {'max_features': 24}
GS.best_score_
# 0.9666666666666668
网格搜索返回了max_features的最小值,可见max_features升高之后,模型的准确率降低了。这说明,我们把模型往右推,模型的泛化误差增加了。前面用max_depth往左推,现在用max_features往右推,泛化误差都增加,这说明模型本身已经处于泛化误差最低点,已经达到了模型的预测上限,没有参数可以左右的部分了。剩下的那些误差,是噪声决定的,已经没有方差和偏差的舞台了。
9. 调整min_samples_leaf
#调整min_samples_leaf
param_grid={'min_samples_leaf':np.arange(1, 1+10, 1)}
#对于min_samples_split和min_samples_leaf,一般是从他们的最小值开始向上增加10或20
#面对高维度高样本量数据,如果不放心,也可以直接+50,对于大型数据,可能需要200~300的范围
#如果调整的时候发现准确率无论如何都上不来,那可以放心大胆调一个很大的数据,大力限制模型的复杂度
rfc = RandomForestClassifier(n_estimators=73
,random_state=90
)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(data.data,data.target)
GS.best_params_
#{'min_samples_leaf': 1}
GS.best_score_
#0.9666353383458647
10. 不懈努力,继续尝试min_samples_split
#调整min_samples_split
param_grid={'min_samples_split':np.arange(2, 2+20, 1)}
rfc = RandomForestClassifier(n_estimators=39
,random_state=90
)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(data.data,data.target)
GS.best_params_
#{'min_samples_split': 2}
GS.best_score_
#0.9666353383458647
和min_samples_leaf一样的结果,返回最小值并且模型整体的准确率降低了
11. 最后尝试一下criterion
#调整Criterion
param_grid = {'criterion':['gini', 'entropy']}
rfc = RandomForestClassifier(n_estimators=39
,random_state=90
)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(data.data,data.target)
GS.best_params_
#{'criterion': 'gini'}
GS.best_score_
#0.9666353383458647
12. 调整完毕,总结出模型的最佳参数
rfc = RandomForestClassifier(n_estimators=73,random_state=90)
score = cross_val_score(rfc,data.data,data.target,cv=10).mean()
score
#0.9666353383458647
score - score_pre
#0.0017543859649122862
总结:在整个调参过程之中,我们首先调整了n_estimators(无论如何都请先走这一步),然后调整max_depth,通过max_depth产生的结果,来判断模型位于复杂度-泛化误差图像的哪一边,从而选择我们应该调整的参数和调参的方向。如果感到困惑,也可以画很多学习曲线来观察参数会如何影响我们的准确率,选取学习曲线中单调的部分来放大研究(如同我们对n_estimators做的)。学习曲线的拐点也许就是我们一直在追求的,最佳复杂度对应的泛化误差最低点(也是方差和偏差的平衡点)。
网格搜索:它也可以一起调整多个参数,大家只要有时间,可以自己跑一下,看看网格搜索会给我们怎样的结果,有时候,它的结果比我们的好,有时候,我们手动调整的结果会比较好。当然了,我们的乳腺癌数据集非常完美,所以只需要调n_estimators一个参数就达到了随机森林在这个数据集上表现得极限。在我们上周使用的泰坦尼克号案例的数据中,我们使用同样的方法调出了如下的参数组合:
rfc = RandomForestClassifier(n_estimators=68
,random_state=90
,criterion="gini"
,min_samples_split=8
,min_samples_leaf=1
,max_depth=12
,max_features=2
,max_leaf_nodes=36
)
这个组合的准确率达到了83.915%,比单棵决策树提升了大约7%,比调参前的随机森林提升了2.02%,这对于调参来说其实是一个非常巨大的进步。
不过,泰坦尼克号数据的运行缓慢,大家量力量时间而行,可以试试看用复杂度-泛化误差方法(方差-偏差方法)来解读一下这个调参结果和过程。