多クラス分類の出力をマスクする

2016-10-06

Tensorflowで多クラス分類で出力するクラスを制限したい場合にどうするか。

ニューラルネットワークで多クラス分類する場合、出力層の活性関数にソフトマックスを使用することが多いと思うが、条件によって有効なクラスを制限したい、といった場合にどうするか。

ソフトマックスを計算した後に無効なクラスに0を掛けてマスクするのは、全出力の合計が1ではなくなり、出力がもはや確率として解釈できなくなってしまうのでよくない。

そこでソフトマックスの計算の途中、各入力の指数を取った後に無効なクラスを0にしてその後合計値で割るという具合で、じまえで計算するとうまく無効化できる。

マスクなしでビルトインのsoftmax関数を使う

じまえでソフトマックスの計算をする前に、ビルトインのtf.nn.softmaxの計算を出力して、じまえの計算がビルトインの計算と同じかどうか確認できるようにしてみる。

仮に分類したいクラスの数を3として:

import tensorflow as tf

NCLASS = 3
x = tf.placeholder(tf.float32, [None, NCLASS])

適当に食わせてみる:

def builtin_softmax(x, input):
output = tf.nn.softmax(x)
with tf.Session() as sess:
print sess.run(output, feed_dict={x: input})

# 適当な入力
input = [[1, 3, 2],
[1, 0, -1]]
builtin_softmax(x, input)
# [[ 0.09003057 0.66524088 0.24472845]
# [ 0.66524094 0.24472848 0.09003057]]

じまえでソフトマックスを計算

expreduce_sumdivを使ってじまえでソフトマックスを計算する:

def manual_softmax(x, input):
max = tf.reduce_max(x)
exps = tf.exp(x - max)
output = tf.div(exps, tf.reduce_sum(exps, 1, keep_dims=True))
with tf.Session() as sess:
print sess.run(output, feed_dict={x: input})

manual_softmax(x, input)
# [[ 0.09003057 0.66524088 0.24472846]
# [ 0.66524094 0.24472848 0.09003058]]
  • 出力微妙に異なる、でもほぼ同じ
  • ミニバッチで計算する場合のために、xの各行ごとに合計値を計算するようreduce_sumの2番目の引数reduction_indices1として、さらにkeep_dims=Trueを指定する
  • /演算子も使えるけど、名前をつけたいといった場合にdivが使える
  • 要素内の最大値で引くことによって、数値の発散を抑える

マスクする

上記のじまえソフトマックス計算を元にして、途中で値を0にマスクして特定のクラスが選ばれないようにする:

def masked_softmax(x, mask, input, mask_values):
exps = tf.exp(x)
masked = exps * mask
output = tf.div(masked, tf.reduce_sum(masked, 1, keep_dims=True))
with tf.Session() as sess:
print sess.run(output, feed_dict={x: input, mask: mask_values})

mask = tf.placeholder(tf.float32, shape=[x.get_shape()[-1]])
mask_values = [1, 0, 1]
masked_softmax(x, mask, input, mask_values)
# [[ 0.2689414 0. 0.7310586 ]
# [ 0.88079709 0. 0.11920293]]
  • mask_valuesに、有効なクラスは1、無効なクラスは0を与えてもらうようにして、reduce_sum前に乗算して最終的な結果が必ず0になるようにする
  • 各行の2列目の出力値が0となっていてマスクされていることがわかる

番外編:マイナス無限大を使う

ビルトインのsoftmaxに渡す値をマイナス無限大にしてやることで無効にすることもできる:

def masked_softmax_infinity(x, mask, input, mask_values):
masked = x - 1 / mask
output = tf.nn.softmax(masked)
with tf.Session() as sess:
print sess.run(output, feed_dict={x: input, mask: mask_values})

masked_softmax_infinity(x, mask, input, mask_values)
# [[ 0.26894143 0. 0.7310586 ]
# [ 0.88079703 0. 0.11920291]]
  • マイナス無限大の指数が0になるので
  • 0除算、マイナス無限大が気持ち悪くなければこれでも良さげ
  • 上の場合と微妙に値が違う(1.0e-8)、しかし2列目の値については==0が成り立っていた
  • GPUで試してない