LeNet5模型原理讲解-手写代码解析LeNet5模型

LeNet-5 是由 Yann LeCun 于 1998 年提出的经典卷积神经网络(CNN),被视为深度学习卷积网络的开山鼻祖,主要用于手写数字识别(MNIST 数据集)。MNIST 准确率:约 99.2%。第一个成功商用的 CNN(银行手写支票识别),后续 AlexNet、VGG、GoogLeNet、ResNet 均沿用其核心范式

核心创新

  1. 局部连接:只连接局部感受野,而非全局全连接
  2. 权值共享:同一卷积核在整张图复用,大幅减少参数
  3. 池化(下采样):降维、保留特征、增强平移不变性
  4. 多层卷积-池化堆叠:分层提取简单→复杂特征

整体结构

LeNet-5 中的 5表示 卷积和全连接的个数, 没有池化一毛儿钱的关系
输入层 → C1(卷积)→ 池化→ C2(卷积)→池化→ C3(卷积)→ F4(全连接)→F5 输出层

letnet5

C1 卷积层
  • 卷积核6 个 5×5 卷积核,步长 1,无填充
  • 输出28×28×6(32−5+1=28)
  • 激活Tanh
  • 参数量:6×(5×5+1) = 156
池化层(下采样)
  • 池化2×2 平均池化,步长 2
  • 输出14×14×6(28/2=14)
  • 参数量:6×(1+1) = 12
C2 卷积层(关键:稀疏连接)
  • 卷积核16 个 5×5,步长 1,无填充
  • 输出10×10×16(14−5+1=10)
  • 参数量:约 1516
池化层
  • 池化2×2 平均池化,步长 2
  • 输出5×5×16(10/2=5)
  • 参数量:16×2 = 32
C3 卷积层
  • 卷积核120 个 5×5,步长 1,无填充
  • 输出1×1×120(输入 5×5,卷积后变成 1×1)
  • 本质:等价于 全连接层
  • 参数量:120×(5×5×16+1) = 48120
F4 全连接层
  • 神经元84
  • 输入:120 维 → 全连接 → 84 维
  • 激活:Tanh
  • 参数量:120×84+84 = 10164
F5 输出层(Output)
  • 神经元10 个(对应 0–9)
  • 原始RBF(径向基函数)
  • 现代Softmax 输出概率

论文代码复现

B站硬核手写letnet5模型

import torch
import torch.nn as nn
import torch.nn.functional as F
import matplotlib.pyplot as plt

class LeNet5(nn.Module):
    def __init__(self, num_classes=65):
        super(LeNet5, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, kernel_size=5, stride=1, padding=0)
        self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2)
        self.conv2 = nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0)
        self.pool2 = nn.AvgPool2d(kernel_size=2, stride=2)
        self.conv3 = nn.Conv2d(16, 120, kernel_size=5, stride=1, padding=0)
        self.fc4 = nn.Linear(120, 84)
        self.fc5 = nn.Linear(84, num_classes)

    def forward(self, x):
        x = self.pool1(torch.tanh(self.conv1(x)))
        x = self.pool2(torch.tanh(self.conv2(x)))
        x = torch.tanh(self.conv3(x))
        x = x.view(x.size(0), -1)
        x = torch.tanh(self.fc4(x))
        x = self.fc5(x)
        return x

if __name__ == '__main__':
    model = LeNet5(num_classes=65)
    # 1张图片  1个通道  高32,宽32
    test_input = torch.randn(1, 1, 32, 32)
    output = model(test_input)
    print("input:", test_input.shape)
    print("output:", output.shape)

现代常用变体

  • 平均池化 → 最大池化
  • 激活函数用 ReLU 替代 Tanh
  • 输出层用 Softmax + CrossEntropyLoss

实战练习

import torch
import torch.nn as nn
import torch.optim as optim  # 优化算法
import torchvision.transforms as transforms
from torchvision.datasets import MNIST
# 数据集
import torch.utils.data as data




plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

# 选择是显卡还是cpu
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print("using {} device.".format(device))


# 下载数据集,需要数据的,因为下载过了
train_datasets = MNIST('data', train=True, transform=transforms.ToTensor(), download=True)
test_datasets = MNIST('../data', train=False, transform=transform)

# 数据集加载
train_loader = data.DataLoader(train_datasets, batch_size=32, shuffle=False)
test_loader = data.DataLoader(test_datasets, batch_size=32, shuffle=False)

class LeNet5(nn.Module):
    def __init__(self):
        super().__init__()
        # - **输入**:**32×32×1**(灰度图,单通道)
        # MNIST 原图是 28×28, padding=2 论文中填充为 32×32 以保留更多边界特征。
        self.conv1 = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, stride=1, padding=2),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        # 第二次卷积
        self.conv2 = nn.Sequential(
            nn.Conv2d(6, 16, 5),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )
		# 第三层使用的 全连接没有 使用论文的卷积
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
    # 定义前向传播,输入为x
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = torch.flatten(x, 1)   # 展开(flatten) 变成全连接网络需要的输入,可以使用view改变张量维度        # 全连接 然后激活
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x


# 创建模型net
net = LeNet5()
# 交叉损失函数
loss_function = nn.CrossEntropyLoss()
# Adam优化器 学习率0.01
optimizer = optim.Adam(net.parameters(), lr=0.001)

# 开始训练 0-10 循环每次
# 定义模型保存路径和文件名
model_path = 'LeNet5.pth'
arr_loss = []
# 训练模型
def train_Model(net, train_loader):
    net.train()
    for epoch in range(5):
        for i, (images, labels) in enumerate(train_loader):
            outputs = net.forward(images)
            # 获取损失函数
            loss = loss_function(outputs, labels)
            # 反向更新
            loss.backward()
            # 更新参数
            optimizer.step()
            # 梯度清零
            optimizer.zero_grad()
            arr_loss.append(loss.item())
            if (i+1) % 200 == 0 or i==0:
                print('[%d, %5d] loss函数->: %.3f' % (epoch + 1, i + 1, loss.item()))
#     保存模型
torch.save(net.state_dict(), model_path)


# 测试模型
def test_Model(net, test_loader):
    # # 把模型从训练模式,设置为测试模式
    net.eval()
    # 模型不可能100% 准确,
    # 使用测试集,计算准确率
    # 默认准确度是0
    correct = 0
    with torch.no_grad():
        # 把测试集,一个一个的取出来,计算准确度
        for i, (img, labels) in enumerate(test_loader):
            outputs = net.forward(img)
            # 找到最大可能性的索引
            pred = torch.argmax(outputs, dim=1)
            correct = correct + torch.sum(torch.eq(pred, labels)).numpy()
        print(correct / len(test_datasets))
        # 测试样例
    examples = iter(test_loader)
    # 显示真实数据的结果  32,1,28,28
    imgs, labels = next(examples)
    py = net.forward(imgs)
    out = torch.argmax(py, dim=1).numpy()
    fig = plt.figure()
    for i in range(16):
        plt.subplot(4, 4, i + 1)
        plt.tight_layout()
        plt.imshow(imgs[i][0], cmap='gray', interpolation='none')
        plt.title("Prediction: {}".format(out[i]))
        plt.xticks([])
        plt.yticks([])
    plt.show()
    print('Finished Training')
    plt.show()

# 存在,直接加载模型
if os.path.exists(model_path):
    # 加载已经训练好的模型 文件地址
    net.load_state_dict(torch.load(model_path))
    # 测试已经训练好的模型
    test_Model(net, test_loader)
else:
    # 训练模型,保存模型
    train_Model(net, train_loader)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值