【深度学习|学习笔记】卷积神经网络LeNet的起源、原理、演化、优缺点详解!附可直接运行的 PyTorch 代码。

【深度学习|学习笔记】卷积神经网络LeNet的起源、原理、演化、优缺点详解!附可直接运行的 PyTorch 代码。



欢迎铁子们点赞、关注、收藏!
祝大家逢考必过!逢投必中!上岸上岸上岸!upupup

大多数高校硕博生毕业要求需要参加学术会议,发表EI或者SCI检索的学术论文会议论文。详细信息可扫描博文下方二维码 “学术会议小灵通”或参考学术信息专栏:https://blog.csdn.net/2401_89898861/article/details/148877490


一、经典卷积分类网络的通用结构(从机器学习范式看)

  • 目标:学一个从图像 x x x 到类别 y y y 的判别函数 f θ ( x ) f_θ(x) fθ(x)
  • 基本骨架[卷积(Conv) → 非线性(Act) → 归一化(Norm) → 下采样(Pool/Stride)] × L → 全局聚合(GAP/FC) → Softmax

动机来自机器学习的两条主线:

  • 归纳偏置:局部连接 + 权值共享(CNN)将先验“局部相关、平移等变”写进模型,减少参数、降过拟合。
  • 分层表示学习:从边缘/角点(浅层)到部件/语义(深层),逐层抽象,避免手工特征。

代表性里程碑(分类方向)LeNet-5 (1998) → AlexNet (2012) → VGG (2014) → GoogLeNet/Inception (2014) → ResNet (2015) → EfficientNet/ConvNeXt 等。下面聚焦 LeNet 的来龙去脉。

二、LeNet 的起源与历史脉络

  • 起源:Yann LeCun 团队在 1989–1998 年间提出的卷积网络家族(LeNet-1/4/5),面向手写数字识别(银行支票、USPS/MNIST)。
  • 关键技术融合:反向传播(80s)+ 卷积/子采样 + 端到端训练 +(当时)有限的数据增强和先验。
  • 代表作:LeNet-5(1998)——论文《Gradient-Based Learning Applied to Document Recognition》(LeCun, Bottou, Bengio, Haffner)。

三、LeNet-5 的原理与结构细节

  • 经典输入是 32 × 32 32×32 32×32 灰度图(MNIST 原图 28 × 28 28×28 28×28 会先零填充到 32 × 32 32×32 32×32)。

层级结构(原版记号:C=Conv,S=Subsampling/Pooling,F=全连接)

  • C1:Conv 5 × 5 5×5 5×5, 输入 1 通道 → 输出 6 通道,尺寸 32 → 28 32→28 3228
  • S2:Average Pool 2 × 2 2×2 2×2(带可学习系数), 6 × 14 × 14 6×14×14 6×14×14
  • C3:Conv 5 × 5 5×5 5×5,输出 16 通道,尺寸 14 → 10 14→10 1410。注意部分连接表(非每个输入通道都连到每个输出通道)以打破对称性、降参。
  • S4:Average Pool 2 × 2 2×2 2×2 16 × 5 × 5 16×5×5 16×5×5
  • C5:Conv 5 × 5 5×5 5×5(在 5 × 5 5×5 5×5 上相当于 全连接到 120 维),输出 120。
  • F6:全连接 120 → 84(配合 tanh 非线性)。
  • 输出:84 → 10(分类),Softmax。

激活/归一化:历史版本用 tanh/sigmoid,无 BatchNorm;子采样是 平均池化(带可训练缩放/偏置)。
损失:多分类交叉熵(原文中也有使用高斯径向基与不同输出层变体的讨论)。

四、LeNet 的发展与影响

  • 从 LeNet 到 AlexNet:LeNet 证明“端到端卷积 + 共享权重 + 子采样”有效,但受算力/数据限制;2012 年AlexNet 用 ReLU、Dropout、数据增强、GPU 并行 把 CNN 推向大规模 ImageNet。
  • 后续演化:VGG 用小卷积堆叠、Inception 用多尺度分支、ResNet 用残差连接(解决退化/梯度消失),形成现代 CNN 族谱。
  • 在小数据/灰度识别上,LeNet 仍是轻量基准;在教学与工业嵌入式原型中常用作基线。

五、优缺点一览

优点

  • 强归纳偏置:局部连接/共享权重显著减少参数,泛化好。
  • 平移稳健:子采样(平均池化)引入一定程度的平移不变性。
  • 端到端:无需手工特征,统一用梯度下降训练。
  • 轻量:参数/算力小,适合入门与小规模任务。

缺点

  • 激活饱和:tanh/sigmoid 易梯度消失;无 BN,收敛慢。
  • 平均池化信息损失:细节被平滑,分类边界可能变钝。
  • 结构浅/容量有限:对复杂/大规模数据表达力不足。
  • 连接表复杂:C3 的部分连接在现代实现中不常保留(改成全连接更简洁)。

因此现代常用“LeNet-like”改造:ReLU/SiLU + BatchNorm + MaxPool,去掉连接表,训练稳定、精度更高

六、PyTorch 实现(原版风格 & 现代化 LeNet)

6.1 原版风格(tanh + AvgPool,简化为 C3 全连接以便实现)

import torch
import torch.nn as nn
import torch.nn.functional as F

class LeNet5Classic(nn.Module):
    def __init__(self, num_classes=10):
        super().__init__()
        # C1: 1x32x32 -> 6x28x28
        self.c1 = nn.Conv2d(1, 6, kernel_size=5, stride=1, padding=0)
        # S2: 6x28x28 -> 6x14x14 (AvgPool with learnable scale is simplified to AvgPool)
        self.s2 = nn.AvgPool2d(kernel_size=2, stride=2)
        # C3: 6x14x14 -> 16x10x10 (we use full connection among channels for simplicity)
        self.c3 = nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0)
        # S4: 16x10x10 -> 16x5x5
        self.s4 = nn.AvgPool2d(kernel_size=2, stride=2)
        # C5: 16x5x5 -> 120x1x1  (conv as FC)
        self.c5 = nn.Conv2d(16, 120, kernel_size=5, stride=1, padding=0)
        # F6: 120 -> 84
        self.f6 = nn.Linear(120, 84)
        # Output: 84 -> num_classes
        self.out = nn.Linear(84, num_classes)

    def forward(self, x):
        x = torch.tanh(self.c1(x))
        x = self.s2(x)
        x = torch.tanh(self.c3(x))
        x = self.s4(x)
        x = torch.tanh(self.c5(x))      # [N,120,1,1]
        x = x.view(x.size(0), -1)       # [N,120]
        x = torch.tanh(self.f6(x))      # [N,84]
        logits = self.out(x)            # [N,C]
        return logits

六、PyTorch 实现(原版风格 & 现代化 LeNet)

6.2 现代化 LeNet(ReLU + BN + MaxPool,训练更稳)

class LeNet5Modern(nn.Module):
    def __init__(self, num_classes=10):
        super().__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(1, 6, 5, 1, 0, bias=False), nn.BatchNorm2d(6), nn.ReLU(inplace=True),
            nn.MaxPool2d(2)  # 32->28->14
        )
        self.conv2 = nn.Sequential(
            nn.Conv2d(6, 16, 5, 1, 0, bias=False), nn.BatchNorm2d(16), nn.ReLU(inplace=True),
            nn.MaxPool2d(2)  # 14->10->5
        )
        self.fc1 = nn.Sequential(nn.Linear(16*5*5, 120), nn.ReLU(inplace=True))
        self.fc2 = nn.Sequential(nn.Linear(120, 84), nn.ReLU(inplace=True))
        self.out = nn.Linear(84, num_classes)

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = x.flatten(1)
        x = self.fc1(x)
        x = self.fc2(x)
        return self.out(x)

6.3 训练脚手架(MNIST 为例;可替换为你的数据)

import torch, torch.nn as nn
from torch.utils.data import DataLoader
# 若有 torchvision 可用:
# from torchvision import datasets, transforms

def train_one_epoch(model, loader, optimizer, device='cuda'):
    model.train()
    ce = nn.CrossEntropyLoss()
    total, correct, total_loss = 0, 0, 0.0
    for x, y in loader:
        x, y = x.to(device), y.to(device)
        logits = model(x)
        loss = ce(logits, y)
        optimizer.zero_grad(); loss.backward(); optimizer.step()
        total_loss += loss.item() * x.size(0)
        correct += (logits.argmax(1) == y).sum().item()
        total += x.size(0)
    return total_loss/total, correct/total

@torch.no_grad()
def evaluate(model, loader, device='cuda'):
    model.eval()
    total, correct = 0, 0
    for x, y in loader:
        x, y = x.to(device), y.to(device)
        pred = model(x).argmax(1)
        correct += (pred == y).sum().item()
        total += x.size(0)
    return correct/total

# 你可以把 DataLoader 换成自己的;例如(需要 torchvision):
# tfm = transforms.Compose([transforms.Pad(2), transforms.ToTensor()])
# trainset = datasets.MNIST(root='./data', train=True, download=True, transform=tfm)
# testset  = datasets.MNIST(root='./data', train=False, download=True, transform=tfm)
# train_loader = DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2)
# test_loader  = DataLoader(testset,  batch_size=256, shuffle=False, num_workers=2)
# model = LeNet5Modern().to('cuda'); opt = torch.optim.AdamW(model.parameters(), lr=1e-3)
# for ep in range(10):
#     tl, ta = train_one_epoch(model, train_loader, opt, 'cuda')
#     va = evaluate(model, test_loader, 'cuda')
#     print(f"Epoch {ep+1}: loss={tl:.4f}, train_acc={ta:.3f}, test_acc={va:.3f}")

七、与现代 CNN 的结构对比要点(有助于理解“为什么 LeNet 如此设计”)

  • 非线性:tanh → ReLU/SiLU(缓解梯度消失、收敛快)。
  • 池化:Average → Max/Strided Conv(保边缘/纹理)。
  • 归一化:无 → BatchNorm/GroupNorm(稳定训练)。
  • 深度与可达性:浅层 + 无残差 → 深层 + 残差/跨层连接(优化更易)。
  • 大规模训练:小数据/CPU → 大数据 + GPU/分布式、数据增强/正则(Label Smoothing、Mixup/CutMix、EMA 等)。

八、小结

  • LeNet 把“卷积 + 子采样 + 端到端”首次系统用于视觉分类,开了 CNN 之先河。
  • 作为教学/基线它依然实用;但在真实复杂任务上,建议使用现代化改造(ReLU+BN+MaxPool 或更先进骨干),再配合现代训练“配方”(Cosine LR、数据增广、蒸馏/正则),能显著提升精度与稳定性。
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐