深度学习--卷积神经网络之迁移学习ResNet

📅 发布时间:2026/7/10 9:29:10 👁️ 浏览次数:
深度学习--卷积神经网络之迁移学习ResNet
一、迁移学习1.迁移学习是什么迁移学习是指利用已经训练好的模型在新的任务上进行微调。迁移学习可以加快模型训练速度提高模型性能并且在数据稀缺的情况下也能很好地工作。2.迁移学习的步骤1、选择预训练的模型和适当的层通常我们会选择在大规模图像数据集如ImageNet上预训练的模型如VGG、ResNet等。然后根据新数据集的特点选择需要微调的模型层。对于低级特征的任务如边缘检测最好使用浅层模型的层而对于高级特征的任务如分类则应选择更深层次的模型。2、冻结预训练模型的参数保持预训练模型的权重不变只训练新增加的层或者微调一些层避免因为在数据集中过拟合导致预训练模型过度拟合。3、在新数据集上训练新增加的层在冻结预训练模型的参数情况下训练新增加的层。这样可以使新模型适应新的任务从而获得更高的性能。4、微调预训练模型的层在新层上进行训练后可以解冻一些已经训练过的层并且将它们作为微调的目标。这样做可以提高模型在新数据集上的性能。5、评估和测试在训练完成之后使用测试集对模型进行评估。如果模型的性能仍然不够好可以尝试调整超参数或者更改微调层。二、具体案例要求使用预训练的ResNet18模型进行迁移学习完成一个20类的食物分类任务。主要模块解析1.数据预处理部分data_transforms { train: transforms.Compose([ transforms.Resize([256, 256]), # 统一图片尺寸 transforms.RandomHorizontalFlip(), # 随机水平翻转数据增强 transforms.ToTensor(), # 转为Tensor格式 transforms.Normalize([0.485, 0.456, 0.406], # ImageNet标准化均值 [0.229, 0.224, 0.225]) # ImageNet标准化标准差 ]), # 验证集不做数据增强 }训练集包含数据增强随机翻转提高模型泛化能力验证集只做基本处理保证评估的稳定性标准化使用ImageNet的均值和标准差因为模型是在ImageNet上预训练的2.迁移学习模型构建resnet_model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT)加载在ImageNet上预训练的ResNet18模型ImageNet有1000类我们的任务只需要20类# 冻结所有卷积层参数 for param in resnet_model.parameters(): param.requires_grad False # 替换原始的全连接层 resnet_model.fc nn.Linear(in_features, 20)冻结特征提取层保留预训练模型在ImageNet上学到的通用特征替换分类头只训练新的全连接层适应新任务20类这种特征提取方式的迁移学习可以大大减少训练时间和数据需求3.自定义数据集类class food_dataset(Dataset): def __init__(self, file_path, transformNone): # 从txt文件读取图片路径和标签 with open(self.file_path, r) as f: samples [x.strip().split( ) for x in f.readlines()]数据集格式txt文件每行包含图片路径 标签实现了错误处理无法加载图片时用黑色图片替代返回格式(图像Tensor, 标签Tensor)4.训练和测试函数def train(dataloader, model, loss_fn, optimizer): model.train() # 设置为训练模式 # 标准训练流程前向传播 → 计算损失 → 反向传播 → 优化 def test(dataloader, model, loss_fn): model.eval() # 设置为评估模式 with torch.no_grad(): # 不计算梯度节省内存 # 计算准确率和损失5.训练配置和循环device cuda if torch.cuda.is_available() else mps if torch.backends.mps.is_available() else cpu自动选择可用设备CUDA (NVIDIA GPU) → MPS (Apple Silicon) → CPUoptimizer torch.optim.Adam(param_to_update, lr0.001)只优化需要训练的参数只有新的全连接层使用Adam优化器学习率0.0016.模型保存torch.save(model,qianyixixue_best1.pth) # 保存完整模型保存的是整个模型包括网络结构和参数注释掉的torch.save(model.state_dict(), ...)只保存参数关键点总结技术点实现方式作用迁移学习预训练ResNet18 替换分类头利用ImageNet知识适应新任务冻结策略requires_grad False保留通用特征只训练分类器数据增强RandomHorizontalFlip提高泛化能力防止过拟合设备自适应自动检测CUDA/MPS/CPU跨平台兼容早停策略保存最佳准确率模型防止过拟合保留最优结果完整代码import torch from torch.utils.data import Dataset, DataLoader import numpy as np from PIL import Image from torchvision import transforms import torchvision.models as models from torch import nn # 数据预处理 data_transforms { train: transforms.Compose([ transforms.Resize([256, 256]), transforms.RandomHorizontalFlip(), # 数据增强 transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # ImageNet标准化 ]), valid: transforms.Compose([ transforms.Resize([256, 256]), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), } 将resnet18模型迁移到食物分类项目上 resnet_model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) # 冻结所有卷积层参数 for param in resnet_model.parameters(): param.requires_grad False # 获取原始全连接层的输入特征数 in_features resnet_model.fc.in_features # 替换原始的全连接层 resnet_model.fc nn.Linear(in_features, 20) print(修改后的模型结构) print(resnet_model) # 收集需要更新的参数只有全连接层的参数 param_to_update [] for param in resnet_model.parameters(): if param.requires_grad: param_to_update.append(param) print(f\n需要训练的参数数量: {len(param_to_update)}) class food_dataset(Dataset): def __init__(self, file_path, transformNone): self.file_path file_path self.imgs [] self.labels [] self.transform transform with open(self.file_path, r) 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(int(label)) # 转换为整数 def __len__(self): return len(self.imgs) def __getitem__(self, idx): try: image Image.open(self.imgs[idx]).convert(RGB) except Exception as e: print(f无法加载图片: {self.imgs[idx]}, 错误: {e}) image Image.new(RGB, (256, 256), colorblack) if self.transform: image self.transform(image) label self.labels[idx] label torch.tensor(label, dtypetorch.long) return image, label # 创建数据集 training_data food_dataset(file_path./trainda.txt, transformdata_transforms[train]) test_data food_dataset(file_path./testda.txt, transformdata_transforms[valid]) train_dataloader DataLoader(training_data, batch_size64, shuffleTrue) test_dataloader DataLoader(test_data, batch_size64, shuffleTrue) device cuda if torch.cuda.is_available() else mps if torch.backends.mps.is_available() else cpu print(f\nUsing {device} device) model resnet_model.to(device) def train(dataloader, model, loss_fn, optimizer): model.train() batch_size_num 1 total_loss 0 num_batches 0 for X, y in dataloader: X, y X.to(device), y.to(device) # 前向传播 pred model(X) loss loss_fn(pred, y) # 反向传播 optimizer.zero_grad() loss.backward() optimizer.step() # 统计信息 loss_value loss.item() total_loss loss_value num_batches 1 # 每100个batch打印一次 if batch_size_num % 100 0: print(floss: {loss_value:7f} [batch number: {batch_size_num}]) batch_size_num 1 # 返回平均损失 avg_loss total_loss / num_batches return avg_loss def test(dataloader, model, loss_fn): size len(dataloader.dataset) num_batches len(dataloader) model.eval() test_loss, correct 0, 0 with torch.no_grad(): for X, y in dataloader: X, y X.to(device), y.to(device) pred model(X) test_loss loss_fn(pred, y).item() correct (pred.argmax(1) y).sum().item() test_loss / num_batches correct / size print(f结果: \nAccuracy: {100 * correct:.2f}%, Avg loss: {test_loss:.6f}) return 100 * correct, test_loss # 初始化损失函数和优化器 loss_fn nn.CrossEntropyLoss() optimizer torch.optim.Adam(param_to_update, lr0.001) # 训练循环 epochs 10 best_acc 0 best_model_state None for t in range(epochs): print(f\nEpoch {t 1}/{epochs}) print(- * 50) # 训练 train_avg_loss train(train_dataloader, model, loss_fn, optimizer) print(fTraining Avg Loss: {train_avg_loss:.6f}) # 测试 acc, test_loss test(test_dataloader, model, loss_fn) # 更新最佳准确率和保存最佳模型 if acc best_acc: best_acc acc # best_model_state model.state_dict().copy() # print(f保存新的最佳模型准确率: {acc:.2f}%) # torch.save(model.state_dict(),qianyixuexi_best.pth)#获取模型中的全部wb参数后缀也可以是ph,t7 torch.save(model,qianyixuexi_best1.pth)#保存完整模型w,b模型cnn模型信息也保存下来 print(f准确率: {acc:.2f}%) print(f\n训练完成最优准确率为: {best_acc:.2f}%)三、ResNet网络1.什么是ResNetResNet 网络是在 2015年 由微软实验室中的何凯明等几位大神提出斩获当年ImageNet竞赛中分类任务第一名目标检测第一名。获得COCO数据集中目标检测第一名图像分割第一名。2.传统卷积神经网络存在的问题卷积神经网络都是通过卷积层和池化层的叠加组成的。在实际的试验中发现随着卷积层和池化层的叠加学习效果不会逐渐变好反而出现2个问题 1、梯度消失和梯度爆炸梯度消失若每一层的误差梯度小于1反向传播时网络越深梯度越趋近于0梯度爆炸若每一层的误差梯度大于1反向传播时网络越深梯度越来越大2、退化问题3.Batch Normalization如何解决问题为了解决梯度消失或梯度爆炸问题论文提出通过数据的预处理以及在网络中使用 BNBatch Normalization层来解决。为了解决深层网络中的退化问题可以人为地让神经网络某些层跳过下一层神经元的连接隔层相连弱化每层之间的强联系。这种神经网络被称为 残差网络 (ResNets)。实线为测试集错误率虚线为训练集错误率4.残差网络residual结构使用了一种shortcut的连接方式也可理解为捷径。让特征矩阵隔层相加注意F(X)和X形状要相同所谓相加是特征矩阵相同位置上的数字进行相加。5.Batch Normalization目的使所有的feature map满足均值为0方差为1的分布规律