当前位置:   article > 正文

决策树算法的简易实现_决策树算法实现

决策树算法实现

目录

介绍:

代码实现:

总结:


  • 介绍:

        决策树算法是一种基于树结构的监督学习算法,用于解决分类和回归问题。它通过构建一个树状的决策模型来进行预测。

        在决策树中,每个内部节点表示特征或属性,叶子节点表示类别或数值。通过逐步划分数据集,决策树能够根据输入特征的不同值将数据集分割成不同的子集,从而实现对数据的分类或回归预测。

        决策树算法的主要思想是选择最佳的特征来进行分割,以使得分割后的子集尽可能地纯净或有序。常用的衡量指标包括熵、信息增益、基尼指数等。通过递归地选择最佳特征并分割数据集,最终构建出一棵完整的决策树。

        以我们之前做的判断游戏受欢迎程度为例,其决策树可以简单表示为:

        上图表示了通过决策树判断一个游戏通过玩家评分、媒体评分、游戏销量、平均游戏时长来判断一个游戏是否受欢迎,箭头表示在一个判断条件在不同情况下的决策路径。当然实际情况远比图示的复杂:判断标准不可能如此简单、实际结果也不止受不受欢迎两个。

  • 代码实现:

        决策树伪代码实现大致如下:

        1.读取数据,并将字符串标签转换成数字标签

        2.将数据集分成训练集和测试集

        3.定义决策树的节点类,包含属性名、子节点列表、分类结果等信息

        4.递归函数生成决策树,在每个节点选择最优的划分属性,递归生成子节点并赋予分类结果

        5.使用测试集评估决策树性能

        我们可以通过在之前KNN算法实现中已有的代码来修改完成决策树算法的实现。

  • 首先就是将数据集划分成训练集和测试集:

        我们使用load_dataset函数从文件中读取数据集,并将每行数据拆分为特征值和标签。特征值被转换为浮点数,而标签被转换为数字编码。数据集通过随机打乱索引并按照给定的test_ratio比例划分为训练集和测试集。

  1. def load_dataset(filename, test_ratio=0.5):
  2. with open(filename) as f:
  3. lines = f.readlines()
  4. dataset = []
  5. labels = []
  6. for line in lines:
  7. record = line.strip().split('\t')
  8. dataset.append([float(x) for x in record[:-1]])
  9. labels.append(record[-1])
  10. # 将label转换为数字编码
  11. label_dict = {'口碑极差':1, '口碑不佳':2, '不受欢迎':3, '一般':4, '受欢迎':5, '较受欢迎':6, '很受欢迎':7}
  12. labels = [label_dict[x] for x in labels]
  13. # 随机打乱数据集,并按照test_ratio的比例划分为训练集和测试集
  14. indices = np.arange(len(dataset))
  15. np.random.shuffle(indices)
  16. test_size = int(test_ratio * len(dataset))
  17. test_indices = indices[:test_size]
  18. train_indices = indices[test_size:]
  19. train_dataset = [dataset[i] for i in train_indices]
  20. test_dataset = [dataset[i] for i in test_indices]
  21. train_labels = [labels[i] for i in train_indices]
  22. test_labels = [labels[i] for i in test_indices]
  23. return np.array(train_dataset), train_labels, np.array(test_dataset), test_labels
  • 添加基尼指数计算的方法:

        基尼指数用于度量集合中各类别样本的不确定性。它通过计算每个类别在集合中出现的概率的平方和来度量不确定性。基尼指数越大,集合的不确定性就越高。我们构造calc_gini函数来计算给定标签集合的基尼指数。

  1. # 计算基尼指数
  2. def calc_gini(labels):
  3. class_count = {}
  4. for label in labels:
  5. if label not in class_count:
  6. class_count[label] = 0
  7. class_count[label] += 1
  8. gini = 1.0
  9. for label in class_count:
  10. prob = float(class_count[label]) / len(labels)
  11. gini -= prob * prob
  12. return gini
  • 添加信息增益计算方法:

        我们构造calc_info_gain函数来计算给定划分属性、标签和划分点的信息增益。它首先计算原始集合的基尼指数,然后根据划分点将数据集分成左右两个子集,分别计算左右子集的基尼指数。最后,通过加权计算得到信息增益,信息增益越大,说明使用该属性进行划分可以获得更多的信息。

  1. # 计算划分属性的信息增益
  2. def calc_info_gain(feature, labels, split_point):
  3. S = calc_gini(labels) # 原始集合的基尼指数
  4. left_labels = [labels[i] for i in range(len(feature)) if feature[i] < split_point]
  5. right_labels = [labels[i] for i in range(len(feature)) if feature[i] >= split_point]
  6. if len(left_labels) == 0 or len(right_labels) == 0:
  7. return 0.0
  8. SL = calc_gini(left_labels) # 左集合的基尼指数
  9. SR = calc_gini(right_labels) # 右集合的基尼指数
  10. prob_L = float(len(left_labels)) / len(labels)
  11. prob_R = float(len(right_labels)) / len(labels)
  12. gain = S - prob_L * SL - prob_R * SR
  13. return gain
  • 决策树构建:

        我们构造create_tree函数通过递归的方式构建决策树。它首先判断是否需要停止递归,如果所有样本属于同一类别或者没有特征可用于划分,则返回叶子节点。否则,它遍历所有的特征和可能的划分点,选择具有最大信息增益的划分属性和划分点。然后根据选定的划分属性和划分点将数据集分成左右两个子集,并递归地构建左右子树。

  1. # 构建决策树
  2. def create_tree(dataset, labels):
  3. if len(labels) == 0: # 如果数据集为空,则返回None
  4. return None
  5. class_count = {} # 统计类别出现次数
  6. for label in labels:
  7. if label not in class_count:
  8. class_count[label] = 0
  9. class_count[label] += 1
  10. if len(class_count) == 1: # 如果数据集中只有一种类别,则返回该类别
  11. return DecisionNode(class_label=labels[0])
  12. num_features = dataset.shape[1] # 特征数量
  13. best_feature_idx = -1 # 最佳划分属性的索引
  14. best_split_point = 0.0 # 最佳划分点的取值
  15. max_gain = 0.0 # 最大信息增益
  16. for i in range(num_features):
  17. feature = dataset[:, i]
  18. split_points = sorted(list(set(feature))) # 去重并排序
  19. for j in range(len(split_points) - 1):
  20. split_point = (split_points[j] + split_points[j+1]) / 2.0
  21. gain = calc_info_gain(feature, labels, split_point)
  22. if gain > max_gain:
  23. best_feature_idx = i
  24. best_split_point = split_point
  25. max_gain = gain
  26. # 根据最佳划分属性和划分点构建决策树
  27. left_dataset = dataset[dataset[:, best_feature_idx] < best_split_point, :]
  28. left_labels = [labels[i] for i in range(len(labels)) if dataset[i, best_feature_idx] < best_split_point]
  29. right_dataset = dataset[dataset[:, best_feature_idx] >= best_split_point, :]
  30. right_labels = [labels[i] for i in range(len(labels)) if dataset[i, best_feature_idx] >= best_split_point]
  31. left_child = create_tree(left_dataset, left_labels)
  32. right_child = create_tree(right_dataset, right_labels)
  33. return DecisionNode(feature_idx=best_feature_idx, split_point=best_split_point,
  34. left_child=left_child, right_child=right_child)
  • 决策树分类:

        根据构建好的决策树对新样本进行分类,从根节点开始,根据划分属性和划分点决定是往左子树还是往右子树走,直到叶子节点得到分类结果。

  1. # 决策树分类
  2. def classify(node, sample):
  3. if node.class_label is not None: # 如果是叶子节点,则返回类别
  4. return node.class_label
  5. else:
  6. if sample[node.feature_idx] < node.split_point: # 根据划分属性和划分点决定是往左子树还是往右子树走
  7. return classify(node.left_child, sample)
  8. else:
  9. return classify(node.right_child, sample)

        完整代码如下,相较于原KNN算法仅保留了数据归一化和输入输出的部分,其他都根据决策树方法进行了对应的改造:

  1. import numpy as np
  2. class DecisionNode(object):
  3. def __init__(self, feature_idx=None, split_point=None, left_child=None, right_child=None, class_label=None):
  4. self.feature_idx = feature_idx # 划分属性的索引
  5. self.split_point = split_point # 划分点的取值
  6. self.left_child = left_child # 左子树
  7. self.right_child = right_child # 右子树
  8. self.class_label = class_label # 叶子节点的分类标签
  9. def load_dataset(filename, test_ratio=0.5):
  10. with open(filename) as f:
  11. lines = f.readlines()
  12. dataset = []
  13. labels = []
  14. for line in lines:
  15. record = line.strip().split('\t')
  16. dataset.append([float(x) for x in record[:-1]])
  17. labels.append(record[-1])
  18. # 将label转换为数字编码
  19. label_dict = {'口碑极差':1, '口碑不佳':2, '不受欢迎':3, '一般':4, '受欢迎':5, '较受欢迎':6, '很受欢迎':7}
  20. labels = [label_dict[x] for x in labels]
  21. # 随机打乱数据集,并按照test_ratio的比例划分为训练集和测试集
  22. indices = np.arange(len(dataset))
  23. np.random.shuffle(indices)
  24. test_size = int(test_ratio * len(dataset))
  25. test_indices = indices[:test_size]
  26. train_indices = indices[test_size:]
  27. train_dataset = [dataset[i] for i in train_indices]
  28. test_dataset = [dataset[i] for i in test_indices]
  29. train_labels = [labels[i] for i in train_indices]
  30. test_labels = [labels[i] for i in test_indices]
  31. return np.array(train_dataset), train_labels, np.array(test_dataset), test_labels
  32. # 数据归一化处理
  33. def autoNorm(dataSet):
  34. minVals = dataSet.min(0)
  35. maxVals = dataSet.max(0)
  36. ranges = maxVals - minVals
  37. normDataSet = np.zeros(np.shape(dataSet))
  38. m = dataSet.shape[0]
  39. normDataSet = dataSet - np.tile(minVals, (m, 1))
  40. normDataSet = normDataSet / np.tile(ranges, (m, 1))
  41. return normDataSet, ranges, minVals
  42. # 计算基尼指数
  43. def calc_gini(labels):
  44. class_count = {}
  45. for label in labels:
  46. if label not in class_count:
  47. class_count[label] = 0
  48. class_count[label] += 1
  49. gini = 1.0
  50. for label in class_count:
  51. prob = float(class_count[label]) / len(labels)
  52. gini -= prob * prob
  53. return gini
  54. # 计算划分属性的信息增益
  55. def calc_info_gain(feature, labels, split_point):
  56. S = calc_gini(labels) # 原始集合的基尼指数
  57. left_labels = [labels[i] for i in range(len(feature)) if feature[i] < split_point]
  58. right_labels = [labels[i] for i in range(len(feature)) if feature[i] >= split_point]
  59. if len(left_labels) == 0 or len(right_labels) == 0:
  60. return 0.0
  61. SL = calc_gini(left_labels) # 左集合的基尼指数
  62. SR = calc_gini(right_labels) # 右集合的基尼指数
  63. prob_L = float(len(left_labels)) / len(labels)
  64. prob_R = float(len(right_labels)) / len(labels)
  65. gain = S - prob_L * SL - prob_R * SR
  66. return gain
  67. # 构建决策树
  68. def create_tree(dataset, labels):
  69. if len(labels) == 0: # 如果数据集为空,则返回None
  70. return None
  71. class_count = {} # 统计类别出现次数
  72. for label in labels:
  73. if label not in class_count:
  74. class_count[label] = 0
  75. class_count[label] += 1
  76. if len(class_count) == 1: # 如果数据集中只有一种类别,则返回该类别
  77. return DecisionNode(class_label=labels[0])
  78. num_features = dataset.shape[1] # 特征数量
  79. best_feature_idx = -1 # 最佳划分属性的索引
  80. best_split_point = 0.0 # 最佳划分点的取值
  81. max_gain = 0.0 # 最大信息增益
  82. for i in range(num_features):
  83. feature = dataset[:, i]
  84. split_points = sorted(list(set(feature))) # 去重并排序
  85. for j in range(len(split_points) - 1):
  86. split_point = (split_points[j] + split_points[j+1]) / 2.0
  87. gain = calc_info_gain(feature, labels, split_point)
  88. if gain > max_gain:
  89. best_feature_idx = i
  90. best_split_point = split_point
  91. max_gain = gain
  92. # 根据最佳划分属性和划分点构建决策树
  93. left_dataset = dataset[dataset[:, best_feature_idx] < best_split_point, :]
  94. left_labels = [labels[i] for i in range(len(labels)) if dataset[i, best_feature_idx] < best_split_point]
  95. right_dataset = dataset[dataset[:, best_feature_idx] >= best_split_point, :]
  96. right_labels = [labels[i] for i in range(len(labels)) if dataset[i, best_feature_idx] >= best_split_point]
  97. left_child = create_tree(left_dataset, left_labels)
  98. right_child = create_tree(right_dataset, right_labels)
  99. return DecisionNode(feature_idx=best_feature_idx, split_point=best_split_point,
  100. left_child=left_child, right_child=right_child)
  101. # 决策树分类
  102. def classify(node, sample):
  103. if node.class_label is not None: # 如果是叶子节点,则返回类别
  104. return node.class_label
  105. else:
  106. if sample[node.feature_idx] < node.split_point: # 根据划分属性和划分点决定是往左子树还是往右子树走
  107. return classify(node.left_child, sample)
  108. else:
  109. return classify(node.right_child, sample)
  110. # 输入输出
  111. def classifyPerson():
  112. resultList = ['口碑极差','口碑不佳','不受欢迎','一般','受欢迎','较受欢迎','很受欢迎']
  113. ign = float(input("IGN评分:"))
  114. player = float(input("玩家评分:"))
  115. sell = float(input("销量:"))
  116. hour = float(input("时长:"))
  117. filename = "test.txt"
  118. train_dataset, train_labels, test_dataset, test_labels = load_dataset(filename)
  119. normMat, ranges, minVals = autoNorm(train_dataset)
  120. inArr = np.array([ign, player,sell, hour])
  121. normArr = (inArr - minVals) / ranges
  122. train_dataset_normalized = (train_dataset - minVals) / ranges
  123. test_dataset_normalized = (test_dataset - minVals) / ranges
  124. tree = create_tree(train_dataset_normalized, train_labels)
  125. result = classify(tree, normArr)
  126. print("预测结果:", resultList[result-1])
  127. classifyPerson()
  • 运行结果:

  • 总结:

        决策树是一种重要的分类和回归算法,它能够通过对数据集进行划分来实现对样本的分类和预测。决策树拥有众多优点如:决策树可以直观地呈现决策过程,易于理解和解释;决策树可以应用于分类和回归问题,并且对于离散特征和连续特征都适用;决策树可以处理多类别分类问题,不需要额外的修改;决策树算法对缺失值和异常值不敏感,能够处理包含缺失值和异常值的数据集。同时,决策树也有着容易过拟合、对输入数据的小变化敏感、忽略了特征间的相关性等缺点。通过学习决策树算法,对于我们深入理解和提升机器学习编程能力都有很大的帮助。

        

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

闽ICP备14008679号