在这篇文章中,我们从头训练了一个简单的图像识别模型,但是实际上,从头训练一个模型需要太多的时间成本了。
而且从头开始训练,需要你的数据集足够优秀,才可以训练出一个效果不错的模型。
那么,如果我想总有一个适合自己数据集的模型,除了从头开始训练之外,还有没有更好的方法呢?
当然有,这就是"微调"(Fine-tuning)。
这篇文章,就以 Resnet50 模型为例子,使用"Oxford-IIIT Pet Dataset" 这个包含了 37 类宠物的样本作为数据集,来详细介绍下微调的过程。
本文最后最后附有完整的微调代码。
Attention:全网最全的 AI 小白到 AI 大神的天梯成长学习路线,几十万原创专栏和硬核视频,点击这里查看:AI小白到AI大神的天梯之路
先看下什么是微调?
微调其实很简单,就是用一个已经预训练过的模型(预训练模型),稍微调整一下模型中某些层的权值,使其可以在新的任务上表现的更好。
ResNet 本身是一个深度卷积神经网络模型,这个模型在 ImageNet 数据集(包含 1000 种物品)上已经训练过了。
所以,我们可以认为,这个模型已经学会了图像识别的基础技能,比如怎么识别图像的边缘、颜色和形状等。
如果我们想用它来进一步识别宠物,其实不用从零开始训练,在这个预训练模型的基础上进行微调,才是一个更好地选择。
微调的原理:只改模型的关键点
如果你深入了解过卷积神经网络这一类的模型原理,你可能会知道,对 ResNet50 而言,前面的几层卷积,也就是浅层卷积,负责识别图片的基本特征信息。
比如"这条线是什么"、"这块颜色像什么"。
而越往后的卷积,识别到的图像特征越抽象。
比如最后一层全连接,其实负责的是把所有的特征信息综合起来,判断"这是哪种物体分类"了。
所以,有些时候最后的全连接层,也可以认为是起到分类器的作用。
对于卷积神经网络而言,微调的基本基本原理就是:
首先,冻结模型的浅层参数。
因为靠前的卷积层已经学会了如何识别图像的基础特征,这个功能很重要,也对所有的图片都适用。
所以我们在微调的时候就不改它们,这些参数冻结不动。
然后,改深层卷积的权值。
比如全连接层,它原来认识 1000 种物体,现在要认识 37 种宠物。
因此需要修改这一层的参数,使其输出37个分类。
最后,用新的数据集稍加训练。
接下来,我们将新的数据集(比如本例子中的宠物样本)输入给调整后的模型。
让它在原有识图能力的基础上,更专注识别新数据集的任务。
这样,一次微调任务就完成了。
接下来,我们就分几步把微调的过程用代码实现。
步骤1:准备数据
python
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:加载现成模型
plain
model = models.resnet50(weights=ResNet50_Weights.IMAGENET1K_V1)
我们直接用 ResNet50 这个训练好的模型,权重选择:
ResNet50_Weights.IMAGENET1K_V1。
这个参数说明使用的是在 imagenet 数据集上完成训练的权重。
步骤3:冻结浅层
python
for name, param in model.named_parameters():
if "layer4" not in name and "fc" not in name:
param.requires_grad = False # 冻结这些层的参数
模型的前面几层卷积识别特征的通用能力很强。
这里,我们冻结这些层,只让最后一层卷积(layer4
)和全连接层(fc
)的参数可以变化。
步骤4:修改分类数目
plain
model.fc = nn.Linear(model.fc.in_features, 37)
ResNet50 原来输出1000种分类,我们需要将其改成 37 种,以此来匹配宠物数据集的分类。
步骤5:用新数据训练
python
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 完成其他的物体分类,比如花、车或者其他图片,微调确实是个简单又好用的办法。
大家可以试一试。
本文完整代码在下面的链接:
我创建了一个《小而精的AI学习圈》知识星球,星球上有几十万字原创高质量的技术专栏分享,同时你也可以在星球向我提问。 点击这里,我们星球见! >>>
点击这里查看所有 AI 技术专栏