TensorFlowのtutorialをChainerでも実装してみた

　GoogleがDeep Learning用のフレームワーク[TensorFlow](http://tensorflow.org/)をオープンソース化して公開しましたね(2015/11/9)。いろんな人がインストールしてみたとか、Tutorialを実行してみたという記事やブログをすでに書かれています。出遅れた感はありますがせっかくなのでTensorFlowをいろいろいじってみました。ただTutorialを実行するだけでは面白くないので同じものを[Chainer](http://chainer.org/)でも書いてみることにしました。Chainerもまだ始めたばかりなので全く同じではないですがそのへんはご勘弁ください。
　なぜChainerなのかというと、CaffeやTheanoが面倒くさそうに見えたのに対し、Chainerはコードがスマートに感じたからです。個人的な感想ですが・・・。あとChainerではRNNが書けるということで、ほかができないものができると言われるとなんだか良さそうに感じでしまうのは人間の性でしょうか。
　今回はTensorFlowのインストールに関しては書きません。理由はなんか色々試行錯誤したのですがすっかり忘れてしまったからです。すいません。

## Deep MNIST for Experts

　今回試したのは「Deep MNIST for Experts」です。詳細はTensorFlowの[Tutorial](http://tensorflow.org/tutorials/mnist/pros/index.md)を参照してください。下の方にコードを載せていますが、ちょっとだけチュートリアルのコードから変更しました。GPU版のTensorFlowをインストールしたのですが、自分の環境がGeForce GTX970だったので、学習後のテストデータで精度を測るところでGPUのメモリが足りなくてOut of Memoryで例外を吐いてうまく動きませんでした。テストデータをいっぺんに使おうとしたからのようです。多分GoogleはTITAN XとかTeslaとか使ってるから気にもしなかったのでしょう。

>print "test accuracy %g"%accuracy.eval(feed_dict={
>    x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})

しかたないのでテストデータを分割して精度を計算するように変更しました。

>tacc = 0
>tbatchsize = 1000
>for i in range(0,N_test,tbatchsize):
>    acc = accuracy.eval(feed_dict={
>        x: mnist.test.images[i:i+tbatchsize], y_: mnist.test.labels[i:i+tbatchsize], keep_prob: 1.0})
>    print "test accuracy %d = %g" % (i//tbatchsize, acc)
>    tacc += acc * tbatchsize
>tacc /= N_test
>print "test accuracy = %g" % tacc    

## Deep MNIST for Expertsの構造
　TensorFlowのtutorialのDeep MNIST for Expertsの構造はいわゆるConvolutional Neural Networkというものです。CNNの説明は世の中にたくさんあるのでここではしないですが、今回のTutorialのCNNの構造は下記のようになっているようです(Fig.1)。先頭がchannel数で続いてMapの横ｘ縦を表しています。convolutionによってchannel数が変化し、poolingによりMapのサイズが変化します。ただしconvolutionのところでpaddingを入れないとMapのサイズが小さくなってしまいます。paddingはkernel sizeの半分を入れてあげればいいです。

1x28x28 -> 32x28x28 -> 32x14x14 -> 64x14x14 -> 64x7x7 -> 1024 -> 10
Fig.1

以下がTensorFlowでNetworkが記述されているところです。

>
>W_conv1 = weight_variable([5, 5, 1, 32])
>b_conv1 = bias_variable([32])
>x_image = tf.reshape(x, [-1,28,28,1])    
>
>h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
>h_pool1 = max_pool_2x2(h_conv1)
>
> ....
>
>W_fc2 = weight_variable([1024, 10])
>b_fc2 = bias_variable([10])
>
>y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

これをchainerで書いたのが以下です。
>model = chainer.FunctionSet(conv1=F.Convolution2D(1,32,5,pad=2),
>                            conv2=F.Convolution2D(32,64,5,pad=2),
>                            fl3=F.Linear(7*7*64,1024),
>                            fl4=F.Linear(1024,10))
>
> ...
>
>def forward(x_data, y_data, train=True):
>    x, t = chainer.Variable(x_data), chainer.Variable(y_data)
>    h_conv1 = F.relu(model.conv1(x))
>    h_pool1 = F.max_pooling_2d(h_conv1, 2)
>    
>    h_conv2 = F.relu(model.conv2(h_pool1))
>    h_pool2 = F.max_pooling_2d(h_conv2, 2)
>
>    h_fc1 = F.relu(model.fl3(h_pool2))
>    h_fc1_drop = F.dropout(h_fc1, ratio=0.5,train=train)
>    
>    y = model.fl4(h_fc1_drop)
>
>    return F.softmax_cross_entropy(y, t), F.accuracy(y, t) 


厳密には以下の部分が同じではないかもしれないです。

1. TensorFlowでは重みWとバイアスbをrandomな値と一定値で初期化しているようです。
 
[truncated_normal](http://www.tensorflow.org/api_docs/python/constant_op.md#truncated_normal)はTensorFlowのドキュメントによると平均から標準偏差の２倍以上離れたところの頻度が０となるような正規分布に従うRandomな値ということです。
[constant](http://www.tensorflow.org/api_docs/python/constant_op.md#constant)は定数のTensorを作る関数です。
>def weight_variable(shape):
>    initial = tf.truncated_normal(shape, stddev=0.1)
>    return tf.Variable(initial)
>    
>def bias_variable(shape):
>    initial = tf.constant(0.1, shape = shape)
>    return tf.Variable(initial)

 ~~2. あとTensorFlowのpadding='SAME'の意味がわからなかったのでとりあえず一旦気にしないことにしました。~~ 
（追記：コメントにより情報をいただきました。SAMEは入力と出力のサイズが同じになるように、入力に0 paddingするということでした。）
   
>def conv2d(x, W):
>    return tf.nn.conv2d(x, W, strides=[1,1,1,1], padding='SAME')
>def max_pool_2x2(x):
>    return tf.nn.max_pool(x, ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')

## 実行結果
 以下は精度と実行時間を比較した結果です。実行時間はTensorFlowとChainer以外の部分が全く一緒ではないため厳密な比較にはなっていないです。データを渡すときの形式が異なるのでChainerの方のコードでは余計な処理が入っています。
　実行した結果を下の表にまとめました。TensorFlow,Chainerとも精度は同じでした。そもそもMNISTは問題が簡単なのでDeep Learningでなくても高い精度が出るそうです。実行時間はChainerの方が速かったのです。TensorFlowの方が速いかなと思っていたのにちょっと意外でした。どこかでTensorFlowにはある処理がChainerには無いとかいう可能性もあります。こんなもんかな程度に留めておいてもらえるといいかなと思います。


|            |  精度 |  時間  |
|------------|:-----:|:------:|
| TensorFlow | 99.2% | 249sec |
|   Chainer  | 99.2% | 213sec |

実行環境
・OS Linux Ubuntu 14.04 LTE
・CPU Core i5-4460 3.2GHz
・Memory 16GB
・GeForce GTX 970

## ソースコード

```lang:tensorflow_mnist.py
# -*- coding: utf-8 -*-
import input_data
print 'load MNIST dataset'
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)

import tensorflow as tf
sess = tf.InteractiveSession()

import time

x = tf.placeholder("float", [None, 784])
y_ = tf.placeholder("float", [None, 10])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

# normal distribution
def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)
    
def bias_variable(shape):
    initial = tf.constant(0.1, shape = shape)
    return tf.Variable(initial)
    
def conv2d(x, W):
    return tf.nn.conv2d(x, W, strides=[1,1,1,1], padding='SAME')
def max_pool_2x2(x):
    return tf.nn.max_pool(x, ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
    
# 1x28x28 -> 32x28x28 -> 32x14x14
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
x_image = tf.reshape(x, [-1,28,28,1])    

h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

# 32x14x14 -> 64x7x7
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)

# 64x7x7 -> 1024
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])

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

# dropout
keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

# Readout
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])

y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
sess.run(tf.initialize_all_variables())

# training
N = len(mnist.train.images)
N_test = len(mnist.test.images)
n_epoch = 20000
batchsize = 50
start_time = time.clock()
for i in range(n_epoch):
  batch = mnist.train.next_batch(batchsize)
  if i%100 == 0:
    train_accuracy = accuracy.eval(feed_dict={
        x:batch[0], y_: batch[1], keep_prob: 1.0})
    print "step %d, training accuracy %g"%(i, train_accuracy)
  train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
  
tacc = 0
tbatchsize = 1000
for i in range(0,N_test,tbatchsize):
    acc = accuracy.eval(feed_dict={
        x: mnist.test.images[i:i+tbatchsize], y_: mnist.test.labels[i:i+tbatchsize], keep_prob: 1.0})
    print "test accuracy %d = %g" % (i//tbatchsize, acc)
    tacc += acc * tbatchsize
tacc /= N_test
print "test accuracy = %g" % tacc    

end_time = time.clock()
print end_time - start_time
```
```lang:chainer_mnist.py
# -*- coding: utf-8 -*-
from chainer import cuda
from chainer import optimizers
import chainer
import chainer.functions as F
import input_data
import numpy as np
import time

gpu_flag = 0

if gpu_flag >= 0:
    cuda.check_cuda_available()
    
xp = cuda.cupy if gpu_flag >= 0 else np

print 'load MNIST dataset'
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)

model = chainer.FunctionSet(conv1=F.Convolution2D(1,32,5,pad=2),
                            conv2=F.Convolution2D(32,64,5,pad=2),
                            fl3=F.Linear(7*7*64,1024),
                            fl4=F.Linear(1024,10))

if gpu_flag >= 0:
    cuda.get_device(gpu_flag).use()
    model.to_gpu()   
                            
def forward(x_data, y_data, train=True):
    x, t = chainer.Variable(x_data), chainer.Variable(y_data)
    h_conv1 = F.relu(model.conv1(x))
    h_pool1 = F.max_pooling_2d(h_conv1, 2)
    
    h_conv2 = F.relu(model.conv2(h_pool1))
    h_pool2 = F.max_pooling_2d(h_conv2, 2)

    h_fc1 = F.relu(model.fl3(h_pool2))
    h_fc1_drop = F.dropout(h_fc1, ratio=0.5,train=train)
    
    y = model.fl4(h_fc1_drop)

    return F.softmax_cross_entropy(y, t), F.accuracy(y, t)                            
    

# setup optimizer
optimizer = optimizers.Adam()
optimizer.setup(model)

n_epoch = 20000
batchsize=50

N = len(mnist.train.images)
N_test = len(mnist.test.images)

start_time = time.clock()
for i in range(n_epoch):        
    x_batch, y_batch = mnist.train.next_batch(batchsize)
    x_batch = x_batch.astype(np.float32)
    x_batch = xp.asarray(x_batch.reshape(batchsize,1,28,28))
    y_batch = xp.asarray(np.argmax(y_batch, axis=1).astype(np.int32))
            
    if i%100 == 0:
        loss, acc = forward(x_batch, y_batch, train=False)
        print 'step {0} training accuracy {1}'.format(i, float(acc.data))

    optimizer.zero_grads()
    loss, acc = forward(x_batch, y_batch)
    loss.backward()
    optimizer.update()
    
sum_accuracy = 0
sum_loss = 0
tbatchsize=1000
for i in range(0, N_test, tbatchsize):
    x_batch = mnist.test.images[i:i+tbatchsize].astype(np.float32)
    y_batch = mnist.test.labels[i:i+tbatchsize]
    x_batch = xp.asarray(x_batch.reshape(tbatchsize,1,28,28))
    y_batch = xp.asarray(np.argmax(y_batch, axis=1).astype(np.int32))


    loss, acc = forward(x_batch, y_batch, train=False)

    sum_loss += float(loss.data)*tbatchsize
    sum_accuracy += float(acc.data)*tbatchsize
    print "test accuracy %d = %g" % (i//tbatchsize, float(acc.data))
    
print 'test accuracy = {0}'.format(sum_accuracy/N_test)

end_time = time.clock()
print end_time - start_time

```