目录

计算机视觉中的迁移学习教程

创建日期: 2017年3月24日 | 最后更新日期: 2024年8月27日 | 最后验证日期: 2024年11月5日

作者: Sasank Chilamkurthy

在本教程中,您将学习如何使用迁移学习训练卷积神经网络进行图像分类。您可以阅读更多关于迁移学习的内容,请参阅 cs231n 笔记

引用这些笔记,

In practice, very few people train an entire Convolutional Network from scratch (with random initialization), because it is relatively rare to have a dataset of sufficient size. Instead, it is common to pretrain a ConvNet on a very large dataset (e.g. ImageNet, which contains 1.2 million images with 1000 categories), and then use the ConvNet either as an initialization or a fixed feature extractor for the task of interest.

这两种主要的迁移学习场景如下所示:

  • ConvNet 的微调: 我们不是进行随机初始化,而是使用一个预训练网络,例如在 ImageNet 1000 数据集上训练过的网络。其余的训练过程与以往相同。

  • ConvNet 作为固定特征提取器: 在这里,我们将冻结网络中除最后一层全连接层外的所有权重。这最后一层全连接层被替换为一个新的全连接层,其权重为随机值,并且只有这一层会被训练。

# License: BSD
# Author: Sasank Chilamkurthy

import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
import torch.backends.cudnn as cudnn
import numpy as np
import torchvision
from torchvision import datasets, models, transforms
import matplotlib.pyplot as plt
import time
import os
from PIL import Image
from tempfile import TemporaryDirectory

cudnn.benchmark = True
plt.ion()   # interactive mode
<contextlib.ExitStack object at 0x7effb18a2050>

加载数据

我们将使用torchvision和torch.utils.data包来加载数据。

今天我们要解决的问题是训练一个模型来分类蚂蚁蜜蜂。我们有大约120张训练图片,分别用于蚂蚁和蜜蜂。 每类有75张验证图片。通常来说,这是一个非常小的数据集,如果从头开始训练的话,很难泛化得很好。但由于我们使用了迁移学习,我们应该能够较好地泛化。

这个数据集是imagenet的一个非常小的子集。

注意

这里下载数据并将其解压到当前目录。

# Data augmentation and normalization for training
# Just normalization for validation
data_transforms = {
    'train': transforms.Compose([
        transforms.RandomResizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
    'val': transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}

data_dir = 'data/hymenoptera_data'
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
                                          data_transforms[x])
                  for x in ['train', 'val']}
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4,
                                             shuffle=True, num_workers=4)
              for x in ['train', 'val']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
class_names = image_datasets['train'].classes

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

可视化几幅图像

让我们可视化一些训练图像,以便理解数据增强。

def imshow(inp, title=None):
    """Display image for Tensor."""
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    inp = std * inp + mean
    inp = np.clip(inp, 0, 1)
    plt.imshow(inp)
    if title is not None:
        plt.title(title)
    plt.pause(0.001)  # pause a bit so that plots are updated


# Get a batch of training data
inputs, classes = next(iter(dataloaders['train']))

# Make a grid from batch
out = torchvision.utils.make_grid(inputs)

imshow(out, title=[class_names[x] for x in classes])
['ants', 'ants', 'ants', 'ants']

训练模型

现在,让我们编写一个通用的函数来训练模型。在这里,我们将说明:

  • 调整学习率的时间安排

  • 保存最佳模型

在以下内容中,参数 scheduler 是一个来自 torch.optim.lr_scheduler 的LR调度器对象。

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

    # Create a temporary directory to save training checkpoints
    with TemporaryDirectory() as tempdir:
        best_model_params_path = os.path.join(tempdir, 'best_model_params.pt')

        torch.save(model.state_dict(), best_model_params_path)
        best_acc = 0.0

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

            # Each epoch has a training and validation phase
            for phase in ['train', 'val']:
                if phase == 'train':
                    model.train()  # Set model to training mode
                else:
                    model.eval()   # Set model to evaluate mode

                running_loss = 0.0
                running_corrects = 0

                # Iterate over data.
                for inputs, labels in dataloaders[phase]:
                    inputs = inputs.to(device)
                    labels = labels.to(device)

                    # zero the parameter gradients
                    optimizer.zero_grad()

                    # forward
                    # track history if only in train
                    with torch.set_grad_enabled(phase == 'train'):
                        outputs = model(inputs)
                        _, preds = torch.max(outputs, 1)
                        loss = criterion(outputs, labels)

                        # backward + optimize only if in training phase
                        if phase == 'train':
                            loss.backward()
                            optimizer.step()

                    # statistics
                    running_loss += loss.item() * inputs.size(0)
                    running_corrects += torch.sum(preds == labels.data)
                if phase == 'train':
                    scheduler.step()

                epoch_loss = running_loss / dataset_sizes[phase]
                epoch_acc = running_corrects.double() / dataset_sizes[phase]

                print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')

                # deep copy the model
                if phase == 'val' and epoch_acc > best_acc:
                    best_acc = epoch_acc
                    torch.save(model.state_dict(), best_model_params_path)

            print()

        time_elapsed = time.time() - since
        print(f'Training complete in {time_elapsed // 60:.0f}m {time_elapsed % 60:.0f}s')
        print(f'Best val Acc: {best_acc:4f}')

        # load best model weights
        model.load_state_dict(torch.load(best_model_params_path, weights_only=True))
    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['val']):
            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(f'predicted: {class_names[preds[j]]}')
                imshow(inputs.cpu().data[j])

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

Finetuning the ConvNet

加载预训练模型,并重置最终的全连接层。

model_ft = models.resnet18(weights='IMAGENET1K_V1')
num_ftrs = model_ft.fc.in_features
# Here the size of each output sample is set to 2.
# Alternatively, it can be generalized to ``nn.Linear(num_ftrs, len(class_names))``.
model_ft.fc = nn.Linear(num_ftrs, 2)

model_ft = model_ft.to(device)

criterion = nn.CrossEntropyLoss()

# Observe that all parameters are being optimized
optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)

# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)
Downloading: "https://download.pytorch.org/models/resnet18-f37072fd.pth" to /var/lib/ci-user/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth

  0%|          | 0.00/44.7M [00:00<?, ?B/s]
 44%|####4     | 19.8M/44.7M [00:00<00:00, 207MB/s]
 89%|########9 | 39.8M/44.7M [00:00<00:00, 208MB/s]
100%|##########| 44.7M/44.7M [00:00<00:00, 209MB/s]

训练和评估

在CPU上运行大约需要15-25分钟。而在GPU上,则只需不到一分钟。

model_ft = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler,
                       num_epochs=25)
Epoch 0/24
----------
train Loss: 0.4761 Acc: 0.7623
val Loss: 0.2903 Acc: 0.8627

Epoch 1/24
----------
train Loss: 0.5393 Acc: 0.7992
val Loss: 0.6532 Acc: 0.7386

Epoch 2/24
----------
train Loss: 0.4307 Acc: 0.8320
val Loss: 0.2170 Acc: 0.9281

Epoch 3/24
----------
train Loss: 0.6076 Acc: 0.7910
val Loss: 0.3034 Acc: 0.8824

Epoch 4/24
----------
train Loss: 0.3890 Acc: 0.8525
val Loss: 0.2492 Acc: 0.9085

Epoch 5/24
----------
train Loss: 0.4929 Acc: 0.8197
val Loss: 0.2845 Acc: 0.8889

Epoch 6/24
----------
train Loss: 0.3594 Acc: 0.8361
val Loss: 0.2774 Acc: 0.8954

Epoch 7/24
----------
train Loss: 0.4217 Acc: 0.8115
val Loss: 0.2437 Acc: 0.9020

Epoch 8/24
----------
train Loss: 0.2371 Acc: 0.9016
val Loss: 0.2413 Acc: 0.9216

Epoch 9/24
----------
train Loss: 0.2811 Acc: 0.8648
val Loss: 0.2473 Acc: 0.9085

Epoch 10/24
----------
train Loss: 0.3361 Acc: 0.8648
val Loss: 0.2125 Acc: 0.9346

Epoch 11/24
----------
train Loss: 0.3290 Acc: 0.8484
val Loss: 0.2937 Acc: 0.8954

Epoch 12/24
----------
train Loss: 0.2295 Acc: 0.9016
val Loss: 0.2473 Acc: 0.9085

Epoch 13/24
----------
train Loss: 0.2972 Acc: 0.8648
val Loss: 0.2357 Acc: 0.9216

Epoch 14/24
----------
train Loss: 0.2716 Acc: 0.8811
val Loss: 0.2788 Acc: 0.8954

Epoch 15/24
----------
train Loss: 0.3079 Acc: 0.8443
val Loss: 0.3347 Acc: 0.8824

Epoch 16/24
----------
train Loss: 0.2281 Acc: 0.9139
val Loss: 0.2408 Acc: 0.9020

Epoch 17/24
----------
train Loss: 0.2544 Acc: 0.9057
val Loss: 0.2331 Acc: 0.9216

Epoch 18/24
----------
train Loss: 0.2837 Acc: 0.8975
val Loss: 0.2432 Acc: 0.9020

Epoch 19/24
----------
train Loss: 0.1827 Acc: 0.9303
val Loss: 0.2225 Acc: 0.9281

Epoch 20/24
----------
train Loss: 0.2550 Acc: 0.8934
val Loss: 0.2539 Acc: 0.9020

Epoch 21/24
----------
train Loss: 0.2541 Acc: 0.8934
val Loss: 0.2751 Acc: 0.9020

Epoch 22/24
----------
train Loss: 0.3206 Acc: 0.8607
val Loss: 0.2277 Acc: 0.9150

Epoch 23/24
----------
train Loss: 0.2752 Acc: 0.8852
val Loss: 0.2637 Acc: 0.9020

Epoch 24/24
----------
train Loss: 0.2931 Acc: 0.8770
val Loss: 0.2243 Acc: 0.9216

Training complete in 1m 4s
Best val Acc: 0.934641
visualize_model(model_ft)
predicted: ants, predicted: bees, predicted: ants, predicted: bees, predicted: bees, predicted: ants

ConvNet 作为固定特征提取器

在这里,我们需要冻结网络中的所有层,除了最终层。我们需要将requires_grad = False设置为冻结参数,以便在backward()中不计算梯度。

您可以在文档中阅读更多关于此内容 这里

model_conv = torchvision.models.resnet18(weights='IMAGENET1K_V1')
for param in model_conv.parameters():
    param.requires_grad = False

# Parameters of newly constructed modules have requires_grad=True by default
num_ftrs = model_conv.fc.in_features
model_conv.fc = nn.Linear(num_ftrs, 2)

model_conv = model_conv.to(device)

criterion = nn.CrossEntropyLoss()

# Observe that only parameters of final layer are being optimized as
# opposed to before.
optimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.001, momentum=0.9)

# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_conv, step_size=7, gamma=0.1)

训练和评估

在CPU上,这将花费大约一半的时间,相比于之前的场景。 这符合预期,因为大多数网络部分不需要计算梯度。然而,前向传播还是需要计算的。

model_conv = train_model(model_conv, criterion, optimizer_conv,
                         exp_lr_scheduler, num_epochs=25)
Epoch 0/24
----------
train Loss: 0.6996 Acc: 0.6516
val Loss: 0.2014 Acc: 0.9346

Epoch 1/24
----------
train Loss: 0.4233 Acc: 0.8033
val Loss: 0.2656 Acc: 0.8758

Epoch 2/24
----------
train Loss: 0.4603 Acc: 0.7869
val Loss: 0.1847 Acc: 0.9477

Epoch 3/24
----------
train Loss: 0.3096 Acc: 0.8566
val Loss: 0.1747 Acc: 0.9477

Epoch 4/24
----------
train Loss: 0.4427 Acc: 0.8156
val Loss: 0.1630 Acc: 0.9477

Epoch 5/24
----------
train Loss: 0.5505 Acc: 0.7828
val Loss: 0.1643 Acc: 0.9477

Epoch 6/24
----------
train Loss: 0.3004 Acc: 0.8607
val Loss: 0.1744 Acc: 0.9542

Epoch 7/24
----------
train Loss: 0.4083 Acc: 0.8361
val Loss: 0.1892 Acc: 0.9412

Epoch 8/24
----------
train Loss: 0.4483 Acc: 0.7910
val Loss: 0.1984 Acc: 0.9477

Epoch 9/24
----------
train Loss: 0.3335 Acc: 0.8279
val Loss: 0.1942 Acc: 0.9412

Epoch 10/24
----------
train Loss: 0.2413 Acc: 0.8934
val Loss: 0.2001 Acc: 0.9477

Epoch 11/24
----------
train Loss: 0.3107 Acc: 0.8689
val Loss: 0.1801 Acc: 0.9412

Epoch 12/24
----------
train Loss: 0.3032 Acc: 0.8689
val Loss: 0.1669 Acc: 0.9477

Epoch 13/24
----------
train Loss: 0.3587 Acc: 0.8525
val Loss: 0.1900 Acc: 0.9477

Epoch 14/24
----------
train Loss: 0.2771 Acc: 0.8893
val Loss: 0.2317 Acc: 0.9216

Epoch 15/24
----------
train Loss: 0.3064 Acc: 0.8852
val Loss: 0.1909 Acc: 0.9477

Epoch 16/24
----------
train Loss: 0.4243 Acc: 0.8238
val Loss: 0.2227 Acc: 0.9346

Epoch 17/24
----------
train Loss: 0.3297 Acc: 0.8238
val Loss: 0.1916 Acc: 0.9412

Epoch 18/24
----------
train Loss: 0.4235 Acc: 0.8238
val Loss: 0.1766 Acc: 0.9477

Epoch 19/24
----------
train Loss: 0.2500 Acc: 0.8934
val Loss: 0.2003 Acc: 0.9477

Epoch 20/24
----------
train Loss: 0.2413 Acc: 0.8934
val Loss: 0.1821 Acc: 0.9477

Epoch 21/24
----------
train Loss: 0.3762 Acc: 0.8115
val Loss: 0.1842 Acc: 0.9412

Epoch 22/24
----------
train Loss: 0.3485 Acc: 0.8566
val Loss: 0.2166 Acc: 0.9281

Epoch 23/24
----------
train Loss: 0.3625 Acc: 0.8361
val Loss: 0.1747 Acc: 0.9412

Epoch 24/24
----------
train Loss: 0.3840 Acc: 0.8320
val Loss: 0.1768 Acc: 0.9412

Training complete in 0m 32s
Best val Acc: 0.954248
visualize_model(model_conv)

plt.ioff()
plt.show()
predicted: bees, predicted: ants, predicted: bees, predicted: bees, predicted: ants, predicted: ants

自定义图片上的推理

使用训练好的模型对自定义图片进行预测,并可视化预测出的类别标签以及图片。

def visualize_model_predictions(model,img_path):
    was_training = model.training
    model.eval()

    img = Image.open(img_path)
    img = data_transforms['val'](img)
    img = img.unsqueeze(0)
    img = img.to(device)

    with torch.no_grad():
        outputs = model(img)
        _, preds = torch.max(outputs, 1)

        ax = plt.subplot(2,2,1)
        ax.axis('off')
        ax.set_title(f'Predicted: {class_names[preds[0]]}')
        imshow(img.cpu().data[0])

        model.train(mode=was_training)
visualize_model_predictions(
    model_conv,
    img_path='data/hymenoptera_data/val/bees/72100438_73de9f17af.jpg'
)

plt.ioff()
plt.show()
Predicted: bees

进一步学习

如果您想了解更多关于迁移学习的应用,请查看我们的计算机视觉教程中的量化迁移学习

脚本总运行时间: (1分钟39.312秒)

通过 Sphinx-Gallery 生成的画廊

文档

访问 PyTorch 的全面开发人员文档

查看文档

教程

获取面向初学者和高级开发人员的深入教程

查看教程

资源

查找开发资源并解答您的问题

查看资源