このページは Cloud Translation API によって翻訳されました。
Switch to English

カスタムオペレーター

TensorFlow Lite組み込み演算子ライブラリは限られた数のTensorFlow演算子のみをサポートするため、すべてのモデルが変換可能であるとは限りません。詳細については、 オペレーターの互換性を参照してください。

変換を可能にするために、ユーザーは、カスタムオペレーターと呼ばれる、サポートされていないTensorFlowオペレーターのカスタム実装をTensorFlow Liteで提供できます。 代わりに、サポートされていない(またはサポートされている)一連のTensorFlow演算子を単一の融合最適化カスタム演算子に結合する場合は、 演算子融合を参照してください。

カスタムオペレーターの使用は、3つのステップで構成されています。

  • TensorFlow Graph DefまたはSavedModelが正しい名前のTensorFlow Lite演算子を参照していることを確認してください。

  • ランタイムがグラフの演算子とパラメーターを実行可能なC / C ++コードにマップする方法をランタイムが認識できるように、カスタムカーネルをTensorFlow Liteに登録します。

  • オペレーターの正確性とパフォーマンスをそれぞれテストおよびプロファイルします。カスタムオペレーターのみをテストする場合は、カスタムオペレーターのみでベンチマークモデルコードを使用してモデルを作成することをお勧めします。

以下では、 Sinの定義の完全な例と、カスタム演算子を含む既存の変換プロセスへのリンクについて説明します。

Sinのカスタムオペレーターの作成

TensorFlow LiteにはないTensorFlow演算子をサポートする例を見てみましょう。 Sin演算子を使用しており、 offsetがトレーニング可能な関数y = sin(x + offset)非常に単純なモデルを構築していると仮定します。

TensorFlowからモデルを生成する

TensorFlowモデルをトレーニングするコードは次のようになります。

offset = tf.get_variable("offset", [1,], tf.float32)
x = tf.placeholder(tf.float32, shape=(None,))
y = tf.sin(x + offset)
y_ = tf.placeholder(tf.float32, shape=(None,))
loss = tf.reduce_sum(tf.square(y - y_))
optimizer = tf.train.GradientDescentOptimizer(0.001)
train = optimizer.minimize(loss)

--allow_custom_ops引数を指定したTensorFlow Lite最適化コンバーターを使用してこのモデルをTensorFlow Lite形式に変換し、デフォルトのインタープリターで実行すると、インタープリターは次のエラーメッセージを生成します。

Didn't find custom op for name 'Sin'
Registration failed.

TensorFlow Liteランタイムでのカーネルの定義

TensorFlow Liteでopを使用するために必要なことは、2つの関数( PrepareおよびEvalPrepare定義し、 TfLiteRegistrationを作成することTfLiteRegistrationです。

TfLiteStatus SinPrepare(TfLiteContext* context, TfLiteNode* node) {
  using namespace tflite;
  TF_LITE_ENSURE_EQ(context, NumInputs(node), 1);
  TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1);

  const TfLiteTensor* input = GetInput(context, node, 0);
  TfLiteTensor* output = GetOutput(context, node, 0);

  int num_dims = NumDimensions(input);

  TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims);
  for (int i=0; i<num_dims; ++i) {
    output_size->data[i] = input->dims->data[i];
  }

  return context->ResizeTensor(context, output, output_size);
}

TfLiteStatus SinEval(TfLiteContext* context, TfLiteNode* node) {
  using namespace tflite;
  const TfLiteTensor* input = GetInput(context, node,0);
  TfLiteTensor* output = GetOutput(context, node,0);

  float* input_data = input->data.f;
  float* output_data = output->data.f;

  size_t count = 1;
  int num_dims = NumDimensions(input);
  for (int i = 0; i < num_dims; ++i) {
    count *= input->dims->data[i];
  }

  for (size_t i=0; i<count; ++i) {
    output_data[i] = sin(input_data[i]);
  }
  return kTfLiteOk;
}

TfLiteRegistration* Register_SIN() {
  static TfLiteRegistration r = {nullptr, nullptr, SinPrepare, SinEval};
  return &r;
}

OpResolver初期化するときに、カスタムopをリゾルバーに追加します。これにより、オペレーターがTensorflow Liteに登録され、TensorFlow Liteが新しい実装を使用できるようになります。注最後の2つの引数そのTfLiteRegistrationに対応SinPrepareSinEvalカスタムOPのために定義された関数。あなたが使用している場合SinInitSinFree OPで使用される変数を初期化するために、それぞれの領域を解放するための機能を、彼らは最初の二つの引数に追加されますTfLiteRegistration 。これらの引数は、この例ではnullptrに設定されています。

tflite::ops::builtin::BuiltinOpResolver builtins;
builtins.AddCustom("Sin", Register_SIN());

Javaでカスタムオペレーターを定義する場合は、現在、独自のカスタムJNIレイヤーを構築し、このjniコードで独自のAARをコンパイルする必要があります。同様に、Pythonで使用可能なこれらの演算子を定義する場合は、 Pythonラッパーコードに登録を配置できます

上記と同様のプロセスに従って、単一の演算子の代わりに一連の操作をサポートできます。必要なだけAddCustom演算子を追加してください。また、 BuiltinOpResolverまた、あなたが使用して組み込みコマンドの実装を上書きすることができますAddBuiltin

ベストプラクティス

  1. メモリの割り当てと割り当て解除を慎重に最適化します。 Prepareでのメモリの割り当てはInvokeよりも効率的であり、ループの前にメモリを割り当てることは、すべての反復よりも優れています。自分自身を割り当てるのではなく、一時的なテンソルデータを使用します(項目2を参照)。できるだけコピーするのではなく、ポインタ/参照を使用してください。

  2. 操作全体を通じてデータ構造が維持される場合は、一時テンソルを使用してメモリを事前に割り当てることをお勧めします。他の関数でテンソルインデックスを参照するには、OpData構造体を使用する必要がある場合があります。 たたみ込みについては、 カーネル内の例を参照してください。サンプルコードスニペットは以下の通りです

auto* op_data = reinterpret_cast<OpData*>(node->user_data);
TfLiteIntArrayFree(node->temporaries);
node->temporaries = TfLiteIntArrayCreate(1);
node->temporaries->data[0] = op_data->temp_tensor_index;
TfLiteTensor* temp_tensor = &context->tensors[op_data->temp_tensor_index];
temp_tensor->type =  kTfLiteFloat32;
temp_tensor->allocation_type = kTfLiteArenaRw;
  1. メモリの浪費にあまりコストがかからない場合は、動的に割り当てられるstd::vectorを実行の反復ごとに使用するのではなく、静的な固定サイズの配列(またはResize事前に割り当てられたstd::vector )を使用することをおstd::vectorます。

  2. バイナリサイズに影響するため、まだ存在しない標準ライブラリコンテナーテンプレートのインスタンス化は避けてください。たとえば、他のカーネルには存在しないstd::mapが操作に必要な場合、直接インデックスマッピングでstd::vectorを使用すると、バイナリサイズを小さく保ちながら機能します。他のカーネルが洞察を得るために使用しているものを確認(または質問)します。

  3. mallocによって返されたメモリへのポインタを確認します。このポインターがnullptr場合、そのポインターを使用して操作を実行しないでください。 mallocmallocを実行してエラー終了した場合は、終了する前にメモリの割り当てを解除してください。

  4. TF_LITE_ENSURE(context, condition)を使用して、特定の条件を確認してください。 TF_LITE_ENSUREを使用する場合、コードでメモリがハングしたTF_LITE_ENSUREはいけません。つまり、リークするリソースが割り当てられる前にこれらのマクロを使用する必要があります。