目录
前言
前面3.3节我们实现了线性回归的简洁实现。 同样,通过深度学习框架的高级API也能更方便地实现softmax回归模型。
至此,我们对深度学习已经有了基本的认识。起码应该明白了,深度学习大概是个什么流程。为后面学习更加复杂的回归打下了基础。
本节是第三章的最后一节,接下来我们开始。
一、加载数据集
这一部分和前一节一样,d2l包里面有集成好的load_data_fashion_mnist,我不用d2l包。但是我们之前已经把load_data_fashion_minist写成了一个类。
from load_data_fashion_mnist import FashionMNISTDataLoader
# 加载数据集
fashion_minist = FashionMNISTDataLoader(batch_size=256)
train_iter, test_iter = fashion_minist.create_dataloaders()
二、初始化模型参数
from torch import nn
from torch.nn import Sequential
# 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);
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
nn.Flatten()将多维输入张量展平为一维向量,nn.Linear(784, 10),创造一个输入为28*28=784输出为10的线性网络。
Sequential中,对于 Fashion-MNIST 的 [batch_size, 1, 28, 28]
输入,经过 Flatten()
后会变成 [batch_size, 784],
然后传递给Linear(748,10)层。
def init_weights(m):#权重初始化
if判断语句的意思是,只对nn.Linear层进行初始化,把权重设置成均值0和标准差0.01随机初始值。
apply()方法会递归地遍历网络中的所有模块,对所有子模块调用init_weights函数。
三、损失函数
上一节,我们在实现损失函数之前。需要定义softmax操作,然后单独定义交叉熵损失。
而这里只需要一行代码:
loss = nn.CrossEntropyLoss(reduction='none')
nn.CrossentropyLoss是用于多酚类问题的标准损失函数,它结合了:
LogSoftmax(对数softmax)
NLLLoss(负数对数似然损失)
当设置reduction='none'时,损失函数不会对批次中的样本损失进行任何聚合,而是为每个样本返回独立的损失值。
四、优化器
和3.3节简单实现线性回归一样。使用学习率为0.1的小批量随机梯度下降作为优化算法。
import torch
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
五、训练
调用3.6节中定义的训练函数来训练模型。
我把训练模型封装成了一个类ClassificationTrainer,只要调用它的训练方法就可以进行训练了。
num_epochs = 10
train = ClassificationTrainer(net,train_iter,test_iter,loss,trainer)
train.train(num_epochs)
训练类:
import torch
from accumulator import Accumulator
from animator import Animator
class ClassificationTrainer:
def __init__(self, net, train_iter, test_iter, loss, updater):
self.net = net
self.train_iter = train_iter
self.test_iter = test_iter
self.loss = loss
self.updater = updater
def accuracy(self, y_hat, y):
"""计算预测正确的数量"""
# 使用 detach() 分离计算图,避免梯度计算
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1)
# 确保使用 detach() 来分离计算图
y_hat = y_hat.detach()
cmp = y_hat.type(y.dtype) == y
return float(cmp.type(y.dtype).sum())
def evaluate_accuracy(self):
"""计算模型在测试数据集上的精度"""
metric = Accumulator(2) # 正确预测数、预测总数
for X, y in self.test_iter:
# 使用 torch.no_grad() 来禁用梯度计算
with torch.no_grad():
y_hat = self.net(X)
metric.add(self.accuracy(y_hat, y), y.numel())
return metric[0] / metric[1]
def train_epoch(self):
"""训练模型一个迭代周期"""
metric = Accumulator(3) # 训练损失总和、训练准确度总和、样本数
for X, y in self.train_iter:
# 计算梯度并更新参数
y_hat = self.net(X)
l = self.loss(y_hat, y)
if isinstance(self.updater, torch.optim.Optimizer):
# 使用PyTorch内置的优化器和损失函数
self.updater.zero_grad()
l.mean().backward()
self.updater.step()
else:
# 使用定制的优化器和损失函数
l.sum().backward()
self.updater(X.shape[0])
# 使用 detach() 和 item() 来安全地获取标量值
metric.add(float(l.sum().detach()), self.accuracy(y_hat, y), y.numel())
# 返回训练损失和训练精度
return metric[0] / metric[2], metric[1] / metric[2]
def train(self, num_epochs):
"""训练模型并显示训练过程"""
# 创建动画器
animator = Animator(xlabel='epoch', xlim=[1, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
for epoch in range(num_epochs):
# 训练一个epoch
train_metrics = self.train_epoch()
# 在测试集上评估精度
test_acc = self.evaluate_accuracy()
# 添加到动画器
animator.add(epoch + 1,
[train_metrics[0], train_metrics[1], test_acc])
print(f'Epoch {epoch + 1}: '
f'train loss {train_metrics[0]:.4f}, '
f'train acc {train_metrics[1]:.3f}, '
f'test acc {test_acc:.3f}')
animator.close()