深入RandomFroest




为什么建树采用GBDT而非RF

      RF也是多棵树,但从效果上有实践证明不如GBDT。且GBDT前面的树,特征分裂主要体现对多数样本有区分度的特征;后面的树,主要体现的是经过前N颗树,残差仍然较大的少数样本。优先选用在整体上有区分度的特征,再选用针对少数样本有区分度的特征,思路更加合理,这应该也是用GBDT的原因。






随机森林体现在随机上,台湾林老师讲了三种随机方式:

1)样本bootstrap

2)特征sample

3)特征交叉组合


看了sklearn的代码,实现了前两者,但没有第三种


看了karpathy的代码(https://github.com/karpathy/Random-Forest-Matlab),没有bootstrap样本,feature组合部分写的也真是太“”随机“”了。大致分为四种方法

1)decision-stump,随机选一列特征

2)随机选两列进行线性组合,包括bias项

3)随机选两列,进行二次组合,但也包括了一次项和bias项

4)基于当前样本与所有样本的RBF距离计算分割threshold



自己写了一下,也比较简单,试了前两种,第三种还没做,不过还是先改成并行的吧,之后写相应的博客。

这里先大概写一下已经完成的RandomForest的设计。。。


先介绍数据结构

一个随机森林由多个树组成,所以设计树的结构是关键,下面只介绍树的设计。

首先,一个TreeClassifer的基本结构大致应该如下

class MY_TreeClassifier:

    

    def __init__(self,

             criterion="entropy",     # "entropy", "gini"

             max_depth=None,      # None, int

             min_leaf_split=None,    # None, int

             max_feature="all",      # "all", "sqrt", int

             bootstrap=False,        # True, False

             ):

参数分别是:分裂的标准(基尼、信息熵),每棵树的最大高度,叶子节点数少于min_leaf_split时停止分裂,建树时考虑的特征个数,是否需要bootstrap样本。

另外还可以添加一些特殊的参数进行个性化设计。

其次,树是由一个个节点构成,因此设计每个node的结构也很关键,一个TreeNode的基本结构应该如下

class MY_TreeNode:

    

    def __init__(self, depth=1):

        self.depth=depth

        self.spliter=None

        self.left_child=None

        self.right_child=None

        

        # for leaf node

        self.y_distribution={}

参数分别是:当前节点对应的深度(用于判断是否停止生长);spliter是一个分裂模型,详细参考MY_Spliter数据结构;左子树对应的TreeNode;右子树对应的TreeNode;另外的y_distribution是叶子节点才有,保存该叶子节点对每个类别的预测概率。

在树的构建过程中,分裂操作是最关键的,因此可以单独构建一个分裂类,保存分裂的标准(最好的分裂feature,最好的threshold,最好的criterion增益等信息);具体的,一个Spliter的基本结构应该如下

class MY_Spliter:

    

    def __init__(self):

        self.gain=0

        self.feature_index=0

        self.threshold=0

        self.left_index=[]

        self.right_index=[]

其中left_indexright_index分别保存该spliter在划分样本时,哪些样本分到了左子树,哪些分到了右子树;该参数非必须,只是为了便于调试。

总结MY_TreeClassifier只用于存储和训练树有关的参数;MY_TreeNode保存最经典的左子树指针,右子树指针即可;另外由于构建树是用来预测的,所以每个MY_TreeNode还应该保存预测标准,具体由spliter这个结构体实现;MY_Spliter存储预测时必不可少的feature_id及对应的threshold即可。

 

 

 

 

再介绍关键函数,以及对应的输入输出。

首先,MY_TreeClassifier至少需要两个函数,分别是trainpredict

Train(X_train, y_train)函数

输入:至少要有三个参数,分别是X_trainy_trainMY_TreeClassifier

输出:是一个构建好的树(实际上只要是一个指向root_node的指针就好)。

大致逻辑:

1)根据MY_TreeClassifier保存的参数对X_train进行bootstrapfeature sample等预处理。

2)调用tree=build_tree(X_train, y_train)递归的构建树即可

Predict(X_test)函数

输入:至少要有三个参数,分别是X_test和已经训练好的树tree,另外还要有MY_TreeClassifier(需要对X_test进行和X_train完全一样的预处理操作,而这些预处理操作的参数由MY_TreeClassifier存储)。

输出:每个样本属于各个类别的概率。

大致逻辑:

1)根据MY_TreeClassifier保存的参数对X_test进行bootstrapfeature sample等预处理。

2)根据训练好的树tree的结构(主要是根据每个TreeNode节点中Spliter的信息),将X_test递归的划分到tree的叶子节点

3)根据tree的叶子节点中保存的y_distribution,输出该样本对于每个类别的预测概率。

 

其次是Train函数中调用的build_tree(X_train, y_train)函数

build_tree(X_train, y_train)函数

输入:X_train, y_train, MY_TreeClassifier(终止条件都在MY_TreeClassifier中存储)

输出:构建好的tree(实际上是指向root_node的指针)

大致逻辑:

1)首先根据输入X_train, y_train判断是否满足停止条件(高度、y_train是否purity等),如果满足停止条件,说明X_train, y_train就可以看作是一个leaf_node了,根据X_train, y_train计算对应的y_distribution,返回该leaf_node即可。否则:

2)调用best_spliter=find_best_spliter(X_train, y_train)函数找到针对X_train, y_train最好的划分feature_id, threshold等信息(保存在best_spliter中)。

3)根据best_spliter中的left_indexright_indexX_train, y_train划分成left_X_train, left_y_trainright_X_train, right_y_train

4)递归的调用build_tree(left_X_train, left_y_train)build_tree(right_X_train, right_y_train)就可以把树构件好。

 

最后是find_best_spliter和与之相关的entropy计算函数,这些函数逻辑很简单。

find_best_spliter(X_train, y_train)函数

输入:X_train, y_train, MY_TreeClassifiercriterion="entropy"/"gini"在MY_TreeClassifier中存储)

输出:针对X_train, y_train最好的划分feature_id, threshold等信息

大致逻辑:

1)调用calculate_entropy(y_train)计算原始数据的entropy

2)遍历所有可能切割的feature_idthreshold,调用calculate_split_entropy(feature_id, threshold)计算切割之后的split_entropy

3)将entropy - split_entropy取值最大的feature_id, threshold作为best_spliter返回即可。

calculate_entropy以及calculate_split_entropy不细说了,就是根据公式计算值即可。

 




代码也贴一些:

#!usr/bin/env python
# -*- coding:utf-8 -*-


from MY_TreeClassifier import MY_TreeClassifier

import numpy as np


from sklearn import metrics
from sklearn.ensemble import RandomForestClassifier





class MY_RandomForestClassifier:
    
    def __init__(self,
                 criterion="entropy", # "entropy", "gini"
                 max_depth=None, # None, int
                 min_leaf_split=None, # None, int
                 max_feature="all", # "all", "sqrt", int
                 bootstrap=False, # True, False
                 seed=0, # int
                 n_jobs=None, # None, int
                 warm_start=False, # True, False
                 n_estimator=10 # int
                 ):
        self.criterion=criterion
        self.max_depth=max_depth
        self.min_leaf_split=min_leaf_split
        self.max_feature=max_feature
        self.bootstrap=bootstrap
        self.seed=seed
        self.n_jobs=n_jobs
        
        self.warm_start=warm_start
        self.n_estimator=n_estimator
    
    def fit(self, X, y):
        self.classes_=np.unique(y)
        
        if not self.warm_start: # free all the estimators if any
            self.estimator_=[]
        n_more_estimator=self.n_estimator-len(self.estimator_)
        if n_more_estimator<=0:
            ValueError('n_estimator=%d must be larger or equal to'
                        'len(self.estimator_)=%d when warm_start==True'
                        % ( self.n_estimator, len(self.estimator_) )  )
        else:
            trees=[]
            for i in range(n_more_estimator):
                tree=MY_TreeClassifier(
                    criterion=self.criterion,
                    max_depth=self.max_depth,
                    min_leaf_split=self.min_leaf_split,
                    max_feature=self.max_feature,
                    bootstrap=self.bootstrap,
                    seed=self.seed,
                    n_jobs=self.n_jobs
                    )
                tree=tree.fit(X, y)
                trees.append(tree)
            # collect newly grown trees
            self.estimator_.extend(trees)
        
        return self
    
    def predict_proba(self, X):
        probas=np.zeros([X.shape[0], len(self.classes_)])
        for e in self.estimator_:
            probas+=e.predict_proba(X)
        probas /= len(self.estimator_)
        return probas
    
    def predict(self, X):
        probas = self.predict_proba(X)
        return self.classes_.take(np.argmax(probas, axis=1), axis=0)



if __name__=="__main__":
    '''
    np.random.seed(34567)
    X=np.random.random((50,5))
    y=np.array([1]*25+[0]*25)
    '''
    np.random.seed(34567)
    X=np.random.random((300,5))
    y=np.array([1]*100+[0]*100+[2]*100)
    
    
    rf=MY_RandomForestClassifier(criterion="entropy", max_depth=3, n_estimator=3)
    rf=rf.fit(X,y)
    
    p=rf.predict(X)
    print metrics.accuracy_score(p,y)
    
    sllearn_rf=RandomForestClassifier(n_estimators=3, criterion="entropy",
                 max_depth=3, max_features=None,
                 bootstrap=True, random_state=None, warm_start=False)
    sllearn_rf.fit(X, y)
    p=sllearn_rf.predict(X)
    print metrics.accuracy_score(p,y)


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值