读 《d2l:Chapter3. 线性神经网络 》随笔

讲一下Softmax回归

先说说自带mnist等库中的one-hot encoding
就是:一个向量,分量 (Row) == 类别
如:
A、B、C
对应 \((1, 0, 0)\)\((0, 1, 0)\)\((0, 0, 1)\)

softmax的用处:

\(softmax\)运算将输出变换成一个合法的类别预测分布

\(O = XW+b\)
\(Y=softmax(O)\)

\(Softmax\)计算,可知

\(Loss(y, \hat y)=log\sum_{k=1}^qexp(o_k)-\sum_{j-1}^qy_jo_j\)

拓展:
CrossEntropyError
我们可以把交叉熵想象为“一个主观相信分布为为 \(Q\) 的观察者,在亲眼看到来自真实分布 \(P\) 的数据时,所感受到的平均惊异程度。
描述实际输出(概率)与期望输出(概率)的距离。交叉熵的值越小,两个概率分布就越接近。
(这里我交了PR,试图改掉原书中的这段落原本稀里糊涂的描述,但是似乎这本书的项目21年后没有更新了qwq)

极大似然估计(MLE)

SoftMax 怎么来的?

先说Softmax函数:

\[\hat{y}_j = \text{softmax}(o)_j = \frac{\exp(o_j)}{\sum_{k=1}^{q} \exp(o_k)} \]

其中:

  • \(o_j\):模型原始的"得分"(logits),没有归一化,可以是任意实数
  • 使用\(exp\)将得分正数化
  • 把模型的原始输出转换成概率

再说交叉熵损失函数:

\[l(y, \hat{y}) = -\sum_{j=1}^{q} y_j \log \hat{y}_j \]

这是损失函数,衡量预测有多"糟糕",值越小说明预测越准。

  • 其中 \(y_j\) 为真实标签中第\(j\)个类别的值(0或1)
  • \(\hat y_j\) 为 模型预测的第\(j\)个类别的概率(0到1之间)
  • \(q\) 为总数据库中的类别总数
  • 而因为概率在0-1之间,\(log\) 值为负,加负号让损失为正
    如果真实标签是 y = [1, 0, 0],而模型预测 ŷ = [0.7, 0.2, 0.1]
    则有如下计算:

\[ \begin{equation}\begin{split} l(y, ŷ) &= -1×log(0.7) + 0×log(0.2) + 0×log(0.1)\\& = -log(0.7)\\& ≈ 0.357\end{split}\end{equation} \]

合起来就是: 在多分类问题中,我们先用softmax把模型的原始输出转成概率分布,然后用交叉熵损失来衡量这个概率分布与真实标签的差异。

\[\begin{equation}\begin{split} l(y, \hat{y})&=-\sum_{j=1}^qy_jlog\hat y_j \\&=-\sum_{j=1}^{q} y_j \log \frac{\exp(o_j)}{\sum_{k=1}^{q} \exp(o_k)} \\ &= -\sum_{j=1}^{q} y_j \left( o_j - \log \sum_{k=1}^{q} \exp(o_k) \right) \\ &= -\sum_{j=1}^{q} y_j o_j + \sum_{j=1}^{q} y_j \log \sum_{k=1}^{q} \exp(o_k) \\ &= \log \sum_{k=1}^{q} \exp(o_k) - \sum_{j=1}^{q} y_j o_j \end{split} \end{equation} \]

然而,当 \(o_k\) 非常大时,很可能导致 \(softmax\) 值超出数据范围,甚至出现 \(NaN\) 。这就是 上溢 。为此,我们可以在每一项减去一个\(max(o_k)\)

\[\begin{split}\begin{aligned} \hat y_j & = \frac{\exp(o_j - \max(o_k))\exp(\max(o_k))}{\sum_k \exp(o_k - \max(o_k))\exp(\max(o_k))} \\ & = \frac{\exp(o_j - \max(o_k))}{\sum_k \exp(o_k - \max(o_k))}. \end{aligned}\end{split} \]

但此时,会出现一些很小的负数。故 \(\hat y_j\) 有可能为 \(0\) ,从而 \(\log(\hat y_j)\)\(-\inf\) 。这对反向传播非常不利。这就是 下溢
为此,我们避免计算 \(\exp(o_j - \max(o_k))\) ,直接使用 \(o_j - \max(o_k)\) ,因为 \(log(\exp(\cdot))\) 被抵消了,如下公式所展示的。

\[\begin{split}\begin{aligned} \log{(\hat y_j)} & = \log\left( \frac{\exp(o_j - \max(o_k))}{\sum_k \exp(o_k - \max(o_k))}\right) \\ & = \log{(\exp(o_j - \max(o_k)))}-\log{\left( \sum_k \exp(o_k - \max(o_k)) \right)} \\ & = o_j - \max(o_k) -\log{\left( \sum_k \exp(o_k - \max(o_k)) \right)}. \end{aligned}\end{split} \]

这就是 \(LogSumExp技巧\)
具体而言,就是这样变化:

\[log(\sum_{i=1}^ne^{x_i})\xrightarrow[LSE变换]{c=\max(x_1,x_2,...,x_n)} c +\log(\sum_{i =1}^ne^{x_i-c}) \]

而有

\[\mathrm{softmax}(\mathbf{X})_{ij} = \frac{\exp(\mathbf{X}_{ij})}{\sum_k \exp(\mathbf{X}_{ik})} \]

def softmax(X):
    X_exp = torch.exp(X)
    partition = X_exp.sum(1, keepdim=True)
    return X_exp / partition

你可以对他求导:

\[ \begin{equation}\begin{split} \partial_{o_j} l(y, \hat{y}) & = \frac{\exp(o_j)}{\sum_{k=1}^{q} \exp(o_k)} - y_j \\&= \text{softmax}(o)_j - y_j \end{split} \end{equation} \]

细嗦Cross Entropy Error的实现

例如:

def cross_entropy(y_hat, y):
    return -torch.log(y_hat[range(len(y_hat)), y])
y = torch.tensor([0, 2])  # 真实标签:样本0的类别是0,样本1的类别是2
y_hat = torch.tensor([[0.1, 0.3, 0.6],  # 样本0的预测概率
                      [0.3, 0.2, 0.5]]) # 样本1的预测概率
>>> cross_entropy(y_hat, y)

一个是 \(0.1\),一个是 \(0.5\)
进行 \(-log\) 运算后得到了 tensor([2.3026, 0.6931])

:为了防止偏差大时出现 \(log(0)\) 之类的运算,应该给他每一项加上一个 \(\epsilon=10^{-8}\)

如何评估训练精度 / 模型拟合效果?

def accuracy(y_hat, y):  #@save
    """计算预测正确的数量"""
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        y_hat = y_hat.argmax(axis=1)
    cmp = (y_hat.type(y.dtype) == y) #若涉及浮点数,可以用 ε 判断
    return float(cmp.type(y.dtype).sum())

有:

y = torch.tensor([0, 2]) #答案真实选择
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]]) #预测选择
# 预测的最大值的ind分别在2、2,只有一个与y真实答案相同。故accuracy = 1 / 2
accuracy(y_hat, y) / len(y) #0.5
posted @ 2025-11-23 15:52  yLDeveloper  阅读(7)  评论(0)    收藏  举报