自我代码提升之随机森林
作者:数据取经团——JQstyle
数据取经团(公众号:zlx19930503)
专注R、Python数据分析挖掘、可视化、机器学习
本期将为大家带来建立在决策树基础上的一种集成学习方法,随机森林模型。
随机森林的思想介绍
从集成学习的基本思想来看,将多个弱学习器组合作为新的模型来提升预测效果,就模型的组合方式而言,可以分为Boosting和Bagging两个大类。随机森林作为一种类Bagging的模型,其内部包含了若干棵彼此独立的子决策树模型(通常采用CART决策树作为基模型),在预测过程中是基于各个子模型的结果来决定。
随机森林的基本建模流程如下:
(1)对于给定的训练数据集S,包含了N条样本、n个特征。设定好模型的相关参数:子决策树的数量m,决策树每个节点选择的特征数f,并设计停止条件(树的深度,节点最小样本数等)
(2)在随机森林中的一个子模型训练过程中,对N个样本采用bootstrap有放回抽样的方式构造新的训练集(通常抽取的样本数等于N)。
(3)在子决策树的训练过程中,在每个节点分割之前,从总特征n中随机抽取m个特征作为本次分割的候选变量,持续训练直到满足当前模型的停止条件。
(4)对所有的子决策树建模重复(2)和(3)步直到所有模型训练完毕。
在预测过程中,所有的子决策树均会对预测数据输出一个预测结果,然后对结果进行汇总(分类问题通常采用投票的方式,回归模型输出的则是所有结果的均值),得到最后的预测结果。
随机森林的特点
和决策树等单一模型相比,随机森林具备集成学习的特征,具备一些优势:
a、随机森林和单一模型相比通常能够达到更高的预测精确度
b、随机森林可以处理大量的变量输入,不需要特别的降维和特征选择工作,并且还能输出各个特征的重要性水平。
c、随机森林在建造多个独立的子模型时,它可以在内部对于一般化后的误差产生不偏差的估计,具有较强的泛化能力,不容易陷入过拟合。
d、随机森林可以估计遗失的资料,并且,即使有很大一部分的资料遗失,也可以维持准确度。
e、随机森林的子模型之间彼此独立,可以并行化运行提升建模效率(通常比Bagging更为高效,且效果更佳)
f、对于不平衡的数据集来说,它可以平衡误差。
然而,随机森林也并非是万能的模型,具有一些局限。事实上,随机森林在某些噪音较大的分类或回归问题上可能会陷入过拟合;而基于CART数的决策树在特征评估时会偏向于取值划分更多的特征。而且,在实际应用中,即使实现优化,随机森林的训练所花费的资源和时间依旧相对较高。
随机森林的建模实践
随机森林的简单实现
我们尝试用python简单实现随机森林算法,首先和先前一样,我们加载需要的模块,并且导入Horse数据集,包含了训练集和测试集(代码见附件)。
随机森林通常是基于CART决策树,所以我们先定义CART树的类,关于CART树的介绍在上一片系列文章中已有介绍,在此不做详述,该类包含了6个函数(代码见附件),类名为cart_classify。
在定义随机森林之前,在此对我们的随机森林函数做一个设定,设定每一棵决策树只单独进行一次对字段的抽样,即单棵树内部节点分裂所参考的变量组是一致的,而对于样本的抽取,由于python的random没有现成的重抽样函数,我们采用不放回抽样,每棵树抽区总体一部分的样本(默认为30%),这些假设在一定程度上可以提升训练的效率。
接下来开始定义随机森林类RandomForest,它是cart_classify的一个子类,可以继承父类所有的性质。定义对象tree_forest和tree_names用于分别存放子模型和每个模型采用的变量名。并且重写父类中的CART训练函数和预测函数:
#随机森林尝试
class RandomForest(cart_classify):
tree_forest = []
tree_names = []
def cart_tree_fit(self,x,y,x_names,thre_num=5,max_depth=3,
now_depth=0): #重写cart树训练函数
tree = {}
type_res = np.unique(y)[0]
max_num = sum(y==np.unique(y)[0])
for type0 in np.unique(y)[1:]:
if max_num < sum(y==type0):
type_res = type0
max_num = sum(y==type0)
if y.shape[0] <= thre_num: #停止条件:当Y的数量低于阈值
return type_res
sel_x = 0
new_cut_x, break_point = self.new_cut_x(x,y) #提取各个X字段切分点和分享以后的X
gini_min = self.Gini(x[:,0],y)
for j in range(1,new_cut_x.shape[1]): #找出基尼系数最小的X字段
if self.Gini(new_cut_x[:,j],y) < gini_min:
gini_min = self.Gini(new_cut_x[:,j],y)
sel_x = j
new_x_low = x[x[:,sel_x]<=break_point[sel_x],:] #当前字段低于切分点的剩余X数据集
new_x_high = x[x[:,sel_x]>break_point[sel_x],:] #当前字段高于切分点的剩余X数据集
new_y_low = y[x[:,sel_x]<=break_point[sel_x]] #当前字段低于切分点的Y数据集
new_y_high = y[x[:,sel_x]>break_point[sel_x]] #当前字段高于切分点的Y数据集
label_low = x_names[sel_x] +'<=%s' %break_point[sel_x] #节点标签1
label_high = x_names[sel_x] +'>%s' %break_point[sel_x] #节点标签2
if np.unique(new_y_low).shape[0]<2 or np.unique(new_y_high).shape[0]<2:
return type_res
tree[label_low] = self.cart_tree_fit(new_x_low,new_y_low,x_names,thre_num,max_depth,now_depth+1) #子节点递归1
tree[label_high] = self.cart_tree_fit(new_x_high,new_y_high,x_names,thre_num,max_depth,now_depth+1) #子节点递归2
if tree[label_low] == tree[label_high]:
return tree[label_high]
return tree
def cart_predict(self,x,x_names,model): #预测函数
result = []
for i in range(x.shape[0]):
result.append(self.cart_predict_line(x[i,:],x_names,model))
result = np.array(result)
return result
然后,分别定义单棵决策树训练的函数(包含了对样本和字段的抽样过程)和随机森林建模主函数:
def random_sel_mod(self,x,y,x_names,max_depth,row_samples,thre_num,
seedc,seedr): #单棵决策树的训练函数(基于随机特征和记录)
random.seed(seedc)
col_sel = random.sample(range(x.shape[1]),max_depth+1)
random.seed(seedr)
row_sel = random.sample(range(y.shape[0]),int(round(y.shape[0]*row_samples)))
x_tmp = x[row_sel,:][:,col_sel]
y_tmp = y[row_sel]
names_tmp = x_names[col_sel]
tree_tmp = self.cart_tree_fit(x_tmp,y_tmp,names_tmp,thre_num=thre_num,max_depth=max_depth)
return tree_tmp,names_tmp
def RandomForest_fit(self,x,y,x_names,num_trees=9,max_depth=None,
row_samples=0.3,thre_num=5,seed=100): #随机森林建模主函数
if max_depth == None:
max_depth == round(x.shape[1])
self.tree_names = []
self.tree_forest = []
for i in range(num_trees):
tree_tmp,names_tmp = self.random_sel_mod(x,y,x_names,max_depth,row_samples,thre_num,seed+i,2*seed+i)
self.tree_names.append(names_tmp)
self.tree_forest.append(tree_tmp)
最后定义随机森林的预测函数(预测投票比例和预测类别)和准确度评分函数。
def RandomForest_predict(self,x,x_names): #预测函数:输出概率
result = np.zeros(x.shape[0])
for i in range(len(self.tree_forest)):
pre_tmp = self.cart_predict(x,x_names,self.tree_forest[i])
result = result + pre_tmp
result = result/float(len(self.tree_forest))
return result
def RandomForest_predict_type(self,x,x_names): #预测函数:输出类别
return np.round(self.RandomForest_predict(x,x_names))
def Acc_Score(self,x,x_names,y): #准确度评价函数
pre_result = self.RandomForest_predict_type(x,x_names)
acc_value = sum(pre_result==y)/float(y.shape[0])
return acc_value
我们对实现的随机森林算法进行测试,设定子决策树数量为20,最大树深为5(即每棵树抽取特征数为5)。
#horse数据集
RF_model = RandomForest()
RF_model.RandomForest_fit(horse_train_x,horse_train_y,horse_name,num_trees=20,
max_depth=5,seed=1000)
RF_model.Acc_Score(horse_test_x,horse_name,horse_test_y) #预测准确度
测试结果表明,采用随机森林可以实现比单棵决策树更好的预测效果,最终准确度达到了77%以上。
sklearn随机森林建模
利用Sklearn机器学习裤同样可以实现随机森林建模,本文给出了简单的实践操作。在建模中关键的参数依旧是最大树深和决策树的数量,为了确定最佳参数,可以采用网格搜索的方法去尝试。
#sklearn库的随机森林示例
from sklearn.ensemble import RandomForestClassifier
RF_model = RandomForestClassifier(n_estimators=100)
RF_model.fit(horse_train_x,horse_train_y)
y_pre_RF=RF_model.predict(horse_test_x) #预测模型
sum(y_pre_RF==horse_test_y)/float(horse_test_y.shape[0]) #准确度
RF_model.feature_importances_ #查看变量的重要性
#超参数搜索寻找最优模型
from sklearn.grid_search import GridSearchCV #网格搜索模块
clf = RandomForestClassifier()#候选参数:树的数量,最大树深,选择的变量树
parameters = {'n_estimators':np.array([25,50,100]),'max_depth':np.array([2,3,4,5,6,7,8]),'max_features':np.array([4,5,6,7])}#网格参数搜索,输入之前的模型流程pipe_process,候选参数parameters,并且设置5折交叉验证
gs_RF = GridSearchCV(clf,parameters,verbose=2,refit=True,cv=5) #设置备选参数组
gs_RF.fit(horse_train_x,horse_train_y) #模型训练过程
print gs_RF.best_params_,gs_RF.best_score_ #查看最佳参数和评分(准确度)
#最佳参数的KNN建模对预测数据预测效果)
print 'The Accuracy of GradientBoostingClassifier model with best parameter and MinMaxScaler is',gs_RF.score(horse_test_x,horse_test_y)
拓展
浅谈Bagging方法
所谓Bagging方法,是集成学习的一种实现形式。其基本思想是,训练若干棵决策树,每棵决策树对训练集进行bootstrap重复抽样(抽取样本数通常等于总体N),在结合对训练完毕的所有决策树进行预测。与随机森林不同的是,Bagging方法中每棵树的节点划分依据会参考所有的字段,而且通常Bagging法所建立的子决策树的数量要少于随机森林。
因此,随机森林可以被理解为Bagging方法的变种。在实际中,随机森林以其二重随机性所具备的模型训练和预测的优势,比后者得到了更为广泛的运用。
多进程提升建模效率
在本文对随机森林模型的实现代码过程中,采用的是简单的循环来建立一棵棵子决策树模型,效率较低。实际上,子决策树彼此的建立是相互独立,不存在先后的关联性,因此可以采用一些方式去并行实现这个流程,降低模型训练所花费的时间。
这里给出一种思路是利用多进程模块multiprocessing来实现子决策树的并行建模。当cpu核心较多时,可以使若干棵决策树同时训练,大幅缩短建模时间。这里给出多进程的建模形式(测试代码见附件)
def __call__(self,x,y,x_names,max_depth,row_samples,thre_num,
seedc,seedr): #定义类默认函数,用于多进程优化
return self.random_sel_mod(x,y,x_names,max_depth,row_samples,thre_num,seedc,seedr)
def RandomForest_fit_process(self,x,y,x_names,num_trees=9,max_depth=None,
row_samples=0.3,thre_num=5,seed=100,
process=2): #多进程优化的随机森林训练函数
if max_depth == None:
max_depth == round(x.shape[1])
self.tree_names = []
self.tree_forest = []
pool = multiprocessing.Pool(processes=process)
result = []
for i in range(num_trees):
result.append(pool.apply_async(self,(x,y,x_names,max_depth,row_samples,thre_num,
seed+i,2*seed+i)))
pool.close()
pool.join()
for res in result:
res_tmp = res.get()
self.tree_names.append(res_tmp[1])
self.tree_forest.append(res_tmp[0])
随机森林和特征选择
随机森林模型经常被运用于正式建模前的特征选择工作中,因为模型可以输出对各个字段的重要性指标。包含了平均准确度下降(MeanDecreasingAccuracy)和平均基尼指数下降(MeanDecreasingGiNi)两个指标。前者指的是,当某个字段失去原有的信息时(如变为随机变量),导致模型预测准确度下降的大小;后者则指的是同样情况下模型节点划分中基尼指数平均下降的大小。
Sklearn随机森林建模结果会将模型各个变量的MeanDecreasingGiNi指标存储在featureimportances中。然而,随机森林对变量重要性的评估也存在一些缺陷,如容易偏袒类别数较多的特征,而对于类别较少的特征(如性别),其重要性往往会被低估,在实际运用中需要注意。
系列相关:
本文代码及数据获取方式:下图扫码关注公众号,在公众号中回复 随机森林 即可~
Python爱好者社区历史文章大合集:
Python爱好者社区历史文章列表(每周append更新一次)
关注后在公众号内回复“课程”即可获取:
0.小编的Python入门视频课程!!!
1.崔老师爬虫实战案例免费学习视频。
2.丘老师数据科学入门指导免费学习视频。
3.陈老师数据分析报告制作免费学习视频。
4.玩转大数据分析!Spark2.X+Python 精华实战课程免费学习视频。
5.丘老师Python网络爬虫实战免费学习视频。