实现神经网络实例
使用PyTorch构建神经网络使用的主要工具(或类)及相互关系,如图3-2所示。
从图3-2可知,构建网络层可以基于Module类或函数(nn.functional)。nn中的大多数
层(Layer)在functional中都有与之对应的函数。nn.functional中函数与nn.Module中的
Layer的主要区别是后者继承Module类,会自动提取可学习的参数。而nn.functional更像是
纯函数。两者功能相同,且性能也没有很大区别,那么如何选择呢?像卷积层、全连接
层、Dropout层等因含有可学习参数,一般使用nn.Module,而激活函数、池化层不含可学
习参数,可以使用nn.functional中对应的函数。下面通过实例来说明如何使用nn构建一个
网络模型。

背景说明
这节将利用神经网络完成对手写数字进行识别的实例,来说明如何借助nn工具箱来实
现一个神经网络,并对神经网络有个直观了解。在这个基础上,后续我们将对nn的各模块
进行详细介绍。实例环境使用PyTorch1.0+,GPU或CPU,源数据集为MNIST。
主要步骤:
1)利用PyTorch内置函数mnist下载数据。
2)利用torchvision对数据进行预处理,调用torch.utils建立一个数据迭代器。
3)可视化源数据。
4)利用nn工具箱构建神经网络模型。
5)实例化模型,并定义损失函数及优化器。
6)训练模型。
7)可视化结果。
神经网络的结构如图3-3所示。
使用两个隐含层,每层激活函数为ReLU,最后使用torch.max(out,1)找出张量out最大
值对应索引作为预测值。

准备数据
1.导人必要的模块
python
import numpy as np
import torch
# 导入 PyTorch 内置的 mnist 数据
from torchvision.datasets import mnist
#导入预处理模块
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
#导入nn及优化器
import torch.nn.functional as F
import torch.optim as optim
from torch import nn
2.定义一些超参数
python
# 定义一些超参数
train_batch_size = 64
test_batch_size = 128
learning_rate = 0.01
num_epoches = 20
lr = 0.01
momentum = 0.5
3.下载数据并对数据进行预处理
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize([0.5], [0.5])])
#下载数据,并对数据进行预处理
train_dataset = mnist.MNIST('./data', train=True, transform=transform, download=True)
test_dataset = mnist.MNIST('./data', train=False, transform=transform)
#dataloader是一个可迭代对象,可以使用迭代器一样使用。
train_loader = DataLoader(train_dataset, batch_size=train_batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=test_batch_size, shuffle=False)
完整代码
python
import numpy as np
import torch
#导入Pytorch内置的mnist数据
from torchvision.datasets import mnist
#导入预处理模块
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
#导入nn及优化器
import torch.nn.functional as F
import torch.optim as optim
from torch import nn
#定义一些超参数
train_batch_size=64
test_batch_size=128
learning_rate=0.01
num_epochs=20
lr=0.01
momentum=0.5
#定义预处理函数,这些预处理依次放在Compose函数中
transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize([0.5],[0.5])])
#下载数据,并对数据进行预处理
train_dataset=mnist.MNIST('./data',train=True,transform=transform,download=True)
test_dataset=mnist.MNIST('./data',train=False,transform=transform)
#dataloader是一个可迭代对象,可以使用迭代器一样使用
train_loader=DataLoader(train_dataset,batch_size=train_batch_size,shuffle=True)
test_loader=DataLoader(test_dataset,batch_size=test_batch_size,shuffle=False)
可视化源数据
python
import matplotlib.pyplot as plt
%matplotlib inline
examples = enumerate(test_loader)
batch_idx, (example_data, example_targets) = next(examples)
fig = plt.figure()
for i in range(6):
plt.subplot(2,3,i+1)
plt.tight_layout()
plt.imshow(example_data[i][0], cmap='gray', interpolation='none')
plt.title("Ground Truth: {}".format(example_targets[i]))
plt.xticks([])
plt.yticks([])
MNIST源数据示例如图3-4所示。

构建模型
数据预处理之后,我们开始构建网络,创建模型。
1)构建网络。
python
class Net(nn.Module):
"""
使用sequential构建网络,Sequential()函数的功能是将网络的层组合到一起
"""
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
super(Net, self).__init__()
self.layer1 = nn.Sequential(nn.Linear(in_dim, n_hidden_1),nn.BatchNorm1d(n_hidden_1))
self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, n_hidden_2),nn.BatchNorm1d (n_hidden_2))
self.layer3 = nn.Sequential(nn.Linear(n_hidden_2, out_dim))
def forward(self, x):
x = F.relu(self.layer1(x))
x = F.relu(self.layer2(x))
x = self.layer3(x)
return x
2)实例化网络。
python
#检测是否有可用的GPU,有则使用,否则使用CPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#实例化网络
model = Net(28 * 28, 300, 100, 10)
model.to(device)
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)
训练模型
训练模型,这里使用for循环,进行迭代。其中包括对训练数据的训练模型,然后用
测试数据的验证模型。
1.训练模型
python
# 开始训练
losses = []
acces = []
eval_losses = []
eval_acces = []
for epoch in range(num_epoches):
train_loss = 0
train_acc = 0
model.train()
#动态修改参数学习率
if epoch%5==0:
optimizer.param_groups[0]['lr']*=0.1
for img, label in train_loader:
img=img.to(device)
label = label.to(device)
img = img.view(img.size(0), -1)
# 前向传播
out = model(img)
loss = criterion(out, label)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 记录误差
train_loss += loss.item()
# 计算分类的准确率
_, pred = out.max(1)
num_correct = (pred == label).sum().item()
acc = num_correct / img.shape[0]
train_acc += acc
losses.append(train_loss / len(train_loader))
acces.append(train_acc / len(train_loader))
# 在测试集上检验效果
eval_loss = 0
eval_acc = 0
# 将模型改为预测模式
model.eval()
for img, label in test_loader:
img=img.to(device)
label = label.to(device)
img = img.view(img.size(0), -1)
out = model(img)
loss = criterion(out, label)
# 记录误差
eval_loss += loss.item()
# 记录准确率
_, pred = out.max(1)
num_correct = (pred == label).sum().item()
acc = num_correct / img.shape[0]
eval_acc += acc
eval_losses.append(eval_loss / len(test_loader))
eval_acces.append(eval_acc / len(test_loader))
print('epoch: {}, Train Loss: {:.4f}, Train Acc: {:.4f}, Test Loss: {:.4f}, Test Acc: {:.4f}'
.format(epoch, train_loss / len(train_loader), train_acc / len(train_loader),
eval_loss / len(test_loader), eval_acc / len(test_loader)))
完整代码
python
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import numpy as np
# 超参数设置
batch_size = 64
lr = 0.01
momentum = 0.9
num_epoches = 10
# 数据加载与预处理
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = datasets.MNIST(
'./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(
'./data', train=False, transform=transform)
train_loader = torch.utils.data.DataLoader(
train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(
test_dataset, batch_size=batch_size, shuffle=False)
# 数据可视化
examples = enumerate(test_loader)
batch_idx, (example_data, example_targets) = next(examples)
fig = plt.figure(figsize=(10, 6))
for i in range(6):
plt.subplot(2, 3, i+1)
plt.tight_layout()
plt.imshow(example_data[i][0](), cmap='gray', interpolation='none')
plt.title("Ground Truth: {}".format(example_targets[i])) # 修复双花括号问题
plt.xticks([])
plt.yticks([])
plt.show()
# 网络定义
class Net(nn.Module):
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
super(Net, self).__init__()
self.layer1 = nn.Sequential(
nn.Linear(in_dim, n_hidden_1),
nn.BatchNorm1d(n_hidden_1)
)
self.layer2 = nn.Sequential(
nn.Linear(n_hidden_1, n_hidden_2),
nn.BatchNorm1d(n_hidden_2)
)
self.layer3 = nn.Linear(n_hidden_2, out_dim)
self.dropout = nn.Dropout(0.2) # 添加Dropout防止过拟合
def forward(self, x):
x = F.relu(self.layer1(x))
x = self.dropout(x)
x = F.relu(self.layer2(x))
x = self.dropout(x)
x = self.layer3(x)
return x
# 设备检测
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
# 模型初始化
model = Net(28*28, 300, 100, 10).to(device)
# 损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1) # 学习率调度器
# 训练过程记录
losses, acces, eval_losses, eval_acces = [], [], [], []
# 训练循环
for epoch in range(num_epoches):
model.train()
train_loss, train_acc = 0, 0
# 使用学习率调度器替代手动调整
scheduler.step()
for img, label in train_loader:
img, label = img.to(device), label.to(device)
img = img.view(img.size(0), -1)
# 前向传播
out = model(img)
loss = criterion(out, label)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 记录指标
train_loss += loss.item()
_, pred = out.max(1)
train_acc += (pred == label).sum().item() / img.shape[0]
# 验证阶段
model.eval()
eval_loss, eval_acc = 0, 0
with torch.no_grad(): # 减少内存消耗
for img, label in test_loader:
img, label = img.to(device), label.to(device)
img = img.view(img.size(0), -1)
out = model(img)
loss = criterion(out, label)
eval_loss += loss.item()
_, pred = out.max(1)
eval_acc += (pred == label).sum().item() / img.shape[0]
# 记录每个epoch的指标
losses.append(train_loss/len(train_loader))
acces.append(train_acc/len(train_loader))
eval_losses.append(eval_loss/len(test_loader))
eval_acces.append(eval_acc/len(test_loader))
# 打印训练进度
print(f'Epoch [{epoch+1}/{num_epoches}], '
f'Train Loss: {losses[-1]:.4f}, Train Acc: {acces[-1]:.4f}, '
f'Test Loss: {eval_losses[-1]:.4f}, Test Acc: {eval_acces[-1]:.4f}')
# 训练过程可视化
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(losses, label='Train Loss')
plt.plot(eval_losses, label='Test Loss')
plt.legend()
plt.title('Loss Curves')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.subplot(1, 2, 2)
plt.plot(acces, label='Train Accuracy')
plt.plot(eval_acces, label='Test Accuracy')
plt.legend()
plt.title('Accuracy Curves')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.tight_layout()
plt.show()
# 模型保存
torch.save(model.state_dict(), 'mnist_model.pth')
print("Model saved to mnist_model.pth")
最后5次迭代的结果:
epoch: 15, Train Loss: 0.0047, Train Acc: 0.9995, Test Loss: 0.0543, Test Acc: 0.9839
epoch: 16, Train Loss: 0.0048, Train Acc: 0.9997, Test Loss: 0.0532, Test Acc: 0.9839
epoch: 17, Train Loss: 0.0049, Train Acc: 0.9996, Test Loss: 0.0544, Test Acc: 0.9839
epoch: 18, Train Loss: 0.0049, Train Acc: 0.9995, Test Loss: 0.0535, Test Acc: 0.9839
epoch: 19, Train Loss: 0.0049, Train Acc: 0.9996, Test Loss: 0.0536, Test Acc: 0.9836
这个神经网络的结构比较简单,只用了两层,也没有使用Dropout层,迭代20次,测
试准确率达到98%左右,效果还可以。不过,还是有提升空间,如果采用cnn、Dropout等
层,应该还可以提升模型性能。
可视化训练及测试损失值
python
plt.title('trainloss')
plt.plot(np.arange(len(losses)), losses)
plt.legend(['Train Loss'], loc='upper right')
运行结果如图3-5所示。
