• 决策树CART--原理与实现


    决策树原理

    首先我们有一个用于训练的群组,前n列是属性,最后一列是标签;我们对决策树的划分是基于群组的混乱程度来划分的,也即是每次寻找一个属性,对该属性分为两组,使得他们的群组混乱程度降低。本文通过以下几个方面来讲解:

    • 定义树节点
    • 划分群组
    • 混乱程度的表示方式
    • 构造树
    • 决策树的显示
    • 对测试样本分类
    • 剪枝
    • 处理缺失数据

    1 定义树节点

    我们来构造树的表达形式,新建一个类,代表数的每一个节点:

    class decisionnode:
        def __init__(self,col=-1,value=None,results=None,tb=None,fb=None):
            self.col = col#待检验的判断条件对应的列索引
            self.value=value#使得结果为True,当前列所匹配的值
            self.results = results#除叶子节点外,其他都为None;叶子节点处代表了分类情况
            self.tb = tb#当前节点左右子树的节点
            self.fb =fb
        

    2 划分群组

    我们需要通过表中某一列的数据将列表拆分成两个数据集,划分群组,使得群组的混乱程度降低

    def divideset(self,rows,col,value):
            #先定义一个划分的函数,根据value值的类型来定义成不同的形式
            split = None
            if isinstance(value,int) or isinstance(value,float):
                split = lambda x:x[col]>=value
            else:
                split = lambda x:x[col]==value
            
            set1 = [row for row in rows if split(row)]
            set2 = [row for row in rows if not split(row)]
            return (set1,set2)

    3 混乱程度的表达方式

    上面说要通过表中某一列的数据来划分,这里我们的目的是找到最合适的一列,使得生成的两个数据集合在混乱程度上能够尽可能小。这里介绍混乱程度的表示。

    我们先通过一个函数来把群组的每一项结果进行计数

     def uniquecount(self,rows):
            results = {}
            for row in rows:
                result = row[len(row)-1]
                results.setdefault(result,0)
                results[result] += 1
            return results
        

    下面通过三种方法来表示混乱程度,其中:基尼不纯度和熵用来表示分类数据,方差更适合来表示数值数据

    3.1 基尼不纯度

    是指集合中的某种结果随机应用到某一个数据项的预期误差率。sum = ΣiΣj(pi * pj)(i≠j),值越高,拆分越不理想,值为0,最为理想。

    def geiniimpurity(self,rows):
            n = len(rows)
            count = self.uniquecount(rows)
            sum = 0
            for ci in count:
                pi = float(count[ci])/n
                for cj in count:
                    if ci == cj : continue
                    pj = float(count[cj])/n
                    sum += pi*pj
            return sum

    3.2 熵

    遍历所有可能结果之后得到的 Σ-p(x)*logp(x)

     def entropy(self,rows):
            from math import log
            log2 = lambda x:log(x)/log(2)
            count = self.uniquecount(rows)
            sum = 0
            for c in count :
                p = float(count[c])/len(rows)
                sum -= p * log2(p)
            return sum

    3.3 方差

    对我们面对以数字为输出结果的数据集时,我们可以使用方差来作为评价函数,它计算一个数据集的统计方差,偏低的方差表示数字彼此接近,偏高的方差表示数字分散,

    def variance(self,rows):
            import math
            results = [row[len(row)-1] for row in rows]
            mean = float(sum(results))/len(rows)
            variance = float(sum([math.pow(result-mean,2) for result in results]))/len(rows)
            return variance

    4 构造树

    • 为了弄明白一个属性的好坏程度,我们首先求出整个群组的熵;
    • 然后尝试利用每个属性的各种可能取值去进行划分群组,并求出两个群组的熵;
    • 为了确定哪个属性最合适,我们计算相应的信息增益--当前熵与加权平均拆分的两个群组的熵的差值,选出信息增益最大的那个属性。
     def buildtree(self,rows):
            current_entropy = self.entropy(rows)#当前群组的熵
            best_gain = 0
            best_criteria = None
            best_sets = None
            
            column = len(rows[0])-1
            for col in range(column):#遍历属性值,寻找最佳属性来划分群组
                values = {}#col列可能的值
                for row in rows:
                    values[row[col]] = 1
                
                for value in values:#寻找该属性值下最佳的一个值
                    (set1,set2)= self.divideset(rows,col,value)
                    p = float(len(set1))/len(rows)
                    gain = current_entropy - p*self.entropy(set1)-(1-p)*self.entropy(set2)#信息增益
                    if gain>best_gain and len(set1)>0 and len(set2)>0:
                        best_gain = gain
                        best_criteria = (col,value)
                        best_sets = (set1,set2)
            if best_gain>0:
                trueBranch = self.bulidtree(best_sets[0])#递归,得到左右子树
                falseBranch = self.buildtree(best_sets[1])
                return decisionnode(col=best_criteria[0],value=best_criteria[1],tb=trueBranch,fb=falseBranch)#返回当前节点
            else:
                return decisionnode(results=self.uniquecount(rows))#信息增益小于等于零,返回节点,results!=None,表示当前分支结果。
            

     5 决策树的显示

    树已经构造完成之后,需要能够将这棵树显示出来,方法有两种:1种是直接print 显示。

     def printtree(self,tree,indent=''):
            if tree.results!=None:
                return tree.results
            else:
                print str(tree.col)+':'+str(tree.value)+'?'
                print indent+'T-->'
                self.printtree(tree.tb,indent+'  ')
                print indent+'F-->'
                self.printtree(tree.fb,indent+'  ')
                

    6 对测试样本分类

    现在我们构造了一棵树,我们可以通过这棵树对观测数据进行分类。

     def classify(self,tree,observation):
            if tree.results!= None:
                return tree.results
            else:
                v = observation[tree.col]
                branch = None
                if isinstance(v,int) or isinstance(v,float):
                    if v>= tree.value: branch = tree.tb
                    else: branch = tree.fb
                else:
                    if v==tree.value : branch = tree.tb
                    else: branch = tree.fb
                return self.classify(branch,observation)

    7 剪枝

    利用上述方法来训练得到的决策树,往往存在一个问题:决策树可能会出现过度拟合。也就是说,它可能过于针对训练数据,专门针对训练集所创造出的分支,其熵值比真实情况

    可能会有所降低。

    我们采取得策略是先构造好一棵树,然后尝试着去消除多余的节点,这个过程即是剪枝。

    具体过程是对具有相同父节点的一组节点进行检查,判断如果将其合并,熵的增加量是否会小于某一个指定的阈值。

     def prune(self,tree,mingain):
            if tree.tb.results!= None:
                self.prune(tree.tb,mingain)
            if tree.fb.results!= None:
                self.prune(tree.fb,mingain)
            
            if tree.tb.results==None and tree.fb.results ==None:
                tb,fb =[],[]
                for v,c in tree.tb.results.items():
                    tb += [[v]]*c
                for v,c in tree.fb.results.items():
                    fb += [[v]]*c
                    
                delta = self.entropy(tb+fb)-(self.entropy(tb)+self.entropy(fb)/2)
                if delta < mingain:
                    tree.tb,tree.fb = None,None
                    tree.results = self.uniquecount(tb+fb)
                
            

    8 处理缺失数据

    决策树还有一个优点,就是处理缺失数据的能力。如果我们缺失了某些数据,而这些数据是确定分支走向所必需的,那么实际上我们选择两个方向都走。

    但,我们不是平均的统计各分支对应的结果值,而是对其进行加权统计。

    def mdclassify(self,tree,observation):
            if tree.results!= None:
                return tree.results
            else:
                v = observation[tree.col]
                branch = None
                if v == None:
                    tb,fb = self.mdclassify(tree.tb,observation),self.mdclassify(tree.fb,observation)
                    tcount = sum(tb.values())
                    fcount = sum(fb.values())
                    tw = float(tcount)/(tcount+fcount)
                    fw = float(fcount)/(tcount+fcount)
                    results = {}
                    for result,value in tb.items():
                        results[result] = value * tw
                    for result ,value in fb.items():
                        results.setdefault(result,0)
                        results[result] += value* fw
                    return results
                else:
                    if isinstance(v,int) or isinstance(v,float):
                        if v>= tree.value: branch = tree.tb
                        else: branch = tree.fb
                    else:
                        if v == tree.value: branch = tree.tb
                        else : branch = tree.fb
                    return mdclassify(branch,observation)
            
  • 相关阅读:
    hdu 1711Number Sequence
    hdu 4911Inversion
    DataView数据变化的各种状态
    c#中的dataview数据视图的sort属性进行排序,用rowfilter属性进行筛选,完成学生档案信息的显示。
    DataView.RowFilter筛选DataTable中的数据
    C# 递归产生树
    treeview递归绑定的两种方法
    C#递归加载树
    c# DropDownList 下拉框实现树形导航
    C# ComboBox 下拉显示层次(树)
  • 原文地址:https://www.cnblogs.com/tosouth/p/4824507.html
Copyright © 2020-2023  润新知