PyTorch与VGG16实现鲜花分类的迁移学习实践

发布时间:2026/10/28 14:18:15
PyTorch与VGG16实现鲜花分类的迁移学习实践

1. 项目概述:用VGG16实现鲜花分类的快速入门方案

这个项目展示了如何利用PyTorch框架和经典的VGG16模型,快速搭建一个能够识别不同种类鲜花的图像分类器。对于刚接触深度学习视觉任务的新手来说,鲜花分类是个理想的起点——数据集容易获取、类别区分度明显、模型效果直观可见。我选择VGG16作为基础模型,不仅因为它的结构清晰适合教学,更因为其预训练权重能帮助我们在小数据集上获得不错的效果。

整个项目从环境配置到模型训练只需不到一小时,但涵盖了PyTorch工作流的完整环节:数据加载、预处理、模型定义、训练循环和性能评估。特别值得一提的是,我会分享如何调整VGG16的全连接层来适配我们的分类任务,这个技巧在迁移学习中非常实用。下面让我们从最基本的开发环境准备开始。

2. 环境配置与工具准备

2.1 PyTorch环境搭建

推荐使用Anaconda创建独立的Python环境,避免包冲突。对于大多数初学者,CPU版本就足够运行这个项目:

conda create -n flower_cls python=3.8 conda activate flower_cls pip install torch torchvision torchaudio

如果你的设备配有NVIDIA显卡,可以安装CUDA版本的PyTorch以获得更快的训练速度。在安装前,先用nvidia-smi查看CUDA版本,然后到PyTorch官网选择对应的安装命令。例如对于CUDA 11.3:

pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 torchaudio==0.12.1 --extra-index-url https://download.pytorch.org/whl/cu113

注意:Windows系统下如果遇到DLL加载错误,通常是CUDA环境变量问题。可以尝试在Anaconda环境中执行conda install cudatoolkit来补充运行时库。

2.2 辅助工具安装

除了PyTorch核心库,我们还需要一些数据处理和可视化的工具包:

pip install matplotlib opencv-python pillow pandas tqdm

建议使用Jupyter Notebook进行开发调试,它能分段执行代码并即时显示图像结果:

pip install notebook jupyter notebook

3. 数据集准备与预处理

3.1 获取鲜花数据集

本项目使用的是公开的Oxford 102 Flowers数据集,包含102类常见鲜花约8,000张图像。通过torchvision可以自动下载:

from torchvision import datasets, transforms train_data = datasets.Flowers102( root='data', split='train', download=True, transform=transforms.ToTensor() )

如果下载速度慢,也可以手动从官网下载压缩包,解压到项目目录下的data/flowers102文件夹中。数据集目录结构应该是:

data/ flowers102/ jpg/ image_00001.jpg image_00002.jpg ... labels.txt

3.2 数据增强与标准化

为了提高模型泛化能力,我们需要对训练图像做随机增强处理。同时,由于VGG16是在ImageNet上预训练的,输入需要做相同的标准化:

from torchvision import transforms train_transform = transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) val_transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])

实操技巧:在资源有限的情况下,可以减小RandomResizedCrop的尺寸到192甚至160,虽然会略微降低精度,但能显著减少显存占用。

3.3 创建数据加载器

使用PyTorch的DataLoader来批量加载数据,设置合适的batch_size(根据GPU显存调整):

from torch.utils.data import DataLoader batch_size = 32 train_loader = DataLoader( train_data, batch_size=batch_size, shuffle=True, num_workers=4 ) val_data = datasets.Flowers102( root='data', split='val', download=True, transform=val_transform ) val_loader = DataLoader( val_data, batch_size=batch_size, shuffle=False, num_workers=4 )

4. 模型构建与迁移学习

4.1 加载预训练VGG16

PyTorch的torchvision.models提供了预训练好的VGG16模型:

import torchvision.models as models model = models.vgg16(pretrained=True)

查看模型结构会发现,原始的VGG16最后是一个1000类的分类器(对应ImageNet类别数),我们需要替换它:

import torch.nn as nn # 冻结所有卷积层的参数 for param in model.features.parameters(): param.requires_grad = False # 替换最后的分类器 num_features = model.classifier[6].in_features model.classifier[6] = nn.Linear(num_features, 102) # 102个鲜花类别

4.2 自定义分类头设计

原始VGG16的分类器由三个全连接层组成。对于我们的任务,可以保留这个结构但调整输出维度:

from collections import OrderedDict classifier = nn.Sequential(OrderedDict([ ('fc1', nn.Linear(25088, 4096)), ('relu1', nn.ReLU(inplace=True)), ('dropout1', nn.Dropout(p=0.5)), ('fc2', nn.Linear(4096, 4096)), ('relu2', nn.ReLU(inplace=True)), ('dropout2', nn.Dropout(p=0.5)), ('fc3', nn.Linear(4096, 102)), ])) model.classifier = classifier

经验分享:全连接层的中间维度(如这里的4096)可以根据任务复杂度调整。对于较小的数据集,可以减小到1024甚至512以减少过拟合风险。

5. 模型训练与验证

5.1 训练配置

设置损失函数和优化器。由于我们冻结了卷积层,只需要对分类器参数进行优化:

import torch.optim as optim criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.classifier.parameters(), lr=0.001) # 学习率调度器 scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1)

5.2 训练循环实现

下面是完整的训练过程,包含验证环节:

def train_model(model, criterion, optimizer, scheduler, num_epochs=10): for epoch in range(num_epochs): model.train() running_loss = 0.0 correct = 0 total = 0 for inputs, labels in train_loader: optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() train_loss = running_loss / len(train_loader) train_acc = 100 * correct / total # 验证阶段 model.eval() val_loss = 0.0 val_correct = 0 val_total = 0 with torch.no_grad(): for inputs, labels in val_loader: outputs = model(inputs) loss = criterion(outputs, labels) val_loss += loss.item() _, predicted = torch.max(outputs.data, 1) val_total += labels.size(0) val_correct += (predicted == labels).sum().item() val_loss /= len(val_loader) val_acc = 100 * val_correct / val_total print(f'Epoch {epoch+1}/{num_epochs}') print(f'Train Loss: {train_loss:.4f} Acc: {train_acc:.2f}%') print(f'Val Loss: {val_loss:.4f} Acc: {val_acc:.2f}%') print('-' * 20) scheduler.step() return model model = train_model(model, criterion, optimizer, scheduler, num_epochs=10)

5.3 训练过程可视化

使用Matplotlib绘制损失和准确率曲线:

import matplotlib.pyplot as plt def plot_training(history): plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.plot(history['train_loss'], label='Train Loss') plt.plot(history['val_loss'], label='Val Loss') plt.title('Loss Curve') plt.legend() plt.subplot(1, 2, 2) plt.plot(history['train_acc'], label='Train Acc') plt.plot(history['val_acc'], label='Val Acc') plt.title('Accuracy Curve') plt.legend() plt.show()

6. 模型评估与优化

6.1 测试集性能评估

在完整训练后,我们需要在测试集上评估最终性能:

test_data = datasets.Flowers102( root='data', split='test', download=True, transform=val_transform ) test_loader = DataLoader( test_data, batch_size=batch_size, shuffle=False, num_workers=4 ) def evaluate(model, loader): model.eval() correct = 0 total = 0 class_correct = [0] * 102 class_total = [0] * 102 with torch.no_grad(): for inputs, labels in loader: outputs = model(inputs) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() for i in range(labels.size(0)): label = labels[i] class_correct[label] += (predicted[i] == label).item() class_total[label] += 1 print(f'Overall Accuracy: {100 * correct / total:.2f}%') # 打印每个类别的准确率 for i in range(102): if class_total[i] > 0: print(f'Class {i}: {100 * class_correct[i] / class_total[i]:.2f}%') evaluate(model, test_loader)

6.2 常见问题与调优策略

在实际训练中可能会遇到以下典型问题:

  1. 过拟合:训练准确率高但验证准确率低

    • 解决方案:增加数据增强(如颜色抖动、随机擦除)、加大Dropout比率、添加L2正则化、减少全连接层维度
  2. 欠拟合:训练和验证准确率都低

    • 解决方案:解冻部分卷积层进行微调、增大模型容量、延长训练轮次
  3. 类别不平衡:某些类别的准确率显著低于其他

    • 解决方案:使用加权交叉熵损失、对少数类过采样或多数类欠采样

这里给出一个解冻部分卷积层进行微调的示例:

# 解冻最后两个卷积块 for param in model.features[24:].parameters(): param.requires_grad = True # 使用更小的学习率微调卷积层 optimizer = optim.Adam([ {'params': model.features[24:].parameters(), 'lr': 1e-5}, {'params': model.classifier.parameters(), 'lr': 1e-4} ]) # 重新训练 model = train_model(model, criterion, optimizer, scheduler, num_epochs=5)

7. 模型部署与应用

7.1 保存与加载模型

训练完成后,保存模型权重供后续使用:

torch.save(model.state_dict(), 'flower_vgg16.pth')

加载时先重建模型结构再加载权重:

model = models.vgg16(pretrained=False) num_features = model.classifier[6].in_features model.classifier[6] = nn.Linear(num_features, 102) model.load_state_dict(torch.load('flower_vgg16.pth')) model.eval()

7.2 单张图像预测

实现一个预测函数,输入图像路径返回预测结果:

from PIL import Image def predict(image_path, model, class_names): image = Image.open(image_path).convert('RGB') image = val_transform(image).unsqueeze(0) with torch.no_grad(): output = model(image) _, predicted = torch.max(output, 1) return class_names[predicted.item()] # 加载类别名称 with open('data/flowers102/labels.txt', 'r') as f: class_names = [line.strip() for line in f.readlines()] # 测试预测 result = predict('test_rose.jpg', model, class_names) print(f'Predicted: {result}')

7.3 性能优化技巧

在实际部署时可以考虑以下优化:

  1. 模型量化:减小模型大小,提高推理速度

    quantized_model = torch.quantization.quantize_dynamic( model, {nn.Linear}, dtype=torch.qint8 )
  2. ONNX导出:跨平台部署

    dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export(model, dummy_input, "flower_vgg16.onnx")
  3. TorchScript序列化:脱离Python环境运行

    scripted_model = torch.jit.script(model) scripted_model.save("flower_vgg16.pt")

8. 项目扩展方向

这个基础项目可以进一步扩展:

  1. 多模型对比:尝试ResNet、EfficientNet等其他架构,比较它们在鲜花数据集上的表现

  2. 细粒度分类:在同一个花种内区分不同变种(如不同颜色的玫瑰)

  3. Web应用开发:使用Flask或FastAPI构建在线识别服务

  4. 移动端部署:将模型转换为Core ML或TFLite格式,开发手机应用

  5. 主动学习:让模型主动选择最有价值的样本进行标注,减少标注成本

一个简单的Flask Web应用示例:

from flask import Flask, request, jsonify from PIL import Image import io app = Flask(__name__) @app.route('/predict', methods=['POST']) def predict(): if 'file' not in request.files: return jsonify({'error': 'No file uploaded'}) file = request.files['file'] image = Image.open(io.BytesIO(file.read())).convert('RGB') image = val_transform(image).unsqueeze(0) with torch.no_grad(): output = model(image) _, predicted = torch.max(output, 1) return jsonify({'class': class_names[predicted.item()]}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)

这个项目虽然从技术角度看不算复杂,但它完整展示了深度学习图像分类任务的标准流程。在实际操作中我发现,合理调整数据增强策略和学习率调度往往比更换更复杂的模型更能提升最终效果。另外,对于这类细粒度分类任务,在最后全连接层前添加一个注意力模块通常会有意想不到的效果提升。