Softmax & Entorpy

本文最后更新于 2025年4月18日 晚上

问题来源

经常在保研经验帖中看到问softmax以及让手写softmax。我只知道有个softmax函数,以及是用在多分类任务里的,知之甚少,所以系统学习一番。

写完后发现有点简单,所以加个cross-entropy凑凑篇幅。

数学原理

Softmax函数定义:(来源于Grok3的回答,暂未在《机器学习》中找到相关描述)

Softmax函数将一个 \(K\) 维实数向量 \(\boldsymbol{z}=[z_1,z_2,...,z_K]\) 转化为概率分布: \[ \sigma(z)_i=\frac{\exp(z_i)}{\sum_{j=1}^K\exp(z_j)},\ i=1,2,...,K \]

比如,\([1.0, 2.0, 3.0]\)会转换为\([0.09, 0.24, 0.67]\),看起来,就是把一个类似权重的向量归一化成一串概率

性质:

  • 输出值在(0, 1)区间
  • 所有输出值的和为1
  • 指数函数放大输入差异,使较大值更接近1,较小值接近0

Softmax的输出可解释为类别 \(i\) 的条件概率: \[ P(y=i|x)=\frac{\exp(w_i^Tx+b_i)}{\sum_{j=1}^K\exp(w_j^Tx+b_j)} \] 其中 \(w_i\)\(b_i\) 是类别 \(i\) 的权重和偏置

Softmax通常与交叉熵损失(cross-entropy)结合使用,用于优化模型参数。假设真实标签为 \(y\) ,预测概率为 \(\hat{y}=\sigma(z)\),交叉熵损失为: \[ L=-\sum_{i=1}^{K} y_i\log(\hat{y_i}) \] 对于单样本,若正确类别为 \(k\)\(y_k=1\) ,其他 \(y_i=0\) ,损失简化为: \[ L=-\log(\hat{y_k})=-\log(\frac{\exp(z_k)}{\sum_{j=1}^K \exp(z_j)}) \]

计算Softmax和数值稳定性

在Python中计算给定向量上的softmax函数的简单实现是:

1
2
3
def softmax(x):
exps = np.exp(x)
return exps / np.sum(exps)

对于之前的示例 [1,2,3] ,输出 [ 0.09003057, 0.24472847, 0.66524096],但是如果数字更大,就会输出nan(not a number),因为Numpy使用的浮点数数值范围有限,所以可以将输入转换一下,避免大指数计算,由数学推导(见参考中的第一条)可得,在exp内部加减常数对结果没有影响,一般采用以下形式: \[ \sigma(z)_i=\frac{\exp(z_i-\max(z))}{\sum_{j=1}^K \exp(z_j-\max(z))} \] 实现:

1
2
3
4
def stablesoftmax(x):
shiftx = x - np.max(x, axis=-1, keepdims=True)
exps = np.exp(shiftx)
return exps / np.sum(exps, axis=-1, keepdims=True)
  • axis=-1:确保对每个样本的 logits 向量(最后一维)计算最大值,而不是对整个数组。这对于多维输入(例如批量数据)至关重要。没有的话,对于多维数据,就会取全局最大,比如[[1000, 2000, 3000], [4000, 5000, 6000]]会输出[[0., 0., 0.], [0., 0., 1.]]
  • keepdims=True:保持输出维度,不加的话,对于多维向量,可能把各行最大值的列向量给变成行向量,后面减法会出错。

但现在:

1
2
x = np.array([1000, 2000, 3000])
[ 0., 0., 1.]

虽然softmax不会输出0,但是差异太大,所以过于接近0.

some Entropy

信息熵(香农熵)

信息量的大小跟随机事件的概率有关,越小概率的事情发生了,产生的信息量越大

比如高中学过的 \(3\sigma\) 原则,当正态分布在 \(3\sigma\) 外的事件发生,就可以认为出现异常

而确定性时间没有信息量,所以概率越小,信息越大,给概率取个倒什么的就可以变成一种权重,于是有了 \(\frac{1}{P(x)}\) ,但是香农的定义还套了一层 \(\log_2\) ,套个对数的好处在于,如果多个独立事件都发生,可以化乘为加,更多具体原因见信息熵为什么要定义成-Σp*log(p)。所以,信息熵的定义是: \[ \begin{aligned} \text{Entropy} & =\sum_iP(i)\log_2\frac{1}{P(i)} \\ & =-\sum_iP(i)\log_2P(i) \end{aligned} \] 在连续变量上: \[ \text{Entropy}=-\int P(x)\log_2P(x)\,\mathrm{d}x \]

相对熵(KL散度)

用来衡量两个概率分布的差异性: \[ D_{KL}(p||q)=\sum_{i=1}^Np(x_i)\log(\frac{p(x_i)}{q(x_i)}) \] 由上式可见,相对熵没有对称性,也就是说 \(D_{KL}(p||q) \ne D_{KL}(q||p)\)

交叉熵

单个样本,预测分布p,真实分布q的交叉熵: \[ H(p,q)=\sum_{i=1}^np(x_i)\log\frac{1}{q(x_i)}=-\sum_{i=1}^np(x_i)\log q(x_i)=D_{KL}(p||q) + H(p) \] \(H(p,q)\) 指分布p和分布q的交叉熵,\(H(p)\) 指分布p的信息熵。

同样,交叉熵也没有对称性。

求平均交叉熵损失的简单实现:

1
2
3
4
5
6
7
8
def cross_entropy(predictions, targets, epsilon=1e-12):
# 取(epsilon, 1-epsilon)内的,防对数数值问题
predictions = np.clip(predictions, epsilon, 1.-epsilon)
# 样本数量
N = predictions.shape[0]
# target就是p,predict就是q,见上面定义式
ce = - np.sum(targets*np.log(predictions)) / N
return ce

区别联系和理解方法

  • 熵在以2为对数底时表示对事件中的随机变量编码所需的最小字节数

  • KL散度指使用B的编码来编A,所需要的额外所需的编码长度

  • 交叉熵指使用B作为密码来表示A时所需要的 “平均的编码长度”

\(H(p,q)=D_{KL}(p||q) + H(p)\) 可知,当p是个常数时,优化KL跟优化交叉熵是一样的。

当不是常数时,就有区别了,但这要根据具体使用场景看,我经验少,举不出例子,只知道一般都用交叉熵,KL在VAG、GAN中用到

参考

The Softmax function and its derivative - Eli Bendersky's website

一篇文章讲清楚交叉熵和KL散度 - 康斯坦丁的文章 - 知乎

KL散度(Kullback-Leibler Divergence)介绍及详细公式推导 | HsinJhao's Blogs


Softmax & Entorpy
http://43.143.57.238/2025/04/15/softmax/
作者
Leoo Yann
发布于
2025年4月15日
更新于
2025年4月18日
许可协议