Advertisement

Transfer Learning for Computer Vision

阅读量:

作者:禅与计算机程序设计艺术

1.简介

1.1 Transfer Learning for Computer Vision(TLCV)的背景

图像识别领域的计算机视觉系统已经历了十多年的发展,在该领域中已发展出一系列经典的模型结构与方法论。然而由于大数据和计算能力的进步,在处理复杂任务方面传统的方法已经难以满足现实世界的需求。鉴于此,在过去几年中基于深度学习的计算机视觉技术受到了越来越多的关注。而深度学习能够涵盖的问题包括:

  • 分类问题
  • 检测问题
  • 分割问题
  • 目标跟踪问题 而对于这些问题,不同的模型架构也不同,不同的训练策略也会带来差异性。传统机器学习方法往往需要依赖于大量的手动标记的数据才能达到较好的效果,而深度学习方法则可以从海量数据中自适应地学习到特征表示。 传统的图像分类方法如AlexNet、VGG等都是基于大量的数据集进行预训练,并固定住网络的前几层参数不进行微调(fine-tuning),然后在目标数据集上微调网络的参数进行后续的训练。这种方式虽然可以取得不错的结果,但是训练周期长、耗时长。而且当数据集不同时,每个模型都需要重新从头训练一遍。 迁移学习通过将源域的知识迁移到目标域,使得目标域上的模型更加有效、准确。主要有以下两种方法:
  • 微调(fine-tuning):将源模型的参数进行微调,使其适用于目标数据集,一般通过冻结除最后几层之外的所有参数,仅训练最后几层的参数。
  • 特征抽取:将源模型的顶层卷积层或全连接层提取出来的特征作为输入,然后直接应用到目标模型上。 迁移学习能够解决的问题:
  • 少样本学习:由于源域有限,所以利用源域的数据进行训练模型可能有限;迁移学习可以在目标域上获得更多信息,从而提高模型的性能。
  • 泛化能力强:不同类别之间的相似性很强,迁移学习可以将源域的特征转移到目标域上,提高泛化能力。
  • 零样本学习:目标域没有足够的标注数据,可以通过迁移学习的方式来快速从源域上学习知识,减少标注时间。

1.2 Transfer Learning for Computer Vision(TLCV)的定义

知识转移学习法是一种机器学习方法,在新任务中使用预先训练好的模型作为起点,并利用该模型学到的知识来提高目标任务的表现。知识转移学习的核心概念在于,在源领域中获得的特征可以在目标领域中进行转移或复用,无需从头开始重新构建 everything. 在TLCV框架下,我们将涵盖那些采用特征提取和微调方法的技术细节。具体来说,我们将详细探讨从预训练模型中提取特征并对其进行微调以适应目标任务的过程。

2.基本概念术语说明

2.1 Fine-tuning

Fine-tuning represents a method in deep learning, involving adjustments to a pre-trained network's weights to better adapt to the data available in the target dataset. Typically comprising two key stages:

  • Freezing some of the layers of the pre-trained model so that their weights cannot be updated during training, effectively keeping them fixed while adjusting the remaining parameters of the last layer of the network (called the head). This step is crucial because freezing prevents the pre-trained model from changing too much due to its high capacity.
  • Unfreezing all other layers and updating only those of the tail, allowing these weights to learn specific patterns unique to the target dataset. The number of epochs required to train a network with fine-tuning depends on the size of the datasets involved, but typically between several hundreds and thousands. After fine-tuning, the resulting model may still need additional training to achieve convergence.

2.2 Convolutional Neural Network(CNN)

A CNN architecture consists of multiple convolutional layers which are followed by pooling layers and finally fully connected layers at the end of the network. The typical structure comprises three main categories of layers:

Convolutional Layer: Convolutes filters over input images to extract specific features such as edges, corners, and textures. Each filter specializes in detecting a particular feature. Filters exhibit diverse shapes, sizes, and orientations to capture a range of image characteristics.

Pooling Layers: The process reduces the output dimensionality of each convolutional layer to diminish the spatial dimensions of the representation, thereby optimizing computational efficiency while enhancing accuracy. These layers typically comprise max pooling, average pooling, and L2 pooling methods.

  1. Fully Connected Layer (FC): Accepts the flattened outputs from preceding layers and passes them through linear neurons for input classification. FC layers are designed to transform raw pixel values into class probabilities or regression outputs.

2.3 Transfer Learning

In transfer learning, pre-trained models serve as the foundation for tackling new tasks. The knowledge acquired from these pre-trained models can be transferred or reused for enhancing outcomes in the respective tasks. There are two primary methodologies involving:

Feature Extraction: Derives the features learned by a pre-trained model and adapts them for a new task. The extracted features may be directly inputted into a fully connected layer or integrated into other components such as auxiliary classifiers. The following methods include DenseNet, ResNet, and VGG.

  1. 微调: 将预训练模型的一部分参数与新增的层一起重新训练以适应新任务, 这种技术涉及对现有架构稍作修改, 并在目标任务上进行几轮训练以适应新任务. 示例包括AlexNet、GoogLeNet和VGG网络. 我们将主要关注于通过微调来进行转移学习, 因为后者似乎更适合我们的命题陈述. 然而, 在计算机视觉应用中使用转移学习时, 这两种方法可以结合起来使用.

3.核心算法原理和具体操作步骤以及数学公式讲解

3.1 数据集准备

3.1.1 源域数据集的准备

在源域数据集的准备阶段中,可以选择多个领域相关的数据集。例如CIFAR-10/100作为一个典型的小型图像分类数据集以及MNIST作为一个经典的 handwritten digit recognition 数据库都是不错的选择。在这里我们采用 MNIST 作为源域的数据库来进行后续的研究与实验操作。具体操作步骤包括首先下载 MNIST 数据库解压完成后按照要求存放于指定存储路径中:

复制代码
    import torchvision.datasets as dsets

    from torch.utils.data import DataLoader
    
    
         
         
    代码解读

mnist_train = dsets.MNIST(存储路径./data, 训练模式, 数据转换为张量形式, 自动下载并加载中)

mnist_test = dsets.MNIST(root='./data', train=False, transform=transforms.ToTensor())

dataloader_args = dict(shuffle=True, batch_size=batch_size, num_workers=4)

train_loader = DataLoader(dataset=mnist_train, *dataloader_args)
test_loader = DataLoader(dataset=mnist_test, *dataloader_args)

复制代码
    其中transform函数用于将图片数据转为张量形式。DataLoader用于加载数据。
    ### 3.1.2 目标域数据集的准备
    对于目标域的数据集,根据实际情况选择是否采用同类别数据集,或者采取翻译、增广、平移等数据增强方式扩充数据集规模。这里为了演示方便,选择了相同数量的源域数据集。下载好源域数据集后,按照如下方式生成目标域数据集:
    ```python
    target_domain_indices = np.random.choice(len(mnist_train), len(mnist_train)) # 随机选择相同数量的数据集作为目标域数据集
    target_domain_data = mnist_train[target_domain_indices]
    target_domain_labels = mnist_train.targets[target_domain_indices].numpy()
    target_domain_dataset = TensorDataset(torch.stack([t.unsqueeze(0) for t in [target_domain_data]])
                                      .squeeze().to('cuda'),
                                       torch.tensor(target_domain_labels).long().to('cuda'))
    
      
      
      
      
      
      
      
      
      
    
    代码解读

其中此代码段的主要功能是通过随机选择与训练集大小相同的索引序列来生成目标域的数据集;该代码段的第一行负责从训练集中随机选取相同数量的样本作为目标域的数据集;第二行则提取这些选定样本对应的图像数据;第三行提取这些样本对应的标签信息并将它们转换为numpy数组以便后续处理;随后第四行将提取到的所有图像数据按批次进行张量化处理并拼接成一个三维数组;最后第五行利用torch库中的函数构建一个包含这些张量的目标域数据集。

3.2 模型搭建

本研究中采用ResNet架构作为基础模型。目前该模型在图像分类领域具有较高的先进性。随后我们将对该模型进行微调训练,并完成必要的配置工作:随后导入所需模块,并完成必要的配置工作:

复制代码
    import torch.nn as nn
    import torchvision.models as models
    from collections import OrderedDict
    
      
      
    
    代码解读

然后创建自定义的ResNet模型:

复制代码
    class CustomModel(nn.Module):
    def __init__(self):
        super().__init__()
    
        self.features = nn.Sequential(
            nn.Conv2d(1, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
    
        self.classifier = nn.Sequential(OrderedDict([
            ('fc1', nn.Linear(256 * 7 * 7, 512)),
            ('relu1', nn.ReLU()),
            ('dropout1', nn.Dropout(p=0.5)),
            ('fc2', nn.Linear(512, 10))
        ]))
    
    def forward(self, x):
        x = self.features(x)
        x = x.view(-1, 256 * 7 * 7)
        x = self.classifier(x)
        return x
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

模型初始化阶段设置了两个子网络结构

复制代码
    model = models.resnet18(pretrained=True)
    num_ftrs = model.fc.in_features
    for param in model.parameters():
    param.requires_grad = False
    model.fc = nn.Linear(num_ftrs, 10)
    
    model.to("cuda")
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(list(filter(lambda p: p.requires_grad, model.parameters())), lr=lr, momentum=momentum)
    scheduler = StepLR(optimizer, step_size=step_size, gamma=gamma)
    
    epochs = 5
    for epoch in range(epochs):
    print("Epoch {}/{}".format(epoch+1, epochs))
    
    running_loss = 0.0
    for i, data in enumerate(train_loader, 0):
        imgs, labels = data
    
        optimizer.zero_grad()
    
        outputs = model(imgs.to('cuda'))
        loss = criterion(outputs, labels.to('cuda'))
    
        loss.backward()
        optimizer.step()
    
        running_loss += loss.item()
    
    scheduler.step()
    
    test_loss = 0.0
    correct = 0
    total = 0
    with torch.no_grad():
        for data in test_loader:
            imgs, labels = data
    
            outputs = model(imgs.to('cuda'))
            loss = criterion(outputs, labels.to('cuda'))
    
            test_loss += loss.item()
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels.to('cuda')).sum().item()
    
    print('[{}/{}]\tloss={:.3f}\taccuracy={:.3%}'.format(epoch+1, epochs,
                                                           running_loss / len(train_loader),
                                                           100 * correct / total))
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

我们构建了一个自定义的ResNet架构,并决定性地锁定了非全连接层的参数。随后将原有的分类器替换成了一个新的设计。接着导入源域预训练模型,并决定性地锁定了除了原有的分类器之外的所有参数,并调整全连接层的输出维度至10个类别(即对应10个不同的识别目标)。设定优化算法采用分阶段递减的学习率策略,在此基础之上完成多次迭代训练过程。当训练任务完成时,请评估模型在测试集上的识别准确率。

复制代码
    correct = 0
    total = 0
    with torch.no_grad():
    for data in target_domain_loader:
        imgs, labels = data
    
        outputs = model(imgs.to('cuda'))
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels.to('cuda')).sum().item()
    
    print('\nTest Accuracy of the model on the target domain:\t{:.3%}'.format(100 * correct / total))
    
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

测试阶段也同样只使用目标域数据集进行测试。

3.3 评估指标

Accuracy。

4.具体代码实例和解释说明

4.1 目标域迁移学习实现

4.1.1 数据集准备

获取源域相关数据(包含模型训练后的参数文件和相应的标签信息)。对各个压缩包进行解码处理,在解压前需明确划分训练集和测试集的数量及比例。

复制代码
    import numpy as np
    import matplotlib.pyplot as plt
    import seaborn as sns
    
    sns.set()
    
    from sklearn.datasets import fetch_openml
    from tensorflow.keras.preprocessing.image import ImageDataGenerator
    
    source_domain = 'MNIST' # MNIST or CIFAR10/100 or any other relevant dataset name
    
    X_src, y_src = fetch_openml('{} SOURCE'.format(source_domain), version=1, return_X_y=True)
    
    assert X_src.shape[0] == y_src.shape[0], "Number of instances does not match number of labels"
    
    n_train = int(0.9 * X_src.shape[0])
    X_tr, y_tr = X_src[:n_train], y_src[:n_train]
    X_te, y_te = X_src[n_train:], y_src[n_train:]
    
    img_rows, img_cols = 28, 28
    input_shape = (img_rows, img_cols, 1)
    
    X_tr = X_tr.reshape((-1,) + input_shape)
    X_te = X_te.reshape((-1,) + input_shape)
    
    num_classes = len(np.unique(y_tr))
    
    if K.image_data_format() == 'channels_first':
    X_tr = X_tr.reshape((X_tr.shape[0], 1) + X_tr.shape[1:])
    X_te = X_te.reshape((X_te.shape[0], 1) + X_te.shape[1:])
    input_shape = (1,) + input_shape
    
    y_tr = keras.utils.to_categorical(y_tr, num_classes)
    y_te = keras.utils.to_categorical(y_te, num_classes)
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

在此例中, 源域数据集选取自MNIST, 其标签范围限定为[0,9], 输入图像尺寸设定为(28,28), 并采用Keras框架进行数据集准备工作。具体而言, 首先获取MNIST数据集后需进行解包操作, 确定源域数据集的具体规模参数, 包括训练样本集合及其对应的标签集合等信息内容。随后, 在数据预处理环节中运用ImageDataGenerator类对源域样本进行批量加载的同时, 进行多维度的数据增强操作: 包括随机旋转、缩放、平移以及裁剪等典型的数据增强方式处理, 以提升模型泛化性能并保证训练效果稳定

复制代码
    source_datagen = ImageDataGenerator(rescale=1./255.,
                                   rotation_range=10,
                                   width_shift_range=0.1,
                                   height_shift_range=0.1,
                                   shear_range=0.1,
                                   zoom_range=0.1,
                                   horizontal_flip=True)
    
    src_train_generator = source_datagen.flow(X_tr, y_tr, batch_size=32)
    src_test_generator = source_datagen.flow(X_te, y_te, batch_size=32)
    
      
      
      
      
      
      
      
      
      
    
    代码解读

4.1.2 模型构建及迁移学习

选定源域模型及其参数,建立模型,并将分类层置于新模型末端。

复制代码
    base_model = tf.keras.applications.MobileNetV2(include_top=False,
                                               alpha=0.35,
                                               weights=None,
                                               input_shape=input_shape)
    
    output = base_model.layers[-1].output
    output = Flatten()(output)
    output = Dense(num_classes, activation="softmax")(output)
    
    new_model = Model(inputs=[base_model.input],
                  outputs=[output])
    
      
      
      
      
      
      
      
      
      
      
    
    代码解读

在此处使用MobileNetV2作为源域模型,并设置其α参数值为0.35。其中分类层采用全连接层结构,在完成模型搭建后进行迁移学习训练,并冻结除了全连接层之外的所有可训练参数;随后进行进一步优化训练以提升性能。

复制代码
    for layer in base_model.layers[:-1]:
      layer.trainable = False
    
    new_model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
                  loss=tf.keras.losses.CategoricalCrossentropy(),
                  metrics=['accuracy'])
    
    history = new_model.fit(src_train_generator,
                        validation_data=src_test_generator,
                        epochs=10, verbose=1)
    
      
      
      
      
      
      
      
      
      
    
    代码解读

在这一阶段,我们设置了一系列关键参数包括学习率 损失函数 优化器 和衡量指标 并调用fit函数对新网络进行训练。基于源域模型的结构特点 最后一层已被更换 从而导致无法直接更新该部分参数 因此我们需要将除最后一层以外的所有层均设为不可训练状态。经过上述步骤后 在验证集上评估该模型的表现变化情况 将有助于后续优化工作

4.1.3 实验结果

在新模型的测试集上,测试准确率可以达到约96%。

全部评论 (0)

还没有任何评论哟~