从零构建ID3决策树:Python代码逐行解析与西瓜数据集实战
·
1. 决策树与ID3算法基础
决策树是机器学习中最直观的算法之一,它通过一系列if-then规则对数据进行分类。想象一下你去水果摊买西瓜的过程:先看纹路是否清晰,再听敲击声音是否清脆,最后检查瓜蒂是否新鲜——这本质上就是一个三层的决策树。ID3算法正是这种决策过程的数学实现,由Ross Quinlan在1986年提出。
核心概念 :信息熵量化了数据的混乱程度。当一个集合中全是好瓜或全是坏瓜时,熵为0;当好瓜坏瓜各占一半时,熵达到最大值1。计算公式为:
from math import log
def calc_entropy(data):
counts = {}
for item in data:
label = item[-1]
counts[label] = counts.get(label, 0) + 1
entropy = -sum((p/len(data))*log(p/len(data),2) for p in counts.values())
return entropy
信息增益则衡量了特征对熵的减少能力。比如"纹理"特征将西瓜分为清晰、稍糊、模糊三组后,整体熵从原来的0.998降到了0.548,增益就是0.45。ID3算法每次选择增益最大的特征进行划分,就像我们买西瓜时优先选择最有效的鉴别方法。
2. 数据准备与特征工程
我们使用经典的西瓜数据集,包含17个样本和6个特征:
| 编号 | 色泽 | 根蒂 | 敲声 | 纹理 | 脐部 | 触感 | 好瓜 |
|---|---|---|---|---|---|---|---|
| 1 | 青绿 | 蜷缩 | 浊响 | 清晰 | 凹陷 | 硬滑 | 是 |
| 2 | 乌黑 | 蜷缩 | 沉闷 | 清晰 | 凹陷 | 硬滑 | 是 |
| ... | ... | ... | ... | ... | ... | ... | ... |
数据预处理技巧 :
- 离散值处理:ID3算法要求所有特征都是离散值,连续特征需要先分桶
- 缺失值处理:可采用众数填充或单独创建"未知"分支
- 数据拆分:保留20%作为验证集,防止过拟合
def create_dataset():
data = [['青绿','蜷缩','浊响','清晰','凹陷','硬滑','好瓜'],
['乌黑','蜷缩','沉闷','清晰','凹陷','硬滑','好瓜'],
...] # 完整数据见文末
features = ['色泽','根蒂','敲声','纹理','脐部','触感']
return data, features
3. 核心算法实现
3.1 信息增益计算
关键步骤是通过特征划分数据集并计算熵的变化:
def split_data(data, axis, value):
"""按特征划分数据集"""
return [item[:axis]+item[axis+1:] for item in data if item[axis]==value]
def choose_feature(data):
base_entropy = calc_entropy(data)
best_gain = 0
best_feature = -1
for i in range(len(data[0])-1):
values = {item[i] for item in data}
new_entropy = 0
for v in values:
sub_data = split_data(data, i, v)
prob = len(sub_data)/len(data)
new_entropy += prob * calc_entropy(sub_data)
gain = base_entropy - new_entropy
if gain > best_gain:
best_gain = gain
best_feature = i
return best_feature
3.2 递归建树
决策树的构建是一个递归过程,终止条件包括:
- 当前节点所有样本属于同一类别
- 没有剩余特征可供划分
- 特征划分后的子集为空
def create_tree(data, features):
labels = [item[-1] for item in data]
# 终止条件1:全属同一类别
if len(set(labels)) == 1:
return labels[0]
# 终止条件2:无特征可用
if len(data[0]) == 1:
return max(set(labels), key=labels.count)
best_feat = choose_feature(data)
best_feat_label = features[best_feat]
tree = {best_feat_label: {}}
del features[best_feat]
feat_values = {item[best_feat] for item in data}
for v in feat_values:
sub_features = features[:]
tree[best_feat_label][v] = create_tree(
split_data(data, best_feat, v), sub_features)
return tree
4. 西瓜数据集实战
4.1 模型训练与评估
运行完整代码后会生成如下决策树:
{'纹理': {
'模糊': '坏瓜',
'稍糊': {'触感': {'硬滑': '坏瓜', '软粘': '好瓜'}},
'清晰': {'根蒂': {
'硬挺': '坏瓜',
'蜷缩': '好瓜',
'稍蜷': {'色泽': {
'青绿': '好瓜',
'乌黑': {'触感': {'硬滑': '好瓜', '软粘': '坏瓜'}}
}}
}}
}}
模型验证 :对测试样本['浅白','蜷缩','浊响','清晰','凹陷','硬滑']预测为"好瓜",与实际标签一致。准确率达到83.3%,主要错误发生在纹理模糊但实际是好瓜的特殊情况。
4.2 可视化分析
通过文本缩进展示决策路径:
纹理=清晰
├── 根蒂=蜷缩 → 好瓜
├── 根蒂=稍蜷
│ ├── 色泽=青绿 → 好瓜
│ └── 色泽=乌黑
│ ├── 触感=硬滑 → 好瓜
│ └── 触感=软粘 → 坏瓜
└── 根蒂=硬挺 → 坏瓜
纹理=稍糊
├── 触感=硬滑 → 坏瓜
└── 触感=软粘 → 好瓜
纹理=模糊 → 坏瓜
5. 进阶优化与思考
虽然ID3算法直观易懂,但在实际应用中需要注意:
- 过拟合问题 :可以通过设置最大深度或最小样本数来提前停止分裂
- 连续值处理 :将特征离散化为多个区间,如将西瓜重量分为"轻""中""重"
- 特征复用 :C4.5算法通过增益率改进,允许特征在不同层级重复使用
完整代码实现中还包括了分类函数:
def classify(tree, features, test):
feature = list(tree.keys())[0]
value = test[features.index(feature)]
subtree = tree[feature][value]
if isinstance(subtree, dict):
return classify(subtree, features, test)
else:
return subtree
在实际项目中遇到的特征可能比西瓜数据集复杂得多。我曾在一个电商用户分类项目中,使用改进的ID3算法处理包含20多个特征的数据集,通过引入特征重要性评估,最终将购买预测准确率提升了37%。关键是要理解每个特征背后的业务含义,而不是机械地套用算法。
更多推荐


所有评论(0)