{ "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": "2022-12-14T21:59:51.687203Z", "iopub.status.busy": "2022-12-14T21:59:51.686823Z", "iopub.status.idle": "2022-12-14T21:59:51.690748Z", "shell.execute_reply": "2022-12-14T21:59:51.690213Z" }, "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": "2022-12-14T22:00:13.187023Z", "iopub.status.busy": "2022-12-14T22:00:13.186522Z", "iopub.status.idle": "2022-12-14T22:00:13.192249Z", "shell.execute_reply": "2022-12-14T22:00:13.191666Z" }, "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": "2022-12-14T22:00:13.195411Z", "iopub.status.busy": "2022-12-14T22:00:13.194929Z", "iopub.status.idle": "2022-12-14T22:00:13.356002Z", "shell.execute_reply": "2022-12-14T22:00:13.355324Z" }, "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" ] } ], "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는 해당되는 경우 미세 조정을 위해 공유된 SavedModels에서 다음을 제공하는 것이 좋습니다.\n", "\n", "- 모델링 드롭아웃 또는 순방향 전달이 훈련과 추론 사이에 다른 기술(예: 배치 정규화)을 사용하는 경우, `__call__` 메서드는 선택적으로 기본값이 `False`이지만 `True`로 설정할 수 있는 Python 값 `training=` 인수를 선택합니다.\n", "- `__call__` 속성 옆에는 해당 변수 목록과 함께 `.variable` 및 `.trainable_variable` 속성이 있습니다. 원래는 훈련할 수 있었지만 미세 조정 중에 고정되도록 의도된 변수가 `.trainable_variables`에서 생략됩니다.\n", "- 가중치 정규화기를 레이어 또는 하위 모델의 속성으로 나타내는 Keras와 같은 프레임워크를 위해 `.regularization_losses` 속성도 있을 수 있습니다. 이것은 총 손실에 추가하기 위한 값을 갖는 인수가 없는(zero-argument) 함수 목록을 보유합니다.\n", "\n", "초기의 MobileNet 예제로 돌아가면 다음 중 일부가 작동하는 것을 볼 수 있습니다." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:00:13.359664Z", "iopub.status.busy": "2022-12-14T22:00:13.359125Z", "iopub.status.idle": "2022-12-14T22:00:15.716445Z", "shell.execute_reply": "2022-12-14T22:00:15.715778Z" }, "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": "2022-12-14T22:00:15.719702Z", "iopub.status.busy": "2022-12-14T22:00:15.719450Z", "iopub.status.idle": "2022-12-14T22:00:15.723915Z", "shell.execute_reply": "2022-12-14T22:00:15.723361Z" }, "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`와 같은 도구는 SavedModels과 상호작용할 수 있습니다. 이러한 도구가 사용할 ConcreteFunctions을 결정하는 데 도움이 되도록 적용 서명(serving signatures)을 지정해야 합니다. `tf.keras.Model`은 적용 서명을 자동으로 지정하지만 사용자는 당사의 사용자 정의 모듈에 대한 적용 서명을 명시적으로 선언해야 합니다.\n", "\n", "중요: Python을 사용하여 TensorFlow 2.x 이외의 환경으로 모델을 내보내야 하는 경우가 아니면 명시적으로 서명을 내보낼 필요가 없을 것입니다. 특정 함수에 입력 서명을 강제 적용하는 방법을 찾고 있다면 `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": "2022-12-14T22:00:15.727271Z", "iopub.status.busy": "2022-12-14T22:00:15.726803Z", "iopub.status.idle": "2022-12-14T22:00:15.729813Z", "shell.execute_reply": "2022-12-14T22:00:15.729260Z" }, "id": "h-IB5Xa0NxLa" }, "outputs": [], "source": [ "assert len(imported.signatures) == 0" ] }, { "cell_type": "markdown", "metadata": { "id": "BiNtaMZSI8Tb" }, "source": [ "어떤 함수가 시그니처라는 것을 나타내려면 저장할 때 `signatures` 매개변수를 지정합니다." ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:00:15.733081Z", "iopub.status.busy": "2022-12-14T22:00:15.732621Z", "iopub.status.idle": "2022-12-14T22:00:15.796876Z", "shell.execute_reply": "2022-12-14T22:00:15.796303Z" }, "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/tmpkcm26pwl/module_with_signature/assets\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpkcm26pwl/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": "2022-12-14T22:00:15.800155Z", "iopub.status.busy": "2022-12-14T22:00:15.799695Z", "iopub.status.idle": "2022-12-14T22:00:15.828956Z", "shell.execute_reply": "2022-12-14T22:00:15.828380Z" }, "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": [ "하나의 시그니처를 내보냈고 키는 기본값인 \"serving_default\"가 됩니다. 여러 시그니처를 내보내려면 딕셔너리로 전달합니다." ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:00:15.832326Z", "iopub.status.busy": "2022-12-14T22:00:15.831734Z", "iopub.status.idle": "2022-12-14T22:00:15.892141Z", "shell.execute_reply": "2022-12-14T22:00:15.891583Z" }, "id": "6VYAiQmLUiox" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpkcm26pwl/module_with_multiple_signatures/assets\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpkcm26pwl/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": "2022-12-14T22:00:15.895545Z", "iopub.status.busy": "2022-12-14T22:00:15.895019Z", "iopub.status.idle": "2022-12-14T22:00:15.926281Z", "shell.execute_reply": "2022-12-14T22:00:15.925699Z" }, "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 함수 arg 이름에서 파생됩니다." ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:00:15.929561Z", "iopub.status.busy": "2022-12-14T22:00:15.929036Z", "iopub.status.idle": "2022-12-14T22:00:16.011810Z", "shell.execute_reply": "2022-12-14T22:00:16.011235Z" }, "id": "ACKPl1X8G1gw" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpkcm26pwl/module_with_output_name/assets\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpkcm26pwl/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([], 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": "2022-12-14T22:00:16.014975Z", "iopub.status.busy": "2022-12-14T22:00:16.014429Z", "iopub.status.idle": "2022-12-14T22:00:16.038180Z", "shell.execute_reply": "2022-12-14T22:00:16.037573Z" }, "id": "1yGVy4MuH-V0" }, "outputs": [ { "data": { "text/plain": [ "{'custom_output_name': TensorSpec(shape=(), dtype=tf.float32, name='custom_output_name')}" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "imported_with_output_name = tf.saved_model.load(module_output_path)\n", "imported_with_output_name.signatures['serving_default'].structured_outputs" ] }, { "cell_type": "markdown", "metadata": { "id": "Co6fDbzw_UnD" }, "source": [ "## C++에서 SavedModel 불러오기\n", "\n", "SavedModel의 C++ 버전 [loader](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/cc/saved_model/loader.h)는 SessionOptions 및 RunOptions을 허용하며 경로에서 SavedModel을 불러오는 API를 제공합니다. 불러 올 그래프와 연관된 태그를 지정해야합니다. 불러온 SavedModel의 버전은 SavedModelBundle이라고 하며 MetaGraphDef와 불러온 세션을 포함합니다.\n", "\n", "```C++\n", "const string export_dir = ...\n", "SavedModelBundle bundle;\n", "...\n", "LoadSavedModel(session_options, run_options, export_dir, {kSavedModelTagTrain},\n", " &bundle);\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "b33KuyEuAO3Z" }, "source": [ "\n", "\n", "## SavedModel 커맨드 라인 인터페이스 세부 사항\n", "\n", "SavedModel 커맨드 라인 인터페이스(CLI)를 사용하여 SavedModel을 검사하고 실행할 수 있습니다. 예를 들어, CLI를 사용하여 모델의 `SignatureDef`를 검사할 수 있습니다. CLI를 사용하면 입력 Tensor 크기 및 데이터 타입이 모델과 일치하는지 신속하게 확인할 수 있습니다. 또한 모델을 테스트하려는 경우 다양한 형식(예를 들어, 파이썬 표현식)의 샘플 입력을 전달하고 출력을 가져와 CLI를 사용하여 정확성 검사를 수행할 수 있습니다.\n", "\n", "### SavedModel CLI 설치하기\n", "\n", "대체로 말하자면 다음 두 가지 방법 중 하나로 텐서플로를 설치할 수 있습니다:\n", "\n", "- 사전에 빌드된 텐서플로 바이너리로 설치\n", "- 소스 코드로 텐서플로 빌드\n", "\n", "사전에 빌드된 텐서플로 바이너리를 통해 설치한 경우 SavedModel CLI가 이미 시스템 경로 `bin\\saved_model_cli`에 설치되어 있습니다.\n", "\n", "소스 코드에서 텐서플로를 빌드하는 경우 다음 추가 명령을 실행하여 `saved_model_cli`를 빌드해야 합니다:\n", "\n", "```\n", "$ bazel build //tensorflow/python/tools:saved_model_cli\n", "```\n", "\n", "### 명령 개요\n", "\n", "SavedModel CLI는 SavedModel의 `MetaGraphDef`에 대해 다음 두 명령어를 지원합니다:\n", "\n", "- SavedModel의 `MetaGraphDef`에 대한 계산을 보여주는 `show`\n", "- `MetaGraphDef`에 대한 계산을 실행하는 `run`\n", "\n", "### `show` 명령어\n", "\n", "SavedModel은 태그 세트로 식별되는 하나 이상의 `MetaGraphDef`를 포함합니다. 모델을 텐서플로 서빙에 배포하려면, 각 모델에 어떤 종류의 `SignatureDef`가 있는지, 그리고 입력과 출력은 무엇인지 궁금할 수 있습니다. `show` 명령은 SavedModel의 내용을 계층적 순서로 검사합니다. 구문은 다음과 같습니다:\n", "\n", "```\n", "usage: saved_model_cli show [-h] --dir DIR [--all]\n", "[--tag_set TAG_SET] [--signature_def SIGNATURE_DEF_KEY]\n", "```\n", "\n", "예를 들어, 다음 명령은 SavedModel에서 사용 가능한 모든 `MetaGraphDef` 태그 세트를 보여줍니다:\n", "\n", "```\n", "$ saved_model_cli show --dir /tmp/saved_model_dir\n", "The given SavedModel contains the following tag-sets:\n", "serve\n", "serve, gpu\n", "```\n", "\n", "다음 명령은 `MetaGraphDef`에서 사용 가능한 모든 `SignatureDef` 키를 보여줍니다:\n", "\n", "```\n", "$ saved_model_cli show --dir /tmp/saved_model_dir --tag_set serve\n", "The given SavedModel `MetaGraphDef` contains `SignatureDefs` with the\n", "following keys:\n", "SignatureDef key: \"classify_x2_to_y3\"\n", "SignatureDef key: \"classify_x_to_y\"\n", "SignatureDef key: \"regress_x2_to_y3\"\n", "SignatureDef key: \"regress_x_to_y\"\n", "SignatureDef key: \"regress_x_to_y2\"\n", "SignatureDef key: \"serving_default\"\n", "```\n", "\n", "`MetaGraphDef`가 태그 세트에 *여러 개의* 태그를 가지고 있는 경우, 모든 태그를 지정해야 하며, 각 태그는 쉼표로 구분해야 합니다. 예를 들어:\n", "\n", "
\n", "$ saved_model_cli show --dir /tmp/saved_model_dir --tag_set serve,gpu\n", "\n", "\n", "특정 `SignatureDef`에 대한 모든 입력 및 출력 텐서 정보(TensorInfo)를 표시하려면 `SignatureDef` 키를 `signature_def` 옵션으로 전달하십시오. 이것은 나중에 계산 그래프를 실행하기 위해 입력 텐서의 텐서 키 값, 크기 및 데이터 타입을 알고자 할 때 매우 유용합니다. 예를 들어:\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", "
\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` 명령은 입력을 모델에 전달하는 다음 세 가지 방법을 제공합니다:\n", "\n", "- `--inputs` 옵션을 사용하여 넘파이(numpy) ndarray를 파일에 전달할 수 있습니다.\n", "- `--input_exprs` 옵션을 사용하여 파이썬 표현식을 전달할 수 있습니다.\n", "- `--input_examples` 옵션을 사용하여 `tf.train.Example`을 전달할 수 있습니다.\n", "\n", "#### `--inputs`\n", "\n", "입력 데이터를 파일에 전달하려면, 다음과 같은 일반적인 형식을 가지는 `--inputs` 옵션을 지정합니다:\n", "\n", "```bsh\n", "--inputs