C4.5是一系列用在机器学习和数据挖掘的分类问题中的算法。它的目标是监督学习:给定一个数据集,其中的每一个元组都能用一组属性值来描述,每一个元组属于一个互斥的类别中的某一类。C4.5的目标是通过学习,找到一个从属性值到类别的映射关系,并且这个映射能用于对新的类别未知的实体进行分类。
C4.5由J.Ross Quinlan在ID3的基础上提出的。ID3算法用来构造决策树。决策树是一种类似流程图的树结构,其中每个内部节点(非树叶节点)表示在一个属性上的测试,每个分枝代表一个测试输出,而每个树叶节点存放一个类标号。一旦建立好了决策树,对于一个未给定类标号的元组,跟踪一条有根节点到叶节点的路径,该叶节点就存放着该元组的预测。
决策树的优势在于不需要任何领域知识或参数设置,适合于探测性的知识发现。从ID3算法中衍生出了C4.5和CART两种算法。
(1)ID3算法在选择根节点和各内部节点中的分支属性时,采用信息增益作为评价标准。信息增益的缺点是倾向于选择取值较多的属性,而这类属性并不一定是最优的属性。 (2)在决策树构造过程中进行剪枝,因为某些具有很少元素的结点可能会使构造的决策树过适应(Overfitting),如果不考虑这些结点可能会更好;
(3)能够处理离散型和连续型的属性类型,即将连续型的属性进行离散化处理;
(4)能够处理具有缺失属性值的训练数据。
优点:产生的分类规则易于理解,准确率较高。
缺点:在构造树的过程中,需要对数据集进行多次的顺序扫描和排序,因而导致算法的低效。此外,C4.5只适合于能够驻留于内存的数据集,当训练集大得无法在内存容纳时程序无法运行。
过拟合的原因:在于学习时过多的考虑如何提高对训练数据的正确分类,从而构建出过于复杂的决策树。
决策树是一种用于分类的树结构。如图所示,它由结点(node)和有向边(directed edge)组成,结点包括内部结点(internal node)和叶结点(leaf node),内部结点表示一个特征或属性,叶结点表示一个类。
决策树学习通常包括3个步骤:特征选择、决策树的生成和决策树的修剪。
决策树如何进行分类呢?从根结点开始,根据待分类数据的某一特征值对其进行划分,分配到相应子结点,因此,每一个子结点都对应了该特征的一个取值。像这样递归进行,直到到达叶结点。
特征选择在于选取对训练数据具有良好分类能力的特征,这样可以提高决策树的学习效率。通常特征选择的准则是信息增益或信息增益比。
(1)熵
在信息论和概率统计中,熵是表示随机变量不确定性的度量。设X是一个取有限个值得离散随机变量,其概率分布为:
P ( X = x i ) = p i , i = 1 , 2 , 3 , . . . , n P(X=x_i) =p_i,i=1,2,3,...,n P(X=xi)=pi,i=1,2,3,...,n
则随机变量X的熵定义为:
H ( X ) = − ∑ i = 1 n p i l o g p i H(X)= -\displaystyle\sum_{i=1}^{n} p_ilogp_i H(X)=−i=1∑npilogpi
熵越大,随机变量的不确定性就越大。
(2)条件熵
设有随机变量(X,Y)其联合概率分布为:
P ( X = x i , Y = y i ) = p i j , i = 1 , 2 , 3 , . . . , n , j = 1 , 2 , 3 , . . . , n P(X=x_i,Y=y_i) =p_{ij},i=1,2,3,...,n,j=1,2,3,...,n P(X=xi,Y=yi)=pij,i=1,2,3,...,n,j=1,2,3,...,n
条件熵H(Y∣X) 表示在已知随机变量X的条件下随机变量Y 的不确定性,定义为X 给定条件下Y 的条件概率分布的熵对X 的数学期望:
H ( Y ∣ X ) = ∑ i = 1 n p i H ( Y ∣ X = x i ) H(Y|X)= \displaystyle\sum_{i=1}^{n} p_iH(Y|X=x_i) H(Y∣X)=i=1∑npiH(Y∣X=xi)
p i = P ( X = x i ) , i = 1 , 2 , 3 , . . . , n p_i=P(X=x_i),i=1,2,3,...,n pi=P(X=xi),i=1,2,3,...,n
(3)经验熵和经验条件熵
当熵和条件熵中的概率由数据估计(特别是极大似然估计)得到时,对应的熵与条件熵分别称为经验熵和经验条件熵。
(4)信息增益
信息增益表示得知特征X 的信息后特征Y 的信息不确定性减少的程度,反应了特征X对于其他特征不确定性的影响程度。
信息增益:特征A对训练数据集D的信息增益 g ( D , A ) g(D,A) g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵 H ( D ∣ A ) H(D|A) H(D∣A)之差,即:
g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D,A)= H(D)-H(D|A) g(D,A)=H(D)−H(D∣A)
根据信息增益准则的特征选择方法是:对训练数据集(或子集)D,计算其每个特征的信息增益,并比较它们的大小,选择信息增益最大的特征。
设训练数据集为D,|D|表示其样本容量,即样本个数。设有K个类 C k C_k Ck, k = 1 , 2 , … , K k=1,2,…,K k=1,2,…,K, ∣ C k ∣ |C_k| ∣Ck∣为属于类 C k C_k Ck的样本个数, ∑ i = 1 n ∣ C k ∣ = ∣ D ∣ \displaystyle\sum_{i=1}^{n} |C_k|=|D| i=1∑n∣Ck∣=∣D∣。设特征A有n个不同的取值 a 1 , a 2 , … , a n {a_1,a_2, …,a_n} a1,a2,…,an,根据特征A的取值将D划分为n个子集 D 1 , D 2 , … , D n D_1,D_2,…,D_n D1,D2,…,Dn, ∣ D i ∣ |D_i| ∣Di∣为Di的样本个数, ∑ i = 1 n ∣ D i ∣ = ∣ D ∣ \displaystyle\sum_{i=1}^{n} |D_i|=|D| i=1∑n∣Di∣=∣D∣。
ID3信息增益算法:
输入:训练数据集D和特征A;
输出:特征A对训练数据集D的信息增益 g ( D ∣ A ) g(D|A) g(D∣A);
(1)计算数据集D的经验熵H(D)
H ( D ) = − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ l o g 2 ∣ C k ∣ ∣ D ∣ H(D)= -\displaystyle\sum_{k=1}^{K} \frac{|C_k|}{|D|}log_2\frac{|C_k|}{|D|} H(D)=−k=1∑K∣D∣∣Ck∣log2∣D∣∣Ck∣
(2)计算特征A对数据集D的经验条件熵H(D|A)
H ( D ∣ A ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ H ( D i ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ ∑ k = 1 K ∣ D i k ∣ ∣ D i ∣ l o g 2 ∣ D i k ∣ ∣ D i ∣ H(D|A)=\displaystyle\sum_{i=1}^{n} \frac{|D_i|}{|D|}H(D_i)= -\displaystyle\sum_{i=1}^{n} \frac{|D_i|}{|D|}\displaystyle\sum_{k=1}^{K} \frac{|D_{ik}|}{|D_i|}log_2 \frac{|D_{ik}|}{|D_i|} H(D∣A)=i=1∑n∣D∣∣Di∣H(Di)=−i=1∑n∣D∣∣Di∣k=1∑K∣Di∣∣Dik∣log2∣Di∣∣Dik∣
(3)计算信息增益
g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D,A)= H(D)-H(D|A) g(D,A)=H(D)−H(D∣A)
(5)信息增益比
以信息增益作为选择特征的准则,存在偏向于选择取值较多的特征的问题。即当一个特征可能的取值较多时,其计算出来的信息增益可能会较高,但是并不一定就一定是一个更有效的分类特征。采用信息增益比可以对这一问题进行校正,这是特征选择的另一准则。
特征A对训练数据集D的信息增益比 g R ( D , A ) g_R(D,A) gR(D,A)定义为其信息增益 g ( D , A ) g(D,A) g(D,A)与训练数据集D的经验熵 H ( D ) H(D) H(D)之比:
g R ( D , A ) = g ( D , A ) H ( D ) g_R(D,A)=\frac{g(D,A)}{H(D)} gR(D,A)=H(D)g(D,A)
C4.5信息增益算法:
输入:训练数据集D和特征A;
输出:特征A对训练数据集D的信息增益 g ( D ∣ A ) g(D|A) g(D∣A);
(1)计算数据集D的经验熵H(D)
H ( D ) = − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ l o g 2 ∣ C k ∣ ∣ D ∣ H(D)= -\displaystyle\sum_{k=1}^{K} \frac{|C_k|}{|D|}log_2\frac{|C_k|}{|D|} H(D)=−k=1∑K∣D∣∣Ck∣log2∣D∣∣Ck∣
(2)计算特征A对数据集D的经验条件熵H(D|A)
H ( D ∣ A ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ H ( D i ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ ∑ k = 1 K ∣ D i k ∣ ∣ D i ∣ l o g 2 ∣ D i k ∣ ∣ D i ∣ H(D|A)=\displaystyle\sum_{i=1}^{n} \frac{|D_i|}{|D|}H(D_i)= -\displaystyle\sum_{i=1}^{n} \frac{|D_i|}{|D|}\displaystyle\sum_{k=1}^{K} \frac{|D_{ik}|}{|D_i|}log_2 \frac{|D_{ik}|}{|D_i|} H(D∣A)=i=1∑n∣D∣∣Di∣H(Di)=−i=1∑n∣D∣∣Di∣k=1∑K∣Di∣∣Dik∣log2∣Di∣∣Dik∣
(3)计算信息增益
g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D,A)= H(D)-H(D|A) g(D,A)=H(D)−H(D∣A)
(4)计算信息增益比
g R ( D , A ) = g ( D , A ) H ( D ) g_R(D,A)=\frac{g(D,A)}{H(D)} gR(D,A)=H(D)g(D,A)
ID3算法的核心是在决策树各个节点上应用信息增益准则选择特征,递归的构建决策树。
ID3算法决策树的生成
输入:训练数据集D,特征集A,阈值 ε \varepsilon ε;
输出:决策树T。
(1)若D中所有实例属于同一类 C k C_k Ck,则T为单结点树,并将类 C k C_k Ck作为该结点的类标记, 返回T;
(2)若A=Ø,则T为单结点树,并将D中实例数最大的类 C k C_k Ck作为该结点的类标记,返回 T;
(3)否则,按ID3信息增益算法计算A中各特征对D的信息增益,选择信息增益最大的特征Ag;
(4)如果Ag的信息增益小于阈值 ε \varepsilon ε ,则置T为单结点树,并将D中实例数最大的类 C k C_k Ck作为该结点的类标记,返回T;
(5)否则,对Ag的每一可能值 a i a_i ai,依Ag= a i a_i ai将D分割为若干非空子集 D i D_i Di,将 D i D_i Di中实例数 最大的类作为标记,构建子结点,由结点及其子结点构成树T,返回T;
(6)对第i个子结点,以 D i D_i Di为训练集,以A-{Ag}为特征集,递归地调用步1~步5,得到子树 T i T_i Ti,返回 T i T_i Ti。
ID | 年龄 | 有工作 | 有自己的房子 | 信贷情况 | 类别 |
---|---|---|---|---|---|
1 | 青年 | 否 | 否 | 一般 | 否 |
2 | 青年 | 否 | 否 | 好 | 否 |
3 | 青年 | 是 | 否 | 好 | 是 |
4 | 青年 | 是 | 是 | 好 | 是 |
5 | 青年 | 否 | 否 | 一般 | 否 |
6 | 中年 | 否 | 否 | 好 | 否 |
7 | 中年 | 否 | 否 | 好 | 否 |
8 | 中年 | 是 | 是 | 好 | 是 |
9 | 中年 | 否 | 是 | 非常好 | 是 |
10 | 中年 | 否 | 是 | 非常好 | 是 |
11 | 老年 | 否 | 是 | 非常好 | 是 |
12 | 老年 | 否 | 是 | 好 | 是 |
13 | 老年 | 是 | 否 | 好 | 是 |
14 | 老年 | 是 | 否 | 非常好 | 是 |
15 | 老年 | 否 | 否 | 一般 | 否 |
统计学习方法例题ID3算法数学实现:
(1)计算经验熵:
H ( D ) = − 9 15 l o g 2 9 15 − 6 15 l o g 2 6 15 = 0.971 H(D) = -\frac{9}{15}log_2\frac{9}{15}-\frac{6}{15}log_2\frac{6}{15}=0.971 H(D)=−159log2159−156log2156=0.971
(2)计算条件经验熵:
分别以A1,A2,A3,A4表示年龄、有工作、 有自己的房子和信贷情况4个特征,则:
H ( D ∣ A 1 ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ H ( D i ) = 5 15 H ( D 1 ) + 5 15 H ( D 2 ) + 5 15 H ( D 3 ) = 5 15 ( − 2 5 l o g 2 2 5 − 3 5 l o g 2 3 5 ) + 5 15 ( − 3 5 l o g 2 3 5 − 2 5 l o g 2 2 5 ) + 5 15 ( − 4 5 l o g 2 4 5 − 1 5 l o g 2 1 5 ) = 0.888 H(D|A_1)=\displaystyle\sum_{i=1}^{n} \frac{|D_i|}{|D|}H(D_i)= \frac{5}{15}H(D_1)+\frac{5}{15}H(D_2)+\frac{5}{15}H(D_3)=\frac{5}{15}(-\frac{2}{5}log_2\frac{2}{5}-\frac{3}{5}log_2\frac{3}{5})+\frac{5}{15}(-\frac{3}{5}log_2\frac{3}{5}-\frac{2}{5}log_2\frac{2}{5})+\frac{5}{15}(-\frac{4}{5}log_2\frac{4}{5}-\frac{1}{5}log_2\frac{1}{5})=0.888 H(D∣A1)=i=1∑n∣D∣∣Di∣H(Di)=155H(D1)+155H(D2)+155H(D3)=155(−52log252−53log253)+155(−53log253−52log252)+155(−54log254−51log251)=0.888
同理:
H ( D ∣ A 2 ) = 0.674 H(D|A_2)=0.674 H(D∣A2)=0.674
H ( D ∣ A 3 ) = 0.551 H(D|A_3)=0.551 H(D∣A3)=0.551
H ( D ∣ A 4 ) = 0.608 H(D|A_4)=0.608 H(D∣A4)=0.608
(3)计算信息增益:
g ( D , A 1 ) = H ( D ) − H ( D ∣ A 1 ) = 0.971 − 0.888 = 0.083 g(D,A_1)= H(D)-H(D|A_1)=0.971-0.888=0.083 g(D,A1)=H(D)−H(D∣A1)=0.971−0.888=0.083
g ( D , A 2 ) = H ( D ) − H ( D ∣ A 1 ) = 0.971 − 0.674 = 0.324 g(D,A_2)= H(D)-H(D|A_1)=0.971-0.674=0.324 g(D,A2)=H(D)−H(D∣A1)=0.971−0.674=0.324
g ( D , A 3 ) = H ( D ) − H ( D ∣ A 1 ) = 0.971 − 0551 = 0.420 g(D,A_3)= H(D)-H(D|A_1)=0.971-0551=0.420 g(D,A3)=H(D)−H(D∣A1)=0.971−0551=0.420
g ( D , A 4 ) = H ( D ) − H ( D ∣ A 1 ) = 0.971 − 0.608 = 0.363 g(D,A_4)= H(D)-H(D|A_1)=0.971-0.608=0.363 g(D,A4)=H(D)−H(D∣A1)=0.971−0.608=0.363
最后,比较各特征的信息增益值。由于特征A3(有自己的房子)的信息增益值最大,所 以选择特征A3作为最优特征。
python代码实现:
from math import log
import operator
def loadDataSet():
dataSet = [['青年', '否', '否', '一般', '否'],
['青年', '否', '否', '好', '否'],
['青年', '是', '否', '好', '是'],
['青年', '是', '是', '一般', '是'],
['青年', '否', '否', '一般', '否'],
['中年', '否', '否', '一般', '否'],
['中年', '否', '否', '好', '否'],
['中年', '是', '是', '好', '是'],
['中年', '否', '是', '非常好', '是'],
['中年', '否', '是', '非常好', '是'],
['老年', '否', '是', '非常好', '是'],
['老年', '否', '是', '好', '是'],
['老年', '是', '否', '好', '是'],
['老年', '是', '否', '非常好', '是'],
['老年', '否', '否', '一般', '否']]
label = ['年龄', '有工作', '有自己的房子', '信贷情况']
return dataSet, label
#计算数据集的香农熵
def calcShannonEnt(dataSet):
numEntries = len(dataSet) #求出数据有多少个对象,即有多少行,计算实例总数
labelCount = {}
for featVec in dataSet: #对数据集逐行求最后一类的数据,并将统计最后一列数据的数目
classify = featVec[-1]
if classify not in labelCount.keys(): #创建一个字典,键值是最后一列的数值
labelCount[classify]=1#当前键值不存在,则扩展字典并将此键值加入字典
else:
labelCount[classify] += 1 #每个键值都记录了当前类别出现的次数
shannonEnt = 0.0
for key in labelCount:
prob = float(labelCount[key])/numEntries #使用统计出的最后一列的数据来计算所有类标签出现的概率
shannonEnt -= prob * log(prob,2) #下面的公式
return shannonEnt
#按照获取最大信息增益的方法来划分数据集
def splitDataSet(dataSet, axis, value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value: #判断axis列的值是否为value
reducedFeatVec = featVec[:axis] #[:axis]表示前axis行,即若axis为2,就是取featVec的前axis行
reducedFeatVec.extend(featVec[axis+1:]) #[axis+1:]表示从跳过axis+1行,取接下来的数据
retDataSet.append(reducedFeatVec)
return retDataSet
#遍历整个数据集,找到最好的特征划分方式
def chooseBestFeatureToSplitForID3(dataSet):
numFeatures = len(dataSet[0]) - 1#特征个数
baseEntropy = calcShannonEnt(dataSet)#数据集dataSet的熵
bestInfoGain = 0.0#最优信息增益
bestFeature = -1#最优特征
for i in range(numFeatures):#对于每一个特征列
featList = [example[i] for example in dataSet]#获取该特征列的
uniqueVals = set(featList)#获得该特征的所有唯一特征值
newEntropy = 0.0#i特征的信息熵
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value)#按照i特征的value唯一特征值--拆分数据集
prob = len(subDataSet)/float(len(dataSet))#新数据集占总数据的比例
newEntropy += prob * calcShannonEnt(subDataSet)#给定特征i的数据集dataSet的条件熵
infoGain = baseEntropy - newEntropy#按照i特征划分的信息增益(数据集dataSet的熵-给定特征i的数据集dataSet的条件熵)
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i #判断出哪种划分方式得到最大的信息增益,且得出该划分方式所选择的特征
# print('最优特征是:', label[bestFeature])
return bestFeature
#频率最高的类别函数 判断递归何时结束
def majorityCnt(classList): #参数classList在下面创建树的代码里,是每一行的最后一个特征
classCount = {}
for vote in classList: #将特征里的元素添加到新建的字典里作为键值,并统计该键值出现次数
if vote not in classCount.keys(): classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0] #返回出现次数最多的键值
#创建决策树
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet]
if len(set(classList)) == 1:#if 数据集中的每个子项的类别完全相同:return 该类别
return classList[0]
if len(dataSet[0]) == 0: #if 遍历完所有的特征:return 频率最高的类别
return majorityCnt(classList)
# 寻找划分数据集的最好特征 #获取当前数据集合和标签列表的最优特征维度
bestFeat = chooseBestFeatureToSplitForID3(dataSet)
# 最优特征维度对应的标签值
bestFeatLabel = labels[bestFeat]
# 创建分支节点
myTree = {bestFeatLabel: {}}
del (labels[bestFeat])
# 划分数据集
featValues = [example[bestFeat] for example in dataSet]
# print(set(featValues))
# for 每个划分的子集
for value in set(featValues):
# 调用createTree()并且增加返回结果到分支节点中
subLabels = labels[:]
subDataSet = splitDataSet(dataSet, bestFeat, value)
myTree[bestFeatLabel][value] = createTree(subDataSet, subLabels)
# return 分支节点
return myTree
dataSet, label = loadDataSet()
print(createTree(dataSet, label))
C4.5算法:与ID3算法相似,不同是用信息增益比来选择特征。
C4.5算法决策树生成:
输入:训练数据集D,特征集A,阈值 ε \varepsilon ε;
输出:决策树T。
(1)如果D中所有实例属于同一类 C k C_k Ck,则置T为单结点树,并将 C k C_k Ck作为该结点的类,返 回T;
(2)如果A=Ø,则置T为单结点树,并将D中实例数最大的类 C k C_k Ck作为该结点的类,返回 T;
(3)否则,计算A中各特征对D的信息增益比,选择信息增益比最大的特征Ag;
(4)如果Ag的信息增益比小于阈值 ε \varepsilon ε ,则置T为单结点树,并将D中实例数最大的类 C k C_k Ck作为该结点的类,返回T;
(5)否则,对Ag的每一可能值 a i a_i ai,依Ag= a i a_i ai将D分割为子集若干非空 D i D_i Di,将 D i D_i Di中实例数 最大的类作为标记,构建子结点,由结点及其子结点构成树T,返回T;
(6)对结点i,以 D i D_i Di为训练集,以A-{Ag}为特征集,递归地调用步(1)~步(5),得到子树Ti,返回 T i T_i Ti。
统计学习方法例题c4.5算法python代码实现:
from math import log
import numpy as np
import pandas as pd
import operator
def loadDataSet():
dataSet = [['青年', '否', '否', '一般', '否'],
['青年', '否', '否', '好', '否'],
['青年', '是', '否', '好', '是'],
['青年', '是', '是', '一般', '是'],
['青年', '否', '否', '一般', '否'],
['中年', '否', '否', '一般', '否'],
['中年', '否', '否', '好', '否'],
['中年', '是', '是', '好', '是'],
['中年', '否', '是', '非常好', '是'],
['中年', '否', '是', '非常好', '是'],
['老年', '否', '是', '非常好', '是'],
['老年', '否', '是', '好', '是'],
['老年', '是', '否', '好', '是'],
['老年', '是', '否', '非常好', '是'],
['老年', '否', '否', '一般', '否']]
label = ['年龄', '有工作', '有自己的房子', '信贷情况']
return dataSet, label
#计算数据集的香农熵
def calcShannonEnt(dataSet):
numEntries = len(dataSet) #求出数据有多少个对象,即有多少行,计算实例总数
labelCount = {}
for featVec in dataSet: #对数据集逐行求最后一类的数据,并将统计最后一列数据的数目
classify = featVec[-1]
if classify not in labelCount.keys(): #创建一个字典,键值是最后一列的数值
labelCount[classify]=1#当前键值不存在,则扩展字典并将此键值加入字典
else:
labelCount[classify] += 1 #每个键值都记录了当前类别出现的次数
shannonEnt = 0.0
for key in labelCount:
prob = float(labelCount[key])/numEntries #使用统计出的最后一列的数据来计算所有类标签出现的概率
shannonEnt -= prob * log(prob,2) #下面的公式
return shannonEnt
#按照获取最大信息增益的方法来划分数据集
def splitDataSet(dataSet, axis, value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value: #判断axis列的值是否为value
reducedFeatVec = featVec[:axis] #[:axis]表示前axis行,即若axis为2,就是取featVec的前axis行
reducedFeatVec.extend(featVec[axis+1:]) #[axis+1:]表示从跳过axis+1行,取接下来的数据
retDataSet.append(reducedFeatVec)
return retDataSet
#遍历整个数据集,找到最好的特征划分方式
def chooseBestFeatureToSplitForC45(dataSet):
numFeatures = len(dataSet[0]) - 1 # 特征个数
baseEntropy = calcShannonEnt(dataSet) # 数据集dataSet的熵
bestInfoGainRatio = 0.0# 最优信息增益
bestFeature = -1 #最优特征
for i in range(numFeatures): # 对于每一个特征列
featList = [example[i] for example in dataSet] # 获取该特征列的值
uniqueVals = set(featList) # 获得该特征的所有唯一特征值
newEntroy = 0.0 # 给定特征i的数据集dataSet的条件熵
ibaseEntroy=0.0#数据集dataSet关于特征i的值的熵(数据集dataSet按照特征i的值划分的各子集的熵*子集占dataSet的比例之和)
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value) # 按照i特征的value唯一特征值--拆分数据集
prob = len(subDataSet) / float(len(dataSet)) # 新数据集占总数据的比例
newEntroy += prob * calcShannonEnt(subDataSet)
ibaseEntroy-=prob*log(prob,2)
infoGainRatio = (baseEntropy - newEntroy)/ibaseEntroy # 按照i特征划分的信息增益比
if (infoGainRatio > bestInfoGainRatio):
bestInfoGainRatio = infoGainRatio
bestFeature = i
return bestFeature
#频率最高的类别函数 判断递归何时结束
def majorityCnt(classList): #参数classList在下面创建树的代码里,是每一行的最后一个特征
classCount = {}
for vote in classList: #将特征里的元素添加到新建的字典里作为键值,并统计该键值出现次数
if vote not in classCount.keys(): classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0] #返回出现次数最多的键值
#创建决策树
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet]
if len(set(classList)) == 1:#if 数据集中的每个子项的类别完全相同:return 该类别
return classList[0]
if len(dataSet[0]) == 0: #if 遍历完所有的特征:return 频率最高的类别
return majorityCnt(classList)
# 寻找划分数据集的最好特征 #获取当前数据集合和标签列表的最优特征维度
bestFeat = chooseBestFeatureToSplitForC45(dataSet)
# 最优特征维度对应的标签值
bestFeatLabel = labels[bestFeat]
# 创建分支节点
myTree = {bestFeatLabel: {}}
del (labels[bestFeat])
# 划分数据集
featValues = [example[bestFeat] for example in dataSet]
# print(set(featValues))
# for 每个划分的子集
for value in set(featValues):
# 调用createTree()并且增加返回结果到分支节点中
subLabels = labels[:]
subDataSet = splitDataSet(dataSet, bestFeat, value)
myTree[bestFeatLabel][value] = createTree(subDataSet, subLabels)
# return 分支节点
return myTree
dataSet, label = loadDataSet()
print(createTree(dataSet, label))
决策树生成算法递归地产生决策树,直到不能继续下去为止。这样产生的树往往对训练数据的分类很准确,但对未知的测试数据的分类却没有那么准确,即出现过拟合现象。过拟合的原因在于学习时过多地考虑如何提高对训练数据的正确分类,从而构建出过于复杂的决策树。解决这个问题的办法是考虑决策树的复杂度,对已生成的决策树进行简化。
在决策树学习中将已生成的树进行简化的过程称为剪枝(pruning)。具体地,剪枝从已生成的树上裁掉一些子树或叶结点,并将其根结点或父结点作为新的叶结点,从而简化分类树模型。
决策树的剪枝往往通过极小化决策树整体的损失函数(loss function)或代价函数(cost function)来实现。设树T的叶结点个数为T,t是树T的叶结点,该叶结点有 N t N_t Nt个样本点,其中k类的样本点有 N t k N_{tk} Ntk个,k=1,2,…,K。
叶结点t上的经验熵 H t ( T ) H_t(T) Ht(T):
H t ( T ) = − ∑ k = 1 K N t k N t l o g N t k N t H_t(T) = -\displaystyle\sum_{k=1}^{K} \frac{N_{tk}}{N_t}log\frac{N_{tk}}{N_t} Ht(T)=−k=1∑KNtNtklogNtNtk
损失函数计算的是每个叶节点的样本数和每个叶节点的经验熵的乘积的累加和,再加上以叶节点个数为基础的惩罚项,α⩾0 为正则化系数。则决策树的损失函数可以定义为:
C α ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) + α ∣ T ∣ C_α(T)=\displaystyle\sum_{t=1}^{|T|} N_tH_t(T)+α|T| Cα(T)=t=1∑∣T∣NtHt(T)+α∣T∣
将损失函数右端的第1项记作:
C ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) = − ∑ t = 1 ∣ T ∣ ∑ k = 1 K N t k l o g N t k N t C(T)=\displaystyle\sum_{t=1}^{|T|} N_tH_t(T)=-\displaystyle\sum_{t=1}^{|T|}\displaystyle\sum_{k=1}^{K} N_{tk}log\frac{N_{tk}}{N_t} C(T)=t=1∑∣T∣NtHt(T)=−t=1∑∣T∣k=1∑KNtklogNtNtk
则:
C α ( T ) = C ( T ) + α ∣ T ∣ C_α(T)=C(T)+α|T| Cα(T)=C(T)+α∣T∣
C(T)表示模型对训练数据的预测误差,即模型与训练数据的拟合程度,|T|表示模型复杂度,参数α≥0控制两者之间的影响。较大的α促使选择较简单的模型(树),较小 的a促使选择较复杂的模型(树)。α=0意味着只考虑模型与训练数据的拟合程度,不考虑模型的复杂度。
剪枝就是当α确定时,选择损失函数最小的模型,即损失函数最小的子树。当α值确定时,子树越大,往往与训练数据的拟合越好,但是模型的复杂度就越高;相反,子树越小, 模型的复杂度就越低,但是往往与训练数据的拟合不好。损失函数正好表示了对两者的平衡。
可以看出,决策树生成只考虑了通过提高信息增益(或信息增益比)对训练数据进行更好的拟合。而决策树剪枝通过优化损失函数还考虑了减小模型复杂度。决策树生成学习局部的模型,而决策树剪枝学习整体的模型。
定义的损失函数的极小化等价于正则化的极大似然估计。所以,利用损失函数最小原则进行剪枝就是用正则化的极大似然估计进行模型选择。
决策树的剪枝算法:
输入:生成算法产生的整个树T,参数α;
输出:修剪后的子树Ta。
(1)计算每个结点的经验熵。
(2)递归地从树的叶结点向上回缩。 设一组叶结点回缩到其父结点之前与之后的整体树分别为 T B T_B TB与 T A T_A TA,其对应的损失函数值分别是 C α ( T B ) C_α(T_B) Cα(TB)与 C α ( T A ) C_α(T_A) Cα(TA),如果
C α ( T A ) ≤ C α ( T B ) C_α(T_A)≤C_α(T_B) Cα(TA)≤Cα(TB)
则进行剪枝,即将父结点变为新的叶结点。
(3)返回(2),直至不能继续为止,得到损失函数最小的子树 T a T_a Ta。 注意,上式只需考虑两个树的损失函数的差,其计算可以在局部进行。所以,决策树的剪枝算法可以由一种动态规划的算法实现。
决策树剪枝方法:
首先剪枝(pruning)的目的是为了避免决策树模型的过拟合。因为决策树算法在学习的过程中为了尽可能的正确的分类训练样本,不停地对结点进行划分,因此这会导致整棵树的分支过多,也就导致了过拟合。决策树的剪枝策略最基本的有两种:预剪枝(pre-pruning)和后剪枝(post-pruning):
预剪枝(pre-pruning):预剪枝就是在构造决策树的过程中,先对每个结点在划分前进行估计,若果当前结点的划分不能带来决策树模型泛华性能的提升,则不对当前结点进行划分并且将当前结点标记为叶结点。
对比未剪枝的决策树和经过预剪枝的决策树可以看出:预剪枝使得决策树的很多分支都没有“展开”,这不仅降低了过拟合的风险,还显著减少了决策树的训练时间开销和测试时间开销。但是,另一方面,因为预剪枝是基于“贪心”的,所以,虽然当前划分不能提升泛华性能,但是基于该划分的后续划分却有可能导致性能提升,因此预剪枝决策树有可能带来欠拟合的风险。
后剪枝(post-pruning):后剪枝就是先把整颗决策树构造完毕,然后自底向上的对非叶结点进行考察,若将该结点对应的子树换为叶结点能够带来泛华性能的提升,则把该子树替换为叶结点。
对比预剪枝和后剪枝,能够发现,后剪枝决策树通常比预剪枝决策树保留了更多的分支,一般情形下,后剪枝决策树的欠拟合风险小,泛华性能往往也要优于预剪枝决策树。但后剪枝过程是在构建完全决策树之后进行的,并且要自底向上的对树中的所有非叶结点进行逐一考察,因此其训练时间开销要比未剪枝决策树和预剪枝决策树都大得多。