[従来] グラフ変換拡張機能

危険

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

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

モデル・オプティマイザーは、フロントフェーズ変換ミドルフェーズ変換、およびバックフェーズ変換を実装するためのさまざまな基本クラスを提供します。 すべてのクラスには、次の共通のクラス属性とメソッドがあります。

  1. enabled 属性は、変換が有効かどうかを指定します。この値は実行時に変更して、モデル変換中の変換の実行を有効または無効にすることができます。デフォルト値は True です。

  2. id 属性は、一意の変換文字列識別子を指定します。この変換識別子は、カンマで区切った ids リストを使用して環境変数 MO_ENABLED_TRANSFORMS (MO_DISABLED_TRANSFORMS) を設定することにより、変換を有効 (無効) にするために使用できます。環境変数は、変換の enabled 属性の値をオーバーライドします。id 属性値を使用する代わりに、完全に定義されたクラス名を MO_ENABLED_TRANSFORMS (MO_DISABLED_TRANSFORMS) 変数に追加できます (extensions.back.NonmalizeToNormalizeL2.NormalizeToNormalizeL2 など)。これはオプションの属性です。

  3. run_not_recursively 属性は、TensorIterator の本体やループなどのサブグラフで変換を行うか指定します。デフォルト値は True です。

  4. force_clean_up 属性は、変換後にグラフのクリーンアップを実行するかどうかを指定します。グラフのクリーンアップでは、モデル入力から到達できないグラフのノードが削除されます。デフォルト値は False です。

  5. force_shape_inference 属性は、True に等しい need_shape_inference 属性でマークされたノードを変換後に再推論するかどうかを指定します。モデル・オプティマイザーは、変換中に入力が変更されたノードに対してこの属性を自動的に設定します。または、特定のノードの変換でこの属性を手動で設定することもできます。デフォルト値は False です。

  6. 属性 graph_condition は、1 つのパラメーター (Graph オブジェクト) を持つ関数のリストを指定します。変換は、すべての関数が True を返した場合にのみ実行されます。属性が設定されていない場合、チェックは実行されません。

  7. run_before() メソッドは、この変換を行う前に実行の必要がある変換クラスのリストを返します。

  8. run_after() メソッドは、この変換を行う必要がある変換クラスのリストを返します。

一部の変換タイプには特定のクラス属性とメソッドがあります。これらについては、このドキュメントの対応するセクションで説明します。

モデル・オプティマイザーは、登録された変換間の依存関係グラフを構築し、それらをトポロジー順に実行します。モデル変換フェーズで変換を実行するため、モデル・オプティマイザーは何も行わないアンカー変換を定義します。すべての変換は、これらのアンカー変換に基づいて順序付けされます。 以下の図は、アンカー変換、ビルトイン変換の一部、およびそれらの依存関係を示しています。

../../../../../_images/MO_transformations_graph.svg

ユーザー定義の変換は、デフォルトでは、対応する Start アンカー変換の後、および対応する Finish アンカー変換の前に実行されます (run_before() メソッドと run_after() メソッドがオーバーライドされていない場合)。

PreMiddleStart アンカーと PostMiddleStart アンカーは、モデル・オプティマイザーのパイプラインをリファクタリングする理由により導入されました。モデル・オプティマイザーのパイプラインは、以前は変換の順序がハードコードされていました。

フロントフェーズ変換

フロントフェーズ変換にはいくつかのタイプがあります。

  1. パターン定義のフロントフェーズ変換は、指定されたパターンと同形で、元のグラフのサブグラフごとにトリガーされます。

  2. 特定の操作のフロントフェーズ変換は、特定の op 属性値を持つノードに対してトリガーされます。

  3. 一般的なフロントフェーズ変換

  4. --transformations_config コマンドライン・パラメーターを指定して、JSON 構成ファイル (TensorFlow、ONNX、および PaddlePaddle モデルの場合) で定義された手動で有効化された変換。

    1. ノード名パターンのフロントフェーズ変換

    2. 開始点と終了点を使用したフロントフェーズ変換

    3. 変換設定ファイルで有効化された汎用フロントフェーズ変換

パターン定義のフロントフェーズ変換

このタイプの変換は、基本クラスとして mo.front.common.replacement.FrontReplacementSubgraph および mo.front.common.replacement.FrontReplacementPattern を使用して実装され、次のように動作します。

  1. 属性とそれらを接続するエッジを含むノードのリストを使用して、照合するサブグラフを定義します (エッジにも属性がある場合があります)。

  2. モデル・オプティマイザーは、指定されたサブグラフ (パターン) と同型の、元のグラフのすべてのサブグラフを検索します。

  3. モデル・オプティマイザーは、一致したサブグラフの各インスタンスに対してグラフ変換の実行に定義された関数を実行します。基本変換クラスのさまざまな関数をオーバーライドして、モデル・オプティマイザーの動作を変えることができます。

    1. replace_sub_graph(self, graph, match) はメソッドをオーバーライドします。この場合、モデル・オプティマイザーはオーバーライドされた関数のみを実行し、graph オブジェクトと、一致したサブグラフを記述する辞書を渡します。変換を記述し、新しく作成したノードをグラフの残りの部分に接続する必要があります。

    2. generate_sub_graph(self, graph, match) はメソッドをオーバーライドします。このケースは最も複雑なアプローチであるため、使用は推奨できません。これは、前述の 2 つのアプローチのいずれかに置き換えることができます。

サブグラフパターンは pattern() 関数で定義されます。この関数は、nodesedges の 2 つのキーを持つ辞書を返す必要があります。

  • nodes キーの値は、2 つの要素を含むタプルのリストです。

    • 最初の要素は、ノード間および変換関数でエッジを定義するために使用されるノードのエイリアス名です。

    • 2 番目の要素は属性を含む辞書です。キーは、ノード内に存在する必要がある属性の名前です。属性の値は、照合する特定の値、または単一のパラメーター (ノードからの属性値) を取得する関数にできます。この関数は、専用の値との属性比較の結果を返す必要があります。

  • edges キーの値は、2 つまたは 3 つの要素を持つタプルのリストです。

    • 最初の要素は、テンソルを生成するノードのエイリアス名です。

    • 2 番目の要素は、テンソルを使用するノードのエイリアス名です。

    • 3 番目の要素 (オプション) は、予期されるエッジ属性を含む辞書です。通常、この辞書には、入力ポートと出力ポートを定義する inout などの属性が含まれています。

Mish アクティベーション関数を定義するサブグラフを 1 つの操作に融合する、extensions/front/Mish_fusion.py ファイルに実装されたフロント変換の例を考えてみます。

from openvino.tools.mo.front.Softplus_fusion import SoftplusFusion
from openvino.tools.mo.ops.activation_ops import Mish
from openvino.tools.mo.front.common.replacement import FrontReplacementSubgraph
from openvino.tools.mo.front.subgraph_matcher import SubgraphMatch
from openvino.tools.mo.graph.graph import Graph, rename_nodes


class MishFusion(FrontReplacementSubgraph):
    """
    The transformation looks for the pattern with Softplus defining the Mish function: Mish(x) = x * tanh(SoftPlus(x)).
    """
    enabled = True  # Transformation is enabled.

    def run_after(self):  # Run this transformation after "SoftplusFusion" transformation.
        return [SoftplusFusion]

    def pattern(self):  # Define pattern according to formulae x * tanh(SoftPlus(x)).
        return dict(
            nodes=[
                ('mul', dict(op='Mul')),
                ('tanh', dict(op='Tanh')),
                ('softplus', dict(op='SoftPlus')),
            ],
            edges=[
                ('softplus', 'tanh'),
                ('tanh', 'mul'),
            ])

    def replace_sub_graph(self, graph: Graph, match: [dict, SubgraphMatch]):  # Entry point for the transformation.
        mul = match['mul']  # Get the Node corresponding to matched "mul" node.
        mul_name = mul.soft_get('name', mul.id)
        softplus = match['softplus']  # Get the Node corresponding to the matched "softplus" node.

        # Determine the input port of Mul which gets the 'input' node output.
        input_port_idx = int(mul.in_port(0).get_connection().get_source().node.soft_get('op') == 'Tanh')

        # Check that the same tensor is provided as input to Mul and SoftPlus.
        if mul.in_port(input_port_idx).get_source() != softplus.in_port(0).get_source():
            return

        mish = Mish(graph, {}).create_node()  # Create Mish operation.
        mish.in_port(0).connect(mul.in_port(input_port_idx).get_source())  # Connect input to the Mish.
        mul.out_port(0).get_connection().set_source(mish.out_port(0))  # Reconnect outgoing edge from "mul" to Mish.

        # Rename the created Mish operation to have the name of the "mul" node, which produced the value equal to the
        # Mish output.
        rename_nodes([(mul, mul_name + '/TBR'), (mish, mul_name)])

特定の操作のフロントフェーズ変換

このタイプの変換は、基本クラスとして mo.front.common.replacement.FrontReplacementOp を使用して実装され、次のように動作します。

  1. 変換をトリガーする操作タイプを定義します。

  2. モデル・オプティマイザーは、指定された値と等しい属性 op を持つグラフ内のすべてのノードを検索します。

  3. モデル・オプティマイザーは、一致したノードの各インスタンスに対してグラフ変換の実行に定義された関数を実行します。基本変換クラスのさまざまな関数をオーバーライドして、モデル・オプティマイザーの動作を変えることができます。

    1. replace_sub_graph(self, graph, match) はメソッドをオーバーライドします。この場合、モデル・オプティマイザーはオーバーライドされた関数のみを実行します。graph オブジェクトと、一致したノードを値として持つ単一のキー op を持つ辞書を渡します。変換を記述し、新しく作成したノードをグラフの残りの部分に接続する必要があります。

    2. replace_op(self, graph, node) はメソッドをオーバーライドします。この場合、モデル・オプティマイザーはオーバーライドされた関数のみを実行します。graph オブジェクトと一致したノードを node パラメーターとして渡します。関数が何らかのノードの id を返す場合、この id を持つ Node は、一致したノードのコンシューマーに接続されます。変換を適用した後、一致したノードがグラフから削除されます。

FrontReplacementOp クラスは、単一の操作を op の特定の値と照合し (pattern() 関数を定義する代わりにクラスに op 属性を書き込む) 属性と変換を実行する、単純なメカニズムを提供します。

TensorFlow の Pack 操作を置き換える、extensions/front/Pack.py ファイルからの変換の例を考えてみます。

from openvino.tools.mo.front.common.partial_infer.utils import int64_array
from openvino.tools.mo.front.common.replacement import FrontReplacementOp
from openvino.tools.mo.front.tf.graph_utils import create_op_with_const_inputs
from openvino.tools.mo.graph.graph import Node, Graph, rename_nodes
from openvino.tools.mo.ops.concat import Concat
from openvino.tools.mo.ops.unsqueeze import Unsqueeze


class Pack(FrontReplacementOp):
    op = "Pack"  # Trigger transformation for all nodes in the graph with the op = "Pack" attribute
    enabled = True  # Transformation is enabled.

    def replace_op(self, graph: Graph, node: Node):  # Entry point for the transformation.
        # Create a Concat operation with a number of inputs equal to a number of inputs to Pack.
        out_node = Concat(graph, {'axis': node.axis, 'in_ports_count': len(node.in_ports())}).create_node()
        pack_name = node.soft_get('name', node.id)

        for ind in node.in_ports():
            # Add dimension of size 1 to all inputs of the Pack operation and add them as Concat inputs.
            unsqueeze_node = create_op_with_const_inputs(graph, Unsqueeze, {1: int64_array([node.axis])},
                                                         {'name': node.soft_get('name', node.id) + '/Unsqueeze'})
            node.in_port(ind).get_connection().set_destination(unsqueeze_node.in_port(0))
            unsqueeze_node.out_port(0).connect(out_node.in_port(ind))

        # Rename the created Concat operation to have the name of the "pack" node, which produced the value equal to the
        # Concat output.
        rename_nodes([(node, pack_name + '/TBR'), (out_node, pack_name)])
        return [out_node.id]  # Reconnect the Pack operation consumers to get input from Concat instead.

一般的なフロントフェーズ変換

モデル・オプティマイザーは、汎用フロントフェーズ変換を実装するメカニズムを提供します。このタイプの変換は、基本クラスとして mo.front.common.replacement.FrontReplacementSubgraph または mo.front.common.replacement.FrontReplacementPattern を使用して実装されます。変換を実行する前に、変換が有効になっていることを確認してください。 次に、モデル・オプティマイザーは find_and_replace_pattern(self, graph) メソッドを実行し、Graph オブジェクトを入力として提供します。

Squeeze 操作の正規化を実行する extensions/front/SqueezeNormalize.py ファイルからの汎用フロント変換の例を考えてみます。旧バージョンの操作には属性としてスクイーズのリストがありましたが、現在は別の入力になっています。下位互換性のため、モデル・オプティマイザーの操作は両方のセマンティクスをサポートします。ただし、IR を生成する前に、仕様に従って動作を正規化する必要があります。

import logging as log

from openvino.tools.mo.front.common.partial_infer.utils import int64_array
from openvino.tools.mo.front.common.replacement import FrontReplacementPattern
from openvino.tools.mo.graph.graph import Graph
from openvino.tools.mo.ops.const import Const
from openvino.tools.mo.utils.error import Error


class SqueezeNormalize(FrontReplacementPattern):
    """
    Normalizes inputs of the Squeeze layers. The layers should have two inputs: the input with data and input with the
    dimensions to squeeze. If the second input is omitted then all dimensions of size 1 should be removed.
    """
    enabled = True  # The transformation is enabled.

    def find_and_replace_pattern(self, graph: Graph):  # The function is called unconditionally.
        for squeeze_node in graph.get_op_nodes(op='Squeeze'):  # Iterate over all nodes with op='Squeeze'.
            # If the operation has only 1 input node and no 'squeeze_dims' Node attribute, then convert the attribute to
            # the operation input.
            if len(squeeze_node.in_nodes()) == 1 and squeeze_node.has_valid('squeeze_dims'):
                dims_node = Const(graph, {'name': squeeze_node.id + '/Dims',
                                          'value': int64_array(squeeze_node.squeeze_dims)}).create_node()
                squeeze_node.in_port(1).connect(dims_node.out_port(0))
                del squeeze_node['squeeze_dims']
            # If two inputs already exist, that means the operation is already normalized.
            elif len(squeeze_node.in_nodes()) == 2:
                log.debug('The Squeeze node "{}" is already normalized'.format(squeeze_node.name))
            # In all other cases, raise an error.
            else:
                raise Error('The Squeeze layer "{}" should either have 2 inputs or one input and an "squeeze_dims" '
                            'attribute'.format(squeeze_node.soft_get('name')))

実装の詳細と、これらのフロントフェーズ変換がどのように機能するかについては、mo/front/common/replacement.py ファイルを参照してください。

ノード名パターンのフロントフェーズ変換

TensorFlow はスコープのメカニズムを使用して、関連する操作ノードをグループ化します。特定のタスクを実行するノードは、同じスコープに入れることを推奨します。このアプローチでは、グラフが TensorBoard で確認しやすい論理ブロックに分割されます。実際、スコープは、それに属するノードの共通名のプリフィクスを定義するだけです。

例えば、インセプション・トポロジーには、いくつかのタイプのいわゆるインセプション・ブロックが含まれます。それらには、互いに等しいものもありますが、ネットワークの異なる場所に配置されています。例えば、TensorFlow-Slim 画像分類モデル・ライブラリーの Inception V4 には、全く同じノードと同じ属性セットを持つ Mixed_5bMixed_5c および Mixed_5d インセプション・ブロックがあります。

これらのインセプション・ブロックは InceptionBlock と呼ばれる単一の推論エンジン操作を使用して実装されており、モデル内のブロックをこの操作のインスタンスに置き換える状況を考えてみます。 モデル・オプティマイザーは、ノード名の正規表現 (スコープ) によって定義された操作のサブグラフの変換をトリガーするメカニズムを提供します。この特定のケースでは、パターンの一部は .*InceptionV4/Mixed_5b.*InceptionV4/Mixed_5c および .*InceptionV4/Mixed_5d です。モデルのフリーズ中にすべてのノード名に InceptionV4 プリフィクスが追加されるため、各パターンは .* で始まります。

このタイプの変換は、基本クラスとして mo.front.tf.replacement.FrontReplacementFromConfigFileSubGraph を使用して実装され、次のように動作します。

  1. ノード名のパターンを定義する JSON 構成ファイル・テンプレートを準備します。

  2. --tensorflow_custom_operations_config_update コマンドライン・パラメーターを指定してモデル・オプティマイザーを実行すると、モデル・オプティマイザーは指定されたサブグラフの入力ノードと出力ノードに関する情報を追加します。

  3. モデル・オプティマイザーは、--transformations_config コマンドライン・パラメーターを使用して手順 2 で更新した構成ファイルへのパスを指定した場合にのみ、定義された変換を実行します。

インセプション・ブロック変換用に次の構成ファイル・テンプレートを考慮してください。

[
    {
        "custom_attributes": {
            "attr1_key": "attr1_value",
            "attr2_key": 123456
        },
        "id": "InceptionBlockTransformation",
        "instances": [
            ".*InceptionV4/Mixed_5b",
            ".*InceptionV4/Mixed_5c",
            ".*InceptionV4/Mixed_5d"
        ],
        "match_kind": "scope"
    }
]

設定ファイルには辞書のリストが含まれています。各辞書は 1 つの変換を定義します。各変換はいくつかのパラメーターで定義されます。

  • id - (必須) — 変換の一意の識別子です。これは、クラスと構成ファイルから変換の説明をリンクする変換を実装する Python コードで使用されます。

  • match_kind - (必須) — マッチング・アルゴリズムを指定する文字列です。ノード名パターンの場合、値は scope と同じである必要があります。別の値については、以下の専用セクションで説明します。

  • instances - (必須) — 一致するサブグラフのインスタンスを指定します。これには、scope タイプの一致する種類のノード名プリフィクス・パターンのリストが含まれています。

  • custom_attributes - (オプション) — 変換コードで使用できる属性を含む辞書です。

テンプレート構成ファイルを指す --tensorflow_custom_operations_config_update パラメーターを追加してモデル・オプティマイザーを実行した後、ファイルの内容が 2 つの新しいセクションの inputsoutputs で更新される必要があります。 アップデート後のファイル内容は以下の通りです。

[
    {
        "id": "InceptionBlockTransformation",
        "custom_attributes": {
            "attr1_key": "attr1_value",
            "attr2_key": 123456
        },
        "instances": [
            ".*InceptionV4/Mixed_5b",
            ".*InceptionV4/Mixed_5c",
            ".*InceptionV4/Mixed_5d"
        ],
        "match_kind": "scope",
        "inputs": [
            [
                {
                    "node": "Branch_2/Conv2d_0a_1x1/Conv2D$",
                    "port": 0
                },
                {
                    "node": "Branch_3/AvgPool_0a_3x3/AvgPool$",
                    "port": 0
                },
                {
                    "node": "Branch_1/Conv2d_0a_1x1/Conv2D$",
                    "port": 0
                },
                {
                    "node": "Branch_0/Conv2d_0a_1x1/Conv2D$",
                    "port": 0
                }
            ]
        ],
        "outputs": [
            {
                "node": "concat$",
                "port": 0
            }
        ]
    }
]

inputs キーの値は、サブグラフの入力テンソルを記述するリストの一覧です。最上位リストの各要素は、サブグラフの 1 つの一意の入力テンソルに対応します。各内部リストは、このテンソルを消費するノードのリストと、テンソルが消費されるポート番号を記述します。モデル・オプティマイザーは、instances によって定義されたサブグラフの各インスタンス内で入力ノード名を一意に識別するため、入力ノード名の正規表現を生成します。これらのノードをサブグラフの入力ノードとして示します。

InceptionV4 トポロジーでは、InceptionV4/Mixed_5b ブロックにはサブグラフの外側からの 4 つの入力テンソルがありますが、それらはすべて InceptionV4/Mixed_5a/concat ノードによって生成されます。したがって、inputs の最上位リストには、このテンソルに対応する 1 つのリストが含まれます。サブグラフの 4 つの入力ノードは、InceptionV4/Mixed_5a/concat ノードによって生成されたテンソルを消費します。この場合、4 つの入力ノードすべてが “ポート 0” への入力テンソルを消費します。

ノードを記述する内部リスト内の項目の順序は重要ではありませんが、トップレベルのリスト内の要素の順序は重要です。この順序は、サブグラフが単一のノードに置き換えられた場合に、モデル・オプティマイザーが入力テンソルを新しく生成されたノードにどのようにアタッチするかを定義します。サブグラフの i 番目の入力ノードは、サブグラフ変換コード内の match.single_input_node(i) 呼び出しで取得されます。API の詳細については、以下を参照してください。入力テンソルの順序を変更する必要がある場合、テキストエディターで構成ファイルを編集できます。

outputs キーの値は、テンソルを生成するサブグラフのノードを記述するリストであり、サブグラフ外部に出るか、子ノードを持ちません。これらのノードをサブグラフの出力ノードとして示します。リスト内の要素の順序は重要です。リストの i 番目の要素は、サブグラフの i 番目の出力テンソルを記述します。これは、match.output_node(i) 呼び出しで取得できます。要素の順序は構成ファイルで手動で変更できます。 モデル・オプティマイザーは、サブグラフが単一のノードに置き換えられると、この順序を使用して出力エッジを接続します。

このタイプの変換の他の例については、TensorFlow オブジェクト検出 API モデルの変換ガイドを参照してください。

開始点と終了点を使用したフロントフェーズ変換

このタイプの変換は、基本クラスとして mo.front.tf.replacement.FrontReplacementFromConfigFileSubGraph を使用して実装され、次のように動作します。

  1. “開始” ノードと “終了” ノードの 2 つのノード名のリストを使用して、一致するサブグラフを定義する JSON 構成ファイルを準備します。

  2. モデル・オプティマイザーは、--transformations_config コマンドライン・パラメーターを使用して構成ファイルへのパスを指定した場合にのみ、定義された変換を実行します。モデル・オプティマイザーは、サブグラフを照合するため次の手順を実行します。

    1. グラフのエッジに向かって、すべての開始ノードからグラフの走査を開始します。検索はエンドノードで停止するか、コンシューマーのないノードで停止します。到達したすべてのノードが、一致したサブグラフに追加されます。

    2. サブグラフの開始ノードではない、つまり “開始” リストのノードを除くすべてのノードから別のグラフの走査を開始します。このステップでは、エッジは反対側のエッジ方向に走査されます。新しく到達したノードはすべて、一致したサブグラフに追加されます。このステップは、一致したサブグラフの内部ノードの計算値に必要なノードを追加するのに必要です。

    3. すべての “終了” ノードが “開始” ノードから到達したことを確認します。そうでない場合は、エラーで終了します。

    4. 追加したノード間に Parameter 操作がないことを確認します。存在する場合、サブグラフはモデルの入力に依存します。このような構成は正しくないと見なされるため、モデル・オプティマイザーはエラーで終了します。

このアルゴリズムは、開始ノードと終了ノードの間のすべてのノードと、一致したサブグラフの非入力ノードの計算に必要なノードを検出します。

開始点と終了点を含む変換の JSON 構成ファイルの例は、extensions/front/tf/ssd_support_api_v1.15.json です。

[
    {
        "custom_attributes": {
            "code_type": "caffe.PriorBoxParameter.CENTER_SIZE",
            "pad_mode": "caffe.ResizeParameter.CONSTANT",
            "resize_mode": "caffe.ResizeParameter.WARP",
            "clip_before_nms": false,
            "clip_after_nms": true
        },
        "id": "ObjectDetectionAPISSDPostprocessorReplacement",
        "include_inputs_to_sub_graph": true,
        "include_outputs_to_sub_graph": true,
        "instances": {
            "end_points": [
                "detection_boxes",
                "detection_scores",
                "num_detections"
            ],
            "start_points": [
                "Postprocessor/Shape",
                "Postprocessor/scale_logits",
                "Postprocessor/Tile",
                "Postprocessor/Reshape_1",
                "Postprocessor/Cast_1"
            ]
        },
        "match_kind": "points"
    }
]

ファイルの形式は、ノード名パターンのフロントフェーズ変換セクションの例に似ています。違いは、match_kind パラメーターの値 (points と等しい必要があります) と、instances パラメーターの形式 (開始ノード名と終了ノード名をそれぞれ定義する start_pointsend_points の 2 つのキーを持つ辞書である必要があります) にあります。

include_inputs_to_sub_graph パラメーターと include_outputs_to_sub_graph パラメーターは冗長であり、常に true にである必要があります。

このサブグラフ一致アルゴリズムには、各開始ノードが 1 つの入力のみを持つという制限があります。例えば、Convolution ノードにはデータテンソルと重み付きテンソルという 2 つの入力があるため、このノードを入力として指定することはできません。

ポイントを使用した変換の例については、 TensorFlow オブジェクト検出 API モデルの変換ガイドを参照してください。

変換設定ファイルで有効化された汎用フロントフェーズ変換

このタイプの変換は、汎用フロントフェーズ変換 <generic_front_phase_transformations) と同様に機能しますが、ノード名パターン・フロント・フェーズ変換および開始点と終了点を使用したフロントフェーズ変換と同様にするには、JSON 構成ファイルが必要です。

このタイプの変換の基本クラスは mo.front.common.replacement.FrontReplacementFromConfigFileGeneral です。モデル・オプティマイザーは、transform_graph(self, graph, replacement_descriptions) メソッドを実行し、提供された JSON 構成ファイルの custom_attributes 属性から解析された値を含む Graph オブジェクトと辞書を提供します。

このタイプの変換の構成ファイルの例は、extensions/front/tf/yolo_v1_tiny.json です。

[
  {
    "id": "TFYOLO",
    "match_kind": "general",
    "custom_attributes": {
      "classes": 20,
      "coords": 4,
      "num": 2,
      "do_softmax": 0
    }
  }
]

および対応する変換ファイルは ./extensions/front/YOLO.py です。

from openvino.tools.mo.front.no_op_eraser import NoOpEraser
from openvino.tools.mo.front.standalone_const_eraser import StandaloneConstEraser
from openvino.tools.mo.ops.regionyolo import RegionYoloOp
from openvino.tools.mo.front.tf.replacement import FrontReplacementFromConfigFileGeneral
from openvino.tools.mo.graph.graph import Node, Graph
from openvino.tools.mo.ops.result import Result
from openvino.tools.mo.utils.error import Error


class YoloRegionAddon(FrontReplacementFromConfigFileGeneral):
    """
    Replaces all Result nodes in graph with YoloRegion->Result nodes chain.
    YoloRegion node attributes are taken from configuration file
    """
    replacement_id = 'TFYOLO'  # The identifier matching the "id" attribute in the JSON file.

    def run_after(self):
        return [NoOpEraser, StandaloneConstEraser]

    def transform_graph(self, graph: Graph, replacement_descriptions):
        op_outputs = [n for n, d in graph.nodes(data=True) if 'op' in d and d['op'] == 'Result']
        for op_output in op_outputs:
            last_node = Node(graph, op_output).in_node(0)
            op_params = dict(name=last_node.id + '/YoloRegion', axis=1, end_axis=-1)
            op_params.update(replacement_descriptions)
            region_layer = RegionYoloOp(graph, op_params)
            region_layer_node = region_layer.create_node([last_node])
            # In here, 'axis' from 'dim_attrs' can be removed to avoid permutation from axis = 1 to axis = 2.
            region_layer_node.dim_attrs.remove('axis')
            Result(graph).create_node([region_layer_node])
            graph.remove_node(op_output)

構成ファイルには 3 つのパラメーターしかありません。変換の id 識別子、match_kind (general と等しい必要があります)、および変換でアクセスできるカスタム属性を含む custom_attributes 辞書です。

ミドルフェーズ変換

次の 2 つのミドルフェーズ変換があります。

  1. パターン定義のミドルフェーズ変換は、指定されたパターンと同形で、元のグラフのサブグラフごとにトリガーされます。

  2. 一般的なミドルフェーズ変換

パターン定義のミドルフェーズ変換

このタイプの変換は、基本クラスとして mo.middle.replacement.MiddleReplacementPattern を使用して実装され、パターン定義のミドルフェーズ変換と同じように機能します。違いは次の 2 つです。

  1. 変換エントリーの関数名が replace_pattern(self, graph, match) です。

  2. グラフの構造はフロントフェーズとミドルフェーズで異なるため、グラフを定義するパターンにはデータノードが含まれている必要があります。グラフ構造の変更の詳細については、部分推論を参照してください。

パターン定義のミドルフェーズ変換の例は、extensions/middle/L2NormToNorm.py ファイルを参照してください。

一般的なミドルフェーズ変換

モデル・オプティマイザーは、汎用ミドルフェーズ変換を実装するメカニズムを提供します。このタイプの変換は、基本クラスとして mo.middle.replacement.MiddleReplacementPattern を使用して実装され、汎用フロントフェーズ変換と同じように機能します。唯一の違いは、変換エントリーの関数名が find_and_replace_pattern(self, graph: Graph) であることです。

この変換の例は、extensions/middle/CheckForCycle.py ファイルを参照してください。

バックフェーズ変換

次の 2 つのバックフェーズ変換があります。

  1. パターン定義のバックフェーズ変換は、指定されたパターンと同形で、元のグラフのサブグラフごとにトリガーされます。

  2. 一般的なバックフェーズ変換

バックフェーズ中のグラフレイアウトは常に NCHW です。ただし、フロントとミドルフェーズでは元のモデルが使用していれば NHWC になる可能性があります。詳細は、モデル最適化パイプラインを参照してください。

パターン定義のバックフェーズ変換

このタイプの変換は、基本クラスとして mo.back.replacement.MiddleReplacementPattern を使用して実装され、パターン定義のミドルフェーズ変換と同じように機能します。

パターン定義のバックフェーズ変換の例は、extensions/back/ShufflenetReLUReorder.py ファイルを参照してください。

一般的なバックフェーズ変換

モデル・オプティマイザーは、汎用バックフェーズ変換を実装するメカニズムを提供します。このタイプの変換は、基本クラスとして mo.back.replacement.BackReplacementPattern を使用して実装され、汎用ミドルフェーズ変換と同じように機能します。

この変換の例は、extensions/back/GatherNormalizer.py ファイルを参照してください。