思路:
这段代码是使用PyTorch实现的LeNet-5卷积神经网络,用于处理Fashion-MNIST数据集。
代码流程:
import torch
import torchvision
from torchvision import transforms
from torch.utils import data
from torch import nn
import matplotlib.pyplot as plt
from batch_normalization import BatchNorm, batch_norm
这些是导入所需的库。torch
是PyTorch的主要库,torchvision
提供了处理视觉数据集的功能,transforms
提供了数据预处理的工具,data
提供了数据加载和处理的类,nn
包含了神经网络的模块和函数,matplotlib.pyplot
用于绘图,BatchNorm
和batch_norm
是自定义的批归一化类。批归一化函数具体可查看:batch-norm slides (d2l.ai)
class Accumulator:
"""在`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]
这是一个累加器类,用于在n
个变量上进行累加操作。它有add
方法用于将输入的参数累加到相应的变量上,reset
方法用于将变量重置为0,__getitem__
方法用于获取指定索引的变量值。
def load_data_fashion_mnist(batch_size, resize=None):
"""下载Fashion-MNIST数据集,然后将其加载到内存中。"""
trans = [transforms.ToTensor()]
if resize:
trans.insert(0, transforms.Resize(resize)) # 对图片进行扩充
trans = transforms.Compose(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, ),
data.DataLoader(mnist_test, batch_size, shuffle=False, ))
这个函数用于下载和加载Fashion-MNIST数据集。它接受batch_size
参数用于指定每个批次的样本数量,resize
参数用于指定是否对图像进行大小调整。通过使用transforms
模块中的函数,可以将数据集转换为张量形式,并进行可选的大小调整。最后,函数返回两个数据加载器,分别用于训练集和测试集。
def accuracy(y_hat, y):
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
return float(cmp.type(y.dtype).sum())
这个函数用于计算模型的预测准确率。它接受模型的预测值y_hat
和真实标签y
作为输入。如果y_hat
的形状大于1维且第二维的大小大于1,则使用argmax
方法获取预测标签。然后,将预测标签与真实标签进行比较,计算准确预测的数量,并返回准确率。
def evaluate_accuracy_gpu(net, data_iter, device=None):
"""使用GPU计算模型在数据集上的精度。"""
if isinstance(net, torch.nn.Module):
net.eval()
if not device:
device = next(iter(net.parameters())).device
metric = Accumulator(2)
for X, y in data_iter:
if isinstance(X, list):
X = [x.to(device) for x in X]
else:
X = X.to(device)
y = y.to(device)
metric.add(accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
这个函数用于在GPU上计算模型在给定数据集上的准确率。它接受模型(net
)、数据迭代器(data_iter
)和设备(device
)作为输入。首先,将模型设置为评估模式,并将其移动到指定的设备上。然后,使用累加器(metric
)来累加准确预测的数量和总样本数量。最后,返回准确率。
class Reshape(torch.nn.Module):
def forward(self, x):
return x.view(-1, 1, 28, 28)
这是一个自定义的PyTorch模块,用于将输入数据的形状调整为指定的形状。它将输入数据的形状调整为(-1, 1, 28, 28)
,其中-1
表示自动计算该维度的大小。
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
print('training on', device)
net.to(device)
# 创建一个空的图形对象和子图对象
fig, ax = plt.subplots()
# 设置 x 轴标签和范围
ax.set_xlabel('epoch')
ax.set_xlim([1, num_epochs])
# 设置图例
ax.legend(['train loss', 'train acc', 'test acc'])
# 创建空列表来保存训练损失、训练准确率和测试准确率
train_loss_list = []
train_acc_list = []
test_acc_list = []
optimizer = torch.optim.SGD(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss()
# 在每个 epoch 结束后更新图形
for epoch in range(num_epochs):
metric = Accumulator(3)
net.train()
for i, (X, y) in enumerate(train_iter):
optimizer.zero_grad()
X, y = X.to(device), y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
l.backward()
optimizer.step()
with torch.no_grad():
metric.add(l * X.shape[0], accuracy(y_hat, y), X.shape[0])
train_l = metric[0] / metric[2]
train_acc = metric[1] / metric[2]
test_acc = evaluate_accuracy_gpu(net, test_iter)
# 将训练损失、训练准确率和测试准确率添加到列表中
train_loss_list.append(train_l)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
# 清除当前图形
ax.clear()
# 绘制训练损失、训练准确率和测试准确率曲线
ax.plot(range(1, epoch + 2), train_loss_list, label='train loss')
ax.plot(range(1, epoch + 2), train_acc_list, label='train acc')
ax.plot(range(1, epoch + 2), test_acc_list, label='test acc')
# 设置 x 轴标签和范围
ax.set_xlabel('epoch')
ax.set_xlim([1, num_epochs])
# 设置图例
ax.legend(['train loss', 'train acc', 'test acc'])
# 更新图形显示
plt.pause(0.1)
# 显示最终的图形
plt.show()
这是一个用于训练模型的函数。它接受模型(`net`)、训练数据迭代器(`train_iter`)、测试数据迭代器(`test_iter`)、训练的轮数(`num_epochs`)、学习率(`lr`)和设备(`device`)作为输入。
首先,使用`init_weights`函数初始化模型的参数,并将模型移动到指定的设备上。然后,创建一个图形对象和子图对象,并设置图形的标签和范围。
接下来,创建空列表来保存训练损失、训练准确率和测试准确率。使用`torch.optim.SGD`定义优化器和`nn.CrossEntropyLoss`定义损失函数。
在每个epoch中,使用累加器(`metric`)来累加训练损失、训练准确率和训练样本数量。然后,计算并更新梯度,使用优化器进行参数更新。
在每个epoch结束后,使用`evaluate_accuracy_gpu`函数计算测试准确率,并将训练损失、训练准确率和测试准确率添加到相应的列表中。
最后,绘制训练损失、训练准确率和测试准确率的曲线,并返回训练过程中的损失和准确率列表。
Lenet = nn.Sequential(Reshape(), nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4),
nn.Sigmoid(), nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16,kernel_size=5), BatchNorm(16, num_dims=4),
nn.Sigmoid(), nn.MaxPool2d(kernel_size=2, stride=2),
nn.Flatten(), nn.Linear(16 * 4 * 4, 120),
BatchNorm(120, num_dims=2), nn.Sigmoid(),
nn.Linear(120, 84), BatchNorm(84, num_dims=2),
nn.Sigmoid(), nn.Linear(84, 10))
然后,我们构建Lenet 卷积神经网络,并加入了批归一化操作。
batch_size = 256
train_iter, test_iter = load_data_fashion_mnist(batch_size)
lr, num_epochs = 1.0, 10
device = torch.device('cuda')
train_ch6(Lenet, train_iter, test_iter, num_epochs, lr, device)
输入超参数,并运行模型进行训练。
代码结果:
1.批归一化后的结果![](https://i-blog.csdnimg.cn/blog_migrate/150c011a00946932d7fd51b0cba3351d.png)
2.原始结果
可以看出,加入批归一化后,训练集和验证集的准确率均有部分提升,但也引发了过拟合的问题。