自我代码提升之随机森林

浏览: 2307

本文作者:数据取经团 - JQstyle

      本期将为大家带来建立在决策树基础上的一种集成学习方法,随机森林模型。

随机森林的思想介绍

  从集成学习的基本思想来看,将多个弱学习器组合作为新的模型来提升预测效果,就模型的组合方式而言,可以分为Boosting和Bagging两个大类。随机森林作为一种类Bagging的模型,其内部包含了若干棵彼此独立的子决策树模型(通常采用CART决策树作为基模型),在预测过程中是基于各个子模型的结果来决定。

image.png

 随机森林的基本建模流程如下:
(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%以上。

image.png

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中。然而,随机森林对变量重要性的评估也存在一些缺陷,如容易偏袒类别数较多的特征,而对于类别较少的特征(如性别),其重要性往往会被低估,在实际运用中需要注意。

系列相关:

1.自我代码提升之逻辑回归

2.自我代码提升之K近邻算法

3.自我代码提升之朴素贝叶斯

4.自我代码提升之决策树


推荐 1
本文由 数据取经团 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册