利用 Resnet50 微调图像分类模型,完成宠物数据集的识别,附源代码。。

这篇文章中,我们从头训练了一个简单的图像识别模型,但是实际上,从头训练一个模型需要太多的时间成本了。

而且从头开始训练,需要你的数据集足够优秀,才可以训练出一个效果不错的模型。

那么,如果我想总有一个适合自己数据集的模型,除了从头开始训练之外,还有没有更好的方法呢?

当然有,这就是“微调”(Fine-tuning)。

这篇文章,就以 Resnet50 模型为例子,使用“Oxford-IIIT Pet Dataset” 这个包含了 37 类宠物的样本作为数据集,来详细介绍下微调的过程。

本文最后最后附有完整的微调代码。

Attention:全网最全的 AI 小白到 AI 大神的天梯成长学习路线,几十万原创专栏和硬核视频,点击这里查看:AI小白到AI大神的天梯之路

先看下什么是微调?

微调其实很简单,就是用一个已经预训练过的模型(预训练模型),稍微调整一下模型中某些层的权值,使其可以在新的任务上表现的更好。

ResNet 本身是一个深度卷积神经网络模型,这个模型在 ImageNet 数据集(包含 1000 种物品)上已经训练过了。

所以,我们可以认为,这个模型已经学会了图像识别的基础技能,比如怎么识别图像的边缘、颜色和形状等。

如果我们想用它来进一步识别宠物,其实不用从零开始训练,在这个预训练模型的基础上进行微调,才是一个更好地选择。

微调的原理:只改模型的关键点

如果你深入了解过卷积神经网络这一类的模型原理,你可能会知道,对 ResNet50 而言,前面的几层卷积,也就是浅层卷积,负责识别图片的基本特征信息。

比如“这条线是什么”、“这块颜色像什么”。

而越往后的卷积,识别到的图像特征越抽象。

比如最后一层全连接,其实负责的是把所有的特征信息综合起来,判断“这是哪种物体分类”了。

所以,有些时候最后的全连接层,也可以认为是起到分类器的作用。

对于卷积神经网络而言,微调的基本基本原理就是:

首先,冻结模型的浅层参数。

因为靠前的卷积层已经学会了如何识别图像的基础特征,这个功能很重要,也对所有的图片都适用。

所以我们在微调的时候就不改它们,这些参数冻结不动。

然后,改深层卷积的权值。

比如全连接层,它原来认识 1000 种物体,现在要认识 37 种宠物。

因此需要修改这一层的参数,使其输出37个分类。

最后,用新的数据集稍加训练。

接下来,我们将新的数据集(比如本例子中的宠物样本)输入给调整后的模型。

让它在原有识图能力的基础上,更专注识别新数据集的任务。

这样,一次微调任务就完成了。

接下来,我们就分几步把微调的过程用代码实现。

步骤1:准备数据

transform = transforms.Compose([    
    transforms.Resize(256), # 把图片缩到256x256    
    transforms.CenterCrop(224),# 剪成224x224        
    transforms.ToTensor(),# 转成数字格式        
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # 调整颜色值])   

train_dataset = datasets.OxfordIIITPet(root="./data", split="trainval", download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)

ResNet50 的输入只能是 224x224 的图片,因此需要对输入图片进行裁剪和缩放。

同时还需要调整图片的数据分布,这样才能和该模型在预训练时的数据分布参数匹配,达到更好地识图效果。

步骤2:加载现成模型

model = models.resnet50(weights=ResNet50_Weights.IMAGENET1K_V1)

我们直接用 ResNet50 这个训练好的模型,权重选择:

ResNet50_Weights.IMAGENET1K_V1。

这个参数说明使用的是在 imagenet 数据集上完成训练的权重。

步骤3:冻结浅层

for name, param in model.named_parameters():        
    if "layer4" not in name and "fc" not in name:        
        param.requires_grad = False  # 冻结这些层的参数

模型的前面几层卷积识别特征的通用能力很强。

这里,我们冻结这些层,只让最后一层卷积(layer4)和全连接层(fc)的参数可以变化。

步骤4:修改分类数目

model.fc = nn.Linear(model.fc.in_features, 37)

ResNet50 原来输出1000种分类,我们需要将其改成 37 种,以此来匹配宠物数据集的分类。

步骤5:用新数据训练

optimizer = optim.Adam([    
    {"params": model.fc.parameters(), "lr": 1e-3}, # 最后一层学快点        
    {"params": model.layer4.parameters(), "lr": 1e-5}  # 前一层学慢点])    

for epoch in range(1, 16):    
    train(epoch)  # 训练    
    test(epoch)   # 测试

训练过程中,layer4 设置较小的学习率,最后一层设置较大的学习率。

经过 15 轮的微调训练后,模型就可以很好的识别出测试集里的37种宠物,准确率可以达到到80%-90%。

所以,比起从零开始训练,微调用时短,效果也不错。

事实上,模型微调的核心其实就三点:

第一点,找一个现成的预训练模型。

第二点,冻结预训练模型的基础部分,改动关键部分。

第三点,用新数据稍微训练一下。

如果你想让 AI 完成其他的物体分类,比如花、车或者其他图片,微调确实是个简单又好用的办法。

大家可以试一试。

本文完整代码在下面的链接:

https://github.com/dongdongcan/ai_model_samples/blob/main/resnet50_fine_tune_oxford_iiit_pet/oxford_iiit_pet_fine_tune.py

### 基于ResNet50实现图像分类模型微调 #### 数据集准备与加载 为了成功应用ResNet50进行图像分类,首先需要准备好数据集并将其加载到程序中。通常情况下,可以使用PyTorch或TensorFlow等框架来处理数据集。对于CIFAR-10这样的标准数据集,可以直接通过内置工具获取和预处理[^1]。 以下是使用PyTorch加载CIFAR-10数据集的一个简单示例: ```python import torch from torchvision import datasets, transforms transform = transforms.Compose([ transforms.Resize((224, 224)), # 将图像调整为适合ResNet50输入大小 transforms.ToTensor(), ]) train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform) train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=32, shuffle=True) test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=32, shuffle=False) ``` #### 构建ResNet50模型 接着,可以通过调用预训练ResNet50模型,并根据具体任务的需求修改其最后一层全连接层以适配新的类别数。如果目标是将ResNet50应用于其他特定领域(如蛋糕图像或多类别的宠物识别),则需替换默认的输出层以匹配新数据集的标签数量][^[^23]。 下面展示了一个基于PyTorch构建ResNet50模型的例子: ```python import torchvision.models as models num_classes = 10 # CIFAR-10有10个类别 model = models.resnet50(pretrained=True) # 替换最后的全连接层 in_features = model.fc.in_features model.fc = torch.nn.Linear(in_features, num_classes) ``` #### 定义优化器与损失函数 在完成上述操作后,还需要定义合适的优化算法以及损失计算方法以便后续训练过程能够顺利开展。常用的组合方式包括随机梯度下降(SGD)配合交叉熵作为主要评价指标之一[^4]。 这里给出一段关于配置这些组件的具体代码片段: ```python criterion = torch.nn.CrossEntropyLoss() optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1) ``` #### 训练与验证阶段 进入实际训练环节之前,请记得把整个流程封装成易于管理的形式;比如创建单独的方法分别负责单轮迭代内的前向传播、反向传播更新权重等工作逻辑。与此同时,在每一轮结束后统计测试集合上的表现情况从而决定何时停止或者保存最优版本权值文件。 以下是一段简化版的训练循环演示: ```python device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model.to(device) best_acc = 0.0 for epoch in range(num_epochs): model.train() running_loss = 0.0 for inputs, labels in train_loader: inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() scheduler.step() correct = 0 total = 0 with torch.no_grad(): model.eval() for inputs, labels in test_loader: inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() accuracy = correct / total if accuracy > best_acc: best_acc = accuracy torch.save(model.state_dict(), './BestCheckPoint/resnet50-best.pth') print(f"Best Accuracy: {best_acc}") ``` #### 结果分析与改进策略 经过一系列实验发现,原始ResNet50模型在某些条件下可能无法达到理想效果。此时可考虑进一步优化方案,例如增加正则化手段防止过拟合现象发生、尝试不同初始化技巧提高收敛速度或是探索更先进的迁移学习技术提升泛化能力等措施[^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董董灿是个攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值