【Optuna】Optuna Tutorial with Pytorch
Optuna Tutorial with Pytorch
先日PFNからハイパーパラメータチューニングを自動でやってくれるというフレームワークが公開されました。
PFN内でもOpen Images Challenge 2018の際にはこれを用いてパラメータチューニングをしていたとか。
これは使うっきゃない!!
ということで、PytorchでMNISTを通してoptunaのtutorialをやります!
最近流行りのgoogle colaborator上で試してみました。
ちなみに2019年2月からgoogle colabでpytorchがデフォルトインストールされたようです。
環境
google colabrator (2019/3)
- optuna: 0.9.0
- pytorch: 1.0.1.post2
1. Optunaのインストール
インストール方法は公式を参考に。
pip install optuna
google colab上では先頭に!をつける必要があります。
!pip install optuna
2. MNISTデータの準備
今回は簡単のためMNISTデータを使って遊んでいきます。
from torch.utils.data import DataLoader from torchvision.datasets import MNIST from torchvision import transforms import numpy as np # バッチサイズ BATCH_SIZE = 64 # 入力画像を[-1, 1]に正規化 # 参考:https://discuss.pytorch.org/t/understanding-transform-normalize/21730 transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))]) # MNISTデータのダウンロード&DataLoaderインスタンス作成 train_set = MNIST(root='./data', train=True, download=True, transform=transform) train_loader = DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True, num_workers=2) test_set = MNIST(root='./data', train=False, download=True, transform=transform) test_loader = DataLoader(test_set, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)
3. モデルの設計
まだOptunaは出てきません。
import torch.nn as nn import torch.nn.functional as F class Net(nn.Module): def __init__(self, activation): super(Net, self).__init__() self.activation = activation self.conv1 = nn.Conv2d(1, 16, kernel_size=3) self.conv2 = nn.Conv2d(16, 32, kernel_size=3) self.conv3 = nn.Conv2d(32, 10, kernel_size=3) def forward(self, x): x = self.activation(F.max_pool2d(self.conv1(x), 2)) x = self.activation(F.max_pool2d(self.conv2(x), 2)) x = self.activation(F.max_pool2d(self.conv3(x), 2)) x = F.adaptive_avg_pool2d(x, output_size=(1, 1)) return x.view(-1, 10)
4. トレーニング & テスト用設定
def train(model, device, train_loader, optimizer, criterion): model.train() for data, target in train_loader: data, target = data.to(device), target.to(device) # Zero the parameter gradients optimizer.zero_grad() # forward + backward + optimize output = model(data) loss = criterion(output, target) loss.backward() optimizer.step() def evaluate(model, device, test_loader): model.eval() correct = 0 with torch.no_grad(): for data, target in test_loader: data, target = data.to(device), target.to(device) output = model(data) _, predicted = torch.max(output.data, 1) correct += (predicted == target).sum().item() accuracy = 1 - correct / len(test_loader.dataset) return accuracy
5. Optunaを用いたハイパーパラメータの設定
ようやくOptunaの実装が始まります。
記事執筆現在では、Optunaでは5つのパラメータ設定方法があります。
# 1. Categorical parameter optimizer = trial.suggest_categorical('optimizer', ['MomentumSGD', 'Adam']) # 2. Int parameter num_layers = trial.suggest_int('num_layers', 1, 3) # 3. Uniform parameter dropout_rate = trial.suggest_uniform('dropout_rate', 0.0, 1.0) # 4. Loguniform parameter learning_rate = trial.suggest_loguniform('learning_rate', 1e-5, 1e-2) # 5. Discrete-uniform parameter drop_path_rate = trial.suggest_discrete_uniform('drop_path_rate', 0.0, 1.0, 0.1)
上記の例のようにハイパーパラメータそれぞれの特性に合わせてサンプリング方法を選択し、探索したいパラメータを設定します。
今回は、optimizer, activation, weight_decay, learning rateをOptunaを使って最適化できるようにしていきます。
import torch.optim as optim def get_optimizer(trial, model): # optimizer をAdamとMomentum SGDで探索 optimizer_names = ['Adam', 'MomentumSGD'] optimizer_name = trial.suggest_categorical('optimizer', optimizer_names) # weight decayの探索 weight_decay = trial.suggest_loguniform('weight_decay', 1e-10, 1e-3) # optimizer_nameで分岐 if optimizer_name == optimizer_names[0]: adam_lr = trial.suggest_loguniform('adam_lr', 1e-5, 1e-1) optimizer = optim.Adam(model.parameters(), lr=adam_lr, weight_decay=weight_decay) else: momentum_sgd_lr = trial.suggest_loguniform('momentum_sgd_lr', 1e-5, 1e-1) optimizer = optim.SGD(model.parameters(), lr=momentum_sgd_lr, momentum=0.9, weight_decay=weight_decay) return optimizer def get_activation(trial): # 活性化関数の探索. ReLU or ELu. return trial.suggest_categorical('activation', [F.relu, F.elu])
6. 目的関数の設計
Optunaは現在、目的関数の最小化のみをサポートしているため目的関数の返り値は小さくなることが目標になるように設計する必要があります。そのため目的関数の返り値をaccuracyではなく、error rateに設定しています。
import optuna import torch EPOCH = 5 def objective(trial): device = "cuda" if torch.cuda.is_available() else "cpu" activation = get_activation(trial) model = Net(activation).to(device) optimizer = get_optimizer(trial, model) criterion = nn.CrossEntropyLoss() # Training for step in range(EPOCH): train(model, device, train_loader, optimizer, criterion) # Evaluation accuracy = evaluate(model, device, test_loader) # 返り値が最小となるようにハイパーパラメータチューニングが実行される return 1.0 - accuracy
7. Optunaの実行
ここまでで準備ができたため、次は実際に最適化を実行していきます。
まずoptuna.create_study()
で学習用のインスタンスを作成します。そしてoptimize()
メソッドで目的関数の返り値を元にパラメータチューニングを実行します。
study = optuna.create_study()
study.optimize(objective, n_trials=100)
8. 結果の確認
以下の操作で各種結果が確認できます。
tudy.best_params # Get best parameters for the objective function. study.best_value # Get best objective value. study.best_trial # Get best trial's information. study.trials # Get all trials' information. study.trials_dataframe() # Get a pandas dataframe like
番外編
RDB(Relational Data Base)を利用した最適化結果の保存
簡単にSQLiteを利用した結果の保存が可能になっています。
study_name = 'example-study' # Unique identifier of the study. study = optuna.create_study(study_name=study_name, storage='sqlite:///example.db')
最適化の再開
create_study()の引数であるload_if_exsits
をTrueにすることで保存した結果を読み込んで最適化を再開可能です。
study = optuna.create_study(study_name='example-study', storage='sqlite:///example.db', load_if_exists=True) study.optimize(objective, n_trials=3)
attributeの追加
set_usr_attr()
を用いて任意の値を結果に保存していくことができます。
def objective(trial): accuracy = ... trial.set_user_attr('accuracy', accuracy) return 1.0 - accuracy
まとめ
元々のコードをほとんどいじることなくパラメータチューニングを行えるため非常に便利ですね。
さすがPFN。俺達にできないことを平然とやってのけるッ
そこにシビれる!あこがれるゥ!