小工具知识网

当前位置: 首页 > 行业知识 > 卷积神经网络(LeNet)详解

行业知识

卷积神经网络(LeNet)详解

2026-11-13 23:15:43 来源:互联网转载

��   手写字体识别模型LeNet5诞生于1994年,是最早的卷积神经网络之一。LeNet5通过巧妙的设计,利用卷积、参数共享、池化等操作提取特征,避免了大量的计算成本,最后再使用全连接神经网络进行分类识别,这个网络也是最近大量神经网络架构的起点。  本文将会对LeNet这个网络结构进行详细的讲解,并记录下自己的学习内容,作为自己日常的复习笔记。在学习过程中,我参阅了网上众多大佬的博文,将会在本博文的最后以参考文献的形式列出来。对此对所有的网上大佬表示感谢。

一、LeNet简介

    LeNet-5共有7层,不包含输入,每层都包含可训练参数;每个层有多个Feature Map,每个Feature Map是通过一种卷积滤波器提取输入的一种特征,然后每个Feature Map有多个神经元。

1.1卷积层的原理

        卷积原因的详细知识点,可以参阅:飞桨PaddlePaddle-源于产业实践的开源深度学习平台

torch.nn.Conv2d()的参数解释如下: 

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)

 参数说明:

in_channels (int) – Number of channels in the input imageout_channels (int) – Number of channels produced by the convolutionkernel_size (int or tuple) – Size of the convolving kernelstride (int or tuple, optional) – Stride of the convolution. Default: 1padding (int, tuple or str, optional) – Padding added to all four sides of the input. Default: 0padding_mode (string, optional) – ‘zeros’, ‘reflect’, ‘replicate’ or ‘circular’. Default: ‘zeros’dilation (int or tuple, optional) – Spacing between kernel elements. Default: 1groups (int, optional) – Number of blocked connections from input channels to output channels. Default: 1bias (bool, optional) – If True, adds a learnable bias to the output. Default: Truein_channels:输入特征矩阵的深度。如输入一张RGB彩色图像,那in_channels=3out_channels:输入特征矩阵的深度。也等于卷积核的个数,使用n个卷积核输出的特征矩阵深度就是nkernel_size:卷积核的尺寸。可以是int类型,如3 代表卷积核的height=width=3,也可以是tuple类型如(3, 5)代表卷积核的height=3,width=5stride:卷积核的步长。默认为1,和kernel_size一样输入可以是int型,也可以是tuple类型padding:补零操作,默认为0。可以为int型如1即补一圈0,如果输入为tuple型如(2, 1) 代表在上下补2行,左右补1列。

经过卷积后的输出大小计算为下:

 1.2下采样层

self.pool1 = nn.MaxPool2d(2, 2)
torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)kernel_size – the size of the window to take a max overstride – the stride of the window. Default value is kernel_sizepadding – implicit zero padding to be added on both sidesdilation – a parameter that controls the stride of elements in the windowreturn_indices – if True, will return the max indices along with the outputs. Useful for torch.nn.MaxUnpool2d laterceil_mode – when True, will use ceil instead of floor to compute the output shape

经过池化后的输出大小计算为下:

1.3 LeNet网络结构的详细讲解 

       卷积神经网络流行之前,我们做分类时常用的是“两段论”,即先用各种特征提取方法,如HOG、SIFT等,再连接分类器,如SVM、贝叶斯分类器等。这种方法工作量大,且最终效果并不好(一部分原因在于 不同特征提取方法和不同分类器的结合,效果无法预测)。而CNN采用的是端到端(end to end)的方法,将特征提取和分类方法全部由计算机自动计算,一方面减少了我们探寻特征提取和分类器的工作,另一方面也会使得分类效果更好。

      LeNet5由7层CNN(不包含输入层)组成,上图中输入的原始图像大小是32×32像素,卷积层用Ci表示,子采样层(pooling,池化)用Si表示,全连接层用Fi表示。下面逐层介绍其作用和示意图上方的数字含义。

LeNet模型的网络结构代码:

class LeNet(nn.Module): # 继承于nn.Module这个父类    def __init__(self):# 初始化网络结构        super(LeNet, self).__init__()    # 多继承需用到super函数        self.conv1 = nn.Conv2d(3, 16, 5)    #16代表输出的通道数,可以根据实际的情况进行调整        self.pool1 = nn.MaxPool2d(2, 2)        self.conv2 = nn.Conv2d(16, 32, 5)    #这里的16和前面的那个16需要对应。        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

(1)输入:网络输入为32*32的单通道图像,我们利用LeNet-5分类时应resize图像为32*32.

(2)第一层:卷积层(C1层):卷积层的目的是提取图像特征,神经网络结构中较前的卷积层主要提取图像的细节特征,较后的卷积层主要提取图像的全局特征。对输入的单通道32*32图像用16个不同的5*5卷积核(也称为滤波器)从左到右,从上到下进行卷积操作.最后结果为16个特征图(feature map,有几个卷积核就有几个对应的特征图),每个特征图大小为28*28(因为输入为32*32,卷积核为5*5,步长(stride)为1,且没有进行补边(padding)操作,所以输出大小为(32-5+1)*(32-5+1)=28*28).
(具体的计算方式,看上文所示)

C1是一个卷积层,卷积核大小为5,输入大小为3X32X32,输出特征图大小为16X28X28。

self.conv1 = nn.Conv2d(3, 16, 5)

(3)第二层:池化层(pooling层,也称为下采样层(subsampling),S2层):下采样层主要降低数据维度。这一层在16个输入特征图做2*2 池化,步长为2(没有重叠池化),所以输出为16个14*14的特征图。具体池化过程为在输入特征图中2*2的范围将四个输入值相加,然后乘以一个权重,然后加上一个偏置,最后经过sigmoid激活函数输出。

S2是一个池化层,kernel_size为2,stride为2,输入大小为16X28X28,输出特征图大小为16X14X14。

self.pool1 = nn.MaxPool2d(2, 2)

(4)第三层:卷积层(C3层):这一层输出为32个10*10的特征图。首先采用的卷积核仍然是5*5,所以输出的特征图大小为(14-5+1)*(14-5+1)=10*10.那么,上一层的16个特征图和这一层的32个特征图是如何连接的呢?

C3是一个卷积层,卷积核大小为5,输入大小为16X14X14,输出特征图大小为32X10X10。

self.conv2 = nn.Conv2d(16, 32, 5)

(5)第四层:池化层(S4层):这一层输入为32个10*10的特征图,经过2*2的池化后输出为16个5*5的特征图。具体池化过程同S2层。 

S4是一个池化层,kernel_size为2,stride为2,输入大小为32X10X10,输出特征图大小为32X5X5。

self.pool2 = nn.MaxPool2d(2, 2)

(6)第五层:卷积层(C5层):这一层输入为32个5*5的特征图,经过5*5的卷积核操作后,输出为120个1*1的特征图((5-5+1)*(5-5+1)=1*1)。 

C5是一个卷积层,卷积核大小为5,输入大小为32X5X5,输出特征图大小为120X1X1。

此处用全连接层代替

self.fc1 = nn.Linear(32*5*5, 120)

 (7)第六层:全连接层(Full connection,F6层):这一层输入为120个1*1的特征图,即120*1维向量,输出为84个1*1的特征图,即84*1维向量,连接方式为全连接,即F6层的每一个神经元都与C5层的每一个神经元连接。输出为输入和权重卷积后加上偏置,在经过sigmoid激活函数后得到。

F6是一个全连接层,输入大小为120,输出特征图大小为84。

self.fc2 = nn.Linear(120, 84)

 (8)第七层:全连接层(F7层,输出层):输入为84*1维向量,输出为10*1维向量(10个分类结果,LeNet-5最早应用于手写体数字识别,所以为10个类别)。这一层采用的是径向基函数(RBF)的网络连接方式。

F7是一个全连接层,输入大小为84,输出特征图大小为10(表示有10种类别)。

self.fc3 = nn.Linear(84, 10)

 如今看来,LeNet-5很简单,但其设计的卷积层、下采样层、全连接层等在深度学习的发展中有着里程碑式的意义。LeCun在其论文中总结如下: 

Convolutional networks combine three architectural ideas to ensure some degree of shift, scale, and distortion invariance: 1) local receptive fields; 2) shared weights (or weight replication); and 3) spatial or temporal subsampling. 

卷积网络有三个设计意图以确保一定程度的偏移、缩放和翻转不变:1)局部感受野  2)共享权值   3)空间或时间下采样

为了更加直观的查看模型的网络结构,我们可以采用如下的方式将网络结构导出。

具体可查看博文:pytorch快速上手-----netron查看神经网络结构图_欲游山河十万里的博客-CSDN博客

torch.onnx.export()是一个用于将PyTorch模型导出为ONNX模型的函数。它的参数含义如下:

  • model: 要导出的PyTorch模型。
  • args: 模型的输入参数。这应该是一个元组,其中包含模型的输入张量。
  • f: 导出的ONNX模型的文件路径。
  • export_params: 是否将模型参数导出为ONNX模型的常量。如果为True,则会将模型参数转换为常量,并将其存储在导出的ONNX模型中。如果为False,则不会将参数导出为常量,而是在运行时加载它们。
  • opset_version: 导出的ONNX模型使用的ONNX规范版本号。默认为10。
  • do_constant_folding: 是否在导出过程中进行常量折叠。如果为True,则会在导出过程中尝试将计算图中的常量合并为单个常量节点。如果为False,则不会进行常量折叠。
  • input_names: 导出的ONNX模型中输入张量的名称。默认为["input"]。
  • output_names: 导出的ONNX模型中输出张量的名称。默认为["output"]。
  • dynamic_axes: 导出的ONNX模型中动态维度的名称和位置。默认为None。
  • verbose: 控制是否打印导出过程中的详细信息。如果为True,则会打印详细信息,否则不会打印。

    这些参数可以帮助我们控制导出过程的各个方面,以便我们能够生成我们需要的ONNX模型。 

    二、LeNet代码实现 

    2.1 model.py

    # 使用torch.nn包来构建神经网络.import torch.nnimport torch.nn as nnimport torch.nn.functional as Fimport onnx.version_converterclass LeNet(nn.Module): # 继承于nn.Module这个父类    def __init__(self):# 初始化网络结构        super(LeNet, self).__init__()    # 多继承需用到super函数        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 xdata = torch.randn(10,3,32,32)#N-Cin-Hin-Winnet = LeNet()# 导出torch.onnx.export(    net,    data,    'LeNetModel.onnx',    export_params=True,    opset_version=10,)# 增加维度信息model_file = 'LeNetModel.onnx'onnx_model = onnx.load(model_file)onnx.save(onnx.shape_inference.infer_shapes(onnx_model), model_file)

    2.2 train.py

    import torchimport torchvisionimport torch.nn as nnfrom model import LeNetimport torch.optim as optimimport torchvision.transforms as transformsimport matplotlib.pyplot as pltimport numpy as npimport timetransform = transforms.Compose(    [transforms.ToTensor(),     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])# 导入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张验证图片# 第一次使用时要将download设置为True才会自动去下载数据集val_set = torchvision.datasets.CIFAR10(root='./data', train=False,                                       download=True, 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_image, val_label = next(val_data_iter)device = torch.device("cuda" if torch.cuda.is_available() else "cpu")print(device)net = LeNet()net.to(device) # 将网络分配到指定的device中loss_function = nn.CrossEntropyLoss()optimizer = optim.Adam(net.parameters(), lr=0.001)for epoch in range(5):    net.train()    running_loss = 0.0    time_start = time.perf_counter()    for step, data in enumerate(train_loader, start=0):        inputs, labels = data        optimizer.zero_grad()        outputs = net(inputs.to(device))  # 将inputs分配到指定的device中        loss = loss_function(outputs, labels.to(device))  # 将labels分配到指定的device中        loss.backward()        optimizer.step()        running_loss += loss.item()        if step % 1000 == 999:            net.eval()            with torch.no_grad():                outputs = net(val_image.to(device)) # 将test_image分配到指定的device中                predict_y = torch.max(outputs, dim=1)[1]                accuracy = (predict_y == val_label.to(device)).sum().item() / val_label.size(0) # 将test_label分配到指定的device中                print('[%d, %5d] train_loss: %.3f  test_accuracy: %.3f' %                      (epoch + 1, step + 1, running_loss / 1000, accuracy))                print('%f s' % (time.perf_counter() - time_start))                running_loss = 0.0print('Finished Training')save_path = './Lenet.pth'torch.save(net.state_dict(), save_path)

    2.2.1代码解答 

    transform = transforms.Compose的参数

    transforms.Compose 是 PyTorch 中的一个类,用于将多个图像变换(transform)组合成一个变换序列。它接受一个列表(list)作为参数,每个列表元素都是一个变换操作(transform)。

    transforms.Compose 的参数是由多个变换操作组成的列表,每个变换操作都是 PyTorch 中 transforms 模块中的一个类,常见的变换操作包括:

    • transforms.Resize(size):调整图像大小
    • transforms.CenterCrop(size):中心裁剪
    • transforms.RandomCrop(size, padding=None):随机裁剪
    • transforms.RandomHorizontalFlip():随机水平翻转
    • transforms.ToTensor():将图像转换为 Tensor
    • transforms.Normalize(mean, std):标准化图像 

       举个例子,如果要将图像先调整为大小为 224X224,然后将其转换为 Tensor,最后进行标准化处理,可以这样写:

      transform = transforms.Compose([    transforms.Resize((224, 224)),    transforms.ToTensor(),    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])

      其中,Resize 将图像调整为大小为 224X224,ToTensor 将图像转换为 Tensor,Normalize 对图像进行标准化处理。注意,这里的 mean 和 std 是根据 ImageNet 数据集计算得到的。在实际使用中,需要根据具体的数据集计算出相应的 mean 和 std。 

      transforms.Normalize参数确定 

      在PyTorch中,transforms.Normalize用于对图像进行归一化操作,使其在各个通道上的均值为0、标准差为1,从而加快模型的训练收敛和提高模型的泛化能力。transforms.Normalize的参数取决于训练集的均值和标准差,一般可以通过以下方式计算:

      1. 对训练集的每个通道分别计算均值和标准差,例如对于3通道的RGB图像,可以分别计算三个通道的均值和标准差,得到一个形状为(3,)的均值向量和标准差向量。
      2. 将计算得到的均值和标准差作为transforms.Normalize的参数传入,例如transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])。

      可以用以下的代码实现计算机

      import torchfrom torchvision import datasets, transformstrain_transforms = transforms.Compose([    transforms.ToTensor(),])train_dataset = datasets.ImageFolder('path/to/train', transform=train_transforms)train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=32, shuffle=True)# 计算均值和标准差mean = 0.std = 0.nb_samples = 0.for data, _ in train_loader:    batch_samples = data.size(0)    data = data.view(batch_samples, data.size(1), -1)    mean += data.mean(2).sum(0)    std += data.std(2).sum(0)    nb_samples += batch_samplesmean /= nb_samplesstd /= nb_samplesprint('mean:', mean)print('std:', std)

      net.train()的作用 

      在深度学习中,神经网络的训练是指通过反向传播算法来优化网络中的参数,以最小化损失函数并提高模型的预测准确性。在PyTorch中,net.train()是一个方法,用于设置神经网络处于训练模式。

      当调用net.train()方法时,PyTorch会启用一些特性,例如Dropout和Batch Normalization,在训练期间对输入数据进行处理,以增强网络的泛化能力和稳健性。此外,这也会告诉PyTorch在反向传播时计算梯度,以便更新模型的参数。

      需要注意的是,在测试或评估阶段,应该使用net.eval()方法,以便禁用这些特性并确保网络处于评估模式。

      net.eval()的作用 

      在深度学习中,神经网络的测试或评估是指使用已训练好的模型对新数据进行预测,以评估模型的性能和准确性。在PyTorch中,net.eval()是一个方法,用于设置神经网络处于评估模式。

      当调用net.eval()方法时,PyTorch会禁用一些特性,例如Dropout和Batch Normalization,以确保网络对输入数据的处理是稳定和确定性的。此外,这也会告诉PyTorch不要计算梯度,以避免浪费计算资源。

      需要注意的是,在训练阶段,应该使用net.train()方法,以便启用这些特性并确保网络处于训练模式。

      2.3 predict.py

      # 导入包import torchimport torchvision.transforms as transformsfrom PIL import Imagefrom model import LeNet# 数据预处理transform = transforms.Compose(    [transforms.Resize((32, 32)), # 首先需resize成跟训练集图像一样的大小     transforms.ToTensor(),     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])# 导入要测试的图像(自己找的,不在数据集中),放在源文件目录下im = Image.open('img.png')im = transform(im)  # [C, H, W]im = torch.unsqueeze(im, dim=0)  # 对数据增加一个新维度,因为tensor的参数是[batch, channel, height, width]# 实例化网络,加载训练好的模型参数net = LeNet()net.load_state_dict(torch.load('Lenet.pth'))# 预测classes = ('plane', 'car', 'bird', 'cat',           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')net.eval()with torch.no_grad():    outputs = net(im)    predict = torch.max(outputs, dim=1)[1].data.numpy()print(classes[int(predict)])

      2.4 modelView.py

      # 使用netron进行模型可视化import netron# 模型的路径modelPath = "LeNetModel.onnx"# 启动模型netron.start(modelPath)

      三、使用LeNet模型进行项目训练

      3.1训练手写数字识别

          手写数字识别是一个非常简单的练习,但是对于我这个新手来说依然充满着挑战。下面我们具体的实现一下手写数字识别的相关项目。

      首先手写数字的训练图片采用的是28X28的。所以根据实际的需要,模型结构中的输入和输出需要进行调整。具体如下所示。

      class LeNetDemo1(nn.Module):# 继承于nn.Module这个父类    def __init__(self):        super(LeNetDemo1, self).__init__()        self.conv = nn.Sequential(#input (N,1,28,28)            nn.Conv2d(1, 6, 5),  #output(N,6,24,24)第一层卷积            nn.ReLU(),            nn.MaxPool2d(2, 2),  #output(N,6,12,12)第二层池化            nn.Conv2d(6, 16, 5), #output(N,16,8,8)第三层卷积            nn.ReLU(),            nn.MaxPool2d(2, 2)   #output(N,16,4,4) 第四层池化        )        self.fc = nn.Sequential(            nn.Linear(16 * 4 * 4, 120),#output(N,120) 第五层全连接            nn.ReLU(),            nn.Linear(120, 84),#output(N,84) 第六层全连接            nn.ReLU(),            nn.Linear(84, 10)#output(N,10) 第七层全连接        )    def forward(self, img):  # 这里定义前向传播的方法,为什么没有定义反向传播的方法呢?这其实就涉及到torch.autograd模块了,        #img = img.unsqueeze(1)# (batch_size, 1, 28, 28)        feature = self.conv(img)        # 把120张大小为1的图像当成一个长度为120的一维tensor?        # .view( )是一个tensor的方法,使得tensor改变size但是元素的总数是不变的。        #  第一个参数-1是说这个参数由另一个参数确定, 比如矩阵在元素总数一定的情况下,确定列数就能确定行数。        #  那么为什么这里只关心列数不关心行数呢,因为马上就要进入全连接层了,而全连接层说白了就是矩阵乘法,        #  你会发现第一个全连接层的首参数是16*5*5,所以要保证能够相乘,在矩阵乘法之前就要把x调到正确的size        # 更多的Tensor方法参考Tensor: http://pytorch.org/docs/0.3.0/tensors.html        output = self.fc(feature.view(img.shape[0], -1))        return output

       然后是训练部分的代码,如下所示。

      #对模型的训练过程进行代码注释import torchimport torchvisionimport torch.nn as nnfrom model import LeNet, LeNetDemo1import torch.optim as optimimport torchvision.transforms as transformsimport matplotlib.pyplot as pltimport numpy as npimport time# Compose将多个步骤组合在一起transform = transforms.Compose([    transforms.ToTensor(),    transforms.Resize((28,28)),    transforms.Normalize((0.1307), (0.3081))])# 导入60000张训练图片train_set = torchvision.datasets.MNIST(root='./data',  # 数据集存放目录 train=True, # 表示是数据集中的训练集                                        download=True,   # 第一次运行时为True,下载数据集,下载完成后改为False                                        transform=transform) # 预处理过程# 加载训练集,实际过程需要分批次(batch)训练train_loader = torch.utils.data.DataLoader(train_set,   # 导入的训练集  batch_size=100, # 每批训练的样本数                                          shuffle=False,  # 是否打乱训练集                                          num_workers=0)  # 使用线程数,在windows下设置为0# 10000张验证图片# 第一次使用时要将download设置为True才会自动去下载数据集val_set = torchvision.datasets.MNIST(root='./data', train=False,                                       download=True, transform=transform)val_loader = torch.utils.data.DataLoader(val_set, batch_size=1000,                                         shuffle=False, num_workers=0)val_data_iter = iter(val_loader)val_image, val_label = next(val_data_iter)device = torch.device("cuda" if torch.cuda.is_available() else "cpu")print(device)net = LeNetDemo1()#导入模型网络net.to(device) # 将网络分配到指定的device中loss_function = nn.CrossEntropyLoss()#定义损失函数optimizer = optim.Adam(net.parameters(), lr=0.001)#定义优化器,nets.parameters()是指网络中的参数,lr是学习率#画图用的变量train_loss = []#训练集的损失集合train_acc= []#训练集的准确率集合test_acc = []#测试集的准确率集合epochs=4#训练的轮数for epoch in range(epochs):#训练的轮数    net.train()#训练模式    running_loss = 0.0#训练集的损失    time_start = time.perf_counter()#计时开始    for step, data in enumerate(train_loader, start=0):#step是训练集的批次,data是训练集的数据        inputs, labels = data#inputs是训练集的图片,labels是训练集的标签        #print(f"labels.size(0)的值是{labels.size(0)}")        #print(f"step的值是{step}")        optimizer.zero_grad()#梯度清零        outputs = net(inputs.to(device))  # 将inputs分配到指定的device中        loss = loss_function(outputs, labels.to(device))  # 将labels分配到指定的device中        loss.backward()#反向传播        optimizer.step()#更新参数        running_loss += loss.item()#训练集的损失        y_hat= net(inputs.to(device))        train_accuary=(y_hat.argmax(dim=1) == labels.to(device)).sum().item()/labels.size(0)#训练集的准确率        if step % 100 == 99:            with torch.no_grad():#不计算梯度                net.eval()#验证模式                outputs = net(val_image.to(device)) # 将test_image分配到指定的device中                predict_y = torch.max(outputs, dim=1)[1]#取出最大值的索引                accuracy = (predict_y == val_label.to(device)).sum().item() / val_label.size(0) # 将test_label分配到指定的device中                print('[%d, %5d] train_loss: %.3f train_acc:%.3f test_accuracy: %.3f' %                      (epoch + 1, step + 1, running_loss/100,train_accuary, accuracy))                if step+1==600:                    train_loss.append(running_loss/100)                    test_acc.append(accuracy)                    train_acc.append(train_accuary)#训练过程中的准确率                print('%f s' % (time.perf_counter() - time_start))                running_loss = 0.0print('Finished Training')save_path = './Lenet.pth'torch.save(net.state_dict(), save_path)print(len(train_loss),len(test_acc),len(train_acc))#绘制图形index=[i for i in range(1,epochs+1)]plt.plot(index,train_loss,'g',label="train_loss")plt.plot(index,train_acc,'b',label="train_acc")plt.plot(index,test_acc,'m',label="test_acc")plt.xlabel("epoch")plt.legend()plt.pause(0.01)  # 暂停0.01秒
      for step, data in enumerate(train_loader, start=0)的作用

       这是一行Python代码,用于在训练神经网络时从训练数据集中加载批次数据。

      train_loader是一个PyTorch的DataLoader对象,用于迭代训练数据集中的批次数据。 enumerate(train_loader, start=0) 是Python内置函数enumerate()的调用,它会返回一个迭代器,该迭代器会生成一个由(index, data) 元组组成的序列,其中index表示当前批次数据的索引(从0开始),data表示当前批次数据本身。 for step, data in enumerate(train_loader,start=0) 是一个for循环,用于遍历enumerate()生成的迭代器中的每一个(index, data)元组。 在每一次迭代中,step会被设置为当前批次数据的索引,data会被设置为当前批次数据本身。 这样,我们就可以对每一个批次数据进行处理或训练了。

      第一次使用时要将download设置为True才会自动去下载数据集val_set = torchvision.datasets.MNIST(root='./data', train=False,                                       download=True, transform=transform)val_loader = torch.utils.data.DataLoader(val_set, batch_size=1000,                                         shuffle=False, num_workers=0)的作用
      这段代码使用了 PyTorch 中 torchvision.datasets 模块中的 MNIST 数据集。MNIST 是一个手写数字数据集,包含了许多手写数字图片及其对应的标签。这里使用了 MNIST 中的验证集,即测试集。参数 train=False 表示加载测试集,download=True 表示如果本地没有下载过该数据集,将自动下载到指定的 root 目录下。transform 是对数据集进行预处理的方法,这里使用了一个名为 transform 的变量,可能是事先定义好的数据处理操作,比如将数据转换成 Tensor 格式并进行归一化等操作。之后,将 val_set 传给了一个 DataLoader,用于分批次读取数据。参数 batch_size=1000 表示每次读取的数据批次大小为 1000,shuffle=False表示不打乱数据集顺序,num_workers=0 表示使用主线程读取数据集。
      # 导入50000张训练图片train_set = torchvision.datasets.MNIST(root='./data',   # 数据集存放目录                               train=True,       # 表示是数据集中的训练集                                        download=True,      # 第一次运行时为True,下载数据集,下载完成后改为False                                        transform=transform) # 预处理过程# 加载训练集,实际过程需要分批次(batch)训练train_loader = torch.utils.data.DataLoader(train_set,    # 导入的训练集                                 batch_size=28, # 每批训练的样本数                                          shuffle=False,  # 是否打乱训练集                                          num_workers=0)  # 使用线程数,在windows下设置为0
      这段代码是用来加载 MNIST 数据集的训练集。MNIST 数据集是一个手写数字图像数据集,共有 60000 张训练图片和 10000 张测试图片,每张图片都是 28x28 的灰度图像。torchvision.datasets.MNIST() 函数会返回一个 MNIST 对象,该对象代表 MNIST 数据集中的训练集或测试集。其中的参数 root 是数据集存放的目录,train 表示是否加载训练集,download 表示是否下载数据集,transform 是对每张图片进行的预处理操作。在这里,我们使用了一个叫做 transform 的对象对图片进行预处理。torch.utils.data.DataLoader() 函数用于将数据集分批次读入。其中的参数 train_set 是我们加载的训练集对象,batch_size 表示每批次训练的样本数,shuffle 表示是否打乱训练集,num_workers 表示使用的线程数。在这里,我们将每批次的样本数设为 28,不打乱训练集,线程数设为 0(在 Windows 系统下需要这样设置)。

      训练10轮之后的结果如下

       从训练结果来看,这个训练的结果还是比较不错,现在我们就开始用自己写的数字图片然后进行预测。

      预测代码的实现。

      from PIL import Image, ImageOpsimport torchvision.transforms as transformsimport torchfrom model import LeNet, LeNetDemo1if __name__ == '__main__':    # 实例化模型    model = LeNetDemo1()    # 损失函数    # 加载模型的参数    model.load_state_dict(torch.load("Lenet.pth"))    # 切换为测试状态    model.eval()    img_path = "data/draw.jpg"    # 转换图片    image = Image.open(img_path).convert('L')    image = ImageOps.invert(image)    transform = transforms.Compose([        transforms.Resize((28, 28)),        transforms.ToTensor(),        transforms.Normalize([0.1307], [0.3081])    ])    image = transform(image)    #image = image.unsqueeze(0)    # transforms.ToPILImage()(image.squeeze(0)).save(img_path)    with torch.no_grad():        output = model(image)        # print(output.squeeze(0))        prediction = output.argmax(dim=1, keepdim=True)        print("预测结果:", prediction.item())

       为了更好的实现界面的美观划,我借用了一些界面设计进行项目的展示,具体如图所示。

       写在了最后

      本次的源代码我将会放置在casn的资源中,但是请不要下载这个资源,资源我将会设置文件压缩密码,请不要下载资源。由于本人的水平暂时有限,所以代码存在的一定的问题。暂时不做开源处理。计划后期个人水平提升之后会进行项目开源,敬请期待,感谢。

      源码地址:https://download.csdn.net/download/qq_41221411/87614467

      参考文献 

      经典CNN之:LeNet介绍_Sheldon_King的博客-CSDN博客

      卷积神经网络(CNN)LeNet-5详解_分辨率为32的图像如何卷积_王阿宅-自定义的博客-CSDN博客

      CNN经典网络(1)---LeNet(模型搭建+训练+预测+可视化)_balabalaba5的博客-CSDN博客

      卷积神经网络模型之——LeNet网络_3层卷积神经网络_Bryant327的博客-CSDN博客

      Pytorch学习(四)—— Lenet模型训练_HelloWorldQAQ。的博客-CSDN博客

      LeNet无需调参即可提高精准度并且降低损失,超过AlexNet_lenet 调参_小刘研CV的博客-CSDN博客

      Python四行代码实现的猜数字小游戏,基于thinker,带GUI界面_程序员柳的博客-CSDN博客

      飞桨PaddlePaddle-源于产业实践的开源深度学习平台

      基于四层神经网络的MNIST手写数字识别(GUI-tkinter)_斗宿三的博客-CSDN博客

上一篇:JS面试题:hash和history的区别

下一篇:微信小程序群接龙启动团购发货通知全攻略,轻松掌握接龙神器,开启便捷团购生活!