在仔细了解了 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')
写在最后
这部分其实还是相对简单的,至少一路写下来个人感觉还是比较顺利,当然这可能是最简单的模型所以自我感觉良好,不过怎么说,万事开头难,还是期待一下下一章的模型吧 (・∀・)
发表回复