决策树

决策树

正方形代表判断模块(decision block),椭圆形代表终止模块(terminating block),表示已经得出结论,可以终止运行。从判断模块引出的左右箭头称作分支(branch),它可以到达另一个判断模块或者终止模块。

决策树的主要优势就在于数据形式非常容易理解。

  • 优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据。

  • 缺点:可能会产生过度匹配问题。

  • 适用数据类型:数值型和标称型。

  • 创建分支的伪代码createBranch()

检测数据集中的每个子项是否属于同一分类:
If so return 类标签;
Else
    寻找划分数据集的最好特征
    划分数据集
    创建分支节点
        for每个划分的子集
            调用函数createBranch并增加返回结果到分支节点中
    return分支节点
  • 一般流程
    • 收集数据:可以使用任何方法。
    • 准备数据:树构造算法只适用于标称型数据,因此数值型数据必须离散化。
    • 分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。
    • 训练算法:构造树的数据结构。
    • 测试算法:使用经验树计算错误率。
    • 使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义。

划分数据集的大原则是将无序的数据变得更加有序。

  • 基尼不纯度(无)
  • 种类
    • ID3
      以信息增益作为树的分裂准则,该算法存在的不足:
      (a) ID3没有考虑连续特征,比如长度,密度都是连续值,无法在ID3运用,如果一定要运用ID3出来连续属性,那么要自己将连续特征离散化(办法非常多)
      (b) 对于缺失值的情况没有做考虑
      (c) 偏向于多值属性。例,如果存在唯一标识属性ID(每个样本的ID属性值都不相同),则ID3会选择它作为优先分裂属性,这样虽然使得划分充分纯净,但这种划分对分类几乎毫无用处
      • 信息增益
        熵定义为信息的期望值,
        • 信息定义为,其中是选择该分类的概率。
        • 信息熵
          其中n是分类的数目。
      • 算法
        • 输入:训练数据集,特征集,阀值;
        • 输出:决策树T.
        • (1)若中所有实例属于同一类,则为单结点树,并将类作为该结点的类标记,返回;
        • (2)若,则为单结点树,并将中实例数最大的类作为该结点的类标记,返回;
        • (3)否则,按算法计算中各特征对的信息增益,选择信息增益最大的特征;
        • (4)如果的信息增益小于阈值,则置为单结点树,并将中实例数最大的类作为该结点的类标记,返回;
        • (5)否则,对的每一可能值,依将分割为若干非空子集,将中实例数最大的类作为标记,构建子结点,由结点及其子结点构成树,返回;
        • (6)对第个子结点,以为训练集,以为特征集,递归地调用步(1)~步(5),得到子树,返回.
- C4.5
    (a)以基于信息增益的增益率(gain ratio)作为树的分裂准则,解决了ID3的偏向于多值属性问题
    (b)内部自己考虑了连续属性离散化过程,所以克服了ID3的没有考虑连续特征问题
    (c)内部考虑了缺失值的自动处理策略
    - 信息增益比
        特征$A$对训练数据集$D$的信息增益比$g_{R}(D, A)$定义为其信息增益$g(D, A)$与训练数据集D的经验熵$H(D)$之比:
        $$g_{R}(D, A)=\frac{g(D, A)}{H(D)}$$
    - 算法
        - 输入:训练数据集$D$,特征集$A$,阀值$\varepsilon$;
        - 输出:决策树T.
        - (1)若$D$中所有实例属于同一类$C_{k}$,则$T$为单结点树,并将类$C_{k}$作为该结点的类标记,返回$T$;
        - (2)若$A=\varnothing$,则$T$为单结点树,并将$D$中实例数最大的类$C_{k}$作为该结点的类标记,返回$T$;
        - (3)否则,按式\ref{equ:5_10}计算$A$中各特征对$D$的信息增益,选择信息增益最大的特征$A_{\mathrm{g}}$;
        - (4)如果$A_{\mathrm{g}}$的信息增益比小于阈值$\varepsilon$,则置$T$为单结点树,并将$D$中实例数最大的类$C_{k}$作为该结点的类标记,返回$T$;
        - (5)否则,对$A_{\mathrm{g}}$的每一可能值$a_{i}$,依$A_{g}=a_{i}$将$D$分割为若干非空子集$D_{i}$,将$D_{i}$中实例数最大的类作为标记,构建子结点,由结点及其子结点构成树$T$,返回$T$;
        - (6)对第$i$个子结点,以$D_{i}$为训练集,以$A-\left\{A_{g}\right\}$为特征集,递归地调用步(1)~步(5),得到子树$T_{i}$,返回$T_{i}$.
- CART
    ID3和C4.5只能处理分类问题,而CART可以处理分类和回归问题,CART考虑问题非常全面,有较多优点,可以自行深入研究
from math import log
import operator
def calcShannonEnt(dataSet):
    """
    计算给定数据集的经验熵(香农熵)
    params dataSet:数据集,要求列表类型,每行长度相同,最后一列为label
    returns shannonEnt:熵
    """
    numEntires = len(dataSet)                        #返回数据集的行数
    labelCounts = {}                                #保存每个标签(Label)出现次数的字典
    for featVec in dataSet:                            #对每组特征向量进行统计
        currentLabel = featVec[-1]                    #提取标签(Label)信息
        if currentLabel not in labelCounts.keys():    #如果标签(Label)没有放入统计次数的字典,添加进去
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1                #Label计数
    shannonEnt = 0.0                                #经验熵(香农熵)
    for key in labelCounts:                            #计算香农熵
        prob = float(labelCounts[key]) / numEntires    #选择该标签(Label)的概率
        shannonEnt -= prob * log(prob, 2)            #利用公式计算
    return shannonEnt 
def splitDataSet(dataSet, axis, value): 
    """
    按照给定特征划分数据集
    params dataSet:数据集
    params axis:划分数据集的特征,第几列的特征
    params value:需要返回的特征的值
    returns retDataSet:返回划分后的数据集
    """      
    retDataSet = []                                        #创建返回的数据集列表
    for featVec in dataSet:                             #遍历数据集
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]                #去掉axis特征
            reducedFeatVec.extend(featVec[axis+1:])     #将符合条件的添加到返回的数据集
            retDataSet.append(reducedFeatVec)
    return retDataSet                                      #返回划分后的数据集

def chooseBestFeatureToSplit(dataSet):
    """
    选择最优特征
    params dataSet:数据集
    returns bestFeature:信息增益最大的(最优)特征的索引值
    """
    numFeatures = len(dataSet[0]) - 1                    #特征数量
    baseEntropy = calcShannonEnt(dataSet)                 #计算数据集的香农熵
    bestInfoGain = 0.0                                  #信息增益
    bestFeature = -1                                    #最优特征的索引值
    for i in range(numFeatures):                         #遍历所有特征
        #获取dataSet的第i个所有特征
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)                         #创建set集合{},元素不可重复
        newEntropy = 0.0                                  #经验条件熵
        for value in uniqueVals:                         #计算信息增益
            subDataSet = splitDataSet(dataSet, i, value)         #subDataSet划分后的子集
            prob = len(subDataSet) / float(len(dataSet))           #计算子集的概率
            newEntropy += prob * calcShannonEnt(subDataSet)     #根据公式计算经验条件熵
        infoGain = baseEntropy - newEntropy                     #信息增益
        # print("第%d个特征的增益为%.3f" % (i, infoGain))            #打印每个特征的信息增益
        if (infoGain > bestInfoGain):                             #计算信息增益
            bestInfoGain = infoGain                             #更新信息增益,找到最大的信息增益
            bestFeature = i                                     #记录信息增益最大的特征的索引值
    return bestFeature                                             #返回信息增益最大的特征的索引值

def majorityCnt(classList):
    """
    params classList:类标签列表
    returns sortedClassCount[0][0]:出现此处最多的元素(类标签)
    """
    classCount = {}
    for vote in classList:                                        #统计classList中每个元素出现的次数
        if vote not in classCount.keys():classCount[vote] = 0   
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)        #根据字典的值降序排序
    return sortedClassCount[0][0]                                #返回classList中出现次数最多的元素


def createTree(dataSet, labels, featLabels):
    """
    params dataSet:训练数据集
    params labels:分类属性标签
    params featLabels:存储选择的最优特征标签
    returns myTree:决策树
    """
    classList = [example[-1] for example in dataSet]            #取分类标签(是否放贷:yes or no)
    if classList.count(classList[0]) == len(classList):            #如果类别完全相同则停止继续划分
        return classList[0]
    if len(dataSet[0]) == 1:                                    #遍历完所有特征时返回出现次数最多的类标签
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet)                #选择最优特征
    bestFeatLabel = labels[bestFeat]                            #最优特征的标签
    featLabels.append(bestFeatLabel)
    myTree = {bestFeatLabel:{}}                                    #根据最优特征的标签生成树
    del(labels[bestFeat])                                        #删除已经使用特征标签
    featValues = [example[bestFeat] for example in dataSet]        #得到训练集中所有最优特征的属性值
    uniqueVals = set(featValues)                                #去掉重复的属性值
    for value in uniqueVals:                                    #遍历特征,创建决策树。                       
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
    return myTree
if __name__ == '__main__':
    dataSet, labels = createDataSet()
    featLabels = []
    myTree = createTree(dataSet, labels, featLabels)
    print(myTree)


# 存储
import pickle
def storeTree(inputTree, filename):
    """
    params inputTree:已经生成的决策树
    params filename:决策树的存储文件名
    """
    with open(filename, 'wb') as fw:
        pickle.dump(inputTree, fw)

# 载入
def grabTree(filename):
    """
    params filename:决策树的存储文件名
    returns pickle.load(fr):决策树字典
    """
    fr = open(filename, 'rb')
    return pickle.load(fr)

  • sklearn实现算法
    • sklearn.tree模块实现决策树模型

      • sklearn.tree.DecisionTreeClassifier
      class sklearn.tree.DecisionTreeClassifier(criterion=’gini’, splitter=’best’, max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, class_weight=None, presort=False)
      
      • 参数
        • params criterion
          特征选择标准,可选参数,默认是gini,可以设置为entropygini是基尼不纯度,是将来自集合的某种结果随机应用于某一数据项的预期误差率,是一种基于统计的思想。entropy是信息熵,是一种基于信息论的思想。Sklearn把gini设为默认参数,应该也是做了相应的斟酌的,精度也许更高些?ID3算法使用的是entropy,CART算法使用的则是gini。
        • params splitter
          特征划分点选择标准,可选参数,默认是best,可以设置为random。每个结点的选择策略。best参数是根据算法选择最佳的切分特征,例如ginientropyrandom随机的在部分划分点中找局部最优的划分点。默认的”best”适合样本量不大的时候,而如果样本数据量非常大,此时决策树构建推荐”random”。
        • params max_features
          划分时考虑的最大特征数,可选参数,默认是None。寻找最佳切分时考虑的最大特征数(n_features为总共的特征数),有如下6种情况:
          • 如果max_features是整型的数,则考虑max_features个特征;
          • 如果max_features是浮点型的数,则考虑int(max_features * n_features)个特征;
          • 如果max_features设为auto,那么max_features = sqrt(n_features);
          • 如果max_features设为sqrt,那么max_featrues = sqrt(n_features),跟auto一样;
          • 如果max_features设为log2,那么max_features = log2(n_features);
          • 如果max_features设为None,那么max_features = n_features,也就是所有特征都用。
          • 一般来说,如果样本特征数不多,比如小于50,我们用默认的”None”就可以了,如果特征数非常多,我们可以灵活使用刚才描述的其他取值来控制划分时考虑的最大特征数,以控制决策树的生成时间。
        • params max_depth
          决策树最大深,可选参数,默认是None。这个参数是这是树的层数的。层数的概念就是,比如在贷款的例子中,决策树的层数是2层。如果这个参数设置为None,那么决策树在建立子树的时候不会限制子树的深度。一般来说,数据少或者特征少的时候可以不管这个值。或者如果设置了min_samples_slipt参数,那么直到少于min_smaples_split个样本为止。如果模型样本量多,特征也多的情况下,推荐限制这个最大深度,具体的取值取决于数据的分布。常用的可以取值10-100之间。
        • params min_samples_split
          内部节点再划分所需最小样本数,可选参数,默认是2。这个值限制了子树继续划分的条件。如果min_samples_split为整数,那么在切分内部结点的时候,min_samples_split作为最小的样本数,也就是说,如果样本已经少于min_samples_split个样本,则停止继续切分。如果min_samples_split为浮点数,那么min_samples_split就是一个百分比,ceil(min_samples_split * n_samples),数是向上取整的。如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。
        • params min_weight_fraction_leaf
          叶子节点最小的样本权重和,可选参数,默认是0。这个值限制了叶子节点所有样本权重和的最小值,如果小于这个值,则会和兄弟节点一起被剪枝。一般来说,如果我们有较多样本有缺失值,或者分类树样本的分布类别偏差很大,就会引入样本权重,这时我们就要注意这个值了。
        • params max_leaf_nodes
          最大叶子节点数,可选参数,默认是None。通过限制最大叶子节点数,可以防止过拟合。如果加了限制,算法会建立在最大叶子节点数内最优的决策树。如果特征不多,可以不考虑这个值,但是如果特征分成多的话,可以加以限制,具体的值可以通过交叉验证得到。
        • params class_weight
          类别权重,可选参数,默认是None,也可以字典、字典列表、balanced。指定样本各类别的的权重,主要是为了防止训练集某些类别的样本过多,导致训练的决策树过于偏向这些类别。类别的权重可以通过{class_label:weight}这样的格式给出,这里可以自己指定各个样本的权重,或者用balanced,如果使用balanced,则算法会自己计算权重,样本量少的类别所对应的样本权重会高。当然,如果你的样本类别分布没有明显的偏倚,则可以不管这个参数,选择默认的None。
        • params random_state
          可选参数,默认是None。随机数种子。如果是证书,那么random_state会作为随机数生成器的随机数种子。随机数种子,如果没有设置随机数,随机出来的数与当前系统时间有关,每个时刻都是不同的。如果设置了随机数种子,那么相同随机数种子,不同时刻产生的随机数也是相同的。如果是RandomState instance,那么random_state是随机数生成器。如果为None,则随机数生成器使用np.random。
        • params min_impurity_split
          节点划分最小不纯度,可选参数,默认是1e-7。这是个阈值,这个值限制了决策树的增长,如果某节点的不纯度(基尼系数,信息增益,均方差,绝对差)小于这个阈值,则该节点不再生成子节点。即为叶子节点 。
        • params presort
          数据是否预排序,可选参数,默认为False,这个值是布尔值,默认是False不排序。一般来说,如果样本量少或者限制了一个深度很小的决策树,设置为true可以让划分点选择更加快,决策树建立的更加快。如果样本量太大的话,反而没有什么好处。问题是样本量少的时候,我速度本来就不慢。所以这个值一般懒得理它就可以了。
      • 方法
        • methods apply(self, X, check_input=True)
        • methods decision_path(self, X, check_input=True)
        • methods fit(self, X, y, sample_weight=None, check_input=True, X_idx_sorted=None)
        • methods get_depth(self)
        • methods get_n_leaves(self)
        • methods get_params(self, deep=True)
        • methods predict(self, X, check_input=True)
        • methods predict_log_proba(self, X)
        • methods predict_proba(self, X, check_input=True)
        • methods score(self, X, y, sample_weight=None)
        • methods set_params(self, **params)
      from sklearn import tree
      clf = tree.DecisionTreeClassifier()
      lenses = clf.fit(lenses, lensesLabels)
      

参考

[1]作者:Jack-Cui
来源:CSDN
原文:https://blog.csdn.net/c406495762/article/details/76262487
版权声明:本文为博主原创文章,转载请附上博文链接!
[2]机器学习实战
[3]统计学习方法

你可能感兴趣的:(决策树)