[従来] モデルの一部を切り取り

危険

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

場合によって、モデルを OpenVINO IR に変換する際にモデルの一部を削除する必要があります。ここでは、モデル変換 API パラメーターを使用してカットの方法について説明します。モデルのカットは主に TensorFlow モデルに適用されます。そのため、ここの例では TensorFlow が使用されますが、他のフレームワークにも有効である場合があります。

モデルカットの目的

次の例は、モデルのカットが役立つ、または必要になる状況を示しています。

  • モデルには、既存の OpenVINO 操作に変換できない前処理部分または後処理部分が含まれています。

  • モデルにはトレーニング部分があり、モデル内に保持しておくと便利ですが、推論時には使用されないデータがあります。

  • モデルには、カスタムレイヤーとして簡単に実装できずサポートされない操作が多数含まれているため、複雑すぎて一度に変換できません。

  • OpenVINO™ ランタイムでのモデル変換や推論で問題が発生することがあります。問題を特定するには、モデル内で問題のある領域を反復検索して変換範囲を制限します。

  • 単一のカスタムレイヤーまたはカスタムレイヤーの組み合わせは、デバッグの目的で分離されます。

内部的には、モデル変換 API を実行すると、モデルがロードされ、トポロジーが調査され、既知のレイヤーのリストからそれぞれのレイヤータイプが検索されます。カスタムレイヤーは、リストに含まれないレイヤーです。トポロジーにそのような種類のレイヤーが含まれている場合、モデル変換 API はそれらをカスタムとして分類します。

モデル変換 API パラメーター

モデル変換 API は、モデルの残りの部分を無視しながら、新しい入口ノードと出口ノードを指定する inputoutput コマンドライン・オプションを提供します。

  • input オプションは、モデルへの新しいエントリーポイントとして扱われる入力モデルのレイヤー名のリストを受け入れます。入力として受け入れられるタイプの完全なリストについては、モデル変換 Python API のページを参照してください。

  • output オプションは、モデルからの新しい出口ポイントとして扱われる入力モデルのレイヤー名のリストを受け入れます。

モデルカットに関係のない場合は input オプションが必要です。例えば、モデルに複数の入力が含まれており、input_shape または mean_values オプションが使用されている場合、input_shapemean_values で提供される複数の項目とモデルの入力を正しくマッピングするために、input オプションでは入力ノードの順序を指定します。

モデルのカットは、models/research/slim リポジトリーにある Inception V1 モデルで説明されています。この先に進むには、モデル変換向けにモデルを準備の手順を必ず実行してください。

入出力のないデフォルトの動作

input または output コマンドライン・オプションも使用されない場合、入力モデルは全体が変換されます。TensorFlow グラフ内のすべての Placeholder 操作は、エントリーポイントとして自動的に識別されます。Input レイヤータイプはそれぞれに対して生成されます。コンシューマーを持たないすべてのノードは、自動的に出口ポイントとして識別されます。

Inception_V1 の場合、Placeholder は入力だけです。モデルを TensorBoard で表示すると、入力操作を簡単に見つけることができます。

Placeholder in Inception V1

Reshape は唯一の出力操作であり、完全名 InceptionV1/Logits/Predictions/Reshape_1 の下で、InceptionV1/Logits/Predictions のネストされた名前スコープに囲まれています。

TensorBoard では、以前のバージョンと合わせて、次のようになります。

TensorBoard with predecessors

このモデルを ov.Model に変換します。

from openvino.tools.mo import convert_model
ov_model = convert_model("inception_v1.pb", batch=1)
mo --input_model inception_v1.pb -b 1 --output_dir <OUTPUT_MODEL_DIR>

ov.Model は、ov.serialize() メソッドを使用して中間表現にシリアル化し、モデル構造の探索に使用できます。IR では、モデルの構造に次のレイヤーがあります。

<layer id="286" name="input" precision="FP32" type="Input">
    <output>
        <port id="0">
            <dim>1</dim>
            <dim>3</dim>
            <dim>224</dim>
            <dim>224</dim>
        </port>
    </output>
</layer>

input レイヤーは TensorFlow グラフの Placeholder 操作 input から変換され、同じ名前を持ちます。

ここで -b オプションは、未定義の可能性のあるバッチサイズ (TensorFlow モデルでは -1 としてコード化される) をオーバーライドする変換に使用されます。モデルが定義されたバッチサイズで凍結されている場合、すべての例でこのオプションを省略できます。

モデルの最後のレイヤーは InceptionV1/Logits/Predictions/Reshape_1 で、TensorFlow グラフの出力操作と一致します。

<layer id="389" name="InceptionV1/Logits/Predictions/Reshape_1" precision="FP32" type="Reshape">
    <data axis="0" dim="1,1001" num_axes="-1"/>
    <input>
        <port id="0">
            <dim>1</dim>
            <dim>1001</dim>
        </port>
    </input>
    <output>
        <port id="1">
            <dim>1</dim>
            <dim>1001</dim>
        </port>
    </output>
</layer>

入力と出力が自動的に識別されるため、モデル全体を変換する inputoutput オプションを提供する必要はありません。次のコマンドは、Inception V1 モデルと同等です。

from openvino.tools.mo import convert_model
ov_model = convert_model("inception_v1.pb", batch=1)

ov_model = convert_model("inception_v1.pb", batch=1, input="input", output="InceptionV1/Logits/Predictions/Reshape_1")
mo --input_model inception_v1.pb -b 1 --output_dir <OUTPUT_MODEL_DIR>

mo --input_model inception_v1.pb -b 1 --input input --output InceptionV1/Logits/Predictions/Reshape_1 --output_dir <OUTPUT_MODEL_DIR>

中間表現はどちらの変換でも同一です。モデルに複数の入力および/または出力がある場合も同様です。

モデルのカット

次に、モデルの一部を切り取る方法を考えてみます。ここでは、次のカットに進むため、Inception V1 モデルの最初の畳み込みブロック InceptionV1/InceptionV1/Conv2d_1a_7x7 について説明します。

Inception V1 first convolution block

最後にカットする

最後にモデルをカットする場合は、次のオプションがあります。

  1. 次のコマンドは、InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu の後のモデルの残りの部分を切り取り、このノードをモデルの最後のノードにします。

    from openvino.tools.mo import convert_model
    ov_model = convert_model("inception_v1.pb", batch=1, output="InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu")
    
    mo --input_model inception_v1.pb -b 1 --output=InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu --output_dir <OUTPUT_MODEL_DIR>
    

    結果として得られる中間表現には 3 つのレイヤーがあります。

    <?xml version="1.0" ?>
    <net batch="1" name="model" version="2">
       <layers>
          <layer id="3" name="input" precision="FP32" type="Input">
             <output>
                <port id="0">...</port>
             </output>
          </layer>
          <layer id="5" name="InceptionV1/InceptionV1/Conv2d_1a_7x7/convolution" precision="FP32" type="Convolution">
             <data dilation-x="1" dilation-y="1" group="1" kernel-x="7" kernel-y="7" output="64" pad-x="2" pad-y="2" stride="1,1,2,2" stride-x="2" stride-y="2"/>
             <input>
                <port id="0">...</port>
             </input>
             <output>
                <port id="3">...</port>
             </output>
             <blobs>
                <weights offset="0" size="37632"/>
                <biases offset="37632" size="256"/>
             </blobs>
          </layer>
          <layer id="6" name="InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu" precision="FP32" type="ReLU">
             <input>
                <port id="0">...</port>
             </input>
             <output>
                <port id="1">...</port>
             </output>
          </layer>
       </layers>
       <edges>
          <edge from-layer="3" from-port="0" to-layer="5" to-port="0"/>
          <edge from-layer="5" from-port="3" to-layer="6" to-port="0"/>
       </edges>
    </net>
    

    TensorBoard の図に示されているように、元のモデルには中間表現よりも多くのノードがあります。convert_model() を使用したモデル変換は、バッチ正規化 InceptionV1/InceptionV1/Conv2d_1a_7x7/BatchNorm と畳み込み InceptionV1/InceptionV1/Conv2d_1a_7x7/convolution の融合を含む一連のモデル変換で構成されます。そのため、最終モデルには存在しません。これは output オプションの影響ではなく、バッチ正規化と畳み込みに対するモデル変換 API の一般的な動作です。output の結果は、ReLU レイヤーが変換されたモデルの最後のレイヤーになります。

  2. 次のコマンドは、InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu の出力ポート 0 からのエッジとモデルの残りの部分をカットし、このノードをモデル内の最後のノードにします。

    from openvino.tools.mo import convert_model
    ov_model = convert_model("inception_v1.pb", batch=1, output="InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu:0")
    
    mo --input_model inception_v1.pb -b 1 --output InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu:0 --output_dir <OUTPUT_MODEL_DIR>
    

    結果として得られる中間表現には、前のケースと同じ 3 つのレイヤーがあります。

    <?xml version="1.0" ?>
    <net batch="1" name="model" version="2">
       <layers>
          <layer id="3" name="input" precision="FP32" type="Input">
             <output>
                <port id="0">...</port>
             </output>
          </layer>
          <layer id="5" name="InceptionV1/InceptionV1/Conv2d_1a_7x7/convolution" precision="FP32" type="Convolution">
             <data dilation-x="1" dilation-y="1" group="1" kernel-x="7" kernel-y="7" output="64" pad-x="2" pad-y="2" stride="1,1,2,2" stride-x="2" stride-y="2"/>
             <input>
                <port id="0">...</port>
             </input>
             <output>
                <port id="3">...</port>
             </output>
             <blobs>
                <weights offset="0" size="37632"/>
                <biases offset="37632" size="256"/>
             </blobs>
          </layer>
          <layer id="6" name="InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu" precision="FP32" type="ReLU">
             <input>
                <port id="0">...</port>
             </input>
             <output>
                <port id="1">...</port>
             </output>
          </layer>
       </layers>
       <edges>
              <edge from-layer="3" from-port="0" to-layer="5" to-port="0"/>
              <edge from-layer="5" from-port="3" to-layer="6" to-port="0"/>
       </edges>
    </net>
    

    このタイプのカットは、複数の出力エッジをカットする場合に便利です。

  3. 次のコマンドは、InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu の 0 入力ポートに来るエッジと InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu を含むモデルの残りの入力ポートに来るエッジをカットし、このノードを削除して、前のノード InceptionV1/InceptionV1/Conv2d_1a_7x7/Conv2D をモデル内の最後のノードにします。

    from openvino.tools.mo import convert_model
    ov_model = convert_model("inception_v1.pb", batch=1, output="0:InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu")
    
    mo --input_model inception_v1.pb -b 1 --output=0:InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu --output_dir <OUTPUT_MODEL_DIR>
    

    結果として得られる中間表現には 2 つのレイヤーがあり、これらは前のケースの最初の 2 つのレイヤーと同じです。

    <?xml version="1.0" ?>
    <net batch="1" name="inception_v1" version="2">
       <layers>
          <layer id="0" name="input" precision="FP32" type="Input">
             <output>
                <port id="0">...</port>
             </output>
          </layer>
          <layer id="1" name="InceptionV1/InceptionV1/Conv2d_1a_7x7/Conv2D" precision="FP32" type="Convolution">
             <data auto_pad="same_upper" dilation-x="1" dilation-y="1" group="1" kernel-x="7" kernel-y="7" output="64" pad-b="3" pad-r="3" pad-x="2" pad-y="2" stride="1,1,2,   2"       stride-x="2" stride-y="2"/>
             <input>
                <port id="0">...</port>
             </input>
             <output>
                <port id="3">...</port>
             </output>
             <blobs>
                <weights offset="0" size="37632"/>
                <biases offset="37632" size="256"/>
             </blobs>
          </layer>
       </layers>
       <edges>
          <edge from-layer="0" from-port="0" to-layer="1" to-port="0"/>
       </edges>
    </net>
    

最初からカットする

さらに進んでモデルの先頭をカットし、ReLU レイヤーだけを残す場合は、次のオプションがあります。

  1. 次のパラメーターを使用します。inputoutput はグラフ内の同じノードを指定します。

    from openvino.tools.mo import convert_model
    ov_model = convert_model("inception_v1.pb", batch=1, output="InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu", input="InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu")
    
    mo --input_model=inception_v1.pb -b 1 --output InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu --input InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu --output_dir <OUTPUT_MODEL_DIR>
    

    結果として得られる中間表現は次のようになります。

    <xml version="1.0">
    <net batch="1" name="model" version="2">
       <layers>
          <layer id="0" name="InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu/placeholder_port_0" precision="FP32" type="Input">
             <output>
                <port id="0">...</port>
             </output>
          </layer>
          <layer id="2" name="InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu" precision="FP32" type="ReLU">
             <input>
                <port id="0">...</port>
             </input>
             <output>
                <port id="1">...</port>
             </output>
          </layer>
       </layers>
       <edges>
          <edge from-layer="0" from-port="0" to-layer="2" to-port="0"/>
       </edges>
    </net>
    

    Input レイヤーは、input で指定されたノードから変換されたレイヤー (この場合は InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu) にフィードするために自動的に作成されます。convert_model() は、ReLU ノードを Input レイヤーで置き換えません。このような ov.Model を生成して、そのノードを最終的な中間表現の最初の実行可能ノードにします。したがって、モデル変換では、input に渡されるノードのすべての入力ポートに供給するのに十分な Inputs が作成されます。

    input_shape がコマンドラインで指定されていない場合でも、レイヤーの形状は、元の TensorFlow モデルの先頭から、新しい入力が定義されるポイントまで推論されます。全体または先頭をカットせずに変換したモデルと同じ形状 [1,64,112,112] になります。

  2. レイヤーに着信するエッジをポート番号でカットします。受信ポートを指定するには、input=port:input_node と表記します。ReLU レイヤーの前ですべてをカットするには、InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu ノードのポート 0 に入るエッジをカットします。

    from openvino.tools.mo import convert_model
    ov_model = convert_model("inception_v1.pb", batch=1, input="0:InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu", output="InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu")
    
    mo --input_model inception_v1.pb -b 1 --input 0:InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu --output InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu --output_dir <OUTPUT_MODEL_DIR>
    

    結果として得られる中間表現は次のようになります。

    <xml version="1.0">
    <net batch="1" name="model" version="2">
       <layers>
          <layer id="0" name="InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu/placeholder_port_0" precision="FP32" type="Input">
             <output>
                <port id="0">...</port>
             </output>
          </layer>
          <layer id="2" name="InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu" precision="FP32" type="ReLU">
             <input>
                <port id="0">...</port>
             </input>
             <output>
                <port id="1">...</port>
             </output>
          </layer>
       </layers>
       <edges>
          <edge from-layer="0" from-port="0" to-layer="2" to-port="0"/>
       </edges>
    </net>
    

    Input レイヤーは、input で指定されたノードから変換されたレイヤー (この場合は InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu) にフィードするために自動的に作成されます。convert_model()ReLU ノードを Input レイヤーで置き換えるのではなく、ノードを最終的な中間表現の最初の実行可能ノードにするため ov.Model を生成します。したがって、convert_model() は、Input に渡されるノードのすべての入力ポートに供給できる十分な input を作成します。

    input_shape がコマンドラインで指定されていない場合でも、レイヤーの形状は、元の TensorFlow モデルの先頭から、新しい入力が定義されるポイントまで推論されます。全体または先頭をカットせずに変換したモデルと同じ形状 [1,64,112,112] になります。

  3. ポート番号ごとにレイヤーから出ているエッジをカットします。送信ポートを指定するには、input=input_node:port と表記します。ReLU レイヤーの前ですべてをカットするには、InceptionV1/InceptionV1/Conv2d_1a_7x7/BatchNorm/batchnorm/add_1 ノードから ReLU までのエッジをカットします。

    from openvino.tools.mo import convert_model
    ov_model = convert_model("inception_v1.pb", batch=1, input="InceptionV1/InceptionV1/Conv2d_1a_7x7/BatchNorm/batchnorm/add_1:0", output="InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu")
    
    mo --input_model inception_v1.pb -b 1 --input InceptionV1/InceptionV1/Conv2d_1a_7x7/BatchNorm/batchnorm/add_1:0 --output InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu --output_dir <OUTPUT_MODEL_DIR>
    

    結果として得られる中間表現は次のようになります。

    <xml version="1.0">
    <net batch="1" name="model" version="2">
       <layers>
          <layer id="0" name="InceptionV1/InceptionV1/Conv2d_1a_7x7/BatchNorm/batchnorm/add_1/placeholder_out_port_0" precision="FP32" type="Input">
             <output>
                <port id="0">...</port>
             </output>
          </layer>
          <layer id="1" name="InceptionV1/InceptionV1/Conv2d_1a_7x7/Relu" precision="FP32" type="ReLU">
             <input>
                <port id="0">...</port>
             </input>
             <output>
                <port id="1">...</port>
             </output>
             layer>
       </layers>
       <edges>
          <edge from-layer="0" from-port="0" to-layer="1" to-port="0"/>
       </edges>
    </net>
    

複数の入力ポートを備えた入力

複数の入力ポートを含む操作があります。ここで考える例では、畳み込み InceptionV1/InceptionV1/Conv2d_1a_7x7/convolution がそのような操作です。input_shape が指定されていない場合、ノードの動的入力ポートごとに新しい Input レイヤーが作成されます。ポートが定数 BLOB として評価される場合、この定数はモデル内に残り、対応する入力レイヤーは作成されません。このモデルで使用される TensorFlow 畳み込みには 2 つのポートが含まれています。

  • ポート 0: 畳み込み用の入力テンソル (動的)

  • ポート 1: 畳み込みの重み (定数)

この動作に従って、convert_model() はポート 0 のみの Input レイヤーを作成し、ポート 1 を定数として残します。次の結果が得られます。

from openvino.tools.mo import convert_model
ov_model = convert_model("inception_v1.pb", batch=1, input="InceptionV1/InceptionV1/Conv2d_1a_7x7/convolution")
mo --input_model inception_v1.pb -b 1 --input InceptionV1/InceptionV1/Conv2d_1a_7x7/convolution --output_dir <OUTPUT_MODEL_DIR>

結果はモデル全体の変換結果と同じになります。これは、この畳み込みが Inception V1 で最初に実行可能な操作であるためです。

input_shape が入力形状をオーバーライドする試みとして使用される場合、動作は異なります。

from openvino.tools.mo import convert_model
ov_model = convert_model("inception_v1.pb", input="InceptionV1/InceptionV1/Conv2d_1a_7x7/convolution", input_shape=[1,224,224,3])
mo --input_model inception_v1.pb--input=InceptionV1/InceptionV1/Conv2d_1a_7x7/convolution --input_shape [1,224,224,3]  --output_dir <OUTPUT_MODEL_DIR>

エラーが発生します (詳細については、モデル変換 FAQ を参照してください)。

[ ERROR ]  Node InceptionV1/InceptionV1/Conv2d_1a_7x7/convolution has more than 1 input and input shapes were provided.
Try not to provide input shapes or specify input port with PORT:NODE notation, where PORT is an integer.
For more information, see FAQ #30

input_shape が指定され、ノードに複数の入力ポートが含まれている場合、入力ノード名と入力ポートのインデックスを指定する必要があります。入力ポートのインデックスは、ノード名の前に ‘:’ を区切り文字として指定します (PORT:NODE)。この場合、ノード InceptionV1/InceptionV1/Conv2d_1a_7x7/convolution のポート・インデックス 0 は、0:InceptionV1/InceptionV1/Conv2d_1a_7x7/convolution として指定する必要があります。

正しいコマンドラインは、次のようになります。

from openvino.tools.mo import convert_model
ov_model = convert_model("inception_v1.pb", input="0:InceptionV1/InceptionV1/Conv2d_1a_7x7/convolution", input_shape=[1,224,224,3])
mo --input_model inception_v1.pb --input 0:InceptionV1/InceptionV1/Conv2d_1a_7x7/convolution --input_shape=[1,224,224,3] --output_dir <OUTPUT_MODEL_DIR>