ニューラルネットの重みを実数値遺伝的アルゴリズムで最適化してみる ～その3：TensorFlow対応【失敗】～

[前回](https://qiita.com/SwitchBlade/items/3eeae78cd6bd1ba57482)作ったOpenAI Gymを**NeuroEvolution**(ニューラルネット＋実数値遺伝的アルゴリズム)で学習する実装をTensorFlowを使って高速化できないかなと思って試してみました。

結果としては、失敗しました。。。2～4倍の時間がかかるようになりました。
他の人が無駄な実装をしないように、迷走をしないようにメモとして残しておきます。

#TensorFlowに関して改めて復習
TensorFlow化するにあたって、引っかかった点を整理します。

##テンソル(Tensor)オブジェクトは数値の配列ではない
numpyと同じような計算ができるのごっちゃになりますが、あくまでも操作(オペレーション)を詰め込んだ箱です。

``` tensor.py
import tensorflow as tf
import numpy as np

def sum(a , b) :
    return a + b
    
#TensorFlowの計算
tfa = tf.constant([1,2])
tfb = tf.constant([10,20])
tfc = tf.constant([100,200])

print('tfa : {0} : <type={1}>'.format(tfa  , type(tfa))) 
#>tfa : Tensor("Const:0", shape=(2,), dtype=int32) : <type=<class 'tensorflow.python.framework.ops.Tensor'>>

sum_op = sum(tfa , tfb)
print('sum_op : {0} : <type={1}>'.format(sum_op  , type(sum_op))) 
#>sum_op : Tensor("add:0", shape=(2,), dtype=int32) : <type=<class 'tensorflow.python.framework.ops.Tensor'>>

sum_op = sum(sum_op , tfc)
print('sum_op : {0} : <type={1}>'.format(sum_op  , type(sum_op))) 
#>sum_op : Tensor("add_1:0", shape=(2,), dtype=int32) : <type=<class 'tensorflow.python.framework.ops.Tensor'>>

sess = tf.Session()
result = sess.run(sum_op)
print('result : {0} : <type={1}>'.format(result  , type(result))) 
#>result : [111 222] : <type=<class 'numpy.ndarray'>>

#numpyの計算
npa = np.array([1,2])
npb = np.array([10,20])
npc = np.array([100,200])
npsum = sum(npa , npb)
npsum = sum(npsum , npc)
print('npsum : {0} : <type={1}>'.format(npsum  , type(npsum))) 
#>npsum : [111 222] : <type=<class 'numpy.ndarray'>>
```

`sess.run()`でオペレーションを実行することで初めて計算結果が受け取れます。


変数ではないので、テンソル(Tensor)オブジェクトをnumpyの関数に渡すとエラーになります。

```TensorError.py
import tensorflow as tf
import numpy as np
#ReLU関数
def relu(x):
    return np.maximum(0, x)

npa = np.array([-1,2])
nprelu = relu( npa )
print('nprelu : {0} : <type={1}>'.format(nprelu  , type(nprelu))) 
#> nprelu : [0 2] : <type=<class 'numpy.ndarray'>>

tfa = tf.constant([-1,2])
relu_op = relu(tfa )

#> TypeError: Using a `tf.Tensor` as a Python `bool` is not allowed. Use `if t is not None:` instead of `if t:` to test if a tensor is defined, and use TensorFlow ops such as tf.cond to execute subgraphs conditioned on the value of a tensor.
```

##オペレーション作成後にTensorFlowの定数を書き換えても反映されない
```TensorConstSum.py
import tensorflow as tf
import numpy as np

def sum(a , b) :
    return a + b
    
tfc = tf.constant([1,2])
b = 10
sum_op = sum(tfc , b)

sess = tf.Session()

result = sess.run(sum_op)
print('result : {0} : <type={1}>'.format(result  , type(result))) 
#>result : [11 12] : <type=<class 'numpy.ndarray'>>

tfc = tf.constant([10,20])
b = 100
result = sess.run(sum_op)
print('result : {0} : <type={1}>'.format(result  , type(result))) 
#>result : [11 12] : <type=<class 'numpy.ndarray'>>
```

##TensorFlowの変数を更新するにはassign()を使う必要がある

```TensorVariableSum.py
import tensorflow as tf
import numpy as np

def sum(a , b) :
    return a + b

tfc = tf.constant([1,2])
tfv = tf.Variable(10)

sum_op = sum(tfc , tfv)

sess = tf.Session()
sess.run(tf.global_variables_initializer()) 

result = sess.run(sum_op)
print('result : {0} : <type={1}>'.format(result  , type(result))) 
#>result : [11 12] : <type=<class 'numpy.ndarray'>>

#変数 更新
assign_op = tfv.assign(100)
sess.run(assign_op)
result = sess.run(sum_op)
print('result : {0} : <type={1}>'.format(result  , type(result))) 
#>result : [101 102] : <type=<class 'numpy.ndarray'>>
```

#NN+実数値GAのTensorFlow対応
OpenAI GymをNN+実数値GAで学習するとき、1個体のシミュレーションの処理フローは以下のようにしていました。

![NNGA_OpenAI.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438288/f883ddc2-9808-cd38-4734-4dbd5409ab2d.jpeg)

OpenAI Gymはnumpyを使って実装されているので、TensorFlow化できるのは「NNで行動を推論」の部分だけに。。なおかつ、NNの重みとバイアスをTensorFlowの変数で表現すると、個体毎に変数の更新処理が必要になります。

##結果
GoogleColaboratoryのGPUモードとCPUモードで速度計測しています。50個体を評価した平均です(時刻取得や出力にかかるコストもあるので、あくまで目安で)

|計測区間             |TensorFlowなし(GPU) | TensorFlow対応(GPU)|TensorFlow対応(CPU) |
|:-----------------------|---------------:|------------:|----------------:|
| 重みとバイアスセット   |       23[μsec] |  21963[μsec] | 25191[μsec] |
| 200stepのシミュレート  |    88587[μsec] | 255974[μsec] |186580[μsec] |
| 合計                   |    88610[μsec] | 277937[μsec] |211771[μsec] |

「重みとバイアスセット」に時間がかかるのは想定内ですが、シミュレートにも時間がかかってます。
1stepのシミュレートの内訳です。

|計測区間          |TensorFlowなし(GPU) | TensorFlow対応(GPU)|TensorFlow対応(CPU) |
|:-------------------------------|-----------:|--------------:|-------------:|
| NNで行動を推論                 |  132[μsec] |     926[μsec] |    559[μsec] |
| OpenAI Gymで1stepシミュレート  |   36[μsec] |      53[μsec] |     55[μsec] |
| 合計                           |  168[μsec] |     979[μsec] |    614[μsec] |

高速化したかった「NNで行動を推論」自体に時間がかかってしまってます。そして、GPUよりCPUの方が速い。。[TensorFlowのチュートリアル](https://www.tensorflow.org/beta/tutorials/eager/basics?hl=ja) を見る限りでは、メモリのコピーがネックになっていそうな感じです。
「OpenAI Gymで1stepシミュレート」で時間がかかるようになってるのは、コンテキストの切り替えとかですかね？？

##結論
[BPを用いた画像認識](https://qiita.com/SwitchBlade/items/6677c283b2402d060cd0)のように、1回のオペレーション実行で大量のバッチ処理を行うような学習ではない限り、TensorFlowで実装するメリットはなさそうですね。