⾸先导⼊本节实现所需的包或模块。
import torch
import torchvision
import numpy as np
import sys
sys.path.append("..") # 为了导⼊上层⽬录的d2lzh_pytorch
import d2lzh_pytorch as d2l
1.获取数据集
使⽤Fashion-MNIST数据集,并设置批量⼤⼩为256。
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
2.初始化模型参数
跟线性回归中的例⼦⼀样,我们将使⽤向量表示每个样本。已知每个样本输⼊是⾼和宽均为28像素的图像。模型的输⼊向量的⻓度是\(28 \times 28 =784\):该向量的每个元素对应图像中每个像素。由于图像有10个类别,单层神经⽹络输出层的输出个数为10,因此softmax回归的权重和偏差参数分别为\(784 \times 10\)和\(1 \times 10\)的矩阵。
num_inputs = 784
num_outputs = 10
W = torch.tensor(np.random.normal(0, 0.01, (num_inputs,
num_outputs)), dtype=torch.float)
b = torch.zeros(num_outputs, dtype=torch.float)
需要模型参数梯度,进行跟踪。
W.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
3. 实现softmax运算
对多维Tensor
按维度操作:给定⼀个 Tensor
矩阵 X
。我们可以只对其中同⼀列( dim=0
)或同⼀⾏( dim=1
)的元素求和,并在结果中保留⾏和列这两个维度( keepdim=True
)。
X = torch.tensor([[1, 2, 3], [4, 5, 6]])
print(X.sum(dim=0, keepdim=True))
print(X.sum(dim=1, keepdim=True))
输出结果:
tensor([[5, 7, 9]])
tensor([[ 6], [15]])
定义softmax运算:
矩阵 X
的⾏数是样本数,列数是输出个数。为了表达样本预测各个输出的概率,softmax运算会先通过 exp 函数对每个元素做指数运算,再对 exp
矩阵同⾏元素求和,最后令矩阵每⾏各元素与该⾏元素之和相除。这样⼀来,最终得到的矩阵每⾏元素和为1且⾮负。因此,该矩阵每⾏都是合法的概率分布。softmax运算的输出矩阵中的任意⼀⾏元素代表了⼀个样本在各个输出类别上的预测概率。
def softmax(X):
X_exp = X.exp()
partition = X_exp.sum(dim=1, keepdim=True)
return X_exp / partition # 这⾥应⽤了⼴播机制
可以看到,对于随机输⼊,我们将每个元素变成了⾮负数,且每⼀⾏和为1。
X = torch.rand((2, 5))
X_prob = softmax(X)
print(X_prob, X_prob.sum(dim=1))
输出结果:
tensor([[0.2206, 0.1520, 0.1446, 0.2690, 0.2138], [0.1540, 0.2290, 0.1387, 0.2019, 0.2765]]) tensor([1., 1.])
4. 定义模型
定义上节描述的softmax回归模型了。这⾥通过 view
函数将每张原始图像改成⻓度为 num_inputs
的向量。
def net(X):
return softmax(torch.mm(X.view((-1, num_inputs)), W) + b)
5. 定义损失函数
为了得到标签的预测概率,我们可以使⽤ gather
函数。在下⾯的例⼦中,变量 y_hat
是2个样本在3个类别的预测概率,变量 y
是这2个样本的标签类别。通过使⽤ gather
函数,我们得到了2个样本的标签的预测概率。与softmax回归数学表述中标签类别离散值从1开始逐⼀递增不同,在代码中,标签类别的离散值是从0开始逐⼀递增的。
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y = torch.LongTensor([0, 2])
y_hat.gather(1, y.view(-1, 1))
输出结果:
tensor([[0.1000], [0.5000]])
实现softmax回归中的交叉熵损失函数:
def cross_entropy(y_hat, y):
return - torch.log(y_hat.gather(1, y.view(-1, 1)))
6. 计算分类准确率
给定⼀个类别的预测概率分布 y_hat
,我们把预测概率最⼤的类别作为输出类别。如果它与真实类别 y
⼀致,说明这次预测是正确的。分类准确率即正确预测数量与总预测数量之⽐。
为了演示准确率的计算,下⾯定义准确率 accuracy
函数。其中 y_hat.argmax(dim=1) 返回矩阵 y_hat 每 ⾏ 中 最 ⼤ 元 素 的 索 引 , 且 返 回 结 果 与 变 量 y 形 状 相 同 。 相 等 条 件 判 断式 (y_hat.argmax(dim=1) == y)
是⼀个类型为 ByteTensor
的 Tensor
,我们⽤ float()
将其转换为值为0(相等为假)或1(相等为真)的浮点型 Tensor 。
def accuracy(y_hat, y):
return (y_hat.argmax(dim=1) == y).float().mean().item()
继续使⽤在演示 gather
函数时定义的变量 y_hat
和 y
,并将它们分别作为预测概率分布和标签。可以看到,第⼀个样本预测类别为2(该⾏最⼤元素0.6在本⾏的索引为2),与真实标签0不⼀致;第⼆个样本预测类别为2(该⾏最⼤元素0.5在本⾏的索引为2),与真实标签2⼀致。因此,这两个样本上的分类准确率为0.5。
print(accuracy(y_hat, y))
输出结果:
0.5
评价模型 net 在数据集 data_iter 上的准确率。
# 本函数已保存在d2lzh_pytorch包中⽅便以后使⽤。该函数将被逐步改进:它的完整实现将在“图像增⼴”⼀节中描述
def evaluate_accuracy(data_iter, net):
acc_sum, n = 0.0, 0
for X, y in data_iter:
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n
因为随机初始化了模型 net ,所以这个随机模型的准确率应该接近于类别个数10的倒数即0.1。
print(evaluate_accuracy(test_iter, net)
输出结果:
0.0568
7. 训练模型
num_epochs, lr = 5, 0.1
# 本函数已保存在d2lzh包中方便以后使用
def train_ch3(net, train_iter, test_iter, loss, num_epochs,
batch_size, params=None, lr=None, optimizer=None):
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
for X, y in train_iter:
y_hat = net(X)
l = loss(y_hat, y).sum()
# 梯度清零
if optimizer is not None:
optimizer.zero_grad()
elif params is not None and params[0].grad is not None:
for param in params:
param.grad.data.zero_()
l.backward()
if optimizer is None:
d2l.sgd(params, lr, batch_size)
else:
optimizer.step() # “softmax回归的简洁实现”⼀节将⽤到
train_l_sum += l.item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
n += y.shape[0]
test_acc = evaluate_accuracy(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f' % (epoch + 1, train_l_sum / n, train_acc_sum / n,test_acc))