【Tensorflow入門】適当にTensorflowを理解して、モデルを作ってみる

この記事は、自身のブログ、[Data Science Struggle](http://marubon-ds.blogspot.jp)を翻訳したものになる。  

##目的
Tensorflowが何なのかを大雑把に理解し、簡単なモデルを作成していく。

##Tensorflowの概略
様々なところで聞くようになったTensorflowとは結局何であって、機械学習においてどのような位置付けになるのかというところから簡単に見ていく。  
1. Tensorflowとは何なのか？  
まず、Tensorflowとは、実際に使って行く上では、***ニューラルネットのパラメーターを決めるための計算を自動でやってくれる計算機***ぐらいの考え方で構わない。 
ここでいう***計算機***とは、1+1の答えを出してくれるというような、文字通り、計算を行ってくれるものだ。  
2. 機械学習においてはどのような位置付け？  
機械学習を用いる方法は大きくわけて三つある。 
一つ目は、アルゴリズム、数理を正しく理解した上で、各言語でモデルを自作してしまうパターン。  
二つ目は、pythonならsklearn、Rなら手法に応じた各ライブラリなどを用いて数行でモデルを作成してしまうパターン。  
三つ目は、機械学習系のモデルを作成するのに向いた計算ライブラリを用いて自作してしまうパターン。  

***Tensorflowは三つ目に当たる。***  

ここで簡単に、上記三つに関する実情を述べる。 
実際に機械学習の数理を学習したのであれば、その数理と仕組みの確認、より柔軟な書き換えのために一度はライブラリなどに依存せずにモデルの作成を行ってみたいところだ。 
とはいえ、一つ一つの状況に応じて全てのモデルをそのように作成することは現実的ではないことがほとんどだし、車輪の再開発は無駄を産む。 
そのため、実際には、機械学習用のライブラリの使い方を習熟して、問題に対応して行くことになる。大体のライブラリは非常に便利で、モデル作成のみであれば数行で目的を達することができる。 
しかし、そのようなライブラリが対応しにくい場面が存在しうる。 
ニューラルネットワークなどはその一例だ。中間層の数のみならず、結合の方法やデータの割り込みなど、特定のルールを守ってさえいればなんでもできてしまう。 
そのようなものの作成に機械学習ライブラリを使用するのは難しいし、かといって、全て自作するには気にするべきことが非常に多くなる。 
***
Tensorflowのような、機械学習向けの計算ライブラリは上の状況を解決してくれる。そもそも、モデル作成を一行で行うような機械学習ライブラリではない。 
***ユーザーがモデルを自作するのを助けてくれるライブラリ***なのだ。 
つまり、仕組みを一切知らなくても使用できる機械学習ライブラリとはことなり、最低でもモデルの組み方に関する知識がなければ使用することも難しいということになる。 

##Tensorflowの計算方法
実際に、Tensorflowがどのように計算を行うライブラリなのかを見て行く。 
まずは、具体的に、以下の計算をTensorflowで行う。

```math
5 + 3
```
これをTensorflowで解くと、こうなる。  

```add.py
import tensorflow as tf
a = tf.constant(5)
b = tf.constant(3)
added = tf.add(a, b)

with tf.Session() as sess:
    print sess.run(added)
```

ここで、addedを普通にprintしてみると、  

```
Tensor("Add_1:0", shape=(), dtype=int32)
```
と出力される。  
適当に解釈するなら、addedは具体的な数字ではなく、aとbを足し合わせたものという計算の形情報で、実際に計算がなされるのはSession内でrunさせた時ということになるのであろう。  
この***形情報***がTensorflowでは***graph***などと呼ばれている。  

##Tensorflowの変数
上では簡単なTensorflowでの計算方法を見たが、もう一点気にしなければならないことがある。それが変数だ。  
機械学習で使用される計算ライブラリである以上、多次元を一度に扱うことは必須となるし、上で述べたように、***graph***を作成したのちにrunによって実計算を行っているということは、変数に与える値を後から指定できたりしたほうが便利となる。実際にTensorflowはその機能を備えている。  
まとめると、***Tensorflowは、多次元、定数、後から代入を受け付ける一時変数を備えている***と言える。
以下のようにそれぞれ変数を定めることができる。  

```
# 定数
a = tf.constant(3)

# 変数
b = tf.Variable(0)

# プレースホルダー
c = tf.placeholder(tf.float32)
```

具体的に、プレースホルダーを使用して計算をしてみると  

```
a = tf.placeholder(tf.float32)
b = tf.placeholder(tf.float32)
added = tf.add(a, b)
with tf.Session() as sess:
    print sess.run(added, feed_dict = {a: 3.0, b: 5.0})
```
上の例ではaddedまでがgraphとなり、runフェーズで具体的な値がfeed_dictにより与えられて計算がなされている。

##Tensorflowによる分類
実際にirisデータをtensorflowを用いて分類してみる。なお、これより先は、全体のフェーズを***設計図作成***と***ビルド***の二つに分けて考える。
***設計図作成***とは、どのようなモデル、graphを作成するのかの部分。
***ビルド***とは、データを上記の設計図に入力することで、設計図内のパラメーターを定めることを指す。 
以下がiris分類のコード例。 
なお、データには 
https://dl.dropboxusercontent.com/u/432512/20120210/data/iris.txt  
よりダウンロードしたものを使用。 

```get_data.py
import pandas as pd
import tensorflow as tf
from sklearn import cross_validation

data = pd.read_csv('https://dl.dropboxusercontent.com/u/432512/20120210/data/iris.txt', sep = "\t")
data = data.ix[:,1:]
train_data, test_data, train_target, test_target = cross_validation.train_test_split(data.ix[:,['Sepal.Length', 'Sepal.Width', 'Petal.Length', 'Petal.Width']], data.ix[:,['Species']], test_size = 0.4, random_state = 0)
```
上記のコードで、irisデータの獲得と、訓練データとテストデータへの分割が行われる。 
今後は、train_dataとtrain_targetの対データを用いてモデルを作成する。train_dataが与えられた時の答えがtrain_targetということになる。 
もちろん、trainとtestに分割しているのだから本来であれば、テストデータを用いたテストも行うべきだが、今回は割愛。 
これより、実際に設計図作成とそれに基づいたビルドを行う。  

```classify.py
# 設計図作成
# プレースホルダー設定
X = tf.placeholder(tf.float32, shape = [None, 4])
Y = tf.placeholder(tf.float32, shape = [None, 3])

# パラメーター設定
W = tf.Variable(tf.random_normal([4, 3], stddev=0.35))

# 活性化関数
y_ = tf.nn.softmax(tf.matmul(X, W))

# ビルド
# 損失関数
cross_entropy = -tf.reduce_sum(Y * tf.log(y_))

# 学習
optimizer = tf.train.GradientDescentOptimizer(0.001)
train = optimizer.minimize(cross_entropy)

# 実行
with tf.Session() as sess:
    sess.run(tf.initialize_all_variables())
    for i in range(1000):
        x = train_data
        y = pd.get_dummies(train_target)
        print(sess.run(W))

        sess.run(train, feed_dict = {X: x, Y: y})

    test = sess.run(W)
```

一つずつ見ていく。 
まずは設計図の作成。 
説明変数の行列をX、被説明変数の行列をY、重みをWと置いた時、この設計図は以下のように表せる。なお、今回はバイアス項は用いない。 

```math
X = [None, 4]\\
W = [4, 3]\\
Y = [None, 3]\\
[None, 4] \times [4, 3] = [None, 3]\\
```

上記の式の[None, 3]はYに一致。
実際にコードに移る。  

```placeholder.py
# プレースホルダー設定
X = tf.placeholder(tf.float32, shape = [None, 4])
Y = tf.placeholder(tf.float32, shape = [None, 3])
```

プレースホルダーを設定。ここで設定しているX,Yはそれぞれ、データにおける説明変数と被説明変数を指していて、***後のパートでデータが入れられることになる***。 
irisデータは説明変数の数は4つ、分類先であるクラスの数は3つになる。加えて、データを食わせる時に与える数（データフレームの行数）は不明であるため、そこはNoneを入れておく。 

``` parameter.py
# パラメーター設定
W = tf.Variable(tf.random_normal([4, 3], stddev=0.35))
```
パラメーター部分、つまり、重みを設定。ビルド部分ではデータの入力を通じてこのパラメーターWを更新、確定させることとなる。 
tf.random_normalやstddevの部分では指定の分布に従う乱数を初期値として与えている。与え方は幾つかあるので各々見ておくべし。 

```activate.py
# 活性化関数
y_ = tf.nn.softmax(tf.matmul(X, W))
```
活性化関数を定める。出力の形、中間層なのか、出力層なのかなどに応じて適切な関数を選択する必要がある。 
今回の場合はこのy_が入力に対する最終出力となる。  

これより先はビルド部分になる。ややこしい計算などはTensorflowの方でうまくやってくれるが、おおよそどのようなことをやっているかの構図は理解しておく必要がある。
ビルド部分の構図を適当に述べると、***損失関数を定義し、その損失が小さくなるようにパラメーターを更新していく***ということになる。 
上で定義したパラメーターWの値によって予測の精度は異なる。なるべく予測の精度が出るようにパラメータを変更していく。その際に、***どれだけ精度があるか***に着目するのではなく***どれだけ間違いが少ないか***に着目してパラメーターを、より間違いが少ないように更新していくということだ。

```lost.py
# 損失関数
cross_entropy = -tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(y_, Y)
```
この損失関数が、上で述べた***どれだけ間違いが少ないか***という部分になる。作成するモデルが分類なのか、回帰なのかなどに応じて損失関数は異なる。これは、Tensorflowの方で損失関数を複数用意してあるので調べたほうが良い。

```train.py
# 学習
optimizer = tf.train.GradientDescentOptimizer(0.001)
train = optimizer.minimize(cross_entropy)
```
optimizerで実際にデータに基づいてどのようにパラメーターを更新するかを定めている。 
trainでは、optimizerで定めた更新方法に基づいて、損失関数を小さく（パラメーター更新）している。

```execute.py
# 実行
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for i in range(1000):
        x = train_data
        y = pd.get_dummies(train_target)
        print(sess.run(W))

        sess.run(train, feed_dict = {X: x, Y: y})

```
ここで具体的なビルドを行う。今回は手抜きで、全データを1単位として1000回読み込ませている。 
sess.run()の部分で、placeholder部分にfeed_dictで具体的なデータを与えている。 
これにより、損失関数を局所最小化するようなパラメーターを探索し、Wを更新して行く。 
実際には損失の程度などを書き出しながら進めて行くことが多い。