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の記事を書くことは初めてでしたが、間違ったことを書かないことを意識して、それぞれについて再び調べることで、私自身非常に勉強になりました。まだまだ未熟者ですので温かい目で見守っていただければ幸いです。
次回は、ハイパーパラメータチューニングをしてモデルの性能を向上させてみたいと思います。