作业2:用 Python 实现 ID3 与 C4.5 决策树
·
一、决策树基础认知
决策树是一种直观易懂的分类算法,核心是通过对特征的逐步划分,将数据集拆解为纯度越来越高的子集,最终形成可用于预测的树形结构。
- 核心思想:类似生活中的 “if-else” 判断逻辑,每个内部节点代表一个特征判断,每个分支代表判断结果,每个叶节点代表最终分类。
- 关键问题:如何选择最优特征进行划分?ID3 和 C4.5 的核心区别就在特征选择准则不同。
- 实战数据:本文使用贷款申请数据集,包含 16 条样本,特征包括 “年龄段”“有工作”“有自己的房子”“信贷情况”,目标是预测 “是否给贷款”。
二、ID3 与 C4.5 核心原理
2.1 ID3 算法:基于信息增益
- 核心准则:选择信息增益最大的特征作为当前划分节点。
- 信息熵:衡量数据集的混乱程度,熵值越小,数据纯度越高。公式为:Ent(D)=−∑k=1Kpklog2pk(pk是第 k 类样本占比)。
- 信息增益:划分后数据集熵值的减少量,公式为:Gain(D,a)=Ent(D)−∑v=1V∣D∣∣Dv∣Ent(Dv)(a 是特征,Dv是特征 a 取第 v 个值的子集)。
- 优缺点:简单易实现,但倾向于选择取值多的特征,对噪声敏感。
2.2 C4.5 算法:基于信息增益比
- 核心准则:解决 ID3 的偏向性问题,选择信息增益比最大的特征。
- 信息增益比:公式为:Gain_ratio(D,a)=Gain(D,a)/,其中IV(a)=−∑v=1V(∣Dv∣/∣D∣)log2(∣Dv∣/∣D∣),(IV (a) 是特征 a 的固有值,取值越多则 IV (a) 越大)。
- 优缺点:平衡了特征取值数量的影响,泛化能力更强,是 ID3 的改进版。
三、Python 实战实现(初学者友好)
3.1 环境准备
无需复杂依赖,仅用 Python 内置库:
import numpy as np
import pandas as pd
from math import log2
3.2 数据预处理
先加载贷款数据集,将文字特征转换为机器可识别的数值(也可直接用文字特征计算):
# 构建数据集(对应文档中数据表)
data = {
'年龄段': [0,0,0,0,0,1,1,1,1,1,2,2,2,2,2,2], # 0=青年,1=中年,2=老年
'有工作': [0,0,1,1,0,0,0,1,0,0,0,0,1,1,0,0], # 0=否,1=是
'有自己的房子': [0,0,0,1,0,0,0,1,1,1,1,1,0,0,0,0], # 0=否,1=是
'信贷情况': [0,1,1,0,0,0,1,1,2,2,2,1,1,2,0,2], # 0=一般,1=好,2=非常好
'是否给贷款': [0,0,1,1,0,0,0,1,1,1,1,1,1,1,0,0] # 0=否,1=是
}
df = pd.DataFrame(data)
X = df.iloc[:, :-1] # 特征
y = df.iloc[:, -1] # 标签
3.3 核心函数实现
(1)计算信息熵
def calc_entropy(y):
"""计算信息熵"""
count = y.value_counts() # 统计每个类别出现次数
entropy = 0.0
total = len(y)
for num in count:
p = num / total
entropy -= p * log2(p) # 熵的公式
return entropy
(2)ID3 特征选择(信息增益)
def select_feature_id3(X, y):
"""ID3:选择信息增益最大的特征"""
base_entropy = calc_entropy(y)
max_gain = 0.0
best_feature = -1
# 遍历所有特征
for i in range(X.shape[1]):
feature_values = X.iloc[:, i].unique() # 该特征的所有取值
new_entropy = 0.0
# 计算该特征每个取值的条件熵
for val in feature_values:
subset_y = y[X.iloc[:, i] == val]
weight = len(subset_y) / len(y)
new_entropy += weight * calc_entropy(subset_y)
# 计算信息增益
gain = base_entropy - new_entropy
# 更新最大增益和最优特征
if gain > max_gain:
max_gain = gain
best_feature = i
return best_feature, max_gain
(3)C4.5 特征选择(信息增益比)
def calc_iv(X, feature_idx):
"""计算特征的固有值IV"""
feature_values = X.iloc[:, feature_idx].unique()
iv = 0.0
total = len(X)
for val in feature_values:
subset_size = len(X[X.iloc[:, feature_idx] == val])
p = subset_size / total
iv -= p * log2(p)
return iv
def select_feature_c45(X, y):
"""C4.5:选择信息增益比最大的特征"""
base_entropy = calc_entropy(y)
max_gain_ratio = 0.0
best_feature = -1
for i in range(X.shape[1]):
# 先计算信息增益
feature_values = X.iloc[:, i].unique()
new_entropy = 0.0
for val in feature_values:
subset_y = y[X.iloc[:, i] == val]
weight = len(subset_y) / len(y)
new_entropy += weight * calc_entropy(subset_y)
gain = base_entropy - new_entropy
# 计算信息增益比(避免IV为0的情况)
iv = calc_iv(X, i)
gain_ratio = gain / iv if iv != 0 else 0
# 更新最优特征
if gain_ratio > max_gain_ratio:
max_gain_ratio = gain_ratio
best_feature = i
return best_feature, max_gain_ratio
(4)构建决策树(递归实现)
def majority_vote(y):
"""当所有特征都用完时,返回出现次数最多的类别(投票机制)"""
return y.value_counts().idxmax()
def build_tree(X, y, feature_names, method='ID3'):
"""递归构建决策树"""
# 1. 若所有样本属于同一类别,返回该类别
if len(y.unique()) == 1:
return y.iloc[0]
# 2. 若没有特征可划分,返回投票结果
if X.empty:
return majority_vote(y)
# 3. 选择最优特征
if method == 'ID3':
best_idx, _ = select_feature_id3(X, y)
else: # C4.5
best_idx, _ = select_feature_c45(X, y)
best_feature = feature_names[best_idx]
# 4. 构建树节点
tree = {best_feature: {}}
# 5. 移除已使用的特征
new_feature_names = feature_names.copy()
new_feature_names.pop(best_idx)
# 6. 遍历最优特征的所有取值,递归构建子树
feature_values = X.iloc[:, best_idx].unique()
for val in feature_values:
subset_X = X[X.iloc[:, best_idx] == val].drop(X.columns[best_idx], axis=1)
subset_y = y[X.iloc[:, best_idx] == val]
tree[best_feature][val] = build_tree(subset_X, subset_y, new_feature_names, method)
return tree
3.4 训练与测试
# 特征名称(用于树的可视化)
feature_names = ['年龄段', '有工作', '有自己的房子', '信贷情况']
# 1. 训练ID3决策树
id3_tree = build_tree(X, y, feature_names, method='ID3')
print("ID3决策树:")
print(id3_tree)
# 2. 训练C4.5决策树
c45_tree = build_tree(X, y, feature_names, method='C4.5')
print("\nC4.5决策树:")
print(c45_tree)
# 3. 简单预测函数
def predict(tree, feature_names, sample):
"""预测单个样本"""
root = list(tree.keys())[0]
root_idx = feature_names.index(root)
val = sample[root_idx]
subtree = tree[root][val]
if isinstance(subtree, dict):
return predict(subtree, feature_names, sample)
else:
return subtree
# 测试样本(来自testset.txt,转换为[年龄段,有工作,有自己的房子,信贷情况])
test_samples = [
[0,0,0,1], [0,1,0,1], [1,0,1,2],
[1,0,0,1], [2,1,0,2], [2,0,0,0], [2,0,0,2]
]
print("\n预测结果:")
for sample in test_samples:
id3_pred = predict(id3_tree, feature_names, sample)
c45_pred = predict(c45_tree, feature_names, sample)
print(f"样本{sample}:ID3预测{id3_pred},C4.5预测{c45_pred}")
四、结果解读与可视化
4.1 输出结果说明
- 训练后会得到嵌套字典形式的决策树,例如 ID3 可能先以 “有自己的房子” 为根节点(信息增益最大),再依次划分其他特征。
- 测试集预测结果中,ID3 和 C4.5 可能因特征选择顺序不同出现细微差异,但整体分类效果相近。
五、常见问题与总结
5.1 常见坑点
- 特征取值为 0 时的处理:计算信息增益比时需避免 IV=0(本文已处理)。
- 递归终止条件:必须判断 “所有样本同类别” 或 “无特征可划分”,否则会无限递归。
- 数据格式:确保 X 和 y 是 DataFrame/Series,避免数组索引混乱。
5.2 算法对比总结
| 算法 | 特征选择准则 | 优点 | 缺点 |
|---|---|---|---|
| ID3 | 信息增益 | 简单易实现、计算快 | 偏向取值多的特征 |
| C4.5 | 信息增益比 | 平衡特征取值影响 | 计算量略大 |
5.3 后续优化方向
- 剪枝处理:解决过拟合(预剪枝 / 后剪枝)。
- 连续值处理:C4.5 支持连续特征离散化,可扩展实现。
- 可视化工具:使用
matplotlib或graphviz绘制更专业的树图。
更多推荐


所有评论(0)