2022年 11月 5日

决策树之ID3算法(python)

一棵决策树的生成过程主要分为以下3个部分:

1、特征选择

2、决策树生成

3、剪枝


在讲解特征选择前,我们先了解一些概念。

不纯度(impurity)–GINI系数

 不纯度(impurity)–Entropy熵

  不纯度(impurity)–分类误差:delta = 1 – max[ p( t ) ]

 信息增益

 

 第一步:切分特征

 选择具有最高信息增益的特征作为测试特征,利用该特征对节点样本进行划分子集,会使得各子集中不同类别样本的混合程度最低,在各子集中对样本划分所需的信息(熵)最少

第二步:决策树的生成

  • 从根节点出发,根节点包括所有的训练样本。
  • 一个节点(包括根节点),若节点内所有样本均属于同一类别,那么将该节点就成为叶节点,并将该节点标记为样本个数最多的类别。
  • 否则利用采用信息增益法来选择用于对样本进行划分的特征,该特征即为测试特征,特征的每一个值都对应着从该节点产生的一个分支及被划分的一个子集。
  • 递归上述划分子集及产生叶节点的过程,这样每一个子集都会产生一个决策(子)树,直到所有节点变成叶节点。
     
  • 递归操作的停止条件就是: 

(1)一个节点中所有的样本均为同一类别,那么产生叶节点 
(2)没有特征可以用来对该节点样本进行划分,这里用attribute_list=null为表示。此时也强制产生叶节点,该节点的类别为样本个数最多的类别 
(3)没有样本能满足剩余特征的取值,即test_attribute= 对应的样本为空。此时也强制产生叶节点,该节点的类别为样本个数最多的类别

第三步:决策树剪枝

python代码实现:

  1. import xlrd #读取Excel
  2. from math import log #运用数学基本公式
  3. import operator #中间排序用到
  4. def calcShannonEnt(dataSet): # 计算数据的熵(entropy)
  5. numEntries=len(dataSet) # 数据条数
  6. labelCounts={}
  7. for featVec in dataSet:
  8. currentLabel=featVec[-1] # 每行数据的最后一个字(类别)
  9. if currentLabel not in labelCounts.keys():
  10. labelCounts[currentLabel]=0
  11. labelCounts[currentLabel]+=1 # 统计有多少个类以及每个类的数量
  12. shannonEnt=0 #加权熵
  13. for key in labelCounts:
  14. prob=float(labelCounts[key])/numEntries # 计算单个类的熵值
  15. shannonEnt-=prob*log(prob,2) # 累加每个类的熵值
  16. return shannonEnt
  17. def createDataSet1(path): # 从Excel取数据
  18. data = xlrd.open_workbook(path)
  19. sheet = data.sheet_by_index(0)
  20. dataSet = []
  21. labels = sheet.row_values(0)[1:] #这个可自定义,取得到特征数据就好
  22. #如两个特征['天气','湿度']
  23. for i in range(1, sheet.nrows):
  24. dataSet.extend([sheet.row_values(i)[1:]])
  25. #这个可自定义,但要返回一个二维矩阵,最后一列是标签
  26. '''
  27. 如dataSet = [['晴朗', '干燥', '是'],
  28. ['雨天', '潮湿', '否']]
  29. '''
  30. return dataSet,labels
  31. def splitDataSet(dataSet,axis,value): # 按某个特征分类后的数据
  32. retDataSet=[]
  33. for featVec in dataSet:
  34. if featVec[axis]==value:
  35. reducedFeatVec =featVec[:axis]
  36. reducedFeatVec.extend(featVec[axis+1:])
  37. retDataSet.append(reducedFeatVec)
  38. return retDataSet
  39. def chooseBestFeatureToSplit(dataSet): # 选择最优的分类特征
  40. numFeatures = len(dataSet[0])-1
  41. baseEntropy = calcShannonEnt(dataSet) # 原始的熵
  42. bestInfoGain = 0
  43. bestFeature = -1
  44. for i in range(numFeatures):
  45. featList = [example[i] for example in dataSet]
  46. uniqueVals = set(featList)
  47. newEntropy = 0
  48. for value in uniqueVals:
  49. subDataSet = splitDataSet(dataSet,i,value)
  50. prob =len(subDataSet)/float(len(dataSet))
  51. newEntropy +=prob*calcShannonEnt(subDataSet) # 按特征分类后的熵
  52. infoGain = baseEntropy - newEntropy # 原始熵与按特征分类后的熵的差值
  53. if (infoGain>bestInfoGain): # 若按某特征划分后,熵值减少的最大,则次特征为最优分类特征
  54. bestInfoGain=infoGain
  55. bestFeature = i
  56. return bestFeature
  57. def majorityCnt(classList): #按分类后类别数量排序,比如:最后分类为2男1女,则判定为男;
  58. classCount={}
  59. for vote in classList:
  60. if vote not in classCount.keys():
  61. classCount[vote]=0
  62. classCount[vote]+=1
  63. sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
  64. return sortedClassCount[0][0]
  65. def createTree(dataSet,labels):
  66. classList=[example[-1] for example in dataSet] # 类别:男或女
  67. if classList.count(classList[0])==len(classList):
  68. return classList[0]
  69. if len(dataSet[0])==1:
  70. return majorityCnt(classList)
  71. bestFeat=chooseBestFeatureToSplit(dataSet) #选择最优特征
  72. bestFeatLabel=labels[bestFeat]
  73. myTree={bestFeatLabel:{}} #分类结果以字典形式保存
  74. del(labels[bestFeat])
  75. featValues=[example[bestFeat] for example in dataSet]
  76. uniqueVals=set(featValues)
  77. for value in uniqueVals:
  78. subLabels=labels[:]
  79. myTree[bestFeatLabel][value]=createTree(splitDataSet\
  80. (dataSet,bestFeat,value),subLabels)
  81. return myTree
  82. if __name__=='__main__':
  83. path = 'data.xls'
  84. dataSet, labels=createDataSet1(path=path) # 创造示列数据
  85. print(createTree(dataSet, labels)) # 输出决策树模型结果

一、xlrd库部分操作解释:

  1. 1、导入模块
  2. import xlrd
  3. 2、打开Excel文件读取数据
  4. data = xlrd.open_workbook('excel.xls')
  5. 3、获取一个工作表
  6. ① table = data.sheets()[0] 通过索引顺序获取
  7. ② table = data.sheet_by_index(0) 通过索引顺序获取
  8. ③ table = data.sheet_by_name(u'Sheet1') 通过名称获取
  9. 4、获取整行和整列的值(返回数组)
  10. table.row_values(i)
  11. table.col_values(i)
  12. 5、获取行数和列数
  13. table.nrows
  14. table.ncols
  15. 6、获取单元格
  16. table.cell(0,0).value
  17. table.cell(2,3).value

二、operator库部分操作解释

  1. d={'a':1,'c':3,'b':2} 首先建一个字典d
  2. d.items()返回的是: dict_items([('a', 1), ('c', 3), ('b', 2)])
  3. d_order=sorted(d.items(),key=lambda x:x[1],reverse=False)
  4. 按字典集合中,每一个元组的第二个元素排列。
  5. x相当于字典集合中遍历出来的一个元组。
  6. print(d_order)
  7. 得到: [('a', 1), ('b', 2), ('c', 3)]
  8. '''
  9. 作用:从可迭代对象中,依次取出一个元素,该元素再按照key规定的排列依据排序。
  10. 可迭代对象:即可依次取值的对象,例如:集合,序列(列表,字符串,元组),字典等。
  11. key : 是列表排列的依据,一般可以自定义一个函数返回排序的依据,再把函数名绑定给key。
  12. reverse : 译为反转,reverse默认等于False,从小到大排序。等于True时,从大到小排序。
  13. '''
  14. '''
  15. 同理代码段的语句:
  16. sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
  17. operator.itemgetter(1)
  18. 即操作 sortedClassCount[0][0]
  19. '''