@Ryu_long

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

kerasで自作の損失関数を作ったがLossがnanになる

解決したいこと

LSTMを使って入力データから出力を3つに分類するモデルを作っています。モデルの損失関数にClass-Balanced Lossという不均衡データに対応するための計算処理を導入するため、自分で損失関数をカスタムしたいです。正解ラベルはOne-hotエンコーディング化されて[0 0 1]のような値になっており、予測された値も[ 1.5790243, 0.26411822, 0.8272542]のような値になっています。
手動で計算したときはreturnで返ってくるLossの値が存在しているのに対し、実際にモデルを実装して訓練してみるとLossがnanとなってしまい上手く学習できません。計算式が間違っているとも思いにくいのですが、色々試しても解決できませんでした。どなたか力をお貸しください。

発生している問題・エラー

46/46 [==============================] - 6s 56ms/step - loss: nan - accuracy: 0.1196 - val_loss: nan - val_accuracy: 0.0909
Epoch 2/10
46/46 [==============================] - 1s 26ms/step - loss: nan - accuracy: 0.1087 - val_loss: nan - val_accuracy: 0.0909
Epoch 3/10
46/46 [==============================] - 1s 27ms/step - loss: nan - accuracy: 0.1087 - val_loss: nan - val_accuracy: 0.0909
Epoch 4/10
46/46 [==============================] - 1s 26ms/step - loss: nan - accuracy: 0.1087 - val_loss: nan - val_accuracy: 0.0909

該当するソースコード

def class_balanced_cross_entropy(y_val, y_pred):
    beta=0.999
    y_val = tf.cast(y_val, tf.float32)
    y_pred = tf.cast(y_pred, tf.float32)
    y_pred = tf.clip_by_value(y_pred, 1e-6, y_pred) 

    # クラスごとのサンプル数を計算
    class_counts = tf.reduce_sum(y_val, axis=0)

    # クラスごとの重みを計算
    class_weights = 1.0 -beta / (1.0 - tf.math.pow(beta, class_counts))
    weights = class_weights /  tf.reduce_sum(class_weights) * len(class_counts)

    # 交差エントロピー
    per_class_loss = -y_val * tf.math.log(y_pred)

    # クラスごとの損失に重みをかけて合計(Class-Balenced Loss)
    class_balanced_loss = tf.reduce_sum(per_class_loss * weights,axis=1)

    # 損失の平均を計算
    mean_class_balanced_loss = tf.reduce_mean(class_balanced_loss)

    return mean_class_balanced_loss

def build_model():
  model = tf.keras.Sequential([
      layers.Input(shape=(50,95)),
      layers.Masking(mask_value=0),
      layers.LSTM(units=95,return_sequences=False),
      layers.Dense(3, activation='softmax')
  ])

  optimizer = Adam(learning_rate=0.001)

  model.compile(loss=class_balanced_cross_entropy,
                optimizer=optimizer,
                metrics=["accuracy"])
  return model

model = build_model()
history = model.fit(X_train, y_train_onehot, epochs=10, batch_size=2, validation_data=(X_test, y_test_onehot))

自分で試したこと

Class-Balanced Lossを実装している記事をQiitaで見つけましたが、pytorchで実装しており自分なりにtensorflowで書き換えましたが上手くいきません。https://qiita.com/myellow/items/75cd786a051d097efa81

Class-Balanced Lossを説明している論文です。この論文を元に導入しています。
https://openaccess.thecvf.com/content_CVPR_2019/papers/Cui_Class-Balanced_Loss_Based_on_Effective_Number_of_Samples_CVPR_2019_paper.pdf

1 likes

1Answer

回答というより感想レベルになってしまって申し訳ありませんが、
私も遭遇したことがあるのでコメントさせて頂きます。

  1. 入力の絶対値が大きい
    入力層だけではなく中間層も含めて入力の値が大きくなってしまった時に発生しました。
    入力層の場合は適当にデータを正規化(平均値に合わせて1.0~0.0レンジにおさまるようにするなど例(x - x.mean()) / x.std())が有効かと思います。
    また、tf.clip_by_value(y_pred, 1e-6, y_pred)も第三引数y_predなので上限clippingをしていない状態となります。意図的な処理でなければ1.0等が良いと思います。
    あわせて以下もlen(class_counts)を外して、weightsの合計が1になるように正規化した方がよいでしょうか。
    weights = class_weights / tf.reduce_sum(class_weights)

  2. 0除算等になりうる処理
    class_weights = 1.0 -beta / (1.0 - tf.math.pow(beta, class_counts))
    tf.math.pow(beta, class_counts)が1になると0除算になる可能性があります。
    0になったら適当に小さい値にclippingなどでしょうか。
    class_weights = 1.0 - beta / (tf.clip_by_value(1.0 - tf.math.pow(beta, class_counts), 1e-9, 1.0))

  3. 学習率が高すぎる
    いわゆる発散が発生する可能性があります。
    ただ現在の設定値は高いようには見えませんし、NANではなくinfになったかもしれません(未検証失礼します)。

2Like

Comments

  1. @Ryu_long

    Questioner

    回答していただきありがとうございます!
    1.における「weightsの合計が1になるように正規化」と2.を実践したところ上手く動きました!
    本当にありがとうございました。数日間詰まっていたので一気に道が開けました。深く感謝いたします。

  2. 私の回答では(x - x.mean()) / x.std())と自前で計算を行ってしまっていましたが
    sklearnで同様の作用があるStandardScalerという関数が用意されているようなので、そちらの方がスマートかもしれません。
    また、この対処は外れ値がある場合に、正常なデータが0や1に近い値ばかりになってしまう可能性があるという注意点があるようです。式を見ると確かにそうですね。

    私もこれ系でハマることが多いので、備忘録として以下の記事に書いておいたのでご参照ください。
    https://qiita.com/hokutoh/items/5394c22f67d4e46cdf94

Your answer might help someone💌