尝试使用 typora
--typora的基本使用方法(快捷键和基础设置)_typora基本操作-CSDN博客
--Typora添加数学公式_typora输入公式-CSDN博客
--【科研学术】Typora之markdown公式,汇总,看这一篇就够了~_typora公式加粗斜体-CSDN博客
softmax
为了解决分类问题而出现,实现为
$$
\pmb{o=Wx+b}\\ \hat{y}=softmax(\pmb{o})\space其中\space\hat{y}_i=\frac{exp(o_j)}{\sum_k{exp(o_k)}}
$$
对于所有j总有 0\leq\hat{y}_j\leq1,且总和为1。并且在预测过程中,选择最有可能的类别。
$$
\underset{j}{argmax\space \hat{y}_j}=\underset{j}{argmax\space\hat{o}_j}
$$
使用了softmax计算模型的输出,然后将此输出送入交叉熵损失。 从数学上讲,这是一件完全合理的事情。 然而,从计算角度来看,指数可能会造成数值稳定性问题。
softmax函数\hat y_j = \frac{\exp(o_j)}{\sum_k \exp(o_k)},其中\hat y_j是预测的概率分布。o_j是未规范化的预测\mathbf{o}的第j个元素。 如果o_k中的一些数值非常大, 那么\exp(o_k)可能大于数据类型容许的最大数字,即上溢(overflow)。 这将使分母或分子变为inf
(无穷大), 最后得到的是0、inf
或nan
(不是数字)的\hat y_j。 在这些情况下,我们无法得到一个明确定义的交叉熵值。
解决这个问题的一个技巧是: 在继续softmax计算之前,先从所有o_k中减去\max(o_k)。 这里可以看到每个o_k按常数进行的移动不会改变softmax的返回值:
$$
\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}
$$
在减法和规范化步骤之后,可能有些o_j - \max(o_k)具有较大的负值。 由于精度受限,\exp(o_j - \max(o_k))将有接近零的值,即下溢(underflow)。 这些值可能会四舍五入为零,使\hat y_j为零, 并且使得\log(\hat y_j)的值为-inf
。 反向传播几步后,可能会发现自己面对一屏幕可怕的nan
结果。
尽管我们要计算指数函数,但最终在计算交叉熵损失时会取它们的对数。 通过将softmax和交叉熵结合在一起,可以避免反向传播过程中可能会困扰我们的数值稳定性问题。 如下面的等式所示,避免计算\exp(o_j - \max(o_k)), 而可以直接使用o_j - \max(o_k),因为\log(\exp(\cdot))被抵消了。
$$
\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}
$$
我们也希望保留传统的softmax函数,以备我们需要评估通过模型输出的概率。 但是,我们没有将softmax概率传递到损失函数中, 而是[在交叉熵损失函数中传递未规范化的预测,并同时计算softmax及其对数], 这是一种类似"LogSumExp技巧"的聪明方式。
交叉熵损失函数
交叉熵损失函数是分类问题最常用的损失函数,它是所有标签分布的预期损失值。
$$
l(y,\hat{y})=-\sum_{j=1}^{q}{y_ilog\hat{y}_j}
$$
import torch from torch import nn from d2l import torch as d2l batch_size = 256 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) # PyTorch不会隐式地调整输入的形状。因此, # 我们在线性层前定义了展平层(flatten),来调整网络输入的形状 net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10)) #权重初始化 def init_weights(m): if type(m) == nn.Linear: nn.init.normal_(m.weight, std=0.01) net.apply(init_weights); #定义损失函数为交叉熵损失函数 loss = nn.CrossEntropyLoss(reduction='none') #优化器为随机梯度下降 trainer = torch.optim.SGD(net.parameters(), lr=0.1) num_epochs = 10 d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
补充:
plt.subplots是一个用于创建带有多个子图的函数。它的作用是一次性创建多个子图,并将它们以矩阵的形式排列在一个画窗中。可以通过指定行数和列数来控制子图的布局。
def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5): #@save """绘制图像列表""" figsize = (num_cols * scale, num_rows * scale) _, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize) axes = axes.flatten() for i, (ax, img) in enumerate(zip(axes, imgs)): if torch.is_tensor(img): # 图片张量 ax.imshow(img.numpy()) else: # PIL图片 ax.imshow(img) ax.axes.get_xaxis().set_visible(False) ax.axes.get_yaxis().set_visible(False) if titles: ax.set_title(titles[i]) return axes