ホーム » 「TensorFlow Extend」タグがついた投稿

タグアーカイブ: TensorFlow Extend

TensorFlow : Extend : 他の言語での TensorFlow

TensorFlow : Extend : 他の言語での TensorFlow (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 04/04/2017

* 本ページは、TensorFlow の本家サイトの Extend – TensorFlow in other languages を翻訳した上で
適宜、補足説明したものです:
    https://www.tensorflow.org/extend/language_bindings
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

背景

このドキュメントは他のプログラミング言語で TensorFlow 機能を作成または開発することに興味のある人たちへのガイドとなることを意図しています。TensorFlow の特徴と同じ事を他のプログラミング言語で利用可能にするための推奨されるステップについて記述します。

Python は TensorFlow でサポートされた最初のクライアント言語で現在では多くの特徴をサポートしています。それらの機能は段々と (C++ で実装された) TensorFlow コアに移されてきて C API 経由で公開されてきています。クライアント言語は TensorFlow 機能を提供するために C API を活用するためには言語の foreign function interface (FFI) を使用するべきです。

 

概要

プログラミング言語で TensorFlow 機能を提供することは幅広いカテゴリに分類されます :

  • 事前定義されたグラフを実行する : GraphDef (or MetaGraphDef) protocol message が与えられた時、session を作成して、query を実行して、そしてテンソル結果を得ることができます。これは事前トレーニングされたモデル上でインターフェイスを実行することを望むモバイル app またはサーバのためには十分です。
  • グラフ構築 : グラフに演算を追加する、定義された TensorFlow op 毎に少なくとも一つの関数。理想的にはこれらの関数は自動的に生成されるでしょう、そうすれば op 定義が変更されたときに同期的に留まれます。
  • 勾配 (AKA 自動微分) : グラフと入力と出力演算のリストが与えられた時、出力に関する入力の偏導関数 (勾配) を計算する演算をグラフに追加します、グラフの特定の演算に対する勾配関数のカスタマイゼーションを許します。
  • 関数 : 主要な GraphDef の複数の場所で呼び出されるかもしれないサブグラフを定義します。GraphDef に含まれる FunctionDefLibrary の FunctionDef を定義します。
  • 制御フロー : ユーザ指定のサブグラフで “If” と “While” を構築します。理想的にはこれらは勾配と一緒に動作します (上を参照)。
  • ニューラルネットワーク・ライブラリ : ニューラルネットワーク・モデルの作成を一緒にサポートしてそれらをトレーニングする多くのコンポーネント (おそらくは分散セッティングで)。他の言語でこれを利用可能にすることは便利である一方で、これを Python 以外の言語でサポートする予定は今のところありません。これらのライブラリは典型的には上述した特徴に渡るラッパーです。

少なくとも、言語バインディングは事前定義されたグラフの実行をサポートするべきですが、多くはグラフ構築もサポートするべきです。TenosrFlow Python API はこれら全ての特徴を提供します。

 

現在のステータス

新しい言語サポートは C API の上に構築されるべきです。けれども、下のテーブルで見て取れるように、まだ全ての機能が C で利用可能ではありません。C API で更なる機能を提供することは on-going なプロジェクトです。

特徴

Python

C

事前定義されたグラフの実行

tf.import_graph_def, tf.Session

TF_GraphImportGraphDef, TF_NewSession

生成された op 関数でグラフ構築

Yes

Yes (C API はこれを行なうクライアント言語をサポート)

勾配

tf.gradients

 

関数

tf.python.framework.function.Defun

 

制御フロー

tf.cond, tf.while_loop

 

ニューラルネットワーク・ライブラリ

tf.train, tf.nn, tf.contrib.layers, tf.contrib.slim

 

 

推奨されるアプローチ

事前定義されたグラフを実行する

言語バインディングは以下のクラスを定義することが期待されます :

  • グラフ : グラフは TensorFlow 計算を表します。(クライアント言語では Operations で表される、) 演算 (= operations) から構成され C API では TF_Graph に相当します。主として新しい Operation オブジェクトを作成する時と Session を開始する時に引数として使用されます。またグラフにおける演算を通して反復を行ない (TF_GraphNextOperation)、名前で演算を検索したり (TF_GraphOperationByName)、GraphDef protocol message へ/から変換をサポートします (C API では TF_GraphToGraphDef と TF_GraphImportGraphDef)。
  • 演算 (Operation) : グラフにおける計算ノードを表します。C API では TF_Operation に相当します。
  • 出力 : グラフの演算の出力の一つを表します。DataType (そして結局は shape) を持ちます。グラフに演算を追加するための関数、あるいはその出力をテンソルとして取得する Session の Run() メソッドへの入力引数として渡されるかもしれません。C API では TF_Output に相当します。
  • Session: TensorFlow ランタイムの特定のインスタンスへのクライアントを表します。その主要なジョブは Graph と幾つかのオプションともに構築されてそれからグラフを Run() するために呼び出します。C API では TF_Session に相当します。
  • テンソル : 要素が全て同じ DataType である N-次元 (矩形) 配列を表します。データは Session の Run() 呼び出しへ/から取得します。C API では TF_Tensor に相当します。
  • DataType: TensorFlow でサポートされる全ての可能なテンソル型の列挙です。C API では TF_DataType に相当して Python API ではしばしば dtype として参照されます。

グラフ構築

TensorFlow は多くの ops を持ち、そのリストは静的ではないので、グラフに ops を追加するためにはそれらを個々に手動で書く代わりに関数を生成することを推奨します(ジェネレータが何を生成すべきかを見つけ出すためには 2, 3 を手書きしてみることは良い方法ですが)。関数を生成するために必要な情報は OpDef protocol message に含まれます。

登録された ops について OpDefs のリストを取得するには 2, 3の方法があります :

  • C API の TF_GetAllOpList は全ての登録された OpDef protocol message を取得します。これはクライアント言語でジェネレータを書くために利用できます。これは、OpDef messages を解釈するためにクライアント言語が protocol buffer サポートを持つことを要求します。
  • C++ 関数 OpRegistry::Global()->GetRegisteredOps() は全ての登録された OpDefs の同じリストを返します ([tensorflow/core/framework/op.h] で定義されています)。これは C++ でジェネレータを書くために利用できます(特に protocol buffer サポートを持たない言語のために有用です)。
  • そのリストの ASCII-serialized 版は自動化されたプロセスで定期的に [tensorflow/core/ops/ops.pbtxt] に check in されます。

OpDef は以下を指定します :

  • op の名前はキャメルケースです。生成された関数のためには言語の慣例に追随します。例えば、言語がスネークケースを使用するならば、op 関数の名前のためにキャメルケースの代わりにそれを使用します。
  • 入力と出力のリスト。新しい op を追加する の入出力のセクションで記述されているように、これらの型は属性を参照することで多型 (polymorphic) かもしれません、
  • 属性のリスト、それらの (もしあれば) デフォルト値に沿って。これらの幾つかは (それらが入力で決定されるのであれば) 推測され、幾つかは (デフォルト値を持てば) オプションで、そして幾つかは (デフォルト値がないならば) 必要です。
  • 一般論として op、そして入力、出力、そして推測できない属性についてドキュメント。
  • ランタイムでは使用されずコード・ジェネレータは無視できる幾つかのフィールド。

OpDef は、(言語の FFI でラップされる) TF_OperationDescription C API を使用してグラフに op を追加する関数のテキストに変換されます。

  • TF_OperationDescription* を作成するために TF_NewOperation() で開始します。
  • 入力毎に TF_AddInput() または TF_AddInputList() を一度呼び出します(入力がリスト型を持つか否かに依存します)。
  • 推測できない属性を設定するために TF_SetAttr*() 関数を呼び出します。デフォルト値をオーバーライドすることを望まないならば属性はデフォルトでスキップできるかもしれません。
  • 必要ならばオプション・フィールドを設定します :
    • TF_SetDevice() : 演算を特定のデバイス上に強制します。
    • TF_AddControlInput(): この演算が動作する前に他の演算は終了しているという要求を追加します。
    • TF_SetAttrString(“_kernel”) : カーネル・ラベルを設定するために (滅多に使われません)。
    • TF_ColocateWith() 一つの演算を他のものと同じ場所に配置するために。
  • 終了したときに TF_FinishOperation() を呼び出します。これは、終了後に変更できないように、グラフに演算を追加します。

既存のサンプルはコード・ジェネレータをビルド・プロセスの一部として実行します (Bazel genrule を使用して)。或いは、コード・ジェネレータは自動化された cron プロセスで実行することもできます、結果を check in することもできます。

これは生成されたコードとレポジトリに check in された OpDefs が分岐するリスクも生みますが、Go の “go get” と Rust の cargo ops のようにより前にコードが生成されることが期待される言語には有用です。その対極に、ある言語に対してはコードは [tensorflow/core/ops/ops.pbtxt] から動的に生成され得るかもしれまん。

定数を処理する

もしユーザが入力引数に定数 (constants) を提供できるのであればコードの呼び出しは遥に簡潔になります。生成されたコードはこれらの定数を演算に変換すべきです、それはグラフに追加されてインスタンス化される op への入力として使用されます。

オプション・パラメータ

言語がオプションのパラメータを関数に許すのであれば (Python のデフォルト値を持つキーワード引数のように)、それらをオプション属性、演算名、デバイス、制御入力 etc. に使用します。ある言語では、(Python の “with” ブロックのように) これらのオプション・パラメータは動的スコープを使用してセットされます。これらの特徴がない場合には、TensorFlow API の C++ 版のように、ライブラリは “builer pattern” に頼るかもしれません。

名前スコープ

階層的なスコーピングの類を使用して名前付けられたグラフ演算のサポートを持つことは良いアイデアです、特に TensorBoard が妥当な方法で巨大なグラフを表示するためにそれに依存しているという事実を考慮に入れて。既存の Python と C++ API は異なるアプローチを取ります : Python では、名前 (everything up to the last “/”) の “directory” パートはブロックに由来します。実際に、名前を階層的に定義するスコープを持つスレッド局所スタック (thread-local stack) があります。名前の最後のコンポーネントは (オプション名前キーワード引数を使用して) ユーザから明示的に提供されるか追加される op の型の名前へのデフォルトであるかです。C++ では名前の “directory” パートは明示的にスコープ・オブジェクトで保持されます。NewSubScope() メソッドは名前のパートに追加して新しいスコープを返します。名前の最後のコンポーネントは WithOpName() メソッドを使用して設定され、そして Python のように op の型の名前へデフォルトが追加されます。スコープ・オブジェクトはコンテキストの名前を指定するために明示的に渡されます。

ラッパー

生成された関数を幾つかの ops に対しては private として保持することは意味があるかもしれません、追加のワークを行なうラッパー関数を代わりに使用できるようにして。これはまた、生成されたコードのスコープの外で特徴をサポートするための緊急避難口を与えます。

ラッパーの一つの使い方は SparseTensor 入力と出力をサポートするためです。SparseTensor は 3 dense (密な) テンソルのタプルです : indices, values, そして shape。value はベクトルサイズ [n]、shape はベクトルサイズ [rank]、そして indices は行列サイズ [n, rank] です。単一のスパース (疎な)・テンソルを表すために triple を使用する幾つかのスパース ops があります。

ラッパーを使用する他の理由は状態を保持する ops のためです。その状態上で動作するための幾つかの companion ops を持つ 2, 3 の ops (e.g. 変数) があります。Python API は、コンストラクタが op を作成するこれらの ops のためのクラスを持ち、そのクラスの上のメソッドは状態の上で動作するグラフに演算を追加します。

他の考察

  • 言語のキーワードとぶつかる op 関数と引数を名前変更するために使われるキーワードのリストを持つは良いことです (あるいは、ライブラリ関数の名前や生成されるコードで参照される変数のような、障害を引き起こす他のシンボル)。
  • Const 演算をグラフに追加するための関数は典型的にはラッパーです、何故ならば生成された関数は典型的には冗長な DataType 入力を持つからです。

勾配、関数と制御フロー

現時点で、勾配、関数そして制御フロー演算 (“if” と “while”) へのサポートは Python 以外の言語では利用可能ではありません。C API が必要なサポートを提供する時にはこれは更新されます。

 

以上

TensorFlow : Extend : TensorFlow アーキテクチャ

TensorFlow : Extend : TensorFlow アーキテクチャ (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 04/01/2017

* 本ページは、TensorFlow の本家サイトの Extend – TensorFlow Architecture を翻訳した上で
適宜、補足説明したものです:
    https://www.tensorflow.org/extend/architecture
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

私たちは TensorFlow をラージ・スケール分散トレーニングと推論のために設計しましたが、新しい機械学習モデルとシステムレベルの最適化の実験をサポートするに十分に柔軟でもあります。

このドキュメントはスケールと柔軟性の組み合わせを可能にするシステム・アーキテクチャについて説明します。貴方が計算グラフ、演算、そしてセッションのような TensorFlow プログラミング・コンセプトについて基本的な知識があることを仮定しています。これらのトピックへの序論については TensorFlow で始めましょう を見てください。分散 TensorFlow についての幾らかの知識もまた手助けとなります。

このドキュメントは、現在の API ではサポートされていない何某かの方法で TensorFlow を拡張することを望む開発者、TensorFlow のために最適化することを望むハードウェア技術者、スケーリングと分散で動作する機械学習システムの実装者、あるいは TensorFlow 内部を見ることを望む任意の人のためのものです。読んだ後にはコア TensorFlow コードを読んで変更するに十分なほど TensorFlow アーキテクチャを良く理解するはずです。

 

概要

TensorFlow ランタイムはクロスプラットフォーム・ライブラリです。Figure 1 はその概略的なアーキテクチャを図示しています。C API は異なる言語のユーザレベル・コードをコア・ランタイムから分離します。


Figure 1

このドキュメントは次の層にフォーカスします :

  • クライアント :
  • 計算をデータフロー・グラフとして定義する。
  • session を使用してグラフ実行を初期化する。
  • 分散マスター
  • Session.run() への引数で定義されるように、グラフから特定のサブグラフを切り取る。
  • サブグラフを異なるプロセスとデバイスで動作する複数のピースに分割する。
  • ワーカー・サービスへグラフ・ピースを分散させる。
  • ワーカー・サービスによるグラフ・ピース実行を初期化する。
  • ワーカー・サービス (各タスクのために一つ)
  • 利用可能なハードウェア (CPU, GPU, etc.) に適当なカーネル実装を使用してグラフ演算の実行をスケジュールする。
  • 他のワーカーサービスへ/から演算結果を送る/受け取る。
  • カーネル実装
  • 個々のグラフ演算のために計算を実行する。

Figure 2 はこれらのコンポーネントの相互作用を図示しています。”/job:worker/task:0″ と “/job:ps/task:0″ は両者ともワーカーサービスのタスクです。”PS” は “パラメータ・サーバ” を表します : モデルのパラメータを保持して更新する責任を持つタスクです。他のタスクはパラメータを最適化する動作をしている時にこれらのパラメータに更新を送ります。タスク間の仕事の特定の分担は必要ではありませんが、分散トレーニングでは一般的です。


Figure 2

分散マスターとワーカー・サービスは分散 TensorFlow でのみ存在することに注意してください。TensorFlow のシングル-プロセス版は、分散マスターが行なう全てを行ないながらローカルプロセスでデバイスと通信するのみという特別な Session 実装を含みます。

次のセクションではコア TensorFlow 層をより詳細に説明してサンプル・グラフの処理を通り抜けます。

 

クライアント

ユーザは、計算グラフを構築するクライアント TensorFlow プログラムを書きます。このプログラムは個々の演算を直接構成することもできますし Estimators API のような便利なライブラリを使用してニューラルネットワーク層と他の高位抽象を構成することもできます。TensorFlow は複数のクライアント言語をサポートし、私たちは Python と C++ を優先してきました、何故ならば内部ユーザがこれらの言語に最も精通しているからです。特徴がより確立された時には、典型的にはそれらを C++ に移植し、その結果ユーザは全てのクライアント言語から最適化された実装にアクセスできます。トレーニング・ライブラリの多くは依然として Python-only ですが、C++ は効率的な推論をサポートします。

クライアントは session を作成します、これはグラフ定義を分散マスターに tf.GraphDef protocol buffer として送ります。クライアントがグラフのノードあるいはノード群を評価する時、その評価は分散マスターへの呼び出しのトリガーとなり計算を初期化します。

Figure 3 では、クライアントはグラフを構築します、これは重み (w) を特徴ベクトル (x) に適用し、バイアス項 (b) を加算しそして結果を変数 (s) に保存します。


Figure 3

コード

 

分散マスター

分散マスター :

  • クライアントから要求されたノードを評価するために必要なサブグラフを得るためにグラフを切り取り、
  • 関係する各デバイスのためにグラフ・ピースを得るためにグラフを分割し、そして
  • 後続のステップで再利用できるようにこれらのピースをキャッシュします。

マスターはステップのための計算全体を見るので、それは共通部分式除去 (common subexpression elimination) と定数畳み込み (constant folding) のような標準的な最適化を提供します。そしてそれはタスクのセットに渡って最適化されたサブグラフの実行を調整します。


Figure 4

Figure 5 はサンプル・グラフの可能な分割を示しています。分散マスターはモデル・パラメータをグループ化します、パラメータ・サーバ上にそれらを一緒に置くためにです。


Figure 5

グラフ・エッジは分割によりカットされ、分散マスターは send と receive ノードを挿入して分散タスク間で情報を渡します (Figure 6)。


Figure 6

分散マスターはそしてグラフ・ピースを分散タスクへ送ります。


Figure 7

コード

 

ワーカー・サービス

各タスクのワーカー・サービスは :

  • マスターからのリクエストを処理し、
  • ローカル・サブグラフを含む演算のためのカーネル実行をスケジュールし、そして
  • タスク間の直接通信を調整します。

大きなグラフを小さなオーバーヘッドで実行するためにワーカーサービスを最適化します。現在の実装では秒毎に何万ものサブグラフが実行可能で、迅速な、きめ細かい訓練ステップを行なうための数多くのレプリカを有効にします。ワーカー・サービスはカーネルをローカルデバイスにディスパッチして可能な時にはカーネルを並列に実行します、例えば複数の CPU コアあるいは GPU ストリームを使用することによってです。

Send と Recv 演算は送信元と送信先のデバイスタイプの各ペアのために特化します :

  • ローカル CPU と GPU デバイス間の転送は cudaMemcpyAsync() API を使用して計算とデータ転送をオーバーラップさせます。
  • 2つのローカル GPU 間の転送は peer-to-peer DMA を使用し、ホスト CPU 経由の高価なコピーを回避します。

タスク間の転送のためには、TensorFlow は複数プロトコルを使用し、以下を含みます :

  • gRPC over TCP.
  • RDMA over Converged Ethernet.

マルチ-GPU 通信のための NVIDIA’s NCCL のための予備的サポートも持ちます (tf.contrib.nccl 参照)。


Figure 8

コード

 

カーネル実装

ランタイムは 200 標準演算以上を含み、数学的、配列操作、制御フロー、そして状態管理演算を含みます。これらの演算の各々は様々なデバイスに最適化されたカーネル実装を持つことができます。演算カーネルの多くは Eigen::Tensor を使用して実装されています、これはマルチコア CPU と GPU のための効率的な並列コードを生成するために C++ テンプレートを使用します ; けれども、私たちは cuDNN のようなライブラリも自在に使用しそこではより効率的なカーネル実装が可能です。更にまた 量子化 も実装しました、これはモバイルデバイスと高スループットなデータセンター・アプリケーションのような環境でより高速な推論を可能にします、そして量子化された計算を高速化するために gemmlowp 低精度行列ライブラリを使用します。

サブ計算を演算の合成として表現することは難しくあるいは非効率で、ユーザは C++ で書かれた効率的な実装を提供する追加のカーネルを登録できます。例えば、ReLU と Sigmoid 活性化関数とそれらの該当する勾配のような、あるパフォーマンス・クリティカルな演算のための貴方自身の融合カーネルを登録することを推奨しています。XLA コンパイラ は自動カーネル融合の実験的な実装を持ちます。

コード

 

以上

TensorFlow : Extend : 新しい Op を追加する

TensorFlow : How To : 新しい Op を追加する (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
更新日時 : 09/16/2017
作成日時 : 03/04/2016

* 本ページは、TensorFlow 本家サイトの Extend – Adding a New Op を翻訳した上で
適宜、補足説明したものです:

* (obsolete, リンク切れ) 本ページは、TensorFlow の本家サイトの How To – Adding a New Op を翻訳した上で
適宜、補足説明したものです:
    https://www.tensorflow.org/versions/master/how_tos/adding_an_op/index.html#adding-a-new-op
* サンプルコードの動作確認はしておりますが、適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 
本文

既存のライブラリでカバーされていない演算を組み入れたい (incorporate) のであれば、
カスタム Op を作成することがてきます。カスタム Op を組み入れるためには、以下が必要となるでしょう :

  • C++ ファイルの新しい Op を登録します。Op 登録は実装からは独立で、どのように Op が呼び出される (invoke) かの semantics を記述します。例えば、それは Op 名を定義して、その入出力を指定します。
  • Op を C++ で実装します。この実装は “カーネル” と呼ばれ、異なるアーキテクチャ(例えば CPU、GPU)や入出力タイプのために複数のカーネルがあってもかまいません。
  • オプションで、Python ラッパーを作成します。このラッパーは Op を作成するための public API です。デフォルト・ラッパーは Op 登録から生成され、これは直接使用できますし、追加することもできます。
  • オプションで、Op のための勾配を計算する関数を書きます。
  • オプションで、Op のための入出力形状 (shape) を記述する関数を書きます。これは貴方の Op と動作するための形状推論を可能にします。
  • Op をテストします、典型的には Python で。もし貴方が勾配を定義するならば、Python GradientChecker でそれらを検証できます。
 

Op のインターフェイスを定義する

TensorFlow システムに登録することで Op のインターフェイスを定義します。登録においては、貴方の Op の名前、その入力(型と名前)と出力(型と名前)、そして Op が必要とするかもしれない docstrings と attrs を指定します。

これがどのように動作するかを見るために、int32s のテンソルを取り、最初の要素を除いてゼロに設定されたテンソルのコピーを出力する Op を作成したいと仮定しましょう。ファイル tensorflow/core/user_ops/zero_out.cc を作成してそのような Op のためのインターフェイスを定義する REGISTER_OP マクロへの呼び出しを追加します。

#include "tensorflow/core/framework/op.h"

REGISTER_OP("ZeroOut")
    .Input("to_zero: int32")
    .Output("zeroed: int32");

この ZeroOut Op は入力として 32-bit 整数の一つのテンソル to_zero を取り、32-bit 整数のテンソル zeroed を出力します。

ネーミングについてのノート : Ops の名前は一意 (unique) でキャメルケースであるべきです。アンダースコア (_) で始まる名前は内部ユースのために予約されています。

 

Op のためのカーネルを実装する

インターフェイスを定義した後は、Op の一つまたはそれ以上の実装を提供します。これらのカーネルを作成するためには、OpKernel を拡張したクラスを作成してCompute メソッドをオーバーライドします。Compute メソッドは型 OpKernelContext* の一つの context 引数を提供し、そこから入出力テンソルのような有用なものにアクセスできます。

上で作成したファイルにカーネルを追加します。カーネルはこのようなものに見えるでしょう:

#include "tensorflow/core/framework/op_kernel.h"

using namespace tensorflow;

class ZeroOutOp : public OpKernel {
 public:
  explicit ZeroOutOp(OpKernelConstruction* context) : OpKernel(context) {}

  void Compute(OpKernelContext* context) override {
    // 入力テンソルを取得します
    const Tensor& input_tensor = context->input(0);
    auto input = input_tensor.flat<int32>();

    // 出力テンソルを作成します
    Tensor* output_tensor = NULL;
    OP_REQUIRES_OK(context, context->allocate_output(0, input_tensor.shape(),
                                                     &output_tensor));
    auto output = output_tensor->template flat<int32>();

    // 出力テンソルの最初の要素以外の全ては 0 にセットされます。
    const int N = input.size();
    for (int i = 1; i < N; i++) {
      output(i) = 0;
    }

    // 可能ならば最初の入力値を保存します。
    if (N > 0) output(0) = input(0);
  }
};

カーネルを実装した後は、それを TensorFlow システムで登録します。登録においては、異なる束縛 (constraints)、そこでこのカーネルが動作します、を指定します。例えば、CPU のために作られた一つのカーネルがあり、そして GPU のために別の一つを持つかもしれません。

ZeroOut op のためにこれを行なうには、zero_out.cc に次を追加します :

REGISTER_KERNEL_BUILDER(Name("ZeroOut").Device(DEVICE_CPU), ZeroOutOp);
 

Op ライブラリをビルドする

With TensorFlow バイナリ・インストレーション

貴方のシステム上で利用可能な g++ あるいは clang のような C++ コンパイラで zero_out.cc をコンパイルできるべきです。バイナリ PIP パッケージは、Op をコンパイルするのに必要なヘッダファイルとライブラリをシステム固有の場所にインストールします。しかしながら、TensorFlow python ライブラリはヘッダとライブラリ・ディレクトリをそれぞれ取得するための関数 get_include と get_lib を提供します。

Ubuntu マシン上のそれらの関数の出力をここに示します。

$ python
>>> import tensorflow as tf
>>> tf.sysconfig.get_include()
'/usr/local/lib/python2.7/site-packages/tensorflow/include'
>>> tf.sysconfig.get_lib()
'/usr/local/lib/python2.7/site-packages/tensorflow/core'
>>> 

g++ がインストールされていると仮定して、ここに、Op をダイナミックライブラリにコンパイルするために使用できるコマンドのシークエンスを示します。

$ TF_INC=$(python -c 'import tensorflow as tf; print(tf.sysconfig.get_include())')
$ TF_LIB=$(python -c 'import tensorflow as tf; print(tf.sysconfig.get_lib())')

$ g++ -std=c++11 -shared zero_out.cc -o zero_out.so \
-I $TF_INC -l tensorflow_framework -L $TF_LIB \
-fPIC -Wl,-rpath $TF_LIB

With TensorFlow ソース・インストレーション

TensorFlow ソース・インストールをしているのであれば、Op をコンパイルするために TensorFlow のビルドシステムが使用できます。次の Bazel ビルド・ルールを持つ BUILD ファイルを tensorflow/core/user_ops ディレクトリに配置してください。

cc_binary(
    name = "zero_out.so",
    srcs = ["zero_out.cc"],
    linkopts = [
        "-Wl,-Bsymbolic",
        "-lm",
    ],
    linkshared = 1,
    linkstatic = 1,
    deps = [
        "//third_party/tensorflow/core:framework",
    ],
)

zero_out.so をビルドするために次のコマンドを実行します。

$ bazel build -c opt //tensorflow/core/user_ops:zero_out.so
 

Python で Op を使用する

TensorFlow Python API は ダイナミックライブラリをロードして Op を TensorFlow フレームワークで登録するために load_op_library 関数を提供します。load_op_library は Python モジュールを返し、これは Op への Python ラッパーを含みます。こうして、ひとたび Op をビルドすれば、Python からそれを実行するために次を行なうことができます :

import tensorflow as tf
zero_out_module = tf.load_op_library('zero_out.so')
with tf.Session(''):
  zero_out_module.zero_out([[1, 2], [3, 4]]).eval()

# Prints
array([[1, 0],
       [0, 0]], dtype=int32)

ノート : 生成された関数は(PEP8 に応じた)snake_case 名が与えられます。そのためもし op が C++ ファイルで ZeroOut と命名されているのであれば、python 関数は zero_out と呼称されます。

Op を Python モジュールから import 可能な標準関数として利用可能にするためには、
次のように Python ソースファイルで load_op_library 呼び出しを持つことは多分有用でしょう(zero_out_op_1.py を見てください):

import tensorflow as tf

_zero_out_module = tf.load_op_library('zero_out_op_kernel_1.so')
zero_out = _zero_out_module.zero_out
 

動作することを検証する

Op が成功的に実装されたかを検証する良い方法はそのためのテストを書くとです。次の内容でファイル tensorflow/python/kernel_tests/zero_out_op_test.py を作成します :

import tensorflow as tf

class ZeroOutTest(tf.test.TestCase):
  def testZeroOut(self):
    zero_out_module = tf.load_op_library('zero_out.so')
    with self.test_session():
      result = zero_out_module.zero_out([5, 4, 3, 2, 1])
      self.assertAllEqual(result.eval(), [5, 0, 0, 0, 0])

そしてテストを実行します :

$ bazel test tensorflow/python:zero_out_op_test

妥当性確認 (Validation)

上の例は Op が任意の形状に適用されることを仮定していました。それがベクタだけに適用されるとしたらどうでしょう?これは上の OpKernel 実装にチェックを追加することを意味しています。

  void Compute(OpKernelContext* context) override {
    // 入力テンソルを取得する
    const Tensor& input_tensor = context->input(0);

    OP_REQUIRES(context, TensorShapeUtils::IsVector(input_tensor.shape()),
                errors::InvalidArgument("ZeroOut expects a 1-D vector."));
    // ...
  }

これは入力がベクタであると assert し、もしそうでないならば InvalidArgument ステータスを設定して返します。OP_REQUIRES マクロ は3つの引数を取ります :

他の選択肢として、もしある関数から返された Status オブジェクトがエラーかどうかテストして、もうそうならばそれを返したいのであれば、OP_REQUIRES_OK を使います。これらマクロ両方はエラー時の関数から返ります。

 

Op 登録

Attrs

Ops は attrs を持てます、この値は Op がグラフに追加された時に設定されます。これらは Op を構成するために使用されて、そしてその値はカーネル実装の内部と Op 登録の入出力の型の中の両方でアクセスできます。可能な時には attr の代わりに入力を使用することが好ましいです、何故なら入力がより柔軟だからです。それらはfeed を使用するように設定する等、全てのステップを変更できます。attr は入力ではできない事柄に対して使用されます : signature(入出力の数や型)に影響したり step-to-step からは変更できない任意の構成です。

Op を登録する時に、Attr メソッドを使用してその名前と型を指定することで attr を定義します、これは次の形式の仕様が期待されます :

<name>: <attr-type-expr>

ここで <name> は文字で始まり、英数字とアンダースコアからなります、そして <attr-type-expr> は下で記述される形式の型の式 (type expression) です。

例えば、ZeroOut Op をユーザ指定 index として preserve (保存) する場合には、0 番目の要素のみの代わりに、Op を次のように登録できます :

REGISTER_OP("ZeroOut")
    .Attr("preserve_index: int")
    .Input("to_zero: int32")
    .Output("zeroed: int32");

そうすれば貴方のカーネルはそのコンストラクタで context パラメータを通してこの attr にアクセスすることができます:

class ZeroOutOp : public OpKernel {
 public:
  explicit ZeroOutOp(OpKernelConstruction* context) : OpKernel(context) {
    // 保存 (preserve) するための値の index を得る。
    OP_REQUIRES_OK(context,
                   context->GetAttr("preserve_index", &preserve_index_));
    // preserve_index が正値であることを確認する。
    OP_REQUIRES(context, preserve_index_ >= 0,
                errors::InvalidArgument("Need preserve_index >= 0, got ",
                                        preserve_index_));
  }
  void Compute(OpKernelContext* context) override {
    // ...
  }
 private:
  int preserve_index_;
};

そしてそれは Compute メソッドで使用できます :

  void Compute(OpKernelContext* context) override {
    // ...

    // preserve_index is が範囲にあることを確認する。
    OP_REQUIRES(context, preserve_index_ < input.dimension(0),
                errors::InvalidArgument("preserve_index out of range"));

    // 出力テンソルの全ての要素を 0 に設定する。
    const int N = input.size();
    for (int i = 0; i < N; i++) {
      output_flat(i) = 0;
    }

    // 要求された入力値を保存します。
    output_flat(preserve_index_) = input(preserve_index_);
  }

後方互換性 を守るためには、既存の op に attr を追加する時 デフォルト値 を指定すべきです :

    REGISTER_OP("ZeroOut")
        .Attr("preserve_index: int = 0")
        .Input("to_zero: int32")
        .Output("zeroed: int32");

Attr 型

attr においては次の型がサポートされます:

  • string: 任意のバイト列 (Any sequence of bytes) (UTF8 である必要はない)。
  • int: 符号付き整数 (signed integer)
  • float: 浮動小数点数値。
  • bool: True または false.
  • type: One of the (non-ref) values of DataType.
  • shape: A TensorShapeProto.
  • tensor: A TensorProto.
  • list(<type>): A list of <type>, where <type> is one of the above types.
    Note that list(list(<type>)) is invalid.

See also: op_def_builder.cc:FinalizeAttr for a definitive list.

Default values & constraints

Attrs may have default values, and some types of attrs can have constraints. To
define an attr with constraints, you can use the following <attr-type-expr>s:

  • {'<string1>', '<string2>'}: The value must be a string that has either the
    value <string1> or <string2>. The name of the type, string, is implied
    when you use this syntax. This emulates an enum:

    REGISTER_OP("EnumExample")
        .Attr("e: {'apple', 'orange'}");
    
  • {<type1>, <type2>}: The value is of type type, and must be one of
    <type1> or <type2>, where <type1> and <type2> are supported
    tensor types. You don't specify
    that the type of the attr is type. This is implied when you have a list of
    types in {...}. For example, in this case the attr t is a type that must
    be an int32, a float, or a bool:

    REGISTER_OP("RestrictedTypeExample")
        .Attr("t: {int32, float, bool}");
    
  • There are shortcuts for common type constraints:

    • numbertype: Type type restricted to the numeric (non-string and
      non-bool) types.
    • realnumbertype: Like numbertype without complex types.
    • quantizedtype: Like numbertype but just the quantized number types.

    The specific lists of types allowed by these are defined by the functions
    (like NumberTypes()) in
    tensorflow/core/framework/types.h.
    In this example the attr t must be one of the numeric types:

    REGISTER_OP("NumberType")
        .Attr("t: numbertype");
    

    For this op:

    tf.number_type(t=tf.int32)  # Valid
    tf.number_type(t=tf.bool)   # Invalid
    
  • int >= <n>: The value must be an int whose value is greater than or equal to
    <n>, where <n> is a natural number.

    For example, the following Op registration specifies that the attr a must
    have a value that is at least 2:

    REGISTER_OP("MinIntExample")
        .Attr("a: int >= 2");
    
  • list(<type>) >= <n>: A list of type <type> whose length is greater than
    or equal to <n>.

    For example, the following Op registration specifies that the attr a is a
    list of types (either int32 or float), and that there must be at least 3
    of them:

    REGISTER_OP("TypeListExample")
        .Attr("a: list({int32, float}) >= 3");
    

To set a default value for an attr (making it optional in the generated code),
add = <default> to the end, as in:

REGISTER_OP("AttrDefaultExample")
    .Attr("i: int = 0");

The supported syntax of the default value is what would be used in the proto
representation of the resulting GraphDef definition.

Here are examples for how to specify a default for all types:

REGISTER_OP("AttrDefaultExampleForAllTypes")
   .Attr("s: string = 'foo'")
   .Attr("i: int = 0")
   .Attr("f: float = 1.0")
   .Attr("b: bool = true")
   .Attr("ty: type = DT_INT32")
   .Attr("sh: shape = { dim { size: 1 } dim { size: 2 } }")
   .Attr("te: tensor = { dtype: DT_INT32 int_val: 5 }")
   .Attr("l_empty: list(int) = []")
   .Attr("l_int: list(int) = [2, 3, 5, 7]");

Note in particular that the values of type type use the DT_* names
for the types
.

 

以上

AI導入支援 #2 ウェビナー

スモールスタートを可能としたAI導入支援   Vol.2
[無料 WEB セミナー] [詳細]
「画像認識 AI PoC スターターパック」の紹介
既に AI 技術を実ビジネスで活用し、成果を上げている日本企業も多く存在しており、競争優位なビジネスを展開しております。
しかしながら AI を導入したくとも PoC (概念実証) だけでも高額な費用がかかり取組めていない企業も少なくないようです。A I導入時には欠かせない PoC を手軽にしかも短期間で認知度を確認可能とするサービの紹介と共に、AI 技術の特性と具体的な導入プロセスに加え運用時のポイントについても解説いたします。
日時:2021年10月13日(水)
会場:WEBセミナー
共催:クラスキャット、日本FLOW(株)
後援:働き方改革推進コンソーシアム
参加費: 無料 (事前登録制)
人工知能開発支援
◆ クラスキャットは 人工知能研究開発支援 サービスを提供しています :
  • テクニカルコンサルティングサービス
  • 実証実験 (プロトタイプ構築)
  • アプリケーションへの実装
  • 人工知能研修サービス
◆ お問合せ先 ◆
(株)クラスキャット
セールス・インフォメーション
E-Mail:sales-info@classcat.com