58.md 15.8 KB
Newer Older
W
wizardforcel 已提交
1
# (beta)计算机视觉教程的量化迁移学习¶
W
wizardforcel 已提交
2

W
wizardforcel 已提交
3
> 原文:<https://pytorch.org/tutorials/intermediate/quantized_transfer_learning_tutorial.html>
W
wizardforcel 已提交
4 5 6 7 8 9 10 11 12 13 14

小费

为了充分利用本教程,我们建议使用此 [Colab 版本](https://colab.research.google.com/github/pytorch/tutorials/blob/gh-pages/_downloads/quantized_transfer_learning_tutorial.ipynb)。 这将使您可以尝试以下信息。

**作者**[Zafar Takhirov](https://github.com/z-a-f)

**由**审核: [Raghuraman Krishnamoorthi](https://github.com/raghuramank100)

**由**编辑:[林 ess 琳](https://github.com/jlin27)

W
wizardforcel 已提交
15
本教程以 [Sasank Chilamkurthy](https://pytorch.org/tutorials/beginner/transfer_learning_tutorial.html) 编写的原始 [PyTorch 迁移学习](https://pytorch.org/tutorials/beginner/transfer_learning_tutorial.html)教程为基础。
W
wizardforcel 已提交
16

W
wizardforcel 已提交
17
迁移学习是指利用预训练的模型应用于不同数据集的技术。 使用迁移学习的主要方法有两种:
W
wizardforcel 已提交
18

W
wizardforcel 已提交
19
1.  **作为固定特征提取器的 ConvNet**:在这里,您[“冻结”](https://arxiv.org/abs/1706.04983) 网络中所有参数的权重,但最后几层(又称“头部”)的权重通常 连接的层)。 将这些最后一层替换为使用随机权重初始化的新层,并且仅训练这些层。
W
wizardforcel 已提交
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
2.  **对 ConvNet 进行微调**:使用随机训练的网络初始化模型,而不是随机初始化,然后像往常一样使用不同的数据集进行训练。 通常,如果输出数量不同,则在网络中也会更换磁头(或磁头的一部分)。 这种方法通常将学习率设置为较小的值。 这样做是因为已经对网络进行了训练,并且只需进行较小的更改即可将其“微调”到新的数据集。

您还可以结合以上两种方法:首先,可以冻结特征提取器,并训练头部。 之后,您可以解冻特征提取器(或其一部分),将学习率设置为较小的值,然后继续进行训练。

在本部分中,您将使用第一种方法-使用量化模型提取特征。

## 第 0 部分。先决条件](docs / modern-java-zh /

在深入学习迁移学习之前,让我们回顾一下“先决条件”,例如安装和数据加载/可视化。

```py
# Imports
import copy
import matplotlib.pyplot as plt
import numpy as np
import os
import time

plt.ion()

```

### 安装每夜构建] [docs / modern-java-zh /

因为您将使用 PyTorch 的 Beta 部分,所以建议安装最新版本的`torch``torchvision`。 您可以在中找到有关本地安装[的最新说明。 例如,要在没有 GPU 支持的情况下进行安装:](https://pytorch.org/get-started/locally/)

```py
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

```

### 加载数据](docs / modern-java-zh /

注意

本部分与原始的迁移学习教程相同。

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

您今天要解决的问题是从图像中对**蚂蚁****蜜蜂**进行分类。 该数据集包含约 120 张针对蚂蚁和蜜蜂的训练图像。 每个类别有 75 个验证图像。 可以认为这是一个很小的数据集。 但是,由于我们正在使用迁移学习,因此我们应该能够很好地进行概括。

*此数据集是 imagenet 的很小子集。*

注意

[此处](https://download.pytorch.org/tutorial/hymenoptera_data.zip)下载数据,并将其提取到`data`目录。

```py
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")

```

### 可视化一些图像] [docs / modern-java-zh /

让我们可视化一些训练图像,以了解数据扩充。

```py
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)

```

### 模型训练的支持功能](docs / modern-java-zh /

以下是模型训练的通用功能。 此功能也

*   安排学习率
*   保存最佳模型

```py
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

```

### 可视化模型预测的支持功能](docs / modern-java-zh /

通用功能,显示一些图像的预测

```py
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 部分。训练基于量化特征提取器的自定义分类器](docs / modern-java-zh /

在本节中,您将使用“冻结”量化特征提取器,并在其顶部训练自定义分类器头。 与浮点模型不同,您无需为量化模型设置 require_grad = False,因为它没有可训练的参数。 请参阅[文档](https://pytorch.org/docs/stable/quantization.html)了解更多详细信息。

加载预训练的模型:在本练习中,您将使用 [ResNet-18](https://pytorch.org/hub/pytorch_vision_resnet/)

```py
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

```

此时,您需要修改预训练模型。 该模型在开始和结束时都有量化/去量化块。 但是,由于只使用要素提取器,因此反量化层必须在线性层(头部)之前移动。 最简单的方法是将模型包装在`nn.Sequential`模块中。

W
wizardforcel 已提交
281
第一步是在 ResNet 模型中隔离特征提取器。 尽管在本示例中,您被责成使用`fc`以外的所有层作为特征提取器,但实际上,您可以根据需要选择任意数量的零件。 如果您也想替换一些卷积层,这将很有用。
W
wizardforcel 已提交
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357

注意

将特征提取器与量化模型的其余部分分开时,必须手动将量化器/去量化器放置在要保持量化的部分的开头和结尾。

下面的函数创建一个带有自定义头部的模型。

```py
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。

```py
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)

```

### 训练和评估](docs / modern-java-zh /

此步骤在 CPU 上大约需要 15-25 分钟。 由于量化模型只能在 CPU 上运行,因此您不能在 GPU 上运行训练。

```py
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 部分。微调可量化模型](docs / modern-java-zh /

W
wizardforcel 已提交
358
在这一部分中,我们将微调用于迁移学习的特征提取器,并对特征提取器进行量化。 请注意,在第 1 部分和第 2 部分中,特征提取器都是量化的。 不同之处在于,在第 1 部分中,我们使用了预训练的量化模型。 在这一部分中,我们将在对感兴趣的数据集进行微调之后创建一个量化的特征提取器,因此这是一种在具有量化优势的同时通过迁移学习获得更好的准确性的方法。 请注意,在我们的特定示例中,训练集非常小(120 张图像),因此微调整个模型的好处并不明显。 但是,此处显示的过程将提高使用较大数据集进行传递学习的准确性。
W
wizardforcel 已提交
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385

预训练特征提取器必须是可量化的。 为确保其可量化,请执行以下步骤:

> 1.  使用 `torch.quantization.fuse_modules` 熔断 `(Conv, BN, ReLU)` , `(Conv, BN)` 和 `(Conv, ReLU)` 。
> 2.  将特征提取器与自定义头部连接。 这需要对特征提取器的输出进行反量化。
> 3.  在特征提取器中的适当位置插入伪量化模块,以模拟训练期间的量化。

对于步骤(1),我们使用`torchvision/models/quantization`中的模型,这些模型具有成员方法`fuse_model`。 此功能将所有`conv``bn``relu`模块融合在一起。 对于自定义模型,这需要使用模块列表调用`torch.quantization.fuse_modules` API 进行手动融合。

步骤(2)由上一节中使用的`create_combined_model`功能执行。

步骤(3)通过使用`torch.quantization.prepare_qat`来实现,它会插入伪量化模块。

在步骤(4)中,您可以开始“微调”模型,然后将其转换为完全量化的版本(步骤 5)。

要将微调模型转换为量化模型,可以调用`torch.quantization.convert`函数(在我们的情况下,仅对特征提取器进行量化)。

注意

由于随机初始化,您的结果可能与本教程中显示的结果不同。

#注意 <cite>quantize = False</cite> model = models.resnet18(pretrained = True,progress = True,quantize = False)num_ftrs = model.fc.in_features

#步骤 1 model.train()model.fuse_model()#步骤 2 model_ft = create_combined_model(model)model_ft [0] .qconfig = torch.quantization.default_qat_qconfig#使用默认 QAT 配置#步骤 3 model_ft = torch.quantization.prepare_qat( model_ft,inplace = True)

### 微调模型](docs / modern-java-zh /

W
wizardforcel 已提交
386
在当前教程中,整个模型都经过了微调。 通常,这将导致更高的精度。 但是,由于此处使用的训练集很小,最终导致我们过度适应了训练集。
W
wizardforcel 已提交
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429

步骤 4.微调模型

```py
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.转换为量化模型

```py
from torch.quantization import convert
model_ft_tuned.cpu()

model_quantized_and_trained = convert(model_ft_tuned, inplace=False)

```

让我们看看量化模型在几张图像上的表现

```py
visualize_model(model_quantized_and_trained)

plt.ioff()
plt.tight_layout()
plt.show()

```