ライブラリを使ってディープラーニングを構築する　～TensorFlow編

#**■0.はじめに**
[ディープラーニングを基本から学ぶ](https://qiita.com/nvtomo1029/items/551a28f40d092265047c)

こちらで、ゼロからディープラーニングを実装することを学びました。
実務的にはライブラリを使ってとなるのが現実的かと思い、ライブラリを使ったディープラーニングを構築してみようと考えました。
[scikit-learn編](https://qiita.com/nvtomo1029/items/200dbfe5928c4a0cd625)、[chainer編](https://qiita.com/nvtomo1029/items/18c3901052b2ee3dd355)に続き、第三弾はTensorFlowです。

#**■1.TensorFlow**
>TensorFlow（テンソルフロー）とは、Googleが開発しオープンソースで公開している、機械学習に用いるためのソフトウェアライブラリである。

>ディープラーニングに対応しており、Googleの各種サービスなどでも広く活用されている。 2017年2月15日に TensorFlow 1.0 がリリースされた。

>対応プログラミング言語はC言語、C++、Python、Java、Go。 対応OSは64ビットのLinux（ただしバイナリ配布はUbuntu用）、macOS、Windows。ハードウェアは CPU と NVIDIA GPU に対応していて、加えて Google TPU や Snapdragon Hexagon DSP などにも対応している。
>[wikipedia](https://ja.wikipedia.org/wiki/TensorFlow)

#**■2.環境**

**・Windows 10**
**・Anaconda 4.4.1**
**・Python 3.6.1**
**・TensorFlow 1.4.0**

#**■3.利用データ**

**MNIST**
・28x28ピクセルからなる手書き数字(0～9)のデータセット
・機械学習のサンプルデータとしてよく使われる
・データ数は70,000枚

#**■4.実装**

##**▼4.1.データ準備**
これまで同様、MNISTのデータを取得します。
TensorFlowでもMNISTを取得するI/Fが用意されてました。

```py3
# MNISTデータのダウンロード
# one_hot=True → [0, 0, 1, 0, …, 0]
# one_hot=False → 2
mnist = input_data.read_data_sets("MNIST_data/", one_hot=False)
```

[tensorflow/input_data.py at v0.6.0 · tensorflow/tensorflow · GitHub](https://github.com/tensorflow/tensorflow/blob/v0.6.0/tensorflow/examples/tutorials/mnist/input_data.py)

戻り値の型は、
class 'tensorflow.contrib.learn.python.learn.datasets.base.Datasets'
となっており、これはTensorFlow独自のクラス、MNISTに特化した型のようです。

```py3
batch_size = 100
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
```

MNISTのデータは、上記のように入力データと正解ラベルの組み合わせを、指定したバッチサイズで取得することができます。

##**▼4.2.ニューラルネットワークの定義**
続いて、学習・予測に使用するニューラルネットワークを定義。
3層構造で、活性化関数にSigmoidを用いています。

```py3
# ニューラルネットワークの構築
def create_model(input_placeholder, input_size, hidden_size, output_size):
    # 重み、バイアスの初期化
    params = {}
    params['W1'] = tf.Variable(tf.random_normal([input_size, hidden_size], mean=0.0, stddev=0.1))
    params['b1'] = tf.Variable(tf.zeros([hidden_size]))
    params['W2'] = tf.Variable(tf.random_normal([hidden_size, hidden_size], mean=0.0, stddev=0.1))
    params['b2'] = tf.Variable(tf.zeros([hidden_size]))
    params['W3'] = tf.Variable(tf.random_normal([hidden_size, output_size], mean=0.0, stddev=0.1))
    params['b3'] = tf.Variable(tf.zeros([output_size]))
    
    a1 = tf.sigmoid(tf.matmul(input_placeholder, params['W1']) + params['b1'])
    a2 = tf.sigmoid(tf.matmul(a1, params['W2']) + params['b2'])
    # 損失関数での計算時にsoftmaxの計算も行うのでそのまま出力する
    model = tf.matmul(a2, params['W3']) + params['b3']
    
    return model
```

chainerとは違って、重み、バイアスも自ら定義していく必要があります。
matmulは、行列同士の乗算を行います。
最後の層の出力はそのまま出力します。
分類問題の場合はsoftmax関数を充てるのが通常ですが、後で出てくる損失関数でsoftmaxの計算も行うためです。

```py3
# 各パラメータの定義
input_size = 784 # 入力サイズ
hidden_size = 300 # 隠れ層のサイズ
output_size = 10 # 出力層のサイズ

# 入力データを入れる箱
x = tf.placeholder(tf.float32, [None, input_size])
# 正解ラベルを入れる箱
y_ = tf.placeholder(tf.int64, [None])
# モデルの構築
y = create_model(x, input_size, hidden_size, output_size)

# 損失関数
cross_entropy = tf.losses.sparse_softmax_cross_entropy(labels=y_, logits=y)
```

TensorFlowでは、モデルの構築時に入力データを入れる箱を定義しておく必要があります。
その箱を定義するのがplaceholderです。
この時点ではplaceholderには何も入ってません。

[tf.placeholder  |  TensorFlow](https://www.tensorflow.org/api_docs/python/tf/placeholder)

損失関数には交差エントロピー誤差を使います。

[tf.losses.sparse_softmax_cross_entropy  |  TensorFlow](https://www.tensorflow.org/api_docs/python/tf/losses/sparse_softmax_cross_entropy)

関数名が示す通りsoftmaxの計算も行った結果を出力します。
同じような名前の関数がありました。

[tf.losses.softmax_cross_entropy  |  TensorFlow](https://www.tensorflow.org/api_docs/python/tf/losses/softmax_cross_entropy)

名前が似ているのでどちらを使っていいのか迷いますが、こちらは正解ラベルがone hot表現の際に用いるよう。

##**▼4.3.学習と評価**
続いて、学習・評価の準備

```py3
# 学習用モデル
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
```

パラメータの最適化手法は勾配降下法を用います。

[tf.train.GradientDescentOptimizer  |  TensorFlow](https://www.tensorflow.org/api_docs/python/tf/train/GradientDescentOptimizer)

他にもAdam、Adagrad、Momentumなど定番のものは使えるようです。

```py3
train_step = tf.train.AdagradOptimizer(0.5).minimize(cross_entropy)
train_step = tf.train.AdamOptimizer(0.01).minimize(cross_entropy)
train_step = tf.train.MomentumOptimizer(0.5, 0.5).minimize(cross_entropy)
```

```py3
# 予測
correct_prediction = tf.equal(tf.argmax(y, 1), y_)
# 認識精度
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
```

評価時に使う予測と認識精度を入れるための変数も定義しておきます。
reduce_meanは平均を計算する関数。

```py3
# セッション
sess = tf.InteractiveSession()
```

TensorFlow独自の仕組みでセッションを最初に定義してそこで学習、評価を行います。

```py3
# 学習用のパラメータ設定
epoch_num = 10
batch_size = 100
batch_count = int(mnist.train.num_examples/batch_size)

# 学習
for epoch in range(epoch_num) :
    T = time.time() # 処理時間計測用
    for i in range(batch_count):
        # 学習用データのセット
        batch_xs, batch_ys = mnist.train.next_batch(batch_size)
        # 学習実行
        sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

    epochT = time.time()-T

    # 学習モデルの評価
    loss, train_accuracy = sess.run([cross_entropy, accuracy], feed_dict={x: mnist.test.images, y_: mnist.test.labels})
    print('Epoch: %d, Time :%.4f (s), Loss: %f,  Accuracy: %f' % (epoch + 1, epochT, loss, train_accuracy))

```

学習と評価を行います。
指定したバッチサイズで、全学習データを学習するのを1エポックとし、指定したエポック数繰り返します。
1エポックごとに学習モデルの評価を行っています。

```py3
# 学習用データのセット
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
# 学習実行
sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
```

実行時に入力データを渡しています。

##**▼4.4.学習結果から予測する**
学習させたモデルを使って予測してみます。

```py3
### 学習させたモデルを使用して予測を行う ###
# テストデータからランダムに抜き出す
idx = np.random.choice(len(mnist.test.images), 100)
for i in idx:
    test_input = mnist.test.images[i][np.newaxis, :]
    # 予測結果を取得
    pre = np.argmax(sess.run(y, feed_dict={x: test_input}))
    # ラベルデータ
    test_y = mnist.test.labels[i]
    # ラベルデータと予測結果が異なっている場合は入力データ(画像データ)を出力
    if(pre != test_y):
        plt.imshow(mnist.test.images[i].reshape(28, 28), cmap='gray')
        # ファイル名：label_(ラベル値)_predict_(予測値).png
        file_name = "label_" + str(test_y) + "_predict_" + str(pre) + ".png"
        plt.savefig(file_name)
```

後半部分は[chainer編](https://qiita.com/nvtomo1029/items/18c3901052b2ee3dd355)の使い回しです(^^;

```py3
test_input = mnist.test.images[i][np.newaxis, :]
```

ここに意外とハマりまして。
MNISTの元データは1次元(784,)なんですが、学習モデルに渡す場合はバッチサイズが加わった2次元で渡す必要がありました。
予測させるだけなのでバッチサイズの部分は1固定でいいのでnp.newaxisを使っています。

##**▼4.5.全実装**

```py3
# coding: utf-8
import numpy as np
import time
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# ニューラルネットワークの構築
def create_model(input_placeholder, input_size, hidden_size, output_size):
    # 重み、バイアスの初期化
    params = {}
    params['W1'] = tf.Variable(tf.random_normal([input_size, hidden_size], mean=0.0, stddev=0.1))
    params['b1'] = tf.Variable(tf.zeros([hidden_size]))
    params['W2'] = tf.Variable(tf.random_normal([hidden_size, hidden_size], mean=0.0, stddev=0.1))
    params['b2'] = tf.Variable(tf.zeros([hidden_size]))
    params['W3'] = tf.Variable(tf.random_normal([hidden_size, output_size], mean=0.0, stddev=0.1))
    params['b3'] = tf.Variable(tf.zeros([output_size]))
    
    a1 = tf.sigmoid(tf.matmul(input_placeholder, params['W1']) + params['b1'])
    a2 = tf.sigmoid(tf.matmul(a1, params['W2']) + params['b2'])
    # 損失関数での計算時にsoftmaxの計算も行うのでそのまま出力する
    model = tf.matmul(a2, params['W3']) + params['b3']
    
    return model

# MNISTデータのダウンロード
mnist = input_data.read_data_sets("MNIST_data/", one_hot=False)

# 各パラメータの定義
input_size = 784 # 入力サイズ
hidden_size = 300 # 隠れ層のサイズ
output_size = 10 # 出力層のサイズ

# 入力データを入れる箱
x = tf.placeholder(tf.float32, [None, input_size])
# 正解ラベルを入れる箱
y_ = tf.placeholder(tf.int64, [None])
# モデルの構築
y = create_model(x, input_size, hidden_size, output_size)

# 損失関数
cross_entropy = tf.losses.sparse_softmax_cross_entropy(labels=y_, logits=y)
# 学習用モデル
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
# 予測
correct_prediction = tf.equal(tf.argmax(y, 1), y_)
# 認識精度
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# セッション
sess = tf.InteractiveSession()
# 初期化
tf.global_variables_initializer().run()

# 学習用のパラメータ設定
epoch_num = 10
batch_size = 100
batch_count = int(mnist.train.num_examples/batch_size)
print("batch_count : ", batch_count)

# 学習
for epoch in range(epoch_num) :
    T = time.time() # 処理時間計測用
    for i in range(batch_count):
        # 学習用データのセット
        batch_xs, batch_ys = mnist.train.next_batch(batch_size)
        # 学習実行
        sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

    epochT = time.time()-T

    # 学習モデルの評価
    loss, train_accuracy = sess.run([cross_entropy, accuracy], feed_dict={x: mnist.test.images, y_: mnist.test.labels})
    print('Epoch: %d, Time :%.4f (s), Loss: %f,  Accuracy: %f' % (epoch + 1, epochT, loss, train_accuracy))

### 学習させたモデルを使用して予測を行う ###
# テストデータからランダムに抜き出す
idx = np.random.choice(len(mnist.test.images), 1)
for i in idx:
    test_input = mnist.test.images[i][np.newaxis, :]
    # 予測結果を取得
    pre = np.argmax(sess.run(y, feed_dict={x: test_input}))
    # ラベルデータ
    test_y = mnist.test.labels[i]
    # ラベルデータと予測結果が異なっている場合は入力データ(画像データ)を出力
    if(pre != test_y):
        plt.imshow(mnist.test.images[i].reshape(28, 28), cmap='gray')
        # ファイル名：label_(ラベル値)_predict_(予測値).png
        file_name = "label_" + str(test_y) + "_predict_" + str(pre) + ".png"
        plt.savefig(file_name)

```

実行結果です。
10エポックで97%弱の認識精度が出ました。

```py3:実行結果
Epoch: 1, Time :1.9868 (s), Loss: 0.318483,  Accuracy: 0.905500
Epoch: 2, Time :1.9723 (s), Loss: 0.255258,  Accuracy: 0.925100
Epoch: 3, Time :1.9682 (s), Loss: 0.212112,  Accuracy: 0.936400
Epoch: 4, Time :1.9776 (s), Loss: 0.177393,  Accuracy: 0.948300
Epoch: 5, Time :1.9786 (s), Loss: 0.157209,  Accuracy: 0.950800
Epoch: 6, Time :1.9838 (s), Loss: 0.139005,  Accuracy: 0.958400
Epoch: 7, Time :1.9713 (s), Loss: 0.133845,  Accuracy: 0.958500
Epoch: 8, Time :1.9760 (s), Loss: 0.122958,  Accuracy: 0.962300
Epoch: 9, Time :1.9753 (s), Loss: 0.112772,  Accuracy: 0.964700
Epoch: 10, Time :1.9829 (s), Loss: 0.099384,  Accuracy: 0.968800
```

#**■5.学習モデルの保存と読み込み**

学習モデルの保存、読み込みにはtf.train.Saverクラスを使用します。

[tf.train.Saver  |  TensorFlow](https://www.tensorflow.org/api_docs/python/tf/train/Saver)

##**▼5.1.学習モデルの保存**

```py3
# モデル保存用
saver = tf.train.Saver()

# セッション
sess = tf.InteractiveSession()

(中略：学習処理)

# 学習済モデルの保存
saver.save(sess, "model/model_TensorFlow.ckpt")
```

保存は学習済モデルを作成したセッションをsave関数に渡すことで行えます。

##**▼5.2.学習済モデルで予測する**

```py3
# モデル読み込み用
saver = tf.train.Saver()

# セッション
sess = tf.InteractiveSession()

# 学習済モデルの復元
saver.restore(sess, "model/model_TensorFlow.ckpt")
```

学習済モデルの読み込み、復元はrestore関数でセッションに渡す形で行われます。

読み込んだ学習済モデルを使用した予測は、同じ手順で行えます。

```py3
### 学習済モデルを使用して予測を行う ###
# テストデータからランダムに抜き出す
idx = np.random.choice(len(mnist.test.images), 100)
for i in idx:
    test_input = mnist.test.images[i][np.newaxis, :]
    pre = np.argmax(sess.run(y, feed_dict={x: test_input}))
    # ラベルデータ
    test_y = mnist.test.labels[i]
    # ラベルデータと予測結果が異なっている場合は入力データ(画像データ)を出力
    if(pre != test_y):
        plt.imshow(mnist.test.images[i].reshape(28, 28), cmap='gray')
        # ファイル名：label_(ラベル値)_predict_(予測値).png
        file_name = "label_" + str(test_y) + "_predict_" + str(pre) + ".png"
        plt.savefig(file_name)
```