机器学习——决策树
(一)决策树的构造
决策树(decision tree)是一类常见的机器学习算法,它是基于树结构来进行决策的。从根节点开始一步步走到叶子节点(决策)。所有的数据最终都会落到叶子节点,既可以做分类也可以做回归。
决策树构建整体流程:
- 在构造决策树时,我们需要解决的第一个问题就是,当前数据集上哪个特征在划分数据分类时起决定性作用。为了找到决定性的特征,划分出最好的结果,我们必须评估每个特征。
- 完成测试后,原始数据就被划分为几个数据子集。这些数据子集会分布在第一个决策点的所有分支上。
- 如果某个分支下的数据属于同一类型,则到这里以及正确地划分数据分类,无序进一步对数据集进行分割。
- 如果数据子集内的数据不属于同一类型,则需要重复划分数据子集的过程。
- 如何划分数据子集的算法和划分原始数据集的方法相同,直到所有具有相同类型的数据均在一个数据子集内。
划分数据集的原则是:将无需的数据变得更加有序。
1.1 信息增益
在划分数据集之前之后信息发生的变化称为信息增益(通俗理解就是特征X使得类Y的不确定性减少的程度),知道如何计算信息增益,我们就可以计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择 。
在可以评测哪种数据划分方式就是最好的数据划分之前,必须学习如何计算信息增益。集合信息的度量方式称为香农熵(information gain) 或者简称为熵(entropy) 。熵是表示随机变量不确定的度量(通俗理解就是物体内部的混乱程度,不确定性越大,得到的熵值也就越大)。
熵定义为信息的期望值,在明晰这个概念之前,我们必须知道信息的定义。如果待分类的事物可能划分在多个分类之中,则符号 x i x_i xi的信息定义为
l ( x i ) = − l o g 2 p ( x i ) l(x_{i})=-log_{2}p(x_{i}) l(xi)=−log2p(xi)
其中 p ( x i ) p(x_i) p(xi)是选择该分类的概率。
为了计算熵,我们需要计算所有类别所有可能值包含的信息期望值,通过下面的公式得到:
H = − ∑ i = 1 n p ( x i ) l o g 2 p ( x i ) H=-\sum_{i=1}^{n}p(x_{i})log_{2}p(x_{i}) H=−i=1∑np(xi)log2p(xi)
n是分类的数目。
当 p ( x i ) = 0 p(x_{i})=0 p(xi)=0或 p ( x i ) = 1 p(x_{i})=1 p(xi)=1时, H = 0 H=0 H=0,随机变量完全没有不确定性。
当 p ( x i ) = 0.5 p(x_{i})=0.5 p(xi)=0.5时, H = 1 H =1 H=1,随机变量的不确定性最大
用Python计算熵
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts = {}
for featVec in dataSet: #the the number of unique elements and their occurance
currentLabel = featVec[-1]
if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries #可能值的期望
shannonEnt -= prob * log(prob,2) #熵
return shannonEnt
代码首先计算数据集的实例总数,然后创建一个数据字典,它的键值是最后一列的数值 。如果当前键值不存在,则扩展字典并将当前键值加入字典。每个键值都记录了当前类别出现的次数。最后,使用所有类标签的发生频率计算类别出现的概率。我们将用这个概率计算香农熵 ,统计所有类标签发生的次数。
用createDataSet()函数得到简单鱼鉴定数据集。
不浮出水面是否可以生存 | 是否有脚蹼 | 属于鱼类 | |
---|---|---|---|
1 | 是 | 是 | 是 |
2 | 是 | 是 | 是 |
3 | 是 | 否 | 否 |
4 | 否 | 是 | 否 |
5 | 否 | 是 | 否 |
def createDataSet():
"""
简单鱼鉴定数据集
"""
dataSet = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']]
labels = ['no surfacing', 'flippers']
return dataSet, labels
import trees
myDat, labels = trees.createDataSet()
print(myDat)
print(trees.calcShannonEnt(myDat))
熵越高,则混合的数据也越多,我们可以在数据集中添加更多的分类,观察熵是如何变化的。添加第三个名为maybe的分类,测试熵的变化:
myDat[0][-1]='maybe'
print(myDat)
print(trees.calcShannonEnt(myDat))
可以看到熵明显增大了,得到熵之后,我们就可以按照获取最大信息增益的方法划分数据集。
1.2 划分数据集
分类算法除了需要测量信息熵,还需要划分数据集,度量划分数据集的熵,以便判断当前是否正确地划分了数据集。将对每个特征划分数据集的结果计算一次信息熵,然后判断按照哪个特征划分数据集是最好的划分方式。
#按照给定特征划分数据集
def splitDataSet(dataSet, axis, value):
"""
按照给定特征划分数据集
:param dataSet:待划分的数据集
:param axis:划分数据集的特征
:param value:需要返回的特征的值
"""
retDataSet = [] # 创建一个新的列表对象
for featVec in dataSet:
if featVec[axis] == value: # 将符合特征的数据抽取出来
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
在函数的开始声明一个新列表对象。因为该函数代码在同一数据集上被调用多次,为了不修改原始数据集,创建一个新的列表对象 。数据集这个列表中的各个元素也是列表,要遍历数据集中的每个元素,一旦发现符合要求的值,则将其添加到新创建的列表中。
可以在简单样本数据上测试函数splitDataSet()。
myDat, labels = trees.createDataSet()
print(myDat)
print(trees.splitDataSet(myDat, 0, 1)) # 抽取,特征[0]值为1
print(trees.splitDataSet(myDat, 0, 0)) # 抽取,特征[0]值为0
接下来遍历整个数据集,循环计算熵和splitDataSet() 函数,找到最好的特征划分方式。熵计算将会告诉我们如何划分数据集是最好的数据组织方式。
def chooseBestFeatureToSplit(dataSet):
"""
选择最好的数据集划分方式
:para