随机森林

1 概述

1.1 集成算法概述

集成学习ensemble learning)是时下非常流行的机器学习算法,它本身不是一个单独的机器学习算法,而是通在数据上构建多个模型集成所有模型的建模结果。基本上所有的机器学习领域都可以看到集成学习的身影,在现实中集成学习也有相当大的作用,它可以用来做市场营销模拟的建模,统计客户来源,保留和流失,也可用来预测疾病的风险和病患者的易感性。在现在的各种算法竞赛中,随机森林,梯度提升树(GBDT),Xgboost等集成算法的身影也随处可见,可见其效果之好,应用之广。

集成算法的目标

集成算法会考虑多个评估器的建模结果,汇总之后得到一个综合的结果,以此来获取比单个模型更好的回归或  分类表现。

多个模型集成成为的模型叫做集成评估器(ensemble estimator),组成集成评估器的每个模型都叫做基评估器(base estimator)对随机森林来说,基评估器就是决策树)。通常来说,有三类集成算法:装袋法(Bagging),提升法(Boosting)和stacking

装袋法核心思想是构建多个相互独立的评估器,然后对其预测进行平均或多数表决原则来决定集成评估器的结果。装袋法的代表模型就是随机森林。采样n次,建n个模型,互不影响,例:随机森林中每棵树都是互相独立的

提升法基评估器是相关,是按顺序一一构建的。其核心思想是结合弱评估器的力量一次次对难以评估的样本进行预测,从而构成一个强评估器。提升法的代表模型有Adaboost和梯度提升树。

1.2 sklearn中的集成算法

sklearn中的集成算法模块ensemble

类的功能

ensemble.AdaBoostClassifier

AdaBoost分类

ensemble.AdaBoostRegressor

Adaboost回归

ensemble.BaggingClassifier

装袋分类器

ensemble.BaggingRegressor

装袋回归器

ensemble.ExtraTreesClassifier

Extra-trees分类(超树,极端随机树)

ensemble.ExtraTreesRegressor

Extra-trees回归

ensemble.GradientBoostingClassifier

梯度提升分类

ensemble.GradientBoostingRegressor

梯度提升回归

ensemble.IsolationForest

隔离森林

ensemble.RandomForestClassifier

随机森林分类

ensemble.RandomForestRegressor

随机森林回归

ensemble.RandomTreesEmbedding

完全随机树的集成

ensemble.VotingClassifier

用于不合适估算器的软投票/多数规则分类器

集成算法中,有一半以上都是树的集成模型,可以想见决策树在集成中必定是有很好的效果。在这堂课中,我们会以随机森林为例,慢慢为大家揭开集成算法的神秘面纱。

· 复习:sklearn中的决策树

在开始随机森林之前,我们先复习一下决策树。决策树是一种原理简单,应用广泛的模型,它可以同时被用于分类和回归问题。决策树的主要功能是从一张有特征和标签的表格中,通过对特定特征进行提问,为我们总结出一系列决策规则对样本分类,并用树状图来呈现这些决策规则。

(例如图中体温是否恒温为特征,哺乳动物为标签)

决策树的核心问题有两个

如何找出正确的特征来进行提问,即如何分枝

生长到什么时候应该停

对于第一个问题,我们定义了用来衡量分枝质量的指标不纯度分类树的不纯度用基尼系数信息熵来衡量,回归的不纯度用MSE均方误差来衡量。每次分枝时,决策树对所有的特征进行不纯度计算选取不纯度最低的特征进行分枝,分枝后,又再对被分枝的不同取值下,计算每个特征的不纯度,继续选取不纯度最低的特征进行分枝。

每分枝一层,树整体的不纯度会越来越小,决策树追求的是最小不纯度。因此,决策树会一致分枝,直到没有更多的特征可用,或整体的不纯度指标已经最优,决策树就会停止生长。

决策树非常容易过拟合,这是说,它很容易在训练集上表现优秀,却在测试集上表现很糟糕。为了防止决策树的拟合,我们要对决策树进行剪枝sklearn中提供了大量的剪枝参数,

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袋装性集成算法,它的所有基评估器都是决策树分类树组成的森林就叫做随机森林分类器回归树所集成的森林就叫做随机森林回归器。这一节主要讲解RandomForestClassifier,随机森林分类器。

2.1 重要参数

2.1.1 控制基评估器的参数

参数

含义

criterion

不纯度衡量指标,有基尼系数信息熵使用父节点和子节点之差即信息增益两种选择

max_depth

树的最大深度,超过最大深度的树枝都会被剪掉限制树的生长参数

min_samples_leaf

一个节点在分枝后的每个子节点都必须包含至少min_samples_leaf个训练样本,否则分枝就不会发生限制叶子节点的生长

min_samples_split

一个节点必须要包含至少min_samples_split个训练样本,这个节点才允许被分枝,否则分枝就不会发生

max_features

max_features限制分枝时考虑的特征个数,超过限制个数的特征都会被舍弃,默认值为总特征个数开平方取整限制树的尺寸

min_impurity_decrease

限制信息增益的大小,信息增益小于设定数值的分枝不会发生限制树的尺寸的参数

这些参数在随机森林中的含义,和我们在上决策树时说明的内容一模一样,单个决策树的准确率越高,随机森林的准确率也会越高,因为装袋法是依赖于平均值或者少数服从多数原则来决定集成的结果的。

2.1.2 n_estimators

这是森林中树木的数量,即基评估器的数量。这个参数对随机森林模型的精确性影响是单调的,n_estimators越大,模型的效果往往越好。但是相应的,任何模型都有决策边界n_estimators达到一定的程度之后,随机森林的精确性往往不在上升或开始波动(只会生长到森林的决策边界),并且,n_estimators越大,需要的计算量和内存也越大,训练的时间也会越来越长。对于这个参数,我们是渴望在训练难度和模型效果之间取得平衡。

n_estimators的默认值在现有版本的sklearn中是10,但是在即将更新的0.22版本中,这个默认值会被修正为100。这个修正显示出了使用者的调参倾向:要更大的n_estimators。

·来建立一片森林吧

树模型的优点是简单易懂,可视化之后的树人人都能够看懂,可惜随机森林是无法被可视化的。所以为了更加直观地让大家体会随机森林的效果,我们来进行一个随机森林和单个决策树效益的对比。我们依然使用红酒数据集。

1.导入我们需要的包

#导入我们需要的包

#画图的时候,需要这个环境,将这个jupyter导入到这个环境

%matplotlib inline

from sklearn.tree import DecisionTreeClassifier #分类树

from sklearn.ensemble import RandomForestClassifier

from sklearn.datasets import load_wine #红酒数据集

2.导入需要的数据集

wine = load_wine()

wine.data

wine.target

3.复习:sklearn建模的基本流程

#1.实例化

#2.训练集带入实例化后的模型去进行训练,使用的接口是fit

#3.使用其他接口将测试集导入我们训练好的模型去获取我们希望获取的结果(score,Y_test)

#导入划分训练集和测试集的类

from sklearn.model_selection import train_test_split

#test_size=0.3 即70%做训练集,30%测试集(训练集和测试集随机划分导致结果不同)

Xtrain,Xtest,Ytrain,Ytest = train_test_split(wine.data,wine.target,test_size=0.3)

#1.实例化

clf = DecisionTreeClassifier(random_state=0)

#实例化随机森林

rfc = RandomForestClassifier(random_state=0)

#2.训练数据

clf = clf.fit(Xtrain,Ytrain)

rfc = rfc.fit(Xtrain,Ytrain)

#3.测试

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.8703703703703703

Random Forest:0.9629629629629629

4.画出随机森林和决策树在一组交叉验证下的效果对比

#交叉验证:cross_val_score

#(分训练集和测试集时,不同的分法会对模型结果产生影响),探讨不同的训练集和测试集中,模型的稳定性

#将数据集划分为n分,依次取每一份做测试集,每n-1份做训练集,多次训练模型以观测模型稳定性的方法

from sklearn.model_selection import cross_val_score

import matplotlib.pyplot as plt

#实例化随机森林(森林中数目的数量=25)

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)

#交叉验证的次数10次

plt.plot(range(1,11),rfc_s,label="RandomForest")

plt.plot(range(1,11),clf_s,label="DecisionTree")

#显示图例

plt.legend()

plt.show()

#====================一种更加有趣也更简单的写法===================# """

label = "RandomForest"

for model in [RandomForestClassifier(n_estimators=25),DecisionTreeClassifier()]: score = cross_val_score(model,wine.data,wine.target,cv=10) print("{}:".format(label)),print(score.mean()) plt.plot(range(1,11),score,label = label)

plt.legend()

label = "DecisionTree"

"""

可以看出总的来说随机森林效果好于决策树

5.画出随机森林和决策树在十组交叉验证下的效果对比

#保存验证结果

rfc_1 = []

clf_1 = []

#进行10次建模,总共100次验证

for i in range(10):

rfc = RandomForestClassifier(n_estimators=25)

#参数(实例化的模型,特征矩阵,标签,循环次数)

rfc_s = cross_val_score(rfc,wine.data,wine.target,cv=10).mean()

rfc_1.append(rfc_s)

clf = DecisionTreeClassifier()

clf_s = cross_val_score(clf,wine.data,wine.target,cv=10).mean()

clf_1.append(clf_s)

plt.plot(range(1,11),rfc_1,label = "Random Forest")

plt.plot(range(1,11),clf_1,label = "Decision Tree")

plt.legend()

plt.show()

进一步体现出随机森林的效果比决策树好,且两条曲线走势相同,即决策树效果越好,随机森林效果也越好

6.n_estimators的学习曲线

#####【TIME WARNING: 2mins 30 seconds】#####

superpa = []

for i in range(200):

#n_estimators=i+1从建1棵到200棵,跑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)

#找出准确率最高的n_estimators,list.index(object)返回对象object在列表list中的索引

print(max(superpa),superpa.index(max(superpa)))

plt.figure(figsize=[20,5])

plt.plot(range(1,201),superpa)

plt.show()

结果:0.9944444444444445 12

可以看出随着n_estimators的增长,效果越来越好,但当达到一定数量时,随机森林的精确性不再上升或波动

思考

随机森林用了什么方法,来保证集成的效果一定好于单个分类器?

2.1.3 random_state

随机森林的本质是一种装袋集成算法bagging),装袋集成算法是对基评估器的预测结果进行平均用多数表决原则来决定集成评估器的结果。在刚才的红酒例子中,我们建立了25棵树,对任何一个样本而言,平均或多数表决原则下,当且仅当有13棵以上的树判断错误的时候,随机森林才会判断错误。单独一棵决策树对红酒数据集的分类准确率在0.85上下浮动,假设一棵树判断错误的可能性为0.2(ε),那20棵树以上都判断错误的可能性是:

其中,i是判断错误的次数,也是判错的树的数量,ε是一棵树判断错误的概率,(1-ε)是判断正确的概率,共判对25-i次。采用组合,是因为25棵树中,有任意i棵都判断错误。

import numpy as np

from scipy.special import comb

#假设一棵树判断错误的概率0.2

np.array([comb(25,i)*(0.2**i)*((1-0.2)**(25-i)) for i in range(13,26)]).sum()

-->0.00036904803455582827

可见,判断错误的几率非常小,这让随机森林在红酒数据集上的表现远远好于单棵决策树。

那现在就有一个问题了:我们说袋装法服从多数表决原则或对基分类器结果求平均,这即是说,我们默认森林中的每棵树应该是不同的,并且会返回不同的结果。设想一下,如果随机森林里所有的树的判断结果都一致(全判断对  或全判断错),那随机森林无论应用何种集成原则来求结果,都应该无法比单棵决策树取得更好的效果才对。但我们使用了一样的类DecisionTreeClassifier,一样的参数,一样的训练集和测试集,为什么随机森林里的众多树会有不同的判断结果?

问到这个问题,很多小伙伴可能就会想到了:sklearn中的分类树DecisionTreeClassifier自带随机性,所以随机森林中的树天生就都是不一样的。我们在讲解分类树时曾提到,决策树从最重要的特征中随机选择出一个特征来进行分枝,因此每次生成的决策树都不一样,这个功能由参数random_state控制。

随机森林中其实也有random_state,用法和分类树中相似,只不过在分类树中,一个random_state只控制生成一棵树,而随机森林中的random_state控制的是生成森林的模式,而非让一个森林中只有一棵树。

随机森林的重要属性之一:estimators查看森林中树的状况

rfc=RandomForestClassifier(n_estimators=20

,random_state=2)

rfc = rfc.fit(Xtrain, Ytrain)

#随机森林的重要属性之一:estimators,查看森林中树的状况rfc.estimators_[0].random_state

for i in range(len(rfc.estimators_)):

print(rfc.estimators_[i].random_state)

我们可以观察到,当random_state固定时,随机森林中生成是一组固定的树,但每棵树依然是不一致的,这是用随机挑选特征进行分枝的方法得到的随机性。并且我们可以证明,当这种随机性越大的时候,袋装法的效果一般会越来越好用袋装法集成时,基分类器应当是相互独立的,是不相同的

但这种做法的局限性是很强的,当我们需要成千上万棵树的时候,数据不一定能够提供成千上万的特征来让我们构筑尽量多尽量不同的树。因此,除了random_state。我们还需要其他的随机性。

2.1.4 bootstrap & oob_score

要让基分类器尽量都不一样,一种很容易理解的方法是使用不同的训练集来进行训练,而袋装法正是通过有放回的随机抽样技术来形成不同的训练数据,bootstrap就是用来控制抽样技术的参数。

在一个含有n个样本的原始训练集中,我们进行随机采样,每次采样一个样本,并在抽取下一个样本之前将该样本放回原始训练集,也就是说下次采样时这个样本依然可能被采集到,这样采集n次,最终得到一个和原始训练集一样大的,n个样本组成的自助集由于是随机采样,这样每次的自助集和原始数据集不同,和其他的采样集也是不同的。这样我们就可以自由创造取之不尽用之不竭,并且互不相同的自助集,用这些自助集来训练我们的基分类器,我们的基分类器自然也就各不相同了。

bootstrap参数默认True,代表采用这种有放回的随机抽样技术。通常,这个参数不会被我们设置为False

然而有放回抽样也会有自己的问题。由于是有放回,一些样本可能在同一个自助集中出现多次,而其他一些却可能被忽略,一般来说,自助集大约平均会包含63%的原始数据。因为每一个样本被抽到某个自助集中的概率为:

(逆向思维:在一个自助集里,样本A永远不会被抽到的概率为(1-1/n)^n,所有被抽到的概率就是1-(1-1/n)^n)

n足够大时,这个概率收敛于1-(1/e),约等于0.632。因此,会有37%的训练数据被浪费掉,没有参与建模,这些数据被称为袋外数据(out of bag data,简写为oob)。除了我们最开始就划分好的测试集之外,这些数据也可以被用来作为集成算法的测试集。也就是说,在使用随机森林时,我们可以不划分测试集和训练集,只需要用袋外数据来测试我们的模型即可。当然,这也不是绝对的,nn_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_

-->0.9550561797752809

2.2 重要属性和接口

至此,我们已经讲完了所有随机森林中的重要参数,为大家复习了一下决策树的参数,并通过n_estimators森林中树木的数量random_stateboostrap有放回的随机抽样oob_score用袋外数据测试这四个参数帮助大家了解了袋装法的基本流程和重要概念。同时,我们还介绍了.estimators_ 查看森林中所有树的状况 .oob_score_ 查看袋外数据测试得分 这两个重要属性。除了这两个属性之外,作为树模型的集成算法,随机森林自然也有.feature_importances_这个属性。

随机森林的接口与决策树完全一致,因此依然有四个常用接口apply,  fit,  predict和score。除此之外,还需要注意随机森林的predict_proba接口,这个接口返回每个测试样本对应的被分到每一类标签的概率,标签有几个分类就返回几个概率。如果是二分类问题,则predict_proba返回的数值大于0.5的,被分为1,小于0.5的,被分为0。传统的随机森林是利用袋装法中的规则,平均或少数服从多数来决定集成的结果,而sklearn中的随机森林平均每个样本对应的predict_proba返回的概率,得到一个平均概率,从而决定测试样本的分类。

#大家可以分别取尝试一下这些属性和接口

rfc = RandomForestClassifier(n_estimators=25)

rfc = rfc.fit(Xtrain, Ytrain)

rfc.score(Xtest,Ytest)

-->0.9629629629629629

#特征重要性,可以用zip加特征名,显示更直观

rfc.feature_importances_

-->array([0.15289792, 0.05454116, 0.01171529, 0.022256  , 0.02074498,

0.06950304, 0.16920349, 0.01004513, 0.03146696, 0.2042654 ,

0.12648034, 0.04322132, 0.08365897])

#apply输入测试集返回每个测试样本所在的叶子节点的索引(一般需要提取叶子节点使用)

rfc.apply(Xtest)

-->array([[ 8,  4,  1, ...,  4, 13,  3],

[ 6, 19, 11, ..., 16, 12, 12],

...,

[ 3, 19,  1, ...,  9,  8,  5],

[ 3, 10,  3, ...,  9,  8,  5]], dtype=int64)

#predict输入测试集返回每个测试样本的标签(预测结果)

rfc.predict(Xtest)

-->array([2, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 1, 1, 0, 1, 1, 2, 0, 2, 1, 1, 1,

0, 2, 1, 1, 0, 0, 0, 2, 0, 2, 1, 2, 1, 1, 2, 0, 2, 1, 0, 1, 2, 0,

0, 2, 0, 1, 0, 2, 1, 1, 1, 1])

#返回每个测试样本对应的被分到每一类标签的概率,标签有几个分类就返回几个概率

rfc.predict_proba(Xtest)

-->array([[0.  , 0.  , 1.  ],

[0.96, 0.  , 0.04],

[0.  , 1.  , 0.  ],

...

[0.32, 0.52, 0.16],

[0.28, 0.56, 0.16],

[0.  , 1.  , 0.  ]])

掌握了上面的知识,基本上要实现随机森林分类已经是没问题了。从红酒数据集的表现上来看,随机森林的效用比单纯的决策树要强上不少,大家可以自己更换其他数据来试试看(比如上周完整课案例中的泰坦尼克号数据)。

BonusBagging的另一个必要条件

之前我们说过,在使用袋装法时要求基评估器要尽量独立。其实,袋装法还有另一个必要条件:基分类器的判断准确率至少要超过随机分类器,即时说,基分类器的判断准确率至少要超过50%。之前我们已经展示过随机森林的准确率公式,基于这个公式,我们画出了基分类器的误差率ε和随机森林的误差率之间的图像。大家可以自己运行一下这段代码,看看图像呈什么样的分布。

import numpy as np

from scipy.special import comb

import matplotlib.pyplot as plt

#linspace函数,在0,1间返回20个均匀分布的数

x = np.linspace(0,1,20)

y = []

for epsilon in np.linspace(0,1,20):

E = np.array([comb(25,i)*(epsilon**i)*((1-epsilon)**(25-i))

for i in range(13,26)]).sum()

y.append(E)

plt.plot(x,y,"o-",label="when estimators are different")

plt.plot(x,x,"--",color="red",label="if all estimators are same")

plt.xlabel("individual estimator's error")

plt.ylabel("RandomForest's error")

plt.legend() plt.show()

estimators:基分类器   x轴:单基分类器误差   y轴:随机森林误差

可以从图像上看出,当基分类器的误差率小于0.5,即准确率大于0.5时,集成的效果随机森林是比基分类器单个决策树要好的。相反,当基分类器的误差率大于0.5,袋装的集成算法就失效了。所以在使用随机森林之前,一定要检查,用来组成随机森林的分类树们是否都有至少50%的预测正确率随机森林不一定效果比决策树好

3 RandomForestRegressor(回归)

class sklearn.ensemble.RandomForesRegressor(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不一致。(①不纯度的指标:分类树和随机森林分类器用基尼系数,信息熵,回归树和随机森林回归:mse均方误差 ②模型的衡量指标:分类树和随机森林分类器:accuracy准确率,回归树和随机森林回归:mse均方误差(通常)和R²

3.1 重要参数,属性与接口

criterion

回归树衡量分枝质量的指标,支持的标准有三种:

1)输入"mse"使用均方误差mean squared error(MSE),父节点和叶子节点之间的均方误差的差额将被用来作为特征选择的标准,这种方法通过使用叶子节点的均值来最小化L2损失

2)输入“friedman_mse”使用费尔德曼均方误差,这种指标使用弗里德曼针对潜在分枝中的问题改进后的均方误差

3)输入"mae"使用绝对平均误差MAE(mean absolute error),这种指标使用叶节点的中值来最小化L1损失

其中N是样本数量,i是每一个数据样本,fi是模型回归出的数值,yi是样本点i实际的数值标签。所以MSE的本质,其实是样本真实数据与回归结果的差异。在回归树中,MSE不只是我们的分枝质量衡量指标,也是我们最常用的衡量回归树回归质量的指标,当我们在使用交叉验证,或者其他方式获取回归树的结果时,我们往往选择均方误差MSE作为我们的评估

(在分类树中这个指标是score代表的预测准确率。在回归中,我们追求的是,MSE越小越好然而,回归树的接口score返回的是R平方,并不是MSE。R平方被定义如下:

其中u是残差平方和(MSE * N),v是总平方和,N是样本数量,i是每一个数据样本,是模型回归出的数值,yi 是样本点i实际的数值标签。y帽是真实数值标签的平均数。R平方可以为正为负(如果模型的残差平方和远远大于模型的总平方和,模型非常糟糕,R平方就会为负R平方的取值范围[-oo,-1]),而均方误差MSE永远为正

值得一提的是,虽然均方误差永远为正但是sklearn当中使用均方误差作为评判标准时却是计算”均方误差“(neg_mean_squared_error)。这是因为sklearn在计算模型评估指标的时候,会考虑指标本身的性质,均方误差本身是一种误差,所以被sklearn划分为模型的一种损失(loss),因此在sklearn当中,都以负数表示。真正的均方误差MSE的数值,其实就是neg_mean_squared_error去掉负号的数字

重要属性和接口

最重要的属性和接口,都与随机森林的分类器相一致,还是apply,  fit,   predictscore最为核心。值得一提的是,机森林回归没有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")

-->array([-10.72900447,  -5.36049859,  -4.74614178, -20.84946337,

-12.23497347, -17.99274635,  -6.8952756 , -93.78884428,

-29.80411702, -15.25776814])

#sklearn当中的模型评估指标(打分)列表

import sklearn

sorted(sklearn.metrics.SCORERS.keys())

-->['accuracy',

'adjusted_mutual_info_score',

'adjusted_rand_score',

...'roc_auc_ovr', 'roc_auc_ovr_weighted','v_measure_score']

返回十次交叉验证的结果,注意在这里,如果不填写scoring = "neg_mean_squared_error",交叉验证默认的模型衡量指标R平方取值[-oo,-1],因此交叉验证的结果可能有正也可能有负。而如果写上scoring,则衡量标准是负MSE,交叉验证的结果只可能为负。

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.target #标签是连续型变量,用回归

dataset.data.shape #506个样本,13个特征

#总共506*13=6578个数据

X_full,y_full = dataset.data,dataset.target

n_samples = X_full.shape[0]  #样本数量506

n_features = X_full.shape[1]  #标签数量13

3.为完整数据集放入缺失值

#为完整的数据集放入缺失值

#首先确定我们希望放入的缺失数据的比例(即多少数据变成缺失值),在这里我们假设是50%,那总共就要有3289个数据缺失

#确认一种固定的随机模式

rng = np.random.RandomState(0)

missing_rate = 0.5

#总共506*13*0.5=3289个数据缺失,用int与np.floor取整

n_missing_samples = int(np.floor(n_samples * n_features * missing_rate))

#np.floor向下取整,返回.0格式的浮点数

#所有数据要随机遍布在数据集的各行各列当中,而一个缺失的数据会需要一个行索引和一个列索引

#如果能够创造一个数组,包含3289个分布在0~506中间的行索引,和3289个分布在0~13之间的列索引,那我们就可以利用索引来为数据中的任意3289个位置赋空值

#然后我们用0,均值和随机森林来填写这些缺失值,然后查看回归的结果如何

#randint(下限,上限,n)请在下限和上限之间取出n个整数

missing_features = rng.randint(0,n_features,n_missing_samples) #0-13取出3289个随机整数

missing_samples = rng.randint(0,n_samples,n_missing_samples)   #0-506任意取出3289个随机整数

len(missing_features)

-->3289

#missing_samples = rng.choice(n_samples,n_missing_samples,replace=False)

#我们现在采样了3289个数据,远远超过我们的样本量506,所以我们使用随机抽取的函数randint

#但如果我们需要的数据量小于我们的样本量506

#那我们可以采用np.random.choice来抽样,choice会随机抽取不重复的随机数,因此可以帮助我们让数据更加分散,确保数据不会集中在一些行中

#不改变原数据集

X_missing = X_full.copy()

y_missing = y_full.copy()

#填充nan,不用处理y_missing,因为特征可以空,标签不能空

X_missing[missing_samples,missing_features] = np.nan

X_missing = pd.DataFrame(X_missing)

#转换成DataFrame是为了后续方便各种操作,numpy对矩阵的运算速度快到拯救人生,但是在索引等功能上却不如pandas来得好用

X_missing

-->

0

1

2

3

4

5

6

7

8

9

10

11

12

0

NaN

18.0

NaN

NaN

0.538

NaN

65.2

4.0900

1.0

296.0

NaN

NaN

4.98

1

0.02731

0.0

NaN

0.0

0.469

NaN

78.9

4.9671

2.0

NaN

NaN

396.90

9.14

2

0.02729

NaN

7.07

0.0

NaN

7.185

61.1

NaN

2.0

242.0

NaN

NaN

NaN

...

...

...

...

...

...

...

...

...

...

...

...

...

...

505

0.04741

0.0

11.93

0.0

0.573

6.030

NaN

NaN

1.0

NaN

NaN

396.90

7.88

506 rows × 13 columns

4.使用0和均值填补缺失值

#使用0和均值填补缺失值

from sklearn.impute import SimpleImputer

#使用均值填补

#参数:缺失值(missing_values=np.nan)是什么样子,strategy="",mean代表均值

imp_mean = SimpleImputer(missing_values=np.nan,strategy="mean") #实例化

#训练fit+导出predict >>> 特殊的接口 fit_transform

X_missing_mean = imp_mean.fit_transform(X_missing)

pd.DataFrame(X_missing_mean).isnull().sum()

#布尔值 False = 0,True = 1 ,结果都为0说明缺失值都填补了

-->

0     0

1     0

2     0

3     0

4     0

5     0

6     0

7     0

8     0

9     0

10    0

11    0

12    0

dtype: int64

#使用0进行填补

#constant:常数,fill_value=0即用0

imp_0 = SimpleImputer(missing_values=np.nan, strategy="constant",fill_value=0)

X_missing_0 = imp_0.fit_transform(X_missing)

pd.DataFrame(X_missing_0)

-->之前nan的位置都被0填补了

0

1

2

3

4

5

6

7

8

9

10

11

12

0

0.00000

18.0

0.00

0.0

0.538

0.000

65.2

4.0900

1.0

296.0

0.0

0.00

4.98

1

0.02731

0.0

0.00

0.0

0.469

0.000

78.9

4.9671

2.0

0.0

0.0

396.90

9.14

2

0.02729

0.0

7.07

0.0

0.000

7.185

61.1

0.0000

2.0

242.0

0.0

0.00

0.00

...

...

...

...

...

...

...

...

...

...

...

...

...

...

505

0.04741

0.0

11.93

0.0

0.573

6.030

0.0

0.0000

1.0

0.0

0.0

396.90

7.88

506 rows × 13 columns

5.使用随机森林填补缺失值

"""(理解很重要)

使用随机森林回归填补缺失值(例如用0,均值等填补缺失值都不合适的时候)

任何回归都是从特征矩阵中学习,然后求解连续型标签y的过程,之所以能够实现这个过程,是因为回归算法认为,特征矩阵和标签之前存在着某种联系。实际上,标签和特征是可以相互转换的,比如说,在一个“用地区,环境,附近学校数量”预测“房价”的问题中,我们既可以用“地区”,“环境”,“附近学校数量”的数据来预测“房价”,也可以反过来,用“环境”,“附近学校数量”和“房价”来预测“地区”。而回归填补缺失值,正是利用了这种思想。

对于一个有n个特征的数据来说,其中特征T有缺失值,我们就把特征T当作标签,其他的n-1个特征和原本的标签组成新的特征矩阵。那对于T来说,它没有缺失的部分,就是我们的Y_train,这部分数据既有标签也有特征,而它缺失的部分,只有特征没有标签,就是我们需要预测的部分

特征T不缺失的值对应的其他n-1个特征 + 本来的标签:X_train(作为特征)

特征T不缺失的值:Y_train(作为标签)

特征T缺失的值对应的其他n-1个特征 + 本来的标签:X_test

特征T缺失的值:未知,我们需要预测的Y_test

这种做法,对于某一个特征大量缺失,其他特征却很完整的情况,非常适用。

那如果数据中除了特征T之外,其他特征也有缺失值怎么办?

答案是遍历所有的特征,从缺失最少的开始进行填补(因为填补缺失最少的特征所需要的准确信息最少)。

填补一个特征时,先将其他特征的缺失值用0代替每完成一次回归预测,就将预测值放到原本的特征矩阵中,再继续填补下一个特征。每一次填补完毕,有缺失值的特征会减少一个,所以每次循环后,需要用0来填补的特征就越来越少。当进行到最后一个特征时(这个特征应该是所有特征中缺失值最多的),已经没有任何的其他特征需要用0来进行填补了,而我们已经使用回归为其他特征填补了大量有效信息,可以用来填补缺失最多的特征。

遍历所有的特征后,数据就完整,不再有缺失值了。

"""

#回归填补缺失值的矩阵依然使用copy

X_missing_reg = X_missing.copy()

#找出数据集中缺失值从小到大排列的特征的顺序(本质是找索引),有了这些特征按缺失值从小到大排序的索引

sortindex = np.argsort(X_missing_reg.isnull().sum(axis=0)).values

#np.argsort会返回从小到大排序的顺序所对应的索引,np.sort排序结果没有索引

(#.values将结果取出

np.argsort(X_missing_reg.isnull().sum(axis=0)).values

-->array([ 6, 12,  8,  7,  9,  0,  2,  1,  5,  4,  3, 10, 11], dtype=int64)

for i in sortindex:

#构建我们的新特征矩阵和新标签df = X_missing_reg

df = X_missing_reg

fillc = df.iloc[:,i]

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)

#找出我们的训练集和测试集(特征T为有缺失需要填补的特征)

Ytrain = fillc[fillc.notnull()]  #特征T不缺失的值,非空的值

Ytest = fillc[fillc.isnull()]    #特征T缺失的值:未知,我们需要预测

Xtrain = df_0[Ytrain.index,:]    #特征T不缺失的值对应的其他n-1个特征 + 本来的标签

Xtest = df_0[Ytest.index,:]      #特征T缺失的值对应的其他n-1个特征 + 本来的标签

#用随机森林回归来填补缺失值

rfc = RandomForestRegressor(n_estimators=100)

rfc = rfc.fit(Xtrain, Ytrain)

Ypredict = rfc.predict(Xtest)

#将填补好的特征返回到我们的原始的特征矩阵中

X_missing_reg.loc[X_missing_reg.iloc[:,i].isnull(),i] = Ypredict

X_missing_reg.head()

-->nan都填补了

0

1

2

3

4

5

6

7

8

9

10

11

12

0

0.274788

18.000

6.2578

0.15

0.538000

6.74979

65.200

4.090000

1.00

296.00

17.627

386.7265

4.9800

1

0.027310

0.000

5.3561

0.00

0.469000

6.15543

78.900

4.967100

2.00

294.18

17.945

396.9000

9.1400

2

0.027290

13.455

7.0700

0.00

0.467599

7.18500

61.100

4.223786

2.00

242.00

18.166

386.0060

5.1293

3

0.091533

14.960

3.3511

0.00

0.458000

6.88065

45.800

4.874204

3.49

222.00

18.700

392.6204

5.8244

4

0.077672

0.000

2.1800

0.00

0.464278

7.14700

58.731

5.176125

3.53

234.81

18.700

392.9744

5.3300

【代码讲解】

#***********代码讲解*************

#构建我们的新特征矩阵(没有被选中去填充的特征+原始的标签)和新标签(被选中去填充的特征)

#不改变X_missing_reg,所以赋值给df,先在df上操作,避免X_missing_reg被填入0,最后再把结果放回原来的X_missing_reg

df = X_missing_reg

#作为新标签,以索引为6(即第七列nan最少)为例

fillc = df.iloc[:,6]

#新特征矩阵(没有被选中去填充的特征+原始的标签)(可以取索引!= 6 的列)

df = pd.concat([df.iloc[:,df.columns != 6],pd.DataFrame(y_full)],axis=1)

#df.iloc[:,df.colums ! = 6] 取所有的行,索引不为6的列

#y_full为原始的标签,把其变为DataFrame(506行,1列)

#通过pd.concat连接,(axis=1即把y_full作为新的一列,不然会加到下面)

#注意:只能执行一次,不然y_full会一直加进去

#在新特征矩阵中,对含有缺失值的列,进行0的填补,实例化

df_0 =SimpleImputer(missing_values=np.nan,strategy='constant',fill_value=0).fit_transform(df)

#找出我们的训练集和测试集

Ytrain = fillc[fillc.notnull()]  #Ytrain是被选中要填充的特征中(现在是作为标签),存在的那些值,非空值

#Ytest需要我们预测,所以全为nan的Ytest,我们需要的不是Ytest的值,需要的是Ytest的索引

Ytest = fillc[fillc.isnull()]  #Ytest是被选中要填充的特征中(现在使我们的标签),不存在的那些值,是空值

#在新特征矩阵上,被选出来的要填充的特征的非空值所对应的记录

Xtrain = df_0[Ytrain.index,:] #Xtrain是要填充的特征不缺失的值(非空值)对应的其他n-1个特征 + 本来的标签

#在新特征矩阵上,被选出来的要填充的那个特征的空值所对应的记录

Xtest = df_0[Ytest.index,:] #Xtest是要填充的特征缺失的值(空值)对应的其他n-1个特征 + 本来的标签'

#用随机森林回归来填补缺失值

rfc = RandomForestRegressor(n_estimators=100)  #实例化

rfc = rfc.fit(Xtrain, Ytrain)   #导入训练集进行训练

Ypredict = rfc.predict(Xtest) #用predict接口,将Xtest导入得到预测(回归)结果,就是用来填补缺失值的值

#将填补好的特征返回到我们的原始的特征矩阵中

X_missing_reg.loc[X_missing_reg.iloc[:,6].isnull(),6] = Ypredict

6.对填补好的数据进行建模

#6.对填补好的数据进行建模

#对所有数据进行建模,取得MSE结果

#分别为原本的数据集,用0填补的数据集,用均值填补的数据集,用随机森林回归填补缺失的数据集

X =[X_full,X_missing_mean,X_missing_0,X_missing_reg]

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)

#元组形式查看结果

[*zip(["X_full","X_missing_mean","X_missing_0","X_missing_reg"],mse)]

-->[('X_full', 21.62860460743544),

('X_missing_mean', 40.84405476955929),

('X_missing_0', 49.50657028893417),

('X_missing_reg', 20.567284947369437)]

#(mse均方误差越小越好,随机森林回归的效果甚至比原数据集更好)

7.用所得结果画出条形图

#7.用所得结果画出条形图

x_labels = ["X_full","X_missing_mean","X_missing_0","X_missing_reg"]

colors = ["r","g","b","orange"]

plt.figure(figsize=(12,6),dpi=80)  #画出画布

ax = plt.subplot(111)  #plt.subplot添加子图

for i in np.arange(len(mse)): #np.arange = range(len(mse)) 0-3

#横条形图,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_labels里面内容作y轴命名

ax.set_yticklabels(x_labels)

plt.show()

可见随机森林回归的效果最好

4 机器学习中调参的基本思想

大多数的机器学习相关的书都是遍历各种算法和案例,为大家讲解各种各样算法的原理和用途,但却对调参探究甚少。

这中间有许多原因,其一是因为,调参的方式总是根据数据的状况而定,所以没有办法一概而论;其二是因为,其实大家也都没有特别好的办法。

通过画学习曲线,或者网格搜索,我们能够探索到调参边缘(代价可能是训练一次模型要跑三天三夜),但是在现实中,高手调参恐怕还是多依赖于经验,而这些经验,来源于:1)非常正确的调参思路和方法,2)对模型评估指标的理解,3)对数据的感觉和经验,4)用洪荒之力去不断地尝试。

我们也许无法学到高手们多年累积的经验,但我们可以学习他们对模型评估指标的理解和调参的思路。

那我们首先来讲讲正确的调参思路。模型调参,第一步是要找准目标:我们要做什么?一般来说,这个目标是提升某个模型评估指标,比如对于随机森林来说,我们想要提升的是模型在未知数据上的准确率(由score或oob_score_来衡量)。找准了这个目标,我们就需要思考:模型在未知数据上的准确率受什么因素影响?在机器学习中,我们用来衡量模型在未知数据上的准确率的指标,叫做泛化误差(Genelization error)

·泛化误差

当模型在未知数据(测试集或者袋外数据)上表现糟糕时,我们说模型的泛化程度不够,泛化误差大,模型的效果不好。泛化误差受到模型的结构(复杂度)影响。看下面这张图,它准确地描绘了泛化误差与模型复杂度的关系,当模型太复杂训练数据学习太具体,模型就会过拟合,泛化能力就不够,所以泛化误差大当模型太简单,模型就会欠拟合,拟合能力就不够,所以误差也会大。只有当模型的复杂度刚刚好的才能够达到泛化误差最小的目标。

那模型的复杂度与我们的参数有什么关系呢?对树模型来说,树越茂盛,深度越深,枝叶越多,模型就越复杂。所以树模型是天生位于图的右上角的模型,随机森林是以树模型为基础,所以随机森林也是天生复杂度高的模型。随机森林的参数,都是向着一个目标去:减少模型的复杂度,把模型往图像的左边移动,防止过拟合。当然了,调参没有绝对,也有天生处于图像左边的随机森林,所以调参之前,我们要先判断,模型现在究竟处于图像的哪一边

泛化误差的背后其实是“偏差-方差困境”,原理十分复杂,无论你翻开哪一本书,你都会看见长篇的数学论证和每个 字都能看懂但是连在一起就看不懂的文字解释。在下一节偏差vs方差中,我用最简单易懂的语言为大家解释了泛化 误差背后的原理,大家选读。那我们只需要记住这四点:

1.模型太复杂或者太简单,都会让泛化误差高,我们追求的是位于中间的平衡点

2.模型太复杂就会过拟合,模型太简单就会欠拟合

3.对树模型和树的集成模型来说,树的深度越深,枝叶越多,模型越复杂

4.树模型树的集成模型的目标,都是减少模型复杂度,把模型往图像的左边移动

那具体每个参数,都如何影响我们的复杂度和模型呢?我们一直以来调参,都是在学习曲线上轮流找最优值,盼望能够将准确率修正到一个比较高的水平。然而我们现在了解了随机森林的调参方向:降低复杂度,我们就可以将那些对复杂度影响巨大的参数挑选出来,研究他们的单调性,然后专注调整那些能最大限度让复杂度降低的参数。对于那些不单调的参数,或者反而会让复杂度升高的参数,我们就视情况使用,大多时候甚至可以退避。基于经验我对各个参数对模型的影响程度做了一个排序。在我们调参的时候,大家可以参考这个顺序。

(树模型天生过拟合,一般使用)

参数

对模型在未知数据上的评估性能的影响

影响程度

n_estimators

提升至平稳,n_estimators,不影响单个模型的复杂度

4

max_depth

有增有减,默认最大深度,即最高复杂度,向复杂度降低的方向调参

max_depth,模型更简单,且向图像的左边移动

3

min_samples

_leaf

有增有减,默认最小限制1,即最高复杂度,向复杂度降低的方向调参

min_samples_leaf,模型更简单,且向图像的左边移动

2

min_samples

_split

有增有减,默认最小限制2,即最高复杂度,向复杂度降低的方向调参

min_samples_split,模型更简单,且向图像的左边移动

2

max_features

有增有减,默认auto,是特征总数的开平方,位于中间复杂度,既可以向复杂度升高的方向,也可以向复杂度降低的方向调参max_features下降, 模 型 更 简 单 , 图 像 左 移max_features上升,模型更复杂,图像右移

max_features的,既能够让型更也能够让型更复杂所以在调整这个参数时候需要虑我们调参方向

1

criterion

有增有减,一般使用gini

看具体情况

有了以上的知识储备,我们现在也能够通过参数的变化来了解,模型什么时候到达了极限,当复杂度已经不能再降  低的时候,我们就不必再调整了,因为调整大型数据的参数是一件非常费时费力的事。除了学习曲线网格搜索,  我们现在有了基于对模型和正确的调参思路的“推测”能力,这能够让我们的调参能力更上一层楼。

5 实例:随机森林在乳腺癌数据上的调参

在这节课中,我们了解了随机森林,并且学习了机器学习中调参的基本思想,了解了方差和偏差如何受到随机森林的参数们的影响。这一节,我们就来使用我们刚才学的,基于方差和偏差的调参方法,在乳腺癌数据上进行一次随机森林的调参。乳腺癌数据是sklearn自带的分类数据之一。

案例中,往往使用真实数据,为什么我们要使用sklearn自带的数据呢?因为真实数据在随机森林下的调参过程,往往非常缓慢。真实数据量大,维度高,在使用随机森林之前需要一系列的处理,因此不太适合用来做直播中的案例演示。在本章,我为大家准备了kaggle上下载的辨别手写数字的数据,有4W多条记录700多个左右的特征,随机森林在这个辨别手写数字的数据上有非常好的表现,其调参案例也是非常经典,但是由于数据的维度太高,太过复杂,运行一次完整的网格搜索需要四五个小时,因此不太可能拿来给大家进行演示。我们上周的案例中用的泰坦尼克号数据,用来调参的话也是需要很长时间,因此我才选择sklearn当中自带的,结构相对清晰简单的数据来为大家做这个案例。大家感兴趣的话,可以直接到kaggle上进行下载,数据集名称是Digit RecognizerDigit Recognizer | Kaggle)。

那我们接下来,就用乳腺癌数据,来看看我们的调参代码。

1.导入需要的库

#导入库

from sklearn.datasets import load_breast_cancer   #从datasets中导入乳腺癌数据

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()

data

data.data.shape  #569个样本,30个特征,数据少容易过拟合

-->(569, 30)

data.target  #二分类0和1

#可以看到,乳腺癌数据集有569条记录,30个特征,单看维度虽然不算太高,但是样本量非常少。过拟合的情况可能存在。

3.进行一次简单的建模,看看模型本身在数据集上的效果

#进行一次简单的建模,看看模型本身在数据集上的效果

#实例化

rfc = RandomForestClassifier(n_estimators=100,random_state=90)

#交叉验证参数(实例化模型,分训练集测试集前的完整的特征矩阵,特征标签,交叉验证次数,(回归时还有scoring选择以什么回归))

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的变化如何引起模型整体准确率的变化

"""

#####【TIME WARNING: 30 seconds】#####

scorel = []

for i in range(0,200,10):

rfc = RandomForestClassifier(n_estimators=i+1, #i+1因为n_estimators不能为0

n_jobs=-1,

random_state=90)

score = cross_val_score(rfc,data.data,data.target,cv=10).mean()

scorel.append(score)

#返回score取最大值时的索引,索引*10+1则为最高的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中的索引

0.9631265664160402 71  即n_estimators为71时最高 

5.在确定好的范围内,进一步细化学习曲线

#在确定好的范围内,进一步细化学习曲线

scorel = []

for i in range(65,75):

rfc = RandomForestClassifier(n_estimators=i, #i+1因为n_estimators不能为0

n_jobs=-1,

random_state=90)

score = cross_val_score(rfc,data.data,data.target,cv=10).mean()

scorel.append(score)

#返回score取最大值时的索引,*是序列解包,*range(0,6) 等效于0 1 2 3 4 5

print(max(scorel),([*range(65,75)][scorel.index(max(scorel))]))

plt.figure(figsize=[20,5])

plt.plot(range(65,75),scorel)

plt.show()

0.9666353383458647 73

调整n_estimators的效果显著,模型的准确率立刻上升了0.003。接下来就进入网格搜索,我们将使用网格搜索参数一个个进行调整。为什么我们不同时调整多个参数呢?原因有两个:1同时调整多个参数会运行非常缓慢,在课堂上我们没有这么多的时间。2同时调整多个参数,会让我们无法理解参数的组合是怎么得来的,所以即便网格搜索调出来的结果不好,我们也不知道从哪里去改。在这里,为了使用复杂度-泛化误差方法(方差-偏差方     法),我们对参数进行一个个地调整。

6.为网格搜索做准备,书写网格搜索的参数

"""

有一些参数是没有参照的,很难说清一个范围,这种情况下我们使用学习曲线

看趋势从曲线跑出的结果中选取一个更小的区间再跑曲线。(参数范围说不清,通过学习曲线缩小区间)

#n_estimators  树最大数量

param_grid = {'n_estimators':np.arange(0, 200, 10)}

#max_depth

param_grid = {'max_depth':np.arange(1, 20, 1)}

#max_leaf_nodes

param_grid = {'max_leaf_nodes':np.arange(25,50,1)}

对于大型数据集,可以尝试从1000来构建,先输入1000,每100个叶子一个区间,再逐渐缩小范围

有一些参数是可以找到一个范围,或者说我们知道他们的取值和随着他们的取值,模型的整体准确率会如何变化

这样的参数我们就可以直接跑网格搜索(能通过参数取值知道准确率如何变化用网格搜索)

#criterion

param_grid = {'criterion':['gini', 'entropy']}

#min_samples_split  节点向下分枝所需的最小样本数

param_grid = {'min_samples_split':np.arange(2, 2+20, 1)}

#min_samples_leaf   分枝的叶子节点处所需的最小样本数

param_grid = {'min_samples_leaf':np.arange(1, 1+10, 1)}

#max_features  最大特征数

param_grid = {'max_features':np.arange(5,30,1)}

"""

7.开始按照参数对模型整体准确率的影响程度进行调参,首先调整max_depth

#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

)

#网格搜索GridSearchCV(实例化的模型,搜索的参数,交叉验证的次数)

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,是让模型变得简单,把模型向左推,而模型整体的准确率下降了,即整体的泛化误差上升了,这说明模型现在位于图像左边,即泛化误差最低点的左边(偏差为主导的一边)。通常来说,随机森林应该在泛化误差最低点的右边,树模型应该倾向过拟合,而不是拟合不足。这和数据集本身有关,但也有可能是我们调整的n_estimators对于数据集来说太大因此将模型拉到泛化误差最低点去了。然而,既然我们追求最低泛化误差,那我们就保留这个n_estimators,除非有其他的因素,可以帮助我们达到更高的准确率。

当模型位于图像左边时,我们需要的是增加模型复杂度(增加方差,减少偏差)的选项,因此max_depth应该尽量min_samples_leafmin_samples_split都应该尽量小。这几乎是在说明,除了max_features,我们没有任何参数可以调整了,因为max_depthmin_samples_leafmin_samples_split剪枝参数,是减小复杂度的参数。在这里,我们可以预言,我们已经非常接近模型的上限,模型很可能没有办法再进步了。

那我们这就来调整一下max_features,看看模型如何变化。

8.调整max_features

#调整max_features

#特征从5-30

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_

-->9666353383458647

可以看见,网格搜索返回了min_samples_leaf的最小值,并且模型整体的准确率还降低了,这max_depth的情况一致,参数把模型向左推,但是模型的泛化误差上升了。在这种情况下,我们显然是不要把这个参数设置起来的,就让它默认就好了。

10.不懈努力,继续尝试min_samples_split

# 调 整 min_samples_split

param_grid={'min_samples_split':np.arange(2, 2+20, 1)}

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_split':2}

#返回调整好的最佳参数对应的准确率

GS.best_score_

-->0.9666353383458647

min_samples_leaf一样的结果,返回最小值并且模型整体的准确率降低了。

11.最后尝试一下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

仍然为默认的“gini”

12.调整完毕,总结出模型的最佳参数

#最佳参数就是n_estimators,我们在实验中取得最佳的为73

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%,这对于调参来说其实是一个非常巨大的进步。不过,泰坦尼克号数据的运行缓慢,大家量力量时间而行,可以试试看用复杂度-泛化误差方法(方差-偏 差方法)来解读一下这个调参结果和过程。

6 附录

6.1 Bagging vs Boosting

装袋法 Bagging

提升法 Boosting

评估器

相互独立,同时运行

相互关联,按顺序依次构建,后建的模型会在先建模型预测失败的样本上有更多的权重

抽样数集

有放回抽样

有放回抽样,但会确认数据的权重,每次抽样都会给容易预测失败的样本更多的权重

决定集成的结果

平均或少数服从多数原则

加权平均,在训练集上表现更好的模型会有更大的权重

目标

降低方差,提高模型整体的稳定性

降低偏差,提高模型整体的精确度

单个评估器存在过拟合问题的时候

能够一定程度上解决过拟合问题

可能会加剧过拟合问题

单个评估器的效力比较弱的时候

不是非常有帮助

很可能会提升模型表现

代表算法

随机森林

梯度提升树,Adaboost

6.2 RFC的参数列表

6.3RFC的属性列表

6.4 RFC的接口列表

机器学习 Sklearn Day2相关推荐

  1. Py之scikit-learn:机器学习Sklearn库的简介、安装、使用方法(ML算法如何选择)、代码实现之详细攻略

    Py之scikit-learn:机器学习Sklearn库的简介.安装.使用方法.代码实现之详细攻略 目录 scikit-learn的简介 scikit-learn的安装 scikit-learn的使用 ...

  2. svd降维 python案例_菜菜的机器学习sklearn实战-----sklearn中的降维算法PCA和SVD

    菜菜的机器学习sklearn实战-----sklearn中的降维算法PCA和SVD 概述 从什么叫维度说开来 简单讲,shape中返回了几个数字就是几维. 一张表最多就是一维 当一个数组中存在2张3行 ...

  3. [机器学习-Sklearn]函数sklearn.feature_extraction.DictVectorizer理解与总结

    机器学习-Sklearn之DictVectorizer函数学习 函数介绍 例子1 例子2 - 文件中读数据 函数介绍 sklearn.featture_extraction.DictVectorize ...

  4. python机器学习-sklearn挖掘乳腺癌细胞(五)

    python机器学习-sklearn挖掘乳腺癌细胞( 博主亲自录制) 网易云观看地址 https://study.163.com/course/introduction.htm?courseId=10 ...

  5. 《菜菜的机器学习sklearn课堂》学习笔记 + 课件

    <菜菜的机器学习sklearn课堂> sklearn入门与决策树 随机森林应用泛化误差调参实例 数据预处理和特征工程 降维算法PCA和SVD 逻辑回归 聚类算法Kmeans SVM支持向量 ...

  6. 《菜菜的机器学习sklearn课堂》聚类算法Kmeans

    聚类算法 聚类算法 无监督学习与聚类算法 sklearn中的聚类算法 KMeans KMeans是如何工作的 簇内误差平方和的定义和解惑 sklearn.cluster.KMeans 重要参数 n_c ...

  7. 《菜菜的机器学习sklearn课堂》逻辑回归

    逻辑回归 概述:名为"回归"的分类器 为什么需要逻辑回归? sklearn中的逻辑回归 linear_model.LogisticRegression 二元逻辑回归的损失函数 损失 ...

  8. 《菜菜的机器学习sklearn课堂》sklearn入门与决策树

    决策树 sklearn简介 决策树 - 概述 决策树是如何工作的 sklearn中的决策树 分类树 DecisionTreeClassifier 重要参数 criterion 建立一颗树(只有一个参数 ...

  9. 机器学习Sklearn学习总结

    Sklearn学习资料推荐: sklearn中文文档 机器学习入门必看:使用 scikit-learn 构建模型的通用模板 机器学习神器Scikit-Learn保姆教程! python机器学习_FF_ ...

最新文章

  1. 【POI】对于POI无法处理超大xls等文件,官方解决方法【已解决】【多线程提升速率待定】...
  2. 局域网网速带宽测试软件,网管的经验 教你如何测试局域网的网速
  3. Content management workbench DMWB
  4. php7 不是有效的32位,Win7系统安装软件提示“不是有效的win32应用程序”怎么办?...
  5. 【Error】IDEA报错:org.jetbrains.jps.builders.java.dependencyView.TypeRepr$PrimitiveType cannot be cast t
  6. js基本包装类型和引用类型
  7. Python列表元素的循环删除问题
  8. vue项目封装腾讯TcPlayer播放器
  9. android刷机教程 华为,华为的安卓手机该怎么刷机
  10. 青岛大学的计算机专业考研分数线,青岛大学考研分数线
  11. 数据结构与算法之二叉树
  12. 2022阿里云国际注册教程-不用绑定paypal注册-Unirech阿里云代充
  13. delphi fastreport4.5 的使用
  14. 超级删除PowerTools
  15. Latex实例[2]:跨页表格制作方法, longtable和 supertabular
  16. 二维vector的创建
  17. android进入wifi权限,判断android设备wifi连接状态,判断android设备wifi,添加访问权限(Andro...
  18. 惠普gk100好不好_惠普GK100如何,惠普GK100评价看这里
  19. 今日睡眠质量记录70
  20. matlab系统辨识尝试之详细过程1,Matlab系统辨识尝试之详细过程1

热门文章

  1. MySQL的核心之InnoDB中的花式索引
  2. 带权重的多点滑动平均
  3. 老鼠和奶酪【力扣2611】
  4. 华为USG系列防火墙配置-网页端登录管理
  5. 华为鸿蒙微内核优势,刚刚,华为鸿蒙正式发布!四大优势全面领先安卓,领先苹果率先推出?基于微内核的全场景分布式OS...
  6. 转——我们正处在区块链行业历史的前三分钟,这个时代意味着什么
  7. 2022年危险化学品生产单位安全生产管理人员考试及危险化学品生产单位安全生产管理人员考试技巧
  8. 逆水寒7.25服务器维护,玩法改动_逆水寒7月25日更新_7月25日改动_3DM网游
  9. CNN 模型压缩与加速算法总结
  10. 台式机更换固态硬盘(二)