書いてる理由
- pytorchを基礎からもう一回
参考
詳細
以下のコードで画像を予測。
# ミニバッチのサイズを指定 batch_size = 32 # DataLoaderを作成 train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True) val_dataloader = torch.utils.data.DataLoader(val_dataset, batch_size=batch_size, shuffle=False) # 辞書型変数にまとめる dataloaders_dict = {"train": train_dataloader, "val": val_dataloader} # 動作確認 # batch_iterator = iter(dataloaders_dict["train"]) # イテレータに変換 # inputs, labels = next(batch_iterator) # 1番目の要素を取り出す # print(inputs.size()) # print(labels) # 学習済みのVGG-16モデルをロード # VGG-16モデルのインスタンスを生成 use_pretrained = True # 学習済みのパラメータを使用 net = models.vgg16(pretrained=use_pretrained) print(net) # VGG16の最後の出力層の出力ユニットをアリとハチの2つに付け替える net.classifier[6] = nn.Linear(in_features=4096, out_features=2) # 訓練モードに設定 net.train() print('ネットワーク設定完了:学習済みの重みをロードし、訓練モードに設定しました') # 損失関数の設定 criterion = nn.CrossEntropyLoss() # 転移学習で学習させるパラメータを、変数params_to_updateに格納する params_to_update = [] # 学習させるパラメータ名 update_param_names = ["classifier.6.weight", "classifier.6.bias"] # 学習させるパラメータ以外は勾配計算をなくし、変化しないように設定 for name, param in net.named_parameters(): if name in update_param_names: param.requires_grad = True params_to_update.append(param) print(name) else: param.requires_grad = False # params_to_updateの中身を確認 print("-----------") print(params_to_update) # 最適化手法の設定 optimizer = optim.SGD(params=params_to_update, lr=0.001, momentum=0.9) # 学習・検証を実行する num_epochs = 2 train_model(net, dataloaders_dict, criterion, optimizer, num_epochs=num_epochs)
ちょっとトリッキーなのが、以下の箇所。
net.classifier[6] = nn.Linear(in_features=4096, out_features=2) update_param_names = ["classifier.6.weight", "classifier.6.bias"] # 学習させるパラメータ以外は勾配計算をなくし、変化しないように設定 for name, param in net.named_parameters(): if name in update_param_names: param.requires_grad = True params_to_update.append(param) print(name) else: param.requires_grad = False
VGG16のネットワークは、以下のように特徴抽出するfeaturesと分類するclassifierに別れていて、
net.classifier[6] = nn.Linear(in_features=4096, out_features=2)
で最終層をout_features=2のDenceに置き換えている。
また、update_param_names = ["classifier.6.weight", "classifier.6.bias"]
以外のパラメータを.requires_grad = Falseにすることで、特徴抽出するところや分類する直前までのパラメータは一切更新せず、最後の分類だけを学習するように修正している。
VGG( (features): Sequential( (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU(inplace=True) (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (3): ReLU(inplace=True) (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (6): ReLU(inplace=True) (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (8): ReLU(inplace=True) (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (11): ReLU(inplace=True) (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (13): ReLU(inplace=True) (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (15): ReLU(inplace=True) (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (18): ReLU(inplace=True) (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (20): ReLU(inplace=True) (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (22): ReLU(inplace=True) (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (25): ReLU(inplace=True) (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (27): ReLU(inplace=True) (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (29): ReLU(inplace=True) (30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (avgpool): AdaptiveAvgPool2d(output_size=(7, 7)) (classifier): Sequential( (0): Linear(in_features=25088, out_features=4096, bias=True) (1): ReLU(inplace=True) (2): Dropout(p=0.5, inplace=False) (3): Linear(in_features=4096, out_features=4096, bias=True) (4): ReLU(inplace=True) (5): Dropout(p=0.5, inplace=False) (6): Linear(in_features=4096, out_features=1000, bias=True) ) )
train_modelは以下の通り
def train_model(net, dataloaders_dict, criterion, optimizer, num_epochs): # epochのループ for epoch in range(num_epochs): print('Epoch {}/{}'.format(epoch + 1, num_epochs)) print('-------------') # epochごとの学習と検証のループ for phase in ['train', 'val']: if phase == 'train': net.train() # モデルを訓練モードに else: net.eval() # モデルを検証モードに epoch_loss = 0.0 # epochの損失和 epoch_corrects = 0 # epochの正解数 # 未学習時の検証性能を確かめるため、epoch=0の訓練は省略 if (epoch == 0) and (phase == 'train'): continue # データローダーからミニバッチを取り出すループ for inputs, labels in tqdm(dataloaders_dict[phase]): # optimizerを初期化 optimizer.zero_grad() # 順伝搬(forward)計算 with torch.set_grad_enabled(phase == 'train'): outputs = net(inputs) loss = criterion(outputs, labels) # 損失を計算 _, preds = torch.max(outputs, 1) # ラベルを予測 # 訓練時はバックプロパゲーション if phase == 'train': loss.backward() optimizer.step() # イタレーション結果の計算 # lossの合計を更新 epoch_loss += loss.item() * inputs.size(0) # 正解数の合計を更新 epoch_corrects += torch.sum(preds == labels.data) # epochごとのlossと正解率を表示 epoch_loss = epoch_loss / len(dataloaders_dict[phase].dataset) epoch_acc = epoch_corrects.double( ) / len(dataloaders_dict[phase].dataset) print('{} Loss: {:.4f} Acc: {:.4f}'.format( phase, epoch_loss, epoch_acc))