kerasの自作損失関数に定義域を設定したい
前書き
異常検知などは,検出したい異常データはそうでないデータに比べて訓練データに含まれる割合は少ない。これを解消するために方法として,できるだけ異常データを集めた後おなじ割合になるように正常なデータを加えて訓練データにするといった方法がとられたりする。
一方で,「この値以上のものは異常」のように値として閾値がある場合には,損失関数で誤差を計算する場合に重みをつければよいのではと考えた。すなわち,ある閾値以下の場合は通常の二乗誤差を利用し,閾値を越えたら二乗誤差に何らかの重みをかければよい。
kerasで機械学習を行う場合は自作の損失関数を利用することができるが,バックエンドがtensorflowの場合にはテンソル演算として計算しなければならない。上記のような損失関数を定義しようとして詰まったので備忘録として書いておく。
自作損失関数の概要
今回は,データの範囲が[0:1]であるとする。このとき,下の図のような重み関数を損失関数に作用させたい。
- 予測すべき値が0.4未満:通常の二乗誤差
- 予測すべき値が0.6以上:通常の二乗誤差を3倍する
- 間の範囲:$\sin^2x$により滑らかに二つの関数をつなぐ
$g(x)$の定義域が$a\leq x\leq b$の場合は,$g(x)=(\alpha - 1) \sin^2(\frac{\pi}{2}\cdot\frac{x-a}{b-a})+1$のような関数を使えばよい。
ここで$\alpha$は最終的な重みを決定するパラメータである。
この関数をpython (keras)で書くと以下のようになる。
from tensorflow.keras import backend as K
import numpy as np
def weight_function(y_true, min_threshold, max_threshold, alpha):
return (alpha - 1) * K.sin(np.pi / (2 * (max_threshold - min_threshold)) * (y_true - min_threshold))
* K.sin(np.pi / (2 * (max_threshold - min_threshold)) * (y_true - min_threshold)) + 1
この関数を使って自作の損失関数を作成する。
import tensorflow as tf
def custom_loss_function(y_true, y_pred): # 損失関数はこの二つのテンソルを入力に持つ
min_threshold = 0.4 # a
max_threshold = 0.6 # b
alpha = 2
# 平方誤差を計算
squared_errors = K.square(y_true, y_pred)
# 0 <= x < aの場合
threshold_errors_1 = K.cast(tf.logical_and(0<=y_true, y_true<min_threshold), dtype=tf.float32) * squared_errors
# a < x < bの場合
threshold_errors_2 = K.cast(tf.logical_and(min_threshold<=y_true, y_true<max_threshold), dtype=tf.float32) * squared_errors
* weight_function(y_true, min_threshold, max_threshold, alpha)
# 0 <= x < aの場合
threshold_errors_3 = K.cast(tf.logical_and(max_threshold<=y_true y_true<=1.0), dtype=tf.float32) * squared_erros * alpha
# それぞれの損失を結合
combined_errors = threshold_errors_1 + threshold_errors_2 + threshold_errors_3
# 最終的な損失値を計算
loss = K.mean(combined_errors)
return loss
ここで,K.castによって第一引数の条件に当てはまる要素を”抽出”しているが,この第一引数の指定で詰まってしまった。
pythonでは$a\lt x \leq b$のように条件を指定できるが,tensorflowではサポートされていない。
また,K.castの第一引数はテンソルを要求するため,上記の理由からpythonのbool値が渡されることになりエラーが生じていた。
したがってこのような条件の場合はtf.logical_andを用いて,かつ分けて条件を記述する必要がある。
後書き
単純に$a\lt x$のような場合は何も気にせず指定できたので,原因を特定するのに時間がかかってしまった。
また,この解決策を見つけるのにお力をいただいたChatGPT君にも感謝を申し上げたい。