CNN之LeNet初学者笔记

本文介绍了LeNet卷积神经网络,它是深度学习的里程碑,首次采用卷积层和池化层,在手写字符识别任务准确率高。文中阐述了其网络结构、模型特点,详细讲解卷积层输入输出计算,介绍LeNet - 5网络框架,并给出代码实现,包括定义模型、训练和测试代码。

1. 介绍

Lenet 是一系列网络的合称,包括 Lenet1 - Lenet5,由 Yann LeCun 等人 在1990年《Handwritten Digit Recognition with a Back-Propagation Network》中提出,是卷积神经网络的开山之作,也是将深度学习推向繁荣的一座里程碑。

LeNet首次采用了卷积层、池化层这两个全新的神经网络组件,接收灰度图像,并输出其中包含的手写数字,在手写字符识别任务上取得了瞩目的准确率。LeNet网络的一系列的版本,以LeNet-5版本最为著名,也是LeNet系列中效果最佳的版本。

2. 网络结构

Lenet是一个 7 层的神经网络,包含 3 个卷积层,2 个池化层,1 个全连接层,1个输出层。其中所有卷积层的卷积核都为 5x5,步长=1,池化方法都为平均池化,激活函数为 Sigmoid(目前使用的Lenet已改为ReLu),网络结构如下:

在这里插入图片描述

3. 模型特点

  1. 首次提出了卷积神经网络基本框架:卷积层,池化层,全连接层
  2. 卷积层的权重共享,比全连接层使用的参数更少,节省了计算量和存储空间
  3. 卷积层的局部连接,保证了图像的空间相关性
  4. 使用映射到空间均值下采样,减少特征数量
  5. 使用双曲线(tanh)或S型(sigmoid)形式的非线性激活函数

4. 卷积层输入输出计算

  1. 卷积层输入特征图(input feature map)的尺寸:

    input_mapsize = Hin×Win {input\_mapsize}\ =\ H_{in} \times W_{in} input_mapsize = Hin×Win

    其中Hin,WinH_{in},W_{in}HinWin依次为输入特征图的高,宽

  2. 输出通道数K(kernel即卷积核个数)

    正方形卷积核的边长为FFF;步幅(stride)为SSS;补零的行数和列数(padding)为PPP

    • 卷积核与输入图片(二维向量)的计算过程:

    output_map = ∑v,winput_map[i+v,j+w]×kernel[v,w] output\_map\ =\ {\sum_{v,w}}{input\_map[i+v,j+w]}{\times}{kernel[v,w]} output_map = v,winput_map[i+v,j+w]×kernel[v,w]

    ​ 其中v,wv,wv,w取值范围为kernel的长宽即[0,F−1][0,F-1][0,F1]

    • 偏置操作:

      ​ 为了更好的拟合数据,卷积算子还需要加上偏置项,即上面得到的输出特征图每项都要加上一个偏置项。

    • 填充(padding):

      ​ 由于输入图像的边缘位置像素点无法进行卷积滤波,进而填充,即在边缘像素点周围填充“0”(即0填充),注意,在这种填充机制下,卷积后的图像分辨率将与卷积前图像分辨率一致,不存在下采样。

    • 下采样(downsampling)

      ​ 通常称为池化(Pooling),它的作用是减小特征图的空间尺寸。最常见的池化操作是最大池化(Max Pooling),它将原始特征图划分为不重叠的小区域,然后在每个区域中选择最大值作为采样点,从而减小特征图的宽度和高度。池化操作有助于提取特征的平移不变性,同时减少了特征图的尺寸,减少了参数量和计算量,缓解了过拟合。

    • 上采样(upsampling)

      ​ 常用于将特征图的空间尺寸恢复到原始输入尺寸,或者增加特征图的分辨率。常见的上采样操作包括反卷积(Deconvolution)或转置卷积(Transpose Convolution)和插值(Interpolation)。

      ​ 反卷积操作通过学习可逆卷积核来进行上采样,而插值操作则通过插值算法(如最近邻插值、双线性插值等)对特征图进行填充和插值,从而增加特征图的尺寸。

  3. 步长(stride)

    当Stride=1时,卷积核滑动跳过1个像素,这是最基本的单步滑动,也是标准的卷积模式。Stride=k表示卷积核移动跳过的步长是k。

  4. 输出特征图(output feature map)的尺寸:

    Hout=Hin−kh+1Wout=Win−kw+1 H_{out} = H_{in}-k_h+1\\ W{out} = W{in}-k_w+1 Hout=Hinkh+1Wout=Winkw+1
    其中kh,kwk_h,k_wkh,kw分别为卷积核的高宽,对上文即都为FFF

    如果在输入图片第一行之前填充Ph1P_{h1}Ph1行,在最后一行之后填充Ph2P_{h2}Ph2行;在图片第1列之前填充Pw1P_{w1}Pw1列,在最后1列之后填充Pw2P_{w2}Pw2列;则填充之后的图片尺寸为(Hin+Ph1+Ph2)×(Win+Pw1+Pw2)(H_{in}+P_{h1}+P_{h2})\times(W_{in}+P_{w1}+P_{w2})(Hin+Ph1+Ph2)×(Win+Pw1+Pw2)。经过大小为kh×kwk_h{\times}k_wkh×kw​的卷积核操作之后,输出图片的尺寸为:
    $$
    H_{out}=H_{in}+P_{h1}+P_{h2}−k_h+1\

    W {out} = W{in}+P_{w1}+ P_{w2}−k_w+1
    一般padding采取等量填充,且卷积核为正方形,即上式可化为 一般padding采取等量填充,且卷积核为正方形,即上式可化为 一般padding采取等量填充,且卷积核为正方形,即上式可化为
    H_{out}=H_{in}+2P_h−F+1\
    W {out} = W{in}+2P_w−F+1
    $$
    即当P=F−12P=\frac{F-1}{2}P=2F1​时,卷积后图像尺寸不变。

    当高和宽方向的步幅分别为Sh,SwS_h,S_wSh,Sw​时,输出特征图尺寸的计算公式是:
    Hout=Hin+2Ph−FSh+1Wout=Win+2Pw−FSw+1 H_{out}=\frac{H_{in}+2P_h−F}{S_h}+1\\ W _{out} = \frac{W_{in}+2P_w−F}{S_w}+1 Hout=ShHin+2PhF+1Wout=SwWin+2PwF+1

    1. 多输入通道,多输出通道和批量操作

      前面都为二维的卷积计算过程,但是对于彩色照片有RGB三个通道。

      此时的形状为
      input_mapsize = Hin×Win×Cin {input\_mapsize}\ =\ H_{in} \times W_{in} \times C_{in} input_mapsize = Hin×Win×Cin
      其中Hin,Win,CinH_{in},W_{in},C_{in}HinWinCin​依次为输入特征图的高,宽,通道数

      • 多输入通道场景

        1. 分别对每个通道设计一个2维数组作为卷积核,形状为kh×kw×Cink_h \times k_w \times C_{in}kh×kw×Cin
        2. 对任一通道Ci∈[0,Cin−1]C_i\in[0,C_{in}-1]Ci[0,Cin1]分别用卷积核卷积
        3. 将多通道结果相加得到一个形状为Hout×WoutH_{out}\times W_{out}Hout×Wout的二维数组
      • 多输出通道场景

        适用于检测多种类型的特征。卷积核数组维度为Cout×Cin×kh×kwC_{out}\times{C_{in}}\times k_h\times k_wCout×Cin×kh×kw

        1. 对任一输出通道cout∈[0,Cout)c_{out}\in[0,C_{out})cout[0,Cout),分别使用上面描述的形状为kh×kw×Cink_h \times k_w \times C_{in}kh×kw×Cin​的卷积核对输入图片做卷积。
        2. 将这CoutC_{out}Cout个形状为Hout×WoutH_{out}\times{W_{out}}Hout×Wout的二维数组拼接在一起,形成维度为Cout×Hout×WoutC_{out}\times H_{out}\times{W_{out}}Cout×Hout×Wout的三维数组。
      • 批量操作

        适用于对多帧样本放在一起形成一个mini-batch进行批量操作。此时输入维度为

        Hin×Win×Cin×NH_{in} \times W_{in} \times C_{in}\times NHin×Win×Cin×N。对每个图片使用同样的卷积核进行卷积操作,设卷积核维度为Cout×Cin×kh×kwC_{out}\times{C_{in}}\times k_h\times k_wCout×Cin×kh×kw,那么最终的输出特征图维度为N×Cout×Hout×WoutN\times C_{out}\times H_{out}\times{W_{out}}N×Cout×Hout×Wout

    2. 图像卷积例子

      在这里插入图片描述

      分别为边缘检测,锐化,盒式模糊,高斯模糊等处理方式

5.LeNet-5网络框架

  1. 输入层

    首先通过尺寸归一化,把输入图像全部转化为32×3232\times3232×32大小

  2. 第一层-卷积层C1

    Parameterssize
    input mapsize32×3232\times3232×32
    kernel size5×55\times55×5
    kernel count6
    featuremap size28×2828\times2828×28
    Neuron count28×28×628\times28\times628×28×6
    Training parameters(5×5+1)×6=156(5\times5+1)\times6=156(5×5+1)×6=156(1为偏置)
    Connection count156×6×28×28=122304156\times6\times28\times28=122304156×6×28×28=122304
  3. 第二层-池化层S2(下采样)

    Parameterssize
    input mapsize28×2828\times2828×28
    pooling size2×22\times22×2
    pooling layers6
    featuremap size14×1414\times1414×14
    Neuron count14×14×614\times14\times614×14×6
    Training parameters2×62\times62×6
    Connection count(2×2+1)×6×14×14(2\times2+1)\times6\times14\times14(2×2+1)×6×14×14

    作用就是特征映射(降维)。这里减半是因为池化单元没有重叠,也就是S=2S=2S=2,根据上面公式Hout=Hin−FS+1H_{out}=\frac{H_{in}-F}{S}+1Hout=SHinF+1得到Hout=Hin2H_{out}=\frac{H_{in}}{2}Hout=2Hin,相当于图像大小减半。

    池化层计算过程:2×2 单元里的值相加,然后再乘以训练参数w,再加上一个偏置参数b(每一个特征图共享相同的w和b),然后取sigmoid值(S函数:0-1区间),作为对应的该单元的值。下面是两种池化方法。

    pooling2way
  4. 第三层-卷积层C3

    Parameterssize
    input mapsizeS2中6个特征图组合
    kernel size5×55\times55×5
    kernel count161616
    featuremap size10×10(14−5+1)10\times10(14-5+1)10×10145+1
    Training parameters6×(3×5×5+1)+6×(4×5×5+1)+3×(4×5×5+1)+1×(6×5×5+1)=15166×(3×5×5+1)+6×(4×5×5+1)+3×(4×5×5+1)\\+1×(6×5×5+1)=15166×(3×5×5+1)+6×(4×5×5+1)+3×(4×5×5+1)+1×(6×5×5+1)=1516
    Connection count10×10×1616=15160010\times10\times1616=15160010×10×1616=151600

    输入的6个feature map与输出的16个feature map的关系图如下:

    在这里插入图片描述

    C3的前6个feature map(上图红框1的6列)与S2层相连的3个feature map相连接(上图红框1的某相邻的3行),后面6个feature map(上图红框2的6列)与S2层相连的4个feature map相连接(上图红框2的某相邻的4行),后面3个feature map(上图红框3的3列)与S2层部分不相连的4个feature map(上图红框3的某不相邻的4行)相连接,最后一个(上图红框4)与S2层的所有feature map(上图红框4的所有行)相连。

  5. 第四层-池化层S4

    类似S2

    Parameterssize
    input mapsize10×1010\times1010×10
    pooling size2×22\times22×2
    pooling layers161616
    featuremap size5×55\times55×5
    Neuron count5×5×16=4005\times5\times16=4005×5×16=400
    Training parameters2×162\times162×16
    Connection count(2×2+1)×16×5×5(2\times2+1)\times16\times5\times5(2×2+1)×16×5×5
  6. 第五层-卷积层C5

    Parameterssize
    input mapsizeS4中1616165×55\times55×5特征图组合
    kernel size5×55\times55×5
    kernel count120120120
    featuremap size1×11\times11×1
    Training parameters\Connection count120×(16×5×5+1)=48120120\times(16\times5\times5+1)=48120120×(16×5×5+1)=48120
  7. 第六层-全连接层F6

    Parameterssize
    input mapsize120×1×1120\times1\times1120×1×1
    output mapsize848484
    Training parameters\Connection count(120+1)×84=10164(120+1)\times84=10164(120+1)×84=10164

    第六层是全连接层。F6层有84个节点,对应于一个7x12的比特图,-1表示白色,1表示黑色,这样每个符号的比特图的黑白色就对应于一个编码。该层的训练参数和连接数是(120 + 1)x84=10164。

  8. 输出层-Output层

    也为全连接层共十个节点,分别代表数字0到9。如果第i个节点的值为0,则表示网络识别的结果是数字i。采用的是径向基函数(RBF)的网络连接方式。假设x是上一层的输入,y是RBF的输出,则RBF输出的计算方式是:
    yi=∑j(xj−wij)2 y_i=\sum_j(x_j-w_{ij})^2 yi=j(xjwij)2
    径向基神经网络:它基于距离进行衡量两个数据的相近程度的,RBF网最显著的特点是隐节点采用输人模式与中心向量的距离(如欧氏距离)作为函数的自变量,并使用径向基函数(如函数)作为激活函数。暂不扩展

6.LeNet代码实现

  • model.py:定义LeNet网络模型
  • train.py:加载数据集并训练,计算loss和accuracy,保存训练好的网络参数
  • predict.py:用自己的数据集进行分类测试
  1. model.py

    # 导入pytorch库
    import torch
    # 导入torch.nn模块
    from torch import nn
     
     
    # 定义LeNet网络模型
    # MyLeNet5(子类)继承nn.Module(父类)
    class MyLeNet5(nn.Module):
        # 子类继承中重新定义Module类的__init__()和forward()函数
        # init()函数:进行初始化,申明模型中各层的定义
        def __init__(self):
            # super:引入父类的初始化方法给子类进行初始化
            super(MyLeNet5, self).__init__()
            # 卷积层,输入大小为28*28,输出大小为28*28,输入通道为1,输出为6,卷积核为5,扩充边缘为2
            self.c1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2)
            # 使用sigmoid作为激活函数
            self.Sigmoid = nn.Sigmoid()
            # AvgPool2d:二维平均池化操作
            # 池化层,输入大小为28*28,输出大小为14*14,输入通道为6,输出为6,卷积核为2,步长为2
            self.s2 = nn.AvgPool2d(kernel_size=2, stride=2)
            # 卷积层,输入大小为14*14,输出大小为10*10,输入通道为6,输出为16,卷积核为5
            self.c3 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
            # 池化层,输入大小为10*10,输出大小为5*5,输入通道为16,输出为16,卷积核为2,步长为2
            self.s4 = nn.AvgPool2d(kernel_size=2, stride=2)
            # 卷积层,输入大小为5*5,输出大小为1*1,输入通道为16,输出为120,卷积核为5
            self.c5 = nn.Conv2d(in_channels=16, out_channels=120, kernel_size=5)
            # Flatten():将张量(多维数组)平坦化处理,张量的第0维表示的是batch_size(数量),所以Flatten()默认从第二维开始平坦化
            self.flatten = nn.Flatten()
            # 全连接层
            # Linear(in_features,out_features)
            # in_features指的是[batch_size, size]中的size,即样本的大小
            # out_features指的是[batch_size,output_size]中的output_size,样本输出的维度大小,也代表了该全连接层的神经元个数
            self.f6 = nn.Linear(120, 84)
            # 全连接层&输出层
            self.output = nn.Linear(84, 10)
     
        # forward():定义前向传播过程,描述了各层之间的连接关系
        def forward(self, x):
            # x输入为28*28*1, 输出为28*28*6
            x = self.Sigmoid(self.c1(x))
            # x输入为28*28*6,输出为14*14*6
            x = self.s2(x)
            # x输入为14*14*6,输出为10*10*16
            x = self.Sigmoid(self.c3(x))
            # x输入为10*10*16,输出为5*5*16
            x = self.s4(x)
            # x输入为5*5*16,输出为1*1*120
            x = self.c5(x)
            x = self.flatten(x)
            # x输入为120,输出为84
            x = self.f6(x)
            # x输入为84,输出为10
            x = self.output(x)
            return x
     
    # 测试代码
    # 每个python模块(python文件)都包含内置的变量 __name__,当该模块被直接执行的时候,__name__ 等于文件名(包含后缀 .py )
    # 如果该模块 import 到其他模块中,则该模块的 __name__ 等于模块名称(不包含后缀.py)
    # “__main__” 始终指当前执行模块的名称(包含后缀.py)
    # if确保只有单独运行该模块时,此表达式才成立,才可以进入此判断语法,执行其中的测试代码,反之不行
    if __name__ == "__main__":
        # rand:返回一个张量,包含了从区间[0, 1)的均匀分布中抽取的一组随机数,此处为四维张量
        x = torch.rand([1, 1, 28, 28])
        # 模型实例化
        model = MyLeNet5()
        y = model(x)
    
  2. train.py

    import torch
    from torch import nn
    from model import MyLeNet5
    # lr_scheduler:提供一些根据epoch训练次数来调整学习率的方法
    from torch.optim import lr_scheduler
    # torchvision:PyTorch的一个图形库,服务于PyTorch深度学习框架的,主要用来构建计算机视觉模型
    # transforms:主要是用于常见的一些图形变换
    # datasets:包含加载数据的函数及常用的数据集接口
    from torchvision import datasets, transforms
    # os:operating system(操作系统),os模块封装了常见的文件和目录操作
    import os
     
    # 数据转化为Tensor格式
    # Compose():将多个transforms的操作整合在一起
    # ToTensor(): 将numpy的ndarray或PIL.Image读的图片转换成形状为(C,H, W)的Tensor格式,且归一化到[0,1.0]之间
    data_transform = transforms.Compose([
        transforms.ToTensor()
    ])
     
    # 加载训练数据集
    # MNIST数据集来自美国国家标准与技术研究所, 训练集 (training set)、测试集(test set)由分别由来自250个不同人手写的数字构成
    # MNIST数据集包含:Training set images、Training set images、Test set images、Test set labels
    # train = true是训练集,false为测试集
    train_dataset = datasets.MNIST(root='./data', train=True, transform=data_transform, download=True)
    # DataLoader:将读取的数据按照batch size大小封装并行训练
    # dataset (Dataset):加载的数据集
    # batch_size (int, optional):每个batch加载多少个样本(默认: 1)
    # shuffle (bool, optional):设置为True时会在每个epoch重新打乱数据(默认: False)
    train_dataloader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=16, shuffle=True)
    # 加载测试数据集
    test_dataset = datasets.MNIST(root='./data', train=False, transform=data_transform, download=True)
    test_dataloader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=16, shuffle=True)
     
    # 如果有NVIDA显卡,转到GPU训练,否则用CPU
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
     
    # 模型实例化,将模型转到device
    model = MyLeNet5().to(device)
     
    # 定义损失函数(交叉熵损失)
    loss_fn = nn.CrossEntropyLoss()
     
    # 定义优化器(随机梯度下降法)
    # params(iterable):要训练的参数,一般传入的是model.parameters()
    # lr(float):learning_rate学习率,也就是步长
    # momentum(float, 可选):动量因子(默认:0),矫正优化率
    optimizer = torch.optim.SGD(model.parameters(), lr=1e-3, momentum=0.9)
     
    # 学习率,每隔10轮变为原来的0.1
    # StepLR:用于调整学习率,一般情况下会设置随着epoch的增大而逐渐减小学习率从而达到更好的训练效果
    # optimizer (Optimizer):需要更改学习率的优化器
    # step_size(int):每训练step_size个epoch,更新一次参数
    # gamma(float):更新lr的乘法因子
    lr_scheduler = lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
     
     
    # 定义训练函数
    def train(dataloader, model, loss_fn, optimizer):
        loss, current, n = 0.0, 0.0, 0
        # dataloader: 传入数据(数据包括:训练数据和标签)
        # enumerate():用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,一般用在for循环当中
        # enumerate返回值有两个:一个是序号,一个是数据(包含训练数据和标签)
        # x:训练数据(inputs)(tensor类型的),y:标签(labels)(tensor类型的)
        for batch, (x, y) in enumerate(dataloader):
            # 前向传播
            x, y = x.to(device), y.to(device)
            # 计算训练值
            output = model(x)
            # 计算观测值(label)与训练值的损失函数
            cur_loss = loss_fn(output, y)
            # torch.max(input, dim)函数
            # input是具体的tensor,dim是max函数索引的维度,0是每列的最大值,1是每行的最大值输出
            # 函数会返回两个tensor,第一个tensor是每行的最大值;第二个tensor是每行最大值的索引
            _, pred = torch.max(output, axis=1)
            # 计算每批次的准确率
            # output.shape[0]一维长度为该批次的数量
            # torch.sum()对输入的tensor数据的某一维度求和
            cur_acc = torch.sum(y == pred) / output.shape[0]
     
            # 反向传播
            # 清空过往梯度
            optimizer.zero_grad()
            # 反向传播,计算当前梯度
            cur_loss.backward()
            # 根据梯度更新网络参数
            optimizer.step()
            # .item():得到元素张量的元素值
            loss += cur_loss.item()
            current += cur_acc.item()
            n = n + 1
     
        train_loss = loss / n
        train_acc = current / n
        # 计算训练的错误率
        print('train_loss' + str(train_loss))
        # 计算训练的准确率
        print('train_acc' + str(train_acc))
     
     
    # 定义验证函数
    def val(dataloader, model, loss_fn):
        # model.eval():设置为验证模式,如果模型中有Batch Normalization或Dropout,则不启用,以防改变权值
        model.eval()
        loss, current, n = 0.0, 0.0, 0
        # with torch.no_grad():将with语句包裹起来的部分停止梯度的更新,从而节省了GPU算力和显存,但是并不会影响dropout和BN层的行为
        with torch.no_grad():
            for batch, (x, y) in enumerate(dataloader):
                # 前向传播
                x, y = x.to(device), y.to(device)
                output = model(x)
                cur_loss = loss_fn(output, y)
                _, pred = torch.max(output, axis=1)
                cur_acc = torch.sum(y == pred) / output.shape[0]
                loss += cur_loss.item()
                current += cur_acc.item()
                n = n + 1
            # 计算验证的错误率
            print("val_loss:" + str(loss / n))
            # 计算验证的准确率
            print("val_acc:" + str(current / n))
            # 返回模型准确率
            return current / n
     
     
    # 开始训练
    # 训练次数
    epoch = 10
    # 用于判断最佳模型
    min_acc = 0
    for t in range(epoch):
        print(f'epoch {t + 1}\n---------------')
        # 训练模型
        train(train_dataloader, model, loss_fn, optimizer)
        # 验证模型
        a = val(test_dataloader, model, loss_fn)
        # 保存最好的模型权重
        if a > min_acc:
            folder = 'save_model'
            # path.exists:判断括号里的文件是否存在,存在为True,括号内可以是文件路径
            if not os.path.exists(folder):
                # os.mkdir() :用于以数字权限模式创建目录
                os.mkdir('save_model')
            min_acc = a
            print('save best model')
            # torch.save(state, dir)保存模型等相关参数,dir表示保存文件的路径+保存文件名
            # model.state_dict():返回的是一个OrderedDict,存储了网络结构的名字和对应的参数
            torch.save(model.state_dict(), 'save_model/best_model.pth')
    print('Done!')
    
  3. predict.py

    import torch
    from model import MyLeNet5
    from torch.autograd import Variable
    from torchvision import datasets, transforms
    from torchvision.transforms import ToPILImage
     
    # Compose():将多个transforms的操作整合在一起
    data_transform = transforms.Compose([
        # ToTensor():数据转化为Tensor格式
        transforms.ToTensor()
    ])
     
    # 加载训练数据集
    train_dataset = datasets.MNIST(root='./data', train=True, transform=data_transform, download=True)
    # 给训练集创建一个数据加载器, shuffle=True用于打乱数据集,每次都会以不同的顺序返回
    train_dataloader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=16, shuffle=True)
     
    # 加载测试数据集
    test_dataset = datasets.MNIST(root='./data', train=False, transform=data_transform, download=True)
    test_dataloader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=16, shuffle=True)
     
    # 如果有NVIDA显卡,转到GPU训练,否则用CPU
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
     
    # 模型实例化,将模型转到device
    model = MyLeNet5().to(device)
     
    # 加载train.py里训练好的模型
    model.load_state_dict(torch.load("D:/pycharm/file/save_model/best_model.pth"))
     
    # 结果类型
    classes = [
        "0",
        "1",
        "2",
        "3",
        "4",
        "5",
        "6",
        "7",
        "8",
        "9",
    ]
     
    # 把Tensor转化为图片,方便可视化
    show = ToPILImage()
     
    # 进入验证阶段
    for i in range(10):
        x, y = test_dataset[i][0], test_dataset[i][1]
        # show():显示图片
        show(x).show()
        # unsqueeze(input, dim),input(Tensor):输入张量,dim (int):插入维度的索引,最终将张量维度扩展为4维
        x = Variable(torch.unsqueeze(x, dim=0).float(), requires_grad=False).to(device)
        with torch.no_grad():
            pred = model(x)
            # argmax(input):返回指定维度最大值的序号
            # 得到验证类别中数值最高的那一类,再对应classes中的那一类
            predicted, actual = classes[torch.argmax(pred[0])], classes[y]
            # 输出预测值与真实值
            print(f'predicted: "{predicted}", actual:"{actual}"')
    
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值