サルでもわかるtensorflow：基本的な変数の扱い方

ディープラーニングのフレームワークの中で、世界で一番使われているtensorflowの使い方を調べてみました。

見ているだけで基本的な使い方が頭に入ってくるものを目指して、できるだけ細かく（冗長に？）コードを書いているので、初めてtensorflowを使う人向けになればいいな、と願っております。

# 動機

今まで機会があるごとにchainerやkerasを触ってきたのですが、

<b>kerasに頼らずtensorflow使いたい...!</b>

ということでネット上の転がってる記事を参考に勉強を始めました。
<br>
ちなみにtensorflowを使おうと思ったきっかけは主に２つです。

・遺伝アルゴリズムを使ってネットワークを進化させてみたい！（とある教授の実験を再現したい）
・~~日本語記事の豊富なchainerばっかり使ってると「いかにも日本人らしい」ってなりそう...~~

# tensorflowのとっつきにくさ

まずtensorflowがとっかかりづらい（実は嬉しさでもある）のは、

<b>計算グラフ（フロー）を作成　→　計算を実行</b>

という手順を踏まないといけない部分です。
普通のpythonコードだと

```python
a = 10
b = a + 10
c = b + 10
```
の各行を実行すると、
aに値が入る　→　bに値が入る　→　cに値が入る
ってなりますよね。<br>
じゃあtensorflowだとどうなの、について見ていきましょう。

# 基本的な計算

まずインポートは以下のように。

```python

import numpy as np
import tensorflow as tf
```

## 定数

以下を実行してみてください。

```python
const = tf.constant(10)  # 定数を定義

with tf.Session() as sess:
    result_const = sess.run(const)
    print(result_const)  # 10がプリントされる
```
定数を定義している箇所は

```python
const = tf.constant(10)
```
ですが、ここではまだ
「constに定数オブジェクトを割り当てる」
という「グラフ」を定義しただけなので、constに10という値は代入されません。

```python
sess.run(const)
```
で初めて値の代入・計算が実行されます。
返り値はconstの値です。
<br>

今の例だとやっていることが単純すぎて逆によくわからないかもしれないので、足し算をしてみましょう。

```python
const_1 = tf.constant(10)
const_2 = tf.constant(30)
added = const_1 + const_2

with tf.Session() as sess:
    result = sess.run(added)
    print(result)  # 10+30の結果である40がプリントされる
```
最初の3行で
(const_1, const_2)　→　added
という計算グラフを作成しています。

そして、

```python
sess.run(added)
```
の返り値は"added"の値そのものになります。

注目すべきは、

```python
sess.run(added)
```
を実行すると、addedに繋がるまでの２つの計算グラフ、すなわち
「const_1に値を代入」「const_2に値を代入」の両方が自動で実行されることです。
つまり、以下のような書き方をする必要はないということです。

```python

const_1 = tf.constant(10)
const_2 = tf.constant(30)

with tf.Session() as sess:
    result_const_1 = sess.run(const_1)
    result_const_2 = sess.run(const_2)
    result = result_const_1 + result_const_2
    print(result)
```
ただし、const_1とconst_2の値を別の計算グラフで計算したい時には、上のような書き方をする必要があるでしょう。

## 変数

変数は"tf.Variable( )"で定義しますが、定数の時と同じように実行すると怒られます。
これ、知らないとちょっと焦ります

```python

val = tf.Variable(10)  # 変数を定義

with tf.Session() as sess:
    result = sess.run(val)
    print(result)
```

tf.Variable( )を使う場合は、初期化してあげる必要があるそうです。

```python
val = tf.Variable(10)  # 変数を定義

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())  # ここが必要！
    result = sess.run(val)
    print(result)
```

## プレースホルダー
「実行するまで値がわからない」変数を扱う時の格納庫的な役割をしてくれます。
tf.constantやtf.Variableと違って、データの型を指定する必要があります。

```python

holder = tf.placeholder(dtype=tf.float32)  # 今はまだ値がわからない
const = tf.constant(10, dtype=tf.float32)
added = holder + const

with tf.Session() as sess:
    the_value = 40  # この時点で初めて値がわかった！
    result = sess.run(added, feed_dict={holder:the_value})  # holderにthe_valueの値を代入してaddedを計算
    print(result)
```
"sess.run( )"時に辞書の形で実際の値を代入してあげます。

## 注意点

今まで触れませんでしたが、同じデータ型同士でないと計算ができないので、今のように型を明示的に書くことを心がける必要がありそうです。

## 代入
pythonでの

```python
var = 100

def main():
    return var + 1

for i in range(3):
    var = main()
    print(var)
```
のように、変数の値をどんどんアップデートする処理をtensorflowで実現したいのですが、Sessionの中で

```python
tf_variable = 101  # tf_variable : tf.Variableオブジェクト
```
のように値を代入することはできません。
値の代入には

```python

tf.assign("アップデートする変数", "代入する値")
```
を使います。


```python
var = tf.Variable(100, dtype=tf.int32)  # この変数をアップデートしていく

holder = tf.placeholder(dtype=tf.int32)  # アップデートする値を代入する用
update = tf.assign(var, holder)  # 値をアップデートする処理

def main():
    return var + 1

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())  # 変数を初期化
    for i in range(3):
        result = sess.run(main())  # 1を加えた値を取得
        sess.run(update, feed_dict={holder:result})  # update関数を使って、varの値をresultの値にアップデート
        print(result)  # 101 102 103 の順番でプリントされる
```

# 以上を踏まえて

## 一般的な書き方？
普通は関数などを定義してコードをわかりやすくするので、足し算なら以下のような書き方になるのではないでしょうか。

```python
def main():
    x = tf.constant(10)
    y = tf.constant(30)
    return x + y

with tf.Session() as sess:
    result = sess.run(main())
    print(result)  # 40
```
はたまた

```python
def add():
    x = tf.constant(10)
    y = tf.constant(30)
    return x + y

z = add() + tf.constant(100)


with tf.Session() as sess:
    result = sess.run(z)
    print(result)  # 140
```

最後のコードの計算グラフをtensorboardを使って可視化するとこうなりました。

<img width="239" alt="スクリーンショット 2018-05-26 2.25.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/211271/35fca0f5-4172-b5d3-b648-498e1df9e7e2.png">

ちゃんと１つの計算グラフになっていますね！
<br>
ただし、気をつけなければいけないのは、以下を実行すると怒られることです。

```python
def main():
    val_1 = tf.Variable(10, dtype=tf.float32)
    val_2 = tf.Variable(20, dtype=tf.float32)
    return val_1 * val_2

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())  # 変数を初期化
    result = sess.run(main())  # ここで怒られる！
```
何が問題かというと、main( )を定義しただけではval_1, val_2が定義されたことにならないので、変数を初期化してるつもりができていないことです。

なので、変数はできるだけ関数の外で定義してあげると混乱しにくいと思います。

## 配列
numpyの配列と同じように計算できます。

```python

def mul(x, y):
    return x * y

val_1 = tf.Variable([10, 20], dtype=tf.float32)
val_2 = tf.Variable([30, 40], dtype=tf.float32)

result = mul(val_1, val_2)


with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())  # 変数を初期化
    result = sess.run(result)
    print(result)  # [ 300.  800.]
```

# 最後に
とりあえず今回は以上で。
次は全結合層をメインにしたネットワーク作って、遺伝アルゴリズムで進化させてみたいと思います。

誤差逆伝播使わないとtensorflowが泣く？
そんなことは知ったこっちゃありません。<br>