• 计算机视觉的迁移学习演示


    本文讲述了如何使用迁移学习来对图片分类任务训练一个卷积神经网络。关于更多的迁移学习可以查看cs231n notes

    关于这些笔记:

    实际上,人们通常不会从头训练一整个卷积神经网络(从随机初始化权重开始),因为通常并没有足够大的数据集。相反,更常见的做法是在一个非常大的数据集上(例如ImageNet数据集,有着1000个类别的120万的图片)训练一个卷积网络,然后将这个网络作为想要的任务的初始化权重或者固定特征提取器(fixed feature extractor)。

    以下有两种主要的迁移学习的应用场景:

    • 微调卷积网络(finetuning the convnet):我们从一个预训练的网络模型开始我们的训练任务,而不是随机初始化权重的方式。
    • 将卷积网络作为固定特征提取器(ConvNet as fixed feature extractor):在这里,我们将冻结(freeze,固定权重)除了网络最后一层的全连接层的所有网络部分。将最后一层的全连接层替换为以随机权重初始化的层,然后只对新的一层进行训练。
    from __future__ import print_function, division
    
    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
    import copy
    
    cudnn.benchmark = True
    plt.ion()   # interactive mode
    
    <matplotlib.pyplot._IonContext at 0x7f590ec129d0>
    

    加载数据

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

    本文要解决的任务是训练一个分类模型,此模型用来分类蚂蚁和蜜蜂。对于这两个类别,我们大约有120张训练图像;75个验证图像。相对于从头开始训练,通常来说这是一个可以泛化(generalize)的非常小的数据集。所以我们将使用迁移学习,我们应该能够很合理的泛化模型。

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

    从此连接下载数据集,并且解压到当前目录。

    # 对于训练集,使用数据增强和规范化
    # 对于验证集,只使用规范化
    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 = "./hymenoptera_data/"
    images_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ["train", "val"]}
    dataloaders = {x: torch.utils.data.DataLoader(images_datasets[x], batch_size=4, shuffle=True, num_workers=4) for x in ["train", "val"]}
    dataset_sizes = {x: len(images_datasets[x]) for x in ['train', 'val']}
    class_names = images_datasets["train"].classes
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    

    绘制部分图像

    为了搞清楚数据增强做了什么事情,我们可以绘制出几张训练图像。

    def imshow(inp, title=None):
        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)
    
    # 获得一个批量的训练数据
    inputs, classes = next(iter(dataloaders["train"]))
    # 对于一个批量,产生一个网格
    out = torchvision.utils.make_grid(inputs)
    imshow(out, title=[class_names[x] for x in classes])
    

    image

    训练模型

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

    • scheduling the learning rate
    • saving the best model

    在接下来的部分,schedulertorch.optim.lr_scheduler的一个LR scheduler对象。

    def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
        since = time.time()
    
        best_model_wts = copy.deepcopy(model.state_dict())
        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
                    best_model_wts = copy.deepcopy(model.state_dict())
    
            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(best_model_wts)
        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)
    

    微调卷积网络

    加载一个预训练模型并且重置最后的全连接层。

    model_ft = models.resnet18(pretrained=True)
    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)
    

    训练并验证

    在CPU上执行时间大概在15-25分钟,而在GPU上,大概只需要不到1分钟。

    model_ft = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler, num_epochs=25)
    
    Epoch 0/24
    ----------
    train Loss: 0.6200 Acc: 0.7049
    val Loss: 0.1974 Acc: 0.9281
    
    Epoch 1/24
    ----------
    train Loss: 0.3805 Acc: 0.8238
    val Loss: 0.2273 Acc: 0.9085
    
    Epoch 2/24
    ----------
    train Loss: 0.6180 Acc: 0.7541
    val Loss: 0.3575 Acc: 0.8301
    
    Epoch 3/24
    ----------
    train Loss: 0.6691 Acc: 0.7213
    val Loss: 0.7067 Acc: 0.7320
    
    Epoch 4/24
    ----------
    train Loss: 0.5769 Acc: 0.7787
    val Loss: 0.3549 Acc: 0.8758
    
    Epoch 5/24
    ----------
    train Loss: 0.5283 Acc: 0.8074
    val Loss: 0.4467 Acc: 0.8693
    
    Epoch 6/24
    ----------
    train Loss: 0.5416 Acc: 0.8115
    val Loss: 0.4135 Acc: 0.8758
    
    Epoch 7/24
    ----------
    train Loss: 0.5169 Acc: 0.8320
    val Loss: 0.2594 Acc: 0.9150
    
    Epoch 8/24
    ----------
    train Loss: 0.3956 Acc: 0.8443
    val Loss: 0.2633 Acc: 0.8889
    
    Epoch 9/24
    ----------
    train Loss: 0.3106 Acc: 0.8811
    val Loss: 0.2324 Acc: 0.9150
    
    Epoch 10/24
    ----------
    train Loss: 0.3582 Acc: 0.8361
    val Loss: 0.2172 Acc: 0.9150
    
    Epoch 11/24
    ----------
    train Loss: 0.3057 Acc: 0.8811
    val Loss: 0.2832 Acc: 0.9085
    
    Epoch 12/24
    ----------
    train Loss: 0.2628 Acc: 0.8893
    val Loss: 0.2257 Acc: 0.9216
    
    Epoch 13/24
    ----------
    train Loss: 0.2785 Acc: 0.8730
    val Loss: 0.2305 Acc: 0.9281
    
    Epoch 14/24
    ----------
    train Loss: 0.2999 Acc: 0.8648
    val Loss: 0.2338 Acc: 0.9281
    
    Epoch 15/24
    ----------
    train Loss: 0.1689 Acc: 0.9426
    val Loss: 0.2332 Acc: 0.9216
    
    Epoch 16/24
    ----------
    train Loss: 0.2660 Acc: 0.9057
    val Loss: 0.2629 Acc: 0.9150
    
    Epoch 17/24
    ----------
    train Loss: 0.3769 Acc: 0.8074
    val Loss: 0.2362 Acc: 0.9281
    
    Epoch 18/24
    ----------
    train Loss: 0.3248 Acc: 0.8607
    val Loss: 0.2255 Acc: 0.9216
    
    Epoch 19/24
    ----------
    train Loss: 0.2859 Acc: 0.8893
    val Loss: 0.2545 Acc: 0.9150
    
    Epoch 20/24
    ----------
    train Loss: 0.2435 Acc: 0.8852
    val Loss: 0.2538 Acc: 0.9150
    
    Epoch 21/24
    ----------
    train Loss: 0.3553 Acc: 0.8525
    val Loss: 0.2828 Acc: 0.9085
    
    Epoch 22/24
    ----------
    train Loss: 0.3092 Acc: 0.8770
    val Loss: 0.2246 Acc: 0.9216
    
    Epoch 23/24
    ----------
    train Loss: 0.2671 Acc: 0.8934
    val Loss: 0.2437 Acc: 0.9216
    
    Epoch 24/24
    ----------
    train Loss: 0.1983 Acc: 0.9262
    val Loss: 0.2293 Acc: 0.9281
    
    Training complete in 8m 22s
    Best val Acc: 0.928105
    
    visualize_model(model_ft)
    

    image

    将ConvNet作为固定特征提取器

    接下来,我们要冻结网络中除最后一层的所有部分。我们需要设置requires_grad=False来冻结网络中的参数,以便于在backward()中梯度不被计算。

    你可以从此文档中查看更多。

    model_conv = torchvision.models.resnet18(pretrained=True)
    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.6645 Acc: 0.6557
    val Loss: 0.2080 Acc: 0.9477
    
    Epoch 1/24
    ----------
    train Loss: 0.4321 Acc: 0.7787
    val Loss: 0.2543 Acc: 0.8889
    
    Epoch 2/24
    ----------
    train Loss: 0.4560 Acc: 0.8320
    val Loss: 0.3580 Acc: 0.8301
    
    Epoch 3/24
    ----------
    train Loss: 0.4300 Acc: 0.8197
    val Loss: 0.1768 Acc: 0.9542
    
    Epoch 4/24
    ----------
    train Loss: 0.4507 Acc: 0.8074
    val Loss: 0.1854 Acc: 0.9412
    
    Epoch 5/24
    ----------
    train Loss: 0.3498 Acc: 0.8525
    val Loss: 0.1786 Acc: 0.9477
    
    Epoch 6/24
    ----------
    train Loss: 0.4869 Acc: 0.8033
    val Loss: 0.3503 Acc: 0.8562
    
    Epoch 7/24
    ----------
    train Loss: 0.3607 Acc: 0.8484
    val Loss: 0.1843 Acc: 0.9477
    
    Epoch 8/24
    ----------
    train Loss: 0.3784 Acc: 0.8238
    val Loss: 0.1927 Acc: 0.9412
    
    Epoch 9/24
    ----------
    train Loss: 0.2795 Acc: 0.8811
    val Loss: 0.1895 Acc: 0.9346
    
    Epoch 10/24
    ----------
    train Loss: 0.3200 Acc: 0.8811
    val Loss: 0.1843 Acc: 0.9477
    
    Epoch 11/24
    ----------
    train Loss: 0.3509 Acc: 0.8607
    val Loss: 0.1780 Acc: 0.9477
    
    Epoch 12/24
    ----------
    train Loss: 0.3471 Acc: 0.8279
    val Loss: 0.1747 Acc: 0.9412
    
    Epoch 13/24
    ----------
    train Loss: 0.3467 Acc: 0.8320
    val Loss: 0.2249 Acc: 0.9150
    
    Epoch 14/24
    ----------
    train Loss: 0.3171 Acc: 0.8484
    val Loss: 0.1845 Acc: 0.9477
    
    Epoch 15/24
    ----------
    train Loss: 0.3456 Acc: 0.8279
    val Loss: 0.1745 Acc: 0.9412
    
    Epoch 16/24
    ----------
    train Loss: 0.3599 Acc: 0.8525
    val Loss: 0.1960 Acc: 0.9477
    
    Epoch 17/24
    ----------
    train Loss: 0.2593 Acc: 0.8934
    val Loss: 0.1894 Acc: 0.9477
    
    Epoch 18/24
    ----------
    train Loss: 0.3548 Acc: 0.8361
    val Loss: 0.1900 Acc: 0.9346
    
    Epoch 19/24
    ----------
    train Loss: 0.3291 Acc: 0.8361
    val Loss: 0.1977 Acc: 0.9412
    
    Epoch 20/24
    ----------
    train Loss: 0.2964 Acc: 0.8402
    val Loss: 0.1920 Acc: 0.9412
    
    Epoch 21/24
    ----------
    train Loss: 0.3526 Acc: 0.8607
    val Loss: 0.2000 Acc: 0.9281
    
    Epoch 22/24
    ----------
    train Loss: 0.3836 Acc: 0.7992
    val Loss: 0.1915 Acc: 0.9412
    
    Epoch 23/24
    ----------
    train Loss: 0.3399 Acc: 0.8566
    val Loss: 0.1821 Acc: 0.9346
    
    Epoch 24/24
    ----------
    train Loss: 0.3259 Acc: 0.8402
    val Loss: 0.1718 Acc: 0.9477
    
    Training complete in 4m 1s
    Best val Acc: 0.954248
    
    visualize_model(model_conv)
    
    plt.ioff()
    plt.show()
    

    image

    下一步阅读

    如果你希望阅读更多的迁移学习的应用,可以查看Quantized Transfer Learning for Computer Vision Tutorial

  • 相关阅读:
    java.net.ConnectException: localhost/127.0.0.1:8088 Connection refused java程序员
    网络模式:GSM,WCDMA,CDMA2000什么意思 java程序员
    Spring contextConfigLocation java程序员
    src总结 java程序员
    广州天河软件园面试Java实习生时的一些面试题 java程序员
    纠结了好久的Android SDK无法更新问题 java程序员
    Android SDK 2.3/3.0/4.0/4.1 下载与安装教程 java程序员
    域名解析文件hosts文件是什么?如何修改hosts文件? java程序员
    安卓模拟器Android SDK 4.0.3 R2安装完整图文教程 java程序员
    SpringBoot+mongoDB实现id自增
  • 原文地址:https://www.cnblogs.com/geekfx/p/16098962.html
Copyright © 2020-2023  润新知