
PyTorch ResNet-50 迁移学习实战10类花卉分类任务Top-1准确率95%当面对一个全新的图像分类任务时从头训练一个深度神经网络往往需要大量的计算资源和时间。但通过迁移学习我们可以利用预训练模型的知识快速构建高效的分类器。本文将带你用PyTorch实现一个基于ResNet-50的花卉分类器在Oxford 102 Flowers数据集上达到95%的Top-1准确率。1. 环境准备与数据加载首先确保你的环境已安装PyTorch和必要的扩展库。推荐使用Python 3.8和PyTorch 1.10版本pip install torch torchvision torchaudio pillow matplotlibOxford 102 Flowers数据集包含102类花卉图像每类有40-258张图片。我们将使用PyTorch的torchvision.datasets模块加载数据import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader # 定义数据增强和预处理 train_transform transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) val_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) # 加载数据集 train_dataset datasets.Flowers102( rootdata, splittrain, downloadTrue, transformtrain_transform) val_dataset datasets.Flowers102( rootdata, splitval, downloadTrue, transformval_transform) # 创建数据加载器 batch_size 32 train_loader DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue) val_loader DataLoader(val_dataset, batch_sizebatch_size, shuffleFalse)提示数据增强是提升模型泛化能力的关键。我们使用了随机裁剪、水平翻转和旋转来增加训练数据的多样性。2. 模型构建与微调策略我们将使用在ImageNet上预训练的ResNet-50作为基础模型替换最后的全连接层以适应我们的分类任务import torch.nn as nn from torchvision import models def build_model(num_classes102): # 加载预训练模型 model models.resnet50(weightsmodels.ResNet50_Weights.DEFAULT) # 冻结所有卷积层参数 for param in model.parameters(): param.requires_grad False # 替换最后的全连接层 in_features model.fc.in_features model.fc nn.Sequential( nn.Linear(in_features, 512), nn.ReLU(), nn.Dropout(0.5), nn.Linear(512, num_classes) ) return model model build_model(num_classes102)微调策略对比表策略训练参数计算成本适用场景预期准确率仅训练最后一层最少最低小数据集中等部分层解冻中等中等中等数据集中高全模型微调全部最高大数据集最高我们采用部分层解冻策略逐步解冻模型的高层def unfreeze_layers(model, num_blocks2): # ResNet-50有4个主要块(conv2_x到conv5_x) children list(model.children()) for child in children[-num_blocks:]: for param in child.parameters(): param.requires_grad True3. 训练流程与超参数优化训练深度学习模型需要精心设置超参数。以下是我们的训练配置import torch.optim as optim from torch.optim import lr_scheduler device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) # 定义损失函数和优化器 criterion nn.CrossEntropyLoss() optimizer optim.AdamW([ {params: model.fc.parameters(), lr: 1e-3}, {params: model.layer4.parameters(), lr: 5e-5} ], weight_decay1e-4) # 学习率调度器 scheduler lr_scheduler.StepLR(optimizer, step_size5, gamma0.1) # 训练函数 def train_model(model, criterion, optimizer, scheduler, num_epochs20): best_acc 0.0 for epoch in range(num_epochs): print(fEpoch {epoch1}/{num_epochs}) print(- * 10) # 训练阶段 model.train() running_loss 0.0 running_corrects 0 for inputs, labels in train_loader: inputs inputs.to(device) labels labels.to(device) optimizer.zero_grad() with torch.set_grad_enabled(True): outputs model(inputs) _, preds torch.max(outputs, 1) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() * inputs.size(0) running_corrects torch.sum(preds labels.data) scheduler.step() epoch_loss running_loss / len(train_dataset) epoch_acc running_corrects.double() / len(train_dataset) print(fTrain Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}) # 验证阶段 val_loss, val_acc evaluate(model, criterion, val_loader) print(fVal Loss: {val_loss:.4f} Acc: {val_acc:.4f}\n) # 保存最佳模型 if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pth) print(fBest val Acc: {best_acc:.4f}) return model def evaluate(model, criterion, data_loader): model.eval() running_loss 0.0 running_corrects 0 for inputs, labels in data_loader: inputs inputs.to(device) labels labels.to(device) with torch.set_grad_enabled(False): outputs model(inputs) _, preds torch.max(outputs, 1) loss criterion(outputs, labels) running_loss loss.item() * inputs.size(0) running_corrects torch.sum(preds labels.data) loss running_loss / len(data_loader.dataset) acc running_corrects.double() / len(data_loader.dataset) return loss, acc关键训练技巧渐进式解冻先训练全连接层再逐步解冻深层卷积层差异化学习率全连接层使用较高学习率(1e-3)卷积层使用较低学习率(5e-5)权重衰减添加L2正则化防止过拟合学习率调度每5个epoch将学习率降低为原来的1/104. 模型评估与结果可视化训练完成后我们需要全面评估模型性能。除了准确率还应关注混淆矩阵和各类别的精确度/召回率import numpy as np import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix, classification_report def plot_confusion_matrix(cm, classes, normalizeFalse, titleConfusion matrix): plt.figure(figsize(12, 10)) plt.imshow(cm, interpolationnearest, cmapplt.cm.Blues) plt.title(title) plt.colorbar() tick_marks np.arange(len(classes)) plt.xticks(tick_marks, classes, rotation45) plt.yticks(tick_marks, classes) if normalize: cm cm.astype(float) / cm.sum(axis1)[:, np.newaxis] thresh cm.max() / 2. for i in range(cm.shape[0]): for j in range(cm.shape[1]): plt.text(j, i, format(cm[i, j], .2f if normalize else d), horizontalalignmentcenter, colorwhite if cm[i, j] thresh else black) plt.ylabel(True label) plt.xlabel(Predicted label) plt.tight_layout() # 加载最佳模型 model.load_state_dict(torch.load(best_model.pth)) model.eval() # 获取所有预测和真实标签 all_preds [] all_labels [] with torch.no_grad(): for inputs, labels in val_loader: inputs inputs.to(device) outputs model(inputs) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) # 计算混淆矩阵 cm confusion_matrix(all_labels, all_preds) plot_confusion_matrix(cm, classestrain_dataset.classes, normalizeTrue) # 打印分类报告 print(classification_report(all_labels, all_preds, target_namestrain_dataset.classes))典型训练日志输出Epoch 1/20 ---------- Train Loss: 1.8324 Acc: 0.6321 Val Loss: 0.9874 Acc: 0.8235 Epoch 5/20 ---------- Train Loss: 0.6542 Acc: 0.9012 Val Loss: 0.4321 Acc: 0.9128 Epoch 10/20 ---------- Train Loss: 0.3215 Acc: 0.9523 Val Loss: 0.3568 Acc: 0.9372 Epoch 15/20 ---------- Train Loss: 0.2154 Acc: 0.9721 Val Loss: 0.3215 Acc: 0.9453 Best val Acc: 0.95065. 高级技巧与性能提升为了进一步提升模型性能我们可以采用以下高级技巧标签平滑减轻模型对标签的过度自信class LabelSmoothingLoss(nn.Module): def __init__(self, smoothing0.1): super().__init__() self.smoothing smoothing def forward(self, logits, targets): num_classes logits.size(-1) log_preds F.log_softmax(logits, dim-1) with torch.no_grad(): targets targets * (1 - self.smoothing) self.smoothing / num_classes return (-targets * log_preds).sum(dim-1).mean()混合精度训练加速训练过程from torch.cuda.amp import GradScaler, autocast scaler GradScaler() for inputs, labels in train_loader: optimizer.zero_grad() with autocast(): outputs model(inputs) loss criterion(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()测试时增强(TTA)提升推理准确率def predict_with_tta(model, image, n_aug5): model.eval() with torch.no_grad(): aug_preds [] for _ in range(n_aug): aug_img train_transform(image) output model(aug_img.unsqueeze(0).to(device)) aug_preds.append(F.softmax(output, dim1)) return torch.mean(torch.stack(aug_preds), dim0)知识蒸馏使用更大的教师模型提升小模型性能teacher_model models.resnet101(pretrainedTrue) teacher_model.fc nn.Linear(teacher_model.fc.in_features, 102) teacher_model.load_state_dict(torch.load(teacher.pth)) def distillation_loss(student_output, teacher_output, labels, temp2.0, alpha0.5): soft_loss F.kl_div( F.log_softmax(student_output/temp, dim1), F.softmax(teacher_output/temp, dim1), reductionbatchmean) * (temp**2) hard_loss F.cross_entropy(student_output, labels) return alpha * soft_loss (1 - alpha) * hard_loss6. 部署与生产环境优化当模型训练完成后我们需要考虑如何高效地部署它模型量化减小模型大小加速推理quantized_model torch.quantization.quantize_dynamic( model, {nn.Linear}, dtypetorch.qint8) torch.save(quantized_model.state_dict(), quantized_model.pth)ONNX导出实现跨平台部署dummy_input torch.randn(1, 3, 224, 224).to(device) torch.onnx.export(model, dummy_input, flower_classifier.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}})TorchScript转换优化推理性能scripted_model torch.jit.script(model) scripted_model.save(flower_classifier.pt)Flask API服务创建RESTful接口from flask import Flask, request, jsonify import io from PIL import Image app Flask(__name__) model load_model() # 加载训练好的模型 app.route(/predict, methods[POST]) def predict(): if file not in request.files: return jsonify({error: no file uploaded}), 400 file request.files[file] img_bytes file.read() img Image.open(io.BytesIO(img_bytes)) # 预处理图像 img_tensor val_transform(img).unsqueeze(0).to(device) # 预测 with torch.no_grad(): output model(img_tensor) _, pred torch.max(output, 1) return jsonify({class: train_dataset.classes[pred.item()]}) if __name__ __main__: app.run(host0.0.0.0, port5000)7. 常见问题与解决方案在实际项目中你可能会遇到以下问题及对应的解决方案问题1模型过拟合增加数据增强如颜色抖动、随机擦除添加更多Dropout层使用更严格的权重衰减尝试标签平滑技术问题2训练不稳定使用梯度裁剪torch.nn.utils.clip_grad_norm_尝试不同的优化器如AdamW调整学习率调度策略检查数据预处理是否正确问题3类别不平衡使用加权交叉熵损失对少数类进行过采样尝试焦点损失Focal Loss调整分类阈值问题4推理速度慢应用模型量化使用更小的输入尺寸如192x192转换为TensorRT引擎尝试知识蒸馏训练更小的模型通过本教程你应该已经掌握了使用PyTorch和ResNet-50进行迁移学习的完整流程。在实际项目中记得根据具体需求调整数据预处理、模型架构和训练策略。