Python

【入門】Optunaを用いたPyTorchのハイパーパラメータチューニング

【入門】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で定義したモデルのハイパーパラメータチューニングを行います。

基本的には、下記の記事で扱ったモデルのハイパーパラメータチューニングを行います。

 

【入門】PyTorchの使い方をMNISTデータセットで学ぶ(15分)本記事では、MNISTデータセットを利用して、PyTorchの実装の流れを紹介しました。本記事を通して読めば、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モジュールに関して詳しく知りたい方は下記を参考にしてください。

 

PytorchのDatasetを徹底解説(自作データセットも作れる)PyTorchのDataset作成方法を徹底的に解説しました。本記事を読むことで、Numpy, Pandasから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のチューニングを行う方法を紹介しました。

読者の方々は、チューニングするパラメータを変えたり、探索範囲を変えて遊んで見てください。

 

ABOUT ME
努力のガリレオ
【運営者】 : 東大で理論物理を研究中(経歴)東京大学, TOEIC950点, NASA留学, カナダ滞在経験有り, 最優秀塾講師賞, オンライン英会話講師試験合格, ブログと独自コンテンツで収益6桁達成 【編集者】: イングリッシュアドバイザーとして勤務中(経歴)中学校教諭一種免許取得[英語],カナダ留学経験あり, TOEIC650点
Python学習を効率化させるサービス

 

Pythonを学習するのに効率的なサービスを紹介していきます。

まず最初におすすめするのは、Udemyです。

Udemyは、Pythonに特化した授業がたくさんあり、どの授業も良質です。

また、セール中は1500円定義で利用することができ、コスパも最強です。

下記の記事では、実際に私が15個以上の講義を受講して特におすすめだった講義を紹介しています。

 

【最新】UdemyでおすすめのPythonコース|東大生が厳選!10万を超える講座があるUdemyの中で、Pythonに関係する講座を厳選しました。また、本記事では、Udemyを使用しながらPythonをどのような順番で勉強するべきかを紹介しました。ぜひ参考にしてください。...

 

他のPythonに特化したオンライン・オフラインスクールも下記の記事でまとめています。

 

【最新】Pythonに強いプログラミングスクール7選|東大生が厳選Pythonの流行と共に、Pythonに強いプログラミングスクールが増えてきました。本記事では、特にPythonを効率的に学ぶことができるプログラミングスクールを経験をもとに厳選して、内容を詳しく解説しています。...

 

自分の学習スタイルに合わせて最適なものを選びましょう。