3. 线性神经网络

3.1 线性归回

3.1.1 线性回归的基本元素

线性回归假设自变量x和因变量y的关系是线性的,并且噪声比较正常(如遵循正态分布)。
3. 线性神经网络 - 图1
权重决定了每个特征对预测值的影响,偏置值b指当所有特征都为0时,预测值应该为多少。即使不存在x均为0的情况,也需要偏置值,如果没有偏置项,模型的表达能力将受到限制。
矩阵X∈Rn×d(n个样本,d个特征)。预测值y∈Rn。可以通过矩阵-向量乘法表示为:
3. 线性神经网络 - 图2
给定训练数据特征X和对应的已知标签y线性回归的目标是找到一组权重向量w和偏置b,当给定从X取样的新样本特征时, 这组权重向量和偏置能够使得新样本预测标签的误差尽可能小。线性拟合通常会出现少量的观测误差。因此需要加入噪声项来考虑观测误差带来的影响。
回归问题中最常用的损失函数是平方误差函数,常数1/2不会带来本质的差别,但使得求导更加简单。为了度量模型在整个数据集上的质量,需计算在训练集n个样本上的损失均值:
3. 线性神经网络 - 图3%3D%5Cfrac%7B1%7D%7Bn%7D%20%5Csum%7Bi%3D1%7D%5E%7Bn%7D%20l%5E%7B(i)%7D(%5Cmathbf%7Bw%7D%2C%20b)%3D%5Cfrac%7B1%7D%7Bn%7D%20%5Csum%7Bi%3D1%7D%5E%7Bn%7D%20%5Cfrac%7B1%7D%7B2%7D%5Cleft(%5Cmathbf%7Bw%7D%5E%7B%5Ctop%7D%20%5Cmathbf%7Bx%7D%5E%7B(i)%7D%2Bb-y%5E%7B(i)%7D%5Cright)%5E%7B2%7D%0A#card=math&code=L%28%5Cmathbf%7Bw%7D%2C%20b%29%3D%5Cfrac%7B1%7D%7Bn%7D%20%5Csum%7Bi%3D1%7D%5E%7Bn%7D%20l%5E%7B%28i%29%7D%28%5Cmathbf%7Bw%7D%2C%20b%29%3D%5Cfrac%7B1%7D%7Bn%7D%20%5Csum%7Bi%3D1%7D%5E%7Bn%7D%20%5Cfrac%7B1%7D%7B2%7D%5Cleft%28%5Cmathbf%7Bw%7D%5E%7B%5Ctop%7D%20%5Cmathbf%7Bx%7D%5E%7B%28i%29%7D%2Bb-y%5E%7B%28i%29%7D%5Cright%29%5E%7B2%7D%0A&id=eM31P)
从损失函数的角度来说,线性回归的目标是找到一组权重向量w和偏置b,使得损失函数的梯度接近零。
梯度[1]下降最简单的用法是计算损失函数关于模型参数的梯度。实际执行可能非常慢,因为必须遍历整个数据集以计算均方损失。为此通常会在每次需要计算更新的时候随机抽取一小批样本, 叫做小批量随机梯度下降(minibatch stochastic gradient descent):

  1. 初始化模型参数的值,如随机初始化;
  2. 从数据集中随机抽取小批量样本且在负梯度的方向上更新参数,并不断迭代这一步骤。 更新参数时,计算小批量的平均损失关于模型参数的梯度,将梯度乘以预先确定的学习率η,并从当前参数的值中减掉:

3. 线性神经网络 - 图4%7D(%5Cmathbf%7Bw%7D%2C%20b)%3D%5Cmathbf%7Bw%7D-%5Cfrac%7B%5Ceta%7D%7B%7C%5Cmathcal%7BB%7D%7C%7D%20%5Csum%7Bi%20%5Cin%20%5Cmathcal%7BB%7D%7D%20%5Cmathbf%7Bx%7D%5E%7B(i)%7D%5Cleft(%5Cmathbf%7Bw%7D%5E%7B%5Ctop%7D%20%5Cmathbf%7Bx%7D%5E%7B(i)%7D%2Bb-y%5E%7B(i)%7D%5Cright)%2C%20%5C%5C%0Ab%20%5Cleftarrow%20b-%5Cfrac%7B%5Ceta%7D%7B%7C%5Cmathcal%7BB%7D%7C%7D%20%5Csum%7Bi%20%5Cin%20%5Cmathcal%7BB%7D%7D%20%5Cpartial%7Bb%7D%20l%5E%7B(i)%7D(%5Cmathbf%7Bw%7D%2C%20b)%3Db-%5Cfrac%7B%5Ceta%7D%7B%7C%5Cmathcal%7BB%7D%7C%7D%20%5Csum%7Bi%20%5Cin%20%5Cmathcal%7BB%7D%7D%5Cleft(%5Cmathbf%7Bw%7D%5E%7B%5Ctop%7D%20%5Cmathbf%7Bx%7D%5E%7B(i)%7D%2Bb-y%5E%7B(i)%7D%5Cright)%20.%0A%5Cend%7Barray%7D%0A#card=math&code=%5Cbegin%7Barray%7D%7Bc%7D%0A%5Cmathbf%7Bw%7D%20%5Cleftarrow%20%5Cmathbf%7Bw%7D-%5Cfrac%7B%5Ceta%7D%7B%7C%5Cmathcal%7BB%7D%7C%7D%20%5Csum%7Bi%20%5Cin%20%5Cmathcal%7BB%7D%7D%20%5Cpartial%7B%5Cmathbf%7Bw%7D%7D%20l%5E%7B%28i%29%7D%28%5Cmathbf%7Bw%7D%2C%20b%29%3D%5Cmathbf%7Bw%7D-%5Cfrac%7B%5Ceta%7D%7B%7C%5Cmathcal%7BB%7D%7C%7D%20%5Csum%7Bi%20%5Cin%20%5Cmathcal%7BB%7D%7D%20%5Cmathbf%7Bx%7D%5E%7B%28i%29%7D%5Cleft%28%5Cmathbf%7Bw%7D%5E%7B%5Ctop%7D%20%5Cmathbf%7Bx%7D%5E%7B%28i%29%7D%2Bb-y%5E%7B%28i%29%7D%5Cright%29%2C%20%5C%5C%0Ab%20%5Cleftarrow%20b-%5Cfrac%7B%5Ceta%7D%7B%7C%5Cmathcal%7BB%7D%7C%7D%20%5Csum%7Bi%20%5Cin%20%5Cmathcal%7BB%7D%7D%20%5Cpartial%7Bb%7D%20l%5E%7B%28i%29%7D%28%5Cmathbf%7Bw%7D%2C%20b%29%3Db-%5Cfrac%7B%5Ceta%7D%7B%7C%5Cmathcal%7BB%7D%7C%7D%20%5Csum%7Bi%20%5Cin%20%5Cmathcal%7BB%7D%7D%5Cleft%28%5Cmathbf%7Bw%7D%5E%7B%5Ctop%7D%20%5Cmathbf%7Bx%7D%5E%7B%28i%29%7D%2Bb-y%5E%7B%28i%29%7D%5Cright%29%20.%0A%5Cend%7Barray%7D%0A&id=dqCi8)
批量大小Batch_Size和学习率η通常是手动预先设定的。这些可以调整但不在训练过程中更新的参数称为超参数(hyperparameter)。
即使函数确实是线性且无噪声的,最终的估计参数也难以使损失函数真正地达到最小值。 因为算法会使得损失向最小值缓慢收敛,但却不能在有限的步数内非常精确地达到最小值。
线性回归问题可以描述为不包括隐藏层的单层神经网络,如下:
singleneuron.svg
每个输入都与每个输出相连的层, 称为全连接层(fully-connected layer)。

3.1.2 正态分布与平方损失

正态分布(normal distribution),也称为高斯分布(Gaussian distribution),其与线性回归之间的关系很密切。
3. 线性神经网络 - 图6%3D%5Cfrac%7B1%7D%7B%5Csqrt%7B2%20%5Cpi%20%5Csigma%5E%7B2%7D%7D%7D%20%5Cexp%20%5Cleft(-%5Cfrac%7B1%7D%7B2%20%5Csigma%5E%7B2%7D%7D(x-%5Cmu)%5E%7B2%7D%5Cright)%0A#card=math&code=p%28x%29%3D%5Cfrac%7B1%7D%7B%5Csqrt%7B2%20%5Cpi%20%5Csigma%5E%7B2%7D%7D%7D%20%5Cexp%20%5Cleft%28-%5Cfrac%7B1%7D%7B2%20%5Csigma%5E%7B2%7D%7D%28x-%5Cmu%29%5E%7B2%7D%5Cright%29%0A&id=aDr8V)

  1. # 计算正态分布
  2. def normal(x, mu, sigma):
  3. p = 1 / math.sqrt(2 * math.pi * sigma**2)
  4. return p * np.exp(-0.5 / sigma**2 * (x - mu)**2)

改变均值会产生沿x轴的偏移,增加方差将会分散分布、降低其峰值。均方误差损失函数可用于线性回归的一个原因是: 假设观测中包含了服从正态分布的噪声。通过给定x观测到特定y的似然(likelihood):
3. 线性神经网络 - 图7%3D%5Cfrac%7B1%7D%7B%5Csqrt%7B2%20%5Cpi%20%5Csigma%5E%7B2%7D%7D%7D%20%5Cexp%20%5Cleft(-%5Cfrac%7B1%7D%7B2%20%5Csigma%5E%7B2%7D%7D%5Cleft(y-%5Cmathbf%7Bw%7D%5E%7B%5Ctop%7D%20%5Cmathbf%7Bx%7D-b%5Cright)%5E%7B2%7D%5Cright)%0A#card=math&code=P%28y%20%5Cmid%20%5Cmathbf%7Bx%7D%29%3D%5Cfrac%7B1%7D%7B%5Csqrt%7B2%20%5Cpi%20%5Csigma%5E%7B2%7D%7D%7D%20%5Cexp%20%5Cleft%28-%5Cfrac%7B1%7D%7B2%20%5Csigma%5E%7B2%7D%7D%5Cleft%28y-%5Cmathbf%7Bw%7D%5E%7B%5Ctop%7D%20%5Cmathbf%7Bx%7D-b%5Cright%29%5E%7B2%7D%5Cright%29%0A&id=adwb3)
根据极大似然估计法,参数w和b的最优值是使整个数据集的似然最大的值:
3. 线性神经网络 - 图8%3D%5Cprod%7Bi%3D1%7D%5E%7Bn%7D%20p%5Cleft(y%5E%7B(i)%7D%20%5Cmid%20%5Cmathbf%7Bx%7D%5E%7B(i)%7D%5Cright)%0A#card=math&code=P%28%5Cmathbf%7By%7D%20%5Cmid%20%5Cmathbf%7BX%7D%29%3D%5Cprod%7Bi%3D1%7D%5E%7Bn%7D%20p%5Cleft%28y%5E%7B%28i%29%7D%20%5Cmid%20%5Cmathbf%7Bx%7D%5E%7B%28i%29%7D%5Cright%29%0A&id=btl0i)
使用最大化似然对数来简化指数的乘积问题。 改为最小化负对数似然−logP(y∣X):
3. 线性神经网络 - 图9%3D%5Csum%7Bi%3D1%7D%5E%7Bn%7D%20%5Cfrac%7B1%7D%7B2%7D%20%5Clog%20%5Cleft(2%20%5Cpi%20%5Csigma%5E%7B2%7D%5Cright)%2B%5Cfrac%7B1%7D%7B2%20%5Csigma%5E%7B2%7D%7D%5Cleft(y%5E%7B(i)%7D-%5Cmathbf%7Bw%7D%5E%7B%5Ctop%7D%20%5Cmathbf%7Bx%7D%5E%7B(i)%7D-b%5Cright)%5E%7B2%7D%0A#card=math&code=-%5Clog%20P%28%5Cmathbf%7By%7D%20%5Cmid%20%5Cmathbf%7BX%7D%29%3D%5Csum%7Bi%3D1%7D%5E%7Bn%7D%20%5Cfrac%7B1%7D%7B2%7D%20%5Clog%20%5Cleft%282%20%5Cpi%20%5Csigma%5E%7B2%7D%5Cright%29%2B%5Cfrac%7B1%7D%7B2%20%5Csigma%5E%7B2%7D%7D%5Cleft%28y%5E%7B%28i%29%7D-%5Cmathbf%7Bw%7D%5E%7B%5Ctop%7D%20%5Cmathbf%7Bx%7D%5E%7B%28i%29%7D-b%5Cright%29%5E%7B2%7D%0A&id=BZJtt)
假设σ是某个固定常数, 那么依赖于w和b的部分和均方误差是一样的,因为上面式子的解并不依赖于σ。 因此,在高斯噪声的假设下,最小化均方误差等价于对线性模型的极大似然估计。

3.2 线性回归的从零实现

  1. import random
  2. import torch
  3. from d2l import torch as d2l

3.2.1 生成数据集

使用参数3. 线性神经网络 - 图10和噪声项ϵ(服从均值为0,标准差为0.01的正态分布)生成数据集:
3. 线性神经网络 - 图11

  1. def synthetic_data(w, b, num_examples): #@save
  2. """使用给定线性函数生成特征X和标签y"""
  3. X = torch.normal(0, 1, (num_examples, len(w))) # n个样本,每个样本len(w)特征
  4. y = torch.matmul(X, w) + b
  5. y += torch.normal(0, 0.01, y.shape)
  6. return X, y.reshape((-1, 1))
  7. true_w = torch.tensor([2, -3.4])
  8. true_b = 4.2
  9. features, labels = synthetic_data(true_w, true_b, 1000)

3.2.2 读取数据集

  1. def data_iter(batch_size, features, labels):
  2. """
  3. 打乱数据集中的样本并以小批量方式获取数据。
  4. :param batch_size:每一批样本数
  5. :param features:特征矩阵
  6. :param labels:标签向量
  7. :return:
  8. """
  9. num_examples = len(features) # 获取样本数
  10. indices = list(range(num_examples)) # 生成索引列表
  11. random.shuffle(indices)
  12. for i in range(0, num_examples, batch_size): # 每次一个batch_size
  13. batch_indices = torch.tensor(indices[i: min(i + batch_size, num_examples)])
  14. yield features[batch_indices], labels[batch_indices] # 返回特征和标签,下次接着此处执行循环

在GPU中,每个样本都可以并行地进行计算,且每个样本损失函数的梯度也可以被并行计算。 默认数据存放在内存中,并使用CPU进行计算。

3.2.3 初始化模型参数

  1. w = torch.normal(0, 0.01, size=(2,1), requires_grad=True) # 反向传播时,自动求导
  2. b = torch.zeros(1, requires_grad=True)

3.2.4 定义模型

根据3. 线性神经网络 - 图12。可获取如下的全连接层代码:

  1. def linreg(X, w, b): #@save
  2. """线性回归模型"""
  3. return torch.matmul(X, w) + b

3.2.5 定义损失函数

  1. def squared_loss(y_hat, y): #@save
  2. """均方损失"""
  3. return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2 # 防止出错转化为相同形状

3.2.5 定义优化算法

小批量随机梯度下降每一步从数据集中随机抽取一个小批量,然后根据参数计算损失的梯度,朝着减少损失的方向更新参数。每一步更新的大小由学习速率lr决定。

  1. def sgd(params, lr, batch_size): #@save
  2. """
  3. 小批量随机梯度下降
  4. :param params: 模型的参数集合
  5. :param lr: 学习率
  6. :param batch_size:每批样本的数量
  7. :return:
  8. """
  9. with torch.no_grad(): # 在不求导和反向传播的情况下更新参数。
  10. for param in params:
  11. param -= lr * param.grad / batch_size # 对param减去对损失函数的平均梯度
  12. param.grad.zero_() # 将梯度值设置为0,防止叠加求导

3.2.7 训练

  • 初始化参数
  • 重复以下训练,直到完成
    • 计算梯度3. 线性神经网络 - 图13%7D%20%5Cfrac%7B1%7D%7B%7C%5Cmathcal%7BB%7D%7C%7D%20%5Csum%7Bi%20%5Cin%20%5Cmathcal%7BB%7D%7D%20l(%5Cmathbf%7Bw%7D%5E%7B(i)%7D%2Cy%5E%7B(i)%7D%2C%5Cmathbf%7Bw%7D%2C%20b)#card=math&code=%5Cmathbf%7Bg%7D%E2%AC%85%5Cpartial%7B%28%5Cmathbf%7Bw%7D%2Cb%29%7D%20%5Cfrac%7B1%7D%7B%7C%5Cmathcal%7BB%7D%7C%7D%20%5Csum_%7Bi%20%5Cin%20%5Cmathcal%7BB%7D%7D%20l%28%5Cmathbf%7Bw%7D%5E%7B%28i%29%7D%2Cy%5E%7B%28i%29%7D%2C%5Cmathbf%7Bw%7D%2C%20b%29&id=Mt9BX)
    • 更新参数:3. 线性神经网络 - 图14%E2%AC%85(%5Cmathbf%7Bw%7D%2Cb)-%CE%B7%5Cmathbf%7Bg%7D#card=math&code=%28%5Cmathbf%7Bw%7D%2Cb%29%E2%AC%85%28%5Cmathbf%7Bw%7D%2Cb%29-%CE%B7%5Cmathbf%7Bg%7D&id=KrclX)

每个迭代周期(epoch)使用data_iter函数遍历整个数据集。 迭代周期个数num_epochs和学习率lr都是超参数。设置超参数很棘手,需要通过反复试验进行调整。

  1. lr = 0.03
  2. num_epochs = 3
  3. net = linreg
  4. loss = squared_loss
  5. batch_size = 15
  6. for epoch in range(num_epochs):
  7. for X, y in data_iter(batch_size, features, labels):
  8. l = loss(net(X, w, b), y)
  9. # l形状是(batch_size,1),求和以计算平均损失
  10. l.sum().backward() # 计算均方损失的和。sgd中除了batch_size,因此这里不用除
  11. sgd([w, b], lr, batch_size) # 使用参数的梯度更新参数
  12. with torch.no_grad():
  13. train_l = loss(net(features, w, b), labels) # 一个epoch后,计算训练集总体的损失。形状为(batch_size,1)
  14. print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')# 打印该epoch的均方损失
  15. """
  16. epoch 1, loss 0.254206
  17. epoch 2, loss 0.003985
  18. epoch 3, loss 0.000119
  19. """

3.3 线性回归简洁实现

PyTorch中的**data**模块提供了数据处理工具,**nn**模块定义了大量的神经网络层和常见损失函数。

  1. # 生成数据集
  2. import numpy as np
  3. import torch
  4. from torch.utils import data
  5. from d2l import torch as d2l
  6. true_w = torch.tensor([2, -3.4])
  7. true_b = 4.2
  8. features, labels = d2l.synthetic_data(true_w, true_b, 1000) # 生成1000个样本和标签
  9. # 读取数据集。data_arrays为特征标签组成的元组(features,labels),is_train表示是否希望数据迭代器在每个迭代周期内打乱数据
  10. def load_array(data_arrays, batch_size, is_train=True): #@save
  11. """构造一个PyTorch数据迭代器"""
  12. dataset = data.TensorDataset(*data_arrays) # 由数据构造数据集
  13. return data.DataLoader(dataset, batch_size, shuffle=is_train) # 返回数据加载器,每次循环一个batch_size并打乱顺序
  14. batch_size = 10
  15. data_iter = load_array((features, labels), batch_size)

标准深度学习模型只需关注使用哪些层来构造模型,而不必关注层的实现细节nn.Sequential类将多个层串联在一起。 先前模型只包含一个层,实际上并不需要Sequential。 在PyTorch中,全连接层由Linear类定义,第一个参数指定输入特征形状,第二个参数指定输出特征形状。

# nn是神经网络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
# 初始化模型参数
net[0].weight.data.normal_(0, 0.01)    # 选择网络中的第一个图层,设置权重
net[0].bias.data.fill_(0)    # 选择网络中的第一个图层,设置偏置值

# 定义损失函数
loss = nn.MSELoss()    # 均方误差损失函数

# 定义优化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.03)    # 参数一为指定要优化的参数,这里通过函数从模型中自动获取

每个迭代周期将完整遍历一次数据集(train_data), 不停地从中获取一个小批量的输入和相应的标签。 每一个小批量进行以下步骤:

  • 通过调用net(X)生成预测并计算损失l(前向传播)。
  • 通过反向传播来计算梯度。
  • 通过调用优化器来更新模型参数。
    num_epochs = 3
    for epoch in range(num_epochs):
      for X, y in data_iter:
          l = loss(net(X) ,y)
          trainer.zero_grad()    # 梯度清空
          l.backward()    # 反向传播计算梯度
          trainer.step()    # 通过梯度更新参数
      l = loss(net(features), labels)    # 计算整体损失
      print(f'epoch {epoch + 1}, loss {l:f}')
    """
    epoch 1, loss 0.000377
    epoch 2, loss 0.000101
    epoch 3, loss 0.000100
    """
    

    3.4 softmax回归

    3.4.1 sofamax

    为了估计所有可能类别的条件概率,需要每个类别对应一个输出,每个输出对应自己的仿射函数。假设有4个特征和3个输出类别, 将需要12个权重标量,3个偏置标量。
    3. 线性神经网络 - 图15
    softmaxreg.svg
    softmax回归就是如上展示的单层神经网络。向量形式表达为3. 线性神经网络 - 图17。模型的输出 yj 视为属于类 j 的概率, 选择最大输出值的类别argmax yj作为预测。概率必须保证在任何数据上的输出都是非负的且总和为1。 softmax函数将未规范化的预测变换为非负并且总和为1,同时保持可导。如下式:
    3. 线性神经网络 - 图18%7D%E5%85%B6%E4%B8%AD%20%5Cwidehat%7By%7Bj%7D%7D%3D%5Cfrac%7Bexp(o%7Bj%7D)%7D%7B%20%7B%5Ctextstyle%20%5Csum%7Bk%7D%5E%7B%7Dexp(o%7Bk%7D%7D)%20%7D%0A#card=math&code=%5Cwidehat%7By%7D%3D%5Cmathbf%7Bsoftmax%28o%29%7D%E5%85%B6%E4%B8%AD%20%5Cwidehat%7By%7Bj%7D%7D%3D%5Cfrac%7Bexp%28o%7Bj%7D%29%7D%7B%20%7B%5Ctextstyle%20%5Csum%7Bk%7D%5E%7B%7Dexp%28o%7Bk%7D%7D%29%20%7D%0A&id=xUtMM)

    3.4.2 损失函数

    与线性回归中一样,使用最大似然估计来度量预测结果。softmax的结果向量y可视为“对给定任意输入x的每个类的条件概率”。假设整个数据集{X,Y}具有n个样本, 其中索引i的样本由特征向量x(i)和独热标签向量y(i)组成。 我们可以将估计值与实际值进行比较:
    3. 线性神经网络 - 图19%3D%5Cprod%7Bi%3D1%7D%5E%7Bn%7D%20P%5Cleft(%5Cmathbf%7By%7D%5E%7B(i)%7D%20%5Cmid%20%5Cmathbf%7Bx%7D%5E%7B(i)%7D%5Cright)%5C%5C%0A-%5Clog%20P(%5Cmathbf%7BY%7D%20%5Cmid%20%5Cmathbf%7BX%7D)%3D%5Csum%7Bi%3D1%7D%5E%7Bn%7D-%5Clog%20P%5Cleft(%5Cmathbf%7By%7D%5E%7B(i)%7D%20%5Cmid%20%5Cmathbf%7Bx%7D%5E%7B(i)%7D%5Cright)%0A#card=math&code=P%28%5Cmathbf%7BY%7D%20%5Cmid%20%5Cmathbf%7BX%7D%29%3D%5Cprod%7Bi%3D1%7D%5E%7Bn%7D%20P%5Cleft%28%5Cmathbf%7By%7D%5E%7B%28i%29%7D%20%5Cmid%20%5Cmathbf%7Bx%7D%5E%7B%28i%29%7D%5Cright%29%5C%5C%0A-%5Clog%20P%28%5Cmathbf%7BY%7D%20%5Cmid%20%5Cmathbf%7BX%7D%29%3D%5Csum%7Bi%3D1%7D%5E%7Bn%7D-%5Clog%20P%5Cleft%28%5Cmathbf%7By%7D%5E%7B%28i%29%7D%20%5Cmid%20%5Cmathbf%7Bx%7D%5E%7B%28i%29%7D%5Cright%29%0A&id=gmCGA)
    向量3. 线性神经网络 - 图20%7D#card=math&code=%5Cmathbf%7By%7D%5E%7B%28i%29%7D&id=Tnpgn)是独热码标签,如[0, 0, 1]。对于每次的预测结果向量3. 线性神经网络 - 图21%7D#card=math&code=%5Chat%7B%5Cmathbf%7By%7D%7D%5E%7B%28i%29%7D&id=GOKWN),只要求3. 线性神经网络 - 图22%7D#card=math&code=%5Chat%7B%5Cmathbf%7By%7D%7D%5E%7B%28i%29%7D&id=VQUmA)与3. 线性神经网络 - 图23%7D#card=math&code=%5Cmathbf%7By%7D%5E%7B%28i%29%7D&id=Nw6Ou)的点积即可获取3. 线性神经网络 - 图24%7D#card=math&code=%5Chat%7B%5Cmathbf%7By%7D%7D%5E%7B%28i%29%7D&id=TZg7z)对应位置的值3. 线性神经网络 - 图25,因此有:
    3. 线性神经网络 - 图26%7D%20%5Cmid%20%5Cmathbf%7Bx%7D%5E%7B(i)%7D%5Cright)%3D-%5Clog%20%5Csum%7Bj%3D1%7D%5E%7Bq%7D%20y%7Bj%7D%5E%7B(i)%7D%20%5Ccdot%20%5Chat%7By%7D%7Bj%7D%5E%7B(i)%7D%3D-%5Clog%20%5Chat%7By%7D%7Bn%7D%5E%7B(i)%7D%0A#card=math&code=-%5Clog%20P%5Cleft%28%5Cmathbf%7By%7D%5E%7B%28i%29%7D%20%5Cmid%20%5Cmathbf%7Bx%7D%5E%7B%28i%29%7D%5Cright%29%3D-%5Clog%20%5Csum%7Bj%3D1%7D%5E%7Bq%7D%20y%7Bj%7D%5E%7B%28i%29%7D%20%5Ccdot%20%5Chat%7By%7D%7Bj%7D%5E%7B%28i%29%7D%3D-%5Clog%20%5Chat%7By%7D%7Bn%7D%5E%7B%28i%29%7D%0A&id=eBRL6)
    因为向量3. 线性神经网络 - 图27%7D#card=math&code=%5Cmathbf%7By%7D%5E%7B%28i%29%7D&id=jx5yk)中仅一个数值为1,其余均为0,因此写为如下形式也不影响结果:
    3. 线性神经网络 - 图28%7D%20%5Cmid%20%5Cmathbf%7Bx%7D%5E%7B(i)%7D%5Cright)%3D-%5Csum%7Bj%3D1%7D%5E%7Bq%7D%20y%7Bj%7D%5E%7B(i)%7D%20%5Clog%20%5Chat%7By%7D%7Bj%7D%5E%7B(i)%7D%0A#card=math&code=-%5Clog%20P%5Cleft%28%5Cmathbf%7By%7D%5E%7B%28i%29%7D%20%5Cmid%20%5Cmathbf%7Bx%7D%5E%7B%28i%29%7D%5Cright%29%3D-%5Csum%7Bj%3D1%7D%5E%7Bq%7D%20y%7Bj%7D%5E%7B%28i%29%7D%20%5Clog%20%5Chat%7By%7D%7Bj%7D%5E%7B%28i%29%7D%0A&id=s8uFh)
    上面的损失函数称为交叉熵损失(cross-entropy loss)。由于每个预测分量均小于1,因此其对数永远不会大于0。将公式(11)代入(14)中:
    3. 线性神经网络 - 图29%20%26%3D-%5Csum%7Bj%3D1%7D%5E%7Bq%7D%20y%7Bj%7D%20%5Clog%20%5Cfrac%7B%5Cexp%20%5Cleft(o%7Bj%7D%5Cright)%7D%7B%5Csum%7Bk%3D1%7D%5E%7Bq%7D%20%5Cexp%20%5Cleft(o%7Bk%7D%5Cright)%7D%20%5C%5C%0A%26%3D%5Csum%7Bj%3D1%7D%5E%7Bq%7D%20y%7Bj%7D%20%5Clog%20%5Csum%7Bk%3D1%7D%5E%7Bq%7D%20%5Cexp%20%5Cleft(o%7Bk%7D%5Cright)-%5Csum%7Bj%3D1%7D%5E%7Bq%7D%20y%7Bj%7D%20o%7Bj%7D%20%5C%5C%0A%26%3D%5Clog%20%5Csum%7Bk%3D1%7D%5E%7Bq%7D%20%5Cexp%20%5Cleft(o%7Bk%7D%5Cright)-%5Csum%7Bj%3D1%7D%5E%7Bq%7D%20y%7Bj%7D%20o%7Bj%7D%0A%5Cend%7Baligned%7D%0A#card=math&code=%5Cbegin%7Baligned%7D%0Al%28%5Cmathbf%7By%7D%2C%20%5Chat%7B%5Cmathbf%7By%7D%7D%29%20%26%3D-%5Csum%7Bj%3D1%7D%5E%7Bq%7D%20y%7Bj%7D%20%5Clog%20%5Cfrac%7B%5Cexp%20%5Cleft%28o%7Bj%7D%5Cright%29%7D%7B%5Csum%7Bk%3D1%7D%5E%7Bq%7D%20%5Cexp%20%5Cleft%28o%7Bk%7D%5Cright%29%7D%20%5C%5C%0A%26%3D%5Csum%7Bj%3D1%7D%5E%7Bq%7D%20y%7Bj%7D%20%5Clog%20%5Csum%7Bk%3D1%7D%5E%7Bq%7D%20%5Cexp%20%5Cleft%28o%7Bk%7D%5Cright%29-%5Csum%7Bj%3D1%7D%5E%7Bq%7D%20y%7Bj%7D%20o%7Bj%7D%20%5C%5C%0A%26%3D%5Clog%20%5Csum%7Bk%3D1%7D%5E%7Bq%7D%20%5Cexp%20%5Cleft%28o%7Bk%7D%5Cright%29-%5Csum%7Bj%3D1%7D%5E%7Bq%7D%20y%7Bj%7D%20o%7Bj%7D%0A%5Cend%7Baligned%7D%0A&id=LD1pl)
    考虑相对于任何未规范化的预测 3. 线性神经网络 - 图30 的导数,我们得到:
    3. 线性神经网络 - 图31%3D%5Cfrac%7B%5Cexp%20%5Cleft(o%7Bj%7D%5Cright)%7D%7B%5Csum%7Bk%3D1%7D%5E%7Bq%7D%20%5Cexp%20%5Cleft(o%7Bk%7D%5Cright)%7D-y%7Bj%7D%3D%5Coperatorname%7Bsoftmax%7D(%5Cmathbf%7Bo%7D)%7Bj%7D-y%7Bj%7D%20.%0A#card=math&code=%5Cpartial%7Bo%20j%7D%20l%28%5Cmathbf%7By%7D%2C%20%5Chat%7B%5Cmathbf%7By%7D%7D%29%3D%5Cfrac%7B%5Cexp%20%5Cleft%28o%7Bj%7D%5Cright%29%7D%7B%5Csum%7Bk%3D1%7D%5E%7Bq%7D%20%5Cexp%20%5Cleft%28o%7Bk%7D%5Cright%29%7D-y%7Bj%7D%3D%5Coperatorname%7Bsoftmax%7D%28%5Cmathbf%7Bo%7D%29%7Bj%7D-y_%7Bj%7D%20.%0A&id=tShAV)
    可见,导数是softmax模型分配的概率与实际发生情况之间的差异。

    3.5 图像分类实战

    使用包括10个类别的Fashion-MNIST数据集,每个类别训练集有6000张图,测试集有1000张图。每张灰度图像的高度和宽度均为28像素。现在定义加载训练集和验证集的数据迭代器函数。 ```python import torch import torchvision from torch.utils import data from torchvision import transforms from d2l import torch as d2l

d2l.use_svg_display()

def get_dataloader_workers(): #@save “””使用4个进程来读取数据””” return 4

resize用于图片形状调整

def load_data_fashion_mnist(batch_size, resize=None): #@save “””下载Fashion-MNIST数据集,然后将其加载到内存中””” trans = [transforms.ToTensor()] # 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式,并除以255使得所有像素的数值均在0到1之间 if resize: trans.insert(0, transforms.Resize(resize)) trans = transforms.Compose(trans) # 将trans中的多个步骤整合 mnist_train = torchvision.datasets.FashionMNIST( root=”../data”, train=True, transform=trans, download=True) mnist_test = torchvision.datasets.FashionMNIST( root=”../data”, train=False, transform=trans, download=True) return (data.DataLoader(mnist_train, batch_size, shuffle=True, num_workers=get_dataloader_workers()), data.DataLoader(mnist_test, batch_size, shuffle=False, num_workers=get_dataloader_workers()))

def get_fashion_mnist_labels(labels): #@save “””返回Fashion-MNIST数据集的文本标签””” text_labels = [‘t-shirt’, ‘trouser’, ‘pullover’, ‘dress’, ‘coat’, ‘sandal’, ‘shirt’, ‘sneaker’, ‘bag’, ‘ankle boot’] return [text_labels[int(i)] for i in labels]

<a name="d6923d36"></a>
## 3.6 softmax回归从零实现
```python
import torch
from IPython import display
from d2l import torch as d2l

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

初始化模型参数
数据集中每个样本都是 28×28 的图像。我们将展平每个图像,把它们看作长度为784的向量。

num_inputs = 784    # 输入维度,展平后的张量
num_outputs = 10    # 输出维度,分类类别

W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)

定义softmax操作
3. 线性神经网络 - 图32%7Bij%7D%7D%3D%5Cfrac%7Bexp(%5Cmathbf%7BX%7D%7Bij%7D)%7D%7B%20%7B%5Ctextstyle%20%5Csum%7Bk%7D%5E%7B%7Dexp(%5Cmathbf%7BX%7D%7Bik%7D%7D)%20%7D%0A#card=math&code=%5Cmathbf%7Bsoftmax%28X%29%7Bij%7D%7D%3D%5Cfrac%7Bexp%28%5Cmathbf%7BX%7D%7Bij%7D%29%7D%7B%20%7B%5Ctextstyle%20%5Csum%7Bk%7D%5E%7B%7Dexp%28%5Cmathbf%7BX%7D%7Bik%7D%7D%29%20%7D%0A&id=MtJrB)

def softmax(X):
    X_exp = torch.exp(X)
    partition = X_exp.sum(1, keepdim=True)
    return X_exp / partition  # 这里应用了广播机制

定义模型

def net(X):# 将数据传递到模型之前使用reshape函数将每张原始图像展平为向量。
    return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)

定义损失函数
3. 线性神经网络 - 图33%7D%20%5Cmid%20%5Cmathbf%7Bx%7D%5E%7B(i)%7D%5Cright)%3D-%5Clog%20%5Csum%7Bj%3D1%7D%5E%7Bq%7D%20y%7Bj%7D%5E%7B(i)%7D%20%5Ccdot%20%5Chat%7By%7D%7Bj%7D%5E%7B(i)%7D%3D-%5Clog%20%5Chat%7By%7D%7Bn%7D%5E%7B(i)%7D%0A#card=math&code=-%5Clog%20P%5Cleft%28%5Cmathbf%7By%7D%5E%7B%28i%29%7D%20%5Cmid%20%5Cmathbf%7Bx%7D%5E%7B%28i%29%7D%5Cright%29%3D-%5Clog%20%5Csum%7Bj%3D1%7D%5E%7Bq%7D%20y%7Bj%7D%5E%7B%28i%29%7D%20%5Ccdot%20%5Chat%7By%7D%7Bj%7D%5E%7B%28i%29%7D%3D-%5Clog%20%5Chat%7By%7D%7Bn%7D%5E%7B%28i%29%7D%0A&id=kFxlG)

y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])

# 交叉熵损失函数
def cross_entropy(y_hat, y):
    return - torch.log(y_hat[range(len(y_hat)), y])    # 获取每个样本标签对应索引的概率,并取对数

y_hat[range(len(y_hat)), y]    # tensor([0.1000, 0.5000])分别取了0和2的位置

定义目标函数:准确率
准确率即正确预测数量与总预测数量之比。

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    # 将y_hat和y的类型设为相同,再比较是否相等
    return float(cmp.type(y.dtype).sum())    # 将bool转为int,并求和即为正确数

accuracy(y_hat, y) / len(y) # 准确率:0.5

def evaluate_accuracy(net, data_iter):  #@save
    """计算在指定数据集上模型的精度"""
    if isinstance(net, torch.nn.Module):
        net.eval()  # 将模型设置为评估模式,此时模型不再求梯度
    metric = Accumulator(2)  # 创建正确预测数、预测总数两个变量,在训练时逐步累加
    with torch.no_grad():
        for X, y in data_iter:
            metric.add(accuracy(net(X), y), y.numel())
    return metric[0] / metric[1]

class Accumulator:  #@save
    """在n个变量上累加"""
    def __init__(self, n):
        self.data = [0.0] * n

    def add(self, *args):
        self.data = [a + float(b) for a, b in zip(self.data, args)]

    def reset(self):
        self.data = [0.0] * len(self.data)

    def __getitem__(self, idx):
        return self.data[idx]

当随机初始化权重时, 模型精度应接近于随机猜测。 例如在有10个类别情况下的精度为0.1。

evaluate_accuracy(net, test_iter)    # 0.1062

训练

def train_epoch_ch3(net, train_iter, loss, updater):  #@save
    """训练模型一个迭代周期"""
    # 将模型设置为训练模式
    if isinstance(net, torch.nn.Module):
        net.train()
    # 训练损失总和、训练准确度总和、样本数
    metric = Accumulator(3)
    for X, y in train_iter:
        # 计算梯度并更新参数
        y_hat = net(X)
        l = loss(y_hat, y)
        if isinstance(updater, torch.optim.Optimizer):
            # 使用PyTorch内置的优化器和损失函数
            updater.zero_grad()
            l.mean().backward()
            updater.step()
        else:
            # 使用定制的优化器和损失函数
            l.sum().backward()
            updater(X.shape[0])
        metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
    # 返回训练损失和训练精度
    return metric[0] / metric[2], metric[1] / metric[2]

接下来实现一个训练函数, 该训练函数将会运行多个迭代周期,并在每个迭代周期结束时,利用测试集对模型进行评估。 我们将利用Animator类来可视化训练进度。

def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):  #@save
    """训练模型(定义见第3章)"""
    # 该类在d2l中定义,用于动态绘图
    animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
                        legend=['train loss', 'train acc', 'test acc'])
    for epoch in range(num_epochs):
        train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
        test_acc = evaluate_accuracy(net, test_iter)
        animator.add(epoch + 1, train_metrics + (test_acc,))
    train_loss, train_acc = train_metrics
    assert train_loss < 0.5, train_loss
    assert train_acc <= 1 and train_acc > 0.7, train_acc
    assert test_acc <= 1 and test_acc > 0.7, test_acc

使用 3.2节中定义的 小批量随机梯度下降来优化模型的损失函数,设置学习率为0.1。

lr = 0.1
num_epochs = 10

def updater(batch_size):
    return d2l.sgd([W, b], lr, batch_size)

train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)

output_softmax-regression-scratch_a48321_180_0.svg

3.7 从softmax看上溢与下溢问题

3. 线性神经网络 - 图35%7D%7B%20%7B%5Ctextstyle%20%5Csum%7Bk%7D%5E%7B%7Dexp(o%7Bk%7D%7D)%20%7D#card=math&code=%5Cwidehat%7By%7Bj%7D%7D%3D%5Cfrac%7Bexp%28o%7Bj%7D%29%7D%7B%20%7B%5Ctextstyle%20%5Csum%7Bk%7D%5E%7B%7Dexp%28o%7Bk%7D%7D%29%20%7D&id=ZQhUR)中如果3. 线性神经网络 - 图36的一些数值非常大,则3. 线性神经网络 - 图37#card=math&code=exp%28o%7Bk%7D%29&id=eoDpl)可能大于数据类型容许的最大数字,即上溢(overflow)。 这将使分母或分子变为inf, 最后得到0、infnan的预测值。为了解决这个问题,从所有![](https://g.yuque.com/gr/latex?o%7Bk%7D#card=math&code=o%7Bk%7D&id=X55LS)中减去![](https://g.yuque.com/gr/latex?max(o%7Bk%7D)#card=math&code=max%28o%7Bk%7D%29&id=Xd7rQ):
![](https://g.yuque.com/gr/latex?%5Cbegin%7Baligned%7D%0A%5Chat%7By%7D
%7Bj%7D%20%26%3D%5Cfrac%7B%5Cexp%20%5Cleft(o%7Bj%7D-%5Cmax%20%5Cleft(o%7Bk%7D%5Cright)%5Cright)%20%5Cexp%20%5Cleft(%5Cmax%20%5Cleft(o%7Bk%7D%5Cright)%5Cright)%7D%7B%5Csum%7Bk%7D%20%5Cexp%20%5Cleft(o%7Bk%7D-%5Cmax%20%5Cleft(o%7Bk%7D%5Cright)%5Cright)%20%5Cexp%20%5Cleft(%5Cmax%20%5Cleft(o%7Bk%7D%5Cright)%5Cright)%7D%20%5C%5C%0A%26%3D%5Cfrac%7B%5Cexp%20%5Cleft(o%7Bj%7D-%5Cmax%20%5Cleft(o%7Bk%7D%5Cright)%5Cright)%7D%7B%5Csum%7Bk%7D%20%5Cexp%20%5Cleft(o%7Bk%7D-%5Cmax%20%5Cleft(o%7Bk%7D%5Cright)%5Cright)%7D%0A%5Cend%7Baligned%7D%0A#card=math&code=%5Cbegin%7Baligned%7D%0A%5Chat%7By%7D%7Bj%7D%20%26%3D%5Cfrac%7B%5Cexp%20%5Cleft%28o%7Bj%7D-%5Cmax%20%5Cleft%28o%7Bk%7D%5Cright%29%5Cright%29%20%5Cexp%20%5Cleft%28%5Cmax%20%5Cleft%28o%7Bk%7D%5Cright%29%5Cright%29%7D%7B%5Csum%7Bk%7D%20%5Cexp%20%5Cleft%28o%7Bk%7D-%5Cmax%20%5Cleft%28o%7Bk%7D%5Cright%29%5Cright%29%20%5Cexp%20%5Cleft%28%5Cmax%20%5Cleft%28o%7Bk%7D%5Cright%29%5Cright%29%7D%20%5C%5C%0A%26%3D%5Cfrac%7B%5Cexp%20%5Cleft%28o%7Bj%7D-%5Cmax%20%5Cleft%28o%7Bk%7D%5Cright%29%5Cright%29%7D%7B%5Csum%7Bk%7D%20%5Cexp%20%5Cleft%28o%7Bk%7D-%5Cmax%20%5Cleft%28o%7Bk%7D%5Cright%29%5Cright%29%7D%0A%5Cend%7Baligned%7D%0A&id=rQUje)
上述操作后,可能有些![](https://g.yuque.com/gr/latex?o
%7Bj%7D%E2%88%92max(o%7Bk%7D)#card=math&code=o%7Bj%7D%E2%88%92max%28o%7Bk%7D%29&id=BkgoW)具有较大的负值,从而导致![](https://g.yuque.com/gr/latex?exp(o%7Bj%7D%E2%88%92max(o%7Bk%7D))#card=math&code=exp%28o%7Bj%7D%E2%88%92max%28o%7Bk%7D%29%29&id=kVVrM)将有接近零的值,即下溢(underflow)。 这些值可能会四舍五入为零,使3. 线性神经网络 - 图38为零,并且使得3. 线性神经网络 - 图39#card=math&code=log%28y%5E%7Bj%7D%29&id=vO87q)的值为-inf。 反向传播几步后,可能会出现nan结果。
但是,在我们的交叉熵损失函数中,尽管要计算指数函数,但最终在计算时使用的是它们的对数。
![](https://g.yuque.com/gr/latex?%5Cbegin%7Baligned%7D%0A%5Clog%20%5Cleft(%5Chat%7By%7D
%7Bj%7D%5Cright)%20%26%3D%5Clog%20%5Cleft(%5Cfrac%7B%5Cexp%20%5Cleft(o%7Bj%7D-%5Cmax%20%5Cleft(o%7Bk%7D%5Cright)%5Cright)%7D%7B%5Csum%7Bk%7D%20%5Cexp%20%5Cleft(o%7Bk%7D-%5Cmax%20%5Cleft(o%7Bk%7D%5Cright)%5Cright)%7D%5Cright)%20%5C%5C%0A%26%3D%5Clog%20%5Cleft(%5Cexp%20%5Cleft(o%7Bj%7D-%5Cmax%20%5Cleft(o%7Bk%7D%5Cright)%5Cright)%5Cright)-%5Clog%20%5Cleft(%5Csum%7Bk%7D%20%5Cexp%20%5Cleft(o%7Bk%7D-%5Cmax%20%5Cleft(o%7Bk%7D%5Cright)%5Cright)%5Cright)%20%5C%5C%0A%26%3Do%7Bj%7D-%5Cmax%20%5Cleft(o%7Bk%7D%5Cright)-%5Clog%20%5Cleft(%5Csum%7Bk%7D%20%5Cexp%20%5Cleft(o%7Bk%7D-%5Cmax%20%5Cleft(o%7Bk%7D%5Cright)%5Cright)%5Cright)%0A%5Cend%7Baligned%7D%0A#card=math&code=%5Cbegin%7Baligned%7D%0A%5Clog%20%5Cleft%28%5Chat%7By%7D%7Bj%7D%5Cright%29%20%26%3D%5Clog%20%5Cleft%28%5Cfrac%7B%5Cexp%20%5Cleft%28o%7Bj%7D-%5Cmax%20%5Cleft%28o%7Bk%7D%5Cright%29%5Cright%29%7D%7B%5Csum%7Bk%7D%20%5Cexp%20%5Cleft%28o%7Bk%7D-%5Cmax%20%5Cleft%28o%7Bk%7D%5Cright%29%5Cright%29%7D%5Cright%29%20%5C%5C%0A%26%3D%5Clog%20%5Cleft%28%5Cexp%20%5Cleft%28o%7Bj%7D-%5Cmax%20%5Cleft%28o%7Bk%7D%5Cright%29%5Cright%29%5Cright%29-%5Clog%20%5Cleft%28%5Csum%7Bk%7D%20%5Cexp%20%5Cleft%28o%7Bk%7D-%5Cmax%20%5Cleft%28o%7Bk%7D%5Cright%29%5Cright%29%5Cright%29%20%5C%5C%0A%26%3Do%7Bj%7D-%5Cmax%20%5Cleft%28o%7Bk%7D%5Cright%29-%5Clog%20%5Cleft%28%5Csum%7Bk%7D%20%5Cexp%20%5Cleft%28o%7Bk%7D-%5Cmax%20%5Cleft%28o%7Bk%7D%5Cright%29%5Cright%29%5Cright%29%0A%5Cend%7Baligned%7D%0A&id=yF2Cw)
此时避免计算![](https://g.yuque.com/gr/latex?exp(o
%7Bj%7D%E2%88%92max(o%7Bk%7D))#card=math&code=exp%28o%7Bj%7D%E2%88%92max%28o_%7Bk%7D%29%29&id=dHAoE),解决了下溢问题。

3.8 softmax回归简洁实现

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)    # 以均值为0,标准差为0.01随机初始化权重

net.apply(init_weights);
loss = nn.CrossEntropyLoss(reduction='none')
# 使用学习率为0.1的小批量随机梯度下降作为优化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
# 模型训练
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

output_softmax-regression-scratch_a48321_180_0.svg


  1. 梯度的方向是函数在给定点上升最快的方向,那么梯度的反方向就是函数在给定点下降最快的方向 ↩︎