
- 个人首页: 永远都不秃头的程序员(互关)
- C语言专栏:从零开始学习C语言
- C++专栏:C++的学习之路
- K-Means专栏:K-Means深度探索系列
- 本章所属专栏:决策树深度探索系列
文章目录
智慧之眼:信息增益是什么?
在决策树的构建过程中,每当我们到达一个节点,都需要决定接下来根据哪个特征进行划分。一个好的特征划分应该能够最大程度地减少当前数据集的混乱程度,使得划分后的子数据集变得更"纯净"。
信息增益 ,顾名思义,就是特征对数据集带来的信息量增加的程度 ,或者更直白地说,是由于使用了某个特征进行划分而导致数据集混乱程度(熵)的减少量。
核心思想 :
一个特征的信息增益越大,说明它在划分数据集时,使得数据的纯度提升得越多,混乱程度降低得越显著。因此,决策树在每个节点都会选择信息增益最大的特征来进行划分。
数学公式:
假设数据集 D D D 的熵为 H ( D ) H(D) H(D)。
用特征 A A A 划分数据集 D D D 后,根据特征 A A A 的不同取值 v v v,数据集被分成若干个子集 D v D_v Dv。
那么,特征 A A A 带来的信息增益 G a i n ( D , A ) Gain(D, A) Gain(D,A) 定义为:
G a i n ( D , A ) = H ( D ) − ∑ v ∈ V a l u e s ( A ) ∣ D v ∣ ∣ D ∣ H ( D v ) Gain(D, A) = H(D) - \sum_{v \in Values(A)} \frac{|D_v|}{|D|} H(D_v) Gain(D,A)=H(D)−v∈Values(A)∑∣D∣∣Dv∣H(Dv)
这里:
- H ( D ) H(D) H(D):是原始数据集 D D D 的熵(未划分前的混乱程度)。
- V a l u e s ( A ) Values(A) Values(A):是特征 A A A 所有可能的取值集合。
- D v D_v Dv:是数据集 D D D 中,特征 A A A 取值为 v v v 的样本组成的子集。
- ∣ D v ∣ |D_v| ∣Dv∣:子集 D v D_v Dv 中样本的数量。
- ∣ D ∣ |D| ∣D∣:原始数据集 D D D 中样本的总数量。
- H ( D v ) H(D_v) H(Dv):是子集 D v D_v Dv 的熵(划分后的混乱程度)。
- ∑ v ∈ V a l u e s ( A ) ∣ D v ∣ ∣ D ∣ H ( D v ) \sum_{v \in Values(A)} \frac{|D_v|}{|D|} H(D_v) ∑v∈Values(A)∣D∣∣Dv∣H(Dv):这部分是划分后所有子集的加权平均熵 。权重 ∣ D v ∣ ∣ D ∣ \frac{|D_v|}{|D|} ∣D∣∣Dv∣ 是每个子集在原始数据集中的比例。
直观理解 :
信息增益 = (划分前的熵)-(划分后所有子集的加权平均熵)。
这个差值越大,说明这个特征的划分效果越好,它"解决"了更多的不确定性。决策树的目标就是找出那个能最大化这个差值的特征。
特征选择:算法如何驱动决策?
信息增益是ID3(Iterative Dichotomiser 3)算法的核心驱动力。ID3算法就是通过不断计算每个可用特征的信息增益,选择增益最大的那个来构建树。这个过程使得决策树在每一步都能做出"最优化"的决策,快速而有效地将数据集分类。
举个例子,假设我们要决定"今天是否打网球":
原始数据集的熵可能很高(因为Yes和No都有)。
-
如果按"天气"划分:
- 晴天组:熵很低(比如很多No)
- 阴天组:熵很低(比如很多Yes)
- 下雨组:熵中等
- 计算出"天气"的信息增益。
-
如果按"温度"划分:
- 热组:熵中等
- 适中组:熵中等
- 凉爽组:熵中等
- 计算出"温度"的信息增益。
比较"天气"和"温度"的信息增益,哪个大就选哪个作为根节点的第一层划分。这个过程会递归地进行,直到叶节点足够纯净。
手撕代码:亲手计算信息增益,找出最佳划分特征!
现在,让我们再次回到我们的"是否打网球"数据集,亲手计算每个特征的信息增益,来找出哪个特征是最佳的根节点划分!
我们将使用上一篇中定义的 calculate_entropy 函数。
python
import math
import collections
# 示例数据集:Outlook, Temperature, Humidity, Wind, PlayTennis
# 数据格式:[特征1, 特征2, ..., 目标变量]
dataset = [
['Sunny', 'Hot', 'High', 'Weak', 'No'],
['Sunny', 'Hot', 'High', 'Strong', 'No'],
['Overcast', 'Hot', 'High', 'Weak', 'Yes'],
['Rain', 'Mild', 'High', 'Weak', 'Yes'],
['Rain', 'Cool', 'Normal', 'Weak', 'Yes'],
['Rain', 'Cool', 'Normal', 'Strong', 'No'],
['Overcast', 'Cool', 'Normal', 'Strong', 'Yes'],
['Sunny', 'Mild', 'High', 'Weak', 'No'],
['Sunny', 'Cool', 'Normal', 'Weak', 'Yes'],
['Rain', 'Mild', 'Normal', 'Weak', 'Yes'],
['Sunny', 'Mild', 'Normal', 'Strong', 'Yes'],
['Overcast', 'Mild', 'High', 'Strong', 'Yes'],
['Overcast', 'Hot', 'Normal', 'Weak', 'Yes'],
['Rain', 'Mild', 'High', 'Strong', 'No']
]
feature_names = ['Outlook', 'Temperature', 'Humidity', 'Wind']
target_index = 4 # 目标变量在数据集中的索引
# 引入上一篇中定义的熵计算函数
def calculate_entropy(data, target_idx):
"""
计算给定数据集的目标变量的熵。
参数:
data: 列表的列表,每个子列表代表一个样本。
target_idx: 目标变量在每个样本中的索引。
返回:
熵值 (float)。
"""
num_samples = len(data)
if num_samples == 0:
return 0.0
label_counts = collections.Counter(sample[target_idx] for sample in data)
entropy = 0.0
for label in label_counts:
prob = label_counts[label] / num_samples
if prob > 0: # 避免 log(0) 的情况
entropy -= prob * math.log2(prob)
return entropy
# 1. 计算信息增益的核心函数
def calculate_information_gain(data, feature_idx, target_idx):
"""
计算给定特征的信息增益。
参数:
data: 数据集。
feature_idx: 要计算信息增益的特征的索引。
target_idx: 目标变量的索引。
返回:
信息增益 (float)。
"""
initial_entropy = calculate_entropy(data, target_idx) # 计算划分前的熵
num_samples = len(data)
# 按照特征值分组
feature_value_groups = collections.defaultdict(list)
for sample in data:
feature_value = sample[feature_idx]
feature_value_groups[feature_value].append(sample)
weighted_avg_entropy = 0.0
for value, group_data in feature_value_groups.items():
prob = len(group_data) / num_samples # 子集在总数据集中的比例
weighted_avg_entropy += prob * calculate_entropy(group_data, target_idx) # 计算加权熵
return initial_entropy - weighted_avg_entropy # 原始熵减去加权平均熵
# --- 找出最佳划分特征! ---
print("🚀 开始计算每个特征的信息增益,寻找最佳决策问题...")
initial_entropy_dataset = calculate_entropy(dataset, target_index)
print(f"原始数据集的熵 H(D): {initial_entropy_dataset:.4f}\n")
best_gain = -1.0
best_feature_name = None
for i, f_name in enumerate(feature_names):
gain = calculate_information_gain(dataset, i, target_index)
print(f"特征 '{f_name}' 的信息增益 Gain(D, {f_name}): {gain:.4f}")
if gain > best_gain:
best_gain = gain
best_feature_name = f_name
print(f"\n 最佳划分特征是: '{best_feature_name}',其信息增益为: {best_gain:.4f}")
print(" 决策树会选择这个特征作为根节点(或当前节点)的第一个决策问题!")
# 进一步分析 "Outlook" 特征的划分情况
print("\n深入分析最佳特征 'Outlook' 的划分效果:")
outlook_groups = collections.defaultdict(list)
for sample in dataset:
outlook_groups[sample[0]].append(sample) # 0 是 Outlook 的索引
for value, group_data in outlook_groups.items():
group_labels = [s[target_index] for s in group_data]
group_entropy = calculate_entropy(group_data, target_index)
print(f" Outlook = '{value}': 样本数={len(group_data)}, 标签={collections.Counter(group_labels)}, 熵={group_entropy:.4f}")
print("\n你看到了吗?通过 'Outlook' 划分后,数据集的混乱程度(熵)显著降低了!")
print("这就是信息增益的魔力!")
代码解读:
-
calculate_entropy函数:- 这部分代码直接复用了上一篇中实现的熵计算函数,它是信息增益计算的基础。
-
calculate_information_gain(data, feature_idx, target_idx)函数:- 这是本篇的核心。它首先计算了整个数据集(未划分前)的初始熵
initial_entropy。 - 接着,它遍历给定特征
feature_idx的所有可能取值,将数据集划分为多个子集 (feature_value_groups)。 - 对于每个子集,它计算其熵,并乘以该子集在总数据集中的比例,得到加权平均熵
weighted_avg_entropy。 - 最后,用
initial_entropy减去weighted_avg_entropy,就得到了该特征的信息增益。
- 这是本篇的核心。它首先计算了整个数据集(未划分前)的初始熵
-
找出最佳划分特征:
- 我们遍历了
feature_names中的每一个特征,调用calculate_information_gain计算它们各自的信息增益。 - 通过比较这些增益值,我们找到了信息增益最大的特征,它就是决策树在第一层(根节点)会选择的"最佳决策问题"!在本例中,通常会是
Outlook。
- 我们遍历了
-
深入分析:
- 代码还进一步展示了,当使用
Outlook特征进行划分后,各个子集(如Sunny、Overcast、Rain)的样本组成和熵值。你会发现,Overcast组的熵直接降为0(因为它里面全是'Yes'),而Sunny和Rain组的熵也显著低于原始数据集的熵,这正是信息增益的直观体现!
- 代码还进一步展示了,当使用
通过这个实践,你是不是对信息增益这个决策树的"智慧之眼"有了更深刻、更直观的理解?你不仅掌握了它的数学原理,更亲手计算并验证了它的有效性!
结语与展望
在未来的"深度探索"系列中,我们将继续前进,探索更多高级主题:
- 信息增益率与Gini不纯度:除了ID3,还有C4.5和CART等算法,它们是如何选择最佳划分特征的?
- 连续型特征的处理:如何为数值型数据找到最佳的划分点?
- 决策树的剪枝:如何让树变得更"健壮",避免过拟合?
- 回归决策树:如何用决策树来预测连续值?
机器学习的道路充满了奇妙的数学和算法之美。保持这份好奇心,我们下篇再见!如果你有任何疑问或想分享你的见解,欢迎在评论区留言哦!