【入門】Optunaを用いたPyTorchのハイパーパラメータチューニング
本記事では、Optunaというパッケージを使用して、PyTorchのハイパーパラメータチューニングを行う方法を紹介します。
簡単な具体例を使って解説するので、初心者の方でも理解することができます。
ぜひ、参考にしてください。
Optunaとは
Optunaは、ベイズ最適化と呼ばれる手法を用いて、自動的にハイパーパラメータのチューニングを行うフレームワークです。
Optunaの使い方
まずは、Optunaの使い方を公式チュートリアルにある例を使って説明します。
def objective(trial):
x = trial.suggest_float("x", -10, 10)
return (x - 2) ** 2
# studyインスタンスを作成
study = optuna.create_study()
# 最適化を実行
study.optimize(objective, n_trials=100)
<output>
[I 2021-10-29 09:12:55,736] A new study created in memory with name: no-name-0b4ddcca-8aca-4be8-8fbd-a5761efe9a7e
[I 2021-10-29 09:12:55,749] Trial 0 finished with value: 58.54166053686611 and parameters: {'x': 9.651252220183707}. Best is trial 0 with value: 58.54166053686611.
:
:
:
[I 2021-10-29 09:12:56,394] Trial 98 finished with value: 3.3996325188321137 and parameters: {'x': 0.15619075855659248}. Best is trial 54 with value: 0.0014086787327806955.
[I 2021-10-29 09:12:56,401] Trial 99 finished with value: 5.958673156532506 and parameters: {'x': -0.44103935989006665}. Best is trial 54 with value: 0.0014086787327806955.
これは、二次関数の最小値を求めるコードになっています。
簡単に対応するコードの説明を以下にまとめます。
objective
という関数は、最小化したい目的関数を出力するように記述trial
オブジェクトは、最適化したいハイパーパラメータを定義するoptuna.create_study()
で最適化専用のインスタンスを生成.optimize(objective, parameter)
で最適化を実行(引数で試行回数等を調節できる)
trial
オブジェクトとして、float型変数を-10〜10の範囲を探索するように設定しています。
それ以外のバリエーションを簡単に以下にまとめておきます。
trialの指定方法 | 説明 |
---|---|
suggest_categorical |
カテゴリ変数の探索 |
suggest_int |
整数値の探索 |
suggest_discrete_uniform |
離散値の探索 |
suggest_loguniform |
対数値の探索 |
また、最適化した結果は以下のようにアクセスできます。
print('最適化されたパラメータ: ', study.best_params)
print('最適値: ', study.best_value)
print('最適値を出した時の試行の情報: ', study.best_trials)
<output>
最適化されたパラメータ: {'x': 1.9624676308664015}
最適値: 0.0014086787327806955
最適値を出した時の試行の情報: [FrozenTrial(number=54, values=[0.0014086787327806955], datetime_start=datetime.datetime(2021, 10, 29, 9, 12, 56, 81816), datetime_complete=datetime.datetime(2021, 10, 29, 9, 12, 56, 85391), params={'x': 1.9624676308664015}, distributions={'x': UniformDistribution(high=10.0, low=-10.0)}, user_attrs={}, system_attrs={}, intermediate_values={}, trial_id=54, state=TrialState.COMPLETE, value=None)]
公式チュートリアルにある動画の解説もとてもわかりやすいです。
時間がある方は参考にしてください。
Optunaを用いたPyTorchのハイパーパラメータチューニング
ここからは、Optunaを使用して、PyTorchで定義したモデルのハイパーパラメータチューニングを行います。
基本的には、下記の記事で扱ったモデルのハイパーパラメータチューニングを行います。
学習や推論のコードで悩んだら下記を参考にしてください。
使用するライブラリをインポート
まずは、使用するライブラリをインポートします。
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
データセットのインポート
今回は、PyTorchのサンプルデータであるMNISTという手書き数字のデータセットを使用します。
下記を実行して、サンプルデータをダウンロードしてください。
train_dataset = torchvision.datasets.MNIST(root='./data',
train=True,
transform=transforms.ToTensor(),
download=True)
test_dataset = torchvision.datasets.MNIST(root='./data',
train=False,
transform=transforms.ToTensor(),
download=True)
datasetのダウンロード方法や、datasetモジュールに関して詳しく知りたい方は下記を参考にしてください。
1エポックの学習と推論の実装
学習や推論をある程度モジュール化して記述しておくと、後で変更しやすいため便利です。
ここでは、1エポックの学習と推論のコードを実装しましょう。
まずは、学習用のコードを下記に示します。
def train_epoch(model, optimizer, criterion, dataloader, device):
model.train()
for step, (images, labels) in enumerate(dataloader):
images, labels = images.view(-1, 28*28).to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
次に推論用のコードを下記に示します。
def inference(model, optimizer, criterion, dataloader, device):
model.eval()
test_loss=0
with torch.no_grad():
for j, (images, labels) in enumerate(dataloader):
images, labels = images.view(-1, 28*28).to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
test_loss += loss.item()
test_loss = test_loss / len(dataloader.dataset)
return test_loss
DataLoaderの試行を実装
先ほども伝えましたが、コードをモジュール化した方が後から変更しやすいです。
そこで、Batchサイズをチューニングするためのコードをモジュール化して以下のように実装しておきます。
# Dataloaderの試行
def trial_loader(trial):
batch_size = int(trial.suggest_discrete_uniform('batch_size', 32, 128, 32))
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)
return train_loader, test_loader
活性化関数の試行を実装
同様に、活性化関数の試行を実行する関数も作成しましょう。
# 活性化関数の試行
def trial_activation(trial):
activation_names = ['relu', 'elu']
activation_name = trial.suggest_categorical('activation', activation_names)
if activation_name == activation_names[0]:
activation = F.relu
else:
activation = F.elu
return activation
最適化手法の試行を実装
次に最適化手法の試行を実行する関数を実装します。
# 最適化手法の試行
def trial_optimizer(trial, model):
optimizer_names = ['Adam', 'MomentumSGD', 'rmsprop']
optimizer_name = trial.suggest_categorical('optimizer', optimizer_names)
weight_decay = trial.suggest_loguniform('weight_decay', 1e-10, 1e-3)
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)
elif optimizer_name == optimizer_names[1]:
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)
else:
optimizer = optim.RMSprop(model.parameters())
return optimizer
ネットワークを試行を実装
ネットワークは、以下のように試行する部分を引数として実装しておきましょう。
class Net(nn.Module):
def __init__(self, trial, mid_units1, mid_units2, in_droprate, middle_droprate):
super(Net, self).__init__()
self.fc1 = nn.Linear(28*28, mid_units1)
self.fc2 = nn.Linear(mid_units1, mid_units2)
self.fc3 = nn.Linear(mid_units2, N_CLASS)
self.in_dropout = nn.Dropout(p=in_droprate)
self.middle_dropout = nn.Dropout(p=middle_droprate)
self.activation = trial_activation(trial)
def forward(self, x):
x = self.in_dropout(x)
x = F.relu(self.fc1(x))
x = self.middle_dropout(x)
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
object関数を作成
最後に最適化するべき値を出力とするobjective
関数を実装しましょう。
def objective(trial):
device = 'cuda' if torch.cuda.is_available() else 'cpu'
# 中間層のユニット数の試行
mid_units1 = int(trial.suggest_discrete_uniform("mid_units1", 300, 500, 100))
mid_units2 = int(trial.suggest_discrete_uniform("mid_units2", 32, 256, 32))
# drop-out rateの試行
in_droprate = trial.suggest_discrete_uniform("in_droprate", 0.0, 0.2, 0.05)
middle_droprate = trial.suggest_discrete_uniform("middle_droprate", 0.0, 0.3, 0.05)
model = Net(trial, mid_units1, mid_units2, in_droprate, middle_droprate).to(device)
criterion = nn.CrossEntropyLoss()
# 最適化手法の試行
optimizer = trial_optimizer(trial, model)
# loaderの試行
train_loader, test_loader = trial_loader(trial)
for epoch in range(EPOCH):
train_epoch(model, optimizer, criterion, train_loader, device)
test_loss = inference(model, optimizer, criterion, test_loader, device)
return test_loss
最適化を実行
あとは、下記を実行すれば最適化することができます。
TRIAL_SIZE = 5
EPOCH = 10
N_CLASS = 10
study = optuna.create_study()
study.optimize(objective, n_trials=TRIAL_SIZE)
<output>
[I 2021-10-30 03:33:52,641] A new study created in memory with name: no-name-8eb88058-6f5d-4a42-82ae-d23940e98079
[I 2021-10-30 03:35:55,000] Trial 0 finished with value: 0.003266687719746278 and parameters: {'mid_units1': 500.0, 'mid_units2': 256.0, 'in_droprate': 0.05, 'middle_droprate': 0.3, 'activation': 'elu', 'optimizer': 'MomentumSGD', 'weight_decay': 3.1798325288230876e-06, 'momentum_sgd_lr': 0.06069139833696702, 'batch_size': 32.0}. Best is trial 0 with value: 0.003266687719746278.
[I 2021-10-30 03:37:58,533] Trial 1 finished with value: 0.005614077868836467 and parameters: {'mid_units1': 300.0, 'mid_units2': 192.0, 'in_droprate': 0.15000000000000002, 'middle_droprate': 0.15000000000000002, 'activation': 'elu', 'optimizer': 'rmsprop', 'weight_decay': 1.4086246807782756e-09, 'batch_size': 32.0}. Best is trial 0 with value: 0.003266687719746278.
[I 2021-10-30 03:39:31,750] Trial 2 finished with value: 0.0008986131339275744 and parameters: {'mid_units1': 400.0, 'mid_units2': 224.0, 'in_droprate': 0.05, 'middle_droprate': 0.15000000000000002, 'activation': 'relu', 'optimizer': 'rmsprop', 'weight_decay': 1.6653710656385112e-06, 'batch_size': 128.0}. Best is trial 2 with value: 0.0008986131339275744.
[I 2021-10-30 03:41:06,008] Trial 3 finished with value: 0.000471876156579674 and parameters: {'mid_units1': 400.0, 'mid_units2': 192.0, 'in_droprate': 0.1, 'middle_droprate': 0.15000000000000002, 'activation': 'relu', 'optimizer': 'Adam', 'weight_decay': 5.533962558253067e-09, 'adam_lr': 0.0004542833419229485, 'batch_size': 128.0}. Best is trial 3 with value: 0.000471876156579674.
[I 2021-10-30 03:42:43,715] Trial 4 finished with value: 0.005172060015797615 and parameters: {'mid_units1': 400.0, 'mid_units2': 160.0, 'in_droprate': 0.1, 'middle_droprate': 0.25, 'activation': 'relu', 'optimizer': 'Adam', 'weight_decay': 8.950202373289854e-10, 'adam_lr': 0.05191883983089551, 'batch_size': 96.0}. Best is trial 3 with value: 0.000471876156579674.
チューニング後の出力は以下のように得ることができます。
study.best_params
<output>
{'activation': 'relu',
'adam_lr': 0.0004542833419229485,
'batch_size': 128.0,
'in_droprate': 0.1,
'mid_units1': 400.0,
'mid_units2': 192.0,
'middle_droprate': 0.15000000000000002,
'optimizer': 'Adam',
'weight_decay': 5.533962558253067e-09}
まとめ
本記事では、Optunaを使用して、PyTorchのチューニングを行う方法を紹介しました。
読者の方々は、チューニングするパラメータを変えたり、探索範囲を変えて遊んで見てください。
Pythonを学習するのに効率的なサービスを紹介していきます。
まず最初におすすめするのは、Udemyです。
Udemyは、Pythonに特化した授業がたくさんあり、どの授業も良質です。
また、セール中は1500円定義で利用することができ、コスパも最強です。
下記の記事では、実際に私が15個以上の講義を受講して特におすすめだった講義を紹介しています。
他のPythonに特化したオンライン・オフラインスクールも下記の記事でまとめています。
自分の学習スタイルに合わせて最適なものを選びましょう。
また、私がPythonを学ぶ際に使用した本を全て暴露しているので参考にしてください。