{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "6bYaCABobL5q" }, "source": [ "##### Copyright 2018 The TensorFlow Authors." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "cellView": "form", "execution": { "iopub.execute_input": "2024-01-11T18:43:06.566178Z", "iopub.status.busy": "2024-01-11T18:43:06.565712Z", "iopub.status.idle": "2024-01-11T18:43:06.570361Z", "shell.execute_reply": "2024-01-11T18:43:06.569452Z" }, "id": "FlUw7tSKbtg4" }, "outputs": [], "source": [ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", "#\n", "# https://www.apache.org/licenses/LICENSE-2.0\n", "#\n", "# Unless required by applicable law or agreed to in writing, software\n", "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", "# See the License for the specific language governing permissions and\n", "# limitations under the License." ] }, { "cell_type": "markdown", "metadata": { "id": "xc1srSc51n_4" }, "source": [ "# SavedModel 形式の使用" ] }, { "cell_type": "markdown", "metadata": { "id": "-nBUqG2rchGH" }, "source": [ "
![]() | \n",
" ![]() | \n",
" ![]() | \n",
" ![]() | \n",
"
ValueError: Could not find matching function to call for canonicalized inputs ((<tf.Tensor 'args_0:0' shape=(1,) dtype=float32>,), {}). Only existing signatures are [((TensorSpec(shape=(), dtype=tf.float32, name=u'x'),), {})].\n", "" ] }, { "cell_type": "markdown", "metadata": { "id": "4Vsva3UZ-2sf" }, "source": [ "### 基本のファインチューニング\n", "\n", "変数オブジェクトを使用でき、インポートされた関数を通じてバックプロパゲーションできます。単純なケースの場合、SavedModel をファインチューニング(再トレーニング)するには、これで十分です。" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T18:43:26.698181Z", "iopub.status.busy": "2024-01-11T18:43:26.697957Z", "iopub.status.idle": "2024-01-11T18:43:26.704654Z", "shell.execute_reply": "2024-01-11T18:43:26.704094Z" }, "id": "PEkQNarJ-7nT" }, "outputs": [], "source": [ "optimizer = tf.keras.optimizers.SGD(0.05)\n", "\n", "def train_step():\n", " with tf.GradientTape() as tape:\n", " loss = (10. - imported(tf.constant(2.))) ** 2\n", " variables = tape.watched_variables()\n", " grads = tape.gradient(loss, variables)\n", " optimizer.apply_gradients(zip(grads, variables))\n", " return loss" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T18:43:26.707579Z", "iopub.status.busy": "2024-01-11T18:43:26.707352Z", "iopub.status.idle": "2024-01-11T18:43:27.324780Z", "shell.execute_reply": "2024-01-11T18:43:27.324049Z" }, "id": "p41NM6fF---3" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "loss=36.00 v=3.20\n", "loss=12.96 v=3.92\n", "loss=4.67 v=4.35\n", "loss=1.68 v=4.61\n", "loss=0.60 v=4.77\n", "loss=0.22 v=4.86\n", "loss=0.08 v=4.92\n", "loss=0.03 v=4.95\n", "loss=0.01 v=4.97\n", "loss=0.00 v=4.98\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n", "I0000 00:00:1704998607.284908 100396 device_compiler.h:186] Compiled cluster using XLA! This line is logged at most once for the lifetime of the process.\n" ] } ], "source": [ "for _ in range(10):\n", " # \"v\" approaches 5, \"loss\" approaches 0\n", " print(\"loss={:.2f} v={:.2f}\".format(train_step(), imported.v.numpy()))" ] }, { "cell_type": "markdown", "metadata": { "id": "XuXtkHSD_KSW" }, "source": [ "### 一般的な微調整\n", "\n", "Keras の SavedModel は、より高度な微調整の事例に対処できる、プレーンな `__call__` よりも[詳細な内容](https://github.com/tensorflow/community/blob/master/rfcs/20190509-keras-saved-model.md#serialization-details)を提供します。TensorFlow Hub は、微調整の目的で共有される SavedModel に、該当する場合は次の項目を提供することをお勧めします。\n", "\n", "- モデルに、フォワードパスがトレーニングと推論で異なるドロップアウトまたはほかのテクニックが使用されている場合(バッチの正規化など)、`__call__` メソッドは、オプションのPython 重視の `training=` 引数を取ります。この引数は、デフォルトで `False` になりますが、`True` に設定することができます。\n", "- `__call__` 属性の隣には、対応する変数リストを伴う `.variable` と `.trainable_variable` 属性があります。もともとトレーニング可能であっても、微調整中には凍結されるべき変数は、`.trainable_variables` から省略されます。\n", "- レイヤとサブモデルの属性として重みの正規化を表現する Keras のようなフレームワークのために、`.regularization_losses` 属性も使用できます。この属性は、値が合計損失に追加することを目的とした引数無しの関数のリストを保有します。\n", "\n", "最初の MobileNet の例に戻ると、これらの一部が動作していることを確認できます。" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T18:43:27.328802Z", "iopub.status.busy": "2024-01-11T18:43:27.328302Z", "iopub.status.idle": "2024-01-11T18:43:29.564653Z", "shell.execute_reply": "2024-01-11T18:43:29.563910Z" }, "id": "Y6EUFdY8_PRD" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MobileNet has 83 trainable variables: conv1/kernel:0, conv1_bn/gamma:0, conv1_bn/beta:0, conv_dw_1/depthwise_kernel:0, conv_dw_1_bn/gamma:0, ...\n" ] } ], "source": [ "loaded = tf.saved_model.load(mobilenet_save_path)\n", "print(\"MobileNet has {} trainable variables: {}, ...\".format(\n", " len(loaded.trainable_variables),\n", " \", \".join([v.name for v in loaded.trainable_variables[:5]])))" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T18:43:29.568028Z", "iopub.status.busy": "2024-01-11T18:43:29.567765Z", "iopub.status.idle": "2024-01-11T18:43:29.572543Z", "shell.execute_reply": "2024-01-11T18:43:29.571919Z" }, "id": "B-mQJ8iP_R0h" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MobileNet also has 54 non-trainable variables: conv1_bn/moving_mean:0, conv1_bn/moving_variance:0, conv_dw_1_bn/moving_mean:0, ...\n" ] } ], "source": [ "trainable_variable_ids = {id(v) for v in loaded.trainable_variables}\n", "non_trainable_variables = [v for v in loaded.variables\n", " if id(v) not in trainable_variable_ids]\n", "print(\"MobileNet also has {} non-trainable variables: {}, ...\".format(\n", " len(non_trainable_variables),\n", " \", \".join([v.name for v in non_trainable_variables[:3]])))" ] }, { "cell_type": "markdown", "metadata": { "id": "qGlHlbd3_eyO" }, "source": [ "## エクスポート中のシグネチャの指定\n", "\n", "TensorFlow Serving や `saved_model_cli` のようなツールは、SavedModel と対話できます。これらのツールがどの ConcreteFunctions を使用するか判定できるように、サービングシグネチャを指定する必要があります。`tf.keras.Model` は、サービングシグネチャを自動的に指定しますが、カスタムモジュールに対して明示的に宣言する必要があります。\n", "\n", "重要: モデルを TensorFlow 2.x と Python 以外の環境にエクスポートする必要がない限り、おそらく明示的にシグネチャをエクスポートする必要はありません。特定の関数に入力シグネチャを強要する方法を探している場合は、`tf.function` への [`input_signature`](https://www.tensorflow.org/api_docs/python/tf/function#args_1) 引数をご覧ください。\n", "\n", "デフォルトでは、シグネチャはカスタム `tf.Module` で宣言されません。" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T18:43:29.575704Z", "iopub.status.busy": "2024-01-11T18:43:29.575453Z", "iopub.status.idle": "2024-01-11T18:43:29.578669Z", "shell.execute_reply": "2024-01-11T18:43:29.578063Z" }, "id": "h-IB5Xa0NxLa" }, "outputs": [], "source": [ "assert len(imported.signatures) == 0" ] }, { "cell_type": "markdown", "metadata": { "id": "BiNtaMZSI8Tb" }, "source": [ "サービングシグネチャを宣言するには、`signatures` kwarg を使用して ConcreteFunction 指定します。単一のシグネチャを指定する場合、シグネチャキーは `'serving_default'` となり、定数 `tf.saved_model.DEFAULT_SERVING_SIGNATURE_DEF_KEY` として保存されます。" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T18:43:29.581893Z", "iopub.status.busy": "2024-01-11T18:43:29.581658Z", "iopub.status.idle": "2024-01-11T18:43:29.653669Z", "shell.execute_reply": "2024-01-11T18:43:29.653040Z" }, "id": "_pAdgIORR2yH" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Tracing with Tensor(\"x:0\", dtype=float32)\n", "Tracing with Tensor(\"x:0\", dtype=float32)\n", "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpwpnio7cy/module_with_signature/assets\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpwpnio7cy/module_with_signature/assets\n" ] } ], "source": [ "module_with_signature_path = os.path.join(tmpdir, 'module_with_signature')\n", "call = module.__call__.get_concrete_function(tf.TensorSpec(None, tf.float32))\n", "tf.saved_model.save(module, module_with_signature_path, signatures=call)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T18:43:29.656839Z", "iopub.status.busy": "2024-01-11T18:43:29.656597Z", "iopub.status.idle": "2024-01-11T18:43:29.689713Z", "shell.execute_reply": "2024-01-11T18:43:29.689050Z" }, "id": "nAzRHR0UT4hv" }, "outputs": [ { "data": { "text/plain": [ "['serving_default']" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "imported_with_signatures = tf.saved_model.load(module_with_signature_path)\n", "list(imported_with_signatures.signatures.keys())\n" ] }, { "cell_type": "markdown", "metadata": { "id": "_gH91j1IR4tq" }, "source": [ "複数のシグネチャをエクスポートするには、シグネチャキーのディクショナリを ConcreteFunction に渡します。各シグネチャキーは 1 つの ConcreteFunction に対応します。" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T18:43:29.692705Z", "iopub.status.busy": "2024-01-11T18:43:29.692472Z", "iopub.status.idle": "2024-01-11T18:43:29.755819Z", "shell.execute_reply": "2024-01-11T18:43:29.755227Z" }, "id": "6VYAiQmLUiox" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpwpnio7cy/module_with_multiple_signatures/assets\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpwpnio7cy/module_with_multiple_signatures/assets\n" ] } ], "source": [ "module_multiple_signatures_path = os.path.join(tmpdir, 'module_with_multiple_signatures')\n", "signatures = {\"serving_default\": call,\n", " \"array_input\": module.__call__.get_concrete_function(tf.TensorSpec([None], tf.float32))}\n", "\n", "tf.saved_model.save(module, module_multiple_signatures_path, signatures=signatures)" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T18:43:29.758973Z", "iopub.status.busy": "2024-01-11T18:43:29.758735Z", "iopub.status.idle": "2024-01-11T18:43:29.793084Z", "shell.execute_reply": "2024-01-11T18:43:29.792519Z" }, "id": "8IPx_0RWEx07" }, "outputs": [ { "data": { "text/plain": [ "['serving_default', 'array_input']" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "imported_with_multiple_signatures = tf.saved_model.load(module_multiple_signatures_path)\n", "list(imported_with_multiple_signatures.signatures.keys())" ] }, { "cell_type": "markdown", "metadata": { "id": "43_Qv2W_DJZZ" }, "source": [ "デフォルトでは、出力されたテンソル名は、`output_0` というようにかなり一般的な名前です。出力の名前を制御するには、出力名を出力にマッピングするディクショナリを返すように `tf.function` を変更します。入力の名前は Python 関数の引数名から取られます。" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T18:43:29.796199Z", "iopub.status.busy": "2024-01-11T18:43:29.795978Z", "iopub.status.idle": "2024-01-11T18:43:29.878102Z", "shell.execute_reply": "2024-01-11T18:43:29.877497Z" }, "id": "ACKPl1X8G1gw" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpwpnio7cy/module_with_output_name/assets\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpwpnio7cy/module_with_output_name/assets\n" ] } ], "source": [ "class CustomModuleWithOutputName(tf.Module):\n", " def __init__(self):\n", " super(CustomModuleWithOutputName, self).__init__()\n", " self.v = tf.Variable(1.)\n", "\n", " @tf.function(input_signature=[tf.TensorSpec(None, tf.float32)])\n", " def __call__(self, x):\n", " return {'custom_output_name': x * self.v}\n", "\n", "module_output = CustomModuleWithOutputName()\n", "call_output = module_output.__call__.get_concrete_function(tf.TensorSpec(None, tf.float32))\n", "module_output_path = os.path.join(tmpdir, 'module_with_output_name')\n", "tf.saved_model.save(module_output, module_output_path,\n", " signatures={'serving_default': call_output})" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T18:43:29.881406Z", "iopub.status.busy": "2024-01-11T18:43:29.881156Z", "iopub.status.idle": "2024-01-11T18:43:29.910417Z", "shell.execute_reply": "2024-01-11T18:43:29.909758Z" }, "id": "1yGVy4MuH-V0" }, "outputs": [ { "data": { "text/plain": [ "{'custom_output_name': TensorSpec(shape=
$ saved_model_cli show --dir /tmp/saved_model_dir --tag_set serve,gpu\n", "\n", "\n", "特定の `SignatureDef` に対するすべての入力と出力の TensorInfo を表示するには、 `signature_def` オプションに `SignatureDef` キーを渡します。これは、後で計算グラフを実行する際の入力テンソルのテンソルキー値、dtype、および形状を知るうえで非常に役立ちます。次に例を示します。\n", "\n", "```\n", "$ saved_model_cli show --dir \\\n", "/tmp/saved_model_dir --tag_set serve --signature_def serving_default\n", "The given SavedModel SignatureDef contains the following input(s):\n", " inputs['x'] tensor_info:\n", " dtype: DT_FLOAT\n", " shape: (-1, 1)\n", " name: x:0\n", "The given SavedModel SignatureDef contains the following output(s):\n", " outputs['y'] tensor_info:\n", " dtype: DT_FLOAT\n", " shape: (-1, 1)\n", " name: y:0\n", "Method name is: tensorflow/serving/predict\n", "```\n", "\n", "SavedModel で利用できるすべての情報を表示するには、次の例のように `--all` オプションを使用します。\n", "\n", "
$ saved_model_cli show --dir /tmp/saved_model_dir --all\n", "MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:\n", "\n", "signature_def['classify_x2_to_y3']:\n", " The given SavedModel SignatureDef contains the following input(s):\n", " inputs['inputs'] tensor_info:\n", " dtype: DT_FLOAT\n", " shape: (-1, 1)\n", " name: x2:0\n", " The given SavedModel SignatureDef contains the following output(s):\n", " outputs['scores'] tensor_info:\n", " dtype: DT_FLOAT\n", " shape: (-1, 1)\n", " name: y3:0\n", " Method name is: tensorflow/serving/classify\n", "\n", "...\n", "\n", "signature_def['serving_default']:\n", " The given SavedModel SignatureDef contains the following input(s):\n", " inputs['x'] tensor_info:\n", " dtype: DT_FLOAT\n", " shape: (-1, 1)\n", " name: x:0\n", " The given SavedModel SignatureDef contains the following output(s):\n", " outputs['y'] tensor_info:\n", " dtype: DT_FLOAT\n", " shape: (-1, 1)\n", " name: y:0\n", " Method name is: tensorflow/serving/predict\n", "\n", "\n", "### `run` コマンド\n", "\n", "グラフ計算を実行して、入力を渡してから出力を表示(または保存)するには `run` コマンドを呼び出します。次に構文を示します。\n", "\n", "```\n", "usage: saved_model_cli run [-h] --dir DIR --tag_set TAG_SET --signature_def\n", " SIGNATURE_DEF_KEY [--inputs INPUTS]\n", " [--input_exprs INPUT_EXPRS]\n", " [--input_examples INPUT_EXAMPLES] [--outdir OUTDIR]\n", " [--overwrite] [--tf_debug]\n", "```\n", "\n", "`run` コマンドでは、次の 3 つの方法でモデルに入力を渡すことができます。\n", "\n", "- `--inputs` オプション: ファイルに numpy ndarray を渡すことができます。\n", "- `--input_exprs` オプション: Python 式を渡すことができます。\n", "- `--input_examples` オプション: `tf.train.Example` を渡すことができます。\n", "\n", "#### `--inputs`\n", "\n", "ファイルに入力データを渡すには、次のような形式で `--inputs` オプションを指定します。\n", "\n", "```bsh\n", "--inputs