TensorFlowを使って顔認識器を作る

# はじめに
![s_スクリーンショット 2016-07-27 12.37.01.png](https://qiita-image-store.s3.amazonaws.com/0/77079/960963a7-9099-0e62-36b7-33736761ad8d.png)

こんにちは、[Hironsan](http://qiita.com/Hironsan)です。

顔認識は画像中に映った人を検知し、人物の識別を行う技術です。顔認識の用途としては、監視カメラのシステムに組み込んでセキュリティ向上に役立てたり、ロボットに組み込んで家族の顔を認識させたりすることがあげられます。

今回はTensorFlowを使って畳み込みニューラルネットワークを構築し、既存のデータセットを使って顔認識器を作ってみます。

# 対象読者

* 畳み込みニューラルネットワーク(CNN)を知っている
* TensorFlowでどう書くかはわからない

CNNの理論については以下を見ればわかると思います。

* [Convolutional Neural Networkとは何なのか](http://qiita.com/icoxfog417/items/5fd55fad152231d706c2)

# 準備
## TensorFlowのインストール
TensorFlowのインストールは公式サイトが丁寧に解説しているのでそちらを参照してください。

* [TensorFlowのインストール](https://www.tensorflow.org/versions/r0.9/get_started/os_setup.html)

## データのダウンロード
まずはデータセットを用意します。今回は以下の顔画像データセットを使います。

* [Olivetti Faces](http://www.cs.nyu.edu/~roweis/data.html)

このデータセットには40人分の画像が各10枚ずつ含まれています。各画像サイズは64x64でグレースケール画像です。
![スクリーンショット 2016-07-25 22.30.04.png](https://qiita-image-store.s3.amazonaws.com/0/77079/c381389a-e8c0-27ab-d94c-6d689831a0fd.png)


# 画像の読み込み
データセットを用意したら、画像を読み込みます。
[PyFaceRecognizer/example/input_data.py](https://github.com/Hironsan/PyFaceRecognizer/blob/master/example/input_data.py)

```py
import input_data
dataset = input_data.read_data_sets('data/olivettifaces.mat')
```
ここで、datasetは学習データ、検証データ、テストデータを含んでいます。また、読み込んだ段階で画像サイズを32x32に縮小しています。


<!--
# セッションの開始
Tensorflowでの計算は、高効率のC++バックエンドに依存しています。このバックエンドとの接続をセッションと呼びます。 TensorFlowプログラムの一般的な使用法は、まずグラフを作成し、セッションでそれを起動することです。
ここでは、コードを構造化する方法についてTensorFlowをより柔軟にする便利なInteractiveSessionクラスを使用します。これを使えば、計算グラフを実行する間に、グラフを構築する操作をはさむことができます。このことは、iPythonのようなインタラクティブな状況で作業する場合、特に便利です。InteractiveSessionを使用しない場合は、セッションを開始し、グラフを起動する前に、全体のグラフを構築する必要があります。

```py
import tensorflow as tf
sess = tf.InteractiveSession()
```

この辺りにplaceholderを書いておく。
-->

# 畳み込みニューラルネットワークの構築
畳み込みニューラルネットワーク(CNN)を用いて顔認識を行います。全体像としては以下のようになっています。

<img width="781" alt="スクリーンショット 2016-07-27 13.19.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/c5896d0e-b09d-b11f-e8f1-88801e478d5d.png">

各層のconv, pool, fcはそれぞれ畳み込み層、プーリング層、全結合層を表しています。関数欄のReLは正規化線形関数を表しています。パラメータを表にすると以下のようになります。

| 層種・名称 | パッチ  | ストライド  | 出力マップサイズ  |  関数  |
|---------|:------:|:-------:|:--------------:|:-----:|
| data    |    -   |    -    |  32 x 32 x 1   |   -   |
| conv1   |  5 x 5 |    1    |  32 x 32 x 32  |  ReL  |
| pool1   |  2 x 2 |    2    |  16 x 16 x 32  |   -   |
| conv2   |  5 x 5 |    1    |  16 x 16 x 64  |  ReL  |
| pool2   |  2 x 2 |    2    |   8 x  8 x 64  |   -   |
| fc3     |    -   |    -    |   1 x 1 x 1024 |  ReL  |
| fc4     |    -   |    -    |   1 x 1 x 40   |  softmax |

コードで書いてあげると以下のようになります。ほぼそのままですね。
[PyFaceRecognizer/example/run.py](https://github.com/Hironsan/PyFaceRecognizer/blob/master/example/run.py)

```py3
def inference(input_placeholder, keep_prob):
    W_conv1 = weight_variable([5, 5, 1, 32])  # 最初の２つはパッチサイズ。残りは入力チャネルと出力チャネルの数
    b_conv1 = bias_variable([32])

    x_image = tf.reshape(input_placeholder, [-1, 32, 32, 1])  # 第２、第３の次元は画像の幅と高さ、最後の次元はカラー・チャネルの数

    h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)  # たたみ込み
    h_pool1 = max_pool_2x2(h_conv1)  # maxプーリング

    W_conv2 = weight_variable([5, 5, 32, 64])
    b_conv2 = bias_variable([64])

    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
    h_pool2 = max_pool_2x2(h_conv2)

    W_fc1 = weight_variable([8 * 8 * 64, 1024])
    b_fc1 = bias_variable([1024])

    h_pool2_flat = tf.reshape(h_pool2, [-1, 8 * 8 * 64])
    h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

    h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

    W_fc2 = weight_variable([1024, 40])
    b_fc2 = bias_variable([40])

    y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)  # 出力そう

    return y_conv
```

<!--

この層に合わせるために、まずxを４次元テンソルに変形します。第２、第３の次元は画像の幅と高さに対応し、最後の次元はカラー・チャネルの数に対応します。

```py
x_image = tf.reshape(x, [-1, 32, 32, 1])
```
-->

## モデルの訓練と評価
inferenceにモデルのコードを書きました。次はモデルを訓練するためのコードを書いていきます。それがlossとtrainingです。lossではクロスエントロピーを計算し、trainingではAdamオプティマイザを使用してパラメータの更新を行っていきます。コードにすると以下の通りです。

```py3
def loss(output, supervisor_labels_placeholder):
    cross_entropy = tf.reduce_mean(-tf.reduce_sum(supervisor_labels_placeholder * tf.log(output), reduction_indices=[1]))
    return cross_entropy


def training(loss):
    train_step = tf.train.AdamOptimizer(1e-4).minimize(loss)
    return train_step
```

以上で定義した、inference, loss, trainingを用いて顔認識を行います。訓練プロセスの100反復ごとにログを出力します。テストするときはドロップアウトさせないように、keep_peobを1.0にしています。

```py
with tf.Session() as sess:
    output = inference(x, keep_prob)
    loss = loss(output, y_)
    training_op = training(loss)

    init = tf.initialize_all_variables()
    sess.run(init)

    for step in range(1000):
        batch = dataset.train.next_batch(40)
        sess.run(training_op, feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
        if step % 100 == 0:
            print(sess.run(loss, feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0}))

    correct_prediction = tf.equal(tf.argmax(output, 1), tf.argmax(y_, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    print('test accuracy %g' % accuracy.eval(feed_dict={x: dataset.test.images, y_: dataset.test.labels, keep_prob: 1.0}))
```

## 実行
実行結果は下のような感じです。クロスエントロピーが下がっている様子が確認できます。

```shell-session
9.8893
1.68918
0.602403
0.261183
0.0490791
0.0525591
0.0133087
0.0121071
0.00673524
0.00580989
```

# ソースコード
以下のリポジトリからソースコードをダウンロードして動かすことができます。

* [PyFaceRecognizer](https://github.com/Hironsan/PyFaceRecognizer)

# おわりに
畳み込みニューラルネットワークを用いて既存の顔データセットに対して顔認識を行ってみました。
次はカメラからリアルタイムに取得した画像を使って、顔検知・顔認識をやってみたいと思います。

# 参考資料

* [TensorFlowのチュートリアル](https://www.tensorflow.org/)
* [深層学習 (機械学習プロフェッショナルシリーズ)](https://www.amazon.co.jp/%E6%B7%B1%E5%B1%A4%E5%AD%A6%E7%BF%92-%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%83%97%E3%83%AD%E3%83%95%E3%82%A7%E3%83%83%E3%82%B7%E3%83%A7%E3%83%8A%E3%83%AB%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E5%B2%A1%E8%B0%B7-%E8%B2%B4%E4%B9%8B/dp/4061529021/ref=sr_1_1?ie=UTF8&qid=1469600155&sr=8-1&keywords=%E6%B7%B1%E5%B1%A4%E5%AD%A6%E7%BF%92)