Lenet 学习笔记 pytorch官方demo代码复现

前言:

参考内容来自up:2.1 pytorch官方demo(Lenet)_哔哩哔哩_bilibili

up的代码和ppt:https://github.com/WZMIAOMIAO/deep-learning-for-image-processing

pytorch官方代码:Training a Classifier — PyTorch Tutorials 1.13.1+cu117 documentation

数据集下载:

链接:https://pan.baidu.com/s/1NBHp0SxEOJ5EIyYUsDHm_g

提取码:qp3k


简介

pytorch Tensor的通过排序:【batch、channel,height、width】,后面对Tensor进行处理时,都是根据这个顺序来处理的。

batch代表一批图像的个数;

channel代表图像的维度,这里使用的CIFAR10 是彩色图像,所以channel为3。

LeNet网络由一个卷积层,一个下采样,一个卷积层,一个下采样和三个全连接层组组成;

LeNet网络使用的是灰度图像,只有一个维度。

demo的流程

  • model.py ——定义LeNet网络模型

  • train.py ——加载数据集并训练,训练集计算loss,测试集计算accuracy,保存训练好的网络参数

  • predict.py——得到训练好的网络参数后,用自己找的图像进行分类测试

1. model.py

import torch.nn as nn
import torch.nn.functional as F


class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 16, 5)   #分别代表输入特征矩阵的深度,卷积核的个数,卷积核的大小;
        self.pool1 = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(16, 32, 5)
        self.pool2 = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(32*5*5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = F.relu(self.conv1(x))    # input(3, 32, 32) output(16, 28, 28)
        x = self.pool1(x)            # output(16, 14, 14)
        x = F.relu(self.conv2(x))    # output(32, 10, 10)
        x = self.pool2(x)            # output(32, 5, 5)
        x = x.view(-1, 32*5*5)       # output(32*5*5)
        x = F.relu(self.fc1(x))      # output(120)
        x = F.relu(self.fc2(x))      # output(84)
        x = self.fc3(x)              # output(10)
        return x

在pytorch中搭建模型的步骤:

  • 定义一个类,这个类要继承nn.Module 这个父类;

  • 在这个中实现两个方法__init__函数和forward函数;

  • 初始化函数__init__,在初始化函数中实现搭建网络中需要使用到的网络层结构;

  • forward函数中定义正向传播的过程,当实例化这个类之后,当参数传递到这个实例中,就会进行正向传播

搭建LeNet 的步骤:

  • 定义一个类LeNet,这个类要继承nn.Module 这个父类;

定义初始化函数:

  • 在其中实现__init__初始化函数,在其中使用super函数,super函数可以解决在调用父类方法时可能出现的问题,涉及到多继承时,一般都要使用super函数;

  • 在pytroch中定义卷积层,就是通过nn.Conv2d函数来定义卷积层,(3, 16, 5)分别代表输入特征矩阵的深度,卷积核的个数,卷积核的大小,经过计算得output(16, 28, 28)—计算公式在下方;

  • 定义下采样操作,使用nn.MaxPool2d方法,(2, 2) 表示使用池化和为2,步距也为2,将高度和宽度缩减为原来的一半,得output(16, 14, 14) ;

  • 定义第二个卷积层,(16, 32, 5)表示,输入特征矩阵的深度为16,卷积核个数为32,卷积核大小为5,经计算得output(32, 10, 10);

  • 定义第二个下采样层,使用nn.MaxPool2d方法定义为(2, 2) ,将高度和宽度缩减为原来的一半,得output(32, 5, 5) ;

  • 定义全连接层,因为全连接层的输入为一维向量,所以需要将得到的特征矩阵展平,变为一维向量,第一个全连接层的输入节点个数就为(32*5*5),同时设置120的节点数;

  • 定义第二个全连接层,输入就是上一层的输出为120,输出为84;

  • 定义第三个全连接层,输入就是上一层的输出为84,输出需要根据训练集修改,这里使用的CIFAR10是是个类别的数据,所以输出为10;

下面定义向前传播过程:

  • X代表输入的数据,即为Tensor的通过排序:【batch、channel,height、width】

  • 将数据通过卷积层一,再将得到的数据经过Relu激活函数;

  • 将输出通过第一次下采样;

  • 将输出通过第二次卷积层,再将得到的数据经过Relu激活函数;

  • 将输出通过第二次下采样,下一步与全连接层进行拼接,需要进行展平处理;

  • 通过view函数进行展平处理,(-1, 32*5*5) ,-1代表第一个维度,因为第一个维度为batch,所以设置为-1,第二个维度为展平后的节点个数,得到一维数据;

  • 将数据通过全连接层一和激活函数;

  • 将数据通过全连接层二和激活函数;

  • 将数据通过全连接层三得到最终的输出。

1.1 Conv2d

函数定义中所使用参数的定义:

def __init__(
        self,
        in_channels: int,                   #输入特征矩阵的深度
        out_channels: int,                  #对应卷积核的个数
        kernel_size: _size_2_t,             #卷积核的大小
        stride: _size_2_t = 1,              #步距,默认为0
        padding: Union[str, _size_2_t] = 0, #在四周进行补零处理,默认为0
        dilation: _size_2_t = 1,
        groups: int = 1,
        bias: bool = True,                  #偏置,默认使用
    )

pytorch官方定义:

dilation默认为1,带入公式,就和下图公式一样

计算 self.conv1 = nn.Conv2d(3, 16, 5) 经卷积后矩阵尺寸的大小:

W为输入图片的大小为32 × 32,F为卷积核大小为5,P为0,S为1

(32-5)/ 1 + 1 = 28

input(3, 32, 32) 通过卷积得到 output(16, 28, 28),16为卷积核个数,变为下一层的输入特征矩阵深度

1.2 MaxPool2d

函数定义中所使用参数的定义:

def __init__(self, 
              kernel_size: _size_any_t,                #池化和的大小
              stride: Optional[_size_any_t] = None,    #步距
              padding: _size_any_t = 0, 
              dilation: _size_any_t = 1,
              return_indices: bool = False, 
              ceil_mode: bool = False) -> None:
        super(_MaxPoolNd, self).__init__()
        self.kernel_size = kernel_size
        self.stride = stride if (stride is not None) else kernel_size
        self.padding = padding
        self.dilation = dilation
        self.return_indices = return_indices
        self.ceil_mode = ceil_mode

self.pool1 = nn.MaxPool2d(2, 2) 表示使用池化和为2,步距也为2;

通过最大下采样得到的输出为 # output(16, 14, 14) ,将高度和宽度缩减为原来的一半,池化操作只改变特征矩阵的高和宽,不改变深度;

1.3 简单测试:打断点debug

可以看到左侧输出即为设置的input

经过第一个池化层

2. train.py

导入包

import torch
import torchvision
import torch.nn as nn
from model import LeNet
import torch.optim as optim
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
import time

2.1数据预处理

对输入的图像数据做预处理:

ToTensor函数:即由shape (H x W x C) in the range [0, 255] → shape (C x H x W) in the range [0.0, 1.0];

注通过PIL或者numpy导入的图片都是 (H x W x C) 格式,必须使用Tensor函数转化为(C x H x W)格式才能使用;

Normalize函数:output[channel] = (input[channel] - mean[channel]) / std[channel];

即输出=(原始数据-均值)/标准差

transform = transforms.Compose(
        [transforms.ToTensor(),
         transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

数据集介绍

利用torchvision.datasets函数可以在线导入pytorch中的数据集,包含一些常见的数据集如MNIST等

导入数据集:通过CIFAR10导入训练集

# 第一次使用时要将download设置为True才会自动去下载数据集
# 导入50000张训练图片
train_set = torchvision.datasets.CIFAR10(root='./data',      # 数据集存放目录
                                         train=True,         # 表示是数据集中的训练集
                                        download=True,       # 第一次运行时为True,下载数据集,下载完成后改为False
                                        transform=transform) # 预处理过程
# 加载训练集,实际过程需要分批次(batch)训练                                        
train_loader = torch.utils.data.DataLoader(train_set,       # 导入的训练集
                                           batch_size=50, # 每批训练的样本数
                                          shuffle=False,  # 是否打乱训练集
                                          num_workers=0)  # 使用线程数,在windows下设置为0

导入测试集

# 导入10000张测试图片
test_set = torchvision.datasets.CIFAR10(root='./data', 
                                        train=False,    # 表示是数据集中的测试集
                                        download=False,transform=transform)
# 加载测试集
test_loader = torch.utils.data.DataLoader(test_set, 
                                          batch_size=10000, # 每批用于验证的样本数
                                          shuffle=False, num_workers=0)
# 获取测试集中的图像和标签,用于accuracy计算
test_data_iter = iter(test_loader)
test_image, test_label = test_data_iter.next()

数据处理:

val_data_iter = iter(val_loader)             #iter将val_loader变为可迭代的迭代器
val_image, val_label = next(val_data_iter)   #next可得到一批数据,即图像和标签
    
classes = ('plane', 'car', 'bird', 'cat',    #导入标签,元组类型(值不能改变)
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

2.2 训练过程
    net = LeNet()                                                    #实例化模型
    loss_function = nn.CrossEntropyLoss()                            #定义损失函数,这个函数已经包含softmax函数,所以网络的最后一层不再使用
    optimizer = optim.Adam(net.parameters(), lr=0.001)                    #定义优化器,Adam优化器,第一个参数是需要训练的参数,将lenet可训练的参数都进行训练,lr是学习率

    for epoch in range(5):  # loop over the dataset multiple times        #将训练集训练多少次

        running_loss = 0.0                                                #累加训练过程中的损失
        for step, data in enumerate(train_loader, start=0):               #通过循环遍历训练集样本,通过enumerate函数,不仅返回数据,还可以返回每一批数据的步数
            # get the inputs; data is a list of [inputs, labels]
            inputs, labels = data                                         #将数据分成输入和标签

            # zero the parameter gradients
            optimizer.zero_grad()                                         #将历史损失梯度清零
            # forward + backward + optimize
            outputs = net(inputs)                                         #将输入的图片进行正向传播
            loss = loss_function(outputs, labels)                         #outputs是网络的预测值,labels是输入图片对应的真实标签
            loss.backward()                                               #反向传播
            optimizer.step()                                              #参数的更新

            # print statistics
            running_loss += loss.item()                                   #将损失累加
            if step % 500 == 499:    # print every 500 mini-batches       #每隔500步打印一次数据信息
                with torch.no_grad():                                     #with是一个上下文管理器,torch.no_grad表示在接下来不要计算误差损失梯度
                    outputs = net(val_image)  # [batch, 10]               #进行正向传播

                    predict_y = torch.max(outputs, dim=1)[1]              #寻找最大的index在哪里,[batch, 10] 第0个维度是batch,第一个维度是输出,[1]是索引,得到最大值对应的标签类别

                    accuracy = torch.eq(predict_y, val_label).sum().item() / val_label.size(0)            
# 预测标签类别和真实标签类别比较,在相同的地方返回1  true,.sum()计算在预测过程中预测对了多少样本,得到结果是tensor,通过.item()拿到数值,在除以训练样本的数目

                    print('[%d, %5d] train_loss: %.3f  test_accuracy: %.3f' %
                          (epoch + 1, step + 1, running_loss / 500, accuracy))
                                         # %d=epoch,%5d是某一轮的多少步, %.3f训练过程中累加的误差
                    running_loss = 0.0   #清零,进行下一次的500步

    print('Finished Training')

    save_path = './Lenet.pth'
    torch.save(net.state_dict(), save_path)                               #保存模型

打印信息如下:

[1,   500] train_loss: 1.779  test_accuracy: 0.451
[1,  1000] train_loss: 1.435  test_accuracy: 0.527
[2,   500] train_loss: 1.230  test_accuracy: 0.578
[2,  1000] train_loss: 1.177  test_accuracy: 0.613
[3,   500] train_loss: 1.053  test_accuracy: 0.632
[3,  1000] train_loss: 1.034  test_accuracy: 0.635
[4,   500] train_loss: 0.943  test_accuracy: 0.651
[4,  1000] train_loss: 0.951  test_accuracy: 0.666
[5,   500] train_loss: 0.846  test_accuracy: 0.668
[5,  1000] train_loss: 0.856  test_accuracy: 0.666
Finished Training

3. predict.py

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

from model import LeNet


def main():
    transform = transforms.Compose(
        [transforms.Resize((32, 32)),           #缩放图片 
         transforms.ToTensor(),                 #转化为tensor
         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()                                                           #实例化LeNet
    net.load_state_dict(torch.load('Lenet.pth'))                            #载入权重文件

    im = Image.open('1.jpg')                                                #使用Image模块载入图片
    im = transform(im)                                         # [C, H, W],通过PIL载入的图片是高度,宽度,深度,必须变为tensor格式才能进行正向传播,进行预处理,得到[C, H, W]格式
    im = torch.unsqueeze(im, dim=0)  # [N, C, H, W],          #使用unsqueeze增加一个维度

    with torch.no_grad():
        outputs = net(im)                                                   #图像传入网络
        predict = torch.max(outputs, dim=1)[1].numpy()                      #寻找输出中最大的index
    print(classes[int(predict)])                                            #传入classes


if __name__ == '__main__':
    main()

输出即为预测的标签

预测结果也可以用 softmax 表示,输出10个概率:

with torch.no_grad():
    outputs = net(im)
    predict = torch.softmax(outputs, dim=1)
print(predict)

输出结果中最大概率值对应的索引即为预测标签的索引

tensor([[8.9619e-01, 8.9764e-04, 5.7712e-02, 4.5166e-03, 1.2172e-02, 9.6238e-04,
         7.4281e-04, 1.6205e-04, 2.4677e-02, 1.9665e-03]])

猜你喜欢

转载自blog.csdn.net/weixin_45897172/article/details/128550035
今日推荐