pytorch使用记录(一) Transfer Learning tutorial | 如何使用预训练模型VGG16_bn
PyTorch官方提供了一个详细指南来介绍如何在深度学习模型之间迁移知识。该教程旨在帮助开发者逐步掌握基于预训练模型的知识迁移技术,并通过实际案例展示其应用方法。访问链接Comprehensive Guide to Transfer Learning即可获取完整的学习材料。
该课程深入探讨了Transfer Learning技术及其在深度学习中的应用,在CS 231n课程的教学资料中提供了详细的技术实现与实践指导
迁移学习(Transfer Learning)
迁移学习在深度学习领域具有重要意义。其本质是将现成的模型参数转移至新的模型架构。现实中很少有学者选择从零开始训练卷积神经网络。其主要原因在于缺乏足够的数据集来进行有效的模型训练;此外,在缺乏足够计算资源和时间的情况下难以进行重新验证。通常会选择现有的大型公开数据集进行基础训练,并以ImageNet为例,在完成基础预 training 后再利用这些预 trained 的模型参数快速部署到自己的应用中以提升效率
主要包含两种典型场景:其一是以卷积网络作为固定的特征提取工具,在经过大量数据集的训练后(...),去除最后的全连接层部分(...),仅进行分类器微调(...)。
经过预训练后,在新收集的数据集上进行微调训练时,不仅替换了原有的全连接层参数以构建新的分类器模型,并且对整个卷积神经网络模型的所有权重参数都进行了反向传播和优化调整.
微调(fine-tune)的注意事项:
决定采用微调策略的关键因素包括两大关键因素:一是新数据集的大小S;二是其相对于预训练数据集的相似度。
记住卷积网络在提取特征的过程中,在前面的部分能够捕获更为普遍的信息,在后面的阶段则会更加专注于细节,并且对原始数据集表现出更强的适应性。
四个基本原则:1. 当新数据集规模较小且高度相关时,应避免对模型进行微调以防止过拟合;2. 建议仅训练基础线性分类器以确保模型的泛化能力
当新数据集规模较大且高度相似时,由于存在充足的训练数据,则可实现网络的微调训练。
3、当新数据集规模较小且与现有数据高度不匹配时,在网络后部进行分类器训练可能效果不佳(相较于在更早的网络层进行SVM分类器训练)
4、新数据集大而且不相似时,因为数据集足够大,所以有充分的信心可以训练好。[数据集大就是可以随心所欲...]
另外提醒一下,在调整预训练网络时,请尽量避免修改其结构,并将学习率设置为一个较小的初始值。
这些内容主要涉及迁移学习的相关知识;在PyTorch框架中有丰富的预训练模型可供选择;接下来重点介绍如何利用VGG16_bn 工具实现目标;遵循官方文档的标准组织形式;欢迎各位根据自身需求提出问题或建议
想直接看重点的,可以直接看模型预训练 部分。
Load data
为了在PyTorch环境中使用预训练模型处理图像数据时的最佳效果,在输入图像上必须遵循特定要求:其形状需满足高度(H)和宽度(W)分别至少达到224像素;同时必须遵循指定均值[μ_r=0.485, μ_g=0.456, μ_b=0.406]和标准差[σ_r=0.171, σ_g=177σ_b=177]进行标准化处理
主要依赖 torchvision.transforms 这个包,它包含了对图片执行基本操作的功能.随后将列举一些关键功能.
Compose() :将不同的transforms操作组合在一起。参数以列表形式存储。
torchvision.transforms.Compose(transforms)
通过在代码中使用RandomResizedCrop() 函数来实现对图片进行处理:即通过该函数采用随机选择大小和宽高比例的方式进行裁剪,并将处理后的图像设置为指定的目标尺寸。
torchvision.transforms.RandomResizedCrop(size, scale=(0.08, 1.0), ratio=(0.75, 1.3333333333333333), interpolation=2)
参数: size:输出的图片大小
scale:裁剪的大小范围,默认是原图的0.08-1.0
ratio: 才见到宽高比,默认是原图的3/4-4/3
interpolation:插值,默认是PIL.Image.BILINEAR
RandomHorizontalFlip() : 对指定的图片进行随机水平翻转,默认概率为0.5。即有50%的概率进行翻转或保持不变。
torchvision.transforms.RandomHorizontalFlip(p=0.5)
Normalize():对一个Tensor图像应用基于指定均值和标准差参数的标准化处理。
input[channel]=(input[channel])-mean[channel])/std[channel]
torchvision.transforms.Normalize(mean, std)
参数: mean:均值,列表形式,给出各个通道的均值。 如[0.485, 0.456, 0.406]
std: 标准差,列表形式,给出各个通道的标准差。如[0.229, 0.224, 0.225]
Visualize a few images
目的是更好的理解对数据的增益变换(第一步的操作)。
觉得有些地方在numpy库中涉及 transpose 函数时会让人感到不易理解。官方文档为:transpose
transpose() 在二维情况下遵循常规定义作为转置操作,在高维空间中则不易于被理解。
numpy.transpose(a,axes=None)
参数: a:输入的array
axes: 进行变换的轴顺序。
对于tensor,使用transpose(a, argsort(axes))。通常是inp.transpose(1,0,2)
个人感受认为,在transposed operation中axis与shape之间存在某种对应关系。另一种更直观的理解方式是将形状的变化作为参考。
如x的shape是(2,3,4),此时的轴顺序为(0,1,2),x' = x.transpose((1,0,2)), 那么x'的shape变成了(3,2,4)。
从理解的角度来看,在数学上,x由两个3×4的二维实数数组构成,x是一个二维数组类型的数据结构。其中axes被调整为(1,0,2),这将导致每个切片沿着不同的维度进行重新排列组合的操作。这种变换的具体实现方式是将原始数据按照特定索引顺序重新组织成新的形状结构。在这种情况下,x经过变换后将被转换成一个三维数组形式(即形状变成(n,h,w))。具体的转换过程包括将输入数据按照指定轴向进行切片重组,并通过一系列计算步骤生成输出数据序列。整个过程通过重新排列输入数据的空间维度来达到预期效果
执行该操作后会将x分割为两个4×3矩阵,并对每个矩阵都进行了转置变换。
clip() 是一种用于限制数值范围的功能。当输入的数组元素超出指定的一个范围时(即若超出该范围),其数值将被调整为该范围的端点之一(具体取靠近端点的一侧)。例如,在区间[0,1]的情况下,则数值0.23通过该函数处理后将被限制在该范围内。
numpy.clip(a, a_min, a_max, out=None)
参数:a:输入
a_min被定义为最小参数,在数学上可以表示为单个数值、序列结构或None类型(这种情况下则不会参与任何比较运算)
a_max:最大值,同样可以是标量、数组或者none
out:输出
该函数负责将标准化后的图像恢复至原始状态;具体来说,在调用torchvision.transforms.Normalize()时,默认会进行通道维度上的标准化操作,默认情况下会将通道维度设为第0位的位置上,并根据这一特点确定了transpose((1,2,0))的具体应用原因。
torchvision.utils.make_grid():实现图片的网格化输出。
torchvision.utils.make_grid(tensor, nrow=8, padding=2, normalize=False, range=None, scale_each=False, pad_value=0
参数设置:指定输出张量。该参数可取两种类型:第一种是张量(Tensor),其形状必须为(B × C × H × W);第二种是尺寸一致的图像列表。
nrow表示每行所包含的图片数量,默认设置为8。最终生成的grid结构将基于这些参数计算出具体的(行数, 列数)。其中,每一行所包含的图片数量等于nrow(即行数),而列数同样由nrow决定。
padding: 填充 默认是2
normalize:归一化处理,默认设置为false;该操作会将图片数据进行标准化处理,并将其像素值缩放至0至1之间;具体来说,默认情况下会将图像中的最小像素值减去后再除以最大像素值的最大差值得到最终结果
range:遵循与normalize配合使用的模式,默认情况下以元组(min, max)的形式存在,并由张量计算得出。
scale_each:设为true,则会对每个图像分别进行缩放处理,并单独计算每个图像的最小值和最大值范围(min,max),而不是从整个批次的所有图片中统一计算出最小值和最大值范围,默认设置为false
pad_value: 填充的像素值,默认是0
一般也就第一个参数会用到,其他都选择默认就行。
接下来才是重头戏:
模型预训练
官方文档中遵循以下流程:训练模型(保存模型参数)→ 参数可视化 → 具体的两种方式分别为微调(fine-tune)和固定特征提取器(fixed feature extractor)。
但是觉得不是很好理解,所以按照个人理解来讲解。、
(一)首先是讲解几个用到的函数 :
state_dict() : 该函数返回一个包含模型全部状态信息的字典。它不仅包括参数(parameter),还特别提到了持续的缓冲区(persistent buffers)。如果有更好的翻译建议,请随时留言。
state_dict(destination=None, prefix='', keep_vars=False)
参数: destination: 参考源码,该参数表示最终的输出结果,默认设置为None(即从无开始),建议避免手动更改默认值。
prefix: 前缀,放在parameter和buffer前,默认是空,不建议修改
keep_vars: 默认是false,不建议修改,否则会影响param的赋值。
load_state_dict() : 加载预训练模型的参数。
load_state_dict(state_dict, strict=True)
参数: state_dict: 字典形式,包含预训练好的模型参数
strict:其默认值设置为true,并且要求在预训练模型中使用的各个参数键(即parameters' keys)必须与新构建模型中的相应键(new model's keys)保持完全一致
(二)介绍两种主要的模型存储与加载方法。其中涉及的核心技术包括Serialization semantics。
1、官方推荐方法:只保存模型参数
torch.save(the_model.state_dict(), PATH)
只加载模型参数
the_model = TheModelClass(*args, **kwargs)
the_model.load_state_dict(torch.load(PATH))
2、保存和加载整个模型
torch.save(the_model, PATH)
加载模型
the_model = torch.load(PATH)
关于数据存储的格式选择问题,在深度学习框架中对文件后缀的要求有所不同。.pth 和 .pkl 文件格式在PyTorch环境下常被采用以实现数据持久化功能。通过基于cPickle库的测试表明二者在功能实现上具有完全一致性。鉴于PyTorch官方文档倾向于推荐采用 .pth 格式来存储模型参数以保证数据持久化效果更加稳定可靠,在后续开发过程中我会优先选择该格式进行数据存储操作
(三) **如何使用VGG16_bn预训练模型
**
官方代码库:vgg16_bn
1、如何修改预训练模型的结构
为了修改预训练模型的结构,需了解模型架构中各组件的名称.例如VGG网络可通过查看源代码识别其主要组成部分.
自定义特征包括 self.features 和 self.classfier。因此,在对模型进行最终优化阶段时
vgg_16_bn = models.vgg16_bn(pretrained=True)
vgg_16_bn.classifier = nn.Sequential(nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(True),
nn.Dropout())
这样就可以修改最后的全连接层。
2、只加载预训练模型的参数 ,并与自己的网络结构对应参数匹配
这里需要用到torch.utils.model_zoo.load_url()函数。使用方法如下
pretrain_state_dict = model_zoo.load_url('https://download.pytorch.org/models/vgg16_bn-6c64b313.pth')
从而得到预训练参数。
# 得到自己网络模型的参数、
model_dict = model.state_dict()
# 将pretrained中与自己模型不匹配的参数去掉
pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}
# 更新自己的model_dict
model_dict.update(pretrained_dict)
# 加载参数
model.load_state_dict(model_dict)
由于参数中的键即为各层名称,在修改模型时为了仅加载特定参数组别所需的权重,则更改后的层必须确保其名称与预训练模型中对应的层级不一致。
3、微调和固定参数不变
参数微调本质上就是基础训练的一种方式,并不需要施加额外的约束条件;持续进行训练以获得预期的结果
某些参数保持不变,在神经网络中需要确保各层的梯度无法反向传播以维持这些参数的稳定。为了实现这一目标,请确保所关注的参数的requires_grad属性被设为False状态,并详细说明其具体实现步骤如下:
for param in model_conv.parameters():
param.requires_grad = False
总结如下:这些记录主要涉及我对预训练模型的应用情况。这些情况下,在构造函数中定义时需要注意以下几点:如若希望将预训练模型纳入自己的网络架构中,则需编写相应的代码块以实现这一功能。其中涉及的具体实现细节包括但不限于数学公式的参数设置等细节部分,请参考相关技术文档获取详细指导信息。
self.vgg_16_bn = models.vgg16_bn(pretrained=True)
这种方案能够实现使用。然而,在实际应用中,默认情况下只有self这个参数会被传递进去;如果需要增加其他参数,则可以在函数定义中进行相应的修改。另外,在初始化过程中,默认情况下只有self这个参数会被传递进去;如果需要增加其他参数,则可以在函数定义中进行相应的修改。
