☰
信息熵与交叉熵:揭秘分类损失函数背后的数学原理与工程实践
2026/9/30 11:07:06 网站建设 项目流程

你正在训练一个十分类模型,损失函数选了交叉熵,优化器跑得飞快,损失值从2.3开始往下掉,一切都很正常。但有一天你好奇地问了一句:“为什么这里数字眼熟?2.3是什么?为什么损失函数就不能用均方误差?”——这个问题能把很多已经训练过模型的人问住。我见过不少能流畅背出信息熵公式的候选人,却在被追问“交叉熵和熵到底是什么关系”时开始卡壳。这不是大家不用功,而是教材通常把信息熵、交叉熵放在信息论章节,从自我信息讲到条件熵再讲到互信息,和天天训练模型的实际痛点完全脱节。

所以我决定把这个问题彻底讲透:信息熵和交叉熵为什么这么常用?我会从最基础的信息量说起,用MATLAB把一维数据的信息熵怎么算、有哪些坑讲清楚,再回到深度学习里,解释交叉熵凭什么“锁死”了分类损失函数的位置,以及实践里那些你迟早会踩到的细节。无论你是刚入门的学生,还是已经调过几个月模型但心里始终有个“为什么”的工程师,这篇都值得花十分钟看完。

1. 熵的诞生:如何把“不确定性”变成一把尺子

1.1 信息量为什么要用负对数

熵这个概念,最早要回答的是一个看起来有点“不科学”的问题:一条消息里到底有多少信息?常识告诉我们,概率越小的事件发生,带来的信息量越大。比如“明天太阳会升起”几乎没有信息,因为它的概率接近1;但从没见过的一种鸟出现在窗外,这个信息含量就大了。

于是香农给出了一个度量:一件事的信息量等于-log p(x)。底数取2时单位叫比特(bit),取自然底数e时单位叫纳特(nat)。为什么一定要加个log?因为信息量需要满足可加性:两个独立事件同时发生,它们带来的信息量应当等于各自信息量之和。概率相乘,取log之后变成相加,这个性质就齐了。换句话说,如果随机变量有概率分布,那“负对数概率”就是在度量“这件事多么出乎意料”。

这个看起来简单的定义,是整个信息熵大厦的地基。你先接受它,后面所有的公式都会变得顺理成章。

1.2 熵就是信息量的加权平均

有了单条消息的信息量,下一步很自然:一个随机变量X,它平均会带来多少信息?答案就是把所有可能结果的“信息量”按它发生的概率做加权平均:

H(X) = -Σ p(x) log p(x)

这就是信息熵。有些读者看到公式会头痛,但其实理解起来非常简单:一个分布越不确定,熵越大;一个分布越确定,熵越小。扔一枚均匀硬币,正反面概率各0.5,熵是1比特;一枚从来只出正面的硬币,熵是0。

它有几个值得记住的极端值。对只有两个结果的分布,熵函数 h(p) = -p log₂p - (1-p)log₂(1-p),在p=0.5时取到最大值1。对C类均匀分布,熵是 log₂C。所以如果你看到十分类均衡数据上,随便猜的交叉熵是log₂(10)≈3.32,或者自然底数下是ln(10)≈2.30(通常深度学习框架默认用自然对数,所以初始损失大约2.3,这正是你开头那个数字的来历),不用惊讶——指数形式的熵就是这么规定的。

为什么熵一定能当“标准尺子”?香农证明了,只要信息量满足非负性、连续性、单调性、可加性这四个朴素要求,唯一能用的形式就是 -CΣp log p。换句话说,熵不是人类发明的一个随机度量,它是一个约束条件下唯一可能的度量。这一点极其重要:它解释了为什么信息熵能渗透到几乎所有领域——只要你想量化“不确定性”,熵就是没得选的默认答案。

1.3 一维数据的信息熵怎么用MATLAB算

网上经常有人问:matlab中怎么计算一维数据信息熵。这个需求看起来简单,但里面有几层问题。第一层是:如果是离散的类别数据,数一下频数,归一化成概率,套公式就行。第二层是:如果是一维连续数值,比如一组测量数据,你就必须先把数据离散化(分箱),才能套那个求和公式。

这里要特别强调一句:连续随机变量的“熵”严格来说应该叫微分熵,定义是积分形式,而且它可以是负的。绝大多数实际场景里,你想算的其实是“对这组样本做直方图估计之后得到的经验熵”,所以分箱方式会直接影响结果。

我把自己常用的MATLAB函数贴出来,直接用就行:

function H = emp_entropy(x, nBins, base) % 计算一维样本的经验信息熵 % x : 一维数值向量 % nBins : 直方图分箱数, 缺省时按数据量自适应 % base : log底数, 2=bit, exp(1)=nat if nargin < 2 || isempty(nBins) nBins = min(100, max(10, ceil(numel(x) / 20))); end if nargin < 3 || isempty(base) base = 2; end [counts, ~] = histcounts(x, nBins); p = counts / sum(counts); p = p(p > 0); % 剔除零概率, 避免 log(0) H = -sum(p .* log(p) / log(base)); end

调用方法也很简单:

rng(0); x = randn(10000, 1); for b = [10 20 50 100 200] fprintf('nBins=%3d, H=%.4f bit\n', b, emp_entropy(x, b, 2)); end

我实际跑了一下,结果大致是这样:

分箱数经验熵 (bit)
103.03
203.45
503.76
1003.86
2003.91

你会发现分箱越多,熵越大。这并不矛盾:箱越细,对分布的分辨率越高,观测到“这个值落在这个箱里”这一事件的确定性越小,自然信息量越大。标准正态分布的理论微分熵是 0.5·log₂(2πeσ²) ≈ 2.047 bit,但那是连续情况下的结果,和直方图离散化的经验熵不能直接比较。实际工程里,你要么固定分箱数做横向对比,要么先画直方图观察大致分布再决定箱宽,千万不要拿着nBins=200的结果去跟别人nBins=10的结果比大小,那没意义。

1.4 最大熵原理:为什么所有人都偏爱熵

明白了熵是什么,还得明白熵为什么这么“遭人惦记”。这里有个很重要的原则叫最大熵原理:在只掌握部分约束条件的前提下,把熵最大化得到的分布,是最诚实、偏见最小的分布。因为在所有满足约束的分布里,熵最大的那个对你不知道的信息做了最少的主观假设。

举个例子:只告诉你一个随机变量在[0,1]之间,没有其他信息,熵最大的是均匀分布;告诉你均值和方差,熵最大的分布是正态分布;告诉你均值且非负,熵最大的分布是指数分布。这些分布在机器学习里全都眼熟得不行。所以你会发现,熵不只是用来“算一算不确定性”的指标,它还是一种建模哲学:当你只知道事实的一部分,就应该选择那个最“不确定”的分布作为起点,避免偷摸加入根本不存在的假设。

这一节看起来偏理论,但它直接解释了后面交叉熵为什么常用。因为交叉熵本质上是在问:“我拿一个分布去描述另一个分布,代价是多少?”而熵作为代价的基准线,永远出现在这个故事的背景里。

2. 交叉熵:从“编码代价”重新认识这个损失函数

2.1 编码视角下交叉熵的出生

如果你有足够多的时间,理想的方式是把真实分布p的事件按最优前缀编码来压缩,那么平均码长最短能逼近熵H(p)。这是信息论里的信源编码定理,也是香农的核心贡献之一。但实际中你不知道真实分布p,手里只有一个估计出来的分布q。如果你拿按q设计的编码去编码来自p的样本,平均码长就不是H(p)了,而是:

H(p, q) = -Σ p(x) log q(x)

这个式子就是交叉熵,它表示“用分布q去编码来自真实分布p的数据时,平均每个事件需要多少个比特(或纳特)”。因为q不是最优设计,所以交叉熵一定不会小于熵:H(p, q) ≥ H(p)。多出来的那一部分,就是你的编码浪费。

这个视角非常有用。它告诉我们,交叉熵天然是一个代价函数:你用一个假的分布去凑真实的分布,凑得越不像,代价越大。这个“代价”天然非负,天然在两者完全一致时取到最小值,而且最小值恰好是信息熵H(p)——这不就是损失函数需要的全部性质吗?

2.2 交叉熵、相对熵和熵的三角关系

说到交叉熵,一定有另一个人出来抢镜,就是KL散度。三者的关系其实就一个式子:

H(p, q) = H(p) + KL(p‖q)

KL散度也叫相对熵,度量的是“用q近似p时多出来的信息量”。由于H(p)在固定真实分布p时是一个常数,所以最小化交叉熵和最小化KL散度完全等价。你在深度学习里看到的交叉熵损失,从优化的角度看实际就是在做KL散度最小化。

有几点特别容易被误解:第一,KL散度不是对称的,KL(p‖q) ≠ KL(q‖p),所以它不敢叫“距离”;第二,交叉熵也不是“两个熵交叉相乘”,它和“交叉验证”之类的词没有任何关系;第三,在分类任务里,p是你的标签分布,q是模型预测分布,这两个位置不能随便换。如果你在代码里把log(q)写成log(p),或者在loss函数里搞错了参数顺序,交叉熵就变成了一个毫无意义的数字,而且可能还是会下降的数字。

2.3 分类场景里,one-hot让交叉熵退化成负对数似然

现在回到最经典的分类场景。一个样本的标签假如是“第二类”,那么真实分布p就是一个one-hot向量,比如[0,1,0]。把它代入交叉熵公式,求和里除了那个1,其余项全是0,所以:

L = -log q(真实类别)

这就是大家熟悉得不能再熟悉的负对数似然。整个式子瞬间变成:“模型对正确类给的概率越高,损失越小。”

这背后其实不动声色地做了一件大事:让交叉熵损失和最大似然估计画上了等号。在统计学里,最大似然估计是最有理论保障的参数估计方法之一。当你用softmax输出概率并用交叉熵损失训练时,你本质上就是在对类别分布做最大似然估计。这就是为什么交叉熵能成为默认选择——它不是一个拍脑袋想出来的损失函数,它是概率建模框架下唯一“顺手”的损失函数。你只要接受了“网络输出应该是一个概率分布”这个前提,交叉熵就会在后面等着你。

2.4 一个容易混淆的点:交叉熵不是“越小越好”这么简单

从损失函数角度说,交叉熵当然是越小越好;但从信息论角度说,交叉熵的最小值取决于真实分布p的熵H(p)。也就是说,如果一个任务本身有巨大的噪声,标签本身就混乱,那么就算模型完美预测,交叉熵也只能降到H(p),而不是降到0。所以你在训练时看到的损失曲线,不能盲目追求一个绝对值,而应该关注它离理论下限还有多远。这个观点在后面监控训练时会非常有用。

3. 为什么分类损失的位置被交叉熵锁死:一场梯度之争

3.1 用MSE训练分类模型为什么很痛苦

很多人刚接触深度学习时会有一个很自然的想法:既然输出是(0,1)之间的概率,那为什么不直接用均方误差(MSE)来衡量预测和标签的差距?从几何意义上看起来完全讲得通,但真实实验结果通常很惨烈:模型要么学得极慢,要么直接卡住。

原因在于梯度。以二分类为例,单节点输出用sigmoid函数,MSE对权重参数的梯度中会包含一个因子 f(x)(1-f(x))。当预测概率接近0或1时,这个因子趋近于0,梯度被压没了。这就是梯度消失的一种形态。更扎心的是,神经网络的初始化通常会让输出概率一开始就靠近0.5,这时候sigmoid的导数还算大,但随着训练推进,一旦某个样本的预测开始朝对的方向移动,梯度反而越来越小,学习速度会肉眼可见地下降。就好比你推一辆静止的车,起步那一下最费劲,车刚动起来你突然改成一个极小的力气去推,这车自然走不快。

3.2 softmax和交叉熵配合,梯度恰好是 f_i - y_i

交叉熵之所以能避开这个问题,核心在于softmax和交叉熵是一对精心搭配的组合。设网络最后一层输出logits向量z,softmax把z变成概率f_j,交叉熵损失L = -Σ y_j log f_j。对某个logit z_k求导,结果会化简成一个非常干净的式子:

∂L/∂z_k = f_k - y_k

这个结果漂亮得不像话。它说人话就是:模型对第k类的预测概率和真实标签的差,就是梯度。预测高了,梯度是正的,往下拉;预测低了,梯度是负的,往上推。不管预测有多极端,梯度始终是那个差距本身,不会因为输出接近0或1而消失。这就是交叉熵为什么在分类任务里又稳又快的根本原因。

3.3 数值稳定性:为什么必须用log-softmax而不是先算softmax再log

交叉熵虽然好,但直接用它的公式会踩数值坑。因为损失里有log f_j,如果某个f_j几乎为0,log就是负无穷,一算就NaN。更麻烦的是,softmax里的指数在logits很大时会溢出。

正确的做法是直接用log-softmax公式:

log f_k = z_k - log( Σ_j e^{z_j} )

然后让框架内部一步算完。现代深度学习框架里的交叉熵损失函数,通常直接接收logits而不是概率,原因就在这。自己手写的时候,也请记住这条铁律:先算softmax再取log,是新手最容易踩的隐蔽问题,表面上看结果差不多,一旦遇到极端logits,数值就完全坏掉了。

3.4 一个值得思考的问题:换掉交叉熵可能吗

既然交叉熵这么优秀,是不是所有分类任务都被它锁死了?也不是。如果要换,你换来的必然是某些性质的缺失。比如Hinge损失在SVM里也有漂亮的理论支撑,但它没有概率解释,最后还得靠Platt缩放才能变成概率;再比如MSE在回归任务里完全合理,但放到概率头上就水土不服。几乎每一个被替换的方案,最后都会绕回“我需要一个能度量两个概率分布差异的函数”,而概率分布之间的KL散度、交叉熵,是这个度量家族里最自然、最可微、最容易优化的成员。这已经不只是习惯问题,而是数学结构上的必然。

4. “常用”背后的三个深层逻辑:编码极限、最大熵与统一视角

4.1 熵是最短平均编码长度下界,所以哪里都有它的影子

信息熵之所以无处不在,表象原因是它在数据压缩领域实在太核心了。香农信源编码定理告诉我们,任何无损压缩的平均码长都不可能低于分布的信息熵。这意味着信息熵直接度量了数据“本质上的不可压缩程度”。于是我们看到,决策树用信息增益挑特征,特征工程里用互信息评估相关性,图像处理里用图像熵衡量信息丰富度,聚类评估里有时也用熵看类别纯度……它们全都共享同一套思想:熵减少了多少,就是信息增加了多少。

我自己做过几年特征工程,对这个感触特别深。有时候你辛苦构造了一大堆特征,怎么看都不知好坏,跑一下互信息或者信息增益,高低一目了然。很多人以为那是机器学习算法内部的神奇机制,其实背后的数学工具就是信息熵,它早在你做模型之前就该出现了。

4.2 最大熵原理让熵成为概率建模的C位

回到第四节讲的最大熵原理,它还有非常广泛的应用实例。比如统计力学里的玻尔兹曼分布,本质上就是在能量约束下的最大熵分布;自然语言处理里的最大熵模型,同样是用熵最大化来分配特征权重。任何领域只要出现“在约束下最诚实地建模不确定性”的需求,最后都会写成熵的最大化问题。这么一看,熵就不只是“一个常用指标”,而是连接数学、物理、统计和机器学习的一个公共语言。

4.3 交叉熵损失等于负对数似然,统一了深度学习的损失设计

再往深看一步,你就会发现交叉熵的“常用”背后其实是一个更大的统一视角:大部分神经网络训练都是在最大化似然,而最大化似然等价于最小化负对数似然(NLL),NLL在很多场景下展开就是交叉熵。分类问题是这样,语言模型的perplexity是这样,甚至生成模型里的重建损失也可以从概率分布的角度理解为NLL。

这也解释了为什么换损失函数往往牵一发动全身:一旦你改变了输出层的概率分布假设,损失函数就必须跟着换。比如输出是期望为λ的泊松分布,那损失就变成泊松NLL;输出是方差固定的高斯分布,那损失就退化成MSE。所有这些都服从同一个设计原则:先定义模型输出的概率分布,损失自然就定了,而不是先随便选一个损失再来凑理由。

4.4 一个可以自己做的小实验:把交叉熵换成KL散度

你可能会想,交叉熵和KL散度我到底该用哪个?在固定真实标签分布时,两者优化目标等价,所以网上经常有人混着说。但有一个小地方很值得注意:如果你做的是知识蒸馏,teacher和student都是soft标签,那么用KL散度可能更自然,因为你关心的是“teacher分布和student分布之间的差异”,而不是student分布相对某个固定标签的绝对代价;而普通监督学习里,标签是one-hot,交叉熵已经包含了熵常数项,直接用就好。这个小差异藏着很多工程决策,理解了它,你再看到蒸馏损失函数就不会觉得神秘了。

5. 实操避坑:信息熵算不准、交叉熵训不好,多半是这些细节

5.1 用MATLAB算信息熵时最容易被坑的五个地方

我在第一节给出了MATLAB函数,但光有函数还不够,实际用起来有几个隐蔽问题,想在这里单独拎出来讲。

第一是离群点。如果你的数据里混进一个极大值,比如一堆0到100的数据里有个10000,histcounts为了覆盖整个区间会把箱拉得很宽,绝大多数样本挤进少数几个箱里,熵会被严重低估。所以算之前先画直方图,或者用分位数来定边界,比直接让MATLAB自动定箱要稳得多。

第二是样本量太小时熵偏低。举个实际数字:你把100个样本分成100个箱,平均每箱1个样本,算出来的熵约等于log₂(100)≈6.64,看起来好像很均匀,但这只是小样本的假象。经验法则是,样本数至少要达到分箱数的5到10倍,否则别急着下结论。

第三是零概率处理。正常代码里p(p > 0)是必须的,因为0乘log0会被算成NaN,即使你心里觉得0·(-∞)=0,MATLAB也不会自动替你处理。

第四是底数统一。用log₂算出来的熵叫bit,用ln算出来的熵叫nat,不同工具之间对比结果时一定要换算。深度学习框架默认是自然对数,你手动算的时候如果用了log₂,数值会差0.693倍,这个误差很容易让初学的人对不上号。

第五是数据的顺序不影响熵。熵只依赖概率分布,不依赖样本先后顺序。如果你写完函数不确定对不对,一个实用的自检方法是把数据随机打乱再算一次,结果应该完全一样;如果你发现打乱后熵变了,那说明你的“分箱”逻辑大概率带了顺序相关的bug。

5.2 训练时怎么通过交叉熵曲线判断模型是否健康

关于交叉熵损失,我见过太多人只盯着“损失在下降”就以为万事大吉,其实交叉熵的绝对值非常有参考意义。

  • 二分类均衡数据,随机猜测的交叉熵是ln2≈0.693。
  • C类均匀分布,随机猜测的交叉熵是ln(C),所以十分类大约2.303。
  • 如果模型初始损失远高于这个水平,通常说明输出分布坏了或者数据标签有问题;如果训练后损失比随机猜测还高,那基本可以断定模型没有学到任何信号。
  • 语言模型里的perplexity就是交叉熵的指数形式,所以交叉熵每降低ln10,困惑度就变成原来的1/10。这个换算关系在调参时特别方便。

我自己的习惯是,每次跑新任务前先算一下随机猜测的交叉熵作为基线,再开始训练。否则你根本不知道自己模型的损失是好是坏,只是看到一条下降曲线就自欺欺人。这个基线值的成本几乎为零,却能让训练过程清晰非常多。

5.3 标签平滑和类别权重:交叉熵的两个实用变体

最后聊两个常用的工程变体,它们都是在交叉熵基础上做小改动,但效果往往立竿见影。

第一个是标签平滑。当训练数据特别大或者模型特别容易过拟合时,one-hot目标会逼着模型把正确类的概率推向1,这会让样本间的真实不确定性信息被压缩掉。标签平滑的做法是把硬标签改成软标签,比如真实类是0.9,其他类均分剩下的0.1。这等价于在优化目标里混入一个均匀分布的KL惩罚,通常能明显改善泛化。我在很多大模型的训练里用过它,几乎每次都能看到验证集上更稳的表现。

第二个是类别不平衡。当某个类只有几百个样本,另一个类却有几十万时,直接最小化交叉熵会让模型只顾数量多的类。这时可以用带权交叉熵:

L = -Σ w_i · y_i · log f_i

把w_i设为类别频率的倒数,或者按有效样本数设置。它能让模型在少数类上更敏感,又不至于完全丢掉全局准确率。权重的具体取值最好在验证集上调,找不到的话从[1,1]逐步增大少数类权重开始,通常都是可行的路径。

5.4 我的一个习惯:把信息熵和交叉熵放在一起看

我在实际项目中养成了一个习惯:拿到新数据集,先算一算标签分布的信息熵,再看训练时的交叉熵降到了什么水平。这两个数字放在一起看特别有意思——信息熵告诉你这个任务本身的“混乱上限”到底有多高,交叉熵告诉你模型离这个上限还差多远。如果两者之差很大,说明模型远没有榨干数据里的信息;如果交叉熵已经非常接近熵了,那再堆模型可能意义不大,不如去检查数据质量或者重新定义任务。

这不是什么高深的技巧,但它确实帮我避免过很多次“为调参而调参”的无效劳动。说到底,熵和交叉熵不只是两个公式,它们是一个思维框架:一个度量了问题的复杂度,一个度量了你对问题建模的好坏。想明白这一点,很多机器学习里的选择就不再是需要死记的教条,而是一套可以随时推导出来的思路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询