首页 > 代码库 > 手把手生成决策树(dicision tree)

手把手生成决策树(dicision tree)

手把手生成决策树(dicision tree)

主要參考资料:

Peter HARRINGTON.机器学习实战[M].李锐,李鹏,曲亚东,王斌译.北京:人民邮电出版社, 2013.
李航.统计学习方法[M].北京:清华大学出版社, 2012


原文链接:http://blog.csdn.net/xuelabizp/article/details/50979469

1.什么是决策树

决策树是一种主要的分类和回归方法。本文主要解说用于分类的决策树。

决策树就是依据相关的条件进行分类的一种树形结构,比方某高端约会站点针对女客户约会对象见面的安排过程就是一个决策树:
技术分享

依据给定的数据集创建一个决策树就是机器学习的课程,创建一个决策树可能会花费较多的时间。可是使用一个决策树却很快。

创建决策树时最关键的问题就是选取哪一个特征作为分类特征。好的分类特征能够最大化的把数据集分开,将无序变为有序。

这里就出现了一个问题。怎样描写叙述一个数据集有序的程度?在信息论和概率统计中,表示随机变量不确定性的度量,即有序的程度。

现给出一个集合D<script type="math/tex" id="MathJax-Element-48">D</script>。本文全部的讨论都以该集合为例:

序号 不浮出水面能否够生存 是否有脚蹼 是否为鱼类
1
2
3
4
5

创建该集合的代码例如以下:

def create_data_set():
    dataSet =   [[1,1,‘yes‘],
                [1,1,‘yes‘],
                [1,0,‘no‘],
                [0,1,‘no‘],
                [0,1,‘no‘]]
    labels = [‘no surfacing‘, ‘flippers‘] #不浮出水面能否够生存。是否有脚蹼
    return dataSet, labels

2.熵,信息增益和信息增益比

2.1熵(entropy)

博主第一次接触“熵”这个字。是在高中的化学课上,可是感觉“熵”在化学课上的含义和信息论中的含义没什么差别。都是表示混乱的程度,熵越大,越混乱,比方一杯浑浊水的熵就比一杯纯净的水熵大。

在信息论和概率统计中。设X<script type="math/tex" id="MathJax-Element-49">X</script>是一个取有限个值的离散随机变量,其概率分布为:

P(X=xi)=pi,i=1,2,3,..,n(1)
<script type="math/tex; mode=display" id="MathJax-Element-50">P(X=x_i)=p_i,i=1,2,3,..,n \tag{1}</script>
则随机变量X<script type="math/tex" id="MathJax-Element-51">X</script>的熵定义为:
H(X)=?i=1npilog2pi(2)
<script type="math/tex; mode=display" id="MathJax-Element-52">H(X)=-\sum _{i=1}^n p_i\log _2p_i\tag{2}</script>

pi=0<script type="math/tex" id="MathJax-Element-53">p_i=0</script>。则规定0log0=0<script type="math/tex" id="MathJax-Element-54">0\log 0=0</script>。须要说明的是。熵仅仅依赖于X<script type="math/tex" id="MathJax-Element-55">X</script>的分布。而不依赖于X<script type="math/tex" id="MathJax-Element-56">X</script>的值。依据(2)式就能够计算出上面给定的集合D<script type="math/tex" id="MathJax-Element-57">D</script>的熵:

H(D)=?(?25log225?35log235)=0.971
<script type="math/tex; mode=display" id="MathJax-Element-58">H(D)=-\left (-\frac {2}{5}\log _2 \frac {2}{5}-\frac {3}{5}\log _2\frac {3}{5}\right )=0.971</script>

编写计算熵的函数,当中dataSet是建立决策树的数据集,每行最后一个元素表示类别:

def cal_Ent(dataSet): #依据给定数据集计算熵
    num = len(dataSet)
    labels = {}
    for row in dataSet: #统计全部标签的个数
        label = row[-1]
        if label not in labels.keys():
            labels[label] = 0
        labels[label] += 1
    Ent = 0.0
    for key in labels: #计算熵
        prob = float(labels[key]) / num
        Ent -= prob * log(prob, 2)
    return Ent

2.2信息增益(information gain)

信息增益表示得知特征X的信息而使得类Y的信息的不确定性降低的程度。

换一个角度解释一下。一杯浑浊的水Y<script type="math/tex" id="MathJax-Element-59">Y</script>,其熵为H1<script type="math/tex" id="MathJax-Element-60">H_1</script>,如今将当中悬浮的一类物质X<script type="math/tex" id="MathJax-Element-61">X</script>去除。这杯水的熵下降为H2<script type="math/tex" id="MathJax-Element-62">H_2</script>,则物质X<script type="math/tex" id="MathJax-Element-63">X</script>对于这杯水的信息增益就为H1?H2<script type="math/tex" id="MathJax-Element-64">H_1-H_2</script>。

特征X<script type="math/tex" id="MathJax-Element-65">X</script>对数据集D<script type="math/tex" id="MathJax-Element-66">D</script>的信息增益记为g(D,X)<script type="math/tex" id="MathJax-Element-67">g(D,X)</script>,计算公式例如以下:

g(D,X)=H(D)?H(D|X)(3)
<script type="math/tex; mode=display" id="MathJax-Element-68">g(D,X)=H(D)-H(D|X) \tag {3}</script>
当中H(D|X)<script type="math/tex" id="MathJax-Element-69">H(D|X)</script>为特征X给定条件下D的经验条件熵。
先解释什么是条件熵:

条件熵H(Y|X)<script type="math/tex" id="MathJax-Element-70">H(Y|X)</script>表示在已知随机变量X的条件下随机变量Y的不确定性,定义为X给定条件下Y的条件概率分布的熵对X的数学期望。

条件熵的计算公式例如以下:

H(Y|X)=i=1npiH(Y|X=xi)(4)
<script type="math/tex; mode=display" id="MathJax-Element-71">H(Y|X)=\sum _{i=1}^np_iH(Y|X=x_i)\tag {4}</script>

当熵和条件熵中的概率由数据预计得到时,所相应的熵与条件熵分别称为经验熵经验条件熵

决策树选择某个特征作为其分类特征的依据就是该特征对于集合的信息增益最大,即去除该特征后,集合变得最有序。

仍旧以给定的集合D<script type="math/tex" id="MathJax-Element-72">D</script>为例,依据计算信息增益准则选择最优分类特征。

X1<script type="math/tex" id="MathJax-Element-73">X_1</script>表示“不浮出水面能否够生存”。则

g(D,X1)=H(D)?[35H(D1)+25H(D2)]=0.971?[35(?23log223?13log213)+25(?22log222)]=0.420
<script type="math/tex; mode=display" id="MathJax-Element-74">\begin{aligned} g(D,X_1)&=H(D)-\left[\frac {3}{5}H(D_1)+\frac {2}{5}H(D_2)\right]\&=0.971-\left[\frac {3}{5}(-\frac {2}{3}\log _2\frac {2}{3}-\frac {1}{3}\log _2\frac {1}{3}) +\frac {2}{5}(-\frac {2}{2}\log _2\frac {2}{2})\right]\&=0.420 \end{aligned}</script>
当中D1,D2<script type="math/tex" id="MathJax-Element-75">D_1,D_2</script>表示D<script type="math/tex" id="MathJax-Element-76">D</script>中X1<script type="math/tex" id="MathJax-Element-77">X_1</script>取“是”和“否”的样本子集。
X2<script type="math/tex" id="MathJax-Element-78">X_2</script>表示“是否有脚蹼”,则
g(D,X2)=H(D)?[45H(D1)+15H(D2)]=0.971?[45(?24log224?24log224)+15(?11log211)]=0.171
<script type="math/tex; mode=display" id="MathJax-Element-79">\begin{aligned} g(D,X_2)&=H(D)-\left[\frac {4}{5}H(D_1)+\frac {1}{5}H(D_2)\right]\&=0.971-\left[\frac {4}{5}(-\frac {2}{4}\log _2\frac {2}{4}-\frac {2}{4}\log _2\frac {2}{4})+\frac {1}{5}(-\frac {1}{1}\log _2\frac {1}{1})\right]\&=0.171 \end{aligned}</script>
当中D1,D2<script type="math/tex" id="MathJax-Element-80">D_1,D_2</script>表示D<script type="math/tex" id="MathJax-Element-81">D</script>中X2<script type="math/tex" id="MathJax-Element-82">X_2</script>取“是”和“否”的样本子集。


比較各个特征的信息增益。X1<script type="math/tex" id="MathJax-Element-83">X_1</script>的信息增益较大,所以选择X1<script type="math/tex" id="MathJax-Element-84">X_1</script>作为分类的最优特征。

编写选择最佳决策特征的函数,当中dataSet是建立决策树的数据集,每行最后一个元素表示类别:

#依照给定特征划分数据集,返回第axis个特征的值为value的全部数据
def split_data_set(dataSet, axis, value): 
    retDataSet = []
    for row in dataSet:
        if (row[axis]) == value:
            reducedRow = row[:axis]
            reducedRow.extend(row[axis+1:])
            retDataSet.append(reducedRow)
    return retDataSet

#选择最佳决策特征
def choose_best_feature(dataSet): 
    num = len(dataSet[0]) - 1 #特征数
    baseEnt = cal_Ent(dataSet)
    bestInfoGain = 0.0
    bestFeature = -1
    for i in range(num):
        featlist = [example[i] for example in dataSet] #按列遍历数据集,选取一个特征的全部值
        uniqueVals = set(featlist) #一个特征能够取的值
        newEnt = 0.0
        for value in uniqueVals:
            subDataSet = split_data_set(dataSet, i, value)
            prob = len(subDataSet) / float(len(dataSet))
            newEnt += prob * cal_Ent(subDataSet)
        infoGain = baseEnt - newEnt #信息增益
        if (infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature

ID3决策树在生成的过程中。依据信息增益来选择特征。

2.3信息增益比(information gain ratio)

以信息增益作为划分训练数据集的特征。存在偏向于选择取值较多的特征的问题,使用信息增益比能够对这一问题进行校正。

特征X对训练数据集D的信息增益比gR(D,X)<script type="math/tex" id="MathJax-Element-85">g_R(D,X)</script>定义为其信息增益g(D,X)<script type="math/tex" id="MathJax-Element-86">g(D,X)</script>与训练数据集D关于特征X的值的熵HX(D)<script type="math/tex" id="MathJax-Element-87">H_X(D)</script>之比。

信息增益比计算公式例如以下:

gR(D,X)=g(D,X)HX(D)(5)
<script type="math/tex; mode=display" id="MathJax-Element-88">g_R(D,X)=\frac {g(D,X)}{H_X(D)} \tag {5}</script>
当中
HX(D)=?i=1n|Di||D|log2|Di||D|(6)
<script type="math/tex; mode=display" id="MathJax-Element-89">H_X(D)=-\sum _{i=1}^n\frac {|D_i|}{|D|}\log _2 \frac {|D_i|}{|D|}\tag {6}</script>

以给定的集合D为例,计算信息增益比。

HX1(D)=?(?35log235?25log225)=0.971
<script type="math/tex; mode=display" id="MathJax-Element-90">H_{X1}(D)=-\left (-\frac {3}{5}\log _2 \frac {3}{5}-\frac {2}{5}\log _2 \frac {2}{5}\right )=0.971</script>
gR(D,X1)=g(D,X1)HX1(D)=0.4200.971=0.433
<script type="math/tex; mode=display" id="MathJax-Element-91">g_R(D,X_1)=\frac {g(D,X_1)}{H_{X1}(D)}=\frac {0.420}{0.971}=0.433</script>
HX2(D)=?(?45log245?15log215)=0.722
<script type="math/tex; mode=display" id="MathJax-Element-92">H_{X2}(D)=-\left (-\frac {4}{5}\log _2 \frac {4}{5}-\frac {1}{5}\log _2 \frac {1}{5}\right )=0.722</script>
gR(D,X2)=g(D,X2)HX2(D)=0.1710.722=0.237
<script type="math/tex; mode=display" id="MathJax-Element-93">g_R(D,X_2)=\frac {g(D,X_2)}{H_{X2}(D)}=\frac {0.171}{0.722}=0.237</script>

依据信息增益比,选择X1<script type="math/tex" id="MathJax-Element-94">X_1</script>作为分类的最优特征。

C4.5决策树在生成的过程中。依据信息增益比来选择特征。

3.实现一个决策树

3.1创建或加载数据集

首先须要创建或加载训练的数据集,第一节用的是创建数据集的方法,只是更经常使用的是利用open()函数打开文件,加载一个数据集。

3.2生成决策树

决策树一般使用递归的方法生成。

编写递归函数有一个好习惯。就是先考虑结束条件。

生成决策树结束的条件有两个:其一是划分的数据都属于一个类,其二是全部的特征都已经使用了。在另外一种结束情况中。划分的数据有可能不全属于一个类,这个时候须要依据多数表决准则确定这个子数据集的分类。

在非结束的条件下。首先选择出信息增益最大的特征,然后依据其分类。

分类開始时,记录分类的特征到决策树中。然后在特征标签集中删除该特征。表示已经使用过该特征。依据选中的特征将数据集分为若干个子数据集,然后将子数据集作为參数递归创建决策树,终于生成一棵完整的决策树。

#多数表决法则
def majorityCnt(classList): 
    print classList 
    classCount = {}
    for vote in classList: #统计数目
        if vote not in classCount.keys(): classCount[vote] = 0
        classCount += 1
    sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
    return classCount[0][0]

# 生成决策树
def create_tree(dataSet, labels):
    labelsCloned = labels[:]
    classList = [example[-1] for example in dataSet] #[yes,yes,no,no,no]
    if classList.count(classList[0]) == len(classList): #仅仅有一种类别,则停止划分
        return classList[0]
    if len(dataSet[0]) == 1: #没有特征,则停止划分
        return majorityCnt(classList)
    #print dataSet
    bestFeat = choose_best_feature(dataSet)
    bestFeatLabel = labelsCloned[bestFeat] #最佳特征的名字
    myTree = {bestFeatLabel:{}}
    del(labelsCloned[bestFeat])
    featValues = [example[bestFeat] for example in dataSet] #获取最佳特征的全部属性
    uniqueVals = set(featValues)
    for value in uniqueVals: #建立子树
        subLabels = labelsCloned[:] #深拷贝,不能改变原始列表的内容,由于每个子树都要使用
        myTree[bestFeatLabel][value] = create_tree(split_data_set(dataSet, bestFeat, value), subLabels)
    return myTree

生成的决策树例如以下所看到的:
技术分享

3.3使用决策树

使用决策树对输入进行分类的函数也是一个递归函数。

分类函数须要三个參数:决策树。特征列表,待分类数据。特征列表是联系决策树和待分类数据的桥梁,决策树的特征通过特征列表获得其索引,再通过索引訪问待分类数据中该特征的值。

def classify(tree, featLabels, testVec):
    firstJudge = tree.keys()[0]
    secondDict = tree[firstJudge]
    featIndex = featLabels.index(firstJudge) #获得特征索引
    for key in secondDict: #进入相应的分类集合
        if key == testVec[featIndex]: #按特征分类
            if type(secondDict[key]).__name__ == ‘dict‘: #假设分类结果是一个字典,则说明还要继续分类
                classLabel = classify(secondDict[key], featLabels, testVec)
            else: #分类结果不是字典。则分类结束
                classLabel = secondDict[key]
    return classLabel

3.4保存或者加载决策树

生成决策树是比較花费时间的,所以决策树生成以后存储起来。等要用的时候直接读取就可以。

def store_tree(tree, fileName): #保存树
    import pickle
    fw = open(fileName, ‘w‘)
    pickle.dump(tree, fw)
    fw.close()

def grab_tree(fileName): #读取树
    import pickle
    fr = open(fileName)
    return pickle.load(fr)

4.决策树可视化

使用字典的形式表示决策树对于人类来说还是有点抽象,假设能以图像的方式呈现就很方便了。很幸运,matplotlib中有模块能够使决策树可视化。这里就不解说了,直接“拿来使用”。将treePlotter.py复制到我们文件的根文件夹。直接导入treePlotter,然后调用treePlotter.createPlot()函数就可以:

import treePlotter
treePlotter.createPlot(tree)

如上面的决策树可视化后例如以下:
技术分享

5.使用决策树预測隐形眼镜类型

隐形眼镜数据集包括患者的眼睛状况以及医生推荐的隐形眼镜类型。患者信息有4维,分别表示年龄,视力类型,是否散光,眼睛状况。隐形眼镜类型有3种,各自是软材质,硬材质和不适合带隐形眼镜。

想要把我们编写的脚本应用于别的数据集?没问题,仅仅要改动加载数据集的函数就可以,其它的函数不须要改变,详细例如以下:

#加载数据
def file2matrix():
    file = open("lenses.data.txt")
    allLines = file.readlines()
    row = len(allLines)
    dataSet = []
    for line in allLines:
        line = line.strip()
        listFromLine = line.split()
        dataSet.append(listFromLine)
    labels = [‘age‘, ‘prescription‘, ‘astigmatic‘, ‘tear rate‘] #年龄,视力类型,是否散光。眼睛状况
    return dataSet, labels

生成的决策树可视化后例如以下:
技术分享

事实上博主还尝试了其它的数据集,只是决策树实在是太复杂了。太大了。密密麻麻根本看不清楚。谁有兴趣能够尝试一下别的数据集。

6.总结

  • 源代码在我的GitHub中。包括了可视化脚本以及数据集
  • MachineLearningAction仓库里面有常见的机器学习算法处理常见数据集的各种实例。欢迎訪问
  • 决策树的长处
    • 决策树易于理解和解释,尤其是可视化后的决策树很直观
    • 决策树分类很快
  • 决策树的缺点
    • easy过拟合
    • 对缺失数据的数据集处理困难
    • 忽略数据集中特征的相互关联
  • 常见的决策树有ID3,C4.5和CART决策树
    • C4.5较之ID3更优。信息增益比能够解决信息增益选取取值较多的特征的问题
    • C4.5决策树生成的过程中有剪枝。能够降低决策树的拟合度
    • C4.5能够处理数值型数据,而ID3仅仅能处理标称型数据
  • 决策树能够应用在贷款发放,约会见面等方面
<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

手把手生成决策树(dicision tree)