質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.58%

keras 自作 損失関数 正解ラベル 予測ラベル 異なる形

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,796

sakuramochi

score 13

損失関数を自作したい

初めての投稿になります.慣れておらず,間違っている部分がありましたら,すみません.
損失関数を自作したいですが,プログラムがうまく動かず,困っています.症状としては,実行した際にすごく時間がかかり止まってしまいました.
loss関数を作成するのは初めてで,調べながら取り組んだものの,コードは明らかに間違っていると思いますが,自分の作りたいloss関数をどう実装すればいいのか分かりません.

CNNモデルは16×16の画像を入力とし,50次元の1次元ベクトルを出力としています.

作りたいloss関数は正解ラベル(教師ベクトル)であるy_true75次元((x,y,c)×25),予測ラベルであるy_pred50次元((x,y)×50)において,xとyのmseに正解ラベルのcをかけたものをlossとしたいです.

(y_true[0]-y_pred[0])^2 * + (y_true[1]-y_pred[1])^2 *y_true[2] がひとまとまりで、(y_true[3]-y_pred[3])^2 * + (y_true[4]-y_pred[4])^2 *y_true[5] 、、、
と順番に足していくイメージです。

(y_trueはx,y,c,x,y,c...y_predはx,y,x,y,...という順番で入っています.)
loss関数には引数として(y_true,y_predict)を用い,それぞれの形はy_true[batchsize][row][col][ch]となっていると思います.
batchsizeは30に指定したので今回はy_true[30][75]のような形になっていると思います.それを踏まえた上でコードを作成しましたが,そもそも損失関数というのはbatchsizeごとに出しているのかも曖昧です.
各,xy要素ごとでしたら,mseで実装できますが,入力のみそこにcが入り込み,入力と出力の形が異なるためにどのようにコードを書けばいいかが全く分かりません.

初心者すぎる質問で申し訳ありません.

 発生している問題・エラーメッセージ

実行できない
出たエラーを消してしまい,今実行できる状況になく,貼れません.

 該当のソースコード

def originalloss(y_true,y_pred):
   loss = 0
   for i in range(30):
      gap = 0
      for j in range(25):
         xgap = 0
         ygap = 0
         xgap = ( y_true[i][3*j]-y_pred[i][2*j] ) * ( y_true[i][3*j]-y_pred[i][2*j] )
         ygap = ( y_true[i][3*j+1]-y_pred[i][2*j]+1 ) * ( y_true[i][3*j+1]-y_pred[i][2*j+1] )
         gap = gap + (xgap + ygap) * y_true[i][3*j+2]
       gap = gap / 50 #平均
     loss = loss + gap

   loss = loss/30 #平均
   return loss

    return loss

 試したこと

上記コードの実行.

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • sakuramochi

    2018/11/10 12:55 編集

    入力は(30,75,1,1)で出力が(30,50,1,1)になると思いますので、バッチサイズは30で一定です。
    model.summaryを今送ることができないので、でき次第送ります!
    ありがとうございます。

    キャンセル

  • tiitoi

    2018/11/10 15:55 編集

    for 文を使うと非常に遅くなるので、こういう計算はforを書かないでできるように工夫しましょう。ちなみになぜ (batchsize, input_dim, 1, 1) なのでしょうか?入力が画像でなく、(batchsize, input_dim, channels) の場合は Conv2D ではなく、Conv1D を使うべきだと思いますが。

    キャンセル

  • sakuramochi

    2018/11/10 19:39

    すみません、入力は[batchsize][16][16]の画像データです。y_trueが[batchsize][75]でy_predが[batchsize][50]になっています。入力とy_trueを勘違いしていました。ごめんなさい。

    キャンセル

回答 1

checkベストアンサー

0

Python では数値計算を行うための for 文は基本的に遅くなるため NG です。
for 文を使わないで計算できるようにしましょう。

 問題設定

BatchSize=30, LabelDim=75, OutputDim=50

モデルの入力 data: (BatchSize, 16, 16, 1) の配列
モデルの出力 Y_pred: (BatchSize, OutputDim) の配列
ラベル labels: (BatchSize, LabelDim) の配列

 サンプルコード

 テスト用に入力及びラベルを作成する。

import numpy as np

batch_size = 30  # バッチサイズ
input_shape = (16, 16, 1)  # モデルの入力サイズ
label_dim = 75  # ラベルの次元数
output_dim = 50  # 出力の次元数

# ダミーの入力データ及びラベルを作成する。
data = np.random.randn(batch_size, *input_shape)  # データ
labels = np.random.randn(batch_size, label_dim)  # ラベル
print('data.shape', data.shape)  # data.shape (30, 16, 16, 1)
print('labels.shape', labels.shape)  # labels.shape (30, 75)

 テスト用に簡単なモデルを作成する。

import tensorflow as tf
from keras.models import Sequential
from keras.layers import Conv2D, GlobalMaxPooling2D

# 入力が  (BatchSize, 16, 16, 1) で出力が (BatchSize, 50) のダミーのモデルを作成する。
model = Sequential()
model.add(Conv2D(output_dim, kernel_size=(3, 3), input_shape=input_shape))
model.add(GlobalMaxPooling2D())
model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 14, 14, 50)        500       
_________________________________________________________________
global_max_pooling2d_1 (Glob (None, 50)                0         
=================================================================
Total params: 500
Trainable params: 500
Non-trainable params: 0
_________________________________________________________________

 損失関数の定義

def custom_loss(labels, Y_pred):
    # ラベルから Y_true を抽出する。
    mask = tf.tile([True, True, False], (label_dim // 3,))
    Y_true = tf.boolean_mask(labels, mask, axis=1)

    # ラベルから重み係数を抽出する。
    mask = tf.math.logical_not(mask)
    coeff = tf.boolean_mask(labels, mask, axis=1)

    # 重み係数を作成する。
    ones = tf.ones_like(coeff)
    W = tf.reshape(
        tf.concat([ones[..., tf.newaxis], coeff[..., tf.newaxis]], axis=-1), 
        [tf.shape(ones)[0], -1])

    return tf.reduce_mean((Y_true - Y_pred) ** 2 * W)

 損失関数の値計算

model.compile(loss=custom_loss, optimizer='adam')
model.fit(data, labels, epochs=3)
loss1 = model.evaluate(data, labels)
print(loss1)  # 0.6780742406845093

 numpy での計算

# モデルの出力
Y_pred = model.predict(data)

# ラベルから Y_true を抽出する。
mask = np.ones(labels.shape[1], dtype=bool)
mask[2::3] = False
Y_true = labels[:, mask]

# ラベルから重み係数を抽出する。
mask = np.logical_not(mask)
coeff = labels[:, mask]

# 重み係数を作成する。
W = np.ones_like(Y_true)
W[:, 1::2] = coeff

loss2 = np.mean((Y_true - Y_pred) ** 2 * W)

# 計算した値が numpy と TensorFlow で一致するか
print(np.allclose(loss1, loss2))

 追記

各処理がなにをやっているのか簡単な例を追加しました。
TensorFlow のテンソルに対して、numpy の indexing のような操作ができないようなので、少々わかりづらいやり方を取らざる得なくなっています。

    import tensorflow as tf

    with tf.Session() as sess:
        label_dim = 6
        labels = tf.constant([[1, 2, 3, 4, 5, 6],
                              [7, 8, 9, 10, 11, 12],
                              [13, 14, 15, 16, 17, 18]])

        # ラベルから Y_true を抽出する。
        mask = tf.tile([True, True, False], (label_dim // 3,))
        print(mask.eval())  # [ True  True False  True  True False]

        # 各サンプルの 1, 2, 4, 5 列目を抜き出している。
        Y_true = tf.boolean_mask(labels, mask, axis=1)
        print(Y_true.eval())
        # [[ 1  2  4  5]
        #  [ 7  8 10 11]
        #  [13 14 16 17]]

        # ラベルから重み係数を抽出する。
        mask = tf.math.logical_not(mask)
        print(mask.eval())  # [False False  True False False  True]

        # 各サンプルの 3, 6 列目を抜き出している。
        coeff = tf.boolean_mask(labels, mask, axis=1)
        print(coeff.eval())
        # [[ 3  6]
        #  [ 9 12]
        #  [15 18]]

        # 末尾に次元を1つ追加している。ones のほうも同様
        print(tf.shape(coeff).eval())  # [3 2]
        print(tf.shape(coeff[..., tf.newaxis]).eval())  # [3 2 1]

        # 次元を1つ増やし、axis=-1 で結合している。
        ones = tf.ones_like(coeff)
        concat = tf.concat([ones[..., tf.newaxis], coeff[..., tf.newaxis]], axis=-1)
        print(concat.eval())
        # [[[ 1  3]
        #   [ 1  6]]
        #  [[ 1  9]
        #   [ 1 12]]
        #  [[ 1 15]
        #   [ 1 18]]]

        # [3 2 2] を [3, 4] に戻している。
        W = tf.reshape(concat, [tf.shape(ones)[0], -1])
        print(tf.shape(concat).eval())  # [3 2 2]
        print(tf.shape(W).eval())  # [3 4]

        print(W.eval())
        # [[ 1  3  1  6]
        #  [ 1  9  1 12]
        #  [ 1 15  1 18]]

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/11 00:42

    またloss関数はbatchsizeに関わらず1つずつ行っているという認識で正しいでしょうか.

    キャンセル

  • 2018/11/11 01:39 編集

    > どのようになっているのか詳しく教えていただけたら幸いです

    簡単な例で各処理がどうなっているのか示すサンプルを追加しました。
    テンソルに対して numpy のような indexing 操作ができない制約上、処理がわかりづらくなっています。
    newaxis は numpy 同様、次元1を追加する処理になります。

    a = np.ones((3, 4))
    # a は (3, 4) の配列
    a = a[..., np.newaxis]
    # a は (3, 4, 1) の配列

    > loss関数はbatchsizeに関わらず1つずつ行っているという認識で正しいでしょうか.

    バッチサイズ分のサンプルに対して一度に計算しています。

    キャンセル

  • 2018/11/12 16:31

    丁寧に解説してくださり、本当にありがとうございました。色々実行してみることでtfを用いた方法の理解を深めることができました。
    また機会がありましたらよろしくお願い致します。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.58%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る