当前位置:   article > 正文

机器学习——决策树(三)预剪枝

机器学习——决策树(三)预剪枝

观前提示:这是本人机器学习决策树内容的第三篇博客沿用了之前相关的代码,包括信息增益计算函数、结点类、预测函数和分类精度计算函数 

完整代码指路

DrawPixel/decisionTree.ipynb at main · ndsoi/DrawPixel (github.com)

前两篇博客详见“机器学习”专栏

使用方法:

方法一:一口气连接——执行是不会报错的

方法二:针对本节内容的代码块

notebook中我做了目录划分

与本节内容相关的代码块如下图:

本节没有用到的是 dotree函数和TreeGenerate函数,所以预剪枝的主要是替换了TreeGenerate的逻辑

1、含义

预剪枝:

剪枝是指在决策树生成过程中,对每个结点在划分前先进行估计,若当前结点的划分不能带来决策树泛化性能提升,则停止划分并将当前结点标记为叶结点

作用:提高构建决策树的效率、防止过拟合、提高模型的鲁棒性

2、实现流程

算法流程:

  1. 划分好训练集和验证集;
  2. 初始化根节点,标记为叶结点,用验证集计算精度,对当然结点进行划分,再用验证集计算精度,如果精度提升,则对当前结点进行划分,若精度无提升则不划分。

用一个队列(名为waitcheck)维护要考虑划分的结点。

step1:初始化根结点,将根结点投入到waitcheck中;

step2:从waitcheck中取出一个结点:

①当前结点是一个叶结点,计算验证集的分类精度a

②考虑当前结点能否在划分:

        若该结点可以划分,则找出它划分的最优属性,进行划分,再计算划分后的验证集预测效果b

                比较a和b的大小:

                        如果b比a小,则不划分,将该结点标记为叶结点,继续考虑waitcheck

                        如果b比a大,则将该结点标记为非叶结点,新生成的子结点加入waitcheck

        不行则考虑下一结点

step3:重复step2,直到waitcheck为空

3、编程实现

1、划分训练集和验证集

按照西瓜书的数据划分:

  1. dataSet = [
  2. # 1
  3. ['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
  4. # 2
  5. ['乌黑', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', '好瓜'],
  6. # 3
  7. ['乌黑', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
  8. # 4
  9. ['青绿', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', '好瓜'],
  10. # 5
  11. ['浅白', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
  12. # 6
  13. ['青绿', '稍蜷', '浊响', '清晰', '稍凹', '软粘', '好瓜'],
  14. # 7
  15. ['乌黑', '稍蜷', '浊响', '稍糊', '稍凹', '软粘', '好瓜'],
  16. # 8
  17. ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '硬滑', '好瓜'],
  18. # ----------------------------------------------------
  19. # 9
  20. ['乌黑', '稍蜷', '沉闷', '稍糊', '稍凹', '硬滑', '坏瓜'],
  21. # 10
  22. ['青绿', '硬挺', '清脆', '清晰', '平坦', '软粘', '坏瓜'],
  23. # 11
  24. ['浅白', '硬挺', '清脆', '模糊', '平坦', '硬滑', '坏瓜'],
  25. # 12
  26. ['浅白', '蜷缩', '浊响', '模糊', '平坦', '软粘', '坏瓜'],
  27. # 13
  28. ['青绿', '稍蜷', '浊响', '稍糊', '凹陷', '硬滑', '坏瓜'],
  29. # 14
  30. ['浅白', '稍蜷', '沉闷', '稍糊', '凹陷', '硬滑', '坏瓜'],
  31. # 15
  32. ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '软粘', '坏瓜'],
  33. # 16
  34. ['浅白', '蜷缩', '浊响', '模糊', '平坦', '硬滑', '坏瓜'],
  35. # 17
  36. ['青绿', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑', '坏瓜']
  37. ]
  38. Attr = ['色泽', '根蒂', '敲击', '纹理', '脐部', '触感']
  39. # 硬编码类别
  40. class_dict = {'坏瓜':0,'好瓜':1}
  41. # 将数据合并格式
  42. D = []
  43. for i in range(len(dataSet)):
  44. d = {}
  45. for j in range(len(Attr)):
  46. d[Attr[j]] = dataSet[i][j]
  47. d['Class'] = class_dict[dataSet[i][-1]]
  48. D.append(d)
  49. print(D)

2、批量验证函数

  1. # 精度计算
  2. def calAccuracy(pred,data):
  3. n = len(data)
  4. re = 0
  5. for i in range(n):
  6. if pred[i] == data[i]['Class']:
  7. re+=1
  8. return re/n
  9. # 返回预测结果和精度
  10. def predict_v4(root_4,val_data):
  11. re = []
  12. for i in range(len(val_data)):
  13. re.append(predict_v2(val_data[i],root_v4))
  14. return re,calAccuracy(re,val_data)

3、结点是否能继续划分

  1. # 若是否有划分的资格
  2. def CanDivide(node_v4):
  3. if node_v4.isSameClass() == True:
  4. return False,[]
  5. boolre,Attr = node_v4.isNoAttr()
  6. if boolre == False:
  7. return True,Attr
  8. return False,[]

4、初始数据集生成根结点、初始化waitcheck

  1. import queue
  2. # 找出初始数据集的最多类
  3. max,cal_class = calMaxClass(train_data,class_num)
  4. # 构造训练集的根结点
  5. root_v4 = Node(train_data,Attr,max,cal_class,class_num)
  6. # 标记根结点位叶结点
  7. root_v4.label = 1
  8. # 初始化waitcheck队列
  9. waitcheck = queue.Queue()
  10. waitcheck.put(root_v4)

5、预剪枝过程(核心) 

  1. def train_v4(waitcheck,root_v4):
  2. node_v4 = waitcheck.get()
  3. # 用验证集算一下精度
  4. res_o,acc_o = predict_v4(root_v4,val_data)
  5. divide,Attr_Div = CanDivide(node_v4)
  6. if divide == False:
  7. # 考虑下一个结点
  8. print("考虑下一个结点")
  9. return
  10. else:
  11. # 先将当前结点的label改为0
  12. node_v4.label = 0
  13. # 尝试划分结点
  14. # 选取最优属性
  15. attr,info = node_v4.bestAttr()
  16. # 获取划分好的数据集
  17. SubDataSets = info[attr]['Dv']
  18. SubInfo = info[attr]['Dv_info']
  19. # 生成子node
  20. # 保持子node
  21. savesubnode = []
  22. Attr = copy.deepcopy(Attr_Div)
  23. Attr.remove(attr)
  24. st = 0
  25. for value,subds in SubDataSets.items():
  26. # 因为假设是离散属性,所以新的self.attr必然要去掉已经选出的attr
  27. subnodeAttr = copy.deepcopy(Attr)
  28. # 获取已经算好的Dv的max和cal_class
  29. submax = SubInfo[st][0]
  30. subcal_class = SubInfo[st][1]
  31. st+=1
  32. # 生成新结点
  33. subnode = Node(subds,subnodeAttr,submax,subcal_class,class_num)
  34. subnode.setflag(attr)
  35. # 假设新结点都是叶子结点
  36. subnode.label = 1
  37. # 暂存取得的新结点,若确定要划分,才加入讨论队列
  38. savesubnode.append(subnode)
  39. # 父结点记录子结点的指引
  40. node_v4.addsubDs(subnode,value)
  41. # 验证集评估
  42. res_d,acc_d = predict_v4(node_v4,val_data)
  43. print(f"未划分时的分类精度:{acc_o},划分后的分类精度:{acc_d}")
  44. print(f"展示一下划分后的树")
  45. drawTree(root_v4)
  46. if acc_d>acc_o:
  47. # 划分后的精度更高,所以划分
  48. # 将新的子结点加入waitcheck
  49. for i in savesubnode:
  50. # i.label = 0 不用改,取出来还要再令label=1
  51. waitcheck.put(i)
  52. else:
  53. # 划分后验证集的预测结果
  54. print("划分后验证集预测结果")
  55. print(res_d)
  56. # 最终还是不划分
  57. node_v4.label = 1

6、训练

  1. while waitcheck.empty()==False:
  2. train_v4(waitcheck,root_v4)
  3. drawTree(root_v4)

4、结果显示

1)对原始数据集考虑划分:可以看到在计算机计算精度内,色泽和脐部属性的信息增益是一样的,但遍历的时候脐部在后,而要求是大于等于最大信息增益的属性就可以替换为最优,所以初次划分的最优属性是脐部

2)按照脐部划分后,验证集评估模型发现划分后的分类精度是0.75,划分前的精度是0.375,所以不剪枝,将新增的子结点加入waitcheck

3)继续考虑新的结点,按照添加顺序应该是“脐部=凹陷”的数据集,该数据集的最优属性划分(看蓝色箭头),在计算机精度内后来者居上最优的属性是“纹理”,然后看下方绿色线,发现按照“纹理”划分,精度又从0.75提升至0.875,因此又保留了“纹理”产生的分支

4)继续考虑waitcheck的数据,发现这些结点再划分都不会使得模型在验证集的精度提升,于是决策树模型完全形成

最终的决策树是:

5、讨论:

这个预剪枝的结果与《机器学习》上的并不一致

原因:就是在最优属性划分上:同样的信息增益,西瓜书的选择是“色泽”,我的程序选择的是“纹理”,当用验证集验证的时候,“色泽”划分会导致正确性下降,而纹理却能继续提升分类精度

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/330964
推荐阅读
相关标签
  

闽ICP备14008679号