TensorFlow : Mobile : モバイル配備のためのモデルを準備する (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 12/17/2017
* 本ページは、TensorFlow の本家サイトの Mobile – Preparing models for mobile deployment を翻訳した上で
適宜、補足説明したものです:
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
訓練中にモデル情報をストアするための要件は、モバイルアプリケーションの一部としてそれをリリースすることを望む時には非常に異なるものになります。このセクションでは、訓練モデルから製品でリリース可能なものに変換することに伴うツールをカバーします。
セーブされた異なるファイルフォーマットの総てはどうなっているのか?
TensorFlow がグラフをセーブ可能な異なる方法総てによって貴方は自身が非常に混乱していることに気がつくかもしれません。手助けするために、幾つかの異なるコンポーネントとそれらが何のために使用されるのかの要約がここにあります。オブジェクトの大部分は protocol buffers で定義されてシリアライズされます :
- NodeDef: モデルの単一の演算を定義します。それは、一意な名前、それが (それらから) 入力を引き出す他のノードのリスト、それが実装する演算タイプ (例えば Add, あるいは Mul)、そしてその演算を制御するために必要な任意の属性を持ちます。これは TensorFlow のための計算の基本的なユニットで、そしてこれらのノードのネットワークを通して反復し、個々の一つを順番に適用することにより総てのワークは遂行されます。知っておくに値する一つの特定の演算タイプは Const です、何故ならばこれは定数 (= constant) についての情報を保持するからです。これは単一の、スカラー数か文字列かもしれませんが、多次元テンソル配列全体を保持することもできます。Const のための値は NodeDef 内部にストアされ、そのため巨大な定数はシリアライズされた時に多くの場所を取ります。
- Checkpoint : モデルのために値を保持するための他の一つの方法は Variable ops を使用することです。Const ops とは異なり、これらはそれらの内容を NodeDef の一部としては保持しません、そのためそれらは GraphDef ファイル内には非常に小さなスペースしか取りません。代わりに計算が実行されている間それらの値は RAM 内で保持され、そしてチェックポイント・ファイルとしてディスクに定期的にセーブアウトされます。これは典型的にはニューラルネットワークが訓練されて重みが更新される時に発生します、そのためそれはタイムクリティカルな演算です。そしてそれは多くのワーカーに渡り分散作法で発生するかもしれませんので、ファイルフォーマットは高速で柔軟でなければなりません。それらはチェックポイント内に何が含まれているかを記述するメタデータ・ファイルと一緒に、複数のチェックポイント・ファイルにストアされます。API でチェックポイントを参照するときは (例えばコマンドライン引数としてファイル名を渡すとき)、関連するファイルのセットのために共通のプレフィックスを使用するでしょう。これらのファイルを持つ場合には :
/tmp/model/model-chkpt-1000.data-00000-of-00002 /tmp/model/model-chkpt-1000.data-00001-of-00002 /tmp/model/model-chkpt-1000.index /tmp/model/model-chkpt-1000.meta
それらを /tmp/model/chkpt-1000 として参照するでしょう。
- GraphDef: は NodeDef のリストを持ち、これらは実行するために計算グラフを一緒に定義します。訓練中、これらのノードの幾つかは Variable ですので、重みも含めて、実行できる完全なグラフを持つことを望むのであれば、チェックポイントからそれらの値を引っ張るにはリストア演算を呼び出す必要があるでしょう。チェックポイント・ローディングは訓練の要請の総てを扱うために柔軟でなければなりません、これはモバイルと組み込みデバイス、特に iOS のような適切なファイルシステムがないものの上で実装することはトリッキーになるかもしれません。これは freeze_graph.py スクリプトが役立つところです。上述したように、Const ops はそれらの値を NodeDef の一部としてストアしますので、総ての Variable 重みが Const ノードに変換されるのであれば、単一の GraphDef ファイルが必要なだけです。graph の freezing はチェックポイントをロードするプロセスを扱い、そして総ての Const を Variable に変換します。それから単一の呼び出しで結果としてのファイルをロードできます、チェックポイントから変数値をレストアしなければならないことなしにです。GraphDef ファイルのために気をつけなければならない一つのことは時々それらは簡単な精査のためにテキストフォーマットでストアされることです。これらのバージョンは通常 ‘.pbtxt’ ファイル名サフィックスを持ちます、一方で倍なるファイルは ‘.pb’ で終わります。
- FunctionDefLibrary: これは GraphDef 内に現れる、それぞれが (それらの) 入力と出力ノードの情報を持つサブグラフの効果的なセットです。そして各サブグラフはメイングラフの op として使用可能で、他の言語で関数がコードをどのようにカプセル化するかと同様の方法で、異なるノード (群) の簡単なインスタンス化を可能にします。
- MetaGraphDef: プレーンな GraphDef は計算ネットワークについての情報を持つだけで、モデルやそれがどのように使用されるかについてのどのような特別な情報も持っていません。MegaGraphDef はモデルの計算パートを定義する GraphDef を含み、しかしまた ‘signatures’ のような情報も含みます。これらはどの入力と出力をモデルと一緒に呼び出すことを望んで良いか、どのようにそしてどこでチェックポイントファイルがセーブされたか、そして使いやすさのために ops を一緒にグループ化する便利なタグについての提案です。
- SavedModel: 変数チェックポイントの共通セットに依存するグラフの異なるバージョンを持つことを望むことは一般的です。例えば、同じグラフの GPU と CPU バージョンを必要とし、しかし両者について同じ重みを保持するかもしれません。またモデルの一部として (ラベル名のような) 幾つかの特別なファイルを必要とするかもしれません。SavedModel フォーマットはこれらの要請に、変数を複製することなしに同じグラフの複数のバージョンをセーブさせて、そしてまた同じバンドルにアセットファイルをストアすることによって対処します。その裏では、それは特別なメタデータファイルと一緒に MetaGraphDef とチェックポイントファイルを使用します。もし貴方が、例えば TensorFlow Serving を使用して web API を配備しているのであれば、それは貴方が使用することを望むフォーマットです。
モバイル上で使用可能なモデルをどのように得るのでしょう?
多くの状況で、TensorFlow でモデルを訓練することは GraphDef ファイル (通常は .pb か .pbtxt サフィックスで終わります) とチェックポイント・ファイルのセットを含むフォルダを与えます。モバイルか組み込みへの配備のために必要なものは、’frozen’ されているか、インライン定数に変換された変数を持つ、単一の GraphDef ファイルですので総てが一つのファイル内にあります。変換を処理するためには、freeze_graph.py スクリプトが必要でしょう、これは tensorflow/python/tools/freeze_graph.py に保持されています。それはこのように実行できるでしょう :
bazel build tensorflow/tools:freeze_graph bazel-bin/tensorflow/tools/freeze_graph \ --input_graph=/tmp/model/my_graph.pb \ --input_checkpoint=/tmp/model/model.ckpt-1000 \ --output_graph=/tmp/frozen_graph.pb \ --output_node_names=output_node (\)
input_graph 引数は、貴方のモデル・アーキテクチャを保持する GraphDef ファイルをポイントすべきです。GraphDef がディスク上のテキストフォーマットでストアされていることもありえます、この場合には .pb の代わりに .pbtxt で終わる可能性が高いです、そしてコマンドに特別な –input_binary=false フラグを追加すべきです。
input_checkpoint は最も最近にセーブされたチェックポイントであるべきです。チェックポイントのセクションで述べたように、完全なファイル名よりも、ここではチェックポイントのセットへの一般的なプレフィックスを与える必要があります。
output_graph は結果としての凍結された (= frozen) GraphDef がセーブされる場所を定義します。テキストフォーマットでは巨大なスペースの総量を取る多くの重み値を含む可能性が高いのでそれはいつでもバイナリ protobuf としてセーブされます。
output_node_names はノードの名前のリストです。これは貴方がグラフの結果から抽出することを望むものです。これが必要な理由は、凍結するプロセスはグラフのどの部分が実際に必要であるか、そしてどれが訓練プロセスの (要約 ops のような) 生成物であるかを理解する必要があるためです。与えられた出力ノードを計算するために寄与する ops のみが保持されるでしょう。グラフがどのように使用されるかを知っていれば、これらは丁度取得ターゲットとして貴方が Session::Run() に渡すノードの名前であるべきです。ノード名を見つける最も簡単な方法は python でグラフを構築する Node オブジェクトを精査することです。TensorBoard で貴方のグラフを精査することも他の単純な方法です。summarize_graph ツール を実行することでありそうな出力について何某かの提言を得ることができます。
TensorFlow のための出力フォーマットは時間とともに変わっていますので、input_saver のような、他の一般的には使用されることの少ない利用可能なフラグも各種ありますが、フレームワークの新しいバージョンでは訓練されたグラフ上ではこれらを必要とすべきではないことを望みます。
グラフ変換ツールを使用する
デバイス上でモデルを効率的に実行するために必要な多くのことは グラフ変換ツール (Graph Transform Tool) を通して利用可能です。このコマンドライン・ツールは入力 GraphDef ファイルを取り、貴方がリクエストする書き換えルールのセットを適用して、そしてそれから結果を GraphDef として書き出します。このツールをどのようにビルドして実行するかについての更なる情報はドキュメントを見てください。
訓練-only ノードを取り除く
訓練コードで生成された TensorFlow GraphDef は、バックプロパゲーションと重み更新、更に入力のキューイングをデコーディング、そしてチェックポイントのセーブのために必要な、総ての計算を含みます。これらのノードの総ては推論の間にはもはや不要で、チェックポイント・セービングのような演算の幾つかはモバイル・プラットフォームではサポートさえされません。デバイスでロード可能なモデルファイルを作成するためにはグラフ変換ツールの strip_unused_nodes ルールを実行することによりそれらの必要でない演算を削除する必要があります。
このプロセスの最もトリッキーな部分は推論の間に入力と出力として使用したいノードの名前を見つけ出すことです。推論を実行し始めたらこれらはいずれにせよ必要でしょう、しかしここでもどのノードが推論 -only パスで必要ないかを変換が計算できるためには、それらが必要です。これらは訓練コードからは明白ではないかもしれません。ノード名を決定する最も簡単な方法は TensorBoard でグラフを調査することです。
モバイル・アプリケーションは典型的にはセンサーからデータを収集してそれをメモリ上に配列として持ちますが、その一方で訓練は典型的にはディスクにストアされたデータ表現をロードしてデコードすることを伴うことを忘れないでください。例として Inception v3 の場合には、グラフの最初に DecodeJpeg op があり、これはディスクから取得されたファイルから JPEG-エンコードされたデータを取りそしてそれを任意のサイズの画像に変えるために設計されています。それの後にはそれを期待するサイズにスケールする BilinearResize op があり、続いて 2, 3 の他の ops があります。これらは byte データを float に変換してグラフの残りが期待する方法で値の大きさをスケールします。典型的なモバイル app はこれらのステップの多くをスキップするでしょう、何故ならばその入力をライブカメラから直接取る続けるからです。そのため貴方が実際に供給する入力ノードはこの場合には (下図の) Mul ノードの出力です。
正しい出力ノードを見つけ出すために同様の精査のプロセスを行なう必要があるでしょう。
frozen GraphDef ファイルだけが与えられてそのコンテンツについて不確かであるならば、summarize_graph ツールを使用してみてください、これはグラフ構造からそれが見つける入力と出力についての情報を表示出力します。オリジナルの Inception v3 ファイルの例がここにあります :
bazel run tensorflow/tools/graph_transforms:summarize_graph -- --in_graph=tensorflow_inception_graph.pb
入力と出力ノードが何であるかの考えをひとたび持つならば、それらをグラフ変換ツールに –input_names と –output_names 引数として供給し、次のように、trip_unused_nodes transform を呼び出すことができます :
bazel run tensorflow/tools/graph_transforms:transform_graph -- --in_graph=tensorflow_inception_graph.pb --out_graph=optimized_inception_graph.pb --inputs='Mul' --outputs='softmax' --transforms=' strip_unused_nodes(type=float, shape="1,299,299,3") fold_constants(ignore_errors=true) fold_batch_norms fold_old_batch_norms'
ここで注意すべき一つのことは入力にこうあってほしいというサイズと型を指定する必要があることです。これは、推論への入力として渡していくどのような値でも特別な Placeholder op ノードに供給される必要があるためで、そして変換はそれらがまだ存在しない場合にはそれらを作成する必要があるかもしれません。例えば Inception v3 の例では、Placeholder ノードは、リサイズされてリスケールされた画像配列を出力するために使用される古い Mul ノードを置き換えます、何故ならば TensorFlow を呼び出す前にその処理を私達自身で行なうからです。けれどもそれは元の名前を保持します、それは変更された Inception グラフでセッションを実行するときに何故 Mul に常に入力を供給するかの理由です。
このプロセスを実行した後は、貴方の予測プロセスを実行するために必要な実際のノードを含むだけのグラフを持つでしょう。これがグラフでメトリクスを走らせることが有用となるポイントで、モデルに何があるかを理解するために再度 summarize_graph を実行することは価値があります。
どの ops をモバイル上で含めるべきでしょうか?
TensorFlow では数百もの利用可能な演算があり、そしてそれぞれの一つは異なるデータ型のために複数の実装を持ちます。モバイル・プラットフォーム上では、コンパイル後に生成される実行可能バイナリのサイズは重要です、何故ならば最善のユーザ体験のためにはアプリケーションのダウンロード・バンドルはできる限り小さくある必要があるからです。総ての ops とデータ型が TensorFlow ライブラリにコンパイルされるならばコンパイルされたライブラリの総計サイズは数十メガバイトになりえますので、デフォルトでは ops とデータ型のサブセットだけが含まれます。
これは、もし貴方がデスクトップ・マシン上で訓練されたモデル・ファイルをロードする場合、それをモバイルにロードするとき “No OpKernel was registered to support Op” エラーを見るかもしれないことを意味します。試すべき最初のことは任意の訓練-only ノードをストリップ・アウトしたことを確かなものにすることです、op が決して実行されない場合でさえもロード時にエラーが起きるでしょう。それが行われた後でも依然として同じ問題に遭遇するのであれば、貴方のビルドされたライブラリに op を追加することを見る必要があるでしょう。
ops と型を含めるための基準は幾つかのカテゴリーに分類されます :
- それらは勾配のためのバックプロパゲーションでのみ有用でしょうか?モバイルは推論にフォーカスしますので、これらを含める必要はありません。
- チェックポイント・セービングのように、それらは他の訓練の要請のために主として有用でしょうか?これらは抜かします。
- libjpeg のような、モバイル上で常に利用可能ではないフレームワークにそれらは依存しているでしょうか?特別な依存を回避するために DecodeJpeg のような ops は含めません。
- 一般的には使用されない型がありますか?例えば boolean の変形は含めません、何故ならば典型的な推論グラフではそれらの使用を殆ど見ることはないからです。
モバイル上での推論のために最適化するためにこれらの ops はデフォルトではトリムされますが、しかしデフォルトを変更するためにビルドファイルを変えることは可能です。ビルドファイルを変更した後、TensorFlow を再コンパイルする必要があるでしょう。どのようにこれを行なうかのより詳細にうちては下を見てください、そしてまたバイナリサイズを小さくするための詳細は Optimizing を見てください。
実装の位置を特定する
演算は2つのパートに分かれます。最初は op 定義で演算の signature を宣言します、これはそれが持つ入力、出力、そして属性です。これらは非常に小さなスペースしか取りませんので、デフォルトで総てが含められます。op 計算の実装はカーネル内でなされ、これは tensorflow/core/kernels フォルダにあります。ライブラリに入れる必要がある op のカーネル実装を含む C++ ファイルをコンパイルする必要があります。それがどのファイルであるかを見出すためには、ソースファイルの演算名を検索することができます。
この検索が Mul op 実装を探していることが見て取れるでしょう、そしてそれは tensorflow/core/kernels/cwise_op_mul_1.cc でそれを見つけています。文字列引数の一つとして貴方がケアする op 名と一緒にREGISTER で始まるマクロを探す必要があります。
このケースでは、実装は実際には複数の .cc ファイルに渡って分かれていますので、貴方のビルドにはそれらの総てを含める必要があるでしょう。コード検索のためにコマンドラインを使用するほうがより快適であるならば、TensorFlow レポジトリのルートから実行した場合に正しいファイルの位置を決める grep コマンドもまたあります :
grep 'REGISTER.*"Mul"' tensorflow/core/kernels/*.cc
実装をビルドに追加する
もし貴方が Bazel を使用して Android のためにビルドしているのであれば、見つけたファイルを android_extended_ops_group1 か android_extended_ops_group2 ターゲットに追加することを望むでしょう。それらがそこで依存する任意の .cc ファイルを含める必要もまたあるかもしれません。ビルドがヘッダファイルの欠落について不平を言うのであれば、必要な .h を android_extended_ops ターゲットに追加します。
iOS, Raspberry Pi, etc. をターゲットとする makefile を使用ているのであれば、tensorflow/contrib/makefile/tf_op_files.txt に行って、そこで正しい実装ファイルを追加してください。
以上