Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

TL;DR

  • UnityがTensorFlowSharpを.unitypackageで配布しているから今すぐ使える!
  • TensorFlowはGCPのCloud Shellにインストール済みだから今すぐ使える!
  • Python側でモデルを作って、Unityで動かしてみよう。
  • 注:ml-agentsの話ではありません。ml-agentsのラッパーを介さずにTensorFlowを使う話なので、先にml-agentsについて(参考リンク)知ったほうが良いかもしれません。

想定読者

  • 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 をダウンロードして展開。
  5. 以下が実行できればOK。
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を有効にする。
  2. Cloud Shellを開く。
  3. iPython(普通のPythonでも可)で以下を実行できればOK。
$ 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で実行しましょう。

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
組み立てた式はOperationの集合としてGraphというオブジェクトに追加されます。Graphは明示的に作らなくてもデフォルトで1つ用意されています。tf.add等を呼び出す度にGraphにOperationが追加されてしまうので、念のため、今の状態を./linear/model.pb.bytes (txt)に一旦保存しましょう。

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$が返って来るはずです。それを試したのが以下です。

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

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

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.GetRunnersess.Runの引数をメソッドチェーンで構築するためのビルダーみたいなものです。

モデルを学習して保存

まず先程のモデルを作り直します。

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|$ が小さくなるように学習するOperatorminimizeを作ります。minimizesess.runする度に $a,b$ が変化して学習が進みます。最小二乗法なら解析的に求まるのでは?ということはここでは忘れてください。

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も一旦保存してください。

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)

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

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とは全然違う値が返ることがわかります。

sess = tf.Session()
sess.run(init)
sess.run(y, feed_dict={x: train_x})
# [2., 1., 0., -1., -2., -3.]

train_xtrain_yを入力としてminimizeを何度も実行することで学習が出来ます。

for _ in range(10000):
    sess.run(minimize, feed_dict={x: train_x, y_goal: train_y})

学習後のモデルではtrain_yに少しは近づいたことがわかります。線形モデルに線形ではないデータを入れたのでもちろんぴったりにはなりません。

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の意味で、別にこの拡張子じゃなくても良かったはず。

saver = tf.train.Saver()
saver.save(sess, 'linear/trained.ckpt')

最後に、TensorFlowSharpがckptに対応していないらしいので、以下のコマンドで、model.pb.txtのVariableにtrained.ckptを埋め込んで定数化したtrained.pb.bytesを出力します。

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を呼ぶ必要がなくなりました。

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を読み込んでminimizeRunすれば出来ます。

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で読む

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など他にもあるようです。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away