(测试版)用于计算机视觉的量化迁移学习教程¶
创建时间: 2019-12-06 |上次更新时间:2021 年 7 月 27 日 |上次验证: Nov 05, 2024
提示
为了充分利用本教程,我们建议使用此 Colab 版本。 这将允许您试验下面提供的信息。
作者: Zafar Takhirov
测评者: Raghuraman Krishnamoorthi
编辑:Jessica Lin
本教程基于 Sasank Chilamkurthy 编写的原始 PyTorch 迁移学习教程。
迁移学习是指利用预训练模型进行 application 的 application 进行验证。 迁移学习有两种主要使用方式:
ConvNet 作为固定特征提取器:在这里,您可以“冻结”网络中所有参数的权重,除了最终 多个层(又名“头部”,通常是完全连接的层)。 这些最后的层被替换为用 random 初始化的新层 weights,并且仅训练这些层。
微调 ConvNet:该模型不是随机初始化,而是 使用预训练网络进行初始化,然后训练继续为 通常,但具有不同的数据集。 通常头部(或部分头部)也会在网络 如果 output 的数量不同。 在此方法中,通常将学习率设置为较小的数字。 这样做是因为网络已经经过训练,并且只有微小的更改 需要将其“微调”为新的数据集。
您也可以结合使用上述两种方法: 首先,您可以冻结特征提取器,并训练头部。后 这样,您可以解冻特征提取器(或其中的一部分),将 学习率降低到更小的水平,然后继续训练。
在这部分,您将使用第一种方法 – 提取特征 使用量化模型。
第 0 部分。先决条件¶
在深入研究迁移学习之前,让我们回顾一下“先决条件”, 例如安装和数据加载/可视化。
# Imports
import copy
import matplotlib.pyplot as plt
import numpy as np
import os
import time
plt.ion()
安装 Nightly Build¶
因为您将使用 PyTorch 的 beta 部分,所以它是
建议安装最新版本的 和 。您可以在本地
在此处安装。
例如,要在不支持 GPU 的情况下进行安装:torch
torchvision
pip install numpy
pip install --pre torch torchvision -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html
# For CUDA support use https://download.pytorch.org/whl/nightly/cu101/torch_nightly.html
加载数据¶
注意
本部分与原始迁移学习教程相同。
我们将使用 and packages 来加载
数据。torchvision
torch.utils.data
您今天要解决的问题是从图像中对蚂蚁和蜜蜂进行分类。该数据集包含大约 120 张训练图像 每个都是蚂蚁和蜜蜂。每个类有 75 个验证图像。 这被认为是一个非常小的数据集,可以概括。然而,由于 我们正在使用迁移学习,我们应该能够概括 相当不错。
此数据集是 imagenet 的一个非常小的子集。
注意
从此处下载数据并将其解压缩到目录中。data
import torch
from torchvision import transforms, datasets
# Data augmentation and normalization for training
# Just normalization for validation
data_transforms = {
'train': transforms.Compose([
transforms.Resize(224),
transforms.RandomCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
'val': transforms.Compose([
transforms.Resize(224),
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=16,
shuffle=True, num_workers=8)
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")
可视化一些图像¶
让我们可视化一些训练图像,以便理解数据 增强。
import torchvision
def imshow(inp, title=None, ax=None, figsize=(5, 5)):
"""Imshow 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)
if ax is None:
fig, ax = plt.subplots(1, figsize=figsize)
ax.imshow(inp)
ax.set_xticks([])
ax.set_yticks([])
if title is not None:
ax.set_title(title)
# Get a batch of training data
inputs, classes = next(iter(dataloaders['train']))
# Make a grid from batch
out = torchvision.utils.make_grid(inputs, nrow=4)
fig, ax = plt.subplots(1, figsize=(10, 10))
imshow(out, title=[class_names[x] for x in classes], ax=ax)
模型训练支持功能¶
下面是用于模型训练的通用函数。 此功能还
计划学习率
保存最佳模型
def train_model(model, criterion, optimizer, scheduler, num_epochs=25, device='cpu'):
"""
Support function for model training.
Args:
model: Model to be trained
criterion: Optimization criterion (loss)
optimizer: Optimizer to use for training
scheduler: Instance of ``torch.optim.lr_scheduler``
num_epochs: Number of epochs
device: Device to run the training on. Must be 'cpu' or 'cuda'
"""
since = time.time()
best_model_wts = copy.deepcopy(model.state_dict())
best_acc = 0.0
for epoch in range(num_epochs):
print('Epoch {}/{}'.format(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('{} Loss: {:.4f} Acc: {:.4f}'.format(
phase, epoch_loss, epoch_acc))
# 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('Training complete in {:.0f}m {:.0f}s'.format(
time_elapsed // 60, time_elapsed % 60))
print('Best val Acc: {:4f}'.format(best_acc))
# load best model weights
model.load_state_dict(best_model_wts)
return model
用于可视化模型预测的 Support function¶
用于显示一些图像的预测的通用函数
def visualize_model(model, rows=3, cols=3):
was_training = model.training
model.eval()
current_row = current_col = 0
fig, ax = plt.subplots(rows, cols, figsize=(cols*2, rows*2))
with torch.no_grad():
for idx, (imgs, lbls) in enumerate(dataloaders['val']):
imgs = imgs.cpu()
lbls = lbls.cpu()
outputs = model(imgs)
_, preds = torch.max(outputs, 1)
for jdx in range(imgs.size()[0]):
imshow(imgs.data[jdx], ax=ax[current_row, current_col])
ax[current_row, current_col].axis('off')
ax[current_row, current_col].set_title('predicted: {}'.format(class_names[preds[jdx]]))
current_col += 1
if current_col >= cols:
current_row += 1
current_col = 0
if current_row >= rows:
model.train(mode=was_training)
return
model.train(mode=was_training)
第 1 部分。基于 Quantized Feature Extractor 训练自定义分类器¶
在本节中,您将使用“冻结的”量化特征提取器,并且 在其上训练自定义分类器 head。与浮点不同 models,则无需requires_grad为量化的 model 的 MODEL 进行验证,因为它没有可训练的参数。请参阅文档 更多细节。
加载预训练模型:在本练习中,您将使用 ResNet-18。
import torchvision.models.quantization as models
# You will need the number of filters in the `fc` for future use.
# 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_fe = models.resnet18(pretrained=True, progress=True, quantize=True)
num_ftrs = model_fe.fc.in_features
此时,您需要修改预训练模型。模型
在 Beginning 和 End 中具有 quantize/dequantize 块。然而
因为您只会使用特征提取器,所以 Dequantization Layer 具有
在线性图层(头部)之前移动。最简单的方法
是将模型包装在模块中。nn.Sequential
第一步是在 ResNet 中隔离特征提取器
型。虽然在此示例中,您的任务是使用除特征提取器之外的所有层,但实际上,您可以采用尽可能多的部分
根据您的需要。如果您想替换一些
卷积层也是如此。fc
注意
将特征提取器与量化 模型,您必须手动将 quantizer/dequantized 放在 你想要保持量化的声部的开头和结尾。
下面的函数将创建一个具有自定义头部的模型。
from torch import nn
def create_combined_model(model_fe):
# Step 1. Isolate the feature extractor.
model_fe_features = nn.Sequential(
model_fe.quant, # Quantize the input
model_fe.conv1,
model_fe.bn1,
model_fe.relu,
model_fe.maxpool,
model_fe.layer1,
model_fe.layer2,
model_fe.layer3,
model_fe.layer4,
model_fe.avgpool,
model_fe.dequant, # Dequantize the output
)
# Step 2. Create a new "head"
new_head = nn.Sequential(
nn.Dropout(p=0.5),
nn.Linear(num_ftrs, 2),
)
# Step 3. Combine, and don't forget the quant stubs.
new_model = nn.Sequential(
model_fe_features,
nn.Flatten(1),
new_head,
)
return new_model
警告
目前,量化模型只能在 CPU 上运行。 但是,可以将模型的非量化部分发送到 GPU。
import torch.optim as optim
new_model = create_combined_model(model_fe)
new_model = new_model.to('cpu')
criterion = nn.CrossEntropyLoss()
# Note that we are only training the head.
optimizer_ft = optim.SGD(new_model.parameters(), lr=0.01, momentum=0.9)
# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)
训练和评估¶
此步骤在 CPU 上大约需要 15-25 分钟。因为量化模型可以 只能在 CPU 上运行,不能在 GPU 上运行训练。
new_model = train_model(new_model, criterion, optimizer_ft, exp_lr_scheduler,
num_epochs=25, device='cpu')
visualize_model(new_model)
plt.tight_layout()
第 2 部分。微调 Quantizable 模型¶
在这一部分中,我们将微调用于传输的特征提取器 学习并量化特征提取器。请注意,在第 1 部分 2,特征提取器被量化。区别在于,在 第 1 部分,我们使用预训练的量化模型。在这部分里,我们创建一个 对 兴趣,因此这是通过迁移学习获得更高准确性的一种方式 同时具有量化的好处。请注意,在我们的特定 例如,训练集非常小(120 张图像),因此优势 对整个模型进行微调并不明显。但是,该过程 此处显示的将提高迁移学习的准确性,并且 数据。
预训练的特征提取器必须是可量化的。 要确保它是可量化的,请执行以下步骤:
熔合 、 和使用 。
(Conv, BN, ReLU)
(Conv, BN)
(Conv, ReLU)
torch.quantization.fuse_modules
将特征提取器与自定义头连接。 这需要对特征提取器的输出进行反量化。
在适当的位置插入假量化模块 在特征提取器中模拟训练期间的量化。
对于步骤 (1),我们使用来自 的模型,其中
have a member 方法 。此函数融合了所有 、 和 模块。对于自定义模型,这需要调用
包含要融合的模块列表的 API
手动地。torchvision/models/quantization
fuse_model
conv
bn
relu
torch.quantization.fuse_modules
步骤 (2) 由函数执行
在上一节中使用。create_combined_model
步骤 (3) 通过使用 实现,它
插入 Fake-Quantization 模块。torch.quantization.prepare_qat
作为步骤 (4),您可以开始 “微调” 模型,然后转换 it 转换为完全量化的版本(步骤 5)。
要将微调模型转换为量化模型,您可以调用函数(在我们的示例中,仅
特征提取器是量化的)。torch.quantization.convert
注意
由于随机初始化,您的结果可能与 本教程中显示的结果。
# notice `quantize=False`
model = models.resnet18(pretrained=True, progress=True, quantize=False)
num_ftrs = model.fc.in_features
# Step 1
model.train()
model.fuse_model()
# Step 2
model_ft = create_combined_model(model)
model_ft[0].qconfig = torch.quantization.default_qat_qconfig # Use default QAT configuration
# Step 3
model_ft = torch.quantization.prepare_qat(model_ft, inplace=True)
微调模型¶
在当前教程中,整个模型进行了微调。在 general,这将导致更高的准确性。但是,由于小 training set 的 training set 时,我们最终会过拟合到训练集。
步骤 4。微调模型
for param in model_ft.parameters():
param.requires_grad = True
model_ft.to(device) # We can fine-tune on GPU if available
criterion = nn.CrossEntropyLoss()
# Note that we are training everything, so the learning rate is lower
# Notice the smaller learning rate
optimizer_ft = optim.SGD(model_ft.parameters(), lr=1e-3, momentum=0.9, weight_decay=0.1)
# Decay LR by a factor of 0.3 every several epochs
exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer_ft, step_size=5, gamma=0.3)
model_ft_tuned = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler,
num_epochs=25, device=device)
步骤 5。转换为量化模型
from torch.quantization import convert
model_ft_tuned.cpu()
model_quantized_and_trained = convert(model_ft_tuned, inplace=False)
让我们看看量化模型在一些图像上的表现
visualize_model(model_quantized_and_trained)
plt.ioff()
plt.tight_layout()
plt.show()