基于 ResNet18 的迁移学习:食物图像分类实现

张开发
2026/4/7 21:22:03 15 分钟阅读

分享文章

基于 ResNet18 的迁移学习:食物图像分类实现
基于ResNet18的迁移学习实现食物图像分类在计算机视觉领域图像分类是经典任务之一而面对特定领域的分类需求如食物分类从头训练深度神经网络不仅耗时耗力还需要大量的标注数据。迁移学习作为一种高效的建模方法能够将预训练模型在大规模数据集上学到的特征提取能力迁移到新任务中大幅降低训练成本并提升模型效果。本文将以ResNet18为预训练模型手把手教大家实现食物图像的20分类任务全程使用PyTorch框架完成代码编写与模型训练。一、迁移学习核心思路迁移学习的核心是复用预训练模型的特征提取层仅训练适配新任务的分类层。选用在ImageNet数据集上预训练的ResNet18模型其卷积层等底层结构已能提取通用的图像特征如边缘、纹理、形状等这些特征对食物图像同样适用。冻结预训练模型的所有特征提取层参数避免训练时破坏已学到的通用特征。替换ResNet18的最后全连接层fc层将原有的1000分类输出改为20分类适配食物分类任务。仅训练新替换的全连接层参数同时使用数据增强提升模型泛化能力最终完成食物图像分类模型的训练。二、环境准备本次实验基于PythonPyTorch框架需要安装以下核心依赖库pip install torch torchvision pillow numpytorch/torchvisionPyTorch核心框架提供预训练模型、数据处理工具和神经网络模块。pillowPython图像处理库用于读取和处理图像。numpy数值计算库用于数据类型转换等操作。同时确保电脑具备GPUNVIDIA CUDA或Apple MPS加速能力大幅提升训练速度。三、完整代码实现与详解接下来将分模块讲解代码从预训练模型加载、数据处理、数据集构建到模型训练与评估实现端到端的食物分类模型开发。3.1 导入核心库首先导入实验所需的所有Python库涵盖模型、数据处理、神经网络层等模块import torch import torchvision.models as models # 包含各类预训练视觉模型 from torch import nn from torch.utils.data import Dataset, DataLoader # 自定义数据集和数据加载器 from torchvision import transforms # 图像变换与数据增强 from PIL import Image import numpy as np3.2 加载预训练模型并改造加载ResNet18预训练模型冻结特征层参数替换分类层以适配食物20分类任务# 加载预训练ResNet18模型使用默认预训练权重 resnet_model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) # 冻结所有特征层参数禁止梯度更新 for param in resnet_model.parameters(): param.requires_grad False # 获取原fc层的输入特征数替换为20分类的全连接层 in_features resnet_model.fc.in_features resnet_model.fc nn.Linear(in_features, 20) # 20为食物分类的类别数关键说明weightsmodels.ResNet18_Weights.DEFAULT加载官方在ImageNet上的预训练权重替代旧版的pretrainedTruePyTorch新版本推荐用法。param.requires_grad False冻结参数让这些层在训练时不更新梯度仅保留特征提取能力。替换fc层ResNet18的最后一层是全连接层原输出为1000类ImageNet此处改为20类仅该层参数参与后续训练。3.3 图像变换与数据增强针对训练集和验证集设计不同的图像变换策略训练集使用数据增强提升泛化能力验证集仅做基础变换保证数据一致性data_transforms { train: transforms.Compose([ transforms.Resize([300, 300]), # 缩放图像至300*300 transforms.RandomRotation(45), # 随机旋转(-45,45)度 transforms.CenterCrop(224), # 中心裁剪至224*224ResNet输入尺寸 transforms.RandomHorizontalFlip(p0.5), # 50%概率水平翻转 transforms.RandomVerticalFlip(p0.5), # 50%概率垂直翻转 transforms.RandomGrayscale(p0.1), # 10%概率转为灰度图 transforms.ToTensor(), # 转为Tensor像素值归一化至[0,1] # 按ImageNet均值和标准差归一化与预训练模型保持一致 transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), valid: transforms.Compose([ transforms.Resize([224, 224]), # 直接缩放至224*224 transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), }数据增强的意义通过随机旋转、翻转、裁剪等操作生成更多“虚拟训练样本”避免模型过拟合提升对不同角度、尺度食物图像的识别能力。归一化说明必须使用ImageNet的均值和标准差因为预训练模型是在该归一化规则下训练的保证特征提取的一致性。3.4 自定义食物数据集PyTorch的Dataset类是自定义数据集的基础此处实现读取食物图像路径和标签的自定义数据集适配txt格式的样本清单每行图像路径 标签class food_dataset(Dataset): def __init__(self, file_path, transformNone): self.file_path file_path # 样本清单txt文件路径 self.imgs [] # 存储所有图像路径 self.labels [] # 存储所有图像标签 self.transform transform # 图像变换策略 # 读取txt文件解析图像路径和标签 with open(self.file_path) as f: samples [x.strip().split( ) for x in f.readlines()] for img_path, label in samples: self.imgs.append(img_path) self.labels.append(label) # 必须实现返回数据集样本总数 def __len__(self): return len(self.imgs) # 必须实现根据索引返回单个样本图像标签 def __getitem__(self, idx): # 读取图像Pillow默认读取为RGB格式 image Image.open(self.imgs[idx]) # 应用图像变换 if self.transform: image self.transform(image) # 标签转换为64位整数Tensor适配PyTorch损失函数 label torch.from_numpy(np.array(self.labels[idx], dtypenp.int64)) return image, label数据集格式要求需准备trainda.txt训练集和testda.txt验证集每行格式为xxx/xxx/food.jpg 0其中0为类别标签0-19。3.5 创建数据加载器通过DataLoader将自定义数据集封装为批量迭代器实现批量读取、随机打乱、多进程加载PyTorch自动实现# 创建训练集和验证集 training_data food_dataset(file_path./trainda.txt, transformdata_transforms[train]) test_data food_dataset(file_path./testda.txt, transformdata_transforms[valid]) # 创建数据加载器batch_size64表示每次读取64个样本 train_dataloader DataLoader(training_data, batch_size64, shuffleTrue) test_dataloader DataLoader(test_data, batch_size64, shuffleTrue)参数说明batch_size64根据GPU显存调整显存小则调小如32、16。shuffleTrue训练集每次迭代前打乱样本顺序避免模型学习样本顺序规律验证集打乱仅为方便不影响结果。3.6 设备配置与模型初始化自动检测并使用GPUCUDA/MPS将模型移至指定设备同时定义损失函数、优化器和学习率调度器# 自动选择训练设备CUDA MPS CPU device cuda if torch.cuda.is_available() else mps if torch.backends.mps.is_available() else cpu print(fUsing {device} device) # 将模型移至指定设备 model resnet_model.to(device) # 定义损失函数交叉熵损失适用于多分类任务 loss_fn nn.CrossEntropyLoss() # 定义优化器Adam优化器仅优化可训练参数此处为新fc层 optimizer torch.optim.Adam(model.parameters(), lr0.001) # 学习率调度器每训练10个epoch学习率乘以0.5 scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.5)关键说明交叉熵损失PyTorch的nn.CrossEntropyLoss已包含Softmax层无需在模型最后手动添加。Adam优化器自适应学习率优化器收敛速度快于SGD适合迁移学习的小批量训练。学习率调度器训练后期降低学习率让模型在最优解附近收敛提升精度。3.7 定义训练和验证函数训练函数实现模型的单次epoch训练包括前向传播、损失计算、反向传播、参数更新def train(dataloader, model, loss_fn, optimizer): model.train() # 将模型设为训练模式启用Dropout/BatchNorm等层的训练特性 for X, y in dataloader: # 将数据移至指定设备 X, y X.to(device), y.to(device) # 前向传播获取模型预测结果 pred model.forward(X) # 计算损失 loss loss_fn(pred, y) # 梯度清零避免上一批次梯度累积 optimizer.zero_grad() # 反向传播计算梯度 loss.backward() # 优化器更新参数仅更新可训练的fc层参数 optimizer.step()验证函数实现模型的单次epoch验证关闭梯度计算以提升速度计算验证集的准确率和平均损失并保存最优模型的准确率best_acc 0 # 保存最优验证准确率 acc_s [] # 记录每个epoch的验证准确率 loss_s [] # 记录每个epoch的验证损失 def test(dataloader, model, loss_fn): global best_acc size len(dataloader.dataset) # 验证集总样本数 num_batches len(dataloader) # 验证集总批次数 model.eval() # 将模型设为评估模式关闭Dropout/BatchNorm等层的训练特性 test_loss, correct 0, 0 # 关闭梯度计算节省显存并提升速度 with torch.no_grad(): for X, y in dataloader: X, y X.to(device), y.to(device) pred model.forward(X) # 累加损失和正确预测数 test_loss loss_fn(pred, y).item() # 取预测概率最大的类别作为预测结果统计正确数 correct (pred.argmax(1) y).type(torch.float).sum().item() # 计算平均损失和准确率 test_loss / num_batches correct / size print(fTest result: \n Accuracy: {(100 * correct)}%, Avg loss: {test_loss:.4f}) # 记录准确率和损失 acc_s.append(correct) loss_s.append(test_loss) # 更新最优准确率 if correct best_acc: best_acc correct3.8 模型训练与结果输出设置训练轮数epochs循环执行训练和验证每轮训练后更新学习率最终输出最优验证准确率epochs 10 # 训练轮数可根据效果调整 for t in range(epochs): print(fEpoch {t1}\n-------------------------------) train(train_dataloader, model, loss_fn, optimizer) scheduler.step() # 每轮训练后更新学习率 test(test_dataloader, model, loss_fn) print(Training done!) print(f最优验证准确率为{best_acc * 100:.2f}%)四、关键优化点与注意事项参数冻结必须冻结预训练模型的特征层参数否则训练时会覆盖已学到的通用特征不仅训练速度慢还容易过拟合。输入尺寸ResNet系列模型的标准输入尺寸为224*224需保证最终输入图像的尺寸符合要求。归一化规则必须使用ImageNet的均值和标准差与预训练模型的训练环境保持一致否则模型特征提取能力会大幅下降。数据格式标签必须转换为64位整数np.int64否则会与PyTorch的交叉熵损失函数数据类型不兼容。模型模式训练时用model.train()验证时用model.eval()避免Dropout和BatchNorm层影响验证结果。梯度清零每次批量训练前必须执行optimizer.zero_grad()否则梯度会累积导致参数更新错误。五、模型改进方向本文实现的基础版本已能完成食物分类任务若想进一步提升模型准确率和泛化能力可尝试以下改进策略微调Fine-tuning冻结部分特征层如仅冻结前几层让后几层特征层与分类层一起训练适配食物图像的专属特征。增加数据增强添加ColorJitter颜色抖动、RandomCrop随机裁剪等操作进一步丰富训练样本。调整超参数优化batch_size、学习率如初始lr设为0.0001、训练轮数或更换优化器如SGD动量。使用更大的预训练模型如ResNet50、ResNet101提升特征提取能力注意显存占用。添加早停Early Stopping当验证集损失连续多轮不下降时停止训练避免过拟合。模型保存在验证函数中添加模型保存代码保存最优准确率对应的模型权重方便后续推理使用if correct best_acc: best_acc correct torch.save(model.state_dict(), ./best_food_model.pth) # 保存最优模型可视化结果使用matplotlib绘制训练过程中的准确率和损失曲线直观分析模型训练趋势。六、总结本文以ResNet18为预训练模型通过迁移学习快速实现了食物图像20分类任务核心是复用预训练特征、仅训练分类层大幅降低了模型训练的成本。整个过程涵盖了PyTorch中自定义数据集、数据增强、模型改造、训练与验证的全流程是迁移学习在计算机视觉领域的典型应用。迁移学习不仅适用于食物分类还可推广到花卉、车辆、医疗图像等各类特定领域的图像分类任务只需根据任务需求调整分类层的类别数和数据集即可。掌握这一方法能让我们在面对新的计算机视觉任务时快速搭建高性能的模型无需从头开始训练。后续可基于本文的基础代码尝试模型改进策略进一步提升分类准确率并将训练好的模型部署到实际应用中如食物识别APP、智能点餐系统等。

更多文章