
【後編】Pytorchの様々な最適化手法(torch.optim.Optimizer)の更新過程や性能を比較検証してみた!
2021.12.20
後編~最適化手法の更新過程や性能を比較検証してみよう!~
前回に引き続き、Pytorchに用意されている各種最適化手法(torch.optim.Optimizer)の学習過程がどのように異なるのかについて、「損失関数」や「精度の比較」により検証します。
本記事は「前編」「後編」でお届けいたします。
前編はこちら
全結合層ニューラルネットワークによる検証
では続いて、ニューラルネットワークのモデルで実際にデータを与えて、各種最適化手法の検証を行っていきたいと思います。
学習データには、「MNISTデータセット」を用います。
コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import torch from torchvision import datasets, transforms from torch import nn, optim import torch.nn.functional as F # transforms transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))]) # dataset train_dataset = datasets.MNIST(root='./content/', train=True, transform=transform, download=True) test_dataset = datasets.MNIST(root='./content/', train=False, transform=transform) # Data Loader batch_size = 256 train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True) test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False) |
学習モデルはシンプルな全結合層ネットワークで、上記で行ったように各 optimizer 毎に学習を行い、「学習中の損失関数」と「精度の推移」を記録します。
各optimizerのパラメータ
各 optimizer には、「model.parameters()」によるモデルのパラメータと学習率 (lr = 0.01) を渡しています。
また、それ以外はデフォルトの値となっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 | #Model class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.fc1 = nn.Linear(28*28, 100) self.fc2 = nn.Linear(100, 100) self.fc3 = nn.Linear(100, 10) def forward(self, x): x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) return self.fc3(x) criterion = nn.CrossEntropyLoss() classes = ['SGD', 'Adagrad', 'RMSprop', 'Adadelta', 'Adam', 'AdamW'] loss_fc = {} acc_fc = {} optimizers = {} for key in classes: loss_fc[key] = [] acc_fc[key] = [] lr = 0.01 for key in classes: model = Net() optimizers['SGD'] = optim.SGD(model.parameters(), lr) optimizers['Adagrad'] = optim.Adagrad(model.parameters(), lr) optimizers['RMSprop'] = optim.RMSprop(model.parameters(), lr) optimizers['Adadelta'] = optim.Adadelta(model.parameters(), lr) optimizers['Adam'] = optim.Adam(model.parameters(), lr) optimizers['AdamW'] = optim.AdamW(model.parameters(), lr) for epoch in range(20): model.train() for image, label in train_loader: image = image.view(-1, 28*28) optimizers[key].zero_grad() output = model(image) loss = criterion(output, label) loss.backward() optimizers[key].step() loss_fc[key].append(loss.item()) model.eval() with torch.no_grad(): for t_image, t_label in test_loader: t_image = t_image.view(-1, 28*28) output = model(t_image) _, predicted = torch.max(output, 1) class_correct = (predicted == t_label).sum().item() acc = class_correct / len(predicted) * 100 acc_fc[key].append(acc) |
損失関数の値の比較
まず以下が、損失関数の値の推移の結果になります。

「Adagrad」、「Adam」、「AdamW」は早期に損失関数の値が小さい値に収束しています。
それに比べて、「Adadelta」、「RMSprop」、「SGD」は学習の速度が遅いということがわかります。
また「RMSprop」については、他と比べて値が大きく上下しており安定していない様子が見られます。
精度の比較
さらに、以下のグラフが精度の推移になります。

損失関数の値と同様に、「Adagrad」、「Adam」、「AdamW」の精度は他と比べ高いことがわかります。
また、損失関数のグラフにノイズが大きかった「RMSprop」では、精度も安定していません。
学習初期を比較
精度の学習初期(~100 iterations)を拡大すると、以下のようになります。

いずれも学習が進めば精度が向上するものの、 「Adadelta」や「SGD」は初期の精度が他と比べると低いことが分かります。
また、損失関数の挙動にも現れているように、学習収束速度は比較的遅いという結果になりました。
CNNモデルによる検証
次に、 CNN モデルにより、先ほどと同様に検証したいと思います。
コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 | #CNN Model class Cnn(nn.Module): def __init__(self): super(Cnn, self).__init__() self.conv1 = nn.Conv2d(1, 6, 5) self.conv2 = nn.Conv2d(6, 16, 5) self.pool = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(16*4*4, 100) self.fc2 = nn.Linear(100, 10) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = x.view(-1, 16*4*4) x = F.relu(self.fc1(x)) return self.fc2(x) classes = ['SGD', 'Adagrad', 'RMSprop', 'Adadelta', 'Adam', 'AdamW'] loss_cnn = {} acc_cnn = {} for key in classes: loss_cnn[key] = [] acc_cnn[key] = [] lr = 0.01 criterion = nn.CrossEntropyLoss() optimizers = {} for key in classes: model = Cnn() optimizers['SGD'] = optim.SGD(model.parameters(), lr) optimizers['Adagrad'] = optim.Adagrad(model.parameters(), lr) optimizers['RMSprop'] = optim.RMSprop(model.parameters(), lr) optimizers['Adadelta'] = optim.Adadelta(model.parameters(), lr) optimizers['Adam'] = optim.Adam(model.parameters(), lr) optimizers['AdamW'] = optim.AdamW(model.parameters(), lr) for epoch in range(20): model.train() for image, label in train_loader: optimizers[key].zero_grad() output = model(image) loss = criterion(output, label) loss.backward() optimizers[key].step() loss_cnn[key].append(loss.item()) model.eval() with torch.no_grad(): for t_image, t_label in test_loader: output = model(t_image) _, predicted = torch.max(output, 1) class_correct = (predicted == t_label).sum().item() acc = class_correct / len(predicted) * 100 acc_cnn[key].append(acc) |
比較結果
モデルの違いにより、損失関数の収束値と精度は共に向上しています。
特に、「RMSprop」はノイズが抑えられ、精度共に改善されたと思います。
全結合層ネットワークの場合と同様に、「Adadelta」と「SGD」では学習速度が他に比べ遅い結果となりました。
以下は、各比較結果のグラフです。
損失関数の値の比較

精度の比較

学習初期の比較

学習初期は、やはり学習速度に違いが出ていることがわかります。
まとめ
今回は、「Adagrad」や「Adam」が、学習速度や精度共に優れているという結果となりました。
しかし厳密には、各 optimizer 毎の詳細なチューニング(学習率など各パラメータの設定)や、ネットワークモデル、学習データにより結果が変わってきます。
また、向き不向きが方法によって違うので、optimizer 毎に詳細な検討は必要かと思います。
さいごに
今回は、 Pytorch を使用して、各種最適化手法の違いについて検証しました。
最適化手法は、ここで紹介したものだけでなく、どんどん新しいものも提案されています。
目的に合わせて、自分の環境や設定の中で変数を変えながら、色々な最適化手法を試してみてください!
前回の記事
こちらの記事もオススメ!
書いた人はこんな人

- 「好きを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もり大歓迎!
また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」「WEBディレクター」を積極採用中です!
インターンや新卒採用も行っております。
以下よりご応募をお待ちしております!
https://rightcode.co.jp/recruit
ライトコードの日常12月 1, 2023ライトコードクエスト〜東京オフィス歴史編〜
ITエンタメ10月 13, 2023Netflixの成功はレコメンドエンジン?
ライトコードの日常8月 30, 2023退職者の最終出社日に密着してみた!
ITエンタメ8月 3, 2023世界初の量産型ポータブルコンピュータを開発したのに倒産!?アダム・オズボーン