One Shot Learning : KerasでSiamise Networkを作りOminiglotデータセットを使って動かしてみる( on collaboratory)

# はじめに
人間は**一度**学習すると、これに基づいて予測できます。
一方で、機械学習やディープラーニングは教師データが少ししか手に入らない場合は、なかなかうまく予測できません。
人間の様に、１つ、あるいは数個の例を見て、予測することができるモデルが作れないか、というのは古くは1980年代頃から研究されています。この分野はFew shot learning や One shot learning と呼ばれています。

ディープラーニングによるAIブームが起こる以前にも、生成モデルを用いた研究（[One-Shot Learning of Object Categories](http://vision.stanford.edu/documents/Fei-FeiFergusPerona2006.pdf)）などがありました。
今日の機械学習の文脈では、[Human-level concept learning through probabilistic program induction](https://www.cs.cmu.edu/~rsalakhu/papers/LakeEtAl2015Science.pdf)があります。
上記2つの論文はどちらも引用数が1000を超えており、重要な研究となっています。



## この記事で扱う内容
この記事では、**Siamise Network**を用いた One shot learning にテーマを絞ります。そして、Kerasを使って動かしてみることを目的とします。

オリジナルの論文はこちらです。
[Siamese Neural Networks for One-shot Image Recognition](https://www.cs.cmu.edu/~rsalakhu/papers/oneshot1.pdf)

オリジナルの論文以外に、以下の英語記事を参考にしています。
[One Shot Learning with Siamese Networks using Keras](https://towardsdatascience.com/one-shot-learning-with-siamese-networks-using-keras-17f34e75bb3d)

使うデータセットはomniglotです。
以下のGitHubで手に入れることができます。
[Omniglotデータセット](https://github.com/brendenlake/omniglot)
pythonフォルダにある「images_background」と「images_evaluation」を使います。それぞれ訓練データとテストデータとなります。

## 環境
私のPCのCPUを使っても計算に時間がかかるので、無料で使えるGoogle Collaboratoryを使って実践します。
上記のデータセットをcollaboratoryで使うため、まずGoogle Driveにアップロードします。

GoogleDriveに入れたファイルをcollaboratoryで使えるようにするため、GoogleDriveをマウントします。マウントするには下図の赤線で囲ったファイルアイコンをクリックして、GoogleDriveを選択します。（以前はAPIを使ってコマンド実行していましたが、簡単になってる！）
<img width="488" alt="001_GoogleDrive_mount.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/8e8e6f1b-e593-5272-1663-864f3c761e5c.png">

<img width="190" alt="002_GoogleDrive_mount_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/8880c2e9-84dc-40d4-433c-2eceb61d3196.png">

# Siamese Networkについて
この論文では文字認識に焦点を絞っています。
そして、large siamese convolutional neural networkを採用しています。これの特徴として、以下が挙げられます。
a)少数のサンプルしかない場合でも、未知のクラスの分布を予測するのに有用な画像の特徴を学習することができる
b)サンプルされたペアについて通常の最適化テクニックを使って学習できる
c)ドメイン固有の知識ではなくディープラーニングのテクニックを探索することで他に引けをとらないモデルとなる

具体的には、シャム（双生児）のようなネットワークを作り、２つの画像をインプットとします。そして、ペアの画像が同じクラスかを識別するモデルを作るという戦略をとります。２つの画像の類似度から、それまで訓練データになかった対象がテストデータに入ってきたことを検知するという、面白い考え方です。
![010_siamese_network.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/0c074aea-380a-922f-f6ab-9283befe77cf.png)



#Siamese Networkの実装

以下の様にKerasで実装しました。

```python
import tensorflow as tf

from keras.models import Sequential
from keras.optimizers import Adam
from keras.layers import Conv2D, ZeroPadding2D, Activation, Input, concatenate
from keras.models import Model

from keras.layers.normalization import BatchNormalization
from keras.layers.pooling import MaxPooling2D
from keras.layers.merge import Concatenate
from keras.layers.core import Lambda, Flatten, Dense
from keras.initializers import glorot_uniform, RandomNormal

from keras.engine.topology import Layer
from keras.regularizers import l2
from keras import backend as K

from sklearn.utils import shuffle

def get_siamese_model(input_shape):
    """
        Model architecture
    """
    
    # Define the tensors for the two input images
    left_input = Input(input_shape)
    right_input = Input(input_shape)
    
    initialize_weights = RandomNormal(mean=0.0, stddev=0.01, seed=None)
    initialize_bias = RandomNormal(mean=0.5, stddev=0.01, seed=None)
    
    # Convolutional Neural Network
    model = Sequential()
    model.add(Conv2D(64, (10,10), activation='relu', input_shape=input_shape,
                   kernel_initializer=initialize_weights, kernel_regularizer=l2(2e-4)))
    model.add(MaxPooling2D())
    model.add(Conv2D(128, (7,7), activation='relu',
                     kernel_initializer=initialize_weights,
                     bias_initializer=initialize_bias, kernel_regularizer=l2(2e-4)))
    model.add(MaxPooling2D())
    model.add(Conv2D(128, (4,4), activation='relu', kernel_initializer=initialize_weights,
                     bias_initializer=initialize_bias, kernel_regularizer=l2(2e-4)))
    model.add(MaxPooling2D())
    model.add(Conv2D(256, (4,4), activation='relu', kernel_initializer=initialize_weights,
                     bias_initializer=initialize_bias, kernel_regularizer=l2(2e-4)))
    model.add(Flatten())
    model.add(Dense(4096, activation='sigmoid',
                   kernel_regularizer=l2(1e-3),
                   kernel_initializer=initialize_weights,bias_initializer=initialize_bias))
    
    # Generate the encodings (feature vectors) for the two images
    encoded_l = model(left_input)
    encoded_r = model(right_input)
    
    # Add a customized layer to compute the absolute difference between the encodings
    L1_layer = Lambda(lambda tensors:K.abs(tensors[0] - tensors[1]))
    L1_distance = L1_layer([encoded_l, encoded_r])
    
    # Add a dense layer with a sigmoid unit to generate the similarity score
    prediction = Dense(1,activation='sigmoid',bias_initializer=initialize_bias)(L1_distance)
    
    # Connect the inputs with the outputs
    siamese_net = Model(inputs=[left_input,right_input],outputs=prediction)
    
    # return the model
    return siamese_net
```

アーキテクチャが定義できたので、このモデルのサマリを出力します。

```python

model = get_siamese_model((105, 105, 1))
model.summary()
```

![100_model_summary.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/e2cdb433-6eed-3100-5e15-97f21067e71c.png)


One shot learningをして精度を検証した結果です。

![101_result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/e729e4d6-932f-0456-13ff-e58ff35ea048.png)

精度は80%くらいでした。論文では92%の精度をだしているので、ちょっと再現が悪いですね。原因はさぐっていますが、まだわかっていません。

#さいごに
One shot learingのひとつであるSiamese Networkについて簡単に記述してみました。
Google collaboratory上で動かしたjupyter notebookは以下のGitHubにあげています。
https://github.com/ShinCom1974/OneShotLearning
精度は論文の値に及んでいないので、データの使い方などで違うところがあると思います。もう一度論文を読んで確認してみます。