【深度学习-图像分类】PyTorch小白大战LeNet

写在前面:

        本文的侧重点在于Pytorch实战,对于网络的理论部分不做过多的介绍。

一、LeNet结构

        了解目标分类网络的结构是复现网络的基础。

        LeNet的结构很简单,主要由卷积层,最大池化层和全连接层组成。为了方便程序编写,我们在这里规定特征图的信息排列顺序为 [channel, width, height]   

        输入图像尺寸为:[3, 32, 32]

        第一卷积层信息为:[in_channels=3, out_channels=16, kernel_size=5],经过第一卷积层后的特征图尺寸为:[16, 28, 28]

        第一最大池化层信息为:[kernel_size=2, stride=2],经过第一最大池化层后的特征图尺寸为:[16, 14, 14]

        第二卷积层信息为:[in_channels=16, out_channels=32, kernel_size=5],经过第二卷积层后的特征图尺寸为:[32, 10, 10]

        第二最大池化层信息为:[kernel_size=2, stride=2],经过第二最大池化层后的特征图尺寸为:[32, 5, 5]

        经过第一全连接层后输出的一维张量大小为:[120]

        经过第二全连接层后输出的一维张量大小为:[84]

        经过第三全连接层后输出的一维张量大小为:[num_class],num_class为需要分割的目标种类的数量  ,这里根据需要我们设定为10。

二、网络搭建

        知晓LeNet结构之后,可以着手网络的搭建了。

        新建module.py脚本,编写LeNet类,在其中编写两个函数,分别描述网络结构和正向传播过程。

import torch.nn as nn                   # torch.nn是专门为神经网络设计的模块化接口
import torch.nn.functional as f         # nn.functional可以看作nn的子库
# 这样引入包的模块,可以通过as后面的简写名称调用函数功能


class LeNet(nn.Module):                 # 定义一个LeNet类,括号内的内容说明它继承于nn.Module这个超类
    def __init__(self):                 # 定义子类的初始化函数,使得子类可以完成自身的特定功能
        super().__init__()              # 使用super函数调用超类的构造函数
        self.conv1 = nn.Conv2d(3, 16, 5)    # 卷积层1,参数类型分别为(in_channels,out_channels, kernel_size)
        self.pool1 = nn.MaxPool2d(2, 2)     # 池化层1,参数类型分别为(kernel_size, stride)
        self.conv2 = nn.Conv2d(16, 32, 5)   # 卷积层2
        self.pool2 = nn.MaxPool2d(2, 2)     # 池化层2
        self.fc1 = nn.Linear(32*5*5, 120)   # 全连接层1
        self.fc2 = nn.Linear(120, 84)       # 全连接层2
        self.fc3 = nn.Linear(84, 10)        # 全连接层3

    def forward(self, x):               # 定义正向传播过程,x表示的数据
        x = f.relu(self.conv1(x))       # 当数据进入卷积层1后,让其通过relu激活函数。input(3, 32, 32) output(16, 28, 28)
        x = self.pool1(x)               # 通过第1个池化层。output(16, 14, 14)
        x = f.relu(self.conv2(x))       # 数据进入卷积层2后,通过relu激活函数。output(32, 10, 10)
        x = self.pool2(x)               # 通过第2个卷积层。output(32, 5, 5)
        x = x.view(-1, 32*5*5)          # 通过view函数将二维张量展平为一维张量。output(32*5*5)
        x = f.relu(self.fc1(x))         # 进入全连接层1之后通过relu激活函数。output(120)
        x = f.relu(self.fc2(x))         # 进入全连接层2之后通过relu激活函数。output(84)
        x = self.fc3(x)                 # 进入全连接层3,不需要通过激活函数了。output(10)
        return x                        # 返回输出值


"""
注:
1、经过卷积之后的特征图尺寸大小N计算公式:
输入图片的尺寸大小:W×W
Kernel_size大小:F×F
步长大小:S
Padding操作数:P
N = (W-F+2P)/S+1
2、经过池化之后的特征图尺寸
由于kernel_size = stride = 2
所以认为通道数不变,特征图大小标为原来的一半
"""

三、网络训练

        网络搭建好之后,就可以着手对网络进行训练了。训练所使用的数据集为CIFAR-10,训练的方法是通过编写train.py脚本来实现。通过这个脚本,我们希望能够对网络进行训练的时候在终端打印出训练信息,并且将训练结果保存在特定的.txt文件中。

import torch
import torchvision
import torch.nn as nn
from module import LeNet
import torch.optim as optim
import torchvision.transforms as transforms


def main():
    transform = transforms.Compose(         # 数据预处理工作
        [transforms.ToTensor(),             # 将数据和值重新排列
         transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])   # 数据的标准化处理,传入的是每个通道的均值和标准差,均为0.5

    # 训练部分,下载数据并进行训练,参数分别为存储路径,是否用于训练,是否下载,转变的图片来源
    train_set = torchvision.datasets.CIFAR10(root='./data', train=True,
                                             download=False, transform=transform)
    # shuffle为询问是否对其打乱,False为不打乱。windows系统num_workers设为0
    train_loader = torch.utils.data.DataLoader(train_set, batch_size=36,
                                               shuffle=True, num_workers=0)

    # 验证部分,下载数据并进行验证
    val_set = torchvision.datasets.CIFAR10(root='./data', train=True,
                                           download=False, transform=transform)
    val_loader = torch.utils.data.DataLoader(val_set, batch_size=5000,
                                             shuffle=False, num_workers=0)

    val_data_iter = iter(val_loader)                # 生成一个迭代器,迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退
    val_images, val_label = next(val_data_iter)     # 通过next访问下一个元素

    net = LeNet()                                   # 对网络进行实例化
    loss_function = nn.CrossEntropyLoss()           # 定义损失函数,使用的是交叉熵损失
    optimizer = optim.Adam(net.parameters(), lr=0.001)      # 定义优化器Adam,学习率为0.001
    f = open(r'.\data.txt', 'w')                    # 打开txt文件,用于训练数据的记录

    for epoch in range(5):                          # 循环迭代
        running_loss = 0.0                          # 用来累加训练中的损失
        for step, data in enumerate(train_loader, start=0):     # 用于将可遍历的对象组合为一个索引序列,同时列出数据和数据下标。序列:train_loader,起始下标:0
            inputs, labels = data                   # data的数据结构为 [inputs, labels]
            optimizer.zero_grad()                   # 每计算一个batch,需要调用一次,因为如果不清除历史梯度,就会对计算的历史梯度进行累加。变相实现很大的batch训练
            outputs = net(inputs)                   # 输入网络得到输出
            loss = loss_function(outputs, labels)   # 计算损失
            loss.backward()                         # 损失的后向传播
            optimizer.step()                        # 通过优化器进行参数的更新
            running_loss += loss.item()             # 累加损失
            # 打印统计数据
            if step % 500 == 499:               # 每次经过500步打印一次信息
                with torch.no_grad():
                    outputs = net(val_images)   # 将验证集的图像输入到网络中,得到输出
                    predict_y = torch.max(outputs, dim=1)[1]        # dim1表示在维度1上找最大值,赋值给预测值y。[1]表示只需要知道索引值就可以
                    accuracy = torch.eq(predict_y, val_label).sum().item()/val_label.size(0)
                    # 精确度的计算。torch.eq()的作用是对两个张量Tensor进行逐元素的比较,若相同位置的两个元素相同,则返回True;若不同,返回False
                    # torch.eq().sum()就是将所有值相加,返回的是一个列表[x]
                    # torch.eq().sum().item()将列表的值返回为值x
                    # 分母是验证集中的所有标签的数量,第0个维度的数据数量。
                    print("[%d, %5d] | train_loss: %.3f | test_accuracy: %.3f" %
                          (epoch + 1, step + 1, running_loss / 500, accuracy))  # %5d说明长度为5;.3f表示保留小数点后三位
                    # 试图将训练结果保存到txt文件中,和终端打印出来的频率相同
                    f.write("epoch: %d step: %5d   train_loss: %.3f test_accuracy: %.3f" %
                          (epoch + 1, step + 1, running_loss / 500, accuracy))
                    f.write('\n')               # 写入数据,写完之后换行
                    running_loss = 0.0          # 归零
    print('Finish Training')                    # 报告训练完成
    f.close()                                   # 不要忘记关闭文件

    save_path = './LeNet.pth'                   # 设置权重文件保存位置
    torch.save(net.state_dict(), save_path)     # 将权重文件保存到对应路径中


if __name__ == '__main__':
    main()

        训练完5个epoch后,打开txt文件,可以打开data.txt看到训练结果。

epoch: 1 step:   500   train_loss: 1.760 test_accuracy: 0.422
epoch: 1 step:  1000   train_loss: 1.440 test_accuracy: 0.503
epoch: 2 step:   500   train_loss: 1.207 test_accuracy: 0.582
epoch: 2 step:  1000   train_loss: 1.158 test_accuracy: 0.626
epoch: 3 step:   500   train_loss: 1.030 test_accuracy: 0.671
epoch: 3 step:  1000   train_loss: 0.997 test_accuracy: 0.673
epoch: 4 step:   500   train_loss: 0.920 test_accuracy: 0.693
epoch: 4 step:  1000   train_loss: 0.916 test_accuracy: 0.717
epoch: 5 step:   500   train_loss: 0.830 test_accuracy: 0.740
epoch: 5 step:  1000   train_loss: 0.824 test_accuracy: 0.746 

四、模型验证 

        网络训练好之后,需要对网络的训练结果进行检验。方法是编写predict.py脚本进行验证。在终端中打印出图像分类后的信息。

import torch
import torchvision.transforms as transforms
from PIL import Image
from module import LeNet


def main():
    transform = transforms.Compose(
        [transforms.Resize((32, 32)),                               # 缩放到合适的尺寸
         transforms.ToTensor(),
         transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])   # 标准化,和训练的时候一样

    classes = ('plane', 'car', 'bird', 'cat', 'deer',
               'dog', 'frog', 'horse', 'ship', 'truck')             # 类别名称标签

    net = LeNet()                                                   # 实例化网络
    net.load_state_dict(torch.load('LeNet.pth'))                    # 导入训练完成后的权重文件
    # 希望能够实现图片的顺序检测
    for i in range(7):
        im = Image.open('./test_picture/{}.jpg'.format(i+1))
        im = transform(im)
        im = torch.unsqueeze(im, dim=0)

        with torch.no_grad():
            outputs = net(im)                                           # 输出的是经过网络之后的图片
            predict = torch.max(outputs, dim=1)[1].numpy()
        print(classes[int(predict)])


if __name__ == '__main__':
    main()

        所使用的验证图片一共有7张,存放在test_picture文件夹中。       

         终端打印出来的信息如下所示:        

         不难发现,前6张图片分类结果正确,最后1张出现了错误(狗:我长得很像鹿吗???)。实际上从侧面反映出此图像分类模型的效果并不好。也是可以理解的,毕竟网络结构过于简单,并且提出时间很久了。不过,练习搭建此网络还是很有意义的,正所谓千里之行始于足下。

写在后面的话:

        作为一个刚刚涉及深度学习的小白,对于Python和Pytorch的掌握还是很不熟练,所以有些代码可能看起来非常的笨拙甚至于可笑,希望大家能够多多包涵。

        在此感谢UP主:@霹雳吧啦Wz。本文的代码主要在其开源的代码的基础上进行修改,再次表示感谢。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值