【决策树深度探索(四)】揭秘“混乱”:香农熵与信息纯度的量化之旅



文章目录


"混乱"的量化:什么是香农熵?

在信息论中, 是一个核心概念,它由克劳德·香农(Claude Shannon)提出,用来量化一个信息源(或数据集)所包含的平均信息量,或者说它的不确定性(uncertainty)或混乱程度(disorder)

想象一下,你面前有一个装满球的箱子:

  • 场景一 :箱子里所有的球都是红色的。这时,你闭着眼睛摸一个球,你百分之百确定会摸到红球。这个系统的不确定性为零,信息量很小。
  • 场景二 :箱子里一半红球,一半蓝球。这时,你闭着眼睛摸一个球,你摸到红球或蓝球的可能性都是一半。这个系统不确定性最高,信息量最大(因为结果最难预测)。
  • 场景三:箱子里25%红球,75%蓝球。不确定性介于场景一和场景二之间。

香农熵就是用来精确描述这种"不确定性"的数学工具。一个系统的熵越高,表示它的不确定性越大,混乱程度越高,所包含的信息量也越大;反之,熵越低,表示系统越确定,越纯净,信息量越小。

核心思想:

熵与"惊喜"程度成正比。如果某个事件发生的概率很高,那么它发生了就不足为奇,提供的信息量很小。反之,如果某个事件发生的概率很低,但它却发生了,我们会感到"惊喜",这说明它提供了大量的信息。熵就是这种"惊喜"程度的平均值。

数学公式:

对于一个具有 n n n 种可能结果(或类别)的离散随机变量 X X X,其每个结果 x i x_i xi 发生的概率为 P ( x i ) P(x_i) P(xi),那么它的熵 H ( X ) H(X) H(X) 定义为:

H ( X ) = − ∑ i = 1 n P ( x i ) log ⁡ 2 ( P ( x i ) ) H(X) = - \sum_{i=1}^{n} P(x_i) \log_2(P(x_i)) H(X)=−i=1∑nP(xi)log2(P(xi))

这里:

  • P ( x i ) P(x_i) P(xi) 是类别 i i i 在数据集中出现的概率。
  • log ⁡ 2 \log_2 log2 表示以2为底的对数。这是因为在信息论中,信息通常用二进制位(bit)来度量。
  • 负号是为了确保熵为正值,因为 P ( x i ) P(x_i) P(xi) 总是介于0和1之间,其对数是负数或零。

直观理解

  • 当所有样本都属于同一类别时(例如, P ( x 1 ) = 1 , P ( x i ) = 0 P(x_1)=1, P(x_i)=0 P(x1)=1,P(xi)=0 for i ≠ 1 i \neq 1 i=1),熵为0,表示完全确定,无混乱。
  • 当所有类别概率均等时(例如,二分类问题中 P ( x 1 ) = 0.5 , P ( x 2 ) = 0.5 P(x_1)=0.5, P(x_2)=0.5 P(x1)=0.5,P(x2)=0.5),熵达到最大值(对于二分类是1 bit),表示不确定性最高,最混乱。

决策树与纯度:熵的妙用

在决策树中,我们希望构建一个能够将数据划分得越来越纯净 的模型。一个纯净的节点意味着所有样本都属于同一个类别,这样我们就能给出明确的分类结果。而,正是衡量这种"纯净度"的完美指标!

  • 高熵节点:表示该节点下的样本类别混杂,不确定性高,需要进一步划分。
  • 低熵节点(最好是0):表示该节点下的样本类别单一,非常纯净,可以作为叶子节点,给出最终分类。

决策树的生长过程,本质上就是不断寻找最佳划分点,以最大程度地降低数据集的熵 ,从而提高其纯度的过程。这个"熵的降低量"就是我们之前提到的信息增益(Information Gain)

手撕代码:亲手计算熵,感受"混乱"的量化!

让我们通过代码,亲手计算不同数据集的熵,来直观感受熵是如何量化纯度的。我们将使用第一篇中"是否打网球"的数据集片段进行演示。

python 复制代码
import math
import collections

# 示例数据集:Outlook, Temperature, Humidity, Wind, PlayTennis
# 数据格式:[特征1, 特征2, ..., 目标变量]
# target_index = 4 # 目标变量在数据集中的索引

# 假设有两个子数据集,我们来计算它们的熵
# 子数据集1:比较混乱,包含Yes和No
data_subset_1 = [
    ['Rain', 'Mild', 'High', 'Weak', 'Yes'],
    ['Rain', 'Cool', 'Normal', 'Weak', 'Yes'],
    ['Rain', 'Cool', 'Normal', 'Strong', 'No'],
    ['Rain', 'Mild', 'Normal', 'Weak', 'Yes'],
    ['Rain', 'Mild', 'High', 'Strong', 'No']
]
# 期望:Yes:3, No:2 -> 熵应该较高

# 子数据集2:相对纯净,大部分是Yes
data_subset_2 = [
    ['Overcast', 'Hot', 'High', 'Weak', 'Yes'],
    ['Overcast', 'Cool', 'Normal', 'Strong', 'Yes'],
    ['Overcast', 'Mild', 'High', 'Strong', 'Yes'],
    ['Overcast', 'Hot', 'Normal', 'Weak', 'Yes']
]
# 期望:Yes:4 -> 熵应该为0 (非常纯净)

# 子数据集3:极端混乱,一半Yes一半No
data_subset_3 = [
    ['Sunny', 'Hot', 'High', 'Weak', 'No'],
    ['Sunny', 'Cool', 'Normal', 'Weak', 'Yes'],
]
# 期望:Yes:1, No:1 -> 熵达到最大值1

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
        # 避免 log(0) 的情况,虽然在 Counter 中不会出现 prob=0 的情况
        if prob > 0:
            entropy -= prob * math.log2(prob)
    return entropy

print(" 开始计算不同数据集的熵...")

# 计算子数据集1的熵
entropy1 = calculate_entropy(data_subset_1, target_idx=4)
print(f"\n数据集1 (Yes:3, No:2) 的熵: {entropy1:.4f}") # 期待结果接近 0.971

# 计算子数据集2的熵
entropy2 = calculate_entropy(data_subset_2, target_idx=4)
print(f"数据集2 (Yes:4) 的熵: {entropy2:.4f}") # 期待结果为 0 (纯净)

# 计算子数据集3的熵
entropy3 = calculate_entropy(data_subset_3, target_idx=4)
print(f"数据集3 (Yes:1, No:1) 的熵: {entropy3:.4f}") # 期待结果为 1 (最混乱)

# 进一步体验:随机生成一个混合度的数据
print("\n 进一步体验:不同比例的类别熵值变化")
total_samples = 100
for yes_count in [0, 10, 25, 50, 75, 90, 100]:
    no_count = total_samples - yes_count
    
    # 构造模拟数据 (只关心标签)
    simulated_data = [['_','_','_','_','Yes']] * yes_count + \
                     [['_','_','_','_','No']] * no_count
    
    sim_entropy = calculate_entropy(simulated_data, target_idx=4)
    print(f"  Yes:{yes_count}, No:{no_count} -> 熵: {sim_entropy:.4f}")
    if yes_count == 0 or yes_count == 100:
        print("    (这是纯净的节点,熵为0!)")
    elif yes_count == 50:
        print("     (这是最混乱的节点,熵为1!)")

print("\n 你现在应该能清晰地看到,当一个数据集越"纯净"时,其熵值越低;越"混乱"时,其熵值越高!")
代码解读:
  1. calculate_entropy(data, target_idx) 函数

    • 这个函数是我们今天的主角,它严格按照香农熵的公式来实现。
    • 首先,它统计了数据集中目标变量(例如"PlayTennis")各个类别的出现次数 (label_counts)。
    • 然后,对于每个类别,计算其概率 prob(即该类别数量 / 总样本数)。
    • 最后,将每个 prob * log2(prob) 的结果累加起来,并取负值,就得到了最终的熵。
  2. 不同数据集的熵计算

    • 我们精心准备了三个不同"纯净度"的子数据集。
    • data_subset_1 混杂程度中等(Yes:3, No:2),其熵值约为0.971。
    • data_subset_2 完全纯净(Yes:4),其熵值完美地为0。
    • data_subset_3 极度混乱(Yes:1, No:1),其熵值完美地达到了二分类的最大值1。
  3. 进一步体验:通过模拟不同比例的 Yes/No 样本,我们可以更直观地观察到,当 Yes 和 No 数量接近时,熵值最高;当其中一方占据绝对优势时,熵值接近0。

通过这个实践,你是不是对熵这个概念有了更深刻的理解?你不仅看到了它的数学公式,更亲手计算了它,感受到了它在量化"混乱"和"纯度"上的强大之处!

结语与展望

此刻,你已经牢牢掌握了决策树的理论根基,对"信息增益"的理解也更为深刻(毕竟它就是熵的减少量!)。

在接下来的"深度探索"系列中,我们将继续深入:

  • 信息增益率与Gini系数:除了信息熵,还有哪些特征划分标准?各自优势何在?
  • 连续特征处理:如何为数值型数据寻找最优分割点?
  • 决策树剪枝:如何提升模型泛化能力,防止过拟合?
  • 回归树模型:决策树如何应用于连续值预测?

相关推荐
永远都不秃头的程序员(互关)2 小时前
【决策树深度探索(三)】树的骨架:节点、分支与叶子,构建你的第一个分类器!
算法·决策树·机器学习
Σίσυφος19002 小时前
OpenCV - SVM算法
人工智能·opencv·算法
臭东西的学习笔记8 小时前
论文学习——机器学习引导的蛋白质工程
人工智能·学习·机器学习
清酒难咽8 小时前
算法案例之递归
c++·经验分享·算法
让我上个超影吧8 小时前
【力扣26&80】删除有序数组中的重复项
算法·leetcode
张张努力变强9 小时前
C++ Date日期类的设计与实现全解析
java·开发语言·c++·算法
沉默-_-10 小时前
力扣hot100滑动窗口(C++)
数据结构·c++·学习·算法·滑动窗口
钱彬 (Qian Bin)10 小时前
项目实践19—全球证件智能识别系统(优化检索算法:从MobileNet转EfficientNet)
算法·全球证件识别
feifeigo12310 小时前
基于EM算法的混合Copula MATLAB实现
开发语言·算法·matlab