PyTor迁移学习教程及示例

⚡ 智能摘要

迁移学习重用已在大数据集上训练好的网络,以便用更少的标记图像和更少的原始训练时间来解决新的相关任务。

  • 🔘 核心思想: ImageNet 上学习到的权重已经编码了边缘、纹理和形状,大多数视觉任务都会重复使用这些权重。
  • ☑️ 两种策略: 特色示例trac调整会冻结主干网络,而微调则会继续训练部分或全部原始层。
  • 示例: 大约 700 张《异形》和《铁血战士》的图片就足以重新训练 VGG19 的最后一层。
  • 🧪 PyTorch 碎片: ImageFolder、transforms.Compose 和 DataLoader 组装网络使用的批次。
  • 🛠️ 冻结层: 将 requires_grad 设置为 False 会停止梯度,因此只有被替换的分类器才能学习。
  • ⚠️ 报告结果: 在样本数据集上,25 个 epoch 的训练时间不到 3 分钟。

PyTor迁移学习教程及示例

什么是迁移学习?

转移学习 是一种利用训练好的模型来解决另一个相关任务的技术。它是一种 机器学习 这种研究方法将解决特定问题过程中获得的知识存储起来,并利用这些知识来解决另一个不同但相关的问题。通过重用先前任务中收集的信息,这种方法提高了效率。

由于从头开始训练一个网络需要大量数据,因此复用其他网络模型的权重是一种常见的做法。为了缩短训练时间,可以采用现有的网络及其权重,并修改最后一层来解决自身问题。这样做的好处是,最后一层只需少量数据集即可进行训练。

在编写任何 Py 代码之前Torch 代码,了解你的问题属于迁移学习的哪一类很有帮助,因为这决定了你需要多少标记数据。

迁移学习的类型

研究文献将这项技术分为三类。具体哪一类适用于你,取决于问题的哪一方被贴上了标签,而不是你使用的框架。

类型 源域 Target 域 典型用途
感应的 标签 已贴标签,但任务不同 将 ImageNet 主干网重新指向双类异形大战铁血战士问题
转导 标签 未标记,相同任务,不同数据分布 领域自适应,例如将模型从影棚照片迁移到手机照片。
无监督 未标记 未标记 Cluster降维或降维,因为给每条记录贴标签是不切实际的。

本教程中构建的示例是归纳式迁移学习。VGG19 模型携带了标注的 ImageNet 知识,然后将其应用于一个它从未见过的标注二分类问题。

特色trac调整与微调

选择预训练网络后,有两种方法可以对其进行调整。区别仅仅在于允许多少层继续学习。

方面 特色示例tracTION 微调
训练层 只有被替换的分类器 分类器加上部分或全部卷积块
主干上需要 grad 对于正在更新的块,此属性成立。
所需数据 规模较小,通常每类只有几百张图片。 更大的,通常有数千个
培训费用 最低配置,运行在 CPU 上 性能越高,GPU就越值得拥有。
典型精度 当源图像和目标图像相似时效果很好。 通常情况下,当两个域不同时效果会更好。

以下步骤使用功能示例trac说明:所有 VGG19 参数都被冻结,只有最终的线性层进行学习。切换到微调模式只需进行一些小改动,即在需要更新的模块中将 requires_grad 设置为 True,并降低学习率,这样借用的权重就不会被破坏。

加载数据集

在你开始使用 Py 进行迁移学习之前Tor首先,你需要了解你将要使用的数据集。在这个迁移学习 Py 中Tor例如,你需要从近 700 张图片中对外星人和铁血战士进行分类。这种方法并不需要大量的训练数据。你可以从以下链接下载数据集: Kaggle:异形大战铁血战士.

该系列作品数量刻意控制在较小范围内,下面展示其中部分图片。

本 Py 程序使用了来自 Kaggle 的《异形大战铁血战士》图像数据集。Tor迁移学习示例

来源: 异形大战 捕食者 Kaggle

接下来是 PyTor本迁移学习教程将教你如何使用 Py 应用迁移学习。Tor一步一步来。

如何使用迁移学习?

以下是使用 Py 进行深度学习的迁移学习的分步指南。TorCH:

步骤1)加载数据

第一步是加载数据并对图像进行一些转换,使其符合网络的要求。

您将从名为 torchvision.datasets 的文件夹中加载数据。该模块会遍历该文件夹,将数据拆分为训练集和验证集。此处使用的转换流程会裁剪图像中心区域,将其转换为张量,并对其进行归一化处理。 深度学习.

from __future__ import print_function, division
import os
import time
import torch
import torchvision
from torchvision import datasets, models, transforms
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt

data_dir = "alien_pred"
input_shape = 224
mean = [0.5, 0.5, 0.5]
std = [0.5, 0.5, 0.5]

#data transformation
data_transforms = {
   'train': transforms.Compose([
       transforms.CenterCrop(input_shape),
       transforms.ToTensor(),
       transforms.Normalize(mean, std)
   ]),
   'validation': transforms.Compose([
       transforms.CenterCrop(input_shape),
       transforms.ToTensor(),
       transforms.Normalize(mean, std)
   ]),
}

image_datasets = {
   x: datasets.ImageFolder(
       os.path.join(data_dir, x),
       transform=data_transforms[x]
   )
   for x in ['train', 'validation']
}

dataloaders = {
   x: torch.utils.data.DataLoader(
       image_datasets[x], batch_size=32,
       shuffle=True, num_workers=4
   )
   for x in ['train', 'validation']
}

dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'validation']}

print(dataset_sizes)
class_names = image_datasets['train'].classes

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

现在将数据集可视化。可视化步骤从训练数据加载器中获取下一批图像和标签,并使用 Matplotlib 进行显示。

images, labels = next(iter(dataloaders['train']))

rows = 4
columns = 4
fig=plt.figure()
for i in range(16):
   fig.add_subplot(rows, columns, i+1)
   plt.title(class_names[labels[i]])
   img = images[i].numpy().transpose((1, 2, 0))
   img = std * img + mean
   plt.imshow(img)
plt.show()

运行该代码片段会绘制一个 4x4 的训练图片网格,每张图片都以加载器返回的类名作为标题。

一批包含十六张训练图像的 4x4 Matplotlib 网格,并带有类别标题。

步骤2)定义模型

在这个深度学习过程中,您将使用 torchvision 模块中的 VGG19。

您将使用 torchvision.models 加载启用预训练权重的 vgg19 模型。之后,冻结模型层,使其无法训练。然后,将最后一层修改为适合问题的线性层,这里指的是两类问题。损失函数使用 CrossEntropyLoss,优化器使用学习率为 0.001、动量为 0.9 的 SGD,如下面的 Python 代码所示。Torch 迁移学习示例。

## Load the model based on VGG19
vgg_based = torchvision.models.vgg19(pretrained=True)

## freeze the layers
for param in vgg_based.parameters():
   param.requires_grad = False

# Modify the last layer
number_features = vgg_based.classifier[6].in_features
features = list(vgg_based.classifier.children())[:-1] # Remove last layer
features.extend([torch.nn.Linear(number_features, len(class_names))])
vgg_based.classifier = torch.nn.Sequential(*features)

vgg_based = vgg_based.to(device)

print(vgg_based)

criterion = torch.nn.CrossEntropyLoss()
optimizer_ft = optim.SGD(vgg_based.parameters(), lr=0.001, momentum=0.9)

版本说明: 这个 预训练=True 该论点仍然有效,但自 torchvision 0.13 版本以来已被取代。 权重 因此,较新的安装程序会要求提供此参数。 torchvision.models.vgg19(weights=VGG19_Weights.DEFAULT) 否则,将打印弃用警告。两种形式加载相同的 ImageNet 权重。

输出模型结构

打印模型会返回完整的 VGG19 图。阅读分类器代码块的最后一行,确认交换是否成功:现在它生成 2 个输出,而不是之前的 1,000 个 ImageNet 类别。

VGG(
  (features): Sequential(
	(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(1): ReLU(inplace)
	(2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(3): ReLU(inplace)
	(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
	(5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(6): ReLU(inplace)
	(7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(8): ReLU(inplace)
	(9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
	(10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(11): ReLU(inplace)
	(12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(13): ReLU(inplace)
	(14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(15): ReLU(inplace)
	(16): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(17): ReLU(inplace)
	(18): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
	(19): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(20): ReLU(inplace)
	(21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(22): ReLU(inplace)
	(23): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(24): ReLU(inplace)
	(25): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(26): ReLU(inplace)
	(27): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
	(28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(29): ReLU(inplace)
	(30): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(31): ReLU(inplace)
	(32): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(33): ReLU(inplace)
	(34): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
	(35): ReLU(inplace)
	(36): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (classifier): Sequential(
	(0): Linear(in_features=25088, out_features=4096, bias=True)
	(1): ReLU(inplace)
	(2): Dropout(p=0.5)
	(3): Linear(in_features=4096, out_features=4096, bias=True)
	(4): ReLU(inplace)
	(5): Dropout(p=0.5)
	(6): Linear(in_features=4096, out_features=2, bias=True)
  )
)

步骤3)训练和测试模型

我们将使用其中的一些函数。 PyTorch 教程 帮助我们训练和评估我们的模型。

def train_model(model, criterion, optimizer, num_epochs=25):
   since = time.time()

   for epoch in range(num_epochs):
       print('Epoch {}/{}'.format(epoch, num_epochs - 1))
       print('-' * 10)

       #set model to trainable
       # model.train()

       train_loss = 0

       # Iterate over data.
       for i, data in enumerate(dataloaders['train']):
           inputs , labels = data
           inputs = inputs.to(device)
           labels = labels.to(device)

           optimizer.zero_grad()
          
           with torch.set_grad_enabled(True):
               outputs  = model(inputs)
               loss = criterion(outputs, labels)

           loss.backward()
           optimizer.step()

           train_loss += loss.item() * inputs.size(0)

           print('{} Loss: {:.4f}'.format(
               'train', train_loss / dataset_sizes['train']))
          
   time_elapsed = time.time() - since
   print('Training complete in {:.0f}m {:.0f}s'.format(
       time_elapsed // 60, time_elapsed % 60))

   return model

def visualize_model(model, num_images=6):
   was_training = model.training
   model.eval()
   images_so_far = 0
   fig = plt.figure()

   with torch.no_grad():
       for i, (inputs, labels) in enumerate(dataloaders['validation']):
           inputs = inputs.to(device)
           labels = labels.to(device)

           outputs = model(inputs)
           _, preds = torch.max(outputs, 1)

           for j in range(inputs.size()[0]):
               images_so_far += 1
               ax = plt.subplot(num_images//2, 2, images_so_far)
               ax.axis('off')
               ax.set_title('predicted: {} truth: {}'.format(class_names[preds[j]], class_names[labels[j]]))
               img = inputs.cpu().data[j].numpy().transpose((1, 2, 0))
               img = std * img + mean
               ax.imshow(img)

               if images_so_far == num_images:
                   model.train(mode=was_training)
                   return
       model.train(mode=was_training)

最后,在 Py 中进行迁移学习Tor例如,训练过程开始时,训练轮数设置为 25,之后评估网络。在每个训练步骤中,模型接收输入并预测输出。预测结果传递给准则以计算损失,反向传播计算梯度,优化器使用自动微分更新权重。

在可视化功能中,使用一批图像对训练好的网络进行测试,以预测标签,并使用 Matplotlib 绘制结果。

vgg_based = train_model(vgg_based, criterion, optimizer_ft, num_epochs=25)

visualize_model(vgg_based)

plt.show()

步骤4)结果

本次运行的准确率为 92%。训练结束时打印的日志显示了最后两个 epoch 的运行损失以及总训练时间。

Epoch 23/24
----------
train Loss: 0.0044
train Loss: 0.0078
train Loss: 0.0141
train Loss: 0.0221
train Loss: 0.0306
train Loss: 0.0336
train Loss: 0.0442
train Loss: 0.0482
train Loss: 0.0557
train Loss: 0.0643
train Loss: 0.0763
train Loss: 0.0779
train Loss: 0.0843
train Loss: 0.0910
train Loss: 0.0990
train Loss: 0.1063
train Loss: 0.1133
train Loss: 0.1220
train Loss: 0.1344
train Loss: 0.1382
train Loss: 0.1429
train Loss: 0.1500
Epoch 24/24
----------
train Loss: 0.0076
train Loss: 0.0115
train Loss: 0.0185
train Loss: 0.0277
train Loss: 0.0345
train Loss: 0.0420
train Loss: 0.0450
train Loss: 0.0490
train Loss: 0.0644
train Loss: 0.0755
train Loss: 0.0813
train Loss: 0.0868
train Loss: 0.0916
train Loss: 0.0980
train Loss: 0.1008
train Loss: 0.1101
train Loss: 0.1176
train Loss: 0.1282
train Loss: 0.1323
train Loss: 0.1397
train Loss: 0.1436
train Loss: 0.1467
Training complete in 2m 47s

然后使用 Matplotlib 将模型的预测结果可视化,如下所示。

训练后,用预测类别和真实类别标记的验证图片

常见的迁移学习错误及其解决方法

迁移学习脚本中的大多数错误都是机械性的,而非数学性的。以下列出了导致上述代码无法运行的错误,并解释了每种错误的含义。

  • 最后一层尺寸不匹配: 替换后的线性层必须接受原始分类器报告的 in_features,并输出恰好 len(class_names) 个类名。像步骤 2 中那样打印模型是确认这两个数值的最快方法。
  • 脊柱从未被冻结: 如果 requires_grad 设置为 True,则 VGG19 的所有参数都会更新,导致 CPU 运行速度极慢。在替换分类器之前,请将其设置为 False。
  • 标准化不匹配: transforms.Normalize 中使用的均值和标准差在训练和推理时必须相同,否则预测结果会无缘无故地发生漂移。
  • 已弃用的权重参数: 在 torchvision 0.13 及更高版本中,pretrained=True 会引发弃用警告,建议使用 weights 枚举。
  • num_workers 在 Windows 以及笔记本: 如果 DataLoader 的 num_workers=4,则需要保护入口点,因此如果 num_workers=4,则应将其设置为 0。 Python 引发生成或序列化错误。
  • 在训练模式下进行评估: 在评分之前调用 model.eval(),以便 Dropout 和 BatchNorm 能够确定性地运行,之后再切换回 model.train()。

常见问题

如果只训练最后一层,每个类别通常只需要几百张图像;本例总共使用了大约 700 张图片。微调更深层的模块则需要更多图像——通常需要几千张——因为需要更新的参数要多得多。

ResNet、VGG、EfficientNet 和 Vision Transformer 等骨干网络都自带 torchvision 权重。ResNet50 是常用的默认权重,因为它在精度和模型大小之间取得了平衡。这里使用的 VGG19 则是一个更重的权重。 卷积网络 但很容易逐层剖析。

不。冻结主干网络并训练单层模型在 CPU 上可以接受,这也是为什么上面的运行能在三分钟内完成的原因。但要微调整个网络,或者用数千张图像进行训练,GPU 就必不可少了。

自动模型搜索会使用您的数据样本对多个预训练网络进行基准测试,并根据准确率、延迟和规模对其进行排名。这消除了骨干网络选择的猜测性,并且与超参数搜索完美结合。 scikit学习.

它能很好地生成样板代码,因为转换、DataLoader 设置和训练循环都遵循常见的模式。检查它无法推断的部分:输出类别的数量、归一化统计信息以及 requires_grad 是否确实被关闭。

负迁移是指源域与目标域差异过大,导致借用的权重反而造成负面影响。需要注意验证损失是否过早趋于平稳,或者是否高于从零开始训练的小型网络。

是的。语言模型先在大规模文本语料库上进行预训练,然后根据训练结果进行调整,用于分类或问答任务,同样的思路也适用于…… 序列模型音频和表格嵌入。只有主干网络发生变化。

远少于从头开始训练的网络。这里使用了 25 个 epoch,但对于特征提取来说,10 到 20 个 epoch 通常就足够了。traction. 当验证损失不再改善时停止,而不是运行固定的计数。

总结一下这篇文章: