机器学习笔记 - sklearn随机森林(集成学习)(kaggle 实战 Space Titanic 入门)

随机森林

简介

随机森林是通过集成学习的思想将多棵决策树集成起来的一种算法,以期获得更准确和更稳定的预测能力。随机森林的基本单元是决策树。随机森林属于机器学习的一个分支--集成学习方法,或者说随机森林就是集成学习思想的产物。

集成学习是一种把多个模型整合起来,互相取长补短,避免局限性,发挥长处,实现更好的分类和回归预测的机器学习方法。它生成多个分类器,各自独立地学习和做出预测,集成学习把这些分类器整合起来。

随机森林将许多棵决策树整合成森林,是一种利用多棵树对样本进行训练并预测的分类器。对于每棵树,它们使用的训练集是从总的训练集中随机有放回地采样出来的。也就是说,总的训练集中的一些样本可能多次出现在一棵树的训练集中,也可能从未出现在任意一棵树的训练集中。

一、集成学习

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

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

装袋法的核心思想是构建多个相互独立的评估器,然后对其预测进行平均或多数表决原则来决定集成评估器的结果。装袋法的代表模型就是随机森林。

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

二、随机森林的构造过程

随机森林由 n_estimators 棵树构成,它的构造过程如下:

(1)用 Bootstrap 方法生成 n_estimators 个训练(子)集。对于每棵树,如果训练(子)集大小为 N,随机且有放回地从训练集中抽取 N 个训练样本(这种采样方式称为 Bootstrap Sample 方法)作为当前决策树的训练(子)集。可以看出,每棵树的训练集将是不同的,而且里面可能包含重复的样本。

如果不进行随机抽样,每棵树的训练集都一样,最终训练出的决策树的分类结果也是完全一样的,这样的话就没有集成的必要了。如果不是有放回的抽样,那么每棵树的训练样本都是不同的,是没有交集的,于是每棵树都是“有偏的”或者“片面的”,这样也不好,也是我们所不期望的。也就是说,训练出来的一系列决策树差异太大。

从某种意义上讲,随机森林的最后分类结果,取决于多棵决策树(弱分类器)的表决结果,存在“求同”的要求。因此使用完全不同的训练集来训练每棵树,对最终分类结果将造成很大的“争执”。

(2)对于每个训练(子)集构造一颗决策树。如果每个样本的特征维度为 M,也就是有 M 个特征,指定一个常数 m << M,随机地从 M 个特征中选取 m 个,构成特征子集。构造这棵树的时候,每次对决策树进行节点分裂时,从这 m 个特征中选择最优的特征进行分裂。也就是说,在寻找特征进行节点分裂的时候,并不是对所有特征找到能使得指标最大(如信息增益)的特征,而是在特征中先随机抽取一部分特征,在抽取到的特征中找到最优特征,应用于节点,进行分裂。

(3)每棵树都最大限度地生长,没有剪枝过程。

执行上述步骤 n_estimators 次,构造 n_estimators 棵树。进行预测的时候,样本馈入这 n_estimators 棵树,最后可以采用投票法,计算整个分类器的预测值(进行回归的时候,可以采用计算平均值的方式计算预测值)。

三、随机森林的参数

1. 控制基评估器的参数

参数含义
criterion不纯度的衡量指标,有基尼系数和信息熵两种选择
max_depth树的最大深度,超过最大深度的树枝都会被剪掉
min_samples_leaf一个节点在分枝后的每个子节点都必须包含至少min_samples_leaf个训练样本,否则分枝就不会发生
min_samples_split一个节点必须要包含至少min_samples_split个训练样本,这个节点才允许被分枝,否则分枝就不会发生
max_featuresmax_features限制分枝时考虑的特征个数,超过限制个数的特征都会被舍弃,默认值为总特征个数开平方取整
min_impurity_decrease限制信息增益的大小,信息增益小于设定数值的分枝不会发生

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

2. n_estimators

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

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

树模型的优点是简单易懂,可视化之后的树人人都能够看懂,可惜随机森林是无法被可视化的。

3. 重要属性和接口

随机森林中有三个非常重要的属性:.estimators_,.oob_score_以及.feature_importances_

.estimators_是用来查看随机森林中所有树的列表的。

oob_score_指的是袋外得分。随机森林为了确保林中的每棵树都不尽相同,所以采用了对训练集进行有放回抽样的方式来不断组成信的训练集,在这个过程中,会有一些数据从来没有被随机挑选到,他们就被叫做“袋外数据”。这些袋外数据,没有被模型用来进行训练,sklearn可以帮助我们用他们来测试模型,测试的结果就由这个属性oob_score_来导出,本质还是模型的精确度。

而.feature_importances_和决策树中的.feature_importances_用法和含义都一致,是返回特征的重要性。

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

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

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

泛化误差

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

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

泛化误差的背后其实是“偏差-方差困境”,原理十分复杂,我们只需要记住这四点:

1)模型太复杂或者太简单,都会让泛化误差高,我们追求的是位于中间的平衡点
2)模型太复杂就会过拟合,模型太简单就会欠拟合
3)对树模型和树的集成模型来说,树的深度越深,枝叶越多,模型越复杂
4)树模型和树的集成模型的目标,都是减少模型复杂度,把模型往图像的左边移动

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

参数对模型在未知数据上的评估性能的影响影响程度
n_estimators提升至平稳,n_estimators↑,不影响单个模型的复杂度⭐⭐⭐⭐
max_depth有增有减,默认最大深度,即最高复杂度,向复杂度降低的方向调参max_depth↓,模型更简单,且向图像的左边移动⭐⭐⭐
min_samples
_leaf
有增有减,默认最小限制1,即最高复杂度,向复杂度降低的方向调参min_samples_leaf↑,模型更简单,且向图像的左边移动⭐⭐
min_samples
_split
有增有减,默认最小限制2,即最高复杂度,向复杂度降低的方向调参min_samples_split↑,模型更简单,且向图像的左边移动⭐⭐
max_features有增有减,默认auto,是特征总数的开平方,位于中间复杂度,既可以向复杂度升高的方向,也可以向复杂度降低的方向调参
max_features↓,模型更简单,图像左移
max_features↑,模型更复杂,图像右移
max_features是唯一的,既能够让模型更简单,也能够让模型更复杂的参数,所以在调整这个参数的时候,需要考虑我们调参的方向
criterion有增有减,一般使用gini看具体情况

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

偏差与方差

观察下面的图像,每个点就是集成算法中的一个基评估器产生的预测值。红色虚线代表着这些预测值的均值,而蓝色的线代表着数据本来的面貌。


 

偏差:模型的预测值与真实值之间的差异,即每一个红点到蓝线的距离。在集成算法中,每个基评估器都会有自己的偏差,集成评估器的偏差是所有基评估器偏差的均值。模型越精确,偏差越低。

方差:反映的是模型每一次输出结果与模型预测值的平均水平之间的误差,即每一个红点到红色虚线的距离,衡量模型的稳定性。模型越稳定,方差越低。

其中偏差衡量模型是否预测得准确,偏差越小,模型越“准”;而方差衡量模型每次预测的结果是否接近,即是说方差越小,模型越“稳”;噪声是机器学习无法干涉的部分,为了让世界美好一点,我们就不去研究了。一个好的模型,要对大多数未知数据都预测得”准“又”稳“。即是说,当偏差和方差都很低的时候,模型的泛化误差就小,在未知数据上的准确率就高。

偏差大偏差小
方差大模型不适合这个数据
换模型
过拟合
模型很复杂
对某些数据集预测很准确
对某些数据集预测很糟糕
方差小欠拟合
模型相对简单
预测很稳定
但对所有的数据预测都不太准确
泛化误差小,我们的目标

通常来说,方差和偏差有一个很大,泛化误差都会很大。然而,方差和偏差是此消彼长的,不可能同时达到最小值。这个要怎么理解呢?来看看下面这张图:

 从图上可以看出,模型复杂度大的时候,方差高,偏差低。偏差低,就是要求模型要预测得“准”。模型就会更努力去学习更多信息,会具体于训练数据,这会导致,模型在一部分数据上表现很好,在另一部分数据上表现却很糟糕。模型泛化性差,在不同数据上表现不稳定,所以方差就大。而要尽量学习训练集,模型的建立必然更多细节,复杂程度必然上升。所以,复杂度高,方差高,总泛化误差高。

相对的,复杂度低的时候,方差低,偏差高。方差低,要求模型预测得“稳”,泛化性更强,那对于模型来说,它就不需要对数据进行一个太深的学习,只需要建立一个比较简单,判定比较宽泛的模型就可以了。结果就是,模型无法在某一类或者某一组数据上达成很高的准确度,所以偏差就会大。所以,复杂度低,偏差高,总泛化误差高。

我们调参的目标是,达到方差和偏差的完美平衡!虽然方差和偏差不能同时达到最小值,但他们组成的泛化误差却可以有一个最低点,而我们就是要寻找这个最低点。对复杂度大的模型,要降低方差,对相对简单的模型,要降低偏差。随机森林的基评估器都拥有较低的偏差和较高的方差,因为决策树本身是预测比较”准“,比较容易过拟合的模型,装袋法本身也要求基分类器的准确率必须要有50%以上。所以以随机森林为代表的装袋法的训练过程旨在降低方差,即降低模型复杂度,所以随机森林参数的默认设定都是假设模型本身在泛化误差最低点的右边

所以,我们在降低复杂度的时候,本质其实是在降低随机森林的方差,随机森林所有的参数,也都是朝着降低方差的目标去。有了这一层理解,我们对复杂度和泛化误差的理解就更上一层楼了,对于我们调参,也有了更大的帮助。

Kaggle Space Titanic

类似 Titanic

也是要我们预测船员的生死(传送是否出问题)

代码示例和解释

train = pd.read_csv("../input/spaceship-titanic/train.csv")
test = pd.read_csv("../input/spaceship-titanic/test.csv")
train.head()
#读入数据
train.drop(["Cabin", "Name"], inplace = True, axis = 1)
train.head()
#发现 Cabin 和 Name 没用可以直接删去
train["Age"] = train["Age"].fillna(train["Age"].mean())
train["CryoSleep"] = train["CryoSleep"].fillna(False)
train["VIP"] = train["VIP"].fillna(False)
train["HomePlanet"] = train["HomePlanet"].fillna("Earth")
train["RoomService"] = train["RoomService"].fillna(train["RoomService"].mean())
train["FoodCourt"] = train["FoodCourt"].fillna(train["FoodCourt"].mean())
train["Spa"] = train["Spa"].fillna(train["Spa"].mean())
train["VRDeck"] = train["VRDeck"].fillna(train["VRDeck"].mean())
train["ShoppingMall"] = train["ShoppingMall"].fillna(train["ShoppingMall"].mean())

Destination = pd.DataFrame
Destination = pd.get_dummies(train['Destination'],prefix = 'Destination')
train = pd.concat([train,Destination],axis = 1)
train.drop('Destination',axis = 1,inplace = True)

HomePlanet = pd.DataFrame
HomePlanet = pd.get_dummies(train['HomePlanet'],prefix = 'HomePlanet')
train = pd.concat([train,HomePlanet],axis = 1)
train.drop('HomePlanet',axis = 1,inplace = True)

train.head()

#处理数据,填补缺失值,转换数据类型
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
features = ["PassengerId", "HomePlanet_Earth", "HomePlanet_Mars", "HomePlanet_Europa", "CryoSleep", "Destination_55 Cancri e", "Age", "VIP","Destination_PSO J318.5-22","Destination_TRAPPIST-1e", "RoomService", "FoodCourt", "ShoppingMall", "Spa", "VRDeck"]
Xtrain, Xtest, Ytrain, Ytest = train_test_split(train[features], train["Transported"], test_size = 0.3)

clf = RandomForestClassifier(n_estimators = 25
                             ,random_state = 25
                             ,max_depth = 7
)
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest)
score

#选取测试和训练集,并拟合随机森林,得到得分
#随机森林里的参数是我提前试出来的
test.drop(["Cabin", "Name"], inplace = True, axis = 1)
test["Age"] = test["Age"].fillna(test["Age"].mean())
test["CryoSleep"] = test["CryoSleep"].fillna(False)
test["VIP"] = test["VIP"].fillna(False)
test["HomePlanet"] = test["HomePlanet"].fillna("Earth")
test["RoomService"] = test["RoomService"].fillna(test["RoomService"].mean())
test["FoodCourt"] = test["FoodCourt"].fillna(test["FoodCourt"].mean())
test["Spa"] = test["Spa"].fillna(test["Spa"].mean())
test["VRDeck"] = test["VRDeck"].fillna(test["VRDeck"].mean())
test["ShoppingMall"] = test["ShoppingMall"].fillna(test["ShoppingMall"].mean())

Destination = pd.DataFrame
Destination = pd.get_dummies(test['Destination'],prefix = 'Destination')
test = pd.concat([test,Destination],axis = 1)
test.drop('Destination',axis = 1,inplace = True)

HomePlanet = pd.DataFrame
HomePlanet = pd.get_dummies(test['HomePlanet'],prefix = 'HomePlanet')
test = pd.concat([test,HomePlanet],axis = 1)
test.drop('HomePlanet',axis = 1,inplace = True)

test.head()

# test 进行同样的操作,方便之后的操作
predict = clf.predict(test[features])
output = pd.DataFrame({"PassengerId": test.PassengerId, "Transported": predict})
output.to_csv("my_submission.csv", index = False)
#得到结果,并导入 "my_submission.csv" 中

这样可以得到 0.78933

参考资料

1. 菜菜的Sklearn

2.《数据科学导论》- 人民大学出版社

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值