Pytorch搭建并训练神经⽹络

流程:

  • 数据预处理
  • 构建模型
  • 定制模型损失函数和优化器
  • 训练并观察超参数

1.数据预处理

模型训练⽤的样本,⼤部分都来⾃于外部⽂件系统。本次训练⽤的数据来⾃框架内置的数据
集,所以代码没有泛化性。
PyTorch 有两个⽤于处理数据的⼯具: torch.utils.data.DataLoadertorch.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("训练完成!") #训练完成
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐