🐑 |迁移学习| 迁移学习详解及基于pytorch的相关代码实现 🐑
文章目录
- 🐑 |迁移学习| 迁移学习详解及基于pytorch的相关代码实现 🐑
- 🐑 前言🐑
- 🐑 迁移学习详解🐑
- 🐑 迁移学习方法🐑
- 🐑 迁移学习实现🐑
- 🐑 ResNet50复现🐑
- 🐑 迁移学习训练🐑
- 🐑 总结🐑
🐑 前言🐑
前段时间一直在疯狂做实验,各种租服务器跑代码,现在感觉整个人跑的有点神志不清了,但好在最后的结果还可以接受,于是把最近用到的迁移学习相关理论以及整体实现记录一下。在代码实现方面本篇博客使用pytorch
作为框架,从搭建迁移学习模型开始到最后使用数据集做迁移学习的训练模板结束。
🐑 迁移学习详解🐑
迁移学习是一种机器学习的方法,它允许模型在新任务上获取从相关任务中获得的知识。这种技术特别有用,因为在某些情况下,我们可能无法为特定问题收集足够的训练数据,或者收集和标注数据的成本非常高昂。通过使用已经在类似或相关任务上训练好的模型,我们可以有效地“转移”这些知识到新的任务上。
这个是迁移学习比较官方的一个注释,解释一下就是将一个表现很好的大模型的性能转移到自己任务训练的模型上。举个例子就是比如学过钢琴的人可以利用他对于五线谱的优势以及音准的优势去学习小提琴;再比如跳舞的人可以利用自身的柔韧性以及肢体协调能力等更容易地学会体操。然而在图片分类(image classification
)的问题中,也有很多迁移学习的实例。对于一个已有的可以正确识别出图片中猫和狗的分类器,该分类器通过学习大量带有🐱和🐶标签的图片获得。如果将该分类器应用于一个新的任务,去识别🐘和🐴,而传统机器学习的方法由于缺少大量可用的带有🐘和🐴标签的图片作为训练数据而遇到瓶颈,这时候利用迁移学习的思想,从两者数据集之间图片的相关性可以看出,此时利用🐱 🐶数据集的相关参数去优化🐘 🐴数据集的参数,则可以使模型快速收敛的同时获得一个良好的泛化能力。
也就是说当自己任务的数据集规模较小的情况下,正常使用神经网络去训练很难获得一个良好的性能,而且质量较低的数据集也直接决定着训练模型的性能的上限不会很高,而此时如果使用迁移学习直接使用在大模型上训练好的参数进行微调的话,一方面会发现收敛速度加快,另一方面效果相较于不用迁移学习相比肯定不会变差。
🐑 迁移学习方法🐑
简单介绍几个关于迁移学习的相关方法。我们根据不同的分类准则,可以使用不同的方式将现有的迁移学习方法进行分类总结。
其中基于样本的迁移学习很好理解,就像上面例子中的🐱 🐶分类迁移到🐘 🐴分类这种 人为提升目标任务中样本学习的权重。
基于特征的迁移学习是目前使用次数最多的一种迁移学习方法,对于两个毫不相干的任务以及数据集,依然可以使用迁移学习的方法。例如我将在ImageNet
训练的ResNet50
的参数迁移到我在全是心电信号上训练的ResNet50
上面,按理说ImageNet
的数据集和心电信号的数据集毫无关联,但是在预训练的模型中,模型学习到了大量的图片纹理、色彩等细节的特征,所以一方面可以使我迁移学习后的模型准确率提高,另一方面泛化能力也会加强。
🐑 迁移学习实现🐑
这里以ResNet50
为例,复现迁移学习的全过程。
🐑 ResNet50复现🐑
关于网络详细的内容部分这里就不多赘述了,详情可以看学长 @浩浩的科研笔记 ResNet残差网络一维、二维复现pytorch-含残差块复现思路分析。
复现代码如下:
import torch
import os
import torchvision.models as models
class Bottleneck(torch.nn.Module):
def __init__(self,in_channels,mid_channels,output_channels,down_sample = False,use_1x1conv = False):
super().__init__()
if down_sample:
self.stride = 2
else:
self.stride = 1
self.use_1x1conv = use_1x1conv
self.conv = torch.nn.Conv2d(in_channels,output_channels,1,self.stride)
self.res = torch.nn.Sequential(
torch.nn.Conv2d(in_channels,mid_channels,1,self.stride),
torch.nn.BatchNorm2d(mid_channels),
torch.nn.ReLU(),
torch.nn.Conv2d(mid_channels,mid_channels,3,padding=1),
torch.nn.BatchNorm2d(mid_channels),
torch.nn.ReLU(),
torch.nn.Conv2d(mid_channels,output_channels,1),
torch.nn.BatchNorm2d(output_channels),
torch.nn.ReLU()
)
def forward(self,x):
residual = x
out = self.res(x)
if self.use_1x1conv:
residual = self.conv(residual)
out = out+residual
out = torch.nn.functional.relu(out)
return out
class Resnet50(torch.nn.Module):
def __init__(self,input_channels,classes):
super().__init__()
self.input_channels = input_channels
# self.output_channels = output_channels
self.classes = classes
self.conv_1 = torch.nn.Sequential(
torch.nn.Conv2d(input_channels,64,7,2,3),
torch.nn.MaxPool2d(3,2,1),
Bottleneck(64,64,256,True,True),
Bottleneck(256,64,256),
Bottleneck(256,64,256),
Bottleneck(256,128,512,True,True),
Bottleneck(512,128,512),
Bottleneck(512, 128, 512),
Bottleneck(512, 128, 512),
Bottleneck(512,256,1024,True,True),
Bottleneck(1024,128,1024),
Bottleneck(1024, 128, 1024),
Bottleneck(1024, 128, 1024),
Bottleneck(1024, 128, 1024),
Bottleneck(1024, 128, 1024),
)
self.conv_2 = torch.nn.Sequential(
Bottleneck(1024, 512, 2048, True, True),
Bottleneck(2048, 512, 2048),
Bottleneck(2048, 512, 2048),
torch.nn.AdaptiveAvgPool2d(1)
)
self.classfier = torch.nn.Sequential(
torch.nn.Flatten(),
torch.nn.Linear(2048,self.classes)
)
def forward(self,x):
x = self.conv_1(x)
x = self.conv_2(x)
x = self.classfier(x)
return x
def download_pretrained_model(save_path):
# 检查保存路径是否存在
os.makedirs(save_path, exist_ok=True)
model_path = os.path.join(save_path, 'resnet50_pretrained.pth')
if not os.path.exists(model_path):
# 下载预训练模型权重
pretrained_resnet50 = models.resnet50(pretrained=True)
torch.save(pretrained_resnet50.state_dict(), model_path)
print(f"预训练模型权重已下载并保存到: {model_path}")
else:
print(f"预训练模型权重已存在于: {model_path}")
return model_path
if __name__ == '__main__':
# 创建自定义的 Resnet50 模型
model = Resnet50(3, 10)
# 加载预训练的权重
pretrained_dict = torch.load('pretrained_weights/resnet50_pretrained.pth')
model_dict = model.state_dict()
# 过滤掉不匹配的权重
pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}
model_dict.update(pretrained_dict)
model.load_state_dict(model_dict)
# 测试前向传播
x = torch.randn(200, 3, 224, 224)
y = model(x)
print(y.shape)
这里需要注意的一点是因为我们预训练的模型是使用ImageNet
训练出来的,那时候输入数据格式为三通道大小为224×224。所以我们自己的数据也需要处理为(3,224,224)
🐑 迁移学习训练🐑
在使用预训练的权重训练时候,为了保证能够有一个更好地泛化能力,我们一般对特征提取的网络层 参数做尽可能少的变化,主要以调整分类层网络参数为主,这方面我们通常以设置不同的学习率来实现,例如这次的ResNet50
网络。
其中红色框和蓝色框标注的为特征提取层,绿色的是分类层。在设置优化器学习率时候为了保证有一个良好的泛化能力,将红色框中的网络设置为1e-5
,蓝色为1e-4
,绿色为1e-3
。
下面是训练优化器部分代码:
#optimizer
learning_rate_feature_extractor_1 = 1e-5
learning_rate_feature_extractor_2 = 1e-4
learning_rate_classfier = 1e-3
optimizer = torch.optim.SGD([
{'params':model.conv_1.parameters(),'lr':learning_rate_feature_extractor_1,'momentum':0.1},
{'params':model.conv_2.parameters(),'lr':learning_rate_feature_extractor_2,'momentum':0.9},
{'params':model.classfier.parameters(),'lr':learning_rate_classfier,'momentum':0.9}
])
#criterion
criterion = torch.nn.CrossEntropyLoss()
后续加入到自己的训练中即可。
🐑 总结🐑
由于使用迁移学习的话对于输入数据格式限制比较大,所以在数据预处理的方面一定要先处理好,另外使用迁移学习也完全不用担心模型训练结果变差,只要训练足够多的轮次,模型性能和不使用迁移学习相比可能不会变好,但一定不会变差,但我们一般为了一个较好的额泛化能力一般都会只训练20轮左右。
如果有写的不对的地方欢迎指出。