{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "WrcIOXsUQh8U" }, "source": [ "##### Copyright 2021 The TensorFlow Authors." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "cellView": "form", "execution": { "iopub.execute_input": "2022-12-14T22:14:05.615281Z", "iopub.status.busy": "2022-12-14T22:14:05.614718Z", "iopub.status.idle": "2022-12-14T22:14:05.618510Z", "shell.execute_reply": "2022-12-14T22:14:05.617970Z" }, "id": "tXAbWHtqs1Y2" }, "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": "HTgMAvQq-PU_" }, "source": [ "# 확장 유형\n", "\n", "\n", " \n", " \n", " \n", " \n", "
TensorFlow.org에서 보기Google Colab에서 실행GitHub에서 소스 보기노트북 다운로드
" ] }, { "cell_type": "markdown", "metadata": { "id": "jHcw9MtgBo7e" }, "source": [ "## 설정" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:05.622051Z", "iopub.status.busy": "2022-12-14T22:14:05.621494Z", "iopub.status.idle": "2022-12-14T22:14:31.680490Z", "shell.execute_reply": "2022-12-14T22:14:31.679764Z" }, "id": "0MsE_F0WBpmc" }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2022-12-14 22:14:29.901620: E tensorflow/tsl/lib/monitoring/collection_registry.cc:81] Cannot register 2 metrics with the same name: /tensorflow/core/bfc_allocator_delay\n" ] } ], "source": [ "!pip install -q tf_nightly\n", "import tensorflow as tf\n", "import numpy as np\n", "from typing import Tuple, List, Mapping, Union, Optional\n", "import tempfile" ] }, { "cell_type": "markdown", "metadata": { "id": "1BAk3bji_0wl" }, "source": [ "## 확장 유형\n", "\n", "사용자 정의 유형을 사용하면 프로젝트를 더 읽기 쉽고 모듈식으로 유지 관리할 수 있습니다. 그러나 대부분의 TensorFlow API는 사용자 정의 Python 유형에 대한 지원이 매우 제한적입니다. 이것은 (예 모두 높은 수준의 API를 포함 [Keras](https://www.tensorflow.org/guide/keras/overview) , [tf.function](https://www.tensorflow.org/guide/function) , [tf.SavedModel](https://www.tensorflow.org/guide/saved_model) (예로서 하위 레벨의 API) `tf.while_loop` 및 `tf.concat` ). TensorFlow **확장 유형** 을 사용하여 TensorFlow의 API와 원활하게 작동하는 사용자 정의 객체 지향 유형을 생성할 수 있습니다. `tf.experimental.ExtensionType` 을 기본으로 하는 Python 클래스를 정의하고 [유형 주석](https://www.python.org/dev/peps/pep-0484/) 을 사용하여 각 필드의 유형을 지정하면 됩니다." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:31.685336Z", "iopub.status.busy": "2022-12-14T22:14:31.684448Z", "iopub.status.idle": "2022-12-14T22:14:31.690006Z", "shell.execute_reply": "2022-12-14T22:14:31.689428Z" }, "id": "7o5KY7L5_nxy" }, "outputs": [], "source": [ "class TensorGraph(tf.experimental.ExtensionType):\n", " \"\"\"A collection of labeled nodes connected by weighted edges.\"\"\"\n", " edge_weights: tf.Tensor # shape=[num_nodes, num_nodes]\n", " node_labels: Mapping[str, tf.Tensor] # shape=[num_nodes]; dtype=any\n", "\n", "class MaskedTensor(tf.experimental.ExtensionType):\n", " \"\"\"A tensor paired with a boolean mask, indicating which values are valid.\"\"\"\n", " values: tf.Tensor\n", " mask: tf.Tensor # shape=values.shape; false for missing/invalid values.\n", "\n", "class CSRSparseMatrix(tf.experimental.ExtensionType):\n", " \"\"\"Compressed sparse row matrix (https://en.wikipedia.org/wiki/Sparse_matrix).\"\"\"\n", " values: tf.Tensor # shape=[num_nonzero]; dtype=any\n", " col_index: tf.Tensor # shape=[num_nonzero]; dtype=int64\n", " row_index: tf.Tensor # shape=[num_rows+1]; dtype=int64" ] }, { "cell_type": "markdown", "metadata": { "id": "FiaNXPa7pNK-" }, "source": [ "`tf.experimental.ExtensionType` 기본 클래스는 표준 Python 라이브러리의 [`typing.NamedTuple`](https://docs.python.org/3/library/typing.html#typing.NamedTuple) 및 [`@dataclasses.dataclass`](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass) 와 유사하게 작동합니다. 특히 필드 유형 주석을 기반으로 생성자와 특수 메서드(예: `__repr__` 및 `__eq__`" ] }, { "cell_type": "markdown", "metadata": { "id": "JsE7X6_uMyLo" }, "source": [ "일반적으로 확장 유형은 다음 두 가지 범주 중 하나로 분류되는 경향이 있습니다.\n", "\n", "- 관련 값의 컬렉션을 그룹화하고 해당 값을 기반으로 유용한 작업을 제공할 수 있는 ***데이터 구조.*** 데이터 구조는 상당히 일반적일 수 있습니다(예 `TensorGraph` 예). 또는 특정 모델에 고도로 맞춤화될 수 있습니다.\n", "\n", "- \"Tensor\"의 개념을 전문화하거나 확장하는 ***Tensor와 유사한 유형입니다.*** 이 범주의 유형에는 `rank` , `shape` 및 일반적으로 `dtype` . `tf.stack` , `tf.add` 또는 `tf.matmul` )과 함께 사용하는 것이 좋습니다. `MaskedTensor` 및 `CSRSparseMatrix` 는 텐서 유사 유형의 예입니다." ] }, { "cell_type": "markdown", "metadata": { "id": "uxngcajlMqIY" }, "source": [ "## 지원되는 API\n", "\n", "확장 유형은 다음 TensorFlow API에서 지원됩니다.\n", "\n", "- **Keras** `Models` 및 `Layers` 대한 입력 및 출력으로 사용할 수 있습니다.\n", "- **tf.data.Dataset** : 확장 유형은 `Datasets` `Iterators` 의해 반환됩니다.\n", "- **TensorFlow 허브**: 확장 유형을 `tf.hub` 모듈의 입력 및 출력으로 사용할 수 있습니다.\n", "- **SavedModel** `SavedModel` 함수에 대한 입력 및 출력으로 사용할 수 있습니다.\n", "- **tf.function** `@tf.function` 데코레이터로 래핑된 함수의 인수 및 반환 값으로 사용할 수 있습니다.\n", "- **While 루프**: 확장 유형을 `tf.while_loop`에서 루프 변수로 사용할 수 있으며, while 루프 본문에서 인수 및 반환 값으로도 사용할 수 있습니다.\n", "- **조건**: `tf.cond`와 `tf.case`를 사용하여 확장 유형을 조건부로 선택할 수 있습니다.\n", "- **`tf.py_function`**: 확장 유형을 인수로 사용하고 `func` 인수의 값을 `tf.py_function`에 반환할 수 있습니다.\n", "- **Tensor 연산**: 텐서 입력을 허용하는 대부분의 TensorFlow 연산(예: `tf.matmul`, `tf.gather`, `tf.reduce_sum`)을 지원하도록 확장 유형을 확장할 수 있습니다. 자세한 정보는 아래의 \"*디스패치*\" 섹션으로 이동하여 확인할 수 있습니다.\n", "- **분산 전략**: 확장 유형을 복제본별 값으로 사용할 수 있습니다.\n", "\n", "자세한 내용은 아래 \"ExtensionTypes를 지원하는 TensorFlow API\" 섹션을 참조하세요.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "VIpZwuPVpwOX" }, "source": [ "## 요구 사항\n" ] }, { "cell_type": "markdown", "metadata": { "id": "nNk_TQeJGVwV" }, "source": [ "### 필드 유형\n", "\n", "모든 필드(인스턴스 변수)를 선언해야 하며 각 필드에 유형 주석을 제공해야 합니다. 지원되는 유형 주석은 다음과 같습니다.\n", "\n", "유형 | 예시\n", "--- | ---\n", "파이썬 정수 | `i: int`\n", "파이썬 수레 | `f: float`\n", "파이썬 문자열 | `s: str`\n", "파이썬 부울 | `b: bool`\n", "파이썬 없음 | `n: None`\n", "[텐서 모양](https://www.tensorflow.org/api_docs/python/tf/TensorShape) | `shape: tf.TensorShape`\n", "[텐서 dtypes](https://www.tensorflow.org/api_docs/python/tf/dtypes/DType) | `dtype: tf.DType`\n", "[텐서](https://www.tensorflow.org/api_docs/python/tf/Tensor) | `t: tf.Tensor`\n", "[확장 유형](https://www.tensorflow.org/api_docs/python/tf/experimental/ExtensionType) | `mt: MyMaskedTensor`\n", "[비정형 텐서](https://www.tensorflow.org/api_docs/python/tf/RaggedTensor) | `rt: tf.RaggedTensor`\n", "희소 텐서 | `st: tf.SparseTensor`\n", "[인덱싱된 슬라이스](https://www.tensorflow.org/api_docs/python/tf/IndexedSlices) | `s: tf.IndexedSlices`\n", "[선택적 텐서](https://www.tensorflow.org/api_docs/python/tf/experimental/Optional) | `o: tf.experimental.Optional`\n", "[유형 조합](https://docs.python.org/3/library/typing.html#typing.Union) | `int_or_float: typing.Union[int, float]`\n", "[튜플](https://docs.python.org/3/library/typing.html#typing.Tuple) | `params: typing.Tuple[int, float, tf.Tensor, int]`\n", "[가변 길이 튜플](https://docs.python.org/3/library/typing.html#typing.Tuple) | `lengths: typing.Tuple[int, ...]`\n", "[매핑](https://docs.python.org/3/library/typing.html#typing.Mapping) | `tags: typing.Mapping[str, tf.Tensor]`\n", "[선택적 값](https://docs.python.org/3/library/typing.html#typing.Optional) | `weight: typing.Optional[tf.Tensor]`" ] }, { "cell_type": "markdown", "metadata": { "id": "iFetYyZsIvf6" }, "source": [ "### 가변성\n", "\n", "확장 유형은 변경 불가능해야 합니다. 이렇게 하면 TensorFlow의 그래프 추적 메커니즘으로 적절하게 추적할 수 있습니다. 확장 유형 값을 변경하려는 경우 값을 변환하는 메서드를 대신 정의하는 것이 좋습니다. 예를 들어 `MaskedTensor` 를 변경하기 위해 `set_mask` `MaskedTensor` 를 반환하는 `replace_mask` 메서드를 정의할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:31.693526Z", "iopub.status.busy": "2022-12-14T22:14:31.693297Z", "iopub.status.idle": "2022-12-14T22:14:31.697033Z", "shell.execute_reply": "2022-12-14T22:14:31.696427Z" }, "id": "DThZLYH2IwFh" }, "outputs": [], "source": [ "class MaskedTensor(tf.experimental.ExtensionType):\n", " values: tf.Tensor\n", " mask: tf.Tensor\n", "\n", " def replace_mask(self, new_mask):\n", " self.values.shape.assert_is_compatible_with(new_mask.shape)\n", " return MaskedTensor(self.values, new_mask)" ] }, { "cell_type": "markdown", "metadata": { "id": "x3JyivI_qAtt" }, "source": [ "## `ExtensionType` 추가한 기능\n", "\n", "`ExtensionType` 기본 클래스는 다음 기능을 제공합니다.\n", "\n", "- 생성자( `__init__` ).\n", "- 인쇄 가능한 표현 방법( `__repr__` ).\n", "- 등식 및 부등식 연산자( `__eq__` ).\n", "- 유효성 검사 방법( `__validate__` ).\n", "- 강제 불변성.\n", "- 중첩된 `TypeSpec` .\n", "- 텐서 API 디스패치 지원.\n", "\n", "이 기능을 사용자 정의하는 방법에 대한 자세한 정보는 아래의 '`ExtensionType` 사용자 정의하기' 섹션으로 이동하여 확인합니다." ] }, { "cell_type": "markdown", "metadata": { "id": "pfSYs6P26gKq" }, "source": [ "### 건설자\n", "\n", "`ExtensionType` 에 의해 추가된 생성자는 각 필드를 명명된 인수로 사용합니다(클래스 정의에 나열된 순서대로). 이 생성자는 각 매개변수를 유형 검사하고 필요한 경우 변환합니다. 특히, `Tensor` `tf.convert_to_tensor` 사용하여 변환됩니다. `Tuple` 필드로 변환됩니다 `tuple` 의; `Mapping` 필드는 변경할 수 없는 사전으로 변환됩니다." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:31.700490Z", "iopub.status.busy": "2022-12-14T22:14:31.699967Z", "iopub.status.idle": "2022-12-14T22:14:35.287448Z", "shell.execute_reply": "2022-12-14T22:14:35.286565Z" }, "id": "DiXwyZ5M5KFW" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "tf.Tensor(\n", "[[1 2 3]\n", " [4 5 6]], shape=(2, 3), dtype=int32)\n" ] } ], "source": [ "class MaskedTensor(tf.experimental.ExtensionType):\n", " values: tf.Tensor\n", " mask: tf.Tensor\n", "\n", "# Constructor takes one parameter for each field.\n", "mt = MaskedTensor(values=[[1, 2, 3], [4, 5, 6]],\n", " mask=[[True, True, False], [True, False, True]])\n", "\n", "# Fields are type-checked and converted to the declared types.\n", "# For example, `mt.values` is converted to a Tensor.\n", "print(mt.values)" ] }, { "cell_type": "markdown", "metadata": { "id": "ezNDe1cYF0Qb" }, "source": [ "필드 값을 선언된 유형으로 변환할 수 없는 경우 생성자는 `TypeError`" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.291019Z", "iopub.status.busy": "2022-12-14T22:14:35.290752Z", "iopub.status.idle": "2022-12-14T22:14:35.295269Z", "shell.execute_reply": "2022-12-14T22:14:35.294630Z" }, "id": "6HnrMaabF5VS" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Got expected TypeError: mask: expected a Tensor, got 'NoneType'\n" ] } ], "source": [ "try:\n", " MaskedTensor([1, 2, 3], None)\n", "except TypeError as e:\n", " print(f\"Got expected TypeError: {e}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "FwQUI3X02s20" }, "source": [ "필드의 기본값은 클래스 수준에서 값을 설정하여 지정할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.298692Z", "iopub.status.busy": "2022-12-14T22:14:35.298214Z", "iopub.status.idle": "2022-12-14T22:14:35.307424Z", "shell.execute_reply": "2022-12-14T22:14:35.306744Z" }, "id": "GbzDT9fz20JA" }, "outputs": [ { "data": { "text/plain": [ "Pencil(color='black', has_erasor=True, length=)" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Pencil(tf.experimental.ExtensionType):\n", " color: str = \"black\"\n", " has_erasor: bool = True\n", " length: tf.Tensor = 1.0\n", "\n", "Pencil()" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.310797Z", "iopub.status.busy": "2022-12-14T22:14:35.310231Z", "iopub.status.idle": "2022-12-14T22:14:35.314891Z", "shell.execute_reply": "2022-12-14T22:14:35.314296Z" }, "id": "nOW7lS9P4Foc" }, "outputs": [ { "data": { "text/plain": [ "Pencil(color='blue', has_erasor=True, length=)" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Pencil(length=0.5, color=\"blue\")" ] }, { "cell_type": "markdown", "metadata": { "id": "S5Eivtg07Aau" }, "source": [ "### 인쇄 가능한 표현\n", "\n", "`ExtensionType` 은 클래스 이름과 각 필드의 값을 포함하는 기본 인쇄 가능한 표현 방법( `__repr__`\n" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.318258Z", "iopub.status.busy": "2022-12-14T22:14:35.317498Z", "iopub.status.idle": "2022-12-14T22:14:35.322316Z", "shell.execute_reply": "2022-12-14T22:14:35.321730Z" }, "id": "5SyiKTe55krG" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MaskedTensor(values=, mask=)\n" ] } ], "source": [ "print(MaskedTensor(values=[1, 2, 3], mask=[True, True, False]))" ] }, { "cell_type": "markdown", "metadata": { "id": "q4l_gnQh6nXR" }, "source": [ "### 등호 연산자\n", "\n", "`ExtensionType` 은 유형이 동일하고 모든 필드가 동일한 경우 두 값을 동일하게 간주하는 기본 동등 연산자( `__eq__` 및 `__ne__` 텐서 필드는 모양이 동일하고 모든 요소에 대해 요소별로 동일한 경우 동일한 것으로 간주됩니다." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.325353Z", "iopub.status.busy": "2022-12-14T22:14:35.324772Z", "iopub.status.idle": "2022-12-14T22:14:35.341435Z", "shell.execute_reply": "2022-12-14T22:14:35.340835Z" }, "id": "bHdLg13V52Xm" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a == a: True\n", "a == b: False\n", "a == a.values: False\n" ] } ], "source": [ "a = MaskedTensor([1, 2], [True, False])\n", "b = MaskedTensor([[3, 4], [5, 6]], [[False, True], [True, True]])\n", "print(f\"a == a: {a==a}\")\n", "print(f\"a == b: {a==b}\")\n", "print(f\"a == a.values: {a==a.values}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "O3HqsO3jZlQq" }, "source": [ "**참고:** `Tensor` 가 포함된 경우 `__eq__` 는 (Python 부울 값 대신) `Tensor` 반환할 수 있습니다." ] }, { "cell_type": "markdown", "metadata": { "id": "hCpBfkKqCuip" }, "source": [ "### 검증 방법\n", "\n", "`ExtensionType`은 필드에 대한 유효성 검사를 수행하기 위해 재정의할 수 있는 `__validate__` 메서드를 추가합니다. 이는 생성자가 호출되고 필드의 유형을 검사하고 선언된 유형으로 변환된 후에 실행되므로 모든 필드에 선언된 유형이 있다고 가정할 수 있습니다.\n", "\n", "다음 예제는 `MaskedTensor`를 업데이트하여 해당 필드의 `shape` 및 `dtype`의 유효성을 검사합니다." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.344970Z", "iopub.status.busy": "2022-12-14T22:14:35.344336Z", "iopub.status.idle": "2022-12-14T22:14:35.348391Z", "shell.execute_reply": "2022-12-14T22:14:35.347808Z" }, "id": "dgZOJRINDn00" }, "outputs": [], "source": [ "class MaskedTensor(tf.experimental.ExtensionType):\n", " \"\"\"A tensor paired with a boolean mask, indicating which values are valid.\"\"\"\n", " values: tf.Tensor\n", " mask: tf.Tensor\n", " def __validate__(self):\n", " self.values.shape.assert_is_compatible_with(self.mask.shape)\n", " assert self.mask.dtype.is_bool, 'mask.dtype must be bool'" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.351552Z", "iopub.status.busy": "2022-12-14T22:14:35.350952Z", "iopub.status.idle": "2022-12-14T22:14:35.354854Z", "shell.execute_reply": "2022-12-14T22:14:35.354258Z" }, "id": "ajSgkGUUn9WL" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Got expected AssertionError: mask.dtype must be bool\n" ] } ], "source": [ "try:\n", " MaskedTensor([1, 2, 3], [0, 1, 0]) # Wrong `dtype` for mask.\n", "except AssertionError as e:\n", " print(f\"Got expected AssertionError: {e}\")" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.358068Z", "iopub.status.busy": "2022-12-14T22:14:35.357497Z", "iopub.status.idle": "2022-12-14T22:14:35.361802Z", "shell.execute_reply": "2022-12-14T22:14:35.361196Z" }, "id": "Fhb96luJn9K7" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Got expected ValueError: Shapes (3,) and (2,) are incompatible\n" ] } ], "source": [ "try:\n", " MaskedTensor([1, 2, 3], [True, False]) # shapes don't match.\n", "except ValueError as e:\n", " print(f\"Got expected ValueError: {e}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "pjIPAF1OCAdO" }, "source": [ "### 강제 불변성\n", "\n", "`ExtensionType` `__setattr__` 및 `__delattr__` 메서드를 재정의하여 변형을 방지하여 확장 유형 값을 변경할 수 없도록 합니다." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.365006Z", "iopub.status.busy": "2022-12-14T22:14:35.364372Z", "iopub.status.idle": "2022-12-14T22:14:35.368178Z", "shell.execute_reply": "2022-12-14T22:14:35.367596Z" }, "id": "NgmJ1C7ilN5C" }, "outputs": [], "source": [ "mt = MaskedTensor([1, 2, 3], [True, False, True])" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.371234Z", "iopub.status.busy": "2022-12-14T22:14:35.370652Z", "iopub.status.idle": "2022-12-14T22:14:35.374118Z", "shell.execute_reply": "2022-12-14T22:14:35.373572Z" }, "id": "cMYmJr3RoFKp" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Got expected AttributeError: Cannot mutate attribute `mask` outside the custom constructor of ExtensionType.\n" ] } ], "source": [ "try:\n", " mt.mask = [True, True, True]\n", "except AttributeError as e:\n", " print(f\"Got expected AttributeError: {e}\")" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.377308Z", "iopub.status.busy": "2022-12-14T22:14:35.376698Z", "iopub.status.idle": "2022-12-14T22:14:35.380211Z", "shell.execute_reply": "2022-12-14T22:14:35.379658Z" }, "id": "ZWwA-zWdzqlU" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Got expected TypeError: 'tensorflow.python.framework.ops.EagerTensor' object does not support item assignment\n" ] } ], "source": [ "try:\n", " mt.mask[0] = False\n", "except TypeError as e:\n", " print(f\"Got expected TypeError: {e}\")" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.383312Z", "iopub.status.busy": "2022-12-14T22:14:35.382809Z", "iopub.status.idle": "2022-12-14T22:14:35.386511Z", "shell.execute_reply": "2022-12-14T22:14:35.385719Z" }, "id": "PN_txJVKoFoF" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Got expected AttributeError: Cannot mutate attribute `mask` outside the custom constructor of ExtensionType.\n" ] } ], "source": [ "try:\n", " del mt.mask\n", "except AttributeError as e:\n", " print(f\"Got expected AttributeError: {e}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "FBVFtCYn69Ou" }, "source": [ "### 중첩된 유형 사양\n", "\n", "각 `ExtensionType` 클래스에는 자동으로 생성되고 `.Spec` `TypeSpec` 클래스가 있습니다.\n", "\n", "이 클래스는 중첩된 텐서의 값을 *제외한* 값에서 모든 정보를 캡처합니다. 특히 `TypeSpec` 은 중첩된 Tensor, ExtensionType 또는 CompositeTensor를 `TypeSpec` 으로 대체하여 생성됩니다.\n" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.389832Z", "iopub.status.busy": "2022-12-14T22:14:35.389263Z", "iopub.status.idle": "2022-12-14T22:14:35.396987Z", "shell.execute_reply": "2022-12-14T22:14:35.396305Z" }, "id": "GRjANkGYKGnV" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "TensorSpec(shape=(), dtype=tf.string, name=None)\n", "ImmutableDict({'height': TensorSpec(shape=(), dtype=tf.float32, name=None), 'speed': TensorSpec(shape=(), dtype=tf.float32, name=None)})\n" ] } ], "source": [ "class Player(tf.experimental.ExtensionType):\n", " name: tf.Tensor\n", " attributes: Mapping[str, tf.Tensor]\n", "\n", "anne = Player(\"Anne\", {\"height\": 8.3, \"speed\": 28.1})\n", "anne_spec = tf.type_spec_from_value(anne)\n", "print(anne_spec.name) # Records `dtype` and `shape`, but not the string value.\n", "print(anne_spec.attributes) # Records keys and TensorSpecs for values." ] }, { "cell_type": "markdown", "metadata": { "id": "I2fkgckxO564" }, "source": [ "`TypeSpec` 값은 명시적으로 구성하거나 `tf.type_spec_from_value` 사용하여 `ExtensionType` 값에서 빌드할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.400506Z", "iopub.status.busy": "2022-12-14T22:14:35.400016Z", "iopub.status.idle": "2022-12-14T22:14:35.403344Z", "shell.execute_reply": "2022-12-14T22:14:35.402740Z" }, "id": "1ehAa7d9OGai" }, "outputs": [], "source": [ "spec1 = Player.Spec(name=tf.TensorSpec([], tf.float32), attributes={})\n", "spec2 = tf.type_spec_from_value(anne)" ] }, { "cell_type": "markdown", "metadata": { "id": "owcFG3cAMCwA" }, "source": [ "`TypeSpec` 은 TensorFlow에서 값을 **정적 구성 요소** 와 **동적 구성** 요소로 나누는 데 사용됩니다.\n", "\n", "- 그래프 생성 시 고정되는 **정적 구성 요소** `tf.TypeSpec` 인코딩됩니다.\n", "- 그래프가 실행될 때마다 다를 수 있는 **동적 구성 요소** `tf.Tensor` 목록으로 인코딩됩니다.\n", "\n", "예를 들어, tf.function은 인수에 이전에 볼 수 없는 `TypeSpec` `tf.function` ." ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.406352Z", "iopub.status.busy": "2022-12-14T22:14:35.405772Z", "iopub.status.idle": "2022-12-14T22:14:35.409258Z", "shell.execute_reply": "2022-12-14T22:14:35.408672Z" }, "id": "pg-m5YLRM1Nd" }, "outputs": [], "source": [ "@tf.function\n", "def anonymize_player(player):\n", " print(\"<>\")\n", " return Player(\"\", player.attributes)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.412301Z", "iopub.status.busy": "2022-12-14T22:14:35.411766Z", "iopub.status.idle": "2022-12-14T22:14:35.455638Z", "shell.execute_reply": "2022-12-14T22:14:35.455078Z" }, "id": "0CCGm7cpeIq-" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<>\n" ] }, { "data": { "text/plain": [ "Player(name='>, attributes=ImmutableDict({'height': , 'speed': }))" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Function gets traced (first time the function has been called):\n", "anonymize_player(Player(\"Anne\", {\"height\": 8.3, \"speed\": 28.1}))" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.459153Z", "iopub.status.busy": "2022-12-14T22:14:35.458575Z", "iopub.status.idle": "2022-12-14T22:14:35.464456Z", "shell.execute_reply": "2022-12-14T22:14:35.463880Z" }, "id": "WB7bt7s83mFE" }, "outputs": [ { "data": { "text/plain": [ "Player(name='>, attributes=ImmutableDict({'height': , 'speed': }))" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Function does NOT get traced (same TypeSpec: just tensor values changed)\n", "anonymize_player(Player(\"Bart\", {\"height\": 8.1, \"speed\": 25.3}))" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.467599Z", "iopub.status.busy": "2022-12-14T22:14:35.466897Z", "iopub.status.idle": "2022-12-14T22:14:35.484485Z", "shell.execute_reply": "2022-12-14T22:14:35.483907Z" }, "id": "dNm7vLpR3nMH" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<>\n" ] }, { "data": { "text/plain": [ "Player(name='>, attributes=ImmutableDict({'height': , 'jump': }))" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Function gets traced (new TypeSpec: keys for attributes changed):\n", "anonymize_player(Player(\"Chuck\", {\"height\": 11.0, \"jump\": 5.3}))" ] }, { "cell_type": "markdown", "metadata": { "id": "U5rN1HPq25xC" }, "source": [ "자세한 내용은 [tf.function 가이드를](https://www.tensorflow.org/guide/function#rules_of_tracing) 참조하십시오." ] }, { "cell_type": "markdown", "metadata": { "id": "gX613uRk0qLz" }, "source": [ "## ExtensionType 사용자 정의\n", "\n", "단순히 필드와 해당 유형을 선언하는 것 외에도 확장 유형은 다음을 수행할 수 있습니다.\n", "\n", "- 기본 인쇄 가능한 표현( `__repr__` )을 재정의합니다.\n", "- 방법을 정의합니다.\n", "- 클래스 메서드와 정적 메서드를 정의합니다.\n", "- 속성을 정의합니다.\n", "- 기본 생성자( `__init__` )를 재정의합니다.\n", "- 기본 항등 연산자( `__eq__` )를 재정의합니다.\n", "- 연산자를 정의합니다(예: `__add__` 및 `__lt__` ).\n", "- 필드의 기본값을 선언합니다.\n", "- 하위 클래스를 정의합니다.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "MK-ePVDj-ROE" }, "source": [ "### 기본 인쇄 가능한 표현 재정의\n", "\n", "확장 유형에 대해 이 기본 문자열 변환 연산자를 재정의할 수 있습니다. 다음 예제에서는 값이 Eager 모드에서 인쇄될 때 더 읽기 쉬운 문자열 표현을 생성 `MaskedTensor`" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.488113Z", "iopub.status.busy": "2022-12-14T22:14:35.487504Z", "iopub.status.idle": "2022-12-14T22:14:35.494686Z", "shell.execute_reply": "2022-12-14T22:14:35.494029Z" }, "id": "gdPhjYEr8IGO" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "class MaskedTensor(tf.experimental.ExtensionType):\n", " \"\"\"A tensor paired with a boolean mask, indicating which values are valid.\"\"\"\n", " values: tf.Tensor\n", " mask: tf.Tensor # shape=values.shape; false for invalid values.\n", "\n", " def __repr__(self):\n", " return masked_tensor_str(self.values, self.mask)\n", "\n", "def masked_tensor_str(values, mask):\n", " if isinstance(values, tf.Tensor):\n", " if hasattr(values, 'numpy') and hasattr(mask, 'numpy'):\n", " return f''\n", " else:\n", " return f'MaskedTensor(values={values}, mask={mask})'\n", " if len(values.shape) == 1:\n", " items = [repr(v) if m else '_' for (v, m) in zip(values, mask)]\n", " else:\n", " items = [masked_tensor_str(v, m) for (v, m) in zip(values, mask)]\n", " return '[%s]' % ', '.join(items)\n", "\n", "mt = MaskedTensor(values=[[1, 2, 3], [4, 5, 6]],\n", " mask=[[True, True, False], [True, False, True]])\n", "print(mt)" ] }, { "cell_type": "markdown", "metadata": { "id": "_MLQU2_v8VjG" }, "source": [ "### 메소드 정의\n", "\n", "확장 유형은 일반 Python 클래스와 마찬가지로 메서드를 정의할 수 있습니다. 예를 들어 `MaskedTensor` `default` 대체된 마스킹된 값 `self` 의 복사본을 반환하는 `with_default` 메서드를 정의할 수 있습니다. `@tf.function` 데코레이터로 주석을 달 수 있습니다." ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.497849Z", "iopub.status.busy": "2022-12-14T22:14:35.497344Z", "iopub.status.idle": "2022-12-14T22:14:35.505215Z", "shell.execute_reply": "2022-12-14T22:14:35.504593Z" }, "id": "7RR-tqee8ZdP" }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class MaskedTensor(tf.experimental.ExtensionType):\n", " values: tf.Tensor\n", " mask: tf.Tensor\n", "\n", " def with_default(self, default):\n", " return tf.where(self.mask, self.values, default)\n", "\n", "MaskedTensor([1, 2, 3], [True, False, True]).with_default(0)" ] }, { "cell_type": "markdown", "metadata": { "id": "Qwd_gGKp9RP0" }, "source": [ "### 클래스 메서드 및 정적 메서드 정의\n", "\n", "`@classmethod` 및 `@staticmethod` 데코레이터를 사용하여 메소드를 정의할 수 있습니다. 예를 들어 `MaskedTensor` 유형은 주어진 값으로 모든 요소를 마스킹하는 팩토리 메소드를 정의할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.508439Z", "iopub.status.busy": "2022-12-14T22:14:35.507880Z", "iopub.status.idle": "2022-12-14T22:14:35.515691Z", "shell.execute_reply": "2022-12-14T22:14:35.515043Z" }, "id": "BacCEJYU9sBR" }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class MaskedTensor(tf.experimental.ExtensionType):\n", " values: tf.Tensor\n", " mask: tf.Tensor\n", "\n", " def __repr__(self):\n", " return masked_tensor_str(self.values, self.mask)\n", "\n", " @staticmethod\n", " def from_tensor_and_value_to_mask(values, value_to_mask):\n", " return MaskedTensor(values, values != value_to_mask)\n", "\n", "x = tf.constant([[1, 0, 2], [3, 0, 0]])\n", "MaskedTensor.from_tensor_and_value_to_mask(x, 0)" ] }, { "cell_type": "markdown", "metadata": { "id": "xIPf9PZX9AwL" }, "source": [ "### 속성 정의\n", "\n", "확장 유형은 일반 Python 클래스와 마찬가지로 `@property` 데코레이터를 사용하여 속성을 정의할 수 있습니다. 예를 들어 `MaskedTensor` 유형은 값의 dtype에 대한 약칭인 `dtype` 속성을 정의할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.518987Z", "iopub.status.busy": "2022-12-14T22:14:35.518460Z", "iopub.status.idle": "2022-12-14T22:14:35.524064Z", "shell.execute_reply": "2022-12-14T22:14:35.523514Z" }, "id": "16E68wZ-9KXp" }, "outputs": [ { "data": { "text/plain": [ "tf.int32" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class MaskedTensor(tf.experimental.ExtensionType):\n", " values: tf.Tensor\n", " mask: tf.Tensor\n", "\n", " @property\n", " def dtype(self):\n", " return self.values.dtype\n", "\n", "MaskedTensor([1, 2, 3], [True, False, True]).dtype" ] }, { "cell_type": "markdown", "metadata": { "id": "Mm5gxoG57nf3" }, "source": [ "### 기본 생성자 재정의\n", "\n", "확장 유형에 대한 기본 생성자를 재정의할 수 있습니다. 사용자 정의 생성자는 선언된 모든 필드에 대해 값을 설정해야 합니다. 사용자 정의 생성자가 반환된 후 모든 필드가 유형 검사되고 위에서 설명한 대로 값이 변환됩니다." ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.527285Z", "iopub.status.busy": "2022-12-14T22:14:35.526815Z", "iopub.status.idle": "2022-12-14T22:14:35.531729Z", "shell.execute_reply": "2022-12-14T22:14:35.531110Z" }, "id": "-8K3KeB08G1S" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Toy(name='ball', price=)\n" ] } ], "source": [ "class Toy(tf.experimental.ExtensionType):\n", " name: str\n", " price: tf.Tensor\n", " def __init__(self, name, price, discount=0):\n", " self.name = name\n", " self.price = price * (1 - discount)\n", "\n", "print(Toy(\"ball\", 5.0, discount=0.2)) # On sale -- 20% off!" ] }, { "cell_type": "markdown", "metadata": { "id": "qyQxMlwLFQt7" }, "source": [ "또는 기본 생성자를 그대로 두고 하나 이상의 팩토리 메서드를 추가하는 방법을 고려할 수 있습니다. 예제는 다음과 같습니다." ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.534907Z", "iopub.status.busy": "2022-12-14T22:14:35.534370Z", "iopub.status.idle": "2022-12-14T22:14:35.538746Z", "shell.execute_reply": "2022-12-14T22:14:35.538182Z" }, "id": "jiApK4hzFY89" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Toy(name='ball', price=)\n" ] } ], "source": [ "class Toy(tf.experimental.ExtensionType):\n", " name: str\n", " price: tf.Tensor\n", "\n", " @staticmethod\n", " def new_toy_with_discount(name, price, discount):\n", " return Toy(name, price * (1 - discount))\n", "\n", "print(Toy.new_toy_with_discount(\"ball\", 5.0, discount=0.2))" ] }, { "cell_type": "markdown", "metadata": { "id": "pdVcRBhG-Uee" }, "source": [ "### 기본 항등 연산자 재정의( `__eq__` )\n", "\n", "확장 유형의 기본 `__eq__` 연산자를 재정의할 수 있습니다. 다음 예제는 동일성을 비교할 때 마스킹된 요소를 무시하도록 `MaskedTensor`를 업데이트합니다." ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.541829Z", "iopub.status.busy": "2022-12-14T22:14:35.541357Z", "iopub.status.idle": "2022-12-14T22:14:35.552596Z", "shell.execute_reply": "2022-12-14T22:14:35.552006Z" }, "id": "dA7DyjfB-Yz0" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "tf.Tensor(True, shape=(), dtype=bool)\n" ] } ], "source": [ "class MaskedTensor(tf.experimental.ExtensionType):\n", " values: tf.Tensor\n", " mask: tf.Tensor\n", "\n", " def __repr__(self):\n", " return masked_tensor_str(self.values, self.mask)\n", "\n", " def __eq__(self, other):\n", " result = tf.math.equal(self.values, other.values)\n", " result = result | ~(self.mask & other.mask)\n", " return tf.reduce_all(result)\n", "\n", "x = MaskedTensor([1, 2, 3, 4], [True, True, False, True])\n", "y = MaskedTensor([5, 2, 0, 4], [False, True, False, True])\n", "print(x == y)" ] }, { "cell_type": "markdown", "metadata": { "id": "n1mZ1Lkyi14B" }, "source": [ "**참고:** 기본 구현은 단순히 `__eq__` 를 호출하고 결과를 무효화하기 `__ne__` 를 재정의할 필요가 없습니다." ] }, { "cell_type": "markdown", "metadata": { "id": "A_Jib1SQD1-z" }, "source": [ "### 정방향 참조 사용\n", "\n", "필드 유형이 아직 정의되지 않은 경우 유형 이름이 포함된 문자열을 대신 사용할 수 있습니다. 다음 예제에서는 `Node` 유형이 아직 (완전히) 정의되지 않았기 때문에 `\"Node\"` `children` 필드에 주석을 다는 데 사용됩니다.\n" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.555809Z", "iopub.status.busy": "2022-12-14T22:14:35.555339Z", "iopub.status.idle": "2022-12-14T22:14:35.560730Z", "shell.execute_reply": "2022-12-14T22:14:35.560165Z" }, "id": "_Z029QKED0Ao" }, "outputs": [ { "data": { "text/plain": [ "Node(value=, children=(Node(value=, children=()), Node(value=, children=())))" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Node(tf.experimental.ExtensionType):\n", " value: tf.Tensor\n", " children: Tuple[\"Node\", ...] = ()\n", "\n", "Node(3, [Node(5), Node(2)])" ] }, { "cell_type": "markdown", "metadata": { "id": "boaNg1zHgoVn" }, "source": [ "### 서브클래스 정의\n", "\n", "확장 유형은 표준 Python 구문을 사용하여 하위 클래스화될 수 있습니다. 확장 유형 하위 클래스는 새 필드, 메서드 및 속성을 추가할 수도 있으며, 생성자, 인쇄 가능한 표현 및 등호 연산자를 재정의할 수도 있습니다. 다음 예제는 3개의 `Tensor` 필드를 사용하여 노드 사이의 에지 세트를 인코딩하는 기본 `TensorGraph` 클래스를 정의합니다. 그런 다음 `Tensor` 필드를 추가하는 하위 클래스를 정의하여 각 노드의 \"속성 값\"을 기록합니다. 또한 하위 클래스는 에지를 따라 속성 값을 전파하는 메서드를 정의합니다." ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.563836Z", "iopub.status.busy": "2022-12-14T22:14:35.563366Z", "iopub.status.idle": "2022-12-14T22:14:35.577631Z", "shell.execute_reply": "2022-12-14T22:14:35.576970Z" }, "id": "58r6qRiK-uZh" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Original features: tf.Tensor([10. 0. 2. 5. -1. 0.], shape=(6,), dtype=float32)\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "After propagating: tf.Tensor([10. 12. 4. 4. -1. 0.], shape=(6,), dtype=float32)\n" ] } ], "source": [ "class TensorGraph(tf.experimental.ExtensionType):\n", " num_nodes: tf.Tensor\n", " edge_src: tf.Tensor # edge_src[e] = index of src node for edge e.\n", " edge_dst: tf.Tensor # edge_dst[e] = index of dst node for edge e.\n", "\n", "class TensorGraphWithNodeFeature(TensorGraph):\n", " node_features: tf.Tensor # node_features[n] = feature value for node n.\n", "\n", " def propagate_features(self, weight=1.0) -> 'TensorGraphWithNodeFeature':\n", " updates = tf.gather(self.node_features, self.edge_src) * weight\n", " new_node_features = tf.tensor_scatter_nd_add(\n", " self.node_features, tf.expand_dims(self.edge_dst, 1), updates)\n", " return TensorGraphWithNodeFeature(\n", " self.num_nodes, self.edge_src, self.edge_dst, new_node_features)\n", "\n", "g = TensorGraphWithNodeFeature( # Edges: 0->1, 4->3, 2->2, 2->1\n", " num_nodes=5, edge_src=[0, 4, 2, 2], edge_dst=[1, 3, 2, 1],\n", " node_features=[10.0, 0.0, 2.0, 5.0, -1.0, 0.0])\n", "\n", "print(\"Original features:\", g.node_features)\n", "print(\"After propagating:\", g.propagate_features().node_features)" ] }, { "cell_type": "markdown", "metadata": { "id": "U_oElT5HzqSG" }, "source": [ "### 개인 필드 정의\n", "\n", "확장 유형의 필드는 접두사에 밑줄을 붙여 비공개로 표시할 수 있습니다(표준 Python 규칙에 따라). 이것은 TensorFlow가 어떤 식으로든 필드를 처리하는 방식에 영향을 미치지 않습니다. 그러나 단순히 확장 유형의 모든 사용자에게 해당 필드가 비공개라는 신호 역할을 합니다.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "oMdH7ORqh8Pl" }, "source": [ "### ExtensionType의 `TypeSpec`\n", "\n", "각 `ExtensionType` 클래스에는 자동으로 생성되고 `.Spec` `TypeSpec` 클래스가 있습니다. 자세한 내용은 위의 \"중첩된 TypeSpec\" 섹션을 참조하세요.\n", "\n", "`TypeSpec` 을 사용자 정의하려면 `Spec` 이라는 자체 중첩 클래스를 정의하기만 하면 `ExtensionType` 이 이를 자동으로 생성된 `TypeSpec` 의 기초로 사용합니다. `Spec` 클래스를 사용자 정의할 수 있습니다.\n", "\n", "- 기본 인쇄 가능한 표현을 재정의합니다.\n", "- 기본 생성자를 재정의합니다.\n", "- 메서드, 클래스 메서드, 정적 메서드 및 속성을 정의합니다.\n", "\n", "다음 예제에서는 사용하기 쉽도록 `MaskedTensor.Spec` 클래스를 사용자 지정합니다." ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.580944Z", "iopub.status.busy": "2022-12-14T22:14:35.580403Z", "iopub.status.idle": "2022-12-14T22:14:35.585895Z", "shell.execute_reply": "2022-12-14T22:14:35.585286Z" }, "id": "Gm4RaqbkLlNG" }, "outputs": [], "source": [ "class MaskedTensor(tf.experimental.ExtensionType):\n", " values: tf.Tensor\n", " mask: tf.Tensor\n", "\n", " shape = property(lambda self: self.values.shape)\n", " dtype = property(lambda self: self.values.dtype)\n", "\n", " def __repr__(self):\n", " return masked_tensor_str(self.values, self.mask)\n", "\n", " def with_values(self, new_values):\n", " return MaskedTensor(new_values, self.mask)\n", "\n", " class Spec:\n", " def __init__(self, shape, dtype=tf.float32):\n", " self.values = tf.TensorSpec(shape, dtype)\n", " self.mask = tf.TensorSpec(shape, tf.bool)\n", "\n", " def __repr__(self):\n", " return f\"MaskedTensor.Spec(shape={self.shape}, dtype={self.dtype})\"\n", "\n", " shape = property(lambda self: self.values.shape)\n", " dtype = property(lambda self: self.values.dtype)" ] }, { "cell_type": "markdown", "metadata": { "id": "s3zzUXPSNF72" }, "source": [ "**참고** : 사용자 정의 `Spec` `ExtensionType` 선언되지 않은 인스턴스 변수를 사용할 수 없습니다." ] }, { "cell_type": "markdown", "metadata": { "id": "rip4GCuYPL7o" }, "source": [ "## 텐서 API 디스패치\n", "\n", "`tf.Tensor` 유형에 의해 정의된 인터페이스를 전문화하거나 확장한다는 점에서 \"텐서와 유사\"할 수 있습니다. 텐서와 유사한 확장 유형의 예로는 `RaggedTensor` , `SparseTensor` 및 `MaskedTensor` 있습니다. ***디스패치 데코레이터*** 는 텐서와 유사한 확장 유형에 적용될 때 TensorFlow 작업의 기본 동작을 재정의하는 데 사용할 수 있습니다. TensorFlow는 현재 세 가지 디스패치 데코레이터를 정의합니다.\n", "\n", "- `@tf.experimental.dispatch_for_api(tf_api)`\n", "- `@tf.experimental.dispatch_for_unary_elementwise_apis(x_type)`\n", "- `@tf.experimental.dispatch_for_binary_elementwise_apis(x_type, y_type)`" ] }, { "cell_type": "markdown", "metadata": { "id": "5BTQHcY4gHwZ" }, "source": [ "### 단일 API에 대한 디스패치\n", "\n", "`tf.experimental.dispatch_for_api` 데코레이터는 지정된 서명으로 호출될 때 지정된 TensorFlow 작업의 기본 동작을 재정의합니다. 예를 들어 이 데코레이터를 사용하여 `tf.stack` 이 `MaskedTensor` 값을 처리하는 방법을 지정할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.589570Z", "iopub.status.busy": "2022-12-14T22:14:35.588988Z", "iopub.status.idle": "2022-12-14T22:14:35.592936Z", "shell.execute_reply": "2022-12-14T22:14:35.592326Z" }, "id": "B4QgO_fUW2o2" }, "outputs": [], "source": [ "@tf.experimental.dispatch_for_api(tf.stack)\n", "def masked_stack(values: List[MaskedTensor], axis = 0):\n", " return MaskedTensor(tf.stack([v.values for v in values], axis),\n", " tf.stack([v.mask for v in values], axis))" ] }, { "cell_type": "markdown", "metadata": { "id": "FxKcKWNUaLvm" }, "source": [ "`MaskedTensor` 값 목록과 함께 호출될 때마다 `tf.stack` 대한 기본 구현을 재정의 `values` `typing.List[MaskedTensor]` 주석으로 지정되어 있기 때문입니다):" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.595672Z", "iopub.status.busy": "2022-12-14T22:14:35.595456Z", "iopub.status.idle": "2022-12-14T22:14:35.603471Z", "shell.execute_reply": "2022-12-14T22:14:35.602894Z" }, "id": "RqpFjaAvaA19" }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = MaskedTensor([1, 2, 3], [True, True, False])\n", "y = MaskedTensor([4, 5, 6], [False, True, True])\n", "tf.stack([x, y])" ] }, { "cell_type": "markdown", "metadata": { "id": "loGi8taCa265" }, "source": [ "`tf.stack` 이 혼합된 `MaskedTensor` 및 `Tensor` 값 목록을 처리할 수 있도록 하려면 `values` 매개변수에 대한 유형 주석을 구체화하고 함수 본문을 적절하게 업데이트할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.606487Z", "iopub.status.busy": "2022-12-14T22:14:35.606232Z", "iopub.status.idle": "2022-12-14T22:14:35.618793Z", "shell.execute_reply": "2022-12-14T22:14:35.618189Z" }, "id": "_xySkm0ganAI" }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tf.experimental.unregister_dispatch_for(masked_stack)\n", "\n", "def convert_to_masked_tensor(x):\n", " if isinstance(x, MaskedTensor):\n", " return x\n", " else:\n", " return MaskedTensor(x, tf.ones_like(x, tf.bool))\n", "\n", "@tf.experimental.dispatch_for_api(tf.stack)\n", "def masked_stack_v2(values: List[Union[MaskedTensor, tf.Tensor]], axis = 0):\n", " values = [convert_to_masked_tensor(v) for v in values]\n", " return MaskedTensor(tf.stack([v.values for v in values], axis),\n", " tf.stack([v.mask for v in values], axis))\n", "x = MaskedTensor([1, 2, 3], [True, True, False])\n", "y = tf.constant([4, 5, 6])\n", "tf.stack([x, y, x])" ] }, { "cell_type": "markdown", "metadata": { "id": "ITioFCyjQm8V" }, "source": [ "재정의할 수 있는 API 목록은 `tf.experimental.dispatch_for_api` 대한 API 설명서를 참조하세요." ] }, { "cell_type": "markdown", "metadata": { "id": "f91SaHSqc-jO" }, "source": [ "### 모든 단항 요소별 API에 대한 디스패치\n", "\n", "`tf.experimental.dispatch_for_unary_elementwise_apis` 데코레이터는 첫 번째 인수(일반적으로 이름이 `x` )에 대한 값이 유형 주석 `x_type` ***과 일치할 때마다 모든*** 단항 요소별 연산(예: `tf.math.cos` )의 기본 동작을 재정의합니다. 데코레이팅된 함수는 두 개의 인수를 취해야 합니다.\n", "\n", "- `api_func`: 단일 매개변수를 사용하고 요소별 연산을 수행하는 함수입니다(예: `tf.abs`).\n", "- `x` : 요소별 연산의 첫 번째 인수입니다.\n", "\n", "`MaskedTensor` 유형을 처리하기 위해 모든 단항 요소별 연산을 업데이트합니다." ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.621819Z", "iopub.status.busy": "2022-12-14T22:14:35.621349Z", "iopub.status.idle": "2022-12-14T22:14:35.633992Z", "shell.execute_reply": "2022-12-14T22:14:35.633421Z" }, "id": "cv5fV4xxZI9q" }, "outputs": [], "source": [ " @tf.experimental.dispatch_for_unary_elementwise_apis(MaskedTensor)\n", " def masked_tensor_unary_elementwise_api_handler(api_func, x):\n", " return MaskedTensor(api_func(x.values), x.mask)" ] }, { "cell_type": "markdown", "metadata": { "id": "qiK4n6vaeFwo" }, "source": [ "`MaskedTensor` 에서 단항 요소별 연산이 호출될 때마다 사용됩니다." ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.637217Z", "iopub.status.busy": "2022-12-14T22:14:35.636622Z", "iopub.status.idle": "2022-12-14T22:14:35.642433Z", "shell.execute_reply": "2022-12-14T22:14:35.641726Z" }, "id": "SkH0xi5gd_41" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ " x = MaskedTensor([1, -2, -3], [True, False, True])\n", " print(tf.abs(x))" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.645349Z", "iopub.status.busy": "2022-12-14T22:14:35.644839Z", "iopub.status.idle": "2022-12-14T22:14:35.650425Z", "shell.execute_reply": "2022-12-14T22:14:35.649834Z" }, "id": "2Ej5fxLBfaXW" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "print(tf.ones_like(x, dtype=tf.float32))" ] }, { "cell_type": "markdown", "metadata": { "id": "Z9OgLyfEejqc" }, "source": [ "### 바이너리 모든 요소별 API에 대한 디스패치\n", "\n", "마찬가지로 `tf.experimental.dispatch_for_binary_elementwise_apis` `MaskedTensor` 유형을 처리하기 위해 모든 바이너리 요소별 연산을 업데이트하는 데 사용할 수 있습니다.\n" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.653352Z", "iopub.status.busy": "2022-12-14T22:14:35.652890Z", "iopub.status.idle": "2022-12-14T22:14:35.766768Z", "shell.execute_reply": "2022-12-14T22:14:35.766011Z" }, "id": "Z8Du-GPofpCW" }, "outputs": [], "source": [ "@tf.experimental.dispatch_for_binary_elementwise_apis(MaskedTensor, MaskedTensor)\n", "def masked_tensor_binary_elementwise_api_handler(api_func, x, y):\n", " return MaskedTensor(api_func(x.values, y.values), x.mask & y.mask)" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.770303Z", "iopub.status.busy": "2022-12-14T22:14:35.769699Z", "iopub.status.idle": "2022-12-14T22:14:35.778042Z", "shell.execute_reply": "2022-12-14T22:14:35.777449Z" }, "id": "gghVHDfSfyi2" }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = MaskedTensor([1, -2, -3], [True, False, True])\n", "y = MaskedTensor([[4], [5]], [[True], [False]])\n", "tf.math.add(x, y)" ] }, { "cell_type": "markdown", "metadata": { "id": "txTGg9pzG0Ux" }, "source": [ "재정의된 요소별 API 목록을 확인하려면 `tf.experimental.dispatch_for_unary_elementwise_apis` 및 `tf.experimental.dispatch_for_binary_elementwise_apis`에 대한 API 문서로 이동합니다." ] }, { "cell_type": "markdown", "metadata": { "id": "UseRtohYKiE5" }, "source": [ "## 일괄 처리 가능한 확장 유형\n", "\n", "`ExtensionType` 단일 인스턴스 값의 배치를 나타내는 데 사용할 수있는 경우 *batchable이다.* `Tensor` 배치 차원을 추가하여 수행됩니다. 다음 TensorFlow API를 사용하려면 모든 확장 유형 입력이 일괄 처리 가능해야 합니다.\n", "\n", "- `tf.data.Dataset` ( `batch` , `unbatch` , `from_tensor_slices` )\n", "- `tf.keras`(`fit`, `evaluate`, `predict`)\n", "- `tf.map_fn`" ] }, { "cell_type": "markdown", "metadata": { "id": "hWPauKGj_yRz" }, "source": [ "기본적으로 `BatchableExtensionType` `Tensor` , `CompositeTensor` 및 `ExtensionType` 일괄 처리하여 일괄 처리된 값을 생성합니다. 이것이 클래스에 적합하지 않은 경우 `tf.experimental.ExtensionTypeBatchEncoder` 를 사용하여 이 기본 동작을 재정의해야 합니다. 예를 들어, 개별 희소 텐서의 `values` , `indices` 및 `dense_shape` `tf.SparseTensor` 값의 배치를 만드는 것은 적절하지 않습니다. 대부분의 경우 이러한 텐서는 호환되지 않는 모양을 가지고 있기 때문에 스택할 수 없습니다. ; 가능하더라도 결과는 유효한 `SparseTensor` .\n", "\n", "**참고** : `BatchableExtensionType` `tf.stack` , `tf.concat` , `tf.slice` 등에 대한 디스패처를 자동으로 정의하지 *않습니다* . 이러한 API에서 클래스를 지원해야 하는 경우 위에서 설명한 디스패치 데코레이터를 사용하세요." ] }, { "cell_type": "markdown", "metadata": { "id": "xkOJ8ke8GH7s" }, "source": [ "### BatchableExtensionType 예: 네트워크\n", "\n", "`Network` 클래스를 생각해 보십시오. 이 클래스는 각 노드에서 수행해야 할 작업의 양과 노드 간에 작업을 이동하는 데 사용할 수 있는 대역폭을 추적합니다." ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.781501Z", "iopub.status.busy": "2022-12-14T22:14:35.781057Z", "iopub.status.idle": "2022-12-14T22:14:35.786887Z", "shell.execute_reply": "2022-12-14T22:14:35.786157Z" }, "id": "tOeEXwCcfrPd" }, "outputs": [], "source": [ "class Network(tf.experimental.ExtensionType): # This version is not batchable.\n", " work: tf.Tensor # work[n] = work left to do at node n\n", " bandwidth: tf.Tensor # bandwidth[n1, n2] = bandwidth from n1->n2\n", "\n", "net1 = Network([5., 3, 8], [[0., 2, 0], [2, 0, 3], [0, 3, 0]])\n", "net2 = Network([3., 4, 2], [[0., 2, 2], [2, 0, 2], [2, 2, 0]])" ] }, { "cell_type": "markdown", "metadata": { "id": "PaOzUev6g3wT" }, "source": [ "이 유형을 배치 처리할 수 있게 설정하려면 기본 유형을 `BatchableExtensionType`으로 변경하고 선택적 배치 차원을 포함하도록 각 필드의 형상을 조정합니다. 다음 예제에서는 배치 형상을 추적하기 위해 `shape` 필드도 추가합니다. 이 `shape` 필드는 `tf.data.Dataset` 또는 `tf.map_fn`에는 필요하지 않지만 `tf.keras`에는 *필요*합니다." ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.790290Z", "iopub.status.busy": "2022-12-14T22:14:35.789754Z", "iopub.status.idle": "2022-12-14T22:14:35.795699Z", "shell.execute_reply": "2022-12-14T22:14:35.795069Z" }, "id": "T03WWBSMg2XC" }, "outputs": [], "source": [ "class Network(tf.experimental.BatchableExtensionType):\n", " shape: tf.TensorShape # batch shape. A single network has shape=[].\n", " work: tf.Tensor # work[*shape, n] = work left to do at node n\n", " bandwidth: tf.Tensor # bandwidth[*shape, n1, n2] = bandwidth from n1->n2\n", "\n", " def __init__(self, work, bandwidth):\n", " self.work = tf.convert_to_tensor(work)\n", " self.bandwidth = tf.convert_to_tensor(bandwidth)\n", " work_batch_shape = self.work.shape[:-1]\n", " bandwidth_batch_shape = self.bandwidth.shape[:-2]\n", " self.shape = work_batch_shape.merge_with(bandwidth_batch_shape)\n", "\n", " def __repr__(self):\n", " return network_repr(self)\n", "\n", "def network_repr(network):\n", " work = network.work\n", " bandwidth = network.bandwidth\n", " if hasattr(work, 'numpy'):\n", " work = ' '.join(str(work.numpy()).split())\n", " if hasattr(bandwidth, 'numpy'):\n", " bandwidth = ' '.join(str(bandwidth.numpy()).split())\n", " return (f\"\")" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.799062Z", "iopub.status.busy": "2022-12-14T22:14:35.798490Z", "iopub.status.idle": "2022-12-14T22:14:35.807994Z", "shell.execute_reply": "2022-12-14T22:14:35.807366Z" }, "id": "NUUJe9HuIPel" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "net1=\n", "net2=\n", "batch=\n" ] } ], "source": [ "net1 = Network([5., 3, 8], [[0., 2, 0], [2, 0, 3], [0, 3, 0]])\n", "net2 = Network([3., 4, 2], [[0., 2, 2], [2, 0, 2], [2, 2, 0]])\n", "batch_of_networks = Network(\n", " work=tf.stack([net1.work, net2.work]),\n", " bandwidth=tf.stack([net1.bandwidth, net2.bandwidth]))\n", "print(f\"net1={net1}\")\n", "print(f\"net2={net2}\")\n", "print(f\"batch={batch_of_networks}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "r0qWur5JGc3d" }, "source": [ "`tf.data.Dataset` 을 사용하여 네트워크 배치를 반복할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.811116Z", "iopub.status.busy": "2022-12-14T22:14:35.810687Z", "iopub.status.idle": "2022-12-14T22:14:35.827355Z", "shell.execute_reply": "2022-12-14T22:14:35.826714Z" }, "id": "BN_kixAUFZtv" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Batch element 0: \n", "Batch element 1: \n" ] } ], "source": [ "dataset = tf.data.Dataset.from_tensor_slices(batch_of_networks)\n", "for i, network in enumerate(dataset):\n", " print(f\"Batch element {i}: {network}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "aXENhTzIIjbM" }, "source": [ "`map_fn` 을 사용하여 각 배치 요소에 함수를 적용할 수도 있습니다." ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.830115Z", "iopub.status.busy": "2022-12-14T22:14:35.829871Z", "iopub.status.idle": "2022-12-14T22:14:35.898059Z", "shell.execute_reply": "2022-12-14T22:14:35.897446Z" }, "id": "j1XEsSWj9a3D" }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def balance_work_greedy(network):\n", " delta = (tf.expand_dims(network.work, -1) - tf.expand_dims(network.work, -2))\n", " delta /= 4\n", " delta = tf.maximum(tf.minimum(delta, network.bandwidth), -network.bandwidth)\n", " new_work = network.work + tf.reduce_sum(delta, -1)\n", " return Network(new_work, network.bandwidth)\n", "\n", "tf.map_fn(balance_work_greedy, batch_of_networks)" ] }, { "cell_type": "markdown", "metadata": { "id": "f_HLsTT02Xul" }, "source": [ "## ExtensionTypes를 지원하는 TensorFlow API" ] }, { "cell_type": "markdown", "metadata": { "id": "NNiQad2U2alT" }, "source": [ "### @tf.function\n", "\n", "[tf.function](https://www.tensorflow.org/guide/function) 은 TensorFlow 코드의 성능을 크게 향상시킬 수 있는 Python 함수용 TensorFlow 그래프를 미리 계산하는 데코레이터입니다. `@tf.function` 함수와 함께 투명하게 사용할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.901507Z", "iopub.status.busy": "2022-12-14T22:14:35.901015Z", "iopub.status.idle": "2022-12-14T22:14:35.938580Z", "shell.execute_reply": "2022-12-14T22:14:35.937961Z" }, "id": "jQ_rAvrA6qEb" }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Pastry(tf.experimental.ExtensionType):\n", " sweetness: tf.Tensor # 2d embedding that encodes sweetness\n", " chewiness: tf.Tensor # 2d embedding that encodes chewiness\n", "\n", "@tf.function\n", "def combine_pastry_features(x: Pastry):\n", " return (x.sweetness + x.chewiness) / 2\n", "\n", "cookie = Pastry(sweetness=[1.2, 0.4], chewiness=[0.8, 0.2])\n", "combine_pastry_features(cookie)" ] }, { "cell_type": "markdown", "metadata": { "id": "u1P-0Udg71Vx" }, "source": [ "`input_signature` 대해 `tf.function` 를 명시적으로 지정 `TypeSpec` 사용하여 지정할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.941749Z", "iopub.status.busy": "2022-12-14T22:14:35.941246Z", "iopub.status.idle": "2022-12-14T22:14:35.981105Z", "shell.execute_reply": "2022-12-14T22:14:35.980437Z" }, "id": "0df90E4x78d7" }, "outputs": [ { "data": { "text/plain": [ "Pastry(sweetness=, chewiness=)" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pastry_spec = Pastry.Spec(tf.TensorSpec([2]), tf.TensorSpec(2))\n", "\n", "@tf.function(input_signature=[pastry_spec])\n", "def increase_sweetness(x: Pastry, delta=1.0):\n", " return Pastry(x.sweetness + delta, x.chewiness)\n", "\n", "increase_sweetness(cookie)" ] }, { "cell_type": "markdown", "metadata": { "id": "CdTfc5nD9JpD" }, "source": [ "#### 구체적인 기능\n", "\n", "`tf.function` 의해 구축된 개별 추적 그래프를 캡슐화합니다. 확장 유형은 구체적인 기능과 함께 투명하게 사용할 수 있습니다.\n" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.984420Z", "iopub.status.busy": "2022-12-14T22:14:35.983895Z", "iopub.status.idle": "2022-12-14T22:14:35.989618Z", "shell.execute_reply": "2022-12-14T22:14:35.989061Z" }, "id": "FyHBBQWk9xz2" }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cf = combine_pastry_features.get_concrete_function(pastry_spec)\n", "cf(cookie)" ] }, { "cell_type": "markdown", "metadata": { "id": "LYas8gtG5IMA" }, "source": [ "### 제어 흐름 작업\n", "\n", "확장 유형은 TensorFlow의 제어 흐름 작업에서 지원됩니다.\n", "\n", "- `tf.cond`\n", "- `tf.case`\n", "- `tf.while_loop`\n", "- `tf.identity`\n" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:35.992896Z", "iopub.status.busy": "2022-12-14T22:14:35.992338Z", "iopub.status.idle": "2022-12-14T22:14:35.998260Z", "shell.execute_reply": "2022-12-14T22:14:35.997586Z" }, "id": "6G2XE9ZtJu8z" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "# Example: using tf.cond to select between two MaskedTensors. Note that the\n", "# two MaskedTensors don't need to have the same shape.\n", "a = MaskedTensor([1., 2, 3], [True, False, True])\n", "b = MaskedTensor([22., 33, 108, 55], [True, True, True, False])\n", "condition = tf.constant(True)\n", "print(tf.cond(condition, lambda: a, lambda: b))" ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:36.001286Z", "iopub.status.busy": "2022-12-14T22:14:36.000776Z", "iopub.status.idle": "2022-12-14T22:14:36.007969Z", "shell.execute_reply": "2022-12-14T22:14:36.007328Z" }, "id": "2NwLOw1kKSek" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "# Example: using tf.while_loop with MaskedTensor.\n", "cond = lambda i, _: i < 10\n", "def body(i, mt):\n", " return i + 1, mt.with_values(mt.values + 3 / 7)\n", "print(tf.while_loop(cond, body, [0, b])[1])" ] }, { "cell_type": "markdown", "metadata": { "id": "zkN7IuWVMRzn" }, "source": [ "### 사인 제어 흐름\n", "\n", "확장 유형은 tf.function의 제어 흐름 문에서도 지원됩니다(autograph 사용). 다음 예에서 `if` 문과 `for` 문은 확장 유형을 지원 `tf.cond` 및 `tf.while_loop` 작업으로 자동 변환됩니다." ] }, { "cell_type": "code", "execution_count": 52, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:36.011259Z", "iopub.status.busy": "2022-12-14T22:14:36.010653Z", "iopub.status.idle": "2022-12-14T22:14:36.158599Z", "shell.execute_reply": "2022-12-14T22:14:36.157655Z" }, "id": "4RFySEl8gZ8w" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n" ] } ], "source": [ "@tf.function\n", "def fn(x, b):\n", " if b:\n", " x = MaskedTensor(x, tf.less(x, 0))\n", " else:\n", " x = MaskedTensor(x, tf.greater(x, 0))\n", " for i in tf.range(5 if b else 7):\n", " x = x.with_values(x.values + 1 / 2)\n", " return x\n", "\n", "print(fn(tf.constant([1., -2, 3]), tf.constant(True)))\n", "print(fn(tf.constant([1., -2, 3]), tf.constant(False)))" ] }, { "cell_type": "markdown", "metadata": { "id": "-FjZt2ohfja4" }, "source": [ "### 케라스\n", "\n", "[tf.keras](https://www.tensorflow.org/guide/keras) 는 딥 러닝 모델을 구축하고 훈련하기 위한 TensorFlow의 고급 API입니다. 확장 유형은 Keras 모델에 대한 입력으로 전달되고, Keras 계층 간에 전달되고, Keras 모델에서 반환될 수 있습니다. Keras는 현재 확장 유형에 두 가지 요구 사항을 적용합니다.\n", "\n", "- 배치 처리할 수 있어야 합니다(위의 \"배치 처리할 수 있는 `ExtensionType`\"으로 이동).\n", "- `shape`이라는 필드 또는 속성이 있어야 합니다. `shape[0]`은 배치 차원으로 간주됩니다.\n", "\n", "다음 두 하위 섹션에서는 확장 유형을 Keras와 함께 사용하는 방법을 보여주는 예를 제공합니다.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "QH1TXQYiGv8u" }, "source": [ "#### Keras 예: `Network`\n", "\n", "첫 번째 예에서는 노드 간의 부하 분산 작업에 사용할 수 있는 위의 \"Batchable ExtensionTypes\" 섹션에 정의된 `Network` 그 정의는 여기에서 반복됩니다." ] }, { "cell_type": "code", "execution_count": 53, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:36.162516Z", "iopub.status.busy": "2022-12-14T22:14:36.161784Z", "iopub.status.idle": "2022-12-14T22:14:36.167421Z", "shell.execute_reply": "2022-12-14T22:14:36.166644Z" }, "id": "zHj1RIS2PK50" }, "outputs": [], "source": [ "class Network(tf.experimental.BatchableExtensionType):\n", " shape: tf.TensorShape # batch shape. A single network has shape=[].\n", " work: tf.Tensor # work[*shape, n] = work left to do at node n\n", " bandwidth: tf.Tensor # bandwidth[*shape, n1, n2] = bandwidth from n1->n2\n", "\n", " def __init__(self, work, bandwidth):\n", " self.work = tf.convert_to_tensor(work)\n", " self.bandwidth = tf.convert_to_tensor(bandwidth)\n", " work_batch_shape = self.work.shape[:-1]\n", " bandwidth_batch_shape = self.bandwidth.shape[:-2]\n", " self.shape = work_batch_shape.merge_with(bandwidth_batch_shape)\n", "\n", " def __repr__(self):\n", " return network_repr(self)" ] }, { "cell_type": "code", "execution_count": 54, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:36.170903Z", "iopub.status.busy": "2022-12-14T22:14:36.170208Z", "iopub.status.idle": "2022-12-14T22:14:36.176101Z", "shell.execute_reply": "2022-12-14T22:14:36.175438Z" }, "id": "w9LPTEVJD0FD" }, "outputs": [], "source": [ "single_network = Network( # A single network with 4 nodes.\n", " work=[8.0, 5, 12, 2],\n", " bandwidth=[[0.0, 1, 2, 2], [1, 0, 0, 2], [2, 0, 0, 1], [2, 2, 1, 0]])\n", "\n", "batch_of_networks = Network( # Batch of 2 networks, each w/ 2 nodes.\n", " work=[[8.0, 5], [3, 2]],\n", " bandwidth=[[[0.0, 1], [1, 0]], [[0, 2], [2, 0]]])" ] }, { "cell_type": "markdown", "metadata": { "id": "IUfWi3SDD0dj" }, "source": [ "`Network` 를 처리하는 새로운 Keras 계층을 정의할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 55, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:36.179815Z", "iopub.status.busy": "2022-12-14T22:14:36.179114Z", "iopub.status.idle": "2022-12-14T22:14:36.183052Z", "shell.execute_reply": "2022-12-14T22:14:36.182399Z" }, "id": "2WSYt58r4SF1" }, "outputs": [], "source": [ "class BalanceNetworkLayer(tf.keras.layers.Layer):\n", " \"\"\"Layer that balances work between nodes in a network.\n", "\n", " Shifts work from more busy nodes to less busy nodes, constrained by bandwidth.\n", " \"\"\"\n", " def call(self, inputs):\n", " # This function is defined above in the \"Batchable `ExtensionType`s\" section.\n", " return balance_work_greedy(inputs)" ] }, { "cell_type": "markdown", "metadata": { "id": "VWwFJNb1E03q" }, "source": [ "그런 다음 이러한 레이어를 사용하여 간단한 모델을 생성할 수 있습니다. `type_spec`가 있는 `tf.keras.layer.Input`를 확장 유형의 `TypeSpec`으로 설정할 수 있습니다. Keras 모델을 사용하여 배치 처리하는 경우 배치 차원에 `type_spec`을 포함해야 합니다." ] }, { "cell_type": "code", "execution_count": 56, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:36.186100Z", "iopub.status.busy": "2022-12-14T22:14:36.185857Z", "iopub.status.idle": "2022-12-14T22:14:36.227987Z", "shell.execute_reply": "2022-12-14T22:14:36.227292Z" }, "id": "plTyqISRExA4" }, "outputs": [], "source": [ "input_spec = Network.Spec(shape=None,\n", " work=tf.TensorSpec(None, tf.float32),\n", " bandwidth=tf.TensorSpec(None, tf.float32))\n", "model = tf.keras.Sequential([\n", " tf.keras.layers.Input(type_spec=input_spec),\n", " BalanceNetworkLayer(),\n", " ])" ] }, { "cell_type": "markdown", "metadata": { "id": "hyeAbt1WFIiO" }, "source": [ "마지막으로 단일 네트워크와 네트워크 배치에 모델을 적용할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:36.231465Z", "iopub.status.busy": "2022-12-14T22:14:36.230933Z", "iopub.status.idle": "2022-12-14T22:14:36.237511Z", "shell.execute_reply": "2022-12-14T22:14:36.236861Z" }, "id": "hH1EtA5lFHdN" }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "model(single_network)" ] }, { "cell_type": "code", "execution_count": 58, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:36.240800Z", "iopub.status.busy": "2022-12-14T22:14:36.240223Z", "iopub.status.idle": "2022-12-14T22:14:36.246585Z", "shell.execute_reply": "2022-12-14T22:14:36.245930Z" }, "id": "V7eM67M7FYYM" }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" } ], "source": [ "model(batch_of_networks)" ] }, { "cell_type": "markdown", "metadata": { "id": "tOxtt9Z1HDCv" }, "source": [ "#### 케라스 예시: MaskedTensor\n", "\n", "이 예에서 `MaskedTensor` `Keras` 를 지원하도록 확장되었습니다. `shape` `values` 필드에서 계산되는 속성으로 정의됩니다. `TypeSpec` 모두에 이 속성을 추가해야 합니다. `MaskedTensor` `SavedModel` 직렬화에 필요한 `__name__` 변수도 정의합니다(아래 참조)." ] }, { "cell_type": "code", "execution_count": 59, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:36.249926Z", "iopub.status.busy": "2022-12-14T22:14:36.249408Z", "iopub.status.idle": "2022-12-14T22:14:36.255842Z", "shell.execute_reply": "2022-12-14T22:14:36.255140Z" }, "id": "1JBZ_t48Ht7e" }, "outputs": [], "source": [ "class MaskedTensor(tf.experimental.BatchableExtensionType):\n", " # __name__ is required for serialization in SavedModel; see below for details.\n", " __name__ = 'extension_type_colab.MaskedTensor'\n", "\n", " values: tf.Tensor\n", " mask: tf.Tensor\n", "\n", " shape = property(lambda self: self.values.shape)\n", " dtype = property(lambda self: self.values.dtype)\n", "\n", " def with_default(self, default):\n", " return tf.where(self.mask, self.values, default)\n", "\n", " def __repr__(self):\n", " return masked_tensor_str(self.values, self.mask)\n", "\n", " class Spec:\n", " def __init__(self, shape, dtype=tf.float32):\n", " self.values = tf.TensorSpec(shape, dtype)\n", " self.mask = tf.TensorSpec(shape, tf.bool)\n", "\n", " shape = property(lambda self: self.values.shape)\n", " dtype = property(lambda self: self.values.dtype)\n", "\n", " def with_shape(self):\n", " return MaskedTensor.Spec(tf.TensorSpec(shape, self.values.dtype),\n", " tf.TensorSpec(shape, self.mask.dtype))" ] }, { "cell_type": "markdown", "metadata": { "id": "oer8BVc8H7_V" }, "source": [ "다음으로 디스패치 데코레이터는 여러 TensorFlow API의 기본 동작을 재정의하는 데 사용합니다. 이러한 API는 표준 Keras 레이어(예: `Dense` 레이어)에서 사용하므로 이를 재정의하면 `MaskedTensor`와 함께 해당 레이어를 사용할 수 있습니다. 이 예제에서는 마스킹된 텐서의 `matmul`이 마스킹된 값을 0으로 처리하도록(즉, 제품에 포함하지 않도록) 정의합니다." ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:36.258960Z", "iopub.status.busy": "2022-12-14T22:14:36.258708Z", "iopub.status.idle": "2022-12-14T22:14:36.279783Z", "shell.execute_reply": "2022-12-14T22:14:36.279135Z" }, "id": "xy0dhQ_b-ca_" }, "outputs": [], "source": [ "@tf.experimental.dispatch_for_unary_elementwise_apis(MaskedTensor)\n", "def unary_elementwise_op_handler(op, x):\n", " return MaskedTensor(op(x.values), x.mask)\n", "\n", "@tf.experimental.dispatch_for_binary_elementwise_apis(\n", " Union[MaskedTensor, tf.Tensor],\n", " Union[MaskedTensor, tf.Tensor])\n", "def binary_elementwise_op_handler(op, x, y):\n", " x = convert_to_masked_tensor(x)\n", " y = convert_to_masked_tensor(y)\n", " return MaskedTensor(op(x.values, y.values), x.mask & y.mask)\n", "\n", "@tf.experimental.dispatch_for_api(tf.matmul)\n", "def masked_matmul(a: MaskedTensor, b,\n", " transpose_a=False, transpose_b=False,\n", " adjoint_a=False, adjoint_b=False,\n", " a_is_sparse=False, b_is_sparse=False,\n", " output_type=None):\n", " if isinstance(a, MaskedTensor):\n", " a = a.with_default(0)\n", " if isinstance(b, MaskedTensor):\n", " b = b.with_default(0)\n", " return tf.matmul(a, b, transpose_a, transpose_b, adjoint_a,\n", " adjoint_b, a_is_sparse, b_is_sparse, output_type)" ] }, { "cell_type": "markdown", "metadata": { "id": "osJ_L-fKJusI" }, "source": [ "그런 다음 표준 Keras 레이어를 사용하여 `MaskedTensor` 입력을 허용하는 Keras 모델을 구성할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:36.283134Z", "iopub.status.busy": "2022-12-14T22:14:36.282601Z", "iopub.status.idle": "2022-12-14T22:14:36.333672Z", "shell.execute_reply": "2022-12-14T22:14:36.333025Z" }, "id": "IS6JCVbk1rd0" }, "outputs": [], "source": [ "input_spec = MaskedTensor.Spec([None, 2], tf.float32)\n", "\n", "masked_tensor_model = tf.keras.Sequential([\n", " tf.keras.layers.Input(type_spec=input_spec),\n", " tf.keras.layers.Dense(16, activation=\"relu\"),\n", " tf.keras.layers.Dense(1)])\n", "masked_tensor_model.compile(loss='binary_crossentropy', optimizer='rmsprop')" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:36.337011Z", "iopub.status.busy": "2022-12-14T22:14:36.336478Z", "iopub.status.idle": "2022-12-14T22:14:37.582721Z", "shell.execute_reply": "2022-12-14T22:14:37.581954Z" }, "id": "SB1WUSzn1RPj" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Epoch 1/3\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\r", "1/1 [==============================] - ETA: 0s - loss: 6.4280" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "1/1 [==============================] - 1s 1s/step - loss: 6.4280\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Epoch 2/3\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\r", "1/1 [==============================] - ETA: 0s - loss: 6.1971" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "1/1 [==============================] - 0s 5ms/step - loss: 6.1971\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Epoch 3/3\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\r", "1/1 [==============================] - ETA: 0s - loss: 6.1420" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r", "1/1 [==============================] - 0s 5ms/step - loss: 6.1420\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "tf.Tensor(\n", "[[-0.31530094]\n", " [ 0.79135597]\n", " [ 0.27101254]], shape=(3, 1), dtype=float32)\n" ] } ], "source": [ "a = MaskedTensor([[1., 2], [3, 4], [5, 6]],\n", " [[True, False], [False, True], [True, True]])\n", "masked_tensor_model.fit(a, tf.constant([[1], [0], [1]]), epochs=3)\n", "print(masked_tensor_model(a))" ] }, { "cell_type": "markdown", "metadata": { "id": "msmd9XcL2bqb" }, "source": [ "### 저장된 모델\n", "\n", "[SavedModel](https://www.tensorflow.org/guide/saved_model) 은 가중치와 계산을 모두 포함하는 직렬화된 TensorFlow 프로그램입니다. Keras 모델 또는 사용자 지정 모델에서 구축할 수 있습니다. 두 경우 모두 확장 유형은 SavedModel에 의해 정의된 함수 및 메소드와 함께 투명하게 사용될 수 있습니다.\n", "\n", "`__name__` 필드가 있는 한 확장 유형을 처리하는 모델, 계층 및 함수를 저장할 수 있습니다. 이 이름은 확장 유형을 등록하는 데 사용되므로 모델을 로드할 때 찾을 수 있습니다." ] }, { "cell_type": "markdown", "metadata": { "id": "PEtbFrz6-Vku" }, "source": [ "#### 예: Keras 모델 저장\n", "\n", "확장 유형을 사용하는 `SavedModel` 사용하여 저장할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 63, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:37.586184Z", "iopub.status.busy": "2022-12-14T22:14:37.585905Z", "iopub.status.idle": "2022-12-14T22:14:38.070807Z", "shell.execute_reply": "2022-12-14T22:14:38.070176Z" }, "id": "ecxQMnybSzV6" }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "WARNING:absl:Function `_wrapped_model` contains input name(s) args_0 with unsupported characters which will be renamed to args_0_1 in the SavedModel.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpt3nnr57a/assets\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpt3nnr57a/assets\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "masked_tensor_model_path = tempfile.mkdtemp()\n", "tf.saved_model.save(masked_tensor_model, masked_tensor_model_path)\n", "imported_model = tf.saved_model.load(masked_tensor_model_path)\n", "imported_model(a)" ] }, { "cell_type": "markdown", "metadata": { "id": "Ne2nu3r6-XMr" }, "source": [ "#### 예: 사용자 정의 모델 저장\n", "\n", "SavedModel은 확장 유형을 처리하는 함수로 `tf.Module` 하위 클래스를 저장하는 데 사용할 수도 있습니다." ] }, { "cell_type": "code", "execution_count": 64, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:38.074099Z", "iopub.status.busy": "2022-12-14T22:14:38.073848Z", "iopub.status.idle": "2022-12-14T22:14:38.187117Z", "shell.execute_reply": "2022-12-14T22:14:38.186404Z" }, "id": "2V6hV3yOT2vz" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpw6f0yvf0/assets\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmpw6f0yvf0/assets\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class CustomModule(tf.Module):\n", " def __init__(self, variable_value):\n", " super().__init__()\n", " self.v = tf.Variable(variable_value)\n", "\n", " @tf.function\n", " def grow(self, x: MaskedTensor):\n", " \"\"\"Increase values in `x` by multiplying them by `self.v`.\"\"\"\n", " return MaskedTensor(x.values * self.v, x.mask)\n", "\n", "module = CustomModule(100.0)\n", "\n", "module.grow.get_concrete_function(MaskedTensor.Spec(shape=None,\n", " dtype=tf.float32))\n", "custom_module_path = tempfile.mkdtemp()\n", "tf.saved_model.save(module, custom_module_path)\n", "imported_model = tf.saved_model.load(custom_module_path)\n", "imported_model.grow(MaskedTensor([1., 2, 3], [False, True, False]))" ] }, { "cell_type": "markdown", "metadata": { "id": "o6beljh576ee" }, "source": [ "#### ExtensionType을 사용할 수 없을 때 저장된 모델 로드\n", "\n", "`ExtensionType`을 사용하는 `SavedModel`을 로드하였으나 해당 `ExtensionType`을 사용할 수 없는 경우(즉, 가져오지 않은 경우) 경고가 표시되고 TensorFlow는 \"익명 확장 유형\" 객체를 사용하도록 대체됩니다. 이 객체는 원래 유형과 동일한 필드를 갖지만 사용자 정의 메서드 또는 속성과 같은 유형에 추가한 추가 사용자 정의 설정이 부족합니다." ] }, { "cell_type": "markdown", "metadata": { "id": "ec9PcUkJ9bFK" }, "source": [ "#### TensorFlow Serving으로 `ExtensionType` 사용하기\n", "\n", "현재 [TensorFlow Serving](https://www.tensorflow.org/tfx/guide/serving)(및 SavedModel \"서명\" 사전의 다른 소비자)에서는 모든 입력과 출력이 원시 텐서여야 합니다. 확장 유형을 사용하는 모델과 함께 TensorFlow Serving을 사용하려는 경우 텐서로부터 확장 유형 값을 구성하거나 분해하는 래핑 메서드를 추가할 수 있습니다. 예제는 다음과 같습니다." ] }, { "cell_type": "code", "execution_count": 65, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:38.190682Z", "iopub.status.busy": "2022-12-14T22:14:38.190422Z", "iopub.status.idle": "2022-12-14T22:14:38.391298Z", "shell.execute_reply": "2022-12-14T22:14:38.390677Z" }, "id": "4VnzAwVo9tTc" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmp53zdnf3c/assets\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "INFO:tensorflow:Assets written to: /tmpfs/tmp/tmp53zdnf3c/assets\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 65, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class CustomModuleWrapper(tf.Module):\n", " def __init__(self, variable_value):\n", " super().__init__()\n", " self.v = tf.Variable(variable_value)\n", "\n", " @tf.function\n", " def var_weighted_mean(self, x: MaskedTensor):\n", " \"\"\"Mean value of unmasked values in x, weighted by self.v.\"\"\"\n", " x = MaskedTensor(x.values * self.v, x.mask)\n", " return (tf.reduce_sum(x.with_default(0)) /\n", " tf.reduce_sum(tf.cast(x.mask, x.dtype)))\n", "\n", " @tf.function()\n", " def var_weighted_mean_wrapper(self, x_values, x_mask):\n", " \"\"\"Raw tensor wrapper for var_weighted_mean.\"\"\"\n", " return self.var_weighted_mean(MaskedTensor(x_values, x_mask))\n", "\n", "module = CustomModuleWrapper([3., 2., 8., 5.])\n", "\n", "module.var_weighted_mean_wrapper.get_concrete_function(\n", " tf.TensorSpec(None, tf.float32), tf.TensorSpec(None, tf.bool))\n", "custom_module_path = tempfile.mkdtemp()\n", "tf.saved_model.save(module, custom_module_path)\n", "imported_model = tf.saved_model.load(custom_module_path)\n", "x = MaskedTensor([1., 2., 3., 4.], [False, True, False, True])\n", "imported_model.var_weighted_mean_wrapper(x.values, x.mask)" ] }, { "cell_type": "markdown", "metadata": { "id": "4dwBadWQ5G9_" }, "source": [ "### 데이터세트\n", "\n", "[tf.data](https://www.tensorflow.org/guide/data) 는 간단하고 재사용 가능한 부분으로 복잡한 입력 파이프라인을 구축할 수 있는 API입니다. 핵심 데이터 구조는 `tf.data.Dataset` 이며, 이는 각 요소가 하나 이상의 구성 요소로 구성된 일련의 요소를 나타냅니다." ] }, { "cell_type": "markdown", "metadata": { "id": "GcIR19FuwRJV" }, "source": [ "#### 확장 유형으로 데이터세트 빌드\n", "\n", "`Dataset.from_tensors` , `Dataset.from_tensor_slices` 또는 `Dataset.from_generator` 사용하여 확장 유형 값에서 데이터 세트를 빌드할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 66, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:38.394495Z", "iopub.status.busy": "2022-12-14T22:14:38.394251Z", "iopub.status.idle": "2022-12-14T22:14:38.407783Z", "shell.execute_reply": "2022-12-14T22:14:38.407124Z" }, "id": "Oe7fRCkzwdub" }, "outputs": [ { "data": { "text/plain": [ "Pastry(sweetness=, chewiness=)" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ds = tf.data.Dataset.from_tensors(Pastry(5, 5))\n", "iter(ds).next()" ] }, { "cell_type": "code", "execution_count": 67, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:38.411103Z", "iopub.status.busy": "2022-12-14T22:14:38.410576Z", "iopub.status.idle": "2022-12-14T22:14:38.429336Z", "shell.execute_reply": "2022-12-14T22:14:38.428727Z" }, "id": "fk9CD2fZx6yT" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "\n", "\n", "\n" ] } ], "source": [ "mt = MaskedTensor(tf.reshape(range(20), [5, 4]), tf.ones([5, 4]))\n", "ds = tf.data.Dataset.from_tensor_slices(mt)\n", "for value in ds:\n", " print(value)" ] }, { "cell_type": "code", "execution_count": 68, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:38.432669Z", "iopub.status.busy": "2022-12-14T22:14:38.432429Z", "iopub.status.idle": "2022-12-14T22:14:38.495775Z", "shell.execute_reply": "2022-12-14T22:14:38.495109Z" }, "id": "DGw8y87awsOJ" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "\n", "\n", "\n" ] } ], "source": [ "def value_gen():\n", " for i in range(2, 7):\n", " yield MaskedTensor(range(10), [j%i != 0 for j in range(10)])\n", "\n", "ds = tf.data.Dataset.from_generator(\n", " value_gen, output_signature=MaskedTensor.Spec(shape=[10], dtype=tf.int32))\n", "for value in ds:\n", " print(value)" ] }, { "cell_type": "markdown", "metadata": { "id": "wfEm4NInyqtj" }, "source": [ "#### 확장 유형이 있는 데이터 세트 일괄 처리 및 일괄 해제\n", "\n", "확장 유형이 있는 데이터세트는 `Dataset.batch` 및 `Dataset.unbatch`를 사용하여 배치 처리 및 배치 처리 취소할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 69, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:38.499381Z", "iopub.status.busy": "2022-12-14T22:14:38.498910Z", "iopub.status.idle": "2022-12-14T22:14:38.541909Z", "shell.execute_reply": "2022-12-14T22:14:38.541305Z" }, "id": "snoOUE1ay1rO" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "\n" ] } ], "source": [ "batched_ds = ds.batch(2)\n", "for value in batched_ds:\n", " print(value)" ] }, { "cell_type": "code", "execution_count": 70, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:14:38.545137Z", "iopub.status.busy": "2022-12-14T22:14:38.544890Z", "iopub.status.idle": "2022-12-14T22:14:38.597087Z", "shell.execute_reply": "2022-12-14T22:14:38.596377Z" }, "id": "f8PTky6EzBVY" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "\n", "\n", "\n" ] } ], "source": [ "unbatched_ds = batched_ds.unbatch()\n", "for value in unbatched_ds:\n", " print(value)" ] } ], "metadata": { "colab": { "collapsed_sections": [], "name": "extension_type.ipynb", "toc_visible": true }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.16" } }, "nbformat": 4, "nbformat_minor": 0 }