Chapter 6 卷积神经网络(CNN)(五)

在仔细了解了 CNN 中的卷积和池化概念后,我们终于迎来了第一个经典的卷积神经网络—— LeNet .事实上,我们业已来到了本章的收尾阶段,此处提及相对简单的 LeNet ,或许可以视为是对下一章的承接,毕竟下一章中我们会看见很多现代的卷积应用算法。

这家伙作为第一个被提出应用于数字图像识别的卷积神经网络算法,其实应该叫 LeNet – 1,截止至今(2025-09-08)最新版本为 LeNet-5 。因为很牛b,所以什么开创先河的漂亮话就不说了,我们直接来看模型:

总体而言,LeNet包括两部分:

  • 卷积编码器
  • 全连接层密集块

其中卷积块的基本单元为卷积层,sigmoid 函数和平均汇聚层,具体排布为:

$$\begin{align} \text{Input}(1\times28\times28) &\xrightarrow[{\text{Padding = 2}}]{\text{ConvLayer}_1(6\times1\times5\times5)} && \text{Feature}_1(6\times28\times28)\\ &\xrightarrow[\text{sigmoid()}]{\text{AvgPoolLayer}_1(6\times2\times2)} && \text{Feature}_2(6\times14\times14) \\ &\xrightarrow[\text{Padding = 2}]{\text{ConvLayer}_2(6\times6\times5\times5)} && \text{Feature}_3 (6\times14\times14) \\ &\xrightarrow[\text{sigmoid()}]{\text{AvgPoolLayer}_2(6\times 2\times2)} && \text{Feature}_4(6\times7\times7)\\ &\xrightarrow[{\text{Padding = 2}}]{\text{Flatten()}} && \text{Vector}(294) \\ &\xrightarrow[\text{sigmoid()}]{\text{Linear}(294 \rightarrow 120)} && \text{Vector}(120) \\ &\xrightarrow[\text{sigmoid()}]{\text{Linear}(120 \rightarrow 84)} && \text{Vector}(84) \\ &\xrightarrow[{\text{sigmoid()}}]{\text{Linear}(84 \rightarrow 10)} && \text{Output}(10) \end{align}$$

书中关于这块的规模似乎计算存在问题,MNIST 的数据集大小为 $28\times28$但原数据为$32\times32$,目测是书只改了输入(

那么基于模型,我们很容易就能写出对应的 Python 代码:

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

class LeNet(nn.Module):
    def __init__(self):
        self.conv1 = nn.Conv2d(1, 6, kernel_size=5, padding=2)
        self.conv2 = nn.Conv2d(6, 16, kernel_size=5, padding=2)
        self.fc1 = nn.Linear(16*5*5, 120)
        self.net = nn.Sequential(
            nn.Conv2d(1, 6, kernel_size=5, padding=2),
            nn.Sigmoid(),
            nn.AvgPool2d(kernel_size=2, stride=2),
            nn.Conv2d(6, 16, kernel_size=5, padding=2),
            nn.Sigmoid(),
            nn.AvgPool2d(kernel_size=2, stride=2),
            nn.Flatten(),
            nn.Linear(16*5*5, 120),
            nn.Linear(120, 84),
            nn.Linear(84, 10)
        )
    
    def forward(self, x):
        return self.net(x)

def train(model, device, train_loader, optimizer, epoch):
    # 设置模型为训练模式
    model.train()
    train_loss = 0
    correct = 0
    total = 0

    # 训练模型
    for batch_icx,(data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        
        # 梯度清零
        optimizer.zero_grad()
        output = model(data)
        
        # 计算损失
        loss = F.cross_entropy(output, target)
        # 反向传播
        loss.backward()
        # 更新参数
        optimizer.step()

        train_loss += loss.item()
        _, predicted = output.max(1)
        total += target.size(0)
        correct += predicted.eq(target).sum().item()

        if batch_icx % 100 == 0:
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_icx * len(data), len(train_loader.dataset),
                100. * batch_icx / len(train_loader), loss.item()))

    print('Train set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)'.format(
        train_loss / len(train_loader), correct, total,
        100. * correct / total))

if __name__ == '__main__':
    # 检查是否有可用GPU
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    # 定义数据预处理
    transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.1307,), (0.3081,))
    ])

    train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
    test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)

    # 定义数据加载器
    train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
    test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)

    # 定义模型
    model = LeNet().to(device)

    # 定义优化器
    optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)

    # 训练模型
    for epoch in range(1, 11):
        train(model, device, train_loader, optimizer, epoch)

    # 保存模型
    torch.save(model.state_dict(), 'lenet.pth')
    print('Model saved to lenet.pth')

写在最后

这部分其实还是相对简单的,至少一路写下来个人感觉还是比较顺利,当然这可能是最简单的模型所以自我感觉良好,不过怎么说,万事开头难,还是期待一下下一章的模型吧 (・∀・)

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注