環境構築不要？今すぐ始めるTensorFlow on Unity

# TL;DR
* UnityがTensorFlowSharpを.unitypackageで配布しているから今すぐ使える！
* TensorFlowはGCPのCloud Shellにインストール済みだから今すぐ使える！
* Python側でモデルを作って、Unityで動かしてみよう。
* 注：ml-agentsの話ではありません。ml-agentsのラッパーを介さずにTensorFlowを使う話なので、先にml-agentsについて（[参考リンク](http://kan-kikuchi.hatenablog.com/entry/MLAgents)）知ったほうが良いかもしれません。

# 想定読者
* Unityエンジニアだけど今流行りの機械学習を始めたい。

# UnityでTensorFlowを動かしてみる
とりあえず、UnityでTensorFlowが動くことを確かめます。

1. Unity（2017.1以上）をインストール
2. 空のプロジェクトを作る
3. .NET 4.6 を有効にして再起動
    1. Edit > Project Settings > Player
    2. Other Settings / Configuration / Scripting Runtime Version
    3. 「Experimental (.NET 4.6 Equivalent)」 を選ぶ
4. [Unity Tensorflow Plugin](https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Using-TensorFlow-Sharp-in-Unity-(Experimental).md#requirements) をダウンロードして展開。
5. 以下が実行できればOK。

```csharp
using UnityEngine;
using TensorFlow;

public class HelloTensorFlow : MonoBehaviour
{
    void Start()
    {
        TFGraph graph = new TFGraph();
        TFOutput tensor = graph.Const(new TFTensor(12345));
        TFSession session = new TFSession(graph);
        TFTensor[] results =
            session.Run(new TFOutput[0], new TFTensor[0], new []{ tensor });
        print(results [0].GetValue());  // Consoleに12345が表示されるはず！
    }
}
```
GraphやSessionが何なのかはまだ気にしなくて良いです。

# Cloud ShellでTensorFlowを動かしてみる
次にCloud ShellでTensorFlowを動かしてみます。iPythonもnumpyもTensorFlowもインストールされていて便利です。

1. [GCPに登録してCloud Shellを有効にする。](https://cloud.google.com/shell/docs/quickstart) 
2. [Cloud Shell](https://console.cloud.google.com/cloudshell)を開く。
3. iPython（普通のPythonでも可）で以下を実行できればOK。

```sh
$ ipython
In [1]: import tensorflow as tf
In [2]: tensor = tf.constant(12345)
In [3]: session = tf.Session()
In [4]: session.run(tensor)
Out[4]: 12345
```
先ほどと同じことをしているのにPythonの方がややシンプルですね。

これ以降、iPythonは章ごとに閉じて開き直す前提で読んで下さい。

# 機械学習モデルを作って保存
簡単な機械学習モデルを作ってみましょう。簡単と言えば、中学数学でおなじみの1次元線形モデル $y=ax+b$です。$x$が入力、$y$が出力で、パラメーター$a$と$b$を学習データから決定する機械学習モデルだと言い張ることが出来ます。（実用性はさておき、ニューラルネットはこの延長にあるので、練習としては良いと思います！）

$y=ax+b$というモデルをTensorFlowで作ると以下のようになります。iPythonで実行しましょう。

```python
import tensorflow as tf
a = tf.Variable(-1.0, name='a')
b = tf.Variable(2.0, name='b')
init = tf.variables_initializer([a, b], name='init')
x = tf.placeholder(tf.float32, name='x')
y = tf.add(a * x, b, name='y')
```
`tf.Variable`でパラメーター$a$と$b$を作り、`tf.placeholder`で入力用の変数を作ります。そして演算 `tf.add`（`+`）や `tf.multiply`（`*`）を使って $y=ax+b$ という式を組み立てました。

![ax+b.png](https://qiita-image-store.s3.amazonaws.com/0/26821/2e7a4b80-b0f3-bf0e-e4b3-a2ec49bbb44a.png)
組み立てた式はOperationの集合としてGraphというオブジェクトに追加されます。Graphは明示的に作らなくてもデフォルトで1つ用意されています。`tf.add`等を呼び出す度にGraphにOperationが追加されてしまうので、念のため、今の状態を`./linear/model.pb.bytes (txt)`に一旦保存しましょう。

```python
tf.train.write_graph(tf.get_default_graph(), './linear', 'model.pb.bytes', as_text=False)
tf.train.write_graph(tf.get_default_graph(), './linear', 'model.pb.txt', as_text=True)
```

学習はまだですが、初期値 $a=-1,b=2$ を指定しているので、例えば$x=1$を渡したら$y=1$が返って来るはずです。それを試したのが以下です。

```python
sess = tf.Session()
sess.run(init)
sess.run(y, {x: 1.0}) #1.0が返ってくるはず！
```

Graphはモデルの定義であるのに対し、Sessionはその状態であり、Variableの値を持っています。`sess.run`に評価（実行）したいものを渡すことで、Variableを初期化したり、入力を渡して式を評価したり、学習を進めたりすることが出来ます。

# 保存したモデルをUnityで使う
保存したモデルがUnityで動くことを確かめます。まず、Cloud Shellの`ファイルをダウンロード`で`linear/model.pb.bytes`をダウンロードしてUnityのプロジェクトに置きます。

![スクリーンショット 2017-12-17 21.46.31.png](https://qiita-image-store.s3.amazonaws.com/0/26821/3d660d28-737c-3e5a-8e1d-6dd58d7250ba.png)

で、それを以下のMonoBehaviourにセットして実行するとPython版と同様に$x=1$を渡して$y=1$が返って来ることが確かめられます。

```csharp
using UnityEngine;
using TensorFlow;

public class ModelImportExample : MonoBehaviour
{
    // Inspector で model.pb.bytes をセットしてください
    public TextAsset model;

    void Start()
    {
        TFGraph graph = new TFGraph();
        graph.Import(model.bytes);
        TFSession sess = new TFSession(graph);

        // Python の sess.run(init) と同じ
        sess.Run(new TFOutput[0], new TFTensor[0],
            new TFOutput[0], new TFOperation[]{ graph ["init"] });

        // Python の sess.run(y, {x: 1.0}) と同じ
        TFTensor[] results = sess.GetRunner()
            .AddInput(graph ["x"] [0], new TFTensor(1f))
            .Fetch(graph ["y"] [0])
            .Run();
        print(results [0]); // 1が出力されるはず！
    }
}
```
`sess.GetRunner`は`sess.Run`の引数をメソッドチェーンで構築するためのビルダーみたいなものです。

# モデルを学習して保存
まず先程のモデルを作り直します。

```python
import tensorflow as tf
a = tf.Variable(-1.0, name='a')
b = tf.Variable(2.0, name='b')
init = tf.variables_initializer([a, b], name='init')
x = tf.placeholder(tf.float32, name='x')
y = tf.add(a * x, b, name='y')
```

次に $y_{\mathit{goal}}$ という入力を用意して、$y$ が $y_{\mathit{goal}}$ に近くなるように、すなわち $\left|y_{\mathit{goal}}-y\right|$ が小さくなるように学習するOperator`minimize`を作ります。`minimize`を`sess.run`する度に $a,b$ が変化して学習が進みます。~~最小二乗法なら解析的に求まるのでは？ということはここでは忘れてください。~~

```python
y_goal = tf.placeholder(y.dtype, name='y_goal')
optimizer = tf.train.GradientDescentOptimizer(0.01)
minimize = optimizer.minimize(tf.abs(y_goal - y), name='minimize')
```
別の章で使うのでこの状態のGraphも一旦保存してください。

```python
tf.train.write_graph(tf.get_default_graph(), './linear', 'training.pb.bytes', as_text=False)
tf.train.write_graph(tf.get_default_graph(), './linear', 'training.pb.txt', as_text=True)
```

次に学習データを用意します。今回は練習なので意味のない線形っぽいデータを手で作ります。

```python
train_x = [0, 1, 2, 3, 4, 5]
train_y = [6, 4, 5, 3, 1, 0]
```
入力 $x=0$ に対し $y=6$ を出力、入力 $x=1$ に対し $y=4$ を出力、以下略、という意味です。試しに学習前のモデルに入力すると`train_y`とは全然違う値が返ることがわかります。

```python
sess = tf.Session()
sess.run(init)
sess.run(y, feed_dict={x: train_x})
# [2., 1., 0., -1., -2., -3.]
```
`train_x`、`train_y`を入力として`minimize`を何度も実行することで学習が出来ます。

```python
for _ in range(10000):
    sess.run(minimize, feed_dict={x: train_x, y_goal: train_y})
```
学習後のモデルでは`train_y`に少しは近づいたことがわかります。線形モデルに線形ではないデータを入れたのでもちろんぴったりにはなりません。

```python
sess.run(y, feed_dict={x: train_x})
# [5.99999619, 4.7599988, 3.52000141, 2.28000402, 1.04000664, -0.19999075]
```
学習後のモデルの状態、つまりSessionが持っている $a,b$ の値を、以下のように`.ckpt`ファイルに保存します。ckptはcheckpointの意味で、別にこの拡張子じゃなくても良かったはず。

```python
saver = tf.train.Saver()
saver.save(sess, 'linear/trained.ckpt')
```
最後に、TensorFlowSharpがckptに対応していないらしいので、以下のコマンドで、`model.pb.txt`のVariableに`trained.ckpt`を埋め込んで定数化した`trained.pb.bytes`を出力します。

```sh
python $(python -c "import tensorflow as tf; print tf.__path__[0]")/python/tools/freeze_graph.py \
--input_grap linear/model.pb.txt \
--input_checkpoint linear/trained.ckpt \
--output_graph linear/trained.pb.bytes \
--output_node_names y
```

# 学習したモデルをUnityで使う
学習後のモデル`linear/trained.pb.bytes`は、以下のコードで動かすことが出来ます。変数を定数化したので`init`を呼ぶ必要がなくなりました。

```csharp
using UnityEngine;
using TensorFlow;

public class TrainedModelImportExample : MonoBehaviour
{
    // Inspector で trained.pb.bytes をセットしてください
    public TextAsset model;

    void Start()
    {
        TFGraph graph = new TFGraph();
        graph.Import(model.bytes);
        TFSession sess = new TFSession(graph);
        float[] x = new float[] { 0f, 1f, 2f, 3f, 4f, 5f };
        float[] y = sess.GetRunner()
            .AddInput(graph ["x"] [0], x)
            .Fetch(graph ["y"] [0])
            .Run() [0]
            .GetValue() as float[];
        foreach (float value in y) print(value);
    }
}
```
Python側で見た値と同じ値が見られるはずです。

# 学習をUnity側でやりたい場合
途中で保存した`linear/training.pb.bytes`を読み込んで`minimize`を`Run`すれば出来ます。

```csharp
using UnityEngine;
using TensorFlow;

public class TrainingModelImportExample : MonoBehaviour
{
    // Inspector で trainig.pb.bytes をセットしてください
    public TextAsset model;

    void Start()
    {
        TFGraph graph = new TFGraph();
        graph.Import(model.bytes);
        TFSession sess = new TFSession(graph);
        sess.Run(new TFOutput[0], new TFTensor[0],
            new TFOutput[0], new TFOperation[]{ graph ["init"] });

        // 学習
        float[] trainX = new float[] { 0f, 1f, 2f, 3f, 4f, 5f };
        float[] trainY = new float[] { 6f, 4f, 5f, 3f, 1f, 0f };
        for (int i = 0; i < 10000; ++i)
        {
            sess.Run(
                new TFOutput[]{ graph ["x"][0], graph ["y_goal"][0] },
                new TFTensor[]{ trainX, trainY },
                new TFOutput[0],
                new TFOperation[]{ graph ["minimize"] });
        }

        // 学習結果
        float[] y = sess.GetRunner()
            .AddInput(graph ["x"] [0], trainX)
            .Fetch(graph ["y"] [0])
            .Run() [0]
            .GetValue() as float[];
        foreach (float value in y) print(value);
    }
}
```

# What's next?
学習済みモデルが https://github.com/tensorflow/models にたくさんあるので、必要な形式に出力して使ってみる、というのをやってみたいです。

# おまけ：write_graphしたものをPythonで読む
```python
import tensorflow as tf
with open('linear/model.pb.bytes', 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())
    tf.import_graph_def(graph_def, name='')

graph = tf.get_default_graph()
init = graph.get_operation_by_name('init')
x = graph.get_tensor_by_name('x:0')
# あるいは x = graph.get_operation_by_name('x').outputs[0]
```
これだけだとVariableがVariable型として取り出せないようなので不便でした。ただ、Graphの保存に関するAPIは[tf.saved_model](https://www.tensorflow.org/api_docs/python/tf/saved_model)など他にもあるようです。