転移学習とファインチューニング

MACHINE LEARNING

こんにちは
画像認識モデルなどを使うときに、事前学習済みの重みを使うことがよくあると思います。そこで出てくるのが「転移学習」と「ファインチューニング」という二つの単語です。

本記事では二つの違いを実装面から解説しようと思います。

※本記事ではPyTorchを使用します。

準備

まず初めにライブラリをimportします。使うモデルはなんでもいいです。

import torch
import torch.nn as nn
from torchvision.models import resnet18

モデルのインスタンスを作成

model = resneet18(weights='IMAGENET1K_V1')
print(model)

出力してみましょう。

ResNet(
  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
  (layer1): Sequential
・・・・・・・・・・・・
(conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
  (fc): Linear(in_features=512, out_features=1000, bias=True)
)

OKですね。

それでは、転移学習とファインチューニングの違いを詳しくみていきます。

転移学習とファインチューニングの違い

まずは簡単に両者の違いを簡単に説明します。

転移学習

追加・変更した層のみを学習する

ファインチューニング

追加・変更した層を含めた全ての層を学習する

これだけじゃなんのことかわからないので、実装面から違いを見ていきましょう。

勾配の計算をする・しない

そもそも事前学習済みの重みというのは、例えばImageNetの画像で学習した重みパラメータのことで、それを使うということはモデルをそれらで初期化することを意味します。
転移学習では新しく追加した層や、元々あった層を変更した箇所のみを学習するので、それ以外の層は学習させずに重みを”固定”します。
「固定する」とは言い換えれば、勾配を計算しないということです。逆に「固定しない」と勾配を計算します。

具体的にコードから読み解いていきましょう。

parameters()でモデルの重みパラメータを取り出して実際に見ることができます。

list(model.parameters())[0]

>> Parameter containing:
tensor([[[[-8.6490e-03,  1.2777e-02,  3.3870e-02,  ...,  3.1768e-02,
            2.2170e-02, -1.1863e-02],
          [-3.8385e-02,  4.2272e-02, -3.6275e-02,  ..., -2.6337e-02,
           -6.0320e-03, -1.9055e-02],
          [ 1.5568e-02, -1.0671e-02, -1.4883e-03,  ...,  7.9898e-03,
            3.3394e-03, -5.0499e-02],
          ...,
          [-1.6082e-02,  2.2223e-03,  1.5821e-03,  ..., -2.5630e-02,
            3.8837e-02, -5.6174e-04],
          [-5.9140e-02, -2.3612e-02,  1.9768e-02,  ..., -2.8339e-02,
           -3.7333e-03,  1.8573e-03],
          [-2.8504e-02,  9.0133e-03,  3.0084e-02,  ..., -1.0702e-02,
            3.7161e-02, -7.1434e-02]]]], requires_grad=True)

つらつらとテンソルが出力されますが、それぞれの層の最後にrequires_grad=Trueと書かれています。
これはこのモデルで学習を行ったときに、勾配が計算される設定になっている、ということです。

つまり、今の事前学習済みの重みパラメータが更新されてしまいます。

それでは転移学習とファインチューニングの違いに実装面からみた説明を加えてみましょう。

転移学習

追加・変更した層はrequires_grad=Trueで学習する。それ以外の層はrequires_grad=Falseで重みを固定して学習する。

ファインチューニング

追加・変更した層を含めた全ての層をrequires_grad=Trueで学習する

実装と注意点

実装するにあたり、注意点が2つあります。
1つ目は、モデルをインスタンス化した時はデフォルトでrequires_grad=Trueになっているところです。
つまり、このまま学習を始めると、「ファインチューニング」をすることになります。
2つ目は、自分で新しく追加した層や、変更した層のrequires_gradはデフォルトでTrueになっているところです。
それでは、requires_grad=Falseにするにはどうすればいいのかを説明してきます。

転移学習のための準備

転移学習では、変更した層や新しく追加した層のみ勾配を計算します。つまり、最初にモデルの全ての層をrequires_grad=Falseにした後に、変更や追加をすれば、その箇所だけ学習させることが可能になります。まずは、named_parameters()で層の名前とパラメータを取り出してみます。

for name, param in model.named_parameters():
    print(f"weight name:  {name},\nparam config:   {param.requires_grad}\n")

>>>weight name: conv1.weight, param config: True 
weight name: bn1.weight, param config: True 
weight name: bn1.bias, param config: True
・・・・
weight name: layer4.1.bn2.bias, param config: True
weight name: fc.weight, param config: True 
weight name: fc.bias, param config: True 

全てTrueになっていますね、ここで、param.requires_gradFalseを与えてiterableの中身を全てFalseにしましょう。

for name, param in model.named_parameters():
    param.requires_grad=False
    print(f"weight name:  {name},\nparam config:   {param.requires_grad}\n")

>>>weight name:  layer3.0.bn2.weight, param config:   False
weight name:  layer3.0.bn2.bias, param config:   False 
weight name:  layer3.0.downsample.0.weight, param config:   False 
・・・・
weight name:  layer4.1.bn2.bias, param config:   False 
weight name:  fc.weight, param config:   False 
weight name:  fc.bias, param config:   False

Falseにできました。それでは、最終層の分類クラス数を1000から10に変えてみましょう。

model.fc = nn.Linear(in_features=512, out_features=10, bias=True)

先ほど説明した通り、変更などを加えた場合はrequires_gradTrueになります。

for name, param in model.named_parameters():
    print(f"weight name:  {name},\nparam config:   {param.requires_grad}\n")

>>>weight name:  conv1.weight, param config:   False 
weight name:  bn1.weight, param config:   False 
weight name:  bn1.bias, param config:   False
・・・・
weight name:  layer4.1.bn2.bias, param config:   False 
weight name:  fc.weight, param config:   True 
weight name:  fc.bias, param config:   True

最終層のみがTrueになっていることが確認できました。
この状態でモデルを学習すると、事前学習済みの重みで初期化され、最終層のみを学習する「転移学習」がおこなわれます。

ファインチューニングのための準備

ファインチューニングは追加・変更した層を含めた全ての層をrequires_grad=Trueで学習するので、最初にモデルをインスタンス化した後に、最終層を書き換えて学習を行うだけです。
この状態でモデルを学習すると、事前学習済みの重みで初期化され、全ての層の勾配が計算され微調整されることで「ファインチューニング」が行われています。

最後に

いかがだったでしょうか、少しでも理解の助けになれば幸いです。



コメント

タイトルとURLをコピーしました