メモ帳

python, juliaで機械学習をやっていく

Metric Learning: L2 softmax Lossとsoftmax Lossをkerasで実装し、mnistを使って比較してみた

metric learningで有用なL2 Softmax Lossについて調べた。 mnist datasetをNeural Networkで特徴空間に写し、Siamese network (距離関数で手書き文字の類似度を判定させるモデル) を構築した。 L2 Softmax Lossを用いたNeural Networkと、Softmax Lossを用いたものの性能を比較した。なお、通常の分類タスクでよく用いられるSoftmax Lossもmetric learningで使われるようである。

metric learning

neural networkを用いて入力データを特徴空間に写し、その空間上の幾何的距離で入力データの類似度を表す手法です。 顔認証などで使われています。

L2 Softmax Loss

特徴

  • 実装が簡単。よく用いられるsoftmax lossを少し修正するだけでよい。特別なlossは必要ないため非常に手軽に試せる
  • 似ているもののcosine similarityは大きく、異なるものは小さくなるように学習する

    表式

    f:id:atelier-0213:20190824031244p:plain
    l2 softmax Loss
    最終層の出力にL2 normalization -> scale変換 -> softmax layerを施し、cross-entropyをloss関数とし、学習する。推論時はL2 normalization以下を省くことで入力を特徴空間に写せる。2つの入力を特徴空間に写し、cosine similarityを求める。ある閾値を超えた場合、2つの入力が同一であると判定する。

keras実装

L2 softmax Lossは以下のようなlayerを追加するだけです。 f:id:atelier-0213:20190824031505p:plain kerasにはnormalize layerもscale layerもありません。しかし、keras.regularizers.l2がL2 normalize layerとscale layerの役割を果たしてくれます。以下のように、keras.layers.Denseの引数activity_regularizerで層の出力に施すregularizerを指定できます。さらに、単位ベクトル化 -> softmax関数を加えればL2 softmax Lossの定義は終了です。

# ...
# 最終層までのmodel定義
# ...
model.add(Dense(128, activation='linear', 
                              activity_regularizer=regularizers.l2(alpha)))
model.add(Dense(num_classes, activation='softmax'))

activity_regularizer以外はよくあるクラス分類のコードと全く同じです。activity_regularizerを外せばsoftmax Lossとなります。 L2 softmax Lossの効果を見るためにsoftmax Lossと比較してみます。紛らわしいのでsoftmax Lossを使ったmodelをbase modelと書きます。

学習

まずはじめにmninstの分類タスクを学習させます。 精度は以下のようになりました。L2 softmax Lossは学習の収束の高速化も期待できるという話もあるみたいですが、今回はほとんどかわらなかったです。

# 10000 test samples
# (base model)
Test loss: 0.03231845000804315
Test accuracy: 0.9913

# (model with L2softmaxLoss)
Test loss: 0.09844313929080963
Test accuracy: 0.991

architecture

2つのinputを特徴空間に写して、cosine similarityを求めるモデルを構築します。

L2 softmax より上層のみ取得

L2 softmax部分を省きます。以下のようにfunctional APIで最終層より手前の層をoutputに指定したmodelを再定義すればよいです。ここで、層の出力に対するregularizerだけ直接無効化しています。

layer_name = base_model.layers[-2].name
base_model.layers[-2].activity_regularizer = None

x1_input = Input(shape=base_model.input.shape[1:].as_list())
x1 = Model(inputs=base_model.input, outputs=base_model.get_layer(layer_name).output)(x1_input)

cosine similarity

f:id:atelier-0213:20190824034804p:plain 定義より明らかに、各ベクトルをL2 normalizationして、内積をとればよいのでkeras.backendの関数を使えば、

from keras import backend as K

def cosine_distance(inputs):
    x1, x2 = inputs
    x1 = K.l2_normalize(x1, axis=-1)
    x2 = K.l2_normalize(x2, axis=-1)
    return K.sum(x1 * x2, axis=-1, keepdims=True)

この関数をkeras.layers.Lambdaで使用します。

全体(keras.summary())

これらから、kerasのmodel.summary()は以下のようになります。まさに、2つのinputを先程学習したモデルで特徴空間に写してcosine similarityで類似度を計算するモデルとなっています。

__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            (None, 28, 28, 1)    0                                            
__________________________________________________________________________________________________
input_2 (InputLayer)            (None, 28, 28, 1)    0                                            
__________________________________________________________________________________________________
model_1 (Model)                 (None, 128)          1198592     input_1[0][0]                    
__________________________________________________________________________________________________
model_2 (Model)                 (None, 128)          1198592     input_2[0][0]                    
__________________________________________________________________________________________________
lambda_1 (Lambda)               (None, 1)            0           model_1[1][0]                    
                                                                 model_2[1][0]                    
==================================================================================================
Total params: 1,198,592
Trainable params: 1,198,592
Non-trainable params: 0
__________________________________________________________________________________________________

類似度の精度比較

test accuracyがほぼ同じであったbase modelとmodel with L2softmaxLossはmetric learningとしての性能がどれだけ異なるのか調べたいと思います。つまり、どれだけ正確に類似度を求められるか確かめます。

test dataset

mninstのtest dataを使い、手書き文字のペアをつくり、同じクラスであれば1, 違う場合は0をラベルとしました。モデルがある閾値以上であれば1を推測し、低ければ0を推測したものとします。今回は簡単のためにインデックスが隣り合ったtest dataをペアにしています。なので、同じクラスのペアは非常に少ないです。(1000サンプル以下) このようにして作ったデータに対してprecisionとrecallを求めました。適切な閾値を決めるのは微妙な問題なので閾値を0.05 ~ 0.95まで0.05刻みで変化させprecision-recall曲線をプロットすることにします。

性能比較結果

L2 softmax Lossで学習したほうが(クラス分類の精度はほとんど同じにも関わらず)非常に高性能であることが分かります。 ほんの少しの変更でここまでmetric learningに適した学習をしてくれるのは驚きです。

f:id:atelier-0213:20190824040958p:plain
PR-curve

metric learningは自然言語処理やテーブルデータのレコメンドでもよく使われる広く応用されている手法なので使い所がたくさんあると思います。何かレコメンド系のモデルで試してみたいです。

コード

性能比較やモデルの定義などの全てのコードは以下。 github.com

参考:

L2-constrained Softmax Loss for Discriminative Face Verification