[従来] グラフの走査と変更

危険

ここで説明されているコードは非推奨になりました。従来のソリューションの適用を避けるため使用しないでください。下位互換性を確保するためにしばらく保持されますが、最新のアプリケーションでは使用してはなりません

このガイドでは、非推奨である TensorFlow 変換方法について説明します。新しい推奨方法に関するガイドは、フロントエンド拡張に記載されています。

モデル・オプティマイザーで使用されるグラフの走査と変換には、次の 3 つの API があります。

  1. networkx.MultiDiGraph クラスの networkx Python ライブラリーで提供される API であり、mo.graph.graph.Graph オブジェクトの基本クラスです。例えば、次のメソッドはこの API レベルに属します。

    • graph.add_edges_from([list])

    • graph.add_node(x, attrs)

    • graph.out_edges(node_id)

    • graphnetworkx.MultiDiGraph クラスのインスタンスである他のメソッド。

    これは最も詳細レベルの API です。モデル・オプティマイザーの変換では使用しないでください。詳細は、メモリー内のモデル表現を参照してください。

  2. mo.graph.graph.Node クラスを中心にビルドされた API。Node クラスは、グラフノードとその属性を操作する主要なクラスです。メソッドと関数の例は次のとおりです。

    • node.in_node(y)

    • node.out_node(x)

    • node.get_outputs()

    • node.insert_node_after(n1, y)

    • create_edge(n1, n2)

    推奨されない “Node” クラスメソッドがいくつかあり、mo.graph.graph で定義されている関数の一部は非推奨になりました。詳細については、mo/graph/graph.py ファイルを参照してください。

  3. モデル・オプティマイザー・グラフ API と呼ばれる高レベル API であり、mo.graph.graph.Graphmo.graph.port.Port、および mo.graph.connection.Connection クラスを使用します。例えば、次のメソッドはこの API レベルに属します。

    • node.in_port(x)

    • node.out_port(y)

    • port.get_connection()

    • connection.get_source()

    • connection.set_destination(dest_port)

    これは、モデル・オプティマイザーの変換および操作の実装に推奨される API です

    モデル・オプティマイザー・グラフ API を使用する利点は、一部の内部実装の詳細 (グラフにデータノードが含まれているという事実) を隠匿し、安全で予測可能なグラフ操作を実行する API を提供し、グラフに操作セマンティクスを追加できることです。これは、ポートと接続の概念を導入することで実現されます。

    この記事はモデル・オプティマイザー・グラフ API のみに特化しており、他の 2 つの非推奨の API については説明しません。

ポート

操作セマンティクスは、操作に含まれる入力と出力の数を表現します。例えば、Parameter 操作と Const 操作には入力がなく 1 つの出力があり、ReLU 操作には 1 つの入力と 1 つの出力があり、Split 操作には 2 つの入力と属性 num_splits の値に応じて可変数の出力があります。

グラフ内の各操作ノード (Node クラスのインスタンス) には、0 個以上の入力ポートと出力ポート (mo.graph.port.Port クラスのインスタンス) があります。Port オブジェクトにはいくつかの属性があります。

  • node - ポートが属する Node オブジェクトのインスタンス。

  • idx - ポート番号。入力ポートと出力ポートには、0 から始まる独立した番号が付けられます。したがって、ReLU 操作には、1 つの入力ポート (インデックス 0) と 1 つの出力ポート (インデックス 0) があります。

  • type - ポートのタイプ。"in" または "out" のいずれかに等しい可能性があります。

  • data - 対応するデータノードの属性を取得するオブジェクト。このオブジェクトには、対応するデータノードの形状/値を取得/設定するメソッド get_shape() / set_shape() および get_value() / set_value() があります。例えば、in_port.data.get_shape() は入力ポート in_port (in_port.type == 'in') に接続されたテンソルの入力形状を返し、out_port.data.get_value() は出力ポート out_port (out_port.type == 'out') から生成されたテンソルの値を返します。

関数 get_shape() および get_value() は、部分推論フェーズまでは None を返します。モデル変換フェーズの詳細については、モデル変換パイプラインを参照してください。部分推論フェーズの詳細については、部分推論を参照してください。

対応するポートのインスタンスを取得する Node クラスのメソッドがいくつかあります。

  • in_port(x)out_port(x) を使用して、番号 x の入出力ポートを取得します。

  • in_ports() および out_ports() を使用して辞書を取得します。キーはポート番号、値は対応する入力/出力ポートです。

Op クラスのインスタンス属性 in_ports_count および out_ports_count は、Node に対して作成される入力ポートと出力ポートのデフォルト数を定義します。ただし、メソッド add_input_port() および add_output_port() を使用して、入出力ポートを追加できます。ポートは、delete_input_port()delete_output_port() メソッドを使用して削除することもできます。

Port クラスは、特定の Node インスタンスとの間で送受信されるエッジを処理する抽象化です。例えば、idx = 1 の出力ポートは、属性 out = 1 のノードの出力エッジに対応し、idx = 2 の入力ポートは、属性 in = 2 のノードの入力エッジに対応します。

4 つの演算ノード “Op1”、“Op2”、“Op3”、および “Op4” と、薄緑色のボックスで示された多数のデータノードを含むグラフパーツの例を考えてみます。

../../../../_images/MO_ports_example_1.svg

演算ノードには入力ポート (黄色の四角) と出力ポート (薄紫の四角) があります。入力ポートが接続されていない可能性があります。例えば、ノード Op1 の入力ポート 2 には入力エッジがありませんが、出力ポートには常に (データノードがグラフに追加される部分推論後) 関連するデータノードがあり、コンシューマーが存在しない可能性があります。

ポートを使用してグラフを移動できます。入力ポートのメソッド get_source() は、入力ポートで消費されるテンソルを生成する出力ポートを返します。グラフ構造が変化しても (フロントフェーズではグラフにデータノードがありません)、モデル変換のフロントフェーズ、ミドルフェーズ、バックフェーズでメソッドが同じように機能することが重要です。

Node オブジェクト op1op2op3op4 の 4 つのインスタンスがあり、それぞれノード Op1Op2Op3、および Op4 に対応すると仮定します。op2.in_port(0).get_source()op4.in_port(1).get_source() の結果は、Port タイプの同じオブジェクト op1.out_port(1) です。

出力ポートのメソッド get_destination() は、このテンソルを使用するノードの入力ポートを返します。このテンソルのコンシューマーが複数ある場合、エラーが発生します。出力ポートのメソッド get_destinations() は、テンソルを使用する入力ポートのリストを返します。

メソッド disconnect() は、特定の入力ポートに対応するノードの受け入れエッジを削除します。これは、複数のノードに接続されたノード出力ポートのフロントフェーズに適用される場合、いくつかのエッジを削除します。

メソッド port.connect(another_port) は、出力ポート port と入力ポート another_port を接続します。このメソッドは、グラフにデータノード (ミドルフェーズとバックフェーズ) が含まれる状況を処理し、2 つのノード間にエッジを作成しませんが、データノードを自動的に作成するか、既存のデータノードを再利用します。このメソッドがフロントフェーズで使用され、データノードが存在しない場合、メソッドはエッジを作成し、inout のエッジ属性を適切に設定します。

例えば、次の 2 つの方法を上のグラフに適用すると、次のようなグラフが得られます。

op4.in_port(1).disconnect()
op3.out_port(0).connect(op4.in_port(1))
../../../../_images/MO_ports_example_2.svg

使用可能なメソッドの完全なリストについては、mo/graph/graph.py ファイルの Node クラスの実装と、mo/graph/port.py ファイルの Port クラスの実装を参照してください。

接続

接続は、グラフの変更を簡単かつ確実に実行するために導入された概念です。接続は、ソース出力ポートと 1 つ以上のデスティネーション入力ポートの間のリンク、またはデスティネーション入力ポートとデータを生成するソース出力ポート間のリンクに対応します。したがって、各ポートは接続を利用して 1 つ以上のポートに接続されます。 モデル・オプティマイザーは、mo.graph.connection.Connection クラスを使用して接続を表します。

対応する Connection オブジェクトのインスタンスを取得する Port クラスの get_connection() メソッドは 1 つだけです。ポートが接続されていない場合、戻り値は None です。

例えば、op3.out_port(0).get_connection() メソッドは、ノード Op3 からデータノード data_3_0 へのエッジと、データノード data_3_0 からノード Op4 の 2 つのポートへの 2 つのエッジをカプセル化する Connection オブジェクトを返します。

Connection クラスは、接続に対応するソースポートとデスティネーション・ポートを取得するメソッドを提供します。

  • connection.get_source() - テンソルを生成する出力 Port オブジェクトを返します。

  • connection.get_destinations() - データを使用する入力 Port のリストを返します。

  • connection.get_destination() - データを消費する単一の入力 Port を返します。複数のコンシューマーが存在と例外が発生します。

Connection クラスは、接続のソースまたはデスティネーションを変更することでグラフを変更するメソッドを提供します。例えば、関数呼び出し op3.out_port(0).get_connection().set_source(op1.out_port(0)) は、データを消費するエッジのソースポートをポート op3.out_port(0) から op1.out_port(0) に変更します。上記のサンプルから変換されたグラフを以下に示します。

../../../../_images/MO_connection_example_1.svg

別の例は、connection.set_destination(dest_port) メソッドです。dest_port と接続されているすべての入力ポートを切断し、接続元ポートを dest_port に接続します。

接続はフロントフェーズ、ミドルフェーズ、バックフェーズでシームレスに機能し、グラフ構造が異なることは隠蔽されることに注意してください。

使用可能なメソッドの完全なリストについては、mo/graph/connection.py ファイルの Connection クラスの実装を参照してください。