计算机视觉专栏(2)【LeNet】代码实战【pytorch】完整可运行

news2024/10/28 22:59:19

请添加图片描述

LeNet 系列

  • 实践部分
  • 1.引言
  • 2. limu代码
  • 3. plpal代码
    • 3.1 代码调试
    • 3.2 代码详解
  • 4. 总结

实践部分

Lenet的实现分为两种代码,一种是李沐老师的实现代码以及b友up霹雳啪啦的代码,两者都有不同的优点,李老师的lenet十分还原原著中的操作,差异化比较小,并且使用fashion_mnist数据集,但其缺点也比较明显,较多的使用了团队封装的包,对初学者只能看到简单的流程。霹雳啪的代码则是结合了pytorch官网中对这个lenet的实现相对而言是一个完整且清晰的项目,都是借助官网代码进行完成。所以我在这里对两种代码的解决方式进行讲解, 便于各位参考学习。

李老师的代码地址https://zh.d2l.ai/chapter_convolutional-neural-networks/lenet.html
劈啦啪同学的地址 https://github.com/2578562306/deep-learning-for-image-processing/blob/master/pytorch_classification/Test1_official_demo/model.py

1.引言

LeNet,这是最早期的卷积神经网络之一,它由 AT&T 贝尔实验室的研究员 Yann LeCun 在 1989 年提出,并因其在计算机视觉任务中的卓越性能而广受关注。LeNet 的设计初衷是用于识别图像中的手写数字(LeCun et al., 1998),并标志着使用反向传播算法成功训练卷积神经网络的重要突破,代表了神经网络研究发展的多年成果。
LeNet 在当时展现出了与支持向量机(SVM)相媲美的性能,很快成为了监督学习任务中的主流方法。特别是,在自动取款机(ATM)中的应用,LeNet 极大地提升了处理支票识别的效率。走到今天,一些 ATM 机仍在使用上世纪 90 年代 Yann LeCun 和他的同事 Leon Bottou 编写的代码进行操作!
随着机器学习技术的不断进步,尤其是在处理图像数据方面,相比于传统的线性模型,如 softmax 回归和多层感知机,卷积神经网络能够更好地处理图像中的空间结构信息。使用卷积层而非全连接层不仅使得模型结构更为简洁,还大幅降低了模型所需的参数数量,从而提高了模型的训练与运行效率。
总之,LeNet 的开发和应用不仅推动了计算机视觉领域的发展,还对整个人工智能领域产生了长远的影响。

下图将李老师的这一代码进行说明
定义了一个使用PyTorch框架的卷积神经网络Lenet的网络架构,适用于类似于图像识别的任务。下面是逐行的解释和注释:

2. limu代码

import torch
from torch import nn
from d2l import torch as d2l # 这个是动手深度学习这本书使用的包,可能是李老师团队的其中都是对基础的基础进行封装

# 定义一个顺序模型
net = nn.Sequential(
    # 第一层卷积层,输入通道为1(灰度图),***********************************8注意下这里原文原文用到的也是灰度图
    # 输出通道为6,使用5x5的卷积核,边缘填充2个像素
    nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),
    
    # 第一个池化层(平均池化),卷积核大小为2x2,步长为2
    nn.AvgPool2d(kernel_size=2, stride=2),
    
    # 第二层卷积层,输入通道为6,输出通道为16,使用5x5的卷积核
    nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
    
    # 第二个池化层(平均池化),卷积核大小为2x2,步长为2
    nn.AvgPool2d(kernel_size=2, stride=2),
    
    # 展平操作,将三维的特征图展平成一维的向量
    nn.Flatten(),
    
    # 全连接层,输入维度为16 * 5 * 5(来自上一层的输出),输出维度为120
    nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
    
    # 另一个全连接层,输入维度为120,输出维度为84
    nn.Linear(120, 84), nn.Sigmoid(),
    
    # 最后一个全连接层,输入维度为84,输出维度为10(分类的类别数,例如MNIST手写数字识别为10)
    nn.Linear(84, 10)
)

这个网络结构就是limu团队定义的lenet卷积神经网络,适用于基础的图像处理任务,如手写数字识别。每一层的组件都是为了从输入图像中抽取空间特征,并逐层递减空间维度,同时增加抽象层次的复杂性,直到最后通过全连接层进行分类。使用nn.Sigmoid()作为激活函数可以帮助非线性地转化特征。可以看出来和论文精讲部分还是存在差异,激活函数的差异,以及最后使用的一个正常的卷积层,没像论文中提到的卷C3种一样的操作。其实也是现阶段计算机性能的增强产物。

比较引人注意的就是这里采用了一个nn.Sequential()和其采用的这样的卷积写法,激活函数放在卷积层后面,看起来比较整齐,对这个内容感兴趣的读者可以点这里补补课程

X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32) # 随机生成规定大小的数据
for layer in net: # 遍历网络的层
    X = layer(X) # 将生成的数据送入到网络中,然后修改数据X内容,为了下次遍历网络的下一个层能够直接使用
    print(layer.__class__.__name__,'output shape: \t',X.shape) # 打印网络的输出形状

主要用于测试PyTorch定义的 nn.Sequential 网络模型的每一层的输出尺寸。该测试对于理解各层如何改变数据尺寸、调试和优化模型极为有用。以下是对该代码的详细解释:

代码解释

  • 初始化输入数据

    X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)
    

    这行代码生成了一个随机浮点张量 X,它的尺寸为 (1, 1, 28, 28),代表了一个批次大小为1、通道数为1(灰度图),尺寸为28x28像素的图像。这种尺寸对应于典型的手写数字识别图像,如MNIST数据集中的图像格式。

  • 遍历网络层

    for layer in net:
        X = layer(X)
        print(layer.__class__.__name__, 'output shape: \t', X.shape)
    

    这段循环代码逐层应用网络 net 中的每个层到输入张量 X 上,并打印出每层的类名和经过该层处理后的输出张量的形状。这有助于:

    • 理解数据流:通过查看每一层的输出,可以直观地看到数据如何在网络中流动和变换。
    • 调试和设计帮助:若某层的输出尺寸不符合预期,可能表明在网络设计中存在问题,如卷积层的填充(padding)或步长(stride)设置不当等。
    • 性能优化:有时候修改层的参数(例如减少卷积核数量或改变卷积核大小)可以在不影响网络性能的前提下,减少计算量和模型大小。

运行结果:
请添加图片描述

batch_size = 256 # 批次大小
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size) # 下载数据集并且做成了两个迭代器

可以看到还是采用了李老师数据的包,这段代码涉及到使用 d2l.load_data_fashion_mnist 函数从李沐老师所提供的 d2l (Dive into Deep Learning)工具包中加载 Fashion-MNIST 数据集,并将其组织成适用于训练和测试的迭代器。当然在实际的实践场景中,读者需要自行将数据集划分整理并且做成可迭代对象。

由于是封装好的迭代器对象,我对数据进行了展示各位可以看下原始数据集就是各种各样的衣服的类别啥的

import torch
from torchvision.utils import make_grid
import numpy as np
import matplotlib.pyplot as plt

# 假设 train_iter 是一个 DataLoader 实例,迭代提供批次数据
for x, y in train_iter:
    # x 的形状通常是 [批大小, 通道, 高度, 宽度]
    # 使用 torchvision 的 make_grid 创建一个网格图像
    grid_img = make_grid(x, nrow=19)  # nrow 是每行显示的图像数量
    
    # 将张量转换为 numpy 数组,适合 matplotlib 显示
    plt.imshow(grid_img.permute(1, 2, 0))  # 调整维度以适应 matplotlib 的显示要求
    plt.show()
    
    # 可以在这里添加 break 如果你只想显示第一个批次
    break

代码执行结果:

请添加图片描述

数据有了模型有了,可以开始训练了,看过我之前博文的小伙伴可以看奥这里很熟悉就是简单的训练代码,咱们一行一行走起来:

#@save
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
    """用GPU训练模型(在第六章定义)"""
    def init_weights(m):
        if type(m) == nn.Linear or type(m) == nn.Conv2d:
            nn.init.xavier_uniform_(m.weight)
    net.apply(init_weights)
    print('training on', device)
    net.to(device)
    optimizer = torch.optim.SGD(net.parameters(), lr=lr)
    loss = nn.CrossEntropyLoss()
    animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
                            legend=['train loss', 'train acc', 'test acc'])
    timer, num_batches = d2l.Timer(), len(train_iter)
    for epoch in range(num_epochs):
        # 训练损失之和,训练准确率之和,样本数
        metric = d2l.Accumulator(3)
        net.train()
        for i, (X, y) in enumerate(train_iter):
            timer.start()
            optimizer.zero_grad()
            X, y = X.to(device), y.to(device)
            y_hat = net(X)
            l = loss(y_hat, y)
            l.backward()
            optimizer.step()
            with torch.no_grad():
                metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
            timer.stop()
            train_l = metric[0] / metric[2]
            train_acc = metric[1] / metric[2]
            if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
                animator.add(epoch + (i + 1) / num_batches,
                             (train_l, train_acc, None))
        test_acc = evaluate_accuracy_gpu(net, test_iter)
        animator.add(epoch + 1, (None, None, test_acc))
    print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
          f'test acc {test_acc:.3f}')
    print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
          f'on {str(device)}')

逐行解释上述代码:
train_ch6 函数为啥这么命名呢,函数名 train_ch6 中的 ch6 通常代表 “Chapter 6”,这表明该函数是在《动手学深度学习》一书中第六章使用或定义的代码。很好的入门书籍,各位感兴趣可以看看

函数概览

#@save
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
  • net: 要训练的网络模型。
  • train_iter: 训练数据的迭代器。
  • test_iter: 测试数据的迭代器,用于评估模型性能。
  • num_epochs: 训练的轮数。
  • lr: 学习率。
  • device: 训练使用的设备(例如:"cuda""cpu")。

关键步骤和组件

  1. 权重初始化:

    def init_weights(m):
        if type(m) == nn.Linear or type(m) == nn.Conv2d:
            nn.init.xavier_uniform_(m.weight)
    net.apply(init_weights)
    

    使用Xavier均匀初始化对模型的线性层和卷积层权重进行初始化。这有助于保持网络各层激活值和梯度大小适中,促进稳定训练。

  2. 设置优化器和损失函数:

    optimizer = torch.optim.SGD(net.parameters(), lr=lr)
    loss = nn.CrossEntropyLoss()
    

    使用随机梯度下降(SGD)优化器和交叉熵损失函数,这是多分类问题中常用的组合。

  3. 动画显示训练进度:

    animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
                            legend=['train loss', 'train acc', 'test acc'])
    

    动态显示训练损失、训练精度和测试精度。人家用了自己的包。埋个坑有兴趣的dd我,我开一专栏讲解下

  4. 训练循环:

for epoch in range(num_epochs):
    # 训练损失之和,训练准确率之和,样本数
    metric = d2l.Accumulator(3) # 创建一个累加器来存储总损失、总准确率以及处理的总样本数,以计算平均损失和平均准确率。
    net.train() # 调整模型为训练模式,可以进行训练了
    for i, (X, y) in enumerate(train_iter): # 遍历训练数据迭代器,获得一批数据X和它们的标签y。
        timer.start() # 计时器开始
        optimizer.zero_grad() # 梯度信息清除
        X, y = X.to(device), y.to(device) #将数据放到设备上看你是cpu还是GPU
        y_hat = net(X) # 模型预测结果
        l = loss(y_hat, y) # 计算损失
        l.backward() # 计算梯度
        optimizer.step() # 优化其开始通过梯度信息更改参数
        with torch.no_grad(): # 在不计算梯度的情况下,加总损失和准确率,并计算总样本数。
            metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
        timer.stop() # 计时器结束
        train_l = metric[0] / metric[2] # 计算平均训练损失 (train_l) 和平均训练精度 (train_acc):
        train_acc = metric[1] / metric[2]
        if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
            animator.add(epoch + (i + 1) / num_batches,
                         (train_l, train_acc, None))
# 条件判断 if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1: 这行代码决定了何时更新动画显示的频率。通常我们不会每处理完一个 batch 就更新一次动画,因为那样会频繁地进行绘图操作,降低训练效率。这里设定的是每处理总批次数的五分之一时更新一次,或者在每个 epoch 的最后一个 batch 更新。这样可以在不影响性能的情况下较频繁地观察训练进度。

if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
    animator.add(epoch + (i + 1) / num_batches,
                 (train_l, train_acc, None))

这个代码还是很精美的可以使用到自己的模型中

每个周期(epoch)中,函数遍历训练数据集,每次迭代做如下操作:

  • 加载数据到指定设备。
  • 前向传播计算模型输出和损失。
  • 反向传播更新模型参数。
  • 计算并累积损失和精度。
  • 定期(每epoch的五分之一处)使用 animator 更新训练进度展示。
  1. 评估模型: 训练完后开始使用对模型进行测试

    test_acc = evaluate_accuracy_gpu(net, test_iter)
    

    测试函数是之前写好的,我们看下其内容;
    您的函数 evaluate_accuracy_gpu 是用于评估在给定数据集上,基于GPU运行的模型准确率的实用函数。这个函数将模型和数据迭代器作为输入,并输出模型的准确率。这里对函数的工作原理和关键部分提供一个详细的解释。

函数解释

def evaluate_accuracy_gpu(net, data_iter, device=None):
    """使用GPU计算模型在数据集上的精度"""
  • net:待评估的神经网络模型。
  • data_iter:提供数据的迭代器,通常包括特征和标签。
  • device:指定运行计算的设备,如 GPU。如果没有指明,则自动选择模型参数所在的设备。
    if isinstance(net, nn.Module):
        net.eval()  # 设置为评估模式
        if not device:
            device = next(iter(net.parameters())).device
  • 设置为评估模式 (eval()):这一步是为了确保在评估过程中模型的某些特定层(如Dropout和BatchNorm层)表现出与训练时不同的行为。
  • 自动设备选择:如果没有显式指定设备,函数会检测模型参数所在的设备,并将其作为计算设备。
    metric = d2l.Accumulator(2)  # 正确预测的数量,总预测的数量
  • 使用 d2l.Accumulator 实例来累积正确预测的数量和总的预测数量。这是一个简便的工具,用于在遍历数据时累积求和。
    with torch.no_grad():
  • 使用 torch.no_grad() 开启一个上下文管理器,以阻止PyTorch跟踪和存储计算图中的梯度信息,旨在减少内存的使用和提高计算速度,因为在评估模型时不需要进行反向传播。
        for X, y in data_iter:
            if isinstance(X, list):
                # BERT微调所需的(之后将介绍)
                X = [x.to(device) for x in X]
            else:
                X = X.to(device)
            y = y.to(device)
            metric.add(d2l.accuracy(net(X), y), y.numel())
  • 数据装置设置:将输入 X 和标签 y 移至指定的 device(GPU)上。
  • 计算准确率:通过 d2l.accuracy 计算当前批次的准确率,并更新到 metric
  • y.numel():返回 y 中元素的总数,即本批次的大小。
    return metric[0] / metric[1]
  • 计算总的正确率:将累积的正确预测数量除以总的预测数量,得到整个数据集上的平均准确率。
  1. 输出训练结果和性能:
    print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, test acc {test_acc:.3f}')
    print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec on {str(device)}')
    
    最后输出训练损失、训练精度、测试精度和处理速度(样本/秒)。
lr, num_epochs = 0.3, 100
train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu()) # 调用函数开始执行

运行结果:

请添加图片描述

https://zh.d2l.ai/chapter_convolutional-neural-networks/lenet.html#
这里是李老师的代码地址,顺序执行即可

3. plpal代码

下面开始对b友噼里啪啦的lenet实现代码进行讲解。下面我们将深入解析基于LeNet实现的代码。对于对这部分内容感兴趣的读者,您可以通过点击这里访问相关代码。值得注意的是,虽然这套代码的结构和李老师的版本相比可能不那么直观易用,但对于大型项目来说,将不同功能模块化是一种更为直接且易于维护的方式。显然,李老师的代码主要是为了帮助初学者快速入门。在您下载并准备运行这些代码之前,可能需要进行一些修改和调整。接下来,我们会详细查看主要的三个文件——modeltrainpredict,这些文件代表了深度学习项目中的传统三大核心组件。

请添加图片描述

3.1 代码调试

李老师的代码只要环境配置正确就能跑,这个代码还是需要调试下的。
在这里插入图片描述
将训练文件中的地址按照自己的地址进行修改,这行代码会从目标网址下载数据存入到你修改地址的文件夹中,用于模型的训练。修改好地址后要将这两个参数改成真True

在这里插入图片描述
模型训练后会对参数进行保存,便于验证时候使用,所以需要修改喜爱这里的地址,把参数保存到目标目录下:
在这里插入图片描述

然后点运行即可看到模型的训练结果如下:
请添加图片描述
模型训练好了,那么咱们怎么测试下咱们这个模型好不好好用用呢??
各位可以随便在网络上下载一个jpg的图像文件存到代码的文件夹下。我这里随便下载了一个车的图片:
当当当:xiaomi汽车预览下:
测试一下模型能不能识别出来,当然了你下载需要判断的图的时候要看下你训练用的数据集的类别存在不,因为我们任务中使用的数据集就是包含着几个类别请添加图片描述

请添加图片描述
然后就是修改代码中的文件位置了注意这次修改的是predict中的文件位置。
在这里插入图片描述
修改好后执行代码即可:但是但是,这个模型给识别成飞机了✈️了。咱们看下这个运行结果:
在这里插入图片描述
可以理解,因为确实数据集过于老旧,并且代码仅仅训练到了5个epoch,性能也是有限。况且咱们这小米汽车外形也是十分酷炫。感兴趣的读者可以自行改进下代码汇总的网络结果,和学习率以及epcoh数值看看能不能修正这一错误,本章节不再讨论,下面将对代码进行逐行的解释。

3.2 代码详解

上文中主要将对代码的调试过程,现阶段对代码进行逐行的精讲,便于读者理解每一部分存在的含义。首先看下模型的主体架构,即是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) # 输入通道是3和输出通道是16个
        self.pool1 = nn.MaxPool2d(2, 2) # 池化层
        self.conv2 = nn.Conv2d(16, 32, 5) # 从16个特征图输出成32个特征图
        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

看到以上代码,最直观的感受就是没用到快捷的nn.Sequential()当然那个不是我们这个章节需要讨论的,这个网络主体,先对比下和论文中的存在哪些区别呢,我们看下论文中的模型架构:
请添加图片描述

首先输入就存在差别,论文中的输入是一个13232 的数据,但是这个神经网络接受的通道数为3,首先明确下,这个是由数据训练数据决定的。传统的lenet采用训练图是灰度图,和李老师使用数据集类似。而plpal这个b友的则是彩色的图片数据集。所以三原色,三个通道,所以模型的输入是3个通道。即三种特征图。便于直观展示,给大家看下这个这个数据集的介绍。
请添加图片描述
都是彩色图片所以三种输入通道。输出的通道则是和原文是一致的,但是原文中使用的是均值池化,这个代码中则是采用的最大值池化,现阶段最大值池化用的比较多。还有一个不同的点实际上激活函数肯定是也和原文的不同,但是大概还是整体一致的。同样人家也没有使用论文中为了减少运算量奇怪的卷积层。

回顾完模型的主体代码现在可以看训练的代码了即是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 # 处理图片数据的 


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

    # 50000张训练图片
    # 第一次使用时要将download设置为True才会自动去下载数据集,下载数据集并且得到训练的迭代器
    train_set = torchvision.datasets.CIFAR10(root='/Users/wangyang/Desktop/计算机视觉技术/Lenet/data', train=True,
                                             download=True, transform=transform)
    train_loader = torch.utils.data.DataLoader(train_set, batch_size=36,
                                               shuffle=True, num_workers=0)

    # 10000张验证图片
    # 第一次使用时要将download设置为True才会自动去下载数据集,下载数据集并且得到验证的迭代器
    val_set = torchvision.datasets.CIFAR10(root='/Users/wangyang/Desktop/计算机视觉技术/Lenet/data', train=False,
                                           download=True, transform=transform)
    val_loader = torch.utils.data.DataLoader(val_set, batch_size=5000,
                                             shuffle=False, num_workers=0) 
                                             # 设置批大小和线程个数,以及各种迭代器需要使用的超参数,然后生成迭代器。使用的都是很常见的函数,具体的细节,读者可参考我的这个博文内容
                                             # 链接 https://blog.csdn.net/weixin_47332746/article/details/139290051
    val_data_iter = iter(val_loader) # 训练完后进行迭代测试
    val_image, val_label = next(val_data_iter)
    
    # classes = ('plane', 'car', 'bird', 'cat',
    #            'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

    net = LeNet()
    loss_function = nn.CrossEntropyLoss()
    optimizer = optim.Adam(net.parameters(), lr=0.001)

    for epoch in range(20):  # loop over the dataset multiple times

        running_loss = 0.0 # 初始化损失
        for step, data in enumerate(train_loader, start=0): # 便利训练数据
            # get the inputs; data is a list of [inputs, labels]
            inputs, labels = data # 将数据中的数据和label分开

            # zero the parameter gradients
            optimizer.zero_grad() # 梯度清0
            # forward + backward + optimize
            outputs = net(inputs) # 网络开始计算结果
            loss = loss_function(outputs, labels) # 计算损失
            loss.backward() # 计算梯度信息
            optimizer.step() # 使用优化器按照梯度对参数进行更新

            # print statistics
            running_loss += loss.item() # 累计梯度
            if step % 500 == 499:    # print every 500 mini-batches 每 500 个 mini-batches 进行验证
                with torch.no_grad(): # 不计算梯度信息
                    outputs = net(val_image)  # [batch, 10] # 使用验证集合进行验证
                    predict_y = torch.max(outputs, dim=1)[1] # 最大化概率
                    accuracy = torch.eq(predict_y, val_label).sum().item() / val_label.size(0) # 计算准确率

                    print('[%d, %5d] train_loss: %.3f  test_accuracy: %.3f' %
                          (epoch + 1, step + 1, running_loss / 500, accuracy))
                    running_loss = 0.0 # 打印准确率然后损失清0

    print('Finished Training')

    save_path = '/Users/wangyang/Desktop/计算机视觉技术/Lenet.pth'
    torch.save(net.state_dict(), save_path) # 将模型存起来


if __name__ == '__main__':
    main()

这个模型还是存在诸多的不完整比如,并没有采用模型的验证模式,以及模型并没有使用测试和验证的代码分开。

   for epoch in range(20):
       net.train()  # Ensure the network is in training mode
       # Training loop...
       
       # Validation phase
       net.eval()  # Set the network to evaluation mode
       with torch.no_grad(): 
           # Evaluate on the validation set
           # This part of the code handles the evaluation

当然一个调试代码也不能要求的过多,作为学习使用。将训练与验证代码分离,确保在验证阶段使用 net.eval() 以正确处理特殊层的行为,是提高模型评估准确性的关键。这不仅能更精确地反映模型对未见数据的泛化能力,还有助于避免过拟合以及其他可能的评估误差。尽管在训练循环中进行快速验证可以为模型调优提供即时反馈,但在重要的评估阶段应当严格执行标准的评估程序。

同样和上文一样我们也是可以通过函数对图片进行查看的,感兴趣的读者可以自行的试试调试下。给大家看看我的效果:
请添加图片描述

代码和李老师中使用的是一致的如下:
   val_loader = torch.utils.data.DataLoader(val_set, batch_size=200,
                                             shuffle=False, num_workers=0)
    val_data_iter = iter(val_loader)
    val_image, val_label = next(val_data_iter)
    
    # classes = ('plane', 'car', 'bird', 'cat',
    #            'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
    for x, y in val_data_iter:
        # x 的形状通常是 [批大小, 通道, 高度, 宽度]
        # 使用 torchvision 的 make_grid 创建一个网格图像
        grid_img = make_grid(x, nrow=19)  # nrow 是每行显示的图像数量
        
        # 将张量转换为 numpy 数组,适合 matplotlib 显示
        plt.imshow(grid_img.permute(1, 2, 0))  # 调整维度以适应 matplotlib 的显示要求
        plt.show()
        
        # 可以在这里添加 break 如果你只想显示第一个批次
        break

然后就是试着运行就可以了。我们继续看predict.py文件中的代码:

导入所需库

import torch
import torchvision.transforms as transforms
from PIL import Image
from model import LeNet
  • torch: PyTorch框架,深度学习库,用于构建神经网络。
  • torchvision.transforms: 辅助工具,用于进行图片的预处理。
  • Image: 从Pillow库导入,用于打开和处理图像文件。
  • LeNet: 从本地model文件导入的LeNet模型类。

定义main函数

def main():
  • main 函数是程序的入口,当脚本被直接运行时执行。

初始化图像转换器

transform = transforms.Compose([
    transforms.Resize((32, 32)),  # 将图像调整为32x32像素
    transforms.ToTensor(),  # 将图像转换为PyTorch张量
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # 归一化处理
])
  • 使用图像转换序列来预处理输入的图像数据。包括调整大小、转换为张量和归一化。归一化使用均值 (0.5, 0.5, 0.5) 和标准差 (0.5, 0.5, 0.5),这对于图像处理是很常见的,有助于模型更好地理解图像数据。

定义类别

classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
  • 定义类别元组,这些类别与CIFAR-10数据集相关,模型将使用这些类别为图像分类。

加载模型并导入权重

net = LeNet()  # 创建一个LeNet模型实例
net.load_state_dict(torch.load('/Users/wangyang/Desktop/计算机视觉技术/Lenet.pth'))
  • 加载预先训练好的网络权重。这些权重应该是用CIFAR-10或类似数据集预训练的。

图像处理并进行分类

im = Image.open('/Users/wangyang/Desktop/计算机视觉技术/Lenet/小米汽车.jpg')
im = transform(im)   # 应用上面定义的转换处理
im = torch.unsqueeze(im, dim=0)  # 增加一个维度,因为模型期望批次的维度 [N, C, H, W]
  • 加载本地图像文件,应用转换,然后增加一个批次的维度以配合网络输入。

预测和输出结果

with torch.no_grad():  # 不计算梯度
    outputs = net(im)  # 运行模型进行预测
    predict = torch.max(outputs, dim=1)[1].numpy()  # 取得预测结果的分类索引
print(classes[int(predict)])  # 输出预测的类别名称
  • 使用torch.no_grad()表示在此代码块中不记录梯度信息,这是因为在预测模式下不需要反向传播。然后通过模型获得输出,并找到预测类别的索引,最后输出类别名。
if __name__ == '__main__':
    main()
  • 这是Python的常规入口点,if __name__ == '__main__': 确保当脚本作为主程序运行时调用 main() 函数,而不是在被其他脚本导入时执行。

4. 总结

本文对比了李沐老师的LeNet实现和b友噼里啪啦的实现,展示了两种不同风格的代码结构及其适用场景。李沐老师的版本面向初学者,使用自定义的d2l库简化数据处理和训练流程,侧重于教学和易理解性,适合快速掌握基础概念。而噼里啪啦的实现则更接近实际应用,采用模块化的代码组织方式,直接应用PyTorch原生操作,更适合有一定基础的开发者深入学习和项目开发。这两种实现各有优点,选择时应考虑个人学习阶段和目标:初学者或基础理解者适合从李老师的代码学起,而希望进行实际应用或深化理解的开发者则可从劈啦啪的实践代码入手。这样的对比和选择有助于根据个人需求进行有效学习,并为将来的深入使用和开发打下坚实的基础。下一个章节将会对Alexnet进行论文精度和代码复现,感兴趣的读者可以欢迎催更讨论加油。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2225756.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ios 项目升级极光SDK

由于项目使用的是旧版本,隐私合规检查不通过,需要升级到最新版本, 使用cocoapods集成无法正常运行,.a文件找不到,可能项目比较久了,最好选择手动导入 下载最新版本SDK,将 SDK 包解压&#xff…

IROS 2024最新接收的Motion Planning前沿研究成果汇总

No.1 文章标题:Extended Tree Search for Robot Task and Motion Planning 作者:REN, Tianyu; Chalvatzaki, Georgia; Peters, Jan 中文标题:机器人任务和运动规划的扩展树搜索 No.2 文章标题:Kinodynamic Motion Planning fo…

Jmeter分布式性能测试细节+常见问题解决

🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 Jmeter分布式测试时需要的特别注意 1)参数化文件的位置和内容 如果使用csv文件进行参数化,即通过读取csv文件中的数据来为测试脚本提供…

C/C++每日一练:实现选择排序

选择排序 选择排序是一种简单直观的排序算法,时间复杂度为,其中 n 是数组长度,不适合大数据集的排序,适合于元素较少且对性能要求不高的场景。 选择排序的基本思想是:每次从未排序部分选择最小的元素,将其放…

[四轴飞行器] 遥控器操作说明

遥控器操作说明 1 A:无线连接信号强度:已连接 B:控制模式:H定高模式,T定点模式 C:遥控器状态:加锁 D:飞行模式:无头 E:电量显示:遥控器电量(加…

OpenCV系列教程六:信用卡数字识别、人脸检测、车牌/答题卡识别、OCR

文章目录 一、信用卡数字识别1.1 模板匹配1.2 匹配多个对象1.3 处理数字模板1.4 预处理卡片信息,得到4组数字块。1.5 遍历数字块,将卡片中每个数字与模板数字进行匹配 二、人脸检测2.1人脸检测算法原理2.2 OpenCV中的人脸检测流程 三、车牌识别3.1 安装t…

Jupyter Notebook 中使用render_notebook渲染pyecharts图像不显示的一种情况

一开始我发现自己的jupyter文件在渲染pyecharts图片时一开始可以显示,但后来不知道怎么的就不显示了,查找了很多方法,但是没有效果,都是改js渲染什么的,还有就是参数不对的,对于我来说都没什么用&#xff0…

Pytorch学习--DataLoader的使用

一、DataLoader简介 DataLoader官网 重要参数:画红框的参数 dataset: 作用:表示要加载的数据集。DataLoader通过该参数从数据集中读取数据。类型:Dataset,即PyTorch定义的Dataset类,用于封装数据并提供数据索引的功…

C++第八讲:STL--stack和queue的使用及模拟实现

C第八讲:STL--stack和queue的使用及模拟实现 1.stack的使用2.queue的使用3.栈和队列OJ题3.1题目1:最小栈3.2题目2:栈的压入、弹出序列3.3题目3:逆波兰表达式求值3.4题目4:用栈实现队列 4.栈的模拟实现5.队列的模拟实现…

BFS解决最短路问题(4)_为高尔夫比赛砍树

个人主页:C忠实粉丝 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C忠实粉丝 原创 BFS解决最短路问题(4)_为高尔夫比赛砍树 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记,欢迎大家在评论区交流讨论&#x1f48…

LeetCode-684. 冗余连接

. - 力扣(LeetCode) 题目 树可以看成是一个连通且 无环 的 无向 图。 给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于…

传输层UDP

再谈端口号 端口号:标识了主机上进行通信的不同的应用程序 在TCP/IP 协议中我们用“源IP”"源端口号" “目的IP”“目的端口号” “协议号”五元组来标识一个通信 用netstat -n 查看 查看网络信息,我们有两种命令查看网络通信1.用netsta…

Python | Leetcode Python题解之第509题斐波那契数

题目&#xff1a; 题解&#xff1a; class Solution:def fib(self, n: int) -> int:if n < 2:return nq [[1, 1], [1, 0]]res self.matrix_pow(q, n - 1)return res[0][0]def matrix_pow(self, a: List[List[int]], n: int) -> List[List[int]]:ret [[1, 0], [0, …

1 环境配置、创建功能包、编译、Cmake文件及package文件学习笔记

1 基本结构 放张 赵虚左老师的pdf截图 2 环境配置 //每次都需配置 . install/setup.bash//或者一次配置echo "source /path/to/your/workspace_name/install/setup.bash" >> ~/.bashrcsource ~/.bashrc3 创建功能包 ros2 pkg create 包名--build-type 构建类…

气象监测软件的程序设计

老师留了个作业&#xff0c;感觉挺有意思&#xff0c;记录一下 文章目录 气象监测软件的程序设计项目指导书&#xff08;一&#xff09;基本信息&#xff08;二&#xff09;项目目标&#xff08;三&#xff09;任务描述&#xff08;四&#xff09;指导内容任务 1&#xff1a;根…

电磁干扰(EMI)与电磁兼容性(EMC)【小登培训】

电磁干扰&#xff08;EMI&#xff09;和电磁兼容性&#xff08;EMC&#xff09;是每个产品在3C &#xff0c;CE认证过程中必不可少的测试项目&#xff1a; 一、电磁干扰&#xff08;EMI&#xff09; EMI&#xff08;Electromagnetic Interference&#xff09;是指电子设备在工作…

ARM学习(33)英飞凌(infineon)PSOC 6 板子学习

笔者来聊一下psoc62 系列板子的知识 1、PSOC62板子介绍 Psoc6-evaluationkit-062S2 与RT-Thread联合推出的一款32位的双core的板子&#xff0c;基于CortexM4以及CortexM0。 管脚兼容Arduio。板载DAP-Link&#xff0c;可以支持调试以及串口&#xff0c;无需外接2MB的Flash以及…

JavaEE初阶---文件IO总结

文章目录 1.文件初识2.java针对于文件的操作2.1文件系统的操作---file类2.2文件内容的操作---流对象的分类2.4字符流的操作》文本文件2.4.1异常的说明2.4.2第一种文件内容的读取方式2.4.3第二种读取方式2.4.4close的方法的介绍2.4.5close的使用优化操作2.4.6内容的写入 2.3字节…

数据结构与算法汇总整理篇——数组与字符串双指针与滑动窗口的联系学习及框架思考

数组 数组精髓&#xff1a;循环不变量原则 数组是存放在连续内存空间上的相同类型数据的集合&#xff0c;通过索引(下标)访问元素&#xff0c;索引从0开始 随机访问快(O(1)时间复杂度)&#xff1b;插入删除慢(需要移动元素)&#xff1b;长度固定(部分语言中可动态调整) 其存…

【CSS】边界三角形

有三角形 Unicode 字符。您可以在 SVG 中绘制三角形。但还有另一种在 Web 上绘制三角形的方法&#xff0c;只需使用 border 属性和一些 CSS 技巧即可。 想象一个具有粗边框的元素&#xff1a; .triangle {width: 200px;height: 200px;border: 10px solid black; }现在想象一下…