机器学习(四):C4.5决策树(基础篇)
相关的决策树文章:
问题一:为什么要使用C4.5决策树?
前面我们介绍了ID3决策树,ID3决策树有一个很大的缺点:信息增益反映了给定一个条件下以后不确定减少的程度,必然是分得越细的数据集确定性越高,也就是条件熵越小,信息增益越大。但是这样下来只能处理离散型属性,并且倾向于选择取值较多的属性。而C4.5采用的是信息增益率来作为分支的准则。很好的解决了这一缺点。在平时运用上,C4.5要多于ID3.
问题二:C4.5如何进行分支?
信息增益率是C4.5决策树进行分支的一个很重要的指标。在之前的学习中,我们了解了香农熵,和信息增益,那么什么是信息增益率?
信息增益的定义:
C a i n r a t i o ( D , a ) = G a i n ( D , a ) I V ( a ) Cain_{ratio}(D,a) = \frac{Gain(D,a)}{IV(a)} Cainratio(D,a)=IV(a)Gain(D,a)
其中:
I V ( a ) = − ∑ v = 1 V ∣ D v ∣ ∣ D ∣ l o g 2 ∣ D v ∣ ∣ D ∣ IV(a)=-\displaystyle\sum_{v=1}^{V}\frac{|D^v|}{|D|}log_2\frac{|D^v|}{|D|} IV(a)=−v=1∑V∣D∣∣Dv∣log2∣D∣∣Dv∣
分子很简单,分子为信息增益(可参考ID3决策树(基础篇))
分母为属性a的熵,成为属性a的”固有值“。属性a的可能取值数目越多(即V越大),则IV(a)的值通常会越大。
需要注意的是:增益率准则对可取值数目较少的属性有偏好,因此,C4.5算法不是直接选择信息增益率最大的候选划分属性,而是使用了一个启发式:先从候选划分属性中找出信息增益率高于平均水平的属性,再从中选择增益率最高的。
代码实现
C4.5决策树的代码与ID3的代码实现差别不大:只需要更改部分函数即可:
# -*- coding: utf-8 -*-
"""
决策树C4.5的实现
"""
from math import log
import operator
import pandas as pd
import plotTrees
def majorityCnt(classList):
"""
找到最大频繁向量(多数表决器)
:param classList:训练集
:return:最大值
"""
classCounts = {}
for value in classList: #遍历训练集中的每一个变量
if (value not in classCounts.keys()): #如果变量不在列表中
classCounts[value] = 0 #新建一个字典的键
classCounts[value] += 1 #数量加一
sortedClassCount = sorted(classCounts.items(), key=operator.itemgetter(1), reverse=True) #排序
return sortedClassCount[0][0] #输出第一个,即最大值
def splitDataSet(dataSet, axis, value):
"""
以靠指定列的指定值来划分数据集,比如划分西瓜瓜皮形状为椭圆的数据集
:param axis: 索引列,即形状
:param value: 索引列的特定值,即椭圆
:return:
"""
retDataSet = []
for festdataVal in dataSet:
if festdataVal[axis] == value:
reducedFeatVal = festdataVal[:axis] #这两行去掉索引列
reducedFeatVal.extend(festdataVal[axis+1:])
retDataSet.append(reducedFeatVal)
return retDataSet
def calcShannonEnt(columnIndex, dataSet):
"""
计算香农熵
:param dataSet:
:return:
"""
numEntries = len(dataSet) #获得数据集的长度
labelCounts = {} #计算标签的字典
for featDataVal in dataSet:
currentLabels = featDataVal[columnIndex] #取最后一个标签值
if currentLabels not in labelCounts.keys(): #判断有没有在标签里面
labelCounts[currentLabels] = 0
labelCounts[currentLabels] += 1
shannonEnt = 0.0
for key in labelCounts.keys(): #key有几个遍历几次
prob = labelCounts[key]/float(numEntries) #计算频率
shannonEnt -= prob*log(prob, 2)
return shannonEnt
def chooseBestFeatureToSplitOfFurther(dataSet):
"""
选择信息增益率最大的特征值
:param dataSet:数据集
:return:
"""
numFeatures = len(dataSet[0]) - 1 # 看数据集而定,数据中如果最后一行为标签,则删去
baseEntropy = calcShannonEnt(-1, dataSet) # 计算所有数据集的香农熵
bestFeaturesindex = 0 # 最佳特征的索引
bestInfoGainRatio = 0.0 # 最佳信息熵
for i in range(numFeatures): # 有几个特征值循环几次
featEntropy = calcShannonEnt(i, dataSet)
featList = [] # 特征值列表
for example in dataSet: # 获得这个列的值
featList.append(example[i])
uniqueVals = set(featList) # 相同的数据并没有意义,去重
newEntropy = 0.0 # 新信息熵
for value in uniqueVals: # 得到该列的特征值
subDataSet = splitDataSet(dataSet, i, value) # 划分数据集
prob = len(subDataSet) / float(len(dataSet)) # 权重或者条件概率
newEntropy += prob * calcShannonEnt(-1, subDataSet) # 计算信息增益后面的条件经验熵
infoGain = baseEntropy - newEntropy # 计算信息增益
if featEntropy == 0.0 :
infoGainRatio = 0.0
else:
infoGainRatio = infoGain/float(featEntropy)
if infoGainRatio > bestInfoGainRatio: # 更改最大经验熵
bestInfoGainRatio = infoGainRatio
bestFeaturesindex = i
return bestFeaturesindex # 输出最大经验熵的索引
def diferFeature(dataSet, label):
numFeatures = len(dataSet[0]) - 1 # 看数据集而定,数据中如果最后一行为标签,则删去
baseEntropy = calcShannonEnt(-1, dataSet) # 计算所有数据集的香农熵
sumEntropy = 0.0
featureEntropy = []
dellabel = []
retDataSet = dataSet
label1 = label.copy()
for i in range(numFeatures): # 有几个特征值循环几次
featList = [] # 特征值列表
for example in dataSet: # 获得这个列的值
featList.append(example[i])
uniqueVals = set(featList) # 相同的数据并没有意义,去重
newEntropy = 0.0 # 新信息熵
for value in uniqueVals: # 得到该列的特征值
subDataSet = splitDataSet(dataSet, i, value) # 划分数据集
prob = len(subDataSet) / float(len(dataSet)) # 权重或者条件概率
newEntropy += prob * calcShannonEnt(-1, subDataSet) # 计算信息增益后面的条件经验熵
infoGain = baseEntropy - newEntropy # 计算信息增益
featureEntropy.append(infoGain)
sumEntropy += infoGain
averageEntropy = sumEntropy/numFeatures
for i in range(numFeatures):
if featureEntropy[i]<averageEntropy:
dellabel.append(labels[i])
label.append('jieguo')
for i in range(len(dellabel)):
label1.remove(dellabel[i])
retDataSet = pd.DataFrame(retDataSet, columns=label)
retDataSet = retDataSet.drop(dellabel, axis=1)
retDataSet = retDataSet.values.tolist()
return retDataSet, label1
def createTree(dataSet, label):
"""
创建树
:param dataSet:
:param label:
:return:
"""
classList = [] #获得每一个标签
for classVal in dataSet:
classList.append(classVal[-1])
if classList.count(classList[0]) == len(classList): #如果全部标签都相同
return classList[0] #返回该标签
if len(dataSet[0]) == 1: #如果一列只有一个特征
return majorityCnt(classList)
#dataSet, label = self.diferFeature(dataSet, label)
#获取最优的索引值
bestFeatureIndex = chooseBestFeatureToSplitOfFurther(dataSet)
#获取最优索引值的名称
bestFeatureLabel = label[bestFeatureIndex]
mytree = {bestFeatureLabel:{}} #创建根节点
del(label[bestFeatureIndex]) #删去用过的最优节点
bestFeature = [] #最优的特征
for example in dataSet:
bestFeature.append(example[bestFeatureIndex])
uniquesVal = set(bestFeature) #最优特征的种类
for val in uniquesVal:
subLabel = label[:] #创建个子标签
mytree[bestFeatureLabel][val] = createTree(splitDataSet(dataSet, bestFeatureIndex, val), subLabel) #递归
return mytree
def classify( inputTree, featLable, testVec):
"""
获取分类的结果(算法的使用器)
:param inputTree:决策树字典
:param featLable: 标签列表
:param testVec: 测试向量
:return:
"""
#获取根节点的名称,并且把根节点变成列表
firstSide = list(inputTree.keys())
#根节点名称string类型
firstStr = firstSide[0]
#获得根节点对应得子节点
secondDict = inputTree[firstStr]
#获得子节点在标签列表得索引
featIndex = featLable.index(firstStr)
#获得测试向量的值
key = testVec[featIndex]
#获取树干向量后的变量
valueOfFeat = secondDict[key]
#判断是子结点还是叶子节点:子结点就回调分类函数,叶子结点就是分类结果
if isinstance(valueOfFeat, dict):
classLabel = classify(valueOfFeat, featLable, testVec)
else:
classLabel = valueOfFeat
return classLabel
def storeTree(inputTree, filename):
#写入文件
import pickle
fw = open(filename, 'wb+')
pickle.dump(inputTree,fw)
fw.close()
def grabTree(filename):
#读取数
import pickle
fr = open(filename, 'rb')
return pickle.load(fr)
if __name__ == "__main__":
#因为数据集寻找比较复杂,所以采用自己随机建立数组
dataSet = [[1, 1, 3, 4, 2, 3, 'yes'],
[0, 1, 3, 3, 6, 3, 'yes'],
[1, 0, 4, 3, 6, 4, 'no'],
[0, 1, 2, 4, 2, 3, 'no'],
[0, 0, 3, 3, 2, 4, 'no']]
labels = ['no surfacing', 'flippers', 'table', 'type', '1', '2']
#复制
label1 = labels.copy()
#创建树
mytree = createTree(dataSet, label1)
print(mytree)
#使用树的操作
a = classify(mytree, labels, [0,1, 3, 4])
print(a)
plotTrees.createPlot(mytree)
我们来看看结果:
可以很好的看出,生成了我么想要的决策树。以及测试结果出现的答案。