【Pytorch(五)】基于 PyTorch 构建卷积神经网络 CNN

简介: 【Pytorch(五)】基于 PyTorch 构建卷积神经网络 CNN

0. 概述

在前面两项实验内容中,我们已经学习了 PyTorch 的基本数据类型 tensor 及其相关操作,并练习了如何通过 PyTorch 读入并处理数据集。下面,我们就正式开始学习如何基于 PyTorch 搭建一个神经网络,并在 MNIST 数据集上进行训练和测试。


总体来讲,我们在这部分实验中要进行的操作包括以下几点:


1)  读取和处理数据集


2)  定义一个包含可训练参数的神经网络


3)  从数据集中取出一个批次 (batch) 的样本传递给神经网络


4)  通过神经网络处理输入样本


5)  计算损失 (loss)


6)  反向传播计算偏导


7)  更新网络的参数 (一个简单的更新方法:weight = weight - learning_rate *gradient)


8)  重复 3) 到 7) 步,不断调整网络参数


1. 导入 PyTorch 及其他相关库

import torch
import torchvision
import numpy as np

查看 PyTorch 版本,是否可以使用 GPU,及 CUDA 的版本。


print(torch.__version__)
print(torch.cuda.is_available())
print(torch.version.cuda)
1.7.1+cu110
True
11.0

2. 准备数据集 (Data Preparation)

batch_size = 100  # 设置训练集和测试集的 batch size,即每批次将参与运算的样本数
train_set = torchvision.datasets.MNIST('./dataset_mnist', train=True, download=True,
                                       transform=torchvision.transforms.Compose([
                                           torchvision.transforms.ToTensor(),
                                           torchvision.transforms.Normalize(
                                               (0.1307,), (0.3081,)
                                           )
                                       ])
)
##################### please finish the code ########################
# 请把之前在“1_mnist_dataset_import.ipynb”中写的 test_set 代码复制在这里
# test_set = XXX
test_set = torchvision.datasets.MNIST('./dataset_mnist', train=False, download=True,
                                       transform=torchvision.transforms.Compose([
                                           torchvision.transforms.ToTensor(),
                                           torchvision.transforms.Normalize(
                                               (0.1307,), (0.3081,)
                                           )
                                       ])
)
################################ end ################################
train_loader = torch.utils.data.DataLoader(train_set, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=batch_size, shuffle=True)

3. 定义一个神经网络

为了构建神经网络,我们将使用 torch.nn 包,它是 PyTorch 的神经网络库。我们通常会将 torch.nn 包直接导入并命名为 nn。


import torch.nn as nn

PyTorch 的神经网络库包含了构建神经网络所需的所有典型组件。我们构建一个神经网络使用的最主要组件是层,所以 PyTorch 的神经网络库包含了帮助我们构造层的类。在神经网络包里有一类被称为“模块” (nn.Module) 的特殊类,它是所有神经网络模块的母类。PyTorch 中所有的神经网络层均继承了 nn.Module。

 神经网络中的每一层都包含两个主要组成部分,第一是该层参数的集合 (weights and biases),第二是该层进行的变换 (transformation)。其中,进行的变换主要指前向传播过程,每一个 PyTorch nn.Module 都有一个 forward() 函数来定义其前向传播。因此,当我们实现一个自定义的神经网络 (nn.Module 子类) 时,也需要编写其 forward() 函数。值得注意的是,得益于 PyTorch 的自动求导功能,我们并不需要像“实验一”中一样自己编写反向传播的 backward() 函数,PyTorch 会自动帮我们完成。(关于这一点,我们会在之后的实验中详细为大家介绍。)


总结起来,在 PyTorch 中定义神经网络包括如下几个步骤:


基于 nn.Module 创建一个类 (extend the nn.Module base class)


在类构造函数中,将网络的层定义为类属性 (define layers as class attributes)


定义网络的前向传播 (implement the forward() method)


下面,我们就可以创建一个类来表示一个简单的神经网络。


class Network0(nn.Module):
    def __init__(self):
        super(Network, self).__init__()
        self.layer = None
    def forward(self, t):
        t = self.layer(t)
        return t

在这个例子中,我们扩展了 nn.Module 基类。它在构造函数中有一个虚拟层 (self.layer),并在 forward() 函数中实现了一个虚拟的前向传播过程。forward() 接收来自神经网络输入端的张量 t,对其进行张量变换 (transformation),并将变换后的结果返回。


现在,让我们用一些真正的层来替代这个虚拟层,这些层将由PyTorch 神经网络库提供。我们将建立一个卷积神经网络,包含两个卷积层和三个全连接层。其中,卷积层我们将用 torch.nn 的内建层类 nn.Conv2d 来实现,全连接层将用 nn.Linear 来实现。


nn.Conv2d 可见 PyTorch 官网: https://pytorch.org/docs/master/generated/torch.nn.Conv2d.html#torch.nn.Conv2d


nn.Linear 可见 PyTorch 官网: https://pytorch.org/docs/master/generated/torch.nn.Linear.html#torch.nn.Linear


假设我们将使用 MNIST 数据集,每张图片为一个 28*28 的灰度图。在即将定义的神经网络中,每个卷积层之后我们都会进行池化操作 (pooling),池化的 kernel 大小和 stride 均取 2(即每次将对 2*2 的元素进行 max pooling)。 池化层由于不包含可训练的参数,因此不作为 Network1 的类属性(class attribute),而是会在之后写入 forward() 函数里。


class Network1(nn.Module): 
    def __init__(self):
        super(Network1, self).__init__() 
        self.conv1 = nn.Conv2d(in_channels=1,  # 输入通道数
                               out_channels=6,  # 输出通道数
                               kernel_size=5)  # kernel 大小 (对应四维权重张量的 dim2/dim3)
        self.conv2 = nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5)
        ##################### please finish the code ########################
        # self.fc1 = nn.Linear(in_features=XXX,  # 输入激活向量的长度
        #                     out_features=120)  # 输出激活向量的长度
        self.fc1 = nn.Linear(in_features=192, out_features=120) 
        ################################ end ################################
        self.fc2 = nn.Linear(in_features=120, out_features=60)
        self.out = nn.Linear(in_features=60, out_features=10)
    def forward(self, t):
        # we will implement the forward path later
        return t

代码解释:这里是第一个全连接层,它的输入应该是特征张量经过两层卷积,两层池化的张量拉平后的特征数量。由上图可知全连接的前一层是输出一个12x4x4的三维张量,拉平计算出特征个数为192。因此,此空应该填写192。


查看定义好的网络结构


network = Network1()
print(network)
Network1(
  (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(6, 12, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=192, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=60, bias=True)
  (out): Linear(in_features=60, out_features=10, bias=True)
)

查看卷积层及其参数


Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
Conv2d(6, 12, kernel_size=(5, 5), stride=(1, 1))
print(network.conv1.weight)  # check the weights of conv1
Parameter containing:
tensor([[[[-0.0594,  0.0425,  0.0493, -0.1925, -0.0007],
          [-0.0205, -0.0643, -0.1029, -0.1133, -0.0529],
          [ 0.1931,  0.0444,  0.0907,  0.0706,  0.1145],
          [-0.0771,  0.0453, -0.1527, -0.1388,  0.0241],
          [ 0.1286,  0.1522, -0.0919, -0.0032,  0.0155]]],
        [[[-0.1433,  0.0960,  0.0374,  0.0428,  0.1102],
          [ 0.0486,  0.1576, -0.1700, -0.1058,  0.0916],
          [-0.1973,  0.0576, -0.1478,  0.0998, -0.1304],
          [ 0.1752,  0.0953,  0.1984,  0.0957, -0.1613],
          [ 0.0834, -0.1608,  0.1086,  0.1325,  0.0568]]],
        [[[-0.0506,  0.1058,  0.1460, -0.0202, -0.1679],
          [ 0.1060, -0.1501,  0.1612,  0.1004,  0.0375],
          [ 0.1802, -0.0040,  0.0755, -0.0585, -0.0204],
          [ 0.0536, -0.0284, -0.0040, -0.1625,  0.1793],
          [-0.0878, -0.0131, -0.0022,  0.1125,  0.1211]]],
        [[[ 0.1233, -0.1757, -0.0831,  0.1451,  0.0897],
          [ 0.1899,  0.0472,  0.1208, -0.1191,  0.0794],
          [-0.0523,  0.0451,  0.0682,  0.1365, -0.0604],
          [-0.1362,  0.1054, -0.1874, -0.0777,  0.1690],
          [ 0.0094,  0.0829, -0.1800, -0.1377,  0.0510]]],
        [[[ 0.1025, -0.1373, -0.1825,  0.1499, -0.0550],
          [ 0.0860,  0.0571, -0.0274,  0.0951, -0.1526],
          [-0.1932,  0.1938, -0.1422, -0.1451, -0.0634],
          [ 0.1754, -0.1755,  0.1224,  0.0838, -0.0414],
          [-0.0223, -0.1874, -0.0100,  0.0086,  0.1742]]],
        [[[-0.1008, -0.0192,  0.0793, -0.1070,  0.0990],
          [ 0.0631,  0.0327, -0.0732,  0.0610,  0.0688],
          [ 0.1564,  0.0463,  0.1076,  0.1192, -0.0745],
          [ 0.0695,  0.0215,  0.1062,  0.0455,  0.0783],
          [-0.0174,  0.0129, -0.1974, -0.1194, -0.1785]]]], requires_grad=True)
print(network.conv1.weight.shape)  # check the shape of conv1's weight tensor
print(network.conv1.weight[0])  # check weigth[0]
print(network.conv1.weight[0].shape)  # check the shape of weight[0]
torch.Size([6, 1, 5, 5])
tensor([[[ 0.1347,  0.1266, -0.1525, -0.0372,  0.0493],
         [ 0.0575,  0.0078, -0.0345, -0.0093,  0.0675],
         [ 0.1881, -0.1785, -0.0201,  0.0066,  0.0210],
         [-0.1971,  0.1559,  0.1987, -0.0260, -0.0534],
         [-0.0735,  0.0824, -0.1592, -0.1246,  0.0716]]],
       grad_fn=<SelectBackward>)
torch.Size([1, 5, 5])

查看全连接层及其参数


# weight tensor for fc layers
print(network.fc1.weight.shape)
print(network.fc1.weight[0].shape)
torch.Size([120, 192])
torch.Size([192])

查看整个网络的参数


# 方式一
for name, param in network.named_parameters():
    print(name, '\t\t', param.shape)
print("")    
#方式二
for name in network.state_dict():  # state dictionary
    print(name, '\t\t', network.state_dict()[name].shape)
# 查看 conv1 的参数
print(network.state_dict()["conv1.weight"].shape)
print(network.state_dict()["conv1.weight"])
conv1.weight    torch.Size([6, 1, 5, 5])
conv1.bias    torch.Size([6])
conv2.weight    torch.Size([12, 6, 5, 5])
conv2.bias    torch.Size([12])
fc1.weight    torch.Size([120, 192])
fc1.bias    torch.Size([120])
fc2.weight    torch.Size([60, 120])
fc2.bias    torch.Size([60])
out.weight    torch.Size([10, 60])
out.bias    torch.Size([10])
conv1.weight    torch.Size([6, 1, 5, 5])
conv1.bias    torch.Size([6])
conv2.weight    torch.Size([12, 6, 5, 5])
conv2.bias    torch.Size([12])
fc1.weight    torch.Size([120, 192])
fc1.bias    torch.Size([120])
fc2.weight    torch.Size([60, 120])
fc2.bias    torch.Size([60])
out.weight    torch.Size([10, 60])
out.bias    torch.Size([10])
torch.Size([6, 1, 5, 5])
tensor([[[[-1.5001e-01, -1.3848e-02, -1.0378e-01,  6.7523e-02,  1.6334e-01],
          [ 1.2181e-01, -1.5125e-03,  7.3304e-02,  1.8515e-01,  1.4063e-01],
          [ 1.3865e-01,  1.3580e-01, -9.3696e-02,  1.8193e-01, -1.7815e-01],
          [ 1.6409e-01,  1.1340e-01, -1.6066e-01,  8.7652e-03,  5.5207e-02],
          [ 1.9840e-01, -3.1835e-02,  1.4939e-01,  7.9950e-03, -1.6087e-01]]],
        [[[-1.5691e-01,  9.4586e-02, -3.1107e-02, -2.6422e-03, -2.0982e-02],
          [-2.8013e-03,  1.3842e-01, -1.6488e-01,  6.3527e-02,  2.2672e-03],
          [-4.7753e-02, -7.8992e-02,  3.7517e-03,  1.1435e-01, -6.0690e-02],
          [-1.0773e-02,  1.4323e-01, -1.9731e-01, -7.9947e-02,  1.9909e-01],
          [ 9.9044e-02, -1.8865e-01, -2.4798e-02,  1.6735e-01,  6.9572e-02]]],
        [[[-4.4329e-02, -1.2067e-01,  9.5155e-02, -1.7678e-01,  1.3103e-01],
          [-6.4749e-02,  1.3068e-01,  1.4119e-01, -1.8535e-01, -9.6761e-02],
          [ 3.4565e-02, -2.7310e-02,  4.6535e-02,  4.1834e-02,  1.8054e-01],
          [ 8.9089e-03, -7.6159e-02,  1.6551e-01,  1.4622e-01,  1.3593e-01],
          [ 1.9994e-01,  6.4953e-02,  4.9864e-02,  7.4876e-02, -2.2937e-02]]],
        [[[ 6.9347e-02, -1.5495e-01,  1.9534e-02,  1.0272e-01, -7.0459e-02],
          [-3.4613e-02, -3.3175e-02, -1.3626e-01, -1.6995e-01,  3.2141e-02],
          [-8.4856e-02, -4.0157e-02,  1.4215e-01,  4.0197e-02, -2.8605e-02],
          [ 1.0961e-01,  6.0601e-02,  3.8085e-02,  1.9794e-02, -9.8676e-02],
          [-2.4603e-02, -1.2351e-02, -1.1427e-01, -1.2311e-01, -1.7150e-01]]],
        [[[ 1.1733e-02,  1.6737e-01, -1.9645e-01,  8.0192e-03,  1.1080e-01],
          [-8.4502e-02,  8.6131e-02, -2.9822e-02, -1.2681e-01, -1.7805e-01],
          [-1.1166e-01, -1.8238e-02, -1.7278e-01,  9.2117e-02,  1.7788e-01],
          [-8.6538e-02, -4.2994e-04, -1.4617e-01,  6.7663e-02,  2.5682e-02],
          [ 2.5579e-02,  4.7913e-03,  1.4916e-01, -4.2923e-02,  1.7944e-01]]],
        [[[-9.9185e-02, -1.5752e-01,  5.4561e-02, -3.1454e-02, -2.5488e-02],
          [ 6.2888e-02,  4.3282e-02, -1.6043e-01,  1.6768e-01, -1.0158e-01],
          [-1.8152e-01, -2.4528e-02,  2.0376e-02, -1.6032e-02,  3.6327e-02],
          [ 1.3927e-01, -6.5995e-02, -1.2325e-01,  1.2444e-01, -7.8324e-02],
          [ 1.8502e-01,  5.7690e-03,  1.3328e-04, -1.2427e-02,  1.0027e-01]]]])

网络的卷积层和全连接层定义好了。现在,让我们来实现 forward() 函数。


神经网络 forward() 函数的编写通常需要使用来自 nn.functional 的函数,这个包为我们提供了许多实现前向传播时需要的神经网络操作,例如激活函数 ReLU 和池化操作 Max Pooling。值得注意的是,虽然我们常称它们为“激活层”和“池化层”,但实际上它们都是在执行某种操作,并不包含可训练的参数。因此,它们在 PyTorch 中并没有被作为层来处理,也没有继承 nn.Module 基类,而是包含在 nn.functional 中。


import torch.nn.functional as F
class Network2(nn.Module):
    def __init__(self):
        super(Network2, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5)
        self.conv2 = nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5)
        ##################### please finish the code ########################
        # 请把之前 Network1 的 self.fc1 复制到这里:
        # self.fc1 = nn.Linear(in_features=XXX, out_features=120)
        self.fc1 = nn.Linear(in_features=192, out_features=120)
        ################################ end ################################
        self.fc2 = nn.Linear(in_features=120, out_features=60)
        self.out = nn.Linear(in_features=60, out_features=10)
    def forward(self, t):
        # conv1
        t = self.conv1(t)
        t = F.relu(t)  # 这里我们用到了 nn.functional 包中的激活函数 relu
        t = F.max_pool2d(t, kernel_size=2, stride=2)  # 这里我们用到了 nn.functional 包中的池化函数 max_pool2d (max pooling)
        # conv2
        t = self.conv2(t)
        t = F.relu(t)
        t = F.max_pool2d(t, kernel_size=2, stride=2)
        # 特别注意:最后一个卷积层的输出在流入全连接层之前,要对激活值的维度进行变换,变换后的张量
        # 是二维张量,其中 dim0 对应该批次中的不同样本,dim1 对应每个样本流向全连接层的输入激活向量。
        # 请完成如下代码。
        ##################### please finish the code ########################
        # dim0:一个批次的样本数
        # dim1:每一个样本的输入激活向量长度
        # t = t.reshape(XXX,XXX)
        t = t.reshape(batch_size,192)
        ################################ end ################################
        # fc1
        t = self.fc1(t)
        t = F.relu(t)
        # fc2
        t = self.fc2(t)
        t = F.relu(t)
        # output layer
        t = self.out(t)
        return t

画出网络结构

image.png


在 PyTorch 中,当我们直接调用对象名称 conv1/conv2/fc1/fc2 并将输入 t 传递给它时,对应的 forward() 函数将被调用。因此,在上述代码中, 我们直接将输入 t 传递给了 conv1,而没有使用 t = self.conv1.forward(t)。


到这里,我们已经完成了神经网络的构建。下一步,我们将训练这个神经网络来处理手写字体识别任务。


# 关掉 PyTorch 的自动求导功能
print(torch.set_grad_enabled(False))
<torch.autograd.grad_mode.set_grad_enabled object at 0x0000022AB95E5B50>
# 我们取一批图片给 network
batch = next(iter(train_loader))  # DataLoader object: train_loader
images, labels = batch
print(images.shape)  # rank-4 tensor
print(labels.shape)
output = network(images)
torch.Size([100, 1, 28, 28])
torch.Size([100])
# 查看 output 形状: rank-2 tensor ([batch_size, # of classes])
print(output.shape)
torch.Size([100, 10])
# F.softmax(t, dim=1)会为每个预测类返回一个概率值,这些概率值之和等于1。我们没有将这一步放在 Network2.forward()
# 中来做,是因为我们在训练网络将使用交叉熵损失函数 nn.CrossEntropyLoss,它会在其输入上隐式的执行一个 Softmax 操作。
output_prob = F.softmax(output, dim=1)
# 在之前的练习中我们已经知道,torch.max(t, dim=1) 函数会返回两个 tensor,第一个 tensor 是每行的最大值;第二个
# tensor是每行最大值的索引,反映了批处理中每个图像的预测标签。
scores, predict_class = torch.max(output_prob, dim=1)
print("scores: \n", scores)
print("predicted labels: \n", predict_class)
scores: 
 tensor([0.1240, 0.1199, 0.1262, 0.1252, 0.1247, 0.1213, 0.1296, 0.1226, 0.1271,
        0.1217, 0.1226, 0.1253, 0.1202, 0.1286, 0.1241, 0.1248, 0.1245, 0.1258,
        0.1230, 0.1252, 0.1227, 0.1256, 0.1250, 0.1207, 0.1233, 0.1180, 0.1245,
        0.1245, 0.1244, 0.1216, 0.1282, 0.1243, 0.1320, 0.1215, 0.1244, 0.1263,
        0.1251, 0.1256, 0.1172, 0.1225, 0.1256, 0.1213, 0.1224, 0.1219, 0.1236,
        0.1253, 0.1225, 0.1266, 0.1221, 0.1298, 0.1266, 0.1256, 0.1251, 0.1237,
        0.1288, 0.1238, 0.1185, 0.1279, 0.1283, 0.1253, 0.1260, 0.1200, 0.1241,
        0.1216, 0.1265, 0.1229, 0.1270, 0.1255, 0.1196, 0.1261, 0.1208, 0.1221,
        0.1241, 0.1235, 0.1229, 0.1221, 0.1222, 0.1263, 0.1254, 0.1228, 0.1262,
        0.1271, 0.1204, 0.1238, 0.1286, 0.1256, 0.1240, 0.1260, 0.1281, 0.1191,
        0.1251, 0.1268, 0.1225, 0.1233, 0.1227, 0.1239, 0.1163, 0.1257, 0.1299,
        0.1228])
predicted labels: 
 tensor([3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
        3, 3, 3, 3])
# 查看实际标签
print("actual labels: \n", labels)
actual labels: 
 tensor([2, 3, 3, 7, 8, 1, 1, 3, 4, 3, 1, 4, 1, 3, 9, 4, 9, 6, 5, 5, 7, 4, 4, 5,
        8, 9, 6, 7, 6, 7, 0, 2, 1, 8, 5, 6, 9, 4, 9, 8, 8, 7, 0, 4, 5, 4, 5, 1,
        5, 4, 8, 8, 5, 4, 5, 5, 0, 3, 7, 7, 4, 8, 1, 4, 5, 3, 2, 2, 1, 2, 4, 7,
        1, 5, 5, 8, 1, 9, 6, 5, 7, 0, 7, 3, 6, 9, 8, 6, 6, 7, 4, 0, 6, 5, 4, 9,
        5, 1, 2, 8])
# 计算这一批样本识别的“准确率”
correct_predictions = (predict_class == labels).sum().item()  # 识别正确的次数
print("correct predictions: ", correct_predictions)
print("\"accuracy\": ", correct_predictions/len(labels)*100, "%")
correct predictions:  12
"accuracy":  12.0 %

把这一部分代码(从"network = Network2()"开始)多跑几次你会发现,每次 “scores”、“predict_class” 等都会发生变化,请思考这是为什么?另外,尽管"scores"每一次都不同,但其大致取值却都差不多,这又是为什么?


尽管"scores"每一次都不同,但其大致取值却都差不多是因为:直接使用初始化后的网络对一个批次的图片进行预测,由于网络没有开始学习即未从数据中获取任何信息,所以对于这个“十分类问题”,网络只是在“瞎猜”每张图片属于哪种类别,从数学意义上讲,在没有任何其他信息的情况下,网络判断一张图片为各个类别的期望是0.1,因此,从最终"scores"每一次的输出来看,它的取值均约等于0.1(略大于,由于是将概率1分为10份,且对于计算机实现来说不会恰好全部等于0.1)。


# 现在我们可以将自动求导功能打开,为训练做好准备
print(torch.set_grad_enabled(True))
<torch.autograd.grad_mode.set_grad_enabled object at 0x000001A4CE99C6D0>

4. 训练神经网络

在这一部分中,我们将在数据集 MNIST 上训练构建好的神经网络。


在定义完神经网络之后,我们还需要在 torch.nn 包中选择合适的损失函数 (loss function) 来评估网络输出与理想值之间的差别。常用的损失函数都已经定义在了 torch.nn 中,比如均方误差 (nn.MSELoss)、多分类的交叉熵 (nn.CrossEntropyLoss) 及二分类的交叉熵 (nn.BCELoss),等等。由于我们将进行手写字体0到9的识别,本次实验我们将选用交叉熵损失函数。


一个损失函数需要一对输入:模型的实际输出和目标输出。基于此,损失函数将计算一个值 (loss) 来评估实际输出距离目标输出有多远。

loss_func = nn.CrossEntropyLoss()

除了损失函数以外,我们还需要一个能更直观的反映分类准确性的函数,方便我们了解训练的情况。请大家阅读并理解以下 get_num_correct 的代码每一步在做什么。


def get_num_correct(preds, labels):  # get the number of correct times
    return preds.argmax(dim=1).eq(labels).sum().item()

在深度学习的训练过程中,我们需要通过不断调整参数使得损失最小化。优化算法就是一种调整模型参数更新的策略。在这里,我们需要用到一个新的包:torch.optim,其中“optim”是“optimizer”的缩写,它包含了多种常用的优化算法。


import torch.optim as optim

本次实验我们将使用随机梯度下降法 (SGD) 来对网络进行优化。


现在我们已经做好了训练的前期准备。在开始大规模的训练前,我们先来观察一个批次 (batch) 的图片是如何被训练的。


首先,我们取出一个 batch 的样本,传递给 network,并计算损失。


batch = next(iter(train_loader))
images, labels = batch
preds = network(images)
loss = loss_func(preds, labels)
print(loss.item())
2.31160044670105

现在我们有了 loss,下一步可以计算偏导 (gradients)。为了计算偏导,我们会在损失张量上调用反向函数 loss.backward(),PyTorch 会自动帮我们做相关的计算。


loss.backward()

我们可以看看100张图片中分类正确的次数:


print(get_num_correct(preds, labels))
11

下一步是使用这些偏导来更新网络的权重。


我们将同一批样本再次传递给更新后的神经网络,看看 loss 和分类正确的次数有什么变化。


preds = network(images)
loss = loss_func(preds, labels)
print("loss: ", loss.item())
print("number of correct times: ", get_num_correct(preds, labels))
loss:  2.3089065551757812
number of correct times:  11

可以看到 loss 有些微的下降,说明我们确实是在朝着损失减小的方向调整参数。下面我们来完整实现整个训练过程。


total_epochs = 10
for epoch in range(total_epochs):  # 训练周期
    total_loss = 0
    total_train_correct = 0
    for batch in train_loader:  # get a batch from the dataloader
        # 读取样本数据,完成正向传播,计算损失
        ##################### please finish the code ########################
        # images, labels = XXX        
        # preds = XXX
        # loss = XXX
        images, labels = batch    
        preds = network(images)
        loss = loss_func(preds, labels)
        ################################ end ################################
        # 下面这行非常重要,它使得优化器 (optimizer) 将权重的偏导重新归零;
        # 如果不归零,那么在反向传播时,计算出来的偏导会累加在原先的偏导上,
        # 造成错误。
        optimizer.zero_grad()
        # 完成反向传播,更新参数
        ##################### please finish the code ########################
        # 反向传播 (一行代码)
        loss.backward()
        # 更新参数 (一行代码)        
        optimizer.step()
        ################################ end ################################
        total_loss += loss.item()
        total_train_correct += get_num_correct(preds, labels)
    print("epoch:", epoch, 
          "correct times:", total_train_correct,
          f"training accuracy:", "%.3f" %(total_train_correct/len(train_set)*100), "%", 
          "total_loss:", "%.3f" %total_loss)
epoch: 0 correct times: 53088 training accuracy: 88.480 % total_loss: 214.314
epoch: 1 correct times: 58428 training accuracy: 97.380 % total_loss: 49.868
epoch: 2 correct times: 58906 training accuracy: 98.177 % total_loss: 34.823
epoch: 3 correct times: 59105 training accuracy: 98.508 % total_loss: 28.300
epoch: 4 correct times: 59271 training accuracy: 98.785 % total_loss: 23.302
epoch: 5 correct times: 59371 training accuracy: 98.952 % total_loss: 20.107
epoch: 6 correct times: 59428 training accuracy: 99.047 % total_loss: 17.641
epoch: 7 correct times: 59505 training accuracy: 99.175 % total_loss: 15.261
epoch: 8 correct times: 59578 training accuracy: 99.297 % total_loss: 13.247
epoch: 9 correct times: 59599 training accuracy: 99.332 % total_loss: 11.92

训练结果看上去不错,让我们在测试集上看看准确率如何。


total_test_correct = 0
total_loss = 0
for batch in test_loader:  # get a batch from the dataloader
    images, labels = batch
    preds = network(images)
    loss = loss_func(preds, labels)
    total_loss += loss
    total_test_correct += get_num_correct(preds, labels)
print("correct times:", total_test_correct, 
      f"test accuracy:", "%.3f" %(total_test_correct/len(test_set)*100), "%",
      "total_loss:", "%.3f" %total_loss)
correct times: 9899 test accuracy: 98.990 % total_loss: 3.296

相关文章
|
3月前
|
机器学习/深度学习 算法 PyTorch
【Pytorch框架搭建神经网络】基于DQN算法、优先级采样的DQN算法、DQN + 人工势场的避障控制研究(Python代码实现)
【Pytorch框架搭建神经网络】基于DQN算法、优先级采样的DQN算法、DQN + 人工势场的避障控制研究(Python代码实现)
|
3月前
|
机器学习/深度学习 算法 PyTorch
【DQN实现避障控制】使用Pytorch框架搭建神经网络,基于DQN算法、优先级采样的DQN算法、DQN + 人工势场实现避障控制研究(Matlab、Python实现)
【DQN实现避障控制】使用Pytorch框架搭建神经网络,基于DQN算法、优先级采样的DQN算法、DQN + 人工势场实现避障控制研究(Matlab、Python实现)
168 0
|
7月前
|
机器学习/深度学习 PyTorch 算法框架/工具
基于Pytorch 在昇腾上实现GCN图神经网络
本文详细讲解了如何在昇腾平台上使用PyTorch实现图神经网络(GCN)对Cora数据集进行分类训练。内容涵盖GCN背景、模型特点、网络架构剖析及实战分析。GCN通过聚合邻居节点信息实现“卷积”操作,适用于非欧氏结构数据。文章以两层GCN模型为例,结合Cora数据集(2708篇科学出版物,1433个特征,7种类别),展示了从数据加载到模型训练的完整流程。实验在NPU上运行,设置200个epoch,最终测试准确率达0.8040,内存占用约167M。
基于Pytorch 在昇腾上实现GCN图神经网络
|
7月前
|
机器学习/深度学习 算法 PyTorch
Perforated Backpropagation:神经网络优化的创新技术及PyTorch使用指南
深度学习近年来在多个领域取得了显著进展,但其核心组件——人工神经元和反向传播算法自提出以来鲜有根本性突破。穿孔反向传播(Perforated Backpropagation)技术通过引入“树突”机制,模仿生物神经元的计算能力,实现了对传统神经元的增强。该技术利用基于协方差的损失函数训练树突节点,使其能够识别神经元分类中的异常模式,从而提升整体网络性能。实验表明,该方法不仅可提高模型精度(如BERT模型准确率提升3%-17%),还能实现高效模型压缩(参数减少44%而无性能损失)。这一革新为深度学习的基础构建模块带来了新的可能性,尤其适用于边缘设备和大规模模型优化场景。
331 16
Perforated Backpropagation:神经网络优化的创新技术及PyTorch使用指南
|
10月前
|
机器学习/深度学习 数据可视化 算法
PyTorch生态系统中的连续深度学习:使用Torchdyn实现连续时间神经网络
神经常微分方程(Neural ODEs)是深度学习领域的创新模型,将神经网络的离散变换扩展为连续时间动力系统。本文基于Torchdyn库介绍Neural ODE的实现与训练方法,涵盖数据集构建、模型构建、基于PyTorch Lightning的训练及实验结果可视化等内容。Torchdyn支持多种数值求解算法和高级特性,适用于生成模型、时间序列分析等领域。
532 77
PyTorch生态系统中的连续深度学习:使用Torchdyn实现连续时间神经网络
|
7月前
|
机器学习/深度学习 搜索推荐 PyTorch
基于昇腾用PyTorch实现CTR模型DIN(Deep interest Netwok)网络
本文详细讲解了如何在昇腾平台上使用PyTorch训练推荐系统中的经典模型DIN(Deep Interest Network)。主要内容包括:DIN网络的创新点与架构剖析、Activation Unit和Attention模块的实现、Amazon-book数据集的介绍与预处理、模型训练过程定义及性能评估。通过实战演示,利用Amazon-book数据集训练DIN模型,最终评估其点击率预测性能。文中还提供了代码示例,帮助读者更好地理解每个步骤的实现细节。
|
7月前
|
机器学习/深度学习 自然语言处理 PyTorch
基于Pytorch Gemotric在昇腾上实现GAT图神经网络
本实验基于昇腾平台,使用PyTorch实现图神经网络GAT(Graph Attention Networks)在Pubmed数据集上的分类任务。内容涵盖GAT网络的创新点分析、图注意力机制原理、多头注意力机制详解以及模型代码实战。实验通过两层GAT网络对Pubmed数据集进行训练,验证模型性能,并展示NPU上的内存使用情况。最终,模型在测试集上达到约36.60%的准确率。
|
7月前
|
算法 PyTorch 算法框架/工具
PyTorch 实现FCN网络用于图像语义分割
本文详细讲解了在昇腾平台上使用PyTorch实现FCN(Fully Convolutional Networks)网络在VOC2012数据集上的训练过程。内容涵盖FCN的创新点分析、网络架构解析、代码实现以及端到端训练流程。重点包括全卷积结构替换全连接层、多尺度特征融合、跳跃连接和反卷积操作等技术细节。通过定义VOCSegDataset类处理数据集,构建FCN8s模型并完成训练与测试。实验结果展示了模型在图像分割任务中的应用效果,同时提供了内存使用优化的参考。
|
7月前
|
机器学习/深度学习 算法 PyTorch
基于Pytorch Gemotric在昇腾上实现GraphSage图神经网络
本实验基于PyTorch Geometric,在昇腾平台上实现GraphSAGE图神经网络,使用CiteSeer数据集进行分类训练。内容涵盖GraphSAGE的创新点、算法原理、网络架构及实战分析。GraphSAGE通过采样和聚合节点邻居特征,支持归纳式学习,适用于未见节点的表征生成。实验包括模型搭建、训练与验证,并在NPU上运行,最终测试准确率达0.665。
|
3月前
|
机器学习/深度学习 数据采集 人工智能
PyTorch学习实战:AI从数学基础到模型优化全流程精解
本文系统讲解人工智能、机器学习与深度学习的层级关系,涵盖PyTorch环境配置、张量操作、数据预处理、神经网络基础及模型训练全流程,结合数学原理与代码实践,深入浅出地介绍激活函数、反向传播等核心概念,助力快速入门深度学习。
224 1

热门文章

最新文章

推荐镜像

更多