深度学习篇---LeNet-5网络结构
在 PyTorch 中实现 LeNet-5 其实很简单,就像搭积木一样把各个层按照顺序组合起来。我们可以分步骤来实现,从搭建网络结构到训练模型,每一步都很直观。
一、先明确 LeNet-5 的结构(复习)
在 PyTorch 中实现网络,首先要记住 LeNet-5 的经典结构:
输入(32×32灰度图) → 卷积层1 → 池化层1 → 卷积层2 → 池化层2 → 全连接层1 → 全连接层2 → 输出层(10个数字)
二、PyTorch 实现 LeNet-5 的步骤
步骤 1:导入必要的库
就像做菜需要先准备好厨具,实现模型前要导入 PyTorch 相关的工具:
import torch # PyTorch的核心库
import torch.nn as nn # 包含各种神经网络层
import torch.optim as optim # 包含优化器(比如梯度下降)
from torch.utils.data import DataLoader # 用于加载数据
from torchvision import datasets, transforms # 用于处理图像数据
步骤 2:定义 LeNet-5 网络结构
在 PyTorch 中,我们通过创建一个类来定义网络,这个类要继承nn.Module
(PyTorch 中所有网络的基类)。
核心是重写两个方法:__init__
(定义网络层)和forward
(定义数据流向)。
class LeNet5(nn.Module):def __init__(self):super(LeNet5, self).__init__()# 定义卷积层和池化层self.conv_layers = nn.Sequential(# 卷积层1:输入1个通道(灰度图),输出6个通道,卷积核5×5nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5),nn.Tanh(), # 激活函数,让网络能学习非线性特征# 池化层1:2×2最大池化nn.MaxPool2d(kernel_size=2, stride=2),# 卷积层2:输入6个通道,输出16个通道,卷积核5×5nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5),nn.Tanh(),# 池化层2:2×2最大池化nn.MaxPool2d(kernel_size=2, stride=2))# 定义全连接层self.fc_layers = nn.Sequential(# 全连接层1:输入是16×5×5的特征(池化后的结果),输出120nn.Linear(in_features=16*5*5, out_features=120),nn.Tanh(),# 全连接层2:输入120,输出84nn.Linear(in_features=120, out_features=84),nn.Tanh(),# 输出层:输入84,输出10(对应0-9十个数字)nn.Linear(in_features=84, out_features=10))# 定义数据在网络中的流动过程def forward(self, x):x = self.conv_layers(x) # 先经过卷积和池化层x = x.view(-1, 16*5*5) # 把特征图"拉平"成一维向量(为全连接层做准备)x = self.fc_layers(x) # 再经过全连接层return x
简单解释:
nn.Sequential
:像装积木一样把层按顺序组合起来,数据会按顺序流过每一层。Conv2d
:卷积层,kernel_size=5
表示用 5×5 的卷积核。MaxPool2d
:池化层,stride=2
表示每次滑动 2 步(所以尺寸会减半)。Linear
:全连接层,把前面的特征汇总后输出结果。
步骤 3:准备数据(以 MNIST 手写数字为例)
LeNet-5 最初就是为识别手写数字设计的,我们用经典的 MNIST 数据集来训练:
# 数据预处理:把图片转换成Tensor,并做标准化
transform = transforms.Compose([transforms.Resize((32, 32)), # 把MNIST的28×28图片放大到32×32(LeNet-5要求的输入尺寸)transforms.ToTensor(), # 转换成PyTorch能处理的Tensor格式transforms.Normalize((0.1307,), (0.3081,)) # 标准化(MNIST数据集的均值和标准差)
])# 加载训练集和测试集
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform
)
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform
)# 用DataLoader批量加载数据(方便训练时按批次处理)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)
步骤 4:初始化模型、损失函数和优化器
- 模型:就是我们上面定义的 LeNet5。
- 损失函数:衡量模型预测的错误程度(这里用交叉熵损失,适合分类问题)。
- 优化器:用来更新模型参数(这里用 SGD,随机梯度下降)。
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 用GPU加速(如果有的话)
model = LeNet5().to(device) # 初始化模型并放到GPU/CPU上criterion = nn.CrossEntropyLoss() # 交叉熵损失函数(自带Softmax,输出概率)
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9) # 优化器,lr是学习率
步骤 5:训练模型
训练过程就像 “学生做题纠错”:模型先猜答案,根据错误调整自己,反复多次直到准确率提高。
def train(model, train_loader, criterion, optimizer, epoch):model.train() # 切换到训练模式for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device) # 数据放到GPU/CPU上optimizer.zero_grad() # 清空之前的梯度output = model(data) # 模型预测loss = criterion(output, target) # 计算损失(预测和真实答案的差距)loss.backward() # 反向传播,计算梯度optimizer.step() # 根据梯度更新模型参数# 打印训练进度if batch_idx % 100 == 0:print(f'Epoch {epoch}, Batch {batch_idx}, Loss: {loss.item():.6f}')# 训练5轮(可以根据需要增加)
for epoch in range(1, 6):train(model, train_loader, criterion, optimizer, epoch)
训练的核心逻辑:
- 模型用当前参数做预测 → 2. 计算预测错误(损失) → 3. 反向算 “怎么调整参数能减少错误”(梯度) → 4. 按梯度调整参数 → 重复。
步骤 6:测试模型效果
训练完后,用测试集看看模型的准确率:
def test(model, test_loader):model.eval() # 切换到评估模式(关闭 dropout 等训练特有的操作)correct = 0total = 0with torch.no_grad(): # 测试时不计算梯度(节省内存)for data, target in test_loader:data, target = data.to(device), target.to(device)output = model(data)_, predicted = torch.max(output.data, 1) # 取概率最大的那个数字作为预测结果total += target.size(0)correct += (predicted == target).sum().item()print(f'Test Accuracy: {100 * correct / total:.2f}%')# 测试模型
test(model, test_loader)
运行后,你会看到类似这样的结果(训练 5 轮后):
Test Accuracy: 98.50%
(准确率会因训练轮数等略有差异)
三、完整代码总结
把上面的步骤整合起来,就是一个可直接运行的 LeNet-5 实现:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms# 1. 定义LeNet-5网络结构
class LeNet5(nn.Module):def __init__(self):super(LeNet5, self).__init__()# 卷积和池化层self.conv_layers = nn.Sequential(nn.Conv2d(1, 6, kernel_size=5), # 卷积层1:1→6通道,5×5卷积核nn.Tanh(),nn.MaxPool2d(kernel_size=2, stride=2), # 池化层1:2×2,步长2nn.Conv2d(6, 16, kernel_size=5), # 卷积层2:6→16通道,5×5卷积核nn.Tanh(),nn.MaxPool2d(kernel_size=2, stride=2) # 池化层2:2×2,步长2)# 全连接层self.fc_layers = nn.Sequential(nn.Linear(16*5*5, 120), # 全连接层1:输入16×5×5,输出120nn.Tanh(),nn.Linear(120, 84), # 全连接层2:输入120,输出84nn.Tanh(),nn.Linear(84, 10) # 输出层:输入84,输出10(0-9))def forward(self, x):x = self.conv_layers(x) # 经过卷积和池化x = x.view(-1, 16*5*5) # 拉平特征图x = self.fc_layers(x) # 经过全连接层return x# 2. 准备数据
transform = transforms.Compose([transforms.Resize((32, 32)), # 调整为32×32transforms.ToTensor(), # 转为Tensortransforms.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)# 3. 初始化模型、损失函数和优化器
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = LeNet5().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)# 4. 训练函数
def train(model, train_loader, criterion, optimizer, epoch):model.train()for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device)optimizer.zero_grad() # 清空梯度output = model(data) # 模型预测loss = criterion(output, target) # 计算损失loss.backward() # 反向传播optimizer.step() # 更新参数if batch_idx % 100 == 0:print(f'Epoch {epoch}, Batch {batch_idx}, Loss: {loss.item():.6f}')# 5. 测试函数
def test(model, test_loader):model.eval()correct = 0total = 0with torch.no_grad():for data, target in test_loader:data, target = data.to(device), target.to(device)output = model(data)_, predicted = torch.max(output.data, 1)total += target.size(0)correct += (predicted == target).sum().item()print(f'Test Accuracy: {100 * correct / total:.2f}%')# 6. 开始训练和测试
for epoch in range(1, 6):train(model, train_loader, criterion, optimizer, epoch)
test(model, test_loader)
四、关键知识点回顾
- 网络结构:卷积层负责提取特征(线条、圆圈),池化层压缩数据,全连接层做最终判断。
- 训练流程:通过 “预测→算错→调参” 的循环,让模型逐渐学会识别数字。
- PyTorch 技巧:用
nn.Sequential
简化层的组合,用DataLoader
方便数据加载,用to(device)
实现 GPU 加速。
运行这段代码后,你会得到一个能识别手写数字的 LeNet-5 模型,准确率通常能达到 98% 以上,和 LeCun 当年的结果很接近!