美文网首页
决策树学习与ID3算法C++实现

决策树学习与ID3算法C++实现

作者: gaunthan | 来源:发表于2017-01-16 16:14 被阅读642次

PS:看起来简书好像不支持MathJax,文章中的数学公式都没渲染出来,大家将就点看吧...

决策树学习是以实例为基础的归纳学习,它从一类无序、无规则的事物(也就是概念)中推理出决策树表示的分类规则。

决策树


所谓决策树就是一棵表示“怎么做决定”的树。比如你现在考虑是否要去跑步,那么你也许会以这样的顺序来考虑:

  1. 天气怎么样?如果下雨我就不去了;不下的话,我再考虑考虑。
  2. 我状态怎么样?如果良好就出发吧,不然就算了吧。

如果将上面这个考虑(做决定)过程转化为一棵决策树,它会是下面这个样子的:

DecisionTree.png

注意上面列出的两个点之间是有优先级的,第一点显然是我们先考虑的。好的决策树的层次就表现了良好优先级,在顶层的属性(如“天气”),其决策优先级要高,这也意味着这一属性是可以最快导出结果的。

上面例子的决策树碰巧是一棵二叉树,实际上决策树可以是任意多叉的。

决策树学习


决策树学习采用的是自顶向下的递归方法,决策树的每一层结点依照某一属性值向下分为子结点,待分类的实例在每一结点处与该结点相关的属性值进行比较,根据不同的比较结果向相应的子节点扩展,这一过程在到达决策树的叶结点时结束,此时得到结论。

从根结点到叶结点的每一条路径都对应着一条合理的规则,规则间各个部分(各个层的条件)的关系是合取关系。整个决策树就对应着一组析取的规则。决策树学习算法的最大优点是,它是可以自学习的。

在学习的过程中,不需要使用者了解过多背景知识,只需对训练例子进行较好的标注,就能够进行学习。如果在应用中发现不符合规则的实例,程序会询问用户该实例的正确分类,从而生成新的分支和叶子,并添加到树中。

决策树学习相关定义


决策树学习算法是以信息熵为基础的。

下面是一些相关的定义:

自信息量

设信源$X$发出$a_i$的概率为$p(a_i)$,在收到符号$a_i$之前,收信者对$a_i$的不确定性定义为$a_i$的自信息量$I(a_i)$。其中,
$$I(a_i) = -{log}_2\ p(a_i)$$

信息熵

自信息量只能反映符号的不确定性,而信息熵用来度量整个信源整体的不确定性,定义为:
$$ H(X) = p(a_1)I(a_1) + p(a_2)I(a_2) + ··· + p(a_r)I(a_r) = -\sum_{i = 1}^r p(a_i)\ {log}_2\ p(a_i)$$

条件熵

设信源为$X$,收信者收到信息$Y$,用条件熵$H(X|Y)$来描述收信者在收到$Y$后对$X$的不确定性估计。设$X$的符号$a_i$,$Y$的符号$b_j$,$p(a_i|b_j)$为当$Y$为$b_j$时,$X$为$a_i$的概率,则有:
$$H(X|Y) = -\sum_{i = 1}^r\sum_{j = 1}^s p(a_i|b_j)\ {log}_2\ p(a_i|b_j)$$

平均互信息量

平均互信息量用来表示信号$Y$所能提供的关于$X$的信息量的大小,用$I(X, Y)$表示:
$$ I(X, Y) = H(X) - H(X|Y)$$

决策树的构建


构建一棵决策树要解决4个问题:

  1. 收集待分类的数据。这些数据的所有属性应该是完全标注的。
  2. 设计分类原则。即数据的哪些属性可以被用来分类,以及如何将该属性量化。
  3. 分类原则的选择。即在众多分类准则中,每一步选择哪一准则使最终的树更令人满意。
  4. 设计分类停止条件。实际应用中数据的属性很多,真正有分类意义的属性往往是有限几个,因此在必要的时候应该停止数据集分类,这些准则包括:
  5. 该结点包括的数据太少不足以分裂;
  6. 继续分裂数据集对树生成的目标没有贡献;
  7. 树的深度过大不宜再分。

通用的决策树分裂目标是整棵树的熵总量最小,每一步分裂时,选择使熵减小最小的准则,这种方案使最具分类潜力的准则最先被提取出来。

构建流程大致如下图所示:

HowToBuildADecisionTree.png

ID3算法C++实现


ID3算法将信息熵的下降速度作为属性选择标准。

按照上一节提到的构建过程,我们以一个例子来说明具体如何应用ID3来构建决策树。

案例

现有某流感训练数据集,预定义两个类别:患流感和不患流感。其数据集如下所示:

Samples.png

构建过程

按照前面的流程图,第一步骤是计算各个属性的条件熵。条件熵的求取又依赖到信息熵,因此先讲述如何手算条件熵。

对于上面的例子,属性“体温”的条件熵的计算过程为:

  1. 分别计算每个值的信息熵,有
    $$ H(体温{=正常}) = 0$$
    $$ H(体温
    {=高}) = -{2\over3}log_2{2\over3} - {1\over3}log_2{1\over3} = 0.918296 $$
    $$ H(体温_{=很高}) = 0$$
  2. 求条件熵
    $$ H(样本集S|体温) = {2\over7} * H(体温{=正常}) + {3\over7} * H(体温{=高}) + {2\over7}H(体温_{=很高}) = {2\over7}0 + {3\over7}0.918296 + {2\over7}0 = 0.393555 $$

由于属性”体温“取“正常”或“很高”时,其结论均是单一的(一边倒),因此其信息熵为零($log\ 1 = 0$嘛)。而当取“高”时,有3个样本,这3个样本中有两个结论是“患流感”,另一个是“不患流感”,因此其信息熵不为零。

对于其他的属性,同理可求得:
$$ H(样本集S|头痛) = 0.857143 $$
$$ H(样本集S|肌肉痛) = 0.97925 $$

由于“体温”的条件熵最小,因此决策树的第一个结点应该选用“体温”作为判断属性。

最后生成的决策树如下所示:

Result.png

设计思路

设计样本文件内容格式

为了能够简单使用样本数据,可以将文件内容置为以下:

headache     courbature      temperature    hasBirdFlu
1            1               0              0
1            1               1              1
1            1               2              1
0            1               0              0
0            0               1              0
0            1               2              1
1            0               1              1

第一行表出参与决策的属性以及结论。第二行到最后一行是样本数据,用数值来表达属性的取值。

属性列表、样本集的数据结构设计

得益于C++强大的容器库,可以使用 list 来表示这两个逻辑体:

struct Sample;  // 代表一行样本数据
typedef std::list<Sample> Samples; // 样本集类型
...
typdef std::string Attribute;   // 代表一个属性
typdef std::list<Attribute> Attributes; // 属性列表类型

决策树的数据结构设计

决策树本身是一棵树,而树的表示方式有好几种。简单起见,选择孩子兄弟表示法,即每棵树的根结点拥有一个指向其第一个孩子(即第一棵子树)和一个指向其兄弟(即其父亲的下一棵子树)的指针。

为了方便看出结点的决策,可以选用“(属性,属性值;结果)”这一格式来表示一个结点的内容,其中,根节点为空,内部结点没有“结果”项。

信息熵的求取

信息熵的求取涉及到属性的取值,以及属性取该值时的结论分布。下面的函数就是用于求取某一个属性取特定值时的信息熵,过程中的GetValueCntOfAttr()求取该属性取特定值时的样本个数(如“温度”取“正常”时有2个样本),GetTrueCntForValueOfAttr()求取该属性性取特定值时,结论为真的个数(如“温度”取“正常”时有0个为真的结论):

double CalcEntropyOf(const Samples& samples,
                     const Attribute& attr,
                     double attrValue)
{
    double total = GetValueCntOfAttr(samples, attr, attrValue);
    double trueResult = GetTrueCntForValueOfAttr(
                                    samples, attr, attrValue);
    // all has the same value
    if(total == trueResult || trueResult == 0)
        return 0;
    double trueRate = trueResult / total;
    double entropy = -(trueRate * log2(trueRate)) -
                        ((1 - trueRate) * log2(1 - trueRate));
    return entropy;
}

条件熵的求取

求一个属性的条件熵,需要先求出其各个取值的信息熵,然后根据求取条件熵的公式求取该属性的条件熵即可。注意过程中的GetValueTypeCntOfAttr()求取属性的值种类(如“温度”有3个取值类型):

double CalcConditionalEntropyOf(const Samples& samples,
                                const Attribute& attr)
{
    // calculate conditional entropy
    size_t valueTypeCnt = GetValueTypeCntOfAttr(samples, attr);
    double total = samples.size();
    double conditionalEntropy = 0;
    for(size_t value = 0; value != valueTypeCnt; ++value) {
        conditionalEntropy += GetValueCntOfAttr(samples, attr, value)
                                / total *
                                CalcEntropyOf(samples, attr, value);
    }
    return conditionalEntropy;
}

决策树的生成

DecisionTree* GenerateDecisionTree(const Samples& samples,
                                   const Attributes& attributes,
                                   const DecisionNode& root)
{
    if(attributes.empty()) {
        DecisionTree* tree = new DecisionTree(root);
        return tree;
    }
    DecisionTree* tree = new DecisionTree(root);
    const Attribute bestAttr = FindBestAttribute(samples, attributes);
    const size_t valueTypeCnt = GetValueTypeCntOfAttr(
                                            samples, bestAttr);
    for(size_t value = 0; value != valueTypeCnt; ++value) {
         double entropy = CalcEntropyOf(samples, bestAttr, value);
         if(entropy == 0) { // already know the decision
            size_t trueCnt = GetTrueCntForValueOfAttr(
                                        samples, bestAttr, value);
            size_t decision = trueCnt > 0 ? 1 : 0;
            DecisionNode subTreeNode(DN_LEAF_TYPE, bestAttr,
                                     value, decision);
            DecisionTree* subTree = new DecisionTree(subTreeNode);
            tree->addChild(subTree);
         }else {
            DecisionNode subTreeNode(DN_INNER_TYPE, bestAttr, value);
            // generate new attribute list
            Attributes newAttrs(attributes);
            newAttrs.remove(bestAttr);
            // generate new Samples
            Samples newSamples = GenerateSamplesByAttrValue(
                                        samples, bestAttr, value);
            DecisionTree* subTree = GenerateDecisionTree(
                            newSamples, newAttrs, subTreeNode);
            tree->addChild(subTree);
         }
    }
    return tree;
}

总结


如果你懂得了怎么手动进行这一过程,那么你离编码实现也不远了。

完整的程序代码我已经放到了github上:ID3-Implement。由于能力有限,不免出错。如果您发现了错误,请一定联系我!

相关文章

  • 决策树简记

    具有不同划分准则的算法决策树原理剖析及实现(ID3)理解决策树算法(实例详解)-ID3算法与C4.5算法 ID3(...

  • JS简单实现决策树(ID3算法)

    推荐阅读:ID3算法 wiki决策树算法及实现完整示例代码:JS简单实现决策树(ID3算法)_demo.html ...

  • 理论-决策树

    ID3算法Building Decision Trees in Python如何实现并应用决策树算法?

  • 决策树Decision Tree

    决策树是一种解决分类问题的算法 。 常用的 决策树算法有: ID3 算法 ID3 是最早提出的决策树算法,他...

  • 决策树和随机森林

    随机森林和GBDT算法的基础是决策树 而建立决策树的算法由很多,ID3,C4.5,CART等, ID3:ID3算法...

  • 决策树基本要点及方法对比

    决策树的生产,基本方法有ID3、C4.5、CART。基于基础决策树学习器,可进一步构建提升树。 ID3 ID3算法...

  • ID3和C4.5决策树算法总结及其ID3Python实现

    ID3和C4.5决策树算法总结及其ID3Python实现 1.决策树的算法流程 决策树的算法流程主要是:1.如果当...

  • day10-决策树

    今天学了决策树的基本知识。 基于信息论的决策树算法有:ID3, CART, C4.5等算法。 ID3 算法是根...

  • 数据科学(机器学习: 决策树(ID3算法 ))

    决策树构建 ID3算法 ID3算法的核心是在决策树各个结点上对应信息增益准则选择特征,递归地构建决策树。 从根结点...

  • c4.5

    C4.5是机器学习算法中的另一个分类决策树算法,它是基于ID3算法进行改进后的一种重要算法,相比于ID3算法,改进...

网友评论

      本文标题:决策树学习与ID3算法C++实现

      本文链接:https://www.haomeiwen.com/subject/yirpbttx.html