Optunaでのハイパーパラメーターのチューニングが途中で止まってしまう問題

しょくぶつ(^^)です。

Tensorflowをある程度使いこなしている皆様におかれましては 「Optuna」でもりもりハイパーパラメーターをチューニングしているかと思います。

  Optunaとは
  https://cpptake.com/archives/629

ところが、Tensorflowの1回の学習はちゃんとできるのに、
Optunaでパラメーター最適化のためにTensorflowでたくさん学習しようとすると、

管理者権限ある場合 (ローカルPCで実行など)

Trial 1 failed with parameters: {'num_layer': 2, 'mid_units': 400.0, 'num_filter_0': 64.0, 'num_filter_1': 32.0, 'dropout_rate': 0.3470125488297575, 'optimizer': 'adam'} because of the following error: ResourceExhaustedError().

とか、

管理者権限のない場合 ( HPCで実行など )

Trial 2 failed with parameters: {'num_layer': 5, 'mid_units': 1000.0, 'num_filter_0': 224.0, 'num_filter_1': 480.0, 'num_filter_2': 128.0, 'num_filter_3': 320.0, 'num_filter_4': 192.0, 'dropout_rate': 0.05124881725141045, 'optimizer': 'sgd'} because of the following error: KeyboardInterrupt().
※ Ctrl+Cしていなくても

とかになって、強制終了して、Trialがちっとも先に進まなくなっちゃった・・・
以前はできたのに・・・
とかで途方に暮れているモニターの前のあなた !!
これが原因ではないでしょうか?!

  Optuna + Keras が GPU のメモリを食いつぶす #TensorFlow - Qiita
  https://qiita.com/U25CE/items/6aded7f16c825e7be275

GPU メモリの食いつぶし。 たしかに思い当たります。
学習データが以前よりずっと増えて、1G未満から3Gに増えたのが原因かも・・・

さて、対策ですが、このサイト様の方法では、私はダメでした。
その代わり、
  元  データを objective の外で読み込む
  対策 データを objective の「中」で読み込む。
     そして、objectiveの最後で明示的に破棄。
と変更したら、見事に解消されました!!

具体的には次の通りです。

私の場合は、3GBの学習データを使って、optunaでパラメーター最適化していました。

( いやー、過去ブログ "スプラ3対戦相手スペシャル予測"  でそれなりの精度を出すには、これくらいの学習データが必要なんですよ。)

plant-smile.hatenadiary.jp

 
ところが、

study.optimize(objective, n_trials=30)

とやって30回試行したかったのですが、試行の2~3回目、ひどいときは1回目の途中で上記エラーが出てしまいました。
1回あたりの学習時間が長いので、途中で止まるとショックでかいです・・・
しかも強制終了したんで、途中経過すら回収できないし。

なお、 KeyboardInterrupt とは出ていますが決してCtrl+Cしていません。
どうも、マシンがクラッシュする前にメモリを占有しすぎているジョブを強制終了するOOMキラー(Out Of Memory Killer)が働いて強制終了されたらしいです。
こんなとときは KeyboardInterrupt(). と通知されるようです。

そこで、 元のコード(beforre.py)

# -*- coding: utf-8 -*-
"""
Created on Tue Apr 09 22:47:04 2024
@author: Plant (^^)
"""
import numpy as np
import tensorflow as tf
import gc
from tensorflow.keras import models
from tensorflow.keras import layers
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras import backend
import optuna

# 次のURLを参考にハイパーパラメーター最適化
# https://cpptake.com/archives/629

#CNNモデルの定義
# (64,64,3)の画像を入力
# 最適化したいパラメータは次のように変数にしています。
#   畳み込み層の深さ(num_layer), 各畳み込み層のフィルターの形状(num_filters)
#   全結合層のノード数(mid_units), dropout率(dropout_rate)
#  出力は136個(=ブキの数)
def create_model(num_layer, mid_units, num_filters,dropout_rate):
    model = models.Sequential()
    model.add(layers.Conv2D(filters=num_filters[0], kernel_size=(3,3),
                 activation="relu",padding='same',
                 input_shape=(64,64,3)))
    model.add(layers.Conv2D(filters=num_filters[0], kernel_size=(3,3),
                 activation="relu",padding='same'))
    model.add(layers.MaxPooling2D(2,2))
    model.add(layers.Dropout(dropout_rate))
    for i in range(1,num_layer):
        model.add(layers.Conv2D(filters=num_filters[i], kernel_size=(3,3) \
                                , padding="same", activation="relu"))
        model.add(layers.Conv2D(filters=num_filters[i], kernel_size=(3,3) \
                                , padding="same", activation="relu"))
        model.add(layers.MaxPooling2D(2,2))
        model.add(layers.Dropout(dropout_rate))
    model.add(layers.Flatten())
    model.add(layers.Dense(mid_units))
    model.add(layers.Dropout(dropout_rate))
    model.add(layers.Dense(136, activation='softmax'))
    
    return model


#パラメータ最適化部
def objective(trial):
    print("Optimize Start")
    #セッションのクリア
    backend.clear_session()
    #畳込み層の数のパラメータ
    num_layer = trial.suggest_int("num_layer", 2, 5)
    #全結合層のノード数
    mid_units = int(trial.suggest_discrete_uniform("mid_units", 100, 1100, 100))
    #各畳込み層のフィルタ数
    num_filters = [int(trial.suggest_discrete_uniform("num_filter_"+str(i) \
                                    , 32, 512, 32)) for i in range(num_layer)]
    #Dropout率
    dropout_rate = trial.suggest_uniform('dropout_rate', 0.0, 0.5)
    optimizer = trial.suggest_categorical("optimizer", ["sgd", "adam"])
    model = create_model(num_layer, mid_units, num_filters,dropout_rate)
    model.compile(optimizer=optimizer,
          loss="categorical_crossentropy",
          metrics=["accuracy"])
    
    callbacks=[EarlyStopping(monitor="val_accuracy",patience=5)]
    history = model.fit(train_images, train_labels,
                    epochs=100,
                    batch_size=128,
                    verbose=1,
                    callbacks=callbacks,
                    validation_data=(test_images,test_labels))
    
    scores = model.evaluate(train_images, train_labels)
    print('accuracy={}'.format(*scores))

    del npz, train_images, train_labels, test_images, test_labels
    gc.collect()
    
    #検証用データに対する正答率が最大となるハイパーパラメータを求める
    return 1 - history.history["val_accuracy"][-1]



# # # メイン # # #

# 保存したnumpyデータ読み込み
# ※※※ after.pyでは、ここを def objective(trial): の中に移動します。 ※※※
npz = np.load("dataset.npz")
train_images = npz['arr_0']
test_images  = npz['arr_1']
train_labels = npz['arr_2']
test_labels  = npz['arr_3']
num_buki = test_labels.shape[1]
print("データを読み込みました。")
print("num_buki=",num_buki)
print(train_images.shape, test_images.shape, train_labels.shape, test_labels.shape)

# studyオブジェクト生成
study = optuna.create_study()
# 最適化実行
study.optimize(objective, n_trials=30)#時短のため30回
    
print(study.best_params)

・・・を、このコード(after.py)

# -*- coding: utf-8 -*-
"""
Created on Tue Apr 09 22:47:04 2024
@author: Plant (^^)
"""
import numpy as np
import tensorflow as tf
import gc
from tensorflow.keras import models
from tensorflow.keras import layers
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras import backend
import optuna

# 次のURLを参考にハイパーパラメーター最適化
# https://cpptake.com/archives/629

#CNNモデルの定義
# (64,64,3)の画像を入力
# 最適化したいパラメータは次のように変数にしています。
#   畳み込み層の深さ(num_layer), 各畳み込み層のフィルターの形状(num_filters)
#   全結合層のノード数(mid_units), dropout率(dropout_rate)
#  出力は136個(=ブキの数)
def create_model(num_layer, mid_units, num_filters,dropout_rate):
    model = models.Sequential()
    model.add(layers.Conv2D(filters=num_filters[0], kernel_size=(3,3),
                 activation="relu",padding='same',
                 input_shape=(64,64,3)))
    model.add(layers.Conv2D(filters=num_filters[0], kernel_size=(3,3),
                 activation="relu",padding='same'))
    model.add(layers.MaxPooling2D(2,2))
    model.add(layers.Dropout(dropout_rate))
    for i in range(1,num_layer):
        model.add(layers.Conv2D(filters=num_filters[i], kernel_size=(3,3) \
                                , padding="same", activation="relu"))
        model.add(layers.Conv2D(filters=num_filters[i], kernel_size=(3,3) \
                                , padding="same", activation="relu"))
        model.add(layers.MaxPooling2D(2,2))
        model.add(layers.Dropout(dropout_rate))
    model.add(layers.Flatten())
    model.add(layers.Dense(mid_units))
    model.add(layers.Dropout(dropout_rate))
    model.add(layers.Dense(136, activation='softmax'))
    
    return model


#パラメータ最適化部
def objective(trial):
    # before.pyからの変更点1
    # 保存したnumpyデータ読み込み
    # ※※※ befor.pyでは、ここはメインにありました ※※※
    npz = np.load("dataset.npz")
    train_images = npz['arr_0']
    test_images  = npz['arr_1']
    train_labels = npz['arr_2']
    test_labels  = npz['arr_3']
    num_buki = test_labels.shape[1]
    print("データを読み込みました。")
    print("num_buki=",num_buki)
    print(train_images.shape, test_images.shape, train_labels.shape, test_labels.shape)

    print("Optimize Start")
    #セッションのクリア
    backend.clear_session()
    #畳込み層の数のパラメータ
    num_layer = trial.suggest_int("num_layer", 2, 5)
    #全結合層のノード数
    mid_units = int(trial.suggest_discrete_uniform("mid_units", 100, 1100, 100))
    #各畳込み層のフィルタ数
    num_filters = [int(trial.suggest_discrete_uniform("num_filter_"+str(i) \
                                    , 32, 512, 32)) for i in range(num_layer)]
    #Dropout率
    dropout_rate = trial.suggest_uniform('dropout_rate', 0.0, 0.5)
    optimizer = trial.suggest_categorical("optimizer", ["sgd", "adam"])
    model = create_model(num_layer, mid_units, num_filters,dropout_rate)
    model.compile(optimizer=optimizer,
          loss="categorical_crossentropy",
          metrics=["accuracy"])
    
    callbacks=[EarlyStopping(monitor="val_accuracy",patience=5)]
    history = model.fit(train_images, train_labels,
                    epochs=100,
                    batch_size=128,
                    verbose=1,
                    callbacks=callbacks,
                    validation_data=(test_images,test_labels))
    
    scores = model.evaluate(train_images, train_labels)
    print('accuracy={}'.format(*scores))
    # before.pyからの変更点2
    # 読み込んだデータを明示的に破棄
    del npz, train_images, train_labels, test_images, test_labels
    gc.collect()
    
    #検証用データに対する正答率が最大となるハイパーパラメータを求める
    return 1 - history.history["val_accuracy"][-1]


# # # メイン # # #
# before.pyからの変更点3
# ここにあったnumpyデータ読み込みは def objective(trial): の中に移動した。

# studyオブジェクト生成
study = optuna.create_study()
# 最適化実行
study.optimize(objective, n_trials=30)#時短のため30回
    
print(study.best_params)

・・・のように修正すると、
解消されました!!
『Trial が終了するたびに (objective のスコープを抜けるたびに) データを GPUメモリ上から解放する』
という意味では上記サイト様とおんなじことをやっている気がするのですが。
こうしないと上手くいかないのは不思議ですよね・・・
 
お試しください!!