从LeNet出发:手把手教你用PyTorch搭建自定义图像分类器(以猫狗识别为例)
2026/5/12 7:52:33 网站建设 项目流程

从LeNet到实战:用PyTorch构建高精度猫狗分类器的完整指南

当你第一次看到LeNet这个经典的卷积神经网络结构时,可能会觉得它过于简单——毕竟它诞生于1998年,只有5层网络。但正是这个"古老"的架构,为我们打开了一扇理解现代深度学习的大门。本文将带你从LeNet的基础出发,一步步构建一个能够准确区分猫狗图片的实用分类器。

1. 为什么选择LeNet作为起点?

在深度学习领域,LeNet就像是一把瑞士军刀——小巧但功能齐全。它包含了现代卷积神经网络的所有关键组件:卷积层、池化层、全连接层。对于二分类问题(如猫狗识别),经过适当调整的LeNet往往能提供令人惊喜的准确率,同时保持极快的训练速度。

LeNet的核心优势

  • 结构简单,训练速度快
  • 参数数量少,不易过拟合
  • 完美适配32x32像素的输入图像
  • 作为教学工具,能清晰展示CNN工作原理

提示:虽然现代网络如ResNet、EfficientNet在ImageNet等大型数据集上表现更好,但对于小规模自定义数据集(几千张图片),调整后的LeNet往往是性价比最高的选择。

2. 数据准备:构建自己的猫狗数据集

2.1 数据收集与目录结构

首先需要准备猫和狗的图片数据集。建议每种动物至少准备1000张图片,可以从以下渠道获取:

  • Kaggle的Dogs vs Cats数据集
  • 网络爬取(注意版权)
  • 自己拍摄的照片

正确的目录结构对PyTorch的ImageFolder类至关重要:

data/ train/ cat/ cat001.jpg cat002.jpg ... dog/ dog001.jpg dog002.jpg ... val/ cat/ ... dog/ ...

2.2 数据预处理与增强

from torchvision import transforms train_transform = transforms.Compose([ transforms.Resize((32, 32)), # LeNet标准输入尺寸 transforms.RandomHorizontalFlip(), # 水平翻转增强 transforms.RandomRotation(10), # 随机旋转 transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ]) val_transform = transforms.Compose([ transforms.Resize((32, 32)), transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ])

数据增强技巧对比

增强方法作用适用场景
RandomHorizontalFlip水平翻转图像适用于对称性物体
RandomRotation随机旋转图像增加角度不变性
ColorJitter调整亮度/对比度应对光照变化
RandomCrop随机裁剪增加位置不变性

3. 改造LeNet:从十分类到二分类

3.1 网络结构调整

原始LeNet设计用于MNIST的10分类问题,我们需要对其最后一层进行修改:

import torch.nn as nn import torch.nn.functional as F class CatDogLeNet(nn.Module): def __init__(self): super(CatDogLeNet, self).__init__() self.conv1 = nn.Conv2d(3, 16, 5) # 输入通道改为3(RGB) self.pool1 = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(16, 32, 5) self.pool2 = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(32*5*5, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 2) # 输出改为2个类别 def forward(self, x): x = F.relu(self.conv1(x)) x = self.pool1(x) x = F.relu(self.conv2(x)) x = self.pool2(x) x = x.view(-1, 32*5*5) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x

3.2 关键修改点解析

  1. 输入通道调整:原始LeNet用于灰度图像(1通道),我们改为3通道以适应RGB输入
  2. 输出层修改:将最后的全连接层输出从10改为2
  3. 激活函数选择:保持ReLU作为隐藏层激活函数
  4. 损失函数调整:使用适合二分类的BCEWithLogitsLoss代替CrossEntropyLoss

4. 训练策略:小样本数据下的优化技巧

4.1 学习率调度与早停

from torch.optim import lr_scheduler model = CatDogLeNet() criterion = nn.BCEWithLogitsLoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 学习率调度器 scheduler = lr_scheduler.ReduceLROnPlateau( optimizer, mode='max', # 监控验证准确率 factor=0.1, # 学习率衰减因子 patience=5, # 等待epoch数 verbose=True ) # 早停机制 best_acc = 0.0 early_stop_patience = 10 no_improve_epochs = 0

4.2 训练循环优化

for epoch in range(100): # 设置较大的epoch数,由早停控制 model.train() running_loss = 0.0 for images, labels in train_loader: optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels.float().unsqueeze(1)) loss.backward() optimizer.step() running_loss += loss.item() # 验证阶段 model.eval() val_acc = 0.0 with torch.no_grad(): for images, labels in val_loader: outputs = model(images) preds = torch.sigmoid(outputs) > 0.5 val_acc += (preds == labels.unsqueeze(1)).sum().item() val_acc /= len(val_dataset) scheduler.step(val_acc) # 更新学习率 # 早停判断 if val_acc > best_acc: best_acc = val_acc no_improve_epochs = 0 torch.save(model.state_dict(), 'best_model.pth') else: no_improve_epochs += 1 if no_improve_epochs >= early_stop_patience: print(f'Early stopping at epoch {epoch}') break

训练超参数推荐值

参数推荐值说明
Batch Size32-64小数据集可用更小batch
初始学习率0.001Adam优化器的安全起点
权重衰减0.0001防止过拟合
早停耐心值10验证集准确率10轮不提升则停止

5. 模型评估与部署实战

5.1 评估指标选择

对于二分类问题,单一准确率指标往往不够,建议计算以下指标:

from sklearn.metrics import classification_report def evaluate_model(model, dataloader): model.eval() all_preds = [] all_labels = [] with torch.no_grad(): for images, labels in dataloader: outputs = model(images) preds = torch.sigmoid(outputs) > 0.5 all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) print(classification_report(all_labels, all_preds, target_names=['cat', 'dog']))

5.2 部署为Web服务

使用Flask快速创建API接口:

from flask import Flask, request, jsonify from PIL import Image import io app = Flask(__name__) model = CatDogLeNet() model.load_state_dict(torch.load('best_model.pth')) model.eval() @app.route('/predict', methods=['POST']) def predict(): if 'file' not in request.files: return jsonify({'error': 'no file uploaded'}), 400 file = request.files['file'].read() image = Image.open(io.BytesIO(file)) image = val_transform(image).unsqueeze(0) with torch.no_grad(): output = model(image) prob = torch.sigmoid(output).item() return jsonify({ 'prediction': 'dog' if prob > 0.5 else 'cat', 'confidence': prob if prob > 0.5 else 1 - prob }) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)

5.3 性能优化技巧

  1. 模型量化:减小模型大小,提升推理速度
quantized_model = torch.quantization.quantize_dynamic( model, {nn.Linear}, dtype=torch.qint8 )
  1. ONNX导出:实现跨平台部署
dummy_input = torch.randn(1, 3, 32, 32) torch.onnx.export(model, dummy_input, "cat_dog_lenet.onnx", input_names=['input'], output_names=['output'], dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}})
  1. TensorRT加速:NVIDIA GPU上的极致优化

6. 常见问题与解决方案

在实际项目中,你可能会遇到以下典型问题:

问题1:模型准确率停滞不前

解决方案

  • 增加数据增强多样性
  • 尝试更复杂的网络结构
  • 使用预训练模型的特征提取器

问题2:训练损失震荡严重

解决方案

  • 减小学习率
  • 增加批量大小
  • 添加梯度裁剪

问题3:模型过拟合

解决方案

# 在模型定义中添加Dropout层 self.dropout = nn.Dropout(0.5) # 在全连接层后添加 # 在训练时使用更强的L2正则化 optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=0.001)

问题4:类别不平衡

当猫狗图片数量不均衡时:

# 在损失函数中添加类别权重 pos_weight = torch.tensor([num_negatives/num_positives]) # 正样本权重 criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)

7. 进阶方向:从LeNet到更强大的模型

当你在LeNet上获得满意结果后,可以考虑以下升级路径:

  1. 更深的网络结构
# 在原有基础上增加卷积层 self.conv3 = nn.Conv2d(32, 64, 3) self.pool3 = nn.MaxPool2d(2, 2)
  1. 残差连接
# 实现简单的残差块 class ResidualBlock(nn.Module): def __init__(self, in_channels): super().__init__() self.conv1 = nn.Conv2d(in_channels, in_channels, 3, padding=1) self.conv2 = nn.Conv2d(in_channels, in_channels, 3, padding=1) def forward(self, x): residual = x out = F.relu(self.conv1(x)) out = self.conv2(out) out += residual return F.relu(out)
  1. 注意力机制
# 添加简单的通道注意力 class ChannelAttention(nn.Module): def __init__(self, in_channels): super().__init__() self.avg_pool = nn.AdaptiveAvgPool2d(1) self.fc = nn.Sequential( nn.Linear(in_channels, in_channels//8), nn.ReLU(), nn.Linear(in_channels//8, in_channels), nn.Sigmoid() ) def forward(self, x): b, c, _, _ = x.size() y = self.avg_pool(x).view(b, c) y = self.fc(y).view(b, c, 1, 1) return x * y.expand_as(x)
  1. 迁移学习
from torchvision.models import resnet18 model = resnet18(pretrained=True) # 替换最后一层 model.fc = nn.Linear(model.fc.in_features, 2)

在实际猫狗分类项目中,经过适当数据增强和调参的LeNet通常能达到75%-85%的准确率。如果需要更高精度,建议从ResNet18等小型预训练模型开始,通过迁移学习快速获得90%以上的准确率。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询