随机森林(本质是一种集成算法)
1集成算法概述
集成学习(ensemble learning)是时下非常流行的机器学习算法,它本身不是一个单独的机器学习算法,而是通过在数据上构建多个模型,集成算法会考虑多个评估器的建模结果,汇总之后得到一个综合的结果,**以此来获取比单个模型更好的回归或分类表现**。多个模型集成成为的模型叫做集成评估器(ensemble estimator),组成集成评估器的每个模型都叫做基评估器(base estimator)。通常来说,有三类集成算法:装袋法(Bagging),提升法(Boosting)和stacking
2随机森林
随机森林是非常具有代表性的Bagging集成算法,它的所有基评估器都是决策树,将多个决策树结合在一起,每次数据集是随机有放回的选出,同时随机选出部分特征作为输入,所以该算法被称为随机森林算法。可以看到随机森林算法是以决策树为估计器的Bagging算法。
上图展示了随机森林算法的具体流程,随机森林计算过程类似于创建多个结果,从中取出平均值,是的预测结果最接近测试结果。使用Bagging算法能降低过拟合的情况,从而带来了更好的性能。单个决策树对训练集的噪声非常敏感,但通过Bagging算法降低了训练出的多颗决策树之间关联性,有效缓解了上述问题。
3重要参数
criterion 不纯度的衡量指标,有基尼系数和信息熵两种选择
max_depth 树的最大深度,超过最大深度的树枝都会被剪掉
min_samples_leaf 一个节点在分枝后的每个子节点都必须包含至少min_samples_leaf个训练样
本,否则分枝就不会发生
min_samples_split 一个节点必须要包含至少min_samples_split个训练样本,这个节点才允许被分枝,否则分枝就不会发生
max_features 限制分枝时考虑的特征个数,超过限制个数的特征都会被舍弃,
默认值为总特征个数开平方取整
min_impurity_decrease 限制信息增益的大小,信息增益小于设定数值的分枝不会发生
2.n_estimators
这是森林中树木的数量,即基评估器的数量。这个参数对随机森林模型的精确性影响是单调的,n_estimators越大,模型的效果往往越好。但是相应的,任何模型都有决策边界,n_estimators达到一定的程度之后,随机森林的精确性往往不在上升或开始波动,并且,n_estimators越大,需要的计算量和内存也越大,训练的时间也会越来越长。
3.使用红酒数据集建立随机森林并画出随机森林和决策树在一组交叉验证下的效果对比
```python
import numpy as np
from sklearn.tree import DecisionTreeClassifier
class rfc:
"""
随机森林分类器
"""
def __init__(self, n_estimators = 100, random_state = 0):
# 随机森林的大小
self.n_estimators = n_estimators
# 随机森林的随机种子
self.random_state = random_state
def fit(self, X, y):
"""
随机森林分类器拟合
"""
self.y_classes = np.unique(y)
# 决策树数组
dts = []
n = X.shape[0]
rs = np.random.RandomState(self.random_state)
for i in range(self.n_estimators):
# 创建决策树分类器
dt = DecisionTreeClassifier(random_state=rs.randint(np.iinfo(np.int32).max), max_features = "auto")
# 根据随机生成的权重,拟合数据集
dt.fit(X, y, sample_weight=np.bincount(rs.randint(0, n, n), minlength = n))
dts.append(dt)
self.trees = dts
def predict(self, X):
"""
随机森林分类器预测
"""
# 预测结果数组
probas = np.zeros((X.shape[0], len(self.y_classes)))
for i in range(self.n_estimators):
# 决策树分类器
dt = self.trees[i]
# 依次预测结果可能性
probas += dt.predict_proba(X)
# 预测结果可能性取平均
probas /= self.n_estimators
# 返回预测结果
return self.y_classes.take(np.argmax(probas, axis = 1), axis = 0)
```
**随机森林和决策树在十组交叉验证下的对比**
```python
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_wine
wine = load_wine()
wine.data
wine.target
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))
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()
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()
```
**bootstrap & oob_score**
1.什么是oob(袋外样本Out of bag):在随机森林中,m个训练样本会通过bootstrap (有放回的随机抽样) 的抽样方式进行T次抽样每次抽样产生样本数为m的采样集,进入到并行的T个决策树中。这样有放回的抽样方式会导致有部分训练集中的样本(约36.8%)未进入决策树的采样集中,而这部分未被采集的的样本就是袋外数据oob。
2.oob_score
对于单棵用采样集训练完成的决策树Ti,用袋外数据运行后会产生一个oob_score (返回的是R square来判断),对每一棵决策树都重复上述操作,最终会得到T个oob_score,把这T和oob_score平均,最终得到的就是整个随机森林的oob_score
**3.bootstrap**:一种前端框架
4.要让基分类器尽量都不一样,一种很容易理解的方法是使用不同的训练集来进行训练,而袋装法正是通过有放回的随机抽样技术来形成不同的训练数据,bootstrap就是用来控制抽样技术的参数。在一个含有n个样本的原始训练集中,我们进行随机采样,每次采样一个样本,并在抽取下一个样本之前将该样本放回原始训练集,也就是说下次采样时这个样本依然可能被采集到,这样采集n次,最终得到一个和原始训练集一样大的,n个样本组成的自助集。由于是**随机采样**,这样每次的自助集和原始数据集不同,和其他的采样集也是不同的。这样我们就可以自由创造取之不尽用之不竭,并且互不相同的自助集,用这些自助集来训练我们的基分类器,我们的基分类器自然也就各不相同了。
**bootstrap**参数默认True,代表采用这种有放回的随机抽样技术。通常,这个参数不会被我们设置为False。然而有放回抽样也会有自己的问题。由于是有放回,一些样本可能在同一个自助集中出现多次,而其他一些却可能被忽略,一般来说,自助集大约平均会包含63%的原始数据。因为每一个样本被抽到某个自助集中的概率为:当n足够大时,这个概率收敛于1-(1/e),约等于0.632。因此,会有约37%的训练数据被浪费掉,没有参与建模,这些数据被称为袋外数据(out of bag data,简写为oob)。除了我们最开始就划分好的测试集之外,这些数据也可以被用来作为集成算法的测试集。**也就是说,在使用随机森林时,我们可以不划分测试集和训练集,只需要用袋外****数据来测试我们的模型即可。**当然,这也不是绝对的,当n和n_estimators都不够大的时候,很可能就没有数据掉落在袋外,自然也就无法使用oob数据来测试模型了。如果希望用袋外数据来测试,则需要在实例化时就将oob_score这个参数调整为True。
训练完毕之后,我们可以用随机森林的另一个重要属性:oob_score_来查看我们的在袋外数据上测试的结果:
```python
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_wine
rfc = RandomForestClassifier(n_estimators=25,oob_score=True)
rfc = rfc.fit(wine.data,wine.target)
#重要属性oob_score_
rfc.oob_score_
```
Out[]: 0.9719101123595506
**RandomForestRegressor**
所有的参数,属性与接口,全部和随机森林分类器一致。仅有的不同就是回归树与分类树的不同,不纯度的指标,参数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不只是我们的分枝质量衡量指标,也是我们最常用的衡量回归树回归质量的指标,当我们在使用交叉验证,或者其他方式获取回归树的结果时,我们往往选择均方误差作为我们的评估(在分类树中这个指标是score代表的预测准确率)。在回归中,我们追求的是,MSE越小越好。然而,回归树的接口score返回的是R平方,并不是MSE。R平方被定义如下:
其中u是残差平方和(MSE * N),v是总平方和,N是样本数量,i是每一个数据样本,fi是模型回归出的数值,yi是样本点i实际的数值标签。y帽是真实数值标签的平均数。R平方可以为正为负(如果模型的残差平方和远远大于模型的总平方和,模型非常糟糕,R平方就会为负),而均方误差永远为正。
值得一提的是,**虽然均方误差永远为正,但是sklearn当中使用均方误差作为评判标准时,却是计算”负均方误差(neg_mean_squared_error)**。这是因为sklearn在计算模型评估指标的时候,会考虑指标本身的性质,均方误差本身是一种误差,所以被sklearn划分为模型的一种损失(loss),因此在sklearn当中,都以负数表示。真正的均方误差MSE的数值,其实就是neg_mean_squared_error去掉负号的数字。
**实例:用随机森林回归填补缺失值**
import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import load_boston from sklearn.impute import SimpleImputer # 轻松填补缺失数据的类 from sklearn.ensemble import RandomForestRegressor # 随机森林回归器 from sklearn.model_selection import cross_val_score
dataset = load_boston() # dataset print(dataset.data.shape,dataset.target.shape) # 506个样本,13个特征 506*13个数据 print(dataset.data[:3],'\n','\n',dataset.target[:3]) # 标签为连续数据 做回归
# 完整数据集 506*13 = 6578 X_full,Y_full = dataset.data,dataset.target n_samples = X_full.shape[0] n_features = X_full.shape[1] # 构造缺失的数据集 rng = np.random.RandomState(0) missing_rate = 0.5 n_missing_samples = int(np.floor(n_samples * n_features * missing_rate)) # np.floor 像下取整,乘出来的数有可能是个小数,返回.0格式的浮点数 n_missing_samples # 3289 # 缺失的3289个数据的行索引在0-506,列索引在0-13 # 利用索引为数据中任意3289个数据赋空值 # 然后用0、均值、随机森林来填补这些缺失值 missing_features = rng.randint(0,n_features,n_missing_samples) # array([12, 7, 8, ..., 4, 11, 10]) # randint 在指定的上限、下限之间取出指定个整数 missing_samples = rng.randint(0,n_samples,n_missing_samples) # array([406, 114, 358, ..., 177, 317, 449]) # missing_samples = rng.choice(n_samples,n_missing_samples,replace=False) # 需要的缺失值数量3289>>样本数506 这时用randint # 需要的缺失值数量<<样本数 这时用np.random.choice choice会随机抽取不重复的随机数 # 不在原数据上操作 X_missing = X_full.copy() Y_missing = Y_full.copy() print(X_missing) X_missing[missing_samples,missing_features] = np.nan X_missing
X_missing = pd.DataFrame(X_missing) X_missing
用随机森林补充缺失值
X_missing_reg = X_missing.copy()
# np.sort 返回从小到大排列后数组
# np.argsort 返回从小到大排序后的原数组对应位置的索引 values 取出值
sortindex = np.argsort(X_missing_reg.isnull().sum(axis=0)).values
sortindex
for i in sortindex:
df = X_missing_reg # 不在原数据集上做操作
fillc = df.iloc[:, i] # new class
df = pd.concat([df[df != fillc], pd.DataFrame(Y_full)], axis=1) # new feature
df_0 = SimpleImputer(missing_values=np.nan
, strategy='constant', fill_value=0).fit_transform(df) # new feature 里面缺失的用0填补
Ytrain = fillc[fillc.notnull()] # 非空行作为训练 会返回带原始索引的数组
Ytest = fillc[fillc.isnull()] # 空行作为预测填补
Xtrain = df_0[Ytrain.index, :] # 利用Ytrain的原始索引取行
Xtest = df_0[Ytest.index, :]
rfc = RandomForestRegressor(n_estimators=100)
rfc = rfc.fit(Xtrain, Ytrain)
Ypredict = rfc.predict(Xtest) # 训练Xtest对应的Y
X_missing_reg.loc[X_missing_reg.iloc[:, i].isnull(), i] = Ypredict # 将训练结果填补进取
X_missing_reg
![](https://img-blog.csdnimg.cn/direct/76438a2103144ef9b1ffc88760c8db13.png)
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
data = load_breast_cancer()
data
![](https://img-blog.csdnimg.cn/direct/d90c5f7ee5614814a1608ce3bf11a03d.png)
data.target
![](https://img-blog.csdnimg.cn/direct/0df25f6909d64b76b40caf94881f7666.png)
4. 随机森林调整的第一步:无论如何先来调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) 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(35, 45): 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(35, 45)][scorel.index(max(scorel))])) plt.figure(figsize=[20, 5]) plt.plot(range(35, 45), scorel) plt.show() 调整n_estimators的效果显著,模型的准确率立刻上升了0.005。接下来就进入网格搜索,我们将使用网格搜索对参数一个个进行调整。为什么我们不同时调整多个参数呢?原因有两个:1)同时调整多个参数会运行非常缓慢在课堂上我们没有这么多的时间。2)同时调整多个参数,会让我们无法理解参数的组合是怎么得来的,所以即便网格搜索调出来的结果不好,我们也不知道从哪里去改。在这里,为了使用复杂度 - 泛化误差方法(方差 - 偏差方法),我们对参数进行一个个地调整。 6. 为网格搜索做准备,书写网格搜索的参数 """ 有一些参数是没有参照的,很难说清一个范围,这种情况下我们使用学习曲线,看趋势 从曲线跑出的结果中选取一个更小的区间,再跑曲线 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=39 , random_state = 90 ) GS = GridSearchCV(rfc, param_grid, cv=10) GS.fit(data.data, data.target) GS.best_params_ GS.best_score_
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=39
,random_state=90
)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(data.data,data.target)
GS.best_params_
GS.best_score_
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=39
,random_state=90
)
GS = GridSearchCV(rfc,param_grid,cv=10)
GS.fit(data.data,data.target)
GS.best_params_
GS.best_score_
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_
GS.best_score
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_ GS.best_score_ 12. 调整完毕,总结出模型的最佳参数 rfc = RandomForestClassifier(n_estimators=39,random_state=90) score = cross_val_score(rfc,data.data,data.target,cv=10).mean() score score - score_pre