TensorFlow Eager Executionを試してみた



今年2月頃、TensorFlow 1.5が公開され、TensorFlowをDefine by Runで実行できる「Eager Execution for TensorFlow」が追加されました。

TensorFlowといえば、Define and Runが特徴的ですが、その特性上、デバッグがかなりやり辛い印象でした。

ちなみにDefine and/by Runについて整理しておきますと、

  • Define by Run: 計算グラフ(ニューラルネットの構造)の構築をデータを流しながら行う
  • Define and Run: 計算グラフを構築してから、そこにデータを流していく

といったプログラミングスタイルで、ChainerやPyTorchがDefine by Run型のライブラリとなります。

今回はTensorFlowにDefine by Runモードが追加されると聞いて少し気になっていましたので、実際に動かしてみて操作感を確認してみました。

TensorFlow Eager Executionについて

2018年1月26日(米国時間)、Googleがオープンソース機械学習ライブラリの最新版「TensorFlow 1.5」を公開しました。

その時に特に注目された変更点としては、以下の機能が挙げられています。

  • Eager Execution for TensorFlow
  • TensorFlow Lite
  • GPUアクセラレーション対応の強化

今回の本題であるEager Execution for TensorFlowは、Define by Run型のプログラミングスタイルを可能にするインタフェースで、これを有効にすると、PythonからTensorFlow演算を呼び出してすぐに実行できるようになります。

ついでに他の項目も見ていきますと、TensorFlow Liteは、モバイルや組み込みデバイス向けのTensorFlowの軽量版で、学習済みのTensorFlowモデルを「.tflite」ファイルに変換しモバイルデバイスを使って低レイテンシで実行できるようになります。

なんかこれはこれで、気になりますね笑

GPUアクセラレーション対応の強化に関しては、新たに「CUDA 9」と「cuDNN 7」をサポートといったところです。

TensorFlow Eager Executionですが、GoogleはEager Execution for TensorFlowのメリットとして、下記を挙げています。

  1. 実行時エラーの即時確認と, Pythonツールと統合された迅速なデバッグ
  2. 使いやすいPython制御フローを利用した動的モデルのサポート
  3. カスタムおよび高次勾配の強力なサポート
  4. ほとんどのTensorFlow演算が利用可能

1は冒頭でも述べたように、デバッグをする際にも、デバッグするためのグラフを作って値を流すみたいなことをわざわざやらないといけません。

動的に動かすことができるようになるため、2はPythonのif分などで計算のグラフを制御できることを意味します。

3についてはよく分かりません。

ただし、今回触ってみたところ、勾配計算の部分が通常モードとだいぶ異なっているようで、これについてはあまり深掘り出来ていないのですが、便利になった点が何かしらあるのかもしれません。

4は tf.matmul などがすぐ実行できるよってことですかね。

通常モードのTensorFlow

アイリスのデータで分類問題を学習させてみます。

比較のため、まずは通常モードでアイリスを解いてみます。

特に詳しいコメントはしませんので、一気にコードを載せます。

TensorFlowは機械学習を書く時は、低レベルAPI(生TensorFlow)の書き方と高レベルAPI(keras、layersなどを使う)の書き方があります。

使い分けとしては、

  • 低レベルAPI: 機械学習や深層学習のアルゴリズムを自分で実装したい人向け
  • 高レベルAPI: 機械学習や深層学習を使ってみたい人向け

に分かれると思います。

私の場合は生TensorFlowの書き方に慣れていましたので、上記のコードは低レベルAPIの書き方になると思います。

EagerモードのTensorFlow

次にEagerモードでアイリスを学習させてみます。

Eagerモードは、実行する時に下記のコマンドで初期化します。

ちなみに、これを先程のような通常モードのTensorFlowを実行した後などに行おうとすると、

といった感じにエラーとなってしまいます。

どうやら実行カーネル内での通常モードとEagerモードの共存は出来ないようで、Jupyterの場合は、この場合は再起動しなければなりません。

ちなみに逆も然りで、上記初期化が行われた後に、先程の通常モードのTensorFlowのコードを実行しようとすると、

とエラーを吐きます。

同じTensorFlowなのに面白いですね。

さて、Eagerモードで学習させてみます。

実は公式のEagerモードのチュートリアルはアイリスの問題で書かれていますので、まずはサンプルコードをまるまる実行してみます。

公式: https://www.tensorflow.org/get_started/eager

TensorFlowの便利な関数がめっちゃ使われていて、少し分かりづらいです。

少し中身を見てみますが、

は直接TensorFlowからファイルポイントを取得できるようです。

これについて、

で直接CSVファイルの加工も行って、バッチサイズまで決めているようです。

Chainerのイテレータクラスあたりまでの機能を保持しているということでしょうか。

で、モデル、loss関数、最適化を定義しています。

モデルは高レベルAPIの書き方をしていて、loss関数は特に通常モードと変わりありませんが、勾配計算がちょっと見慣れない形になっています。

そして、以下のように学習となっています。

特に微分計算のところがまた特殊な形であることと、あと評価の結果を保持するためのクラスが用意されているようです。

train_dataset でループすると、そのままバッチサイズごとに取り出せる模様。

tf.data は色々なファイルやデータベース等、接続先が多種多様に用意されていれば、Pythonへのデータの入力からTensorFlowの世界に閉じることができそうですので、これは別途調べてみたい要素でした。

何はともあれ、このサンプルコードのままじゃ、処理がよく分かりません。

なので、一旦、通常モードの時と同様に、scikit-learnから落としてきたデータ(numpy)からスタートするように書き直してみました。

だいぶ理解できてきました。

確かに、Eagerモードとなればnumpyを触る必要もあまりありませんので、最初にnumpyをTensor型にしています。

PyTorchでいえばtorch型になると考えれば良さそうです。

モデル、微分計算は変わりありませんが、loss関数は、

と、以前のようにone-hot型で評価するようにしました。

結局どちらでも良いのですが、tf.nn.softmax_cross_entropy_with_logits_v2 は以前のようにラベルはone-hot型の tf.float32tf.losses.sparse_softmax_cross_entropy はChainerなどのように、ラベルをそのまま tf.int32 で評価できるようです。

_with_logits は、softmaxはこちらで取るので、順伝播で出力されたラベル次元数のベクトルをそのまま渡してねという意味です。

学習ループに関しては、

でバッチを回しています。

Tensor型はnumpy型のようには扱えませんので、そのために変更しています。

numpy型だと、取得したいインデックスを配列にして複数同時にアクセスすることができますが、Tensor型ではそれを tf.gater 関数で行います。

一旦、評価は以前と同じものにしました。

metrics というクラスが用意されていることは分かりましたが、慣れてから勉強します。

と、このようにして、学習させることができましたし、なんとなく理解できてきました。

やはり通常モードと大きく違う点としては、微分計算のところがだいぶ様子が異なっています。(ひょっとしたら、これも同じように書き直せるのかもしれないですが)

どうやらGradientTapeクラスが、dloss/dw の微分計算を担当するようで、tape = tfe.GradientTape() として tape(loss, w) とするようです。

なるほど、ここは慣れは必要ですね。

また、上記は高レベルAPIの書き方なので、ちょっと生TensorFlowの書き方に寄せてみました。

こんな書き方をして良いのか分かりませんが、動いたからいいや。

変数は tf.Variable ではなく tfe.Variable を使います。

とりあえず、これで自分で変数までも定義させた上で、Eagerモードで実行することができました。

ちなみに、上記の高レベルAPIの書き方はkerasですが、kerasじゃない書き方( tf.layers )をさせる場合は、下記のように tfe.Network クラスが便利そうです。

このように、高レベルAPIの使い方+Eagerで書いていると、やはり高レベルなライブラリでDefine by RunのChainerやPyTorchになんとなくコードの構成が似てきます。

とりあえず、これでEagerモードの使い方が分かりましたし、動的なグラフをTensorFlowで学習させることができるようになりました。

例えば、何の意味もないですが、以下のように無駄にもう一つ順伝播を通ったり通らなかったりみたいなネットワークも、Pythonのif文で学習させることができます。

あっちいったりこっちいったりするネットワークなので、学習が安定しません。面白い。

この程度なら別にプレースホルダーに確率値を供給することで通常モードでも可能ですが、データによってとか、バッチごとに異なるネットワークを通したい時には使えるということになりそうです。

感想

TensorFlow Eager Execution使ってみました。

といっても今回はCPUのみで、GPUにする場合はどうなるかなどは、また別の調べてみます。

TensorFlowのことなので、おそらくCPUとGPUの行き来は楽にできているのではないかと思います。

そう考えると、Eagerモードにすることで、結構numpyライクに数値計算が書けるようになりましたので、これをGPUで行えるようになると考えると、機械学習に限らず、一般的な計算でも便利になる場面があるのかもしれません。

深層学習のライブラリとしては、やっぱり他と突出しているような要素は見つかりませんでしたし、結局ChainerでもPyTorchでもTensorFlowでもTensorFlow Eagerでも、使いやすいものを選べばいいやって感じです笑

あと、本題とはあまり関係ないですが、TensorFlowは低レベルAPI、高レベルAPI、そしてEagerも追加されてと、TensorFlowの中だけでも複雑になってきましたので、高レベルAPIで使うか、低レベルAPIで使うかを先に決めておかないと、TensorFlow死ねますね。



 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です