决策树decision tree
1.决策树是一种监督学习方法,既可以处理分类问题,也可以处理回归问题,甚至包括多输出(multioutput)任务.
2.熵泛指某些物质系统状态的一种量度,某些物质系统状态可能出现的程度。在信息论与概率统计中,熵(entropy)是表示随机变量不确定性的变量。熵越大随机变量的不确定性越强,那么从特征重要度的角度来说,该特征越不具备较强的表征分裂能力。 信息越有序,信息熵越低。
3.决策树的众多特性之一就是, 它不需要太多的数据预处理, 尤其是不需要进行特征的缩放或者归一化。
4.决策树模型呈树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是 if-then 规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。
5.决策树学习通常包括 3 个步骤:特征选择、决策树的生成和决策树的修剪。
6.内部结点表示一个特征或属性(features),叶结点表示一个类(labels)。
7.信息增益(information gain):在划分数据集前后信息发生的变化称为信息增益。
-
缺点:容易过拟合。
8.为了防止“过拟合”造成较差的泛化能力,常用策略就是使用“剪枝“进行去噪,进而学习更加朴质的特征,学到数据的本质。
9.这种现象就是“过拟合”,本质原因在于学习器在学习的过程中把训练特征中的一个非朴素特质当做了潜在的真实分布造成的。
10.预剪枝:在树生成的阶段进行剪枝,后剪枝:在树生成后,在检查需要去掉哪些分支。
11.ID3算法起源于概念学习系统(CLS),以信息熵的下降速度为选取测试属性的标准,即在每个节点选取还尚未被用来划分的具有最高信息增益的属性作为划分标准,然后继续这个过程,直到生成的决策树能完美分类训练样例。
12.2.4.2 ID3算法优缺点总结
-
模型结构:N叉树,如果特征的取值较多,计算量非常可观。
-
模型目标:适合处理分类问题。
-
特征相关:没有给出连续型特征的处理方法,适合处理离散型特征,对于缺失的特征没有需要在预处理阶段自行解决缺失值问题。
-
模型优化:没有模型的剪纸优化操作,容易发生过拟合。
-
内存消耗:随着样本的特征空间维度迅速膨胀。
-
计算效率:N叉树,随着样本的特征空间进行分支,计算耗时较高。
1 import operator 2 from math import log 3 import decisionTreePlot as dtPlot 4 from collections import Counter 5 6 7 def createDataSet(): 8 """ 9 Desc: 10 创建数据集 11 Args: 12 无需传入参数 13 Returns: 14 返回数据集和对应的label标签 15 """ 16 # dataSet 前两列是特征,最后一列对应的是每条数据对应的分类标签 17 dataSet = [[1, 1, 'yes'], 18 [1, 1, 'yes'], 19 [1, 0, 'no'], 20 [0, 1, 'no'], 21 [0, 1, 'no']] 22 # dataSet = [['yes'], 23 # ['yes'], 24 # ['no'], 25 # ['no'], 26 # ['no']] 27 # labels 露出水面 脚蹼,注意: 这里的labels是写的 dataSet 中特征的含义,并不是对应的分类标签或者说目标变量 28 labels = ['no surfacing', 'flippers'] 29 # 返回 30 return dataSet, labels 31 32 33 def calcShannonEnt(dataSet): 34 """ 35 Desc: 36 calculate Shannon entropy -- 计算给定数据集的香农熵 37 Args: 38 dataSet -- 数据集 39 Returns: 40 shannonEnt -- 返回 每一组 feature 下的某个分类下,香农熵的信息期望 41 """ 42 # -----------计算香农熵的第一种实现方式start-------------------------------------------------------------------------------- 43 # 求list的长度,表示计算参与训练的数据量 44 numEntries = len(dataSet) 45 # 下面输出我们测试的数据集的一些信息 46 # 例如: <type 'list'> numEntries: 5 是下面的代码的输出 47 # print(type(dataSet), 'numEntries: ', numEntries) 48 49 # 计算分类标签label出现的次数 50 labelCounts = {} 51 # the the number of unique elements and their occurance 52 for featVec in dataSet: 53 # 将当前实例的标签存储,即每一行数据的最后一个数据代表的是标签 54 currentLabel = featVec[-1] 55 # 为所有可能的分类创建字典,如果当前的键值不存在,则扩展字典并将当前键值加入字典。每个键值都记录了当前类别出现的次数。 56 if currentLabel not in labelCounts.keys(): 57 labelCounts[currentLabel] = 0 58 labelCounts[currentLabel] += 1 59 # print('-----', featVec, labelCounts) 60 61 # 对于label标签的占比,求出label标签的香农熵 62 shannonEnt = 0.0 63 for key in labelCounts.keys(): 64 # 使用所有类标签的发生频率计算类别出现的概率。 65 prob = float(labelCounts[key])/numEntries 66 # log base 2 67 # 计算香农熵,以 2 为底求对数 68 shannonEnt -= prob * log(prob, 2) 69 # print('---', prob, prob * log(prob, 2), shannonEnt) 70 # -----------计算香农熵的第一种实现方式end-------------------------------------------------------------------------------- 71 72 # # -----------计算香农熵的第二种实现方式start-------------------------------------------------------------------------------- 73 # # 统计标签出现的次数 74 # label_count = Counter(data[-1] for data in dataSet) 75 # # 计算概率 76 # probs = [p[1] / len(dataSet) for p in label_count.items()] 77 # # 计算香农熵 78 # shannonEnt = sum([-p * log(p, 2) for p in probs]) 79 # # -----------计算香农熵的第二种实现方式end-------------------------------------------------------------------------------- 80 return shannonEnt 81 82 83 def splitDataSet(dataSet, index, value): 84 """ 85 Desc: 86 划分数据集 87 splitDataSet(通过遍历dataSet数据集,求出index对应的colnum列的值为value的行) 88 就是依据index列进行分类,如果index列的数据等于 value的时候,就要将 index 划分到我们创建的新的数据集中 89 Args: 90 dataSet -- 数据集 待划分的数据集 91 index -- 表示每一行的index列 划分数据集的特征 92 value -- 表示index列对应的value值 需要返回的特征的值。 93 Returns: 94 index 列为 value 的数据集【该数据集需要排除index列】 95 """ 96 # -----------切分数据集的第一种方式 start------------------------------------ 97 retDataSet = [] 98 for featVec in dataSet: 99 # index列为value的数据集【该数据集需要排除index列】 100 # 判断index列的值是否为value 101 if featVec[index] == value: #一行一行读 102 # chop out index used for splitting 103 # [:index]表示前index行,即若 index 为2,就是取 featVec 的前 index 行 104 reducedFeatVec = featVec[:index] 105 ''' 106 请百度查询一下: extend和append的区别 107 list.append(object) 向列表中添加一个对象object 108 list.extend(sequence) 把一个序列seq的内容添加到列表中 109 1、使用append的时候,是将new_media看作一个对象,整体打包添加到music_media对象中。 110 2、使用extend的时候,是将new_media看作一个序列,将这个序列和music_media序列合并,并放在其后面。 111 result = [] 112 result.extend([1,2,3]) 113 print(result) 114 result.append([4,5,6]) 115 print(result) 116 result.extend([7,8,9]) 117 print(result) 118 结果: 119 [1, 2, 3] 120 [1, 2, 3, [4, 5, 6]] 121 [1, 2, 3, [4, 5, 6], 7, 8, 9] 122 ''' 123 reducedFeatVec.extend(featVec[index+1:]) #一个样本数据 124 # [index+1:]表示从跳过 index 的 index+1行,取接下来的数据 125 # 收集结果值 index列为value的行【该行需要排除index列】 126 retDataSet.append(reducedFeatVec) #子数据集 127 # -----------切分数据集的第一种方式 end------------------------------------ 128 129 # # -----------切分数据集的第二种方式 start------------------------------------ 130 # retDataSet = [data[:index] + data[index + 1:] for data in dataSet for i, v in enumerate(data) if i == index and v == value] 131 # # -----------切分数据集的第二种方式 end------------------------------------ 132 return retDataSet 133 134 135 def chooseBestFeatureToSplit(dataSet): 136 """ 137 Desc: 138 选择切分数据集的最佳特征 139 Args: 140 dataSet -- 需要切分的数据集 141 Returns: 142 bestFeature -- 切分数据集的最优的特征列 143 """ 144 145 # -----------选择最优特征的第一种方式 start------------------------------------ 146 # 求第一行有多少列的 Feature, 最后一列是label列嘛 147 numFeatures = len(dataSet[0]) - 1 148 # label的信息熵 149 baseEntropy = calcShannonEnt(dataSet) 150 # 最优的信息增益值, 和最优的Featurn编号 151 bestInfoGain, bestFeature = 0.0, -1 152 # iterate over all the features 153 for i in range(numFeatures): 154 # create a list of all the examples of this feature 155 # 获取每一个实例的第i+1个feature,组成list集合 156 featList = [example[i] for example in dataSet] #[example[0] for example in dataSet]:[1, 1, 1, 0, 0] 157 # get a set of unique values 158 # 获取剔重后的集合,使用set对list数据进行去重 159 uniqueVals = set(featList) 160 # 创建一个临时的信息熵 161 newEntropy = 0.0 162 # 遍历某一列的value集合,计算该列的信息熵 163 # 遍历当前特征中的所有唯一属性值,对每个唯一属性值划分一次数据集,计算数据集的新熵值,并对所有唯一特征值得到的熵求和。 164 for value in uniqueVals: 165 subDataSet = splitDataSet(dataSet, i, value) 166 prob = len(subDataSet)/float(len(dataSet)) 167 newEntropy += prob * calcShannonEnt(subDataSet) 168 # gain[信息增益]: 划分数据集前后的信息变化, 获取信息熵最大的值 169 # 信息增益是熵的减少或者是数据无序度的减少。最后,比较所有特征中的信息增益,返回最好特征划分的索引值。 170 infoGain = baseEntropy - newEntropy 171 print('infoGain=', infoGain, 'bestFeature=', i, baseEntropy, newEntropy) 172 if (infoGain > bestInfoGain): 173 bestInfoGain = infoGain 174 bestFeature = i 175 return bestFeature 176 # -----------选择最优特征的第一种方式 end------------------------------------ 177 178 # # -----------选择最优特征的第二种方式 start------------------------------------ 179 # # 计算初始香农熵 180 # base_entropy = calcShannonEnt(dataSet) 181 # best_info_gain = 0 182 # best_feature = -1 183 # # 遍历每一个特征 184 # for i in range(len(dataSet[0]) - 1): 185 # # 对当前特征进行统计 186 # feature_count = Counter([data[i] for data in dataSet]) 187 # # 计算分割后的香农熵 188 # new_entropy = sum(feature[1] / float(len(dataSet)) * calcShannonEnt(splitDataSet(dataSet, i, feature[0])) \ 189 # for feature in feature_count.items()) 190 # # 更新值 191 # info_gain = base_entropy - new_entropy 192 # print('No. {0} feature info gain is {1:.3f}'.format(i, info_gain)) 193 # if info_gain > best_info_gain: 194 # best_info_gain = info_gain 195 # best_feature = i 196 # return best_feature 197 # # -----------选择最优特征的第二种方式 end------------------------------------ 198 199 200 def majorityCnt(classList): 201 """ 202 Desc: 203 选择出现次数最多的一个结果 204 Args: 205 classList label列的集合 206 Returns: 207 bestFeature 最优的特征列 208 """ 209 # -----------majorityCnt的第一种方式 start------------------------------------ 210 classCount = {} 211 for vote in classList: 212 if vote not in classCount.keys(): 213 classCount[vote] = 0 214 classCount[vote] += 1 215 # 倒叙排列classCount得到一个字典集合,然后取出第一个就是结果(yes/no),即出现次数最多的结果 216 sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) 217 print('sortedClassCount:', sortedClassCount) 218 return sortedClassCount[0][0] 219 # -----------majorityCnt的第一种方式 end------------------------------------ 220 221 # # -----------majorityCnt的第二种方式 start------------------------------------ 222 # major_label = Counter(classList).most_common(1)[0] 223 # return major_label 224 # # -----------majorityCnt的第二种方式 end------------------------------------ 225 226 227 def createTree(dataSet, labels): 228 """ 229 Desc: 230 创建决策树 231 Args: 232 dataSet -- 要创建决策树的训练数据集 233 labels -- 训练数据集中特征对应的含义的labels,不是目标变量 234 Returns: 235 myTree -- 创建完成的决策树 236 """ 237 classList = [example[-1] for example in dataSet] 238 # 如果数据集的最后一列的第一个值出现的次数=整个集合的数量,也就说只有一个类别,就只直接返回结果就行 239 # 第一个停止条件: 所有的类标签完全相同,则直接返回该类标签。 240 # count() 函数是统计括号中的值在list中出现的次数 241 if classList.count(classList[0]) == len(classList): 242 return classList[0] 243 # 如果数据集只有1列,那么最初出现label次数最多的一类,作为结果 244 # 第二个停止条件: 使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组。 245 if len(dataSet[0]) == 1: 246 return majorityCnt(classList) 247 248 # 选择最优的列,得到最优列对应的label含义 249 bestFeat = chooseBestFeatureToSplit(dataSet) 250 # 获取label的名称 251 bestFeatLabel = labels[bestFeat] 252 # 初始化myTree 253 myTree = {bestFeatLabel: {}} 254 # 注: labels列表是可变对象,在PYTHON函数中作为参数时传址引用,能够被全局修改 255 # 所以这行代码导致函数外的同名变量被删除了元素,造成例句无法执行,提示'no surfacing' is not in list 256 del(labels[bestFeat]) 257 # 取出最优列,然后它的branch做分类 258 featValues = [example[bestFeat] for example in dataSet] 259 uniqueVals = set(featValues) 260 for value in uniqueVals: 261 # 求出剩余的标签label 262 subLabels = labels[:] 263 # 遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数createTree() 264 myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels) 265 print('myTree',bestFeat, value, myTree) 266 return myTree 267 268 269 def classify(inputTree, featLabels, testVec): 270 """ 271 Desc: 272 对新数据进行分类 273 Args: 274 inputTree -- 已经训练好的决策树模型 myTree 0 1 {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}} 275 featLabels -- Feature标签对应的名称,不是目标变量 276 testVec -- 测试输入的数据 277 Returns: 278 classLabel -- 分类的结果值,需要映射label才能知道名称 279 """ 280 # 获取tree的根节点对于的key值 281 firstStr = list(inputTree.keys())[0] #0 282 # 通过key得到根节点对应的value 283 secondDict = inputTree[firstStr] #{0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}} 284 # 判断根节点名称获取根节点在label中的先后顺序,这样就知道输入的testVec怎么开始对照树来做分类 285 featIndex = featLabels.index(firstStr) #0 286 # 测试数据,找到根节点对应的label位置,也就知道从输入的数据的第几位来开始分类 287 key = testVec[featIndex] #1 288 valueOfFeat = secondDict[key] 289 print('featIndex',featIndex,'key',key) #{'flippers': {0: 'no', 1: 'yes'}} 290 print('+++', firstStr, 'xxx', secondDict, '---', key, '>>>', valueOfFeat) 291 292 # 判断分枝是否结束: 判断valueOfFeat是否是dict类型 293 if isinstance(valueOfFeat, dict): 294 classLabel = classify(valueOfFeat, featLabels, testVec) 295 else: 296 classLabel = valueOfFeat 297 return classLabel 298 299 """ 300 featIndex 0 key 1 301 +++ no surfacing xxx {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}} --- 1 >>> {'flippers': {0: 'no', 1: 'yes'}} 302 featIndex 1 key 1 303 +++ flippers xxx {0: 'no', 1: 'yes'} --- 1 >>> yes 304 yes 305 """ 306 307 def storeTree(inputTree, filename): 308 """ 309 Desc: 310 将之前训练好的决策树模型存储起来,使用 pickle 模块 311 Args: 312 inputTree -- 以前训练好的决策树模型 313 filename -- 要存储的名称 314 Returns: 315 None 316 """ 317 import pickle 318 # -------------- 第一种方法 start -------------- 319 fw = open(filename, 'wb') 320 pickle.dump(inputTree, fw) 321 fw.close() 322 # -------------- 第一种方法 end -------------- 323 324 # -------------- 第二种方法 start -------------- 325 with open(filename, 'wb') as fw: 326 pickle.dump(inputTree, fw) 327 # -------------- 第二种方法 start -------------- 328 329 330 def grabTree(filename): 331 """ 332 Desc: 333 将之前存储的决策树模型使用 pickle 模块 还原出来 334 Args: 335 filename -- 之前存储决策树模型的文件名 336 Returns: 337 pickle.load(fr) -- 将之前存储的决策树模型还原出来 338 """ 339 import pickle 340 fr = open(filename, 'rb') 341 return pickle.load(fr) 342 343 344 def fishTest(): 345 """ 346 Desc: 347 对动物是否是鱼类分类的测试函数,并将结果使用 matplotlib 画出来 348 Args: 349 None 350 Returns: 351 None 352 """ 353 # 1.创建数据和结果标签 354 myDat, labels = createDataSet() 355 # print(myDat, labels) 356 357 # 计算label分类标签的香农熵 358 # calcShannonEnt(myDat) 359 360 # # 求第0列 为 1/0的列的数据集【排除第0列】 361 # print('1---', splitDataSet(myDat, 0, 1)) 362 # print('0---', splitDataSet(myDat, 0, 0)) 363 364 # # 计算最好的信息增益的列 365 # print(chooseBestFeatureToSplit(myDat)) 366 367 import copy 368 myTree = createTree(myDat, copy.deepcopy(labels)) 369 print(myTree) 370 print('================================') 371 # [1, 1]表示要取的分支上的节点位置,对应的结果值 372 print(classify(myTree, labels, [1, 1])) 373 374 # 画图可视化展现 375 dtPlot.createPlot(myTree) 376 377 378 fishTest() 379 380 """ 381 infoGain= 0.4199730940219749 bestFeature= 0 baseEntropy= 0.9709505944546686 newEntropy= 0.5509775004326937 382 infoGain= 0.17095059445466854 bestFeature= 1 baseEntropy= 0.9709505944546686 newEntropy= 0.8 383 infoGain= 0.9182958340544896 bestFeature= 0 baseEntropy= 0.9182958340544896 newEntropy= 0.0 384 {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}} 385 ================================ 386 +++ no surfacing xxx {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}} --- 1 >>> {'flippers': {0: 'no', 1: 'yes'}} 387 +++ flippers xxx {0: 'no', 1: 'yes'} --- 1 >>> yes 388 yes 389 """
浙公网安备 33010602011771号