LoginSignup
6
3

More than 1 year has passed since last update.

ニューラルネットワークにおけるOptunaを用いたハイパーパラメータチューニング

Last updated at Posted at 2021-12-22

Optunaを使ってハイパーパラメータチューニングを体験しよう!

自己紹介

こんにちは。豊田工業大学3年のひのです。

お恥ずかしながら、プログラミングをちゃんと勉強し始めてまだ1年ですので、学習記録として残します。今回はNNモデルのハイパーパラメータチューニングをOptunaを用いて実装します。

他にも豊田工業大学Kaggleサークルの皆さんがQiitaの記事を書いているので、是非見てくださいね!下に同大学の友人と先輩の記事貼っておきますね。

CNNを使って0から9の数字を分類する:@umeboshia_0w
計算力がなければPythonを使えばいいじゃない:@Kofuku

プログラムのコードに関してはこちらの方のものを参考に一部改変させていただいています。
Optuna で PyTorch のハイパーパラメータチューニングを試みる(テーブルデータ回帰)

実行環境

  • Windowsの使用

    • エディション Windows 10 Pro
    • バージョン 21H1
    • OSビルド 19043.1415
  • Python 3.7.12

    • pandas 1.1.5
    • torch 1.10.0+cu111

データセット

データセットはKaggleのデータセットを使用しました。
House Prices - Advanced Regression Techniques
Predict sales prices and practice feature engineering, RFs, and gradient boosting

このコンペの内容はボストンの住宅価格を予測するもので、データとしては部屋数や犯罪率などが含まれています。私自身このコンペを素通りしていたのでいい機会だと思い、このデータセットを使用しております。

データの読み込み

まずは、訓練データとテストデータを確認します。

import pandas as pd

train = pd.read_csv("./House Prices/train.csv")
test = pd.read_csv("./House Prices/test.csv")
train.head()
Id MSSubClass MSZoning LotFrontage LotArea Street ... SalePrice
1 60 RL 65.0 8450 Pave ... 208500
2 20 RL 80.0 9600 Pave ... 181500
3 60 RL 68.0 11250 Pave ... 223500
4 70 RL 60.0 9550 Pave ... 140000
5 60 RL 84.0 14260 Pave ... 250000

前処理

次に、欠損値補間をしたいのでこの二つのデータにおいて、それぞれの説明変数でデータを連結させます。

all_X = pd.concat((train.loc[:, 'MSSubClass':'SaleCondition'],
                      test.loc[:, 'MSSubClass':'SaleCondition']))

連結したそれぞれのカラムにおけるデータの型を確認します。

print(all_X.dtypes)
------------------------
MSSubClass         int64
MSZoning          object
LotFrontage      float64
LotArea            int64
Street            object
                  ...   
MiscVal            int64
MoSold             int64
YrSold             int64
SaleType          object
SaleCondition     object
Length: 79, dtype: object

int型object型のデータが含まれていることが確認できました。object型は単にstr型ではないので注意してください。詳細に関しては以下を参照してください。
Pythonのオブジェクトとは

それぞれのデータに対して、学習できる形にデータを成形します。

numnic_feats = all_X.dtypes[all_X.dtypes!='object'].index
all_X[numnic_feats] = all_X[numnic_feats].apply(lambda x: (x-x.mean())/x.std())
all_X = pd.get_dummies(all_X, dummy_na=True)

まず、object型ではないデータに対しては、正規化を行います。次に、pd.get_dummiesにより、Nanも1つのカテゴリーとしてダミー変数化します。

all_X.isnull().sum()
----------------
MSSubClass                 0
LotFrontage              486
LotArea                    0
OverallQual                0
OverallCond                0
                        ... 
SaleCondition_Alloca       0
SaleCondition_Family       0
SaleCondition_Normal       0
SaleCondition_Partial      0
SaleCondition_nan          0
Length: 331, dtype: int64

欠損値を確認し、欠損値を平均値で補完します。

all_X = all_X.fillna(all_X.mean())

以上で簡単ではありますが前処理を終えたので、データを訓練データとテストデータに戻しておきましょう。

num_train = train.shape[0]
X_train = all_X[:num_train]
X_test = all_X[num_train:]
y_train = train.SalePrice

ニューラルネットワーククラスの定義

class Net(nn.Module):

  def __init__(self, input_dim, hidden_dim, n_layers):
    super().__init__()
    self.input_dim = input_dim
    self.hidden_dim = hidden_dim
    self.n_layers = n_layers

    self.input_layer = nn.Sequential(
        nn.BatchNorm1d(input_dim),
        nn.Linear(input_dim, hidden_dim),
        nn.ReLU(inplace=True)
    )

    middle = []
    for _ in range(n_layers):
      middle.append(nn.BatchNorm1d(hidden_dim),)
      middle.append(nn.Linear(hidden_dim, hidden_dim))
      middle.append(nn.ReLU(inplace=True))

    self.middle_layers = nn.Sequential(*middle)

    self.output_layer = nn.Linear(hidden_dim, 1)


  def forward(self, x):
    x = self.input_layer(x)
    x = self.middle_layers(x)
    x = self.output_layer(x)
    return x

今回NNモデルのハイパーパラメータチューニングの対象としては以下の4つです。

  • 中間層の数
  • 中間層のノード数
  • 最適化手法
  • 学習率

input_dimは、入力層へのノード数です。データの説明変数の次元を変更した際に、それに合わせて変更する必要があります。
hidden_dimは、中間層のノード数です。なお、中間層の活性化関数はReLU関数を使用しています。
n_layersは、中間層の数です。何層の中間層にするかを決定します。
このように、Optunaでのハイパーパラメータチューニングの際に、中間層の数とノード数を最適化したいため、このように定義しています。なお、活性化関数の最適化も可能です。

目的関数の定義

max_epoch = 30
criterion = nn.MSELoss()

def objective(trial):
  device = "cuda" if torch.cuda.is_available() else "cpu"

  n_layers = trial.suggest_int("n_layers", 1, 5)
  hidden_dim = trial.suggest_int("hidden_dim", 8, 256, 8)
  optimizer = trial.suggest_categorical("optimizer", ["Adam", "RMSprop", "SGD"])
  learning_rate = trial.suggest_discrete_uniform("learning_rate", 1e-5, 1e-1, q=1e-5)

  net = Net(input_dim=331, hidden_dim=hidden_dim, n_layers=n_layers).to(device)
  optimizer = getattr(torch.optim, optimizer)(net.parameters(), lr=learning_rate)

  # train 
  net.train()
  for epoch in range(max_epoch):
    for batch in train_dataloader:
      x, t = batch
      x.to(device)
      t.to(device)

      optimizer.zero_grad()
      y = net(x)
      loss = criterion(y, t)
      loss.backward()
      optimizer.step()

  # validation
  net.eval()
  validation_loss = 0.0
  with torch.no_grad():
    for batch in test_dataloader:
      x, t = batch
      x.to(device)
      t.to(device)
      y = net(x)
      loss = criterion(y, t)
      validation_loss += loss.item() * x.size(0)

    validation_loss = validation_loss / len(test_dataloader.dataset)

  return validation_loss

objective()という関数で最小化する目的関数を設定します。trialオブジェクトは、Optunaに実装されているクラスのインスタンスで、最適化したい対象のハイパーパラメータの定義に用います。
ハイパーパラメータチューニングのパラメータの設定を行います。

  • 中間層の数: 1~5
  • 中間層のノード数: 8~256 (8間隔)
  • 最適化手法: "Adam", "RMSprop", "SGD"
  • 学習率: 1e-5~1e-1 (1e-5間隔)

studyオブジェクトの定義

import optuna

study = optuna.create_study()
study.optimize(objective, n_trials=50)

ハイパーパラメータチューニングを行うstudyオブジェクトoptuna.create_study()で作成します。なお、今回はスコアを最小化していますが、最大化したい場合は、optuna.create_study(direction='maximize')で最大化を行います。
study.optimizeにより目的関数の最小化を行い、ハイパーパラメータチューニングを行います。なお、試行回数を指定していますが、timeout=60のように時間で指定することも可能です。

study.best_trial
---------------------------
FrozenTrial(number=1, values=[6909027840.0], datetime_start=datetime.datetime(2021, 12, 22, 16, 26, 32, 457532), datetime_complete=datetime.datetime(2021, 12, 22, 16, 26, 33, 355795), params={'n_layers': 4, 'hidden_dim': 40, 'optimizer': 'RMSprop', 'learning_rate': 0.08705}, distributions={'n_layers': IntUniformDistribution(high=5, low=1, step=1), 'hidden_dim': IntUniformDistribution(high=256, low=8, step=8), 'optimizer': CategoricalDistribution(choices=('Adam', 'RMSprop', 'SGD')), 'learning_rate': DiscreteUniformDistribution(high=0.1, low=1e-05, q=1e-05)}, user_attrs={}, system_attrs={}, intermediate_values={}, trial_id=1, state=TrialState.COMPLETE, value=None)

ハイパーパラメータだけを見てみましょう。

#学習結果
params={'n_layers': 4, 'hidden_dim': 40, 'optimizer': 'RMSprop', 'learning_rate': 0.08705}

このように、中間層の数、中間層のノード数、最適化手法、学習率の4つに対してハイパーパラメータチューニングを行うことができました。

まとめ

ここまで読んで頂き、誠にありがとうございます。

Qiitaの記事を書くことは初めてでしたが、間違ったことを書かないことを意識して、それぞれについて再び調べることで、私自身非常に勉強になりました。まだまだ未熟者ですので温かい目で見守っていただければ幸いです。

次回は、ハイパーパラメータチューニングをしてモデルの性能を向上させてみたいと思います。

参考

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3