【PyTorchチュートリアル】TRANSFER LEARNING FOR COMPUTER VISION TUTORIAL-2、転移学習

1_プログラミング

今回のチュートリアルは説明が少ないですね。

いろいろ調べながら進めましょう。

今回は、「TRANSFER LEARNING FOR COMPUTER VISION TUTORIAL」チュートリアルのモデルを訓練するところまでやりました。

チュートリアルのページは次のリンクです。

こんな人の役に立つかも

・機械学習プログラミングを勉強している人

・PyTorchチュートリアルを勉強している人

・PyTorchで転移学習を行いたい人

スポンサーリンク

チュートリアルプログラミング

全体の流れ

今回のチュートリアルは、ほぼプログラムのため、まずチュートリアルプログラムの訓練までの流れを確認します。

①訓練処理を関数で定義

②検証データで結果を可視化する関数を定義

③訓練済みモデルをロードして、訓練のための準備を行う

④訓練処理関数で訓練を行い、最後に可視化を確認

①訓練処理を関数で定義

まずは、訓練を行う処理を関数として定義しています。引数として「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('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)

        #それぞれのエポックで訓練データで訓練→検証データで検証
        for phase in ['train', 'val']:
            if phase == 'train':
                model.train()  # モデルを訓練モードに
            else:
                model.eval()   # モデルを検証モードに

            #各種変数の初期化
            running_loss = 0.0
            running_corrects = 0

            #データローダーからミニバッチを読み込むループ
            for inputs, labels in dataloaders[phase]:
                inputs = inputs.to(device)
                labels = labels.to(device)

                #勾配を初期化
                optimizer.zero_grad()

                #順伝播
                #※1:訓練データの時はテンソルの勾配を求める
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = model(inputs)
                    _, preds = torch.max(outputs, 1)
                    loss = criterion(outputs, labels)

                    #訓練データの時に逆伝播+重みの更新を行う
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()

                #損失等を計算する
                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))

            #検証データで精度が以前より高ければモデルをdeepcopyする
            if phase == 'val' and epoch_acc > best_acc:
                #ベストの精度を更新
                best_acc = epoch_acc
                #モデルのコピーを保存して「best_model_wts」に格納
                best_model_wts = copy.deepcopy(model.state_dict())

        print()

    #1エポック終了毎の表示
    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))

    #最も良いモデルを読みだして返す
    model.load_state_dict(best_model_wts)
    return model

訓練処理は、「model, criterion, optimizer, scheduler, num_epochs=25」という引数を取ります。

(モデルインスタンス、損失関数インスタンス、最適化手法インスタンス、学習率のスケジューラ、エポック数固定値25)

処理の流れとしては次のようになっています。

※1:」の書き方で以前は「no_grad」として勾配を計算しない処理を書いていましたが、今回は次のように勾配を求めるかどうか条件判断をしているようです。

「with torch.set_grad_enabled(phase == ‘train’):」 は、torch.set_grad_enabled(True)だと自動微分をオンにして勾配を求める設定となるので、(phase==’train’)とすることで訓練データのときにTrueとなり、訓練データをいれたときに勾配を求めつつ順伝播を行う設定となります。一方で「phase」が「’val’」の時は勾配を求める設定がオフになり、順伝播が行われます。

②検証データで結果を可視化する関数を定義

訓練したモデルを、最後に可視化するための関数を定義しています。

#モデルの検証データでの結果を6枚だけ可視化する関数
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('predicted: {}'.format(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)

③訓練済みモデルをロードして、訓練のための準備を行う

訓練済みモデルをロードして今回のモデルに組み込みます

事前学習モデルは。「ResNet18」を利用するようです。

事前学習済みの ResNet-18 畳み込みニューラル ネットワーク - MATLAB resnet18 - MathWorks 日本
ResNet-18 は、ImageNet データベース の 100 万枚を超えるイメージで学習済みの畳み込みニューラル ネットワークです。

このResNet18は「torchvision」から簡単に呼び出して利用することができるようです。

#torchvisonからresnet18を読み込む
model_ft = models.resnet18(pretrained=True)
#ResNet18の特徴量を取り出す
num_ftrs = model_ft.fc.in_features
# 今回は出力層の分類数は2に固定で設定
# 「nn.Linear(num_ftrs, len(class_names))」のように識別クラス数を与えることもできます。
#torchvisionから読みだしたモデルに、全結合層として層を追加しています。
model_ft.fc = nn.Linear(num_ftrs, 2)

#デバイスの指定を行います
model_ft = model_ft.to(device)

#損失関数を設定します。交差クロスエントロピーを利用します。
criterion = nn.CrossEntropyLoss()

#最適化手法をMomentumSGDに設定します。
optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)

# 7エポック毎に学習率を0.1減衰させる「StepLR」です。
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)

「model_ft.fc」では、読みだしたモデルに全結合層を追加しているようです。今回は、まだ読みだした「resnet18」の重みを固定していないようです。固定する方法はもう少し後に出てきました。

また、この段階で、損失関数、最適化手法を設定して、学習率を変化させる「lr_scheduler」もセットしています。

④訓練処理関数で訓練を行い、最後に可視化を確認

最後に、今まで定義したものを利用して訓練を行います。train_model関数に引数を与えて訓練を行います。

model_ft = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler,
                       num_epochs=25)
Epoch 0/24
----------
train Loss: 0.6049 Acc: 0.7500
val Loss: 0.4479 Acc: 0.8366

Epoch 1/24
----------
train Loss: 0.4446 Acc: 0.8156
val Loss: 0.6298 Acc: 0.7451

略

Epoch 24/24
----------
train Loss: 0.2607 Acc: 0.8934
val Loss: 0.2191 Acc: 0.9281

Training complete in 2m 39s
Best val Acc: 0.934641

モデルの検証データでの結果を画像6枚分可視化します。

visualize_model(model_ft)

Google Colaboで実行可能なチュートリアルのプログラムをGitHubに配置しますので、ご利用ください。

続きの記事はこちらです。

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