[従来] モデルの一部を切り取り¶
危険
ここで説明されているコードは非推奨になりました。従来のソリューションの適用を避けるため使用しないでください。下位互換性を確保するためにしばらく保持されますが、最新のアプリケーションでは使用してはなりません。
場合によって、モデルを OpenVINO IR に変換する際にモデルの一部を削除する必要があります。ここでは、モデル変換 API パラメーターを使用してカットの方法について説明します。モデルのカットは主に TensorFlow モデルに適用されます。そのため、ここの例では TensorFlow が使用されますが、他のフレームワークにも有効である場合があります。
モデルカットの目的¶
次の例は、モデルのカットが役立つ、または必要になる状況を示しています。
モデルには、既存の OpenVINO 操作に変換できない前処理部分または後処理部分が含まれています。
モデルにはトレーニング部分があり、モデル内に保持しておくと便利ですが、推論時には使用されないデータがあります。
モデルには、カスタムレイヤーとして簡単に実装できずサポートされない操作が多数含まれているため、複雑すぎて一度に変換できません。
OpenVINO™ ランタイムでのモデル変換や推論で問題が発生することがあります。問題を特定するには、モデル内で問題のある領域を反復検索して変換範囲を制限します。
単一のカスタムレイヤーまたはカスタムレイヤーの組み合わせは、デバッグの目的で分離されます。
注
内部的には、モデル変換 API を実行すると、モデルがロードされ、トポロジーが調査され、既知のレイヤーのリストからそれぞれのレイヤータイプが検索されます。カスタムレイヤーは、リストに含まれないレイヤーです。トポロジーにそのような種類のレイヤーが含まれている場合、モデル変換 API はそれらをカスタムとして分類します。
モデル変換 API パラメーター¶
モデル変換 API は、モデルの残りの部分を無視しながら、新しい入口ノードと出口ノードを指定する input
と output
コマンドライン・オプションを提供します。
input
オプションは、モデルへの新しいエントリーポイントとして扱われる入力モデルのレイヤー名のリストを受け入れます。入力として受け入れられるタイプの完全なリストについては、モデル変換 Python API のページを参照してください。output
オプションは、モデルからの新しい出口ポイントとして扱われる入力モデルのレイヤー名のリストを受け入れます。
モデルカットに関係のない場合は input
オプションが必要です。例えば、モデルに複数の入力が含まれており、input_shape
または mean_values
オプションが使用されている場合、input_shape
と mean_values
で提供される複数の項目とモデルの入力を正しくマッピングするために、input
オプションでは入力ノードの順序を指定します。
モデルのカットは、models/research/slim
リポジトリーにある Inception V1 モデルで説明されています。この先に進むには、モデル変換向けにモデルを準備の手順を必ず実行してください。
入出力のないデフォルトの動作¶
input
または output
コマンドライン・オプションも使用されない場合、入力モデルは全体が変換されます。TensorFlow グラフ内のすべての Placeholder
操作は、エントリーポイントとして自動的に識別されます。Input
レイヤータイプはそれぞれに対して生成されます。コンシューマーを持たないすべてのノードは、自動的に出口ポイントとして識別されます。
Inception_V1 の場合、Placeholder
は入力だけです。モデルを TensorBoard で表示すると、入力操作を簡単に見つけることができます。
Reshape
は唯一の出力操作であり、完全名 InceptionV1/Logits/Predictions/Reshape_1
の下で、InceptionV1/Logits/Predictions
のネストされた名前スコープに囲まれています。
TensorBoard では、以前のバージョンと合わせて、次のようになります。
このモデルを 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>
入力と出力が自動的に識別されるため、モデル全体を変換する input
と output
オプションを提供する必要はありません。次のコマンドは、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
について説明します。
最後にカットする¶
最後にモデルをカットする場合は、次のオプションがあります。
-
次のコマンドは、
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
レイヤーが変換されたモデルの最後のレイヤーになります。 -
次のコマンドは、
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>
このタイプのカットは、複数の出力エッジをカットする場合に便利です。
-
次のコマンドは、
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
レイヤーだけを残す場合は、次のオプションがあります。
-
次のパラメーターを使用します。
input
とoutput
はグラフ内の同じノードを指定します。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]
になります。 -
レイヤーに着信するエッジをポート番号でカットします。受信ポートを指定するには、
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]
になります。 -
ポート番号ごとにレイヤーから出ているエッジをカットします。送信ポートを指定するには、
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>