Tensorflow for Javaの概要

  • link:/category/programming/ [プログラミング]

1. 概要

https://www.tensorflow.org/[TensorFlow]は、データフロープログラミング用のオープンソースライブラリです。 これはもともとGoogleが開発したもので、さまざまなプラットフォームで利用できます。 TensorFlowは単一のコアで動作しますが、*複数のCPU、GPU、またはTPUを利用することで簡単にメリットを得ることができます*。
このチュートリアルでは、TensorFlowの基本とJavaでのTensorFlowの使用方法を説明します。 TensorFlow Java APIは実験的なAPIであり、安定性の保証の対象外であることに注意してください。 チュートリアルの後半で、TensorFlow Java APIを使用するための考えられるユースケースについて説明します。

2. 基本

TensorFlowの計算は、基本的に*グラフとセッション*という2つの基本概念を中心に展開します。 チュートリアルの残りの部分を実行するために必要な背景を得るために、それらをすばやく確認しましょう。

2.1. TensorFlowグラフ

まず、TensorFlowプログラムの基本的な構成要素を理解しましょう。 *計算は、TensorFlow *のグラフとして表されます。 グラフは通常、操作とデータの非循環有向グラフであり、たとえば次のとおりです。
link:/uploads/TensorFlow-Graph-1-1-100x57.jpg%20100w []
上の図は、次の方程式の計算グラフを表しています。
f(x, y) = z = a*x + b*y
TensorFlow計算グラフは、2つの要素で構成されます。
  1. * Tensor:これらは、TensorFlowのデータのコアユニットです。*
    計算グラフのエッジとして表され、グラフを通るデータの流れを表します。 テンソルは、任意の次元の形状を持つことができます。 テンソルの次元数は通常、ランクと呼ばれます。 したがって、スカラーはランク0のテンソル、ベクトルはランク1のテンソル、行列はランク2のテンソルなどとなります。

  2. 操作:これらは計算グラフのノードです
    テンソルが操作に送られるときに発生する可能性のある多種多様な計算に。 多くの場合、計算グラフの操作から生じるテンソルも生成されます。

2.2. TensorFlowセッション

現在、TensorFlowグラフは、実際には値を持たない計算の単なる図式です。 そのような*グラフは、評価されるグラフのテンソルのTensorFlowセッションと呼ばれるセッション内で実行する必要があります*。 セッションは、グラフから入力パラメーターとして評価するために、多数のテンソルを取ることができます。 次に、グラフを逆方向に実行し、それらのテンソルを評価するために必要なすべてのノードを実行します。
これで、これを取得してJava APIに適用する準備ができました!

3. Mavenセットアップ

JavaでTensorFlowグラフを作成して実行するために、簡単なMavenプロジェクトをセットアップします。 必要なのはhttps://search.maven.org/search?q=g:org.tensorflow%20AND%20a:tensorflow%20AND%20v:1.12.0[_tensorflow_dependency]:
<dependency>
    <groupId>org.tensorflow</groupId>
    <artifactId>tensorflow</artifactId>
    <version>1.12.0</version>
</dependency>

4. グラフを作成する

TensorFlow Java APIを使用して、前のセクションで説明したグラフを作成してみましょう。 より正確には、このチュートリアルでは、TensorFlow Java APIを使用して、次の式で表される関数を解決します。
z = 3*x + 2*y
最初のステップは、グラフを宣言して初期化することです:
Graph graph = new Graph()
ここで、必要なすべての操作を定義する必要があります。 TensorFlowの操作は、ゼロ以上のテンソルを消費して生成することを忘れないでください。 さらに、グラフ内のすべてのノードは、定数とプレースホルダーを含む操作です。 これは直観に反するように思えるかもしれませんが、しばらくは耐えてください!
クラス_Graph_には、_opBuilder()_と呼ばれる汎用関数があり、TensorFlowであらゆる種類の操作を構築します。

4.1. 定数の定義

まず、上のグラフで定数操作を定義しましょう。 *定数演算にはその値のテンソル*が必要であることに注意してください:
Operation a = graph.opBuilder("Const", "x")
  .setAttr("dtype", DataType.fromClass(Double.class))
  .setAttr("value", Tensor.<Double>create(3.0, Double.class))
  .build();
Operation b = graph.opBuilder("Const", "y")
  .setAttr("dtype", DataType.fromClass(Double.class))
  .setAttr("value", Tensor.<Double>create(2.0, Double.class))
  .build();
ここでは、定数タイプの_Operation_を定義し、_Tensor_に_Double_値2.0および3.0を入力します。 そもそも圧倒されるように思えるかもしれませんが、それは今のところJava APIでの方法です。 これらの構成は、Pythonのような言語でははるかに簡潔です。

4.2. プレースホルダーの定義

定数に値を提供する必要がありますが、*プレースホルダーは定義時に値を必要としません*。 グラフがセッション内で実行されるときに、プレースホルダーへの値を提供する必要があります。 この部分については、チュートリアルの後半で説明します。
とりあえず、プレースホルダーを定義する方法を見てみましょう。
Operation x = graph.opBuilder("Placeholder", "x")
  .setAttr("dtype", DataType.fromClass(Double.class))
  .build();
Operation y = graph.opBuilder("Placeholder", "y")
  .setAttr("dtype", DataType.fromClass(Double.class))
  .build();
プレースホルダーに値を指定する必要はありませんでした。 これらの値は、実行時に_Tensors_として供給されます。

4.3. 関数を定義する

最後に、結果を得るために、方程式の数学演算、つまり乗算と加算を定義する必要があります。
これらもTensorFlowの__Operation__sであり、_Graph.opBuilder()_は再び便利です。
Operation ax = graph.opBuilder("Mul", "ax")
  .addInput(a.output(0))
  .addInput(x.output(0))
  .build();
Operation by = graph.opBuilder("Mul", "by")
  .addInput(b.output(0))
  .addInput(y.output(0))
  .build();
Operation z = graph.opBuilder("Add", "z")
  .addInput(ax.output(0))
  .addInput(by.output(0))
  .build();
ここでは、_Operation_を定義しました。2つは入力を乗算し、最後の1つは中間結果を合計するためのものです。 ここでの操作は、以前の操作の出力にすぎないテンソルを受け取ることに注意してください。
インデックス「0」を使用して、_Operation_から出力_Tensor_を取得していることに注意してください。 前に説明したように、* Operation_は1つ以上の_Tensor_ *をもたらす可能性があるため、そのハンドルを取得する際に、インデックスに言及する必要があります。 オペレーションは1つの_Tensor_のみを返すことがわかっているため、「0」は問題なく機能します。

5. グラフの視覚化

グラフのサイズが大きくなるにつれて、タブを保持することは困難です。 これにより、何らかの方法で視覚化することが重要になります。 以前に作成した小さなグラフのような手描きをいつでも作成できますが、大きなグラフには実用的ではありません。 * TensorFlowは、これを容易にするためにTensorBoardと呼ばれるユーティリティを提供します*。
残念ながら、Java APIには、TensorBoardによって消費されるイベントファイルを生成する機能がありません。 しかし、PythonでAPIを使用すると、次のようなイベントファイルを生成できます。
writer = tf.summary.FileWriter('.')
......
writer.add_graph(tf.get_default_graph())
writer.flush()
これがJavaのコンテキストで意味をなさない場合は気にしないでください。これは完全を期すためにここに追加されたものであり、チュートリアルの残りの部分を続ける必要はありません。
TensorBoardでイベントファイルを読み込んで視覚化できるようになりました。
tensorboard --logdir .
link:/uploads/Screenshot-2019-03-25-at-16.55.39.png%201030w []
TensorBoardはTensorFlowインストールの一部として提供されます。
これと以前に手動で描かれたグラフの類似性に注意してください!

6. セッションでの作業

TensorFlow Java APIの単純な方程式の計算グラフを作成しました。 しかし、どのように実行しますか? それに取り組む前に、この時点で作成したばかりの_Graph_の状態を見てみましょう。 最終的な_Operation_の出力を印刷しようとすると、次のようになります。
System.out.println(z.output(0));
これは次のようになります。
<Add 'z:0' shape=<unknown> dtype=DOUBLE>
これは期待したものではありません! しかし、前に議論したことを思い出せば、これは実際に意味をなします。 *定義した_Graph_はまだ実行されていないため、そのテンソルは実際には実際の値を保持しません。*上記の出力は、これが_Double_型の_Tensor_であることを示しています。
_Graph_を実行する_Session_を定義しましょう:
Session sess = new Session(graph)
最後に、グラフを実行して、期待していた出力を取得する準備ができました。
Tensor<Double> tensor = sess.runner().fetch("z")
  .feed("x", Tensor.<Double>create(3.0, Double.class))
  .feed("y", Tensor.<Double>create(6.0, Double.class))
  .run().get(0).expect(Double.class);
System.out.println(tensor.doubleValue());
ここで何をしているのでしょうか? それはかなり直感的でなければなりません:
  • _Session_から_Runner_を取得します

  • 名前「z」でフェッチする_Operation_を定義します

  • プレースホルダ「x」および「y」のテンソルをフィードする

  • _Session_で_Graph_を実行します

    そして今、スカラー出力が表示されます。
21.0
これは私たちが期待したことですよね!

7. Java APIのユースケース

この時点で、TensorFlowは基本的な操作を実行するためのやり過ぎのように聞こえるかもしれません。 しかし、もちろん、* TensorFlowはこれよりもはるかに大きなグラフを実行することを意図しています*。
さらに、実際のモデルで扱うテンソルのサイズとランクははるかに大きくなります*。 これらは、TensorFlowが実際に使用される実際の機械学習モデルです。
TensorFlowのコアAPIを使用した作業は、グラフのサイズが大きくなるにつれて非常に面倒になる可能性があることを確認するのは難しくありません。 この目的のために、* TensorFlowはhttps://www.tensorflow.org/guide/keras[Keras]などの高レベルAPIを提供して、複雑なモデルで動作します*。 残念ながら、JavaでのKerasの公式サポートはまだほとんどありません。
ただし、TensorFlowで直接、またはKerasなどの高レベルAPIを使用して、Pythonを使用して複雑なモデルを定義およびトレーニングすることができます。 その後、トレーニング済みのモデルをエクスポートし、TensorFlow Java APIを使用してJavaで使用できます。
さて、なぜそんなことをしたいのでしょうか? これは、Javaで実行されている既存のクライアントで機械学習対応機能を使用する場合に特に役立ちます。 たとえば、Androidデバイスのユーザー画像にキャプションを推奨します。 それにもかかわらず、機械学習モデルの出力に関心があるが、必ずしもJavaでそのモデルを作成およびトレーニングしたくない場合がいくつかあります。
これは、TensorFlow Java APIがその使用の大部分を見つける場所です。 次のセクションでは、これをどのように達成できるかを説明します。

8. 保存したモデルを使用する

これで、TensorFlowのモデルをファイルシステムに保存し、完全に異なる言語とプラットフォームにロードする方法を理解できます。 * TensorFlowは、https://developers.google.com/protocol-buffers/ [Protocol Buffer]と呼ばれる言語およびプラットフォームに依存しない構造でモデルファイルを生成するAPIを提供します。*

8.1. モデルをファイルシステムに保存する

Pythonで以前に作成した同じグラフを定義し、それをファイルシステムに保存することから始めます。
Pythonでこれができることを見てみましょう:
import tensorflow as tf
graph = tf.Graph()
builder = tf.saved_model.builder.SavedModelBuilder('./model')
with graph.as_default():
  a = tf.constant(2, name='a')
  b = tf.constant(3, name='b')
  x = tf.placeholder(tf.int32, name='x')
  y = tf.placeholder(tf.int32, name='y')
  z = tf.math.add(a*x, b*y, name='z')
  sess = tf.Session()
  sess.run(z, feed_dict = {x: 2, y: 3})
  builder.add_meta_graph_and_variables(sess, [tf.saved_model.tag_constants.SERVING])
  builder.save()
Javaでのこのチュートリアルの焦点として、「saved_model.pb」というファイルを生成するという事実を除いて、Pythonでこのコードの詳細にあまり注意を払わないでください。 Javaと比較して同様のグラフを定義する際に簡潔さを渡すことに注意してください!

8.2. ファイルシステムからのモデルの読み込み

ここで、「saved_model.pb」をJavaにロードします。 Java TensorFlow APIには、保存されたモデルを操作するための_SavedModelBundle_があります。
SavedModelBundle model = SavedModelBundle.load("./model", "serve");
Tensor<Integer> tensor = model.session().runner().fetch("z")
  .feed("x", Tensor.<Integer>create(3, Integer.class))
  .feed("y", Tensor.<Integer>create(3, Integer.class))
  .run().get(0).expect(Integer.class);
System.out.println(tensor.intValue());
上記のコードが何をしているのかを理解することは、今ではかなり直感的であるはずです。 プロトコルバッファからモデルグラフを単にロードし、その中のセッションを利用可能にします。 そこから先は、ローカルで定義されたグラフの場合と同じように、このグラフでほとんど何でもできます。

9. 結論

要約すると、このチュートリアルでは、TensorFlow計算グラフに関連する基本的な概念について説明しました。 TensorFlow Java APIを使用してこのようなグラフを作成および実行する方法を見ました。 次に、TensorFlowに関するJava APIのユースケースについて説明しました。
その過程で、TensorBoardを使用してグラフを視覚化し、プロトコルバッファーを使用してモデルを保存および再読み込みする方法も理解しました。
いつものように、例のコードは利用可能ですhttps://github.com/eugenp/tutorials/tree/master/tensorflow-java[over on GitHub]。