LeNet 论文精读 | 深度解析+PyTorch代码复现(下)

在这里插入图片描述

B站视频讲解: 深度学习入门篇:使用pytorch搭建LeNet网络并代码详解实战
上篇文章讲解了LeNet的具体细节:深度学习入门篇–来瞻仰卷积神经网络的鼻祖LeNet

这次给大家带来卷积神经网络入门级网络LeNet的代码详解,并一步步的实现,并给同学们总结出pytorch的代码套路,当你在使用神经网络的时候,直接套用代码模版就可以直接训练了

废话不多说,开整!

LeNet 作为卷积神经网络的开山之作,奠定了卷积神经网络的基本结构,包括了卷积,池化,全连接三大件

从代码的角度来讲,包含了 model.py train.py 以及 predict.py 三个python文件

model.py

我们先从第一步,设计卷积神经网络结构开始,也就是model.py文件,用来定义网络结构

class LeNet(nn.Module):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.conv1 = nn.Conv2d(3,6,5,1,0)
        self.pool1 = nn.MaxPool2d(2,2,0)
        self.conv2 = nn.Conv2d(6, 16, 5, 1, 0)
        self.pool2 = nn.MaxPool2d(2,2,0)
        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))
        x = self.pool1(x)
        x = F.relu(self.conv2(x))
        x = self.pool2(x)
        x = x.view(-1,32*5*5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

类定义:class LeNet(nn.Module)

这行定义了一个继承自 nn.Module 的子类 LeNet,这意味着我们可以通过 LeNet 创建神经网络实例,并使用 PyTorch 的模块化特性来构建和训练模型。

__init__ 方法:

__init__ 是模型的构造函数,在实例化 LeNet 类时会调用。它负责定义网络的结构。

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
  • super().__init__(*args, **kwargs) 调用了父类 nn.Module 的构造函数,这样 LeNet 类就继承了 PyTorch Module 的一些基础功能,比如参数管理、梯度计算等。

接下来,我们看到具体的层定义:

self.conv1 = nn.Conv2d(3, 6, 5, 1, 0)
self.pool1 = nn.MaxPool2d(2, 2, 0)
self.conv2 = nn.Conv2d(6, 16, 5, 1, 0)
self.pool2 = nn.MaxPool2d(2, 2, 0)
self.fc1 = nn.Linear(32 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
  1. self.conv1 = nn.Conv2d(3, 6, 5, 1, 0)

    • nn.Conv2d 定义了一个二维卷积层。
    • 参数:
      • 3:输入通道数(例如,RGB图像有3个通道)。
      • 6:输出通道数(卷积层将生成6个特征图)。
      • 5:卷积核的大小为 5x5。
      • 1:步长为1,即卷积核每次移动一个像素。
      • 0:填充为0,即不对输入数据进行零填充(padding)。
  2. self.pool1 = nn.MaxPool2d(2, 2, 0)

    • nn.MaxPool2d 定义了一个最大池化层。
    • 参数:
      • 2:池化窗口的大小为 2x2。
      • 2:池化步长为2,每次移动两个像素。
      • 0:不使用填充。
  3. self.conv2 = nn.Conv2d(6, 16, 5, 1, 0)

    • 这是另一个卷积层。输入通道是 6(来自 conv1 的输出),输出通道是 16,卷积核大小仍然是 5x5,步长和填充都为 1 和 0。
  4. self.pool2 = nn.MaxPool2d(2, 2, 0)

    • 第二个池化层,参数与 pool1 相同。
  5. self.fc1 = nn.Linear(32 \* 5 \* 5, 120)

    • nn.Linear 定义了一个全连接层。

    • 输入特征的数量是

      32 * 5 * 5
      

      ,这个值是根据卷积层和池化层的输出大小计算的。

      • 卷积后,输入图像大小会变小,所以我们需要计算卷积后图像的尺寸。假设输入图像是32x32像素,经过两次 5x5 的卷积和两次 2x2 的池化后,尺寸大约为 5x5。
    • 输出的节点数是 120

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

    • 第二个全连接层,输入是 120 个节点,输出是 84 个节点。
  7. self.fc3 = nn.Linear(84, 10)

    • 最后的全连接层,输入是 84 个节点,输出是 10 个节点。这里的 10 是为了分类任务(例如 MNIST 数据集,10 个类别)。

forward 方法:

forward 方法定义了前向传播过程,即数据如何通过网络层进行处理。

def forward(self, x):
    x = F.relu(self.conv1(x))
    x = self.pool1(x)
    x = F.relu(self.conv2(x))
    x = self.pool2(x)
    x = x.view(-1, 32 * 5 * 5)
    x = F.relu(self.fc1(x))
    x = F.relu(self.fc2(x))
    x = self.fc3(x)
    return x
  1. x = F.relu(self.conv1(x))
    • self.conv1(x):通过卷积层对输入 x 进行卷积运算。
    • F.relu(...):应用 ReLU 激活函数,激活函数通过将负值置为0来引入非线性。
  2. x = self.pool1(x)
    • 对卷积后的结果 x 进行池化,减少空间维度(下采样)。
  3. x = F.relu(self.conv2(x))
    • 第二个卷积层及 ReLU 激活。
  4. x = self.pool2(x)
    • 第二次池化。
  5. x = x.view(-1, 32 * 5 * 5)
    • x.view(-1, 32 * 5 * 5) 用于将 x 的形状展平。卷积和池化操作通常会降低数据的空间维度(宽度和高度),而我们需要将这些特征传递给全连接层。-1 表示自动计算该维度的大小,32 * 5 * 5 表示展平后的特征向量长度。
  6. x = F.relu(self.fc1(x))
    • 通过第一个全连接层,并应用 ReLU 激活。
  7. x = F.relu(self.fc2(x))
    • 通过第二个全连接层,并应用 ReLU 激活。
  8. x = self.fc3(x)
    • 最后通过第三个全连接层输出 10 个类别的预测值。
  9. return x
    • 返回模型的输出,通常是一个包含类别概率的向量。

train.py

这一部分主要分为, 下载训练和测试数据 , 用设计好的神经网络来进行训练, 以及用测试数据来进行测试一下看看效果如何

数据集介绍

torchvision.datasets.CIFAR10 是一个在计算机视觉领域广泛使用的数据集,专门用于图像分类任务。它是一个小型的图像数据集,包含了 10 类不同的物体,每一类有 6000 张图像。总共有 60,000 张图像,分为训练集和测试集。数据集中的每张图像都是 32x32 像素的彩色图像(RGB 图像),适合用来进行图像分类模型的训练与测试。

CIFAR-10 数据集的特点:

  • 图像大小:每张图像的分辨率是 32x32 像素。

  • 图像通道:每张图像是 RGB 彩色图像,因此每张图像有 3 个通道(红、绿、蓝)

  • 类别数量:共有 10 个类别,每个类别有 6,000 张图片。分类包括:

    classes = (‘plane’, ‘car’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’)

  • 训练集和测试集

    • 训练集包含 50,000 张图像(每个类别 5,000 张图像)
    • 测试集包含 10,000 张图像(每个类别 1,000 张图像)

数据集结构:

  • 训练集(train set):用于训练模型的数据。
  • 测试集(test set):用于评估模型性能的数据。
from random import shuffle
import torch
from model_official import LeNet
import torchvision
import torch.nn as nn
import torchvision.transforms as transforms
import torch.optim as optim
import torch.utils

def main():
    # 使用gpu进行计算
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    
    transform = transforms.Compose(
        [transforms.ToTensor(),
        transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))]
    )
    
    
    train_set = torchvision.datasets.CIFAR10(root="./data",train=True,
                                            download=True,transform=transform)
    train_loader = torch.utils.data.DataLoader(train_set,batch_size=36,
                                            shuffle=True,num_workers=0)
    test_set = torchvision.datasets.CIFAR10(root="./data",train=True,
                                            download=True,transform=transform)
    test_loader = torch.utils.data.DataLoader(test_set,batch_size=5000,
                                            shuffle=True,num_workers=0)
    
    dataiter = iter(test_loader)
    test_images, test_labels = next(dataiter)
    test_images = test_images.to(device)
    test_labels = test_labels.to(device)
    
    net = LeNet()
    net = net.to(device)
    # 损失函数
    loss_function = nn.CrossEntropyLoss()  
    loss_function = loss_function.to(device) 
    # 迭代器  optim 是pytorch的模块,其中Adam是一个优化算法
    optimizer = optim.Adam(net.parameters(), lr=0.001) 
    #训练轮数
    for epoch in range(5):
        
        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
            inputs = inputs.to(device)
            labels = labels.to(device)
            
            optimizer.zero_grad()
            outputs = net(inputs)
            loss = loss_function(outputs,labels)
            loss.backward()
            optimizer.step()
            
            running_loss += loss.item()
            if step % 500 == 499:
                with torch.no_grad():
                    outputs = net(test_images)
                    predict_y = torch.max(outputs, dim=1)[1]
                    accuracy = torch.eq(predict_y,test_labels).sum().item() /                               test_labels.size(0)
                    
                    print('[%d, %5d] train_loss: %.3f test_accuracy:%.3f' % 
                          (epoch+1,step+1,running_loss / 500, accuracy))
                    running_loss = 0.0
    print("Finished Training")
    save_path = './Lenet.pth'
    torch.save(net.state_dict(), save_path)

if __name__ == '__main__':
    main()

这段代码是一个典型的使用 PyTorch 训练 LeNet 网络的完整流程,主要包括数据加载、模型训练、损失计算、优化、测试精度计算等步骤。下面我将逐行分析各个部分:

1. 选择设备:使用GPU或CPU

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  • 解释:这行代码用于检测是否有可用的GPU。如果有GPU,则使用GPU进行计算,否则回退到CPU。
  • torch.cuda.is_available():检查系统是否有可用的CUDA设备(即GPU)。
  • torch.device('cuda'):选择GPU进行训练。
  • torch.device('cpu'):如果没有GPU可用,则选择CPU。

2. 数据预处理

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

    :这部分代码定义了数据的预处理步骤。

    • transforms.ToTensor():将图像转换为PyTorch张量(Tensor)。这会把图像从PIL格式转换为一个大小为 (C, H, W) 的 Tensor(其中 C 是通道数,H 是高度,W 是宽度),并且会把像素值从 [0, 255] 归一化到 [0.0, 1.0]
    • transforms.Normalize(mean, std):对图像进行标准化。标准化是深度学习中常用的操作,目的是将数据的均值和标准差调整到一个统一的范围,通常是 0 均值,1 标准差。这里 (0.5, 0.5, 0.5) 是 RGB 通道的均值, (0.5, 0.5, 0.5) 是标准差。

3. 加载训练和测试数据集

train_set = torchvision.datasets.CIFAR10(root="./data", train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train_set, batch_size=36, shuffle=True, num_workers=0)
test_set = torchvision.datasets.CIFAR10(root="./data", train=False, download=True, transform=transform)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=5000, shuffle=False, num_workers=0)
  • 解释:

    • torchvision.datasets.CIFAR10()  #这行代码用于加载CIFAR-10数据集。
      
      • root="./data":指定数据集存储路径。

      • train=True 表示加载训练集,train=False 表示加载测试集。

      • download=True:如果数据集没有下载,会自动从互联网上下载。

      • transform=transform:对数据集应用之前定义的预处理。

      • batch_size=36:每个训练批次包含36个样本。

      • shuffle=True:在训练时打乱数据集顺序。

      • num_workers=0:用于加载数据的线程数,这里设置为0表示在主线程中加载数据。

4. 将数据加载到GPU

dataiter = iter(test_loader)
test_images, test_labels = next(dataiter)
test_images = test_images.to(device)
test_labels = test_labels.to(device)
  • 解释:
    • dataiter = iter(test_loader):将 test_loader 转换为迭代器。
    • test_images, test_labels = next(dataiter):从迭代器中获取一个批次的测试图像和标签。
    • test_images.to(device)test_labels.to(device):将数据迁移到选定的设备(CPU或GPU)。

5. 模型定义和迁移到设备

net = LeNet()
net = net.to(device)
  • 解释:实例化LeNet模型,并将模型迁移到前面选择的设备(GPU或CPU)。

6. 定义损失函数和优化器

loss_function = nn.CrossEntropyLoss()
loss_function = loss_function.to(device)
optimizer = optim.Adam(net.parameters(), lr=0.001)
  • 损失函数

    nn.CrossEntropyLoss()
    

    用于多类分类任务,计算模型预测的概率分布与真实标签之间的交叉熵损失。

    • loss_function.to(device):将损失函数迁移到相应的设备。
  • 优化器optim.Adam() 是一个常用的优化算法,它结合了动量法和自适应学习率的优势。这里设置了学习率为 0.001。

7. 模型训练

for epoch in range(5):
    running_loss = 0.0
    for step, data in enumerate(train_loader, start=0):
        inputs, labels = data
        inputs = inputs.to(device)
        labels = labels.to(device)
        
        optimizer.zero_grad()
        outputs = net(inputs)
        loss = loss_function(outputs, labels)
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item()
        if step % 500 == 499:
            with torch.no_grad():
                outputs = net(test_images)
                predict_y = torch.max(outputs, dim=1)[1]
                accuracy = torch.eq(predict_y, test_labels).sum().item() /                             test_labels.size(0)
                
                print('[%d, %5d] train_loss: %.3f test_accuracy:%.3f' % 
                      (epoch+1, step+1, running_loss / 500, accuracy))
                running_loss = 0.0
  • 训练过程
    • for epoch in range(5):训练 5 个周期(epochs)。
    • for step, data in enumerate(train_loader):遍历每一个训练批次。
    • inputs, labels = data:从数据加载器获取输入图像和标签。
    • inputs = inputs.to(device)labels = labels.to(device):将输入和标签移到 GPU 或 CPU 上。
    • optimizer.zero_grad():将优化器的梯度清零,防止梯度累积。
    • outputs = net(inputs):通过模型得到预测结果。
    • loss = loss_function(outputs, labels):计算损失。
    • loss.backward():反向传播计算梯度。
    • optimizer.step():更新模型的参数。
    • 每 500 步输出一次训练损失和测试准确率。
  • 测试准确率计算
    • torch.max(outputs, dim=1)[1]:对模型的输出进行 argmax 操作,得到每个图像预测的标签。
    • torch.eq(predict_y, test_labels).sum().item():计算预测标签与真实标签相等的个数。
    • accuracy = ... / test_labels.size(0):计算准确率。

8. 训练完成后保存模型

print("Finished Training")
save_path = './Lenet.pth'
torch.save(net.state_dict(), save_path)
  • 解释
    • torch.save(net.state_dict(), save_path):将训练好的模型保存到指定路径 ./Lenet.pth 中。state_dict() 是 PyTorch 中保存和加载模型的标准方法,它只保存模型的参数,而不包括优化器的状态等信息。

总结:

这段代码完成了 LeNet 模型的训练流程,主要包括:(以下都是在训练模型时候的固定套路,除了有些私有的数据集需要自己处理之外,其他的都是一样的处理思路)

  1. 数据预处理和加载(CIFAR-10 数据集)。
  2. 将数据和模型迁移到合适的设备(GPU 或 CPU)。
  3. 定义损失函数和优化器。
  4. 通过迭代训练模型,并计算每 500 步的训练损失和测试集准确率。
  5. 最终保存训练好的模型。

predict.py

预测部分的代码就比较简单了,首先你已经训练好了一个模型,要摩拳擦掌开始试试效果了,那么你在网上下载了一个小狗的照片,然后放在predict.py 的文件中

代码思路如下:

先对图片进行裁剪(resize到模型规定的输入大小,规格) —> 调用模型并下载训练好的参数(俗称:给模型注入灵魂) —>输入图片开始进行训练并区最大值

import torch
import torchvision.transforms as transforms
from PIL import Image
from model_official 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)) 
    ])
    im = Image.open("1.png").convert("RGB")
    im = transform(im)  # [c,h,w]
    im = torch.unsqueeze(im,dim=0) # [N,c,h,w]
    
    classes = ('plane', 'car', 'bird', 'cat',
            'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

    net = LeNet()
    net.load_state_dict(torch.load("Lenet.pth"))
  
    with torch.no_grad():
        outputs = net(im)
        predict = torch.argmax(outputs,dim=1).item()
    
    print(classes[predict])

if __name__ == "__main__":
    main()

这段代码实现了用训练好的 LeNet 模型对一张图片进行分类。具体来说,代码执行以下几个步骤:

1. 导入所需的库

import torch
import torchvision.transforms as transforms
from PIL import Image
from model_official import LeNet
  • torch: PyTorch的核心库,用于创建和操作张量。
  • torchvision.transforms: 用于对图像进行各种预处理操作。
  • PIL.Image: 用于加载和处理图片(Python Imaging Library)。
  • LeNet:自定义的LeNet模型(假设定义在model_official.py文件中)。

2. 图像预处理

transform = transforms.Compose([
    transforms.Resize((32, 32)),
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) 
])
  • transforms.Resize((32, 32)):将输入图像调整为 32x32 像素。LeNet模型通常处理32x32的输入图像。
  • transforms.ToTensor():将图像从PIL格式转换为PyTorch的Tensor格式,并且将图像像素值缩放到[0, 1]的范围。
  • transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)):对图像进行归一化操作。这里的均值和标准差是0.5,通常是为了加速训练和改善模型的收敛。输入图像的每个通道(RGB)都会减去均值0.5并除以标准差0.5,使其值大致在[-1, 1]范围内。

3. 读取和处理输入图片

im = Image.open("1.png").convert("RGB")
im = transform(im)  # [c,h,w]
im = torch.unsqueeze(im, dim=0)  # [N,c,h,w]
  • Image.open("1.png"):打开文件名为 1.png 的图片。
  • .convert("RGB"):确保图像是RGB格式,如果是灰度图像,则会转换为RGB三通道。
  • transform(im):对图像进行预处理,应用上面定义的所有变换。
  • torch.unsqueeze(im, dim=0):将图像张量的维度从 [c, h, w](单张图片的3D张量)增加一维,变成 [N, c, h, w],其中 N=1 表示批次大小为1。这个操作是因为PyTorch模型期望输入是一个批次的图像,而不是单张图像。

4. 加载并使用LeNet模型

classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

net = LeNet()
net.load_state_dict(torch.load("Lenet.pth"))
  • classes:这是一个包含10个类标签的元组,对应 CIFAR-10 数据集中的类别。模型将根据输入图像预测这些类别中的一个。
  • LeNet():实例化一个LeNet模型。假设这个模型已经在 model_official.py 文件中定义。
  • net.load_state_dict(torch.load("Lenet.pth")):加载训练好的LeNet模型的权重参数(假设文件名为Lenet.pth)。

5. 进行预测

with torch.no_grad():
    outputs = net(im)
    predict = torch.argmax(outputs, dim=1).item()
  • with torch.no_grad():此上下文管理器禁用梯度计算,以减少内存使用并加快推理过程,因为推理阶段不需要梯度。
  • net(im):将图像输入到训练好的LeNet模型中,得到模型的输出(预测结果)。
  • torch.argmax(outputs, dim=1).item()outputs 是模型的输出,通常是一个形状为 [1, 10] 的张量,表示每个类别的预测得分。torch.argmax(outputs, dim=1) 会返回最大得分的索引,这就是模型预测的类别。.item() 将这个索引转换为一个纯数值。

6. 输出预测结果

print(classes[predict])
  • 根据模型的预测结果,从 classes 元组中获取对应的类别名称,并打印出来。

7. 主函数执行

python复制代码if __name__ == "__main__":
    main()
  • 这是标准的Python主程序入口,确保只有在直接运行这个脚本时才会调用 main() 函数。

总结:

这个代码的目标是:

  1. 加载一张图像并对其进行预处理。
  2. 使用一个训练好的LeNet模型对图像进行分类。
  3. 输出图像属于哪个类别(例如:飞机、汽车、猫等)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值