李沐动手学深度学习(PyTorch)课程学习笔记第二章:线性神经网络。
1. 线性回归的从零实现
为了简单起见,我们将根据带有噪声的线性模型构造一个人造数据集。我们的任务是使用这个有限样本的数据集来恢复这个模型的参数。
首先我们生成数据集:
1 | import random |
训练模型时要对数据集进行遍历,每次抽取一小批量样本,并使用它们来更新我们的模型。由于这个过程是训练机器学习算法的基础,所以有必要定义一个函数,该函数能打乱数据集中的样本并以小批量方式获取数据。
在下面的代码中,我们定义一个 data_iter
函数,该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为 batch_size
的小批量。每个小批量包含一组特征和标签:
1 | def data_iter(batch_size, features, labels): |
在我们开始用小批量随机梯度下降优化我们的模型参数之前,我们需要先有一些参数。在下面的代码中,我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重,并将偏置初始化为0:
1 | w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True) |
定义线性回归模型:
1 | def linreg(X, w, b): |
定义损失函数:
1 | def squared_loss(y_hat, y): |
定义优化算法:
1 | def sgd(params, lr, batch_size): |
现在我们已经准备好了模型训练所有需要的要素,可以实现主要的训练过程部分了。理解这段代码至关重要,因为从事深度学习后,相同的训练过程几乎一遍又一遍地出现。在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。计算完损失后,我们开始反向传播,存储每个参数的梯度。最后,我们调用优化算法(随机梯度下降法SGD)来更新模型参数:
1 | # 超参数 |
2. 线性回归的简洁实现
首先生成数据集:
1 | import numpy as np |
读取数据集:
1 | def load_array(data_arrays, batch_size, is_train=True): |
接下来我们定义模型,在 PyTorch 中,全连接层在 Linear
类中定义。值得注意的是,我们将两个参数传递到 nn.Linear
中。第一个指定输入特征形状,即2,第二个指定输出特征形状,输出特征形状为单个标量,因此为1:
1 | from torch import nn |
定义损失函数,计算均方误差使用的是 MSELoss
类,也称平方 L2 范数,默认情况下,它返回所有样本损失的平均值:
1 | loss_function = nn.MSELoss() |
定义优化算法:
1 | optimizer = torch.optim.SGD(net.parameters(), lr=0.03) |
训练过程代码与我们从零开始实现时所做的非常相似:
1 | num_epochs = 3 |
3. Softmax回归的从零实现
首先读入 Fashion-MNIST 数据集,原始数据集中的每个样本都是28*28的图像。本节将展平每个图像,把它们看作长度为784的向量。在后面的章节中,我们将讨论能够利用图像空间结构的特征,但现在我们暂时只把每个像素位置看作一个特征。
1 | import torch |
初始化模型参数,在 Softmax 回归中,我们的输出与类别一样多。因为我们的数据集有10个类别,所以网络输出维度为10。因此,权重将构成一个784*10的矩阵,偏置将构成一个1*10的行向量:
1 | num_inputs = 784 |
定义 Softmax 操作,注意,虽然这在数学上看起来是正确的,但我们在代码实现中有点草率。矩阵中的非常大或非常小的元素可能造成数值上溢或下溢,但我们没有采取措施来防止这点:
1 | def softmax(X): |
定义 Softmax 操作后,我们可以实现 Softmax 回归模型。下面的代码定义了输入如何通过网络映射到输出。注意,将数据传递到模型之前,我们使用 reshape
函数将每张原始图像展平为向量:
1 | def net(X): |
接下来我们定义损失函数,交叉熵采用真实标签的预测概率的负对数似然。这里我们不使用 Python 的 for
循环迭代预测(这往往是低效的),而是通过一个运算符选择所有元素。下面我们创建一个数据样本 y_hat
,其中包含2个样本在3个类别的预测概率,以及它们对应的标签 y
。有了 y
,我们知道在第一个样本中,第一类是正确的预测;而在第二个样本中,第三类是正确的预测。然后使用 y
作为 y_hat
中概率的索引,我们选择第一个样本中第一个类的概率和第二个样本中第三个类的概率:
1 | y = torch.tensor([0, 2]) |
为了计算精度,我们执行以下操作。首先,如果 y_hat
是矩阵,那么假定第二个维度存储每个类的预测分数。我们使用 argmax
获得每行中最大元素的索引来获得预测类别。然后我们将预测类别与真实 y
元素进行比较。由于等式运算符 ==
对数据类型很敏感,因此我们将 y_hat
的数据类型转换为与 y
的数据类型一致。结果是一个包含0(错)和1(对)的张量。最后,我们求和会得到正确预测的数量:
1 | def accuracy(y_hat, y): |
同样,对于任意数据迭代器 data_iter
可访问的数据集,我们可以评估在任意模型 net
的精度,这里定义一个实用程序类 Accumulator
,用于对多个变量进行累加。在 evaluate_accuracy
函数中,我们在 Accumulator
实例中创建了2个变量,分别用于存储正确预测的数量和预测的总数量。当我们遍历数据集时,两者都将随着时间的推移而累加:
1 | def evaluate_accuracy(net, data_iter): |
接下来可以开始进行训练:
1 | def train_epoch_ch3(net, train_iter, loss_function, updater): |
可以在项目路径下打开 Anaconda 的 PyTorch 环境,然后使用 TensorBoard 查看训练曲线:
1 | tensorboard --logdir logs\FashionMNIST_train_log |
4. Softmax回归的简洁实现
首先读取数据集:
1 | import torch |
定义模型,我们只需在 Sequential
中添加一个带有10个输出的全连接层,这10个输出分别表示对10个类别的预测概率:
1 | # PyTorch不会隐式地调整输入的形状。因此,我们在线性层前定义了展平层(Flatten),来调整网络输入的形状 |
定义训练函数,由于之后很多模型的训练过程也是相似的,因此该函数可以复用:
1 | # 以后较为通用的函数将定义到util.functions.py中 |
最后设定超参数训练模型:
1 | writer_path = '../logs/FashionMNIST_train_log' |
下一章:多层感知机。