1实验目的:
实现c4.5算法。要求:
1.给定一个训练数据集,得到决策树并用图形显示该决策树。
2. 用学习得到的决策树对测试集进行判决或分类,得到测试集上的分类错误率。
2实验过程:
本实验采用西瓜数据集2.0
dataSet=[['青绿','蜷缩','浊响','清晰','凹陷','硬滑','是'],
['乌黑','蜷缩','沉闷','清晰','凹陷','硬滑','是'],
['乌黑','蜷缩','浊响','清晰','凹陷','硬滑','是'],
['青绿','蜷缩','沉闷','清晰','凹陷','硬滑','是'],
['浅白','蜷缩','浊响','清晰','凹陷','硬滑','是'],
['青绿','稍蜷','浊响','清晰','稍凹','软粘','是'],
['乌黑','稍蜷','浊响','稍糊','稍凹','软粘','是'],
['乌黑','稍蜷','浊响','清晰','稍凹','硬滑','是'],
['乌黑','稍蜷','沉闷','稍糊','稍凹','硬滑','否'],
['青绿','硬挺','清脆','清晰','平坦','软粘','否'],
['浅白','硬挺','清脆','模糊','平坦','硬滑','否'],
['浅白','蜷缩','浊响','模糊','平坦','软粘','否'],
['青绿','稍蜷','浊响','稍糊','凹陷','硬滑','否'],
['浅白','稍蜷','沉闷','稍糊','凹陷','硬滑','否'],
['乌黑','稍蜷','浊响','清晰','稍凹','软粘','否'],
['浅白','蜷缩','浊响','模糊','平坦','硬滑','否'],
['青绿','蜷缩','沉闷','稍糊','稍凹','硬滑','否']]
labels=['色泽','根蒂','敲声','纹理','脐部','触感']
2.C4.5算法实现
2.1定义函数:计算信息熵
# 函数说明:计算给定数据集的经验熵(香农熵)
def calcShannonEnt(dataSet):
numEntires = len(dataSet) #返回数据集的行数
labelCounts = {} #保存每个标签(Label)出现次数的字典
for featVec in dataSet: #对每组特征向量进行统计
currentLabel = featVec[-1] #提取标签(Label)信息
if currentLabel not in labelCounts.keys(): #如果标签(Label)没有放入统计次数的字典,添加进去
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1 #Label计数
shannonEnt = 0.0 #经验熵(香农熵)
for key in labelCounts: #计算香农熵
prob = float(labelCounts[key]) / numEntires#选择该标签(Label)的概率
shannonEnt -= prob * log(prob, 2) #利用公式计算
return shannonEnt #返回经验熵(香农熵)
2.2 定义函数:将数据集中某一特征所在的列去掉
def splitDataSet(dataSet, axis, value):
retDataSet = [] #创建返回的数据集列表
for featVec in dataSet: #遍历数据集
if featVec[axis] == value:
reducedFeatVec = featVec[:axis] #去掉axis特征
reducedFeatVec.extend(featVec[axis+1:])#将符合条件的添加到返回的数据集
retDataSet.append(reducedFeatVec)
return retDataSet #返回划分后的数据集
2.3定义函数:计算信息增益率,并返回当前数据集的信息增益率最大的属性
def chooseBestFeatureToSplit(dataSet,label):
numFeatures = len(label) #特征数量
baseEntropy = calcShannonEnt(dataSet) #计算数据集的香农熵
bestInfoGainRatio = 0.0 #信息增益率
bestFeature = -1 #最优特征的索引值
for i in range(numFeatures): #遍历所有特征
#获取dataSet的第i个所有特征
featList = [example[i] for example in dataSet]
uniqueVals = set(featList) #创建set集合{},元素不可重复
newEntropy = 0.0 #经验条件熵
IV = 1e-5
for value in uniqueVals: #计算信息增益
subDataSet = splitDataSet(dataSet, i, value) #subDataSet划分后的子集
prob = len(subDataSet) / float(len(dataSet)) #计算子集的概率
newEntropy += prob * calcShannonEnt(subDataSet)#根据公式计算经验条件熵
IV -= prob * log(prob,2) #计算IV
infoGain = baseEntropy - newEntropy #信息增益
Gain_ratio = infoGain/IV #增益率
# print("第%d个特征的增益为%.3f" % (i, infoGain)) #打印每个特征的信息增益
if (Gain_ratio > bestInfoGainRatio): #计算信息增益
bestInfoGainRatio = Gain_ratio #更新信息增益,找到最大的信息增益
bestFeature = i #记录信息增益最大的特征的索引值
return bestFeature #返回信息增益最大的特征的索引值
2.4定义函数:统计分类数组中类别最多的标签
def majorityCnt(classList):
classCount = {}
for vote in classList:#统计classList中每个元素出现的次数
if vote not in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)#根据字典的值降序排序
return sortedClassCount[0][0]#返回classList中出现次数最多的元素
2.5定义函数:判断所有样本在所有属性上取值是否相同
def isSame(dataSet):
temp = dataSet[0]
for data in dataSet:
i =0
while i < len(dataSet[0]):
if temp[i] != data[i]:
return 0
i = i+1
return 1
2.6定义函数:创建决策树
def createTree(dataSet,label,featLabels,G,parentId,edge_name,dataSet_orgin,label_orgin):
label2 = label[:]
nowId = G.number_of_nodes()+1 #记录递归栈中本层的节点编号
classList = [example[-1] for example in dataSet] #取分类标签(是否放贷:yes or no)
if classList.count(classList[0]) == len(classList): #如果类别完全相同则停止继续划分case1
print("nowId1==",nowId,"parentId1==",parentId)
G.add_node(nowId,label=classList[0],fontname="Microsoft YaHei", style="filled",color="#B4E7B7")
print("edge_name1==",edge_name)
G.add_edge(parentId,nowId,color="#B4DBFF", penwidth=1.5, fontsize=12,fontname="Microsoft YaHei", label=edge_name)
return classList[0]
if (len(label) == 0 or isSame(dataSet)): #case2:当前属性集为空或者所有样本在所有属性上取值相同,返回出现次数最多的类标签
maxLabel = majorityCnt(classList)
G.add_node(nowId,label=maxLabel,fontname="Microsoft YaHei", style="filled",color="#dddddd")
G.add_edge(parentId,nowId,color="#B4DBFF", penwidth=1.5, fontsize=12,fontname="Microsoft YaHei", label=edge_name)
return maxLabel
bestFeat = chooseBestFeatureToSplit(dataSet,label) #选择最优特征
print('bestFeat==',bestFeat,'labels==',labels)
bestFeatLabel = label[bestFeat] #最优特征的标签
#先把这个最优特征的点加到图中
print("nowId2==",nowId,"parentId2==",parentId)
G.add_node(nowId,label=bestFeatLabel,fontname="Microsoft YaHei", style="filled",color="#1296db")
if parentId != -1:
print('edgename2==',edge_name)
print("nowId3==",nowId,"parentId3==",parentId)
G.add_edge(parentId,nowId,color="#B4DBFF", penwidth=1.5, fontsize=12,fontname="Microsoft YaHei", label=edge_name)
print("bestFeatLabel",bestFeatLabel)
featLabels.append(bestFeatLabel)
myTree = {bestFeatLabel:{}} #根据最优特征的标签生成树
del(label2[bestFeat]) #删除已经使用特征标签
orginIndex = label_orgin.index(bestFeatLabel) #输出最优特征在原始数据集的位置
featValues = [example[orginIndex] for example in dataSet_orgin]#得到训练集中所有最优特征的属性值
uniqueVals = set(featValues) #去掉重复的属性值
print("uniqueVals",uniqueVals)
fatherDataSet = []
for value in uniqueVals: #遍历特征,创建决策树。
newDataSet = splitDataSet(dataSet, bestFeat, value)
if newDataSet:
for item in newDataSet:
fatherDataSet.append(item)
for value in uniqueVals: #遍历特征,创建决策树。
print("value==",value)
newDataSet = splitDataSet(dataSet, bestFeat, value)
if newDataSet:
#print('newDataSet==',newDataSet)
myTree[bestFeatLabel][value] = createTree(newDataSet,label2, featLabels,G,nowId,value,dataSet_orgin,label_orgin)
else:
#print('fatherDataSet==',fatherDataSet)
classList = [example[-1] for example in fatherDataSet]
mlabel = majorityCnt(classList)
print("mlabel==",mlabel)
nextId = G.number_of_nodes()+1
G.add_node(nextId,label=mlabel,fontname="Microsoft YaHei", style="filled",color="#B4E7B7")
G.add_edge(nowId,nextId,color="#B4DBFF", penwidth=1.5, fontsize=12,fontname="Microsoft YaHei", label=value)
return myTree
2.7定义函数:测试模型
def judge(node,data,labels):
key=list(node.keys())[0]
node = node[key]
idx = labels.index(key)
pred = None
for key in node:
if data[idx] == key:
if isinstance(node[key],dict):
pred = judge(node[key],data,labels)
else:
pred = node[key]
return pred
2.8划分数据集训练模型
G = pgh.AGraph()
#划分数据集
np.random.shuffle(readyArr)
offset = int(len(readyArr)*0.9)
trainData = readyArr[:offset]
testData = readyArr[offset:]
featLabels = []
Trees = createTree(trainData,labels,featLabels,G,-1,'0',trainData,labels)
print(Trees)
G.layout()
G.draw(r'C:\Users\admin\OneDrive\anaconda\c45test.dot')
2.9测试模型,并计算错误率
errSum =0
for data in testData:
test_result = judge(Trees,data,labels)
if test_result != data[-1]:
errSum = errSum +1
totalNum = len(testData)
print("出错率为",float(errSum/totalNum))
3实验总结:
- 在决策树生成过程中, 由于为了方便计算决策树分支的最优属性,所以会删除已经作为过最优属性的属性,当某个属性被用到后直接删除后,在后面再使用到该属性时会出现错误。因此,需要改正算法,思路是在递归时,只有在进入分类属性X下一层递归时才会删除当前分类属性X,当前分类属性X平行的节点则不会删除分类属性X.
- 分类到某分支时,会出现某属性A的某个取值v没有样本数据的情况,需要将该属性A当前样本的最多的分类作为v的分类。
- 另外,也采用sklearn进行训练,对比C4.5算法结果,发现不一样。通过阅读源码,得知sklearn 里面的决策树采用的是基尼指数,而C4.5决策树采用的是信息增益率来选择最优分类属性。