(四)决策树 定义: 书中实例:贷款申请样本,通过一个人的年龄、是否有工作、是否有自己的房子、信贷情况这四个特征判定,最终构建模型来判别是否给予贷款,如图: 希望通过所给的训练数据学习一个贷款申请的决策树,用来对未来贷款申请进行分类(二分类),决策树可以理解成:有一个根节点开始,往下进行分支,越重要的节点应该离根越近,我们将重要的、影响度大的特征作为根节点,依次向下,其次重要的往下面街接,如图:
熵与条件熵的定义: 熵: 表示随机变量不确定性的度量,设$X$是一个取有限个值的离散随机变量,其概率分布为:$$P(X=x_i)=p_i, i=1,2,…,n$$ 则随机变量$X$的熵定义为: $$H(X)=-\sum_{i=1}^{n}p_i\log{p_i}$$ 越大的概率,得到的熵值越小,也就是说概率大的确定性大,不确定不就小了嘛,反之亦然; 举例:$A$集合:[1,1,1,1,1,1,1,2,2] $B$集合:[1,2,3,4,5,6,7,8,9] 显然$A$集合的熵值要低,因为A里面只有两种类别,相对稳定一些,而B中类别太多,熵值就会大很多,而在分类问题中我们当然是希望分支后的数据类别的熵值小,确定性就大嘛,熵值越低,分类效果越好撒 同理条件熵 就是表示在已知随机变量X的条件下随机变量Y的不确定性$H(Y|X)$,定义为$X$给定条件下Y的条件概率分布的熵对X的数学期望: $$H(Y|X)=\sum_{i=1}^{n}p_iH(Y|X=x_i)$$
信息增益: 做决策树目的就是在过程中将熵值不断减小,增益呢,就是熵值下降了多少,通过信息增益来遍历计算所有特征,哪个特征使得我们的信息增益最大,最大的哪个特征就拿过来当做根节点,接着同理把剩下的特征也这么来排序,排出第二个节点,第三个节点。。。 信息增益表示得知特征$X$的信息而使得类$Y$的信息不确定性减少的程度。 特征$A$对训练数据集$D$的信息增益$g(D,A)$,定义为集合$D$的经验熵,经验熵就是不考虑特征,只考虑整个样本label的熵,附上书中实例: $H(D)$与特征$A$给定条件下$D$的经验条件熵$H(D|A)$之差,即: $$g(D,A)=H(D)-H(D|A)$$ 熵$H(Y)$与条件熵$H(Y|X)$之差成为互信息,此时信息增益等于互信息。信息增益算法: 输入:训练数据集$D$和特征$A$; 输出:特征$A$对训练数据集$D$的信息增益$g(D,A)$。 (1)计算数据集$D$的经验熵$H(D)$ $$H(D)=-\sum_{k=1}^{k}\frac{|C_k|}{|D|}\log{\frac{|C_k|}{|D|}}$$ (2)计算特征$A$对数据集$D$的经验条件熵$H(D|A)$ $$H(D|A)=\sum_{i=1}^{n}\frac{|D_I|}{|D|}H(D_i)=-\sum_{i=1}^{n}\frac{|D_i|}{D}\sum_{k=1}^{k}\frac{|D_{ik}|}{D_i}\log_{2}\frac{|D_{ik}|}{|D_i|}$$ (3)计算信息增益: $$g(D,A)=H(D)-H(D,A)$$
信息增益比: 以信息增益作为划分训练集的特征,存在偏向于选择取值较多的特征的问题,使用信息增益比对其校正:特征$A$对训练数据集$D$的信息增益比$g_R(D,A)$定义为其信息增益$g(D,A$与训练数据集$D$关于特征$A$的值的熵$H_A(D)$之比,即: $$g_R(D,A)=\frac{g(D,A)}{H_A(D)}$$ 以上讨论都是离散值,如果是连续值呢?
ID3算法 : 核心是在决策树各个结点上应用信息增益准则选择信息增益最大且大于阈值的特征,递归地构建决策树.ID3相当于用极大似然法进行概率模型的选择.甶于算法只有树的生成,所以容易产生过拟合。决策树剪枝策略: 为什么要剪枝:决策树过拟合风险很大, 预剪枝:边建立决策树边进行剪枝的操作 后剪枝:当建立完决策树后进行剪枝操作
代码: 参考代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 ''' 数据集:Mnist 训练集数量:60000 测试集数量:10000 ------------------------------ 运行结果:ID3(未剪枝) 正确率:85.9% 运行时长:356s ''' import timeimport numpy as npdef loadData (fileName) : ''' 加载文件 :param fileName:要加载的文件路径 :return: 数据集和标签集 ''' dataArr = []; labelArr = [] fr = open(fileName) for line in fr.readlines(): curLine = line.strip().split(',' ) dataArr.append([int(int(num) > 128 ) for num in curLine[1 :]]) labelArr.append(int(curLine[0 ])) return dataArr, labelArr def majorClass (labelArr) : ''' 找到当前标签集中占数目最大的标签 :param labelArr: 标签集 :return: 最大的标签 ''' classDict = {} for i in range(len(labelArr)): if labelArr[i] in classDict.keys(): classDict[labelArr[i]] += 1 else : classDict[labelArr[i]] = 1 classSort = sorted(classDict.items(), key=lambda x: x[1 ], reverse=True ) return classSort[0 ][0 ] def calc_H_D (trainLabelArr) : ''' 计算数据集D的经验熵,参考公式5.7 经验熵的计算 :param trainLabelArr:当前数据集的标签集 :return: 经验熵 ''' H_D = 0 trainLabelSet = set([label for label in trainLabelArr]) for i in trainLabelSet: p = trainLabelArr[trainLabelArr == i].size / trainLabelArr.size H_D += -1 * p * np.log2(p) return H_D def calcH_D_A (trainDataArr_DevFeature, trainLabelArr) : ''' 计算经验条件熵 :param trainDataArr_DevFeature:切割后只有feature那列数据的数组 :param trainLabelArr: 标签集数组 :return: 经验条件熵 ''' H_D_A = 0 trainDataSet = set([label for label in trainDataArr_DevFeature]) for i in trainDataSet: H_D_A += trainDataArr_DevFeature[trainDataArr_DevFeature == i].size / trainDataArr_DevFeature.size \ * calc_H_D(trainLabelArr[trainDataArr_DevFeature == i]) return H_D_A def calcBestFeature (trainDataList, trainLabelList) : ''' 计算信息增益最大的特征 :param trainDataList: 当前数据集 :param trainLabelList: 当前标签集 :return: 信息增益最大的特征及最大信息增益值 ''' trainDataArr = np.array(trainDataList) trainLabelArr = np.array(trainLabelList).T featureNum = trainDataArr.shape[1 ] maxG_D_A = -1 maxFeature = -1 for feature in range(featureNum): H_D = calc_H_D(trainLabelArr) trainDataArr_DevideByFeature = np.array(trainDataArr[:, feature].flat) G_D_A = H_D - calcH_D_A(trainDataArr_DevideByFeature, trainLabelArr) if G_D_A > maxG_D_A: maxG_D_A = G_D_A maxFeature = feature return maxFeature, maxG_D_A def getSubDataArr (trainDataArr, trainLabelArr, A, a) : ''' 更新数据集和标签集 :param trainDataArr:要更新的数据集 :param trainLabelArr: 要更新的标签集 :param A: 要去除的特征索引 :param a: 当data[A]== a时,说明该行样本时要保留的 :return: 新的数据集和标签集 ''' retDataArr = [] retLabelArr = [] for i in range(len(trainDataArr)): if trainDataArr[i][A] == a: retDataArr.append(trainDataArr[i][0 :A] + trainDataArr[i][A+1 :]) retLabelArr.append(trainLabelArr[i]) return retDataArr, retLabelArr def createTree (*dataSet) : ''' 递归创建决策树 :param dataSet:(trainDataList, trainLabelList) <<-- 元祖形式 :return:新的子节点或该叶子节点的值 ''' Epsilon = 0.1 trainDataList = dataSet[0 ][0 ] trainLabelList = dataSet[0 ][1 ] print('start a node' , len(trainDataList[0 ]), len(trainLabelList)) classDict = {i for i in trainLabelList} if len(classDict) == 1 : return trainLabelList[0 ] if len(trainDataList[0 ]) == 0 : return majorClass(trainLabelList) Ag, EpsilonGet = calcBestFeature(trainDataList, trainLabelList) if EpsilonGet < Epsilon: return majorClass(trainLabelList) treeDict = {Ag:{}} treeDict[Ag][0 ] = createTree(getSubDataArr(trainDataList, trainLabelList, Ag, 0 )) treeDict[Ag][1 ] = createTree(getSubDataArr(trainDataList, trainLabelList, Ag, 1 )) return treeDict def predict (testDataList, tree) : ''' 预测标签 :param testDataList:样本 :param tree: 决策树 :return: 预测结果 ''' while True : (key, value), = tree.items() if type(tree[key]).__name__ == 'dict' : dataVal = testDataList[key] del testDataList[key] tree = value[dataVal] if type(tree).__name__ == 'int' : return tree else : return value def test (testDataList, testLabelList, tree) : ''' 测试准确率 :param testDataList:待测试数据集 :param testLabelList: 待测试标签集 :param tree: 训练集生成的树 :return: 准确率 ''' errorCnt = 0 for i in range(len(testDataList)): if testLabelList[i] != predict(testDataList[i], tree): errorCnt += 1 return 1 - errorCnt / len(testDataList) if __name__ == '__main__' : start = time.time() trainDataList, trainLabelList = loadData('../Mnist/mnist_train.csv' ) testDataList, testLabelList = loadData('../Mnist/mnist_test.csv' ) print('start create tree' ) tree = createTree((trainDataList, trainLabelList)) print('tree is:' , tree) print('start test' ) accur = test(testDataList, testLabelList, tree) print('the accur is:' , accur) end = time.time() print('time span:' , end - start)