PyTorch神经网络实战:搭建、训练与优化全解析
PyTorch神经网络实战:搭建、训练与优化全解析
Pytorch搭建并训练神经⽹络
流程:
- 数据预处理
- 构建模型
- 定制模型损失函数和优化器
- 训练并观察超参数
1.数据预处理
模型训练⽤的样本,⼤部分都来⾃于外部⽂件系统。本次训练⽤的数据来⾃框架内置的数据
集,所以代码没有泛化性。
PyTorch 有两个⽤于处理数据的⼯具: torch.utils.data.DataLoader 和 torch.utils.data.Dataset 。 Dataset 存储的是数据样本和对应的标签, DataLoader 把Dataset包装成⼀个可迭代的对象
本次案例的数据样本来⾃于Pytorch的TorchVision 数据集
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
#训练集
training_data = datasets.FashionMNIST(
root='./fashion_data', #下载的数据集文件保存到当前用户工作目录下
train=True, #选择训练集
download=True, #如果数据集不存在则下载
transform=ToTensor() #将数据集中的图片转换为Tensor
)
#测试集
test_data = datasets.FashionMNIST(
root='./fashion_data', #下载的数据集文件保存到当前用户工作目录下
train=False, #选择测试集
download=True, #如果数据集不存在则下载
transform=ToTensor() #将数据集中的图片转换为Tensor
)
下⼀步就是对已加载数据集的封装,把 Dataset 作为参数传递给 DataLoader 。这样,就在我们的数据集上包装了⼀个迭代器(iterator),这个迭代器还⽀持⾃动批处理、采样、打乱顺序和多进程数据加载等这些强⼤的功能。这⾥我们定义了模型训练期间,每个批次的数据样本量⼤⼩为64,即数据加载器在迭代中,每次返回⼀批 64 个数据特征和标签
batch_size = 64
#创建数据加载器
train_dataloader = DataLoader(training_data, batch_size=batch_size, shuffle=True) #shuffle=True表示随机打乱数据
test_dataloader = DataLoader(test_data, batch_size=batch_size,)
#测试数据加载器输出
for X,y in test_dataloader:
print(X.shape)
print(y.shape)
break
torch.Size([64, 1, 28, 28])
torch.Size([64])
2.构建模型
为了在 PyTorch 中定义神经⽹络,我们创建了⼀个继承⾃ nn.Module 的类。我们在 init 函数中定义⽹络层,并在 forward 函数中指定数据将如何通过⽹络。为了加速神经⽹络中的操作,我们可以将其移⾄ GPU(如果可⽤)
#检测可以使用的设备
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f'Using {device} device')
#定义神经网络模型
class NeuralNetwork(nn.Module):
def __init__(self):#初始化模型
#调用父类的构造函数
#nn.Module是所有神经网络模块的基类
#在构造函数中定义模型的层
super(NeuralNetwork, self).__init__()
self.flatten = nn.Flatten() #将输入的图片展平
self.linear_relu_stack = nn.Sequential(
#wx+b=[64,1,784] * [784,512] = [64,1,512]
nn.Linear(28*28, 512), #输入层到隐藏层
nn.ReLU(), #激活函数
nn.Linear(512, 512), #隐藏层到隐藏层
nn.ReLU(), #激活函数
nn.Linear(512, 10) #隐藏层到输出层
)
def forward(self, x):
x = self.flatten(x) #展平
logits = self.linear_relu_stack(x) #前向传播
return logits
model = NeuralNetwork().to(device) #将模型移动到GPU上(我这边用的是mac,只有cpu)
#打印模型结构
print(model)
Using cpu device
NeuralNetwork(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear_relu_stack): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)
)
)
pytorch是通过继承nn.Module类来定义模型的,nn.Module是一个抽象类,所有的模型都需要继承这个类。nn.Module类有两个重要的方法:__init__和forward。
- __init__方法用于定义模型的层,初始化神经网络层。
- forward方法用于定义模型的前向传播过程 ,模型的前向传播过程是指输入数据经过模型各层的计算,最终输出预测结果。
模型层分解
为了⽅便分解 FashionMNIST 模型中的各个层进⾏说明,我们取⼀个由 3 张⼤⼩为 28x28 的图像组成的⼩批量样本,看看当它们通过⽹络传递时会发⽣什么
input_image = torch.rand(3, 28, 28) # 随机生成一个3*28*28的张量
print(input_image.size()) # torch.Size([3, 28, 28])
torch.Size([3, 28, 28])
nn.Flatten
我们初始化nn.Flatten 层,将每个28x28 ⼤⼩的⼆维图像转换为 784 个像素值的连续数组(保持⼩批量维度(dim=0))
flatten = nn.Flatten()
flat_image = flatten(input_image) #展平
print(flat_image.size()) # torch.Size([3, 784])
torch.Size([3, 784])
nn.Linear
linear layer线性层是⼀个模块,它使⽤其存储的权重和偏置对输⼊应⽤线性变换。
layer1 = nn.Linear(in_features=28*28, out_features=20) #输入层到隐藏层
hidden1 = layer1(flat_image) #前向传播
print(hidden1.size()) # torch.Size([3, 20])
torch.Size([3, 20])
nn.ReLU
为了在模型的输⼊和输出之间创建复杂映射,我们使⽤⾮线性激活。激活函数在线性变换之后被调⽤,以便把结果值转为⾮线性,帮助神经⽹络学习到各种各样的关键特征值。 在这个模型中,线性层之间使⽤了 nn.ReLU ,还有很多激活函数可以在模型中引⼊⾮线性
print(f"ReLU 之前的数据:{hidden1}\n")
hidden1 = nn.ReLU()(hidden1) #激活函数
print(f"ReLU 之后的数据:{hidden1}\n")
ReLU 之前的数据:tensor([[ 0.1076, -0.3889, -0.0487, -0.3135, 0.4965, -0.3734, -0.0875, -0.4264,
-0.1462, 0.1055, 0.4484, -0.0941, 0.3491, 0.1826, 0.0837, -0.1524,
0.1437, 0.0158, 0.3113, -0.1521],
[ 0.0498, -0.2816, 0.0641, -0.3618, 0.6320, -0.2365, -0.3372, -0.1055,
-0.0341, -0.2914, 0.0492, -0.0811, 0.2271, 0.1012, 0.5374, -0.3657,
0.2239, -0.4457, -0.0392, -0.2840],
[-0.0594, -0.3986, 0.1304, -0.4336, 0.4789, -0.2339, -0.2769, -0.2875,
-0.0292, 0.1204, 0.2984, 0.1552, 0.0302, 0.0894, 0.4475, -0.2034,
0.2433, -0.1592, 0.2785, -0.5638]], grad_fn=<AddmmBackward0>)
ReLU 之后的数据:tensor([[0.1076, 0.0000, 0.0000, 0.0000, 0.4965, 0.0000, 0.0000, 0.0000, 0.0000,
0.1055, 0.4484, 0.0000, 0.3491, 0.1826, 0.0837, 0.0000, 0.1437, 0.0158,
0.3113, 0.0000],
[0.0498, 0.0000, 0.0641, 0.0000, 0.6320, 0.0000, 0.0000, 0.0000, 0.0000,
0.0000, 0.0492, 0.0000, 0.2271, 0.1012, 0.5374, 0.0000, 0.2239, 0.0000,
0.0000, 0.0000],
[0.0000, 0.0000, 0.1304, 0.0000, 0.4789, 0.0000, 0.0000, 0.0000, 0.0000,
0.1204, 0.2984, 0.1552, 0.0302, 0.0894, 0.4475, 0.0000, 0.2433, 0.0000,
0.2785, 0.0000]], grad_fn=<ReluBackward0>)
nn.Sequential
nn.Sequential 是⼀个有序的模块容器。数据按照容器中定义的顺序通过所有模块。我们可以使⽤顺序容器来组合⼀个像 seq_modules 这样的快速处理⽹络
seq_module = nn.Sequential(
flatten,
layer1,
nn.ReLU(),
nn.Linear(20, 10)
)
input_image = torch.rand(3, 28, 28)
logits = seq_module(input_image) #前向传播
nn.Softmax
神经⽹络的最后⼀个线性层返回的是 logits类型的值,它们的取值是[-∞, ∞]。 把这些值传递给nn.Softmax 模块。 logits的值将会被缩放到 [0, 1]的取值区间,代表模型对每个类别的预测概率。 dim 参数指⽰我们在向量的哪个维度中计算softmax的值(和为1)。使用方法如下:
softmax = nn.Softmax(dim=1)
output = softmax(logits) # 应用softmax
模型参数
神经⽹络内的许多层都是包含可训练参数的,即具有在训练期间可以优化的相关权重(weight)和偏置(bias)。⼦类 nn.Module 可以⾃动跟踪模型对象中定义的所有参数字段。使⽤模型的parameters() 或 named_parameters() ⽅法可以访问模型中所有的参数。 下⾯的代码可以迭代模型中的每⼀个参数,并打印出它们的⼤⼩和它们的值
print("Model structure:\n", model,"\n\n")
for name, param in model.named_parameters():
print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n") #打印模型参数的名称、大小和前两个值
Model structure:
NeuralNetwork(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear_relu_stack): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)
)
)
Layer: linear_relu_stack.0.weight | Size: torch.Size([512, 784]) | Values : tensor([[ 0.0300, 0.0312, 0.0013, …, 0.0280, -0.0099, -0.0330],
[ 0.0241, 0.0134, -0.0085, …, 0.0195, -0.0150, 0.0347]],
grad_fn=)
Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values : tensor([-0.0193, 0.0211], grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.2.weight | Size: torch.Size([512, 512]) | Values : tensor([[ 1.0689e-02, 7.8428e-03, 3.1157e-02, ..., -3.5875e-03,
-6.3815e-03, 5.0813e-05],
[-3.2946e-02, -1.8118e-03, 1.7812e-02, ..., -8.5875e-03,
4.2370e-02, -3.9422e-02]], grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.2.bias | Size: torch.Size([512]) | Values : tensor([0.0005, 0.0025], grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.4.weight | Size: torch.Size([10, 512]) | Values : tensor([[ 0.0433, -0.0411, -0.0135, ..., 0.0413, 0.0318, -0.0177],
[-0.0411, -0.0198, 0.0389, ..., 0.0359, -0.0325, 0.0429]],
grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values : tensor([-0.0167, -0.0243], grad_fn=<SliceBackward0>)
3.定制模型损失函数和优化器
训练模型之前,我们需要为模型定制⼀个损失函数 loss function 和⼀个优化器 optimizer。我们可以使用 PyTorch 提供的内置损失函数和优化器,也可以根据需要自定义。
loss_fn = nn.CrossEntropyLoss() #交叉熵损失函数
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3) #随机梯度下降优化器
4.训练并观察超参数
在单个训练循环中,模型对训练数据集进⾏预测(分批输⼊),并反向传播预测误差以调整模型参数
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset) #数据集大小,训练数据样本总量
model.train() #训练模式
for batch, (X, y) in enumerate(dataloader): #遍历数据加载器
X, y = X.to(device), y.to(device) #将数据移动到GPU上张量数据加载到设备
#计算预测的误差
pred = model(X) #前向传播,调用模型获得结果(forward时被自动调用)
loss = loss_fn(pred, y) #计算损失函数
#反向传播
model.zero_grad() #清空梯度
loss.backward() #反向传播
optimizer.step() #更新参数
if batch % 100 == 0: #每100个batch打印一次
loss, current = loss.item(), batch * len(X) #当前损失和当前样本数
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]") #打印损失和当前样本数
我们还要依赖测试数据集来检查模型的性能,以确保它的学习优化效果
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset) #数据集大小
num_batches = len(dataloader) #批次数
model.eval() #评估模式
test_loss, correct = 0, 0 #测试损失和正确预测数
with torch.no_grad(): #不计算梯度
for X, y in dataloader: #遍历数据加载器
X, y = X.to(device), y.to(device) #将数据移动到GPU上
pred = model(X) #前向传播
test_loss += loss_fn(pred, y).item() #计算损失函数
correct += (pred.argmax(1) == y).type(torch.float).sum().item() #计算正确预测数
test_loss /= num_batches #平均损失
correct /= size #平均正确率
print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n") #打印测试结果
默认情况下,所有 requires_grad=True 属性值的张量都会被跟踪,以便于根据上⼀次的值来⽀持对梯度计算。但是,在某些情况下我们并不需要这样做,例如,当我们训练了模型但只想将其应⽤于某些输⼊数据的时。或者说⽩了就是我们只想通过⽹络进⾏前向计算时。我们可以把所有计算代码写在 torch.no_grad() 下⾯来停⽌跟踪计算。训练过程在多轮迭代(epochs)中进⾏。在每个epoch中,模型通过学习更新内置的参数,以期做出更好的预测。我们在每个epochs打印模型的准确率和损失值;当然,最希望看到的,就是每个 epoch 过程中准确率的增加⽽损失函数值的减⼩。
epochs = 5 #训练轮数
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer) #训练
test(test_dataloader, model, loss_fn) #测试
print("训练完成!") #训练完成
Epoch 1
-------------------------------
loss: 2.296409 [ 0/60000]
loss: 2.278774 [ 6400/60000]
loss: 2.268839 [12800/60000]
loss: 2.260496 [19200/60000]
loss: 2.248405 [25600/60000]
loss: 2.235106 [32000/60000]
loss: 2.192645 [38400/60000]
loss: 2.189089 [44800/60000]
loss: 2.177440 [51200/60000]
loss: 2.163005 [57600/60000]
Test Error:
Accuracy: 30.8%, Avg loss: 2.146272
Epoch 2
-------------------------------
loss: 2.143353 [ 0/60000]
loss: 2.111427 [ 6400/60000]
loss: 2.107543 [12800/60000]
loss: 2.075837 [19200/60000]
loss: 2.060123 [25600/60000]
loss: 2.034776 [32000/60000]
loss: 2.009829 [38400/60000]
loss: 1.949075 [44800/60000]
loss: 1.969814 [51200/60000]
loss: 1.895747 [57600/60000]
Test Error:
Accuracy: 56.5%, Avg loss: 1.878862
Epoch 3
-------------------------------
loss: 1.912499 [ 0/60000]
loss: 1.862449 [ 6400/60000]
loss: 1.896388 [12800/60000]
loss: 1.720874 [19200/60000]
loss: 1.746487 [25600/60000]
loss: 1.795699 [32000/60000]
loss: 1.628211 [38400/60000]
loss: 1.610798 [44800/60000]
loss: 1.608859 [51200/60000]
loss: 1.436870 [57600/60000]
Test Error:
Accuracy: 61.0%, Avg loss: 1.514670
Epoch 4
-------------------------------
loss: 1.503860 [ 0/60000]
loss: 1.457020 [ 6400/60000]
loss: 1.433628 [12800/60000]
loss: 1.361627 [19200/60000]
loss: 1.306390 [25600/60000]
loss: 1.336350 [32000/60000]
loss: 1.321888 [38400/60000]
loss: 1.318274 [44800/60000]
loss: 1.241129 [51200/60000]
loss: 1.329783 [57600/60000]
Test Error:
Accuracy: 63.7%, Avg loss: 1.244850
Epoch 5
-------------------------------
loss: 1.250433 [ 0/60000]
loss: 1.225043 [ 6400/60000]
loss: 1.056149 [12800/60000]
loss: 1.080819 [19200/60000]
loss: 1.137038 [25600/60000]
loss: 1.176616 [32000/60000]
loss: 1.130931 [38400/60000]
loss: 1.223776 [44800/60000]
loss: 1.152667 [51200/60000]
loss: 1.118786 [57600/60000]
Test Error:
Accuracy: 64.7%, Avg loss: 1.076765
训练完成!
完整代码
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
#1.数据预处理
#训练集
training_data = datasets.FashionMNIST(
root='./fashion_data', #下载的数据集文件保存到当前用户工作目录下
train=True, #选择训练集
download=True, #如果数据集不存在则下载
transform=ToTensor() #将数据集中的图片转换为Tensor
)
#测试集
test_data = datasets.FashionMNIST(
root='./fashion_data', #下载的数据集文件保存到当前用户工作目录下
train=False, #选择测试集
download=True, #如果数据集不存在则下载
transform=ToTensor() #将数据集中的图片转换为Tensor
)
batch_size = 64
#创建数据加载器
train_dataloader = DataLoader(training_data, batch_size=batch_size, shuffle=True) #shuffle=True表示随机打乱数据
test_dataloader = DataLoader(test_data, batch_size=batch_size,)
#2.构建模型
#检测可以使用的设备
device = 'cuda' if torch.cuda.is_available() else 'cpu'
#定义神经网络模型
class NeuralNetwork(nn.Module):
def __init__(self):#初始化模型
#调用父类的构造函数
#nn.Module是所有神经网络模块的基类
#在构造函数中定义模型的层
super(NeuralNetwork, self).__init__()
self.flatten = nn.Flatten() #将输入的图片展平
self.linear_relu_stack = nn.Sequential(
#wx+b=[64,1,784] * [784,512] = [64,1,512]
nn.Linear(28*28, 512), #输入层到隐藏层
nn.ReLU(), #激活函数
nn.Linear(512, 512), #隐藏层到隐藏层
nn.ReLU(), #激活函数
nn.Linear(512, 10) #隐藏层到输出层
)
def forward(self, x):
x = self.flatten(x) #展平
logits = self.linear_relu_stack(x) #前向传播
return logits
model = NeuralNetwork().to(device) #将模型移动到GPU上(我这边用的是mac,只有cpu)
#3.定制模型损失函数和优化器
loss_fn = nn.CrossEntropyLoss() #交叉熵损失函数
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3) #随机梯度下降优化器
#4.训练模型并观察超参数
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset) #数据集大小,训练数据样本总量
model.train() #训练模式
for batch, (X, y) in enumerate(dataloader): #遍历数据加载器
X, y = X.to(device), y.to(device) #将数据移动到GPU上张量数据加载到设备
#计算预测的误差
pred = model(X) #前向传播,调用模型获得结果(forward时被自动调用)
loss = loss_fn(pred, y) #计算损失函数
#反向传播
model.zero_grad() #清空梯度
loss.backward() #反向传播
optimizer.step() #更新参数
if batch % 100 == 0: #每100个batch打印一次
loss, current = loss.item(), batch * len(X) #当前损失和当前样本数
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]") #打印损失和当前样本数
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset) #数据集大小
num_batches = len(dataloader) #批次数
model.eval() #评估模式
test_loss, correct = 0, 0 #测试损失和正确预测数
with torch.no_grad(): #不计算梯度
for X, y in dataloader: #遍历数据加载器
X, y = X.to(device), y.to(device) #将数据移动到GPU上
pred = model(X) #前向传播
test_loss += loss_fn(pred, y).item() #计算损失函数
correct += (pred.argmax(1) == y).type(torch.float).sum().item() #计算正确预测数
test_loss /= num_batches #平均损失
correct /= size #平均正确率
print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n") #打印测试结果
epochs = 5 #训练轮数
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer) #训练
test(test_dataloader, model, loss_fn) #测试
print("训练完成!") #训练完成
更多推荐



所有评论(0)