{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "ZjN_IJ8mhJ-4" }, "source": [ "##### Copyright 2020 The TensorFlow Authors." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "cellView": "form", "execution": { "iopub.execute_input": "2022-12-14T21:24:25.515521Z", "iopub.status.busy": "2022-12-14T21:24:25.515002Z", "iopub.status.idle": "2022-12-14T21:24:25.518863Z", "shell.execute_reply": "2022-12-14T21:24:25.518230Z" }, "id": "sY3Ffd83hK3b" }, "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": "03Pw58e6mTHI" }, "source": [ "# TensorFlow 기반의 NumPy API" ] }, { "cell_type": "markdown", "metadata": { "id": "7WpGysDJmZsg" }, "source": [ "\n", " \n", " \n", " \n", " \n", "
TensorFlow.org에서 보기 Google Colab에서 실행 GitHub에서 소스 보기 노트북 다운로드
" ] }, { "cell_type": "markdown", "metadata": { "id": "s2enCDi_FvCR" }, "source": [ "## 개요\n", "\n", "TensorFlow는 `tf.experimental.numpy`로 사용할 수 있는 [NumPy API](https://numpy.org/doc/1.16)의 하위 집합을 구현합니다. 이를 통해 TensorFlow에서 NumPy 코드를 빠르게 실행할 수 있으며 TensorFlow의 모든 API에 액세스할 수 있습니다." ] }, { "cell_type": "markdown", "metadata": { "id": "ob1HNwUmYR5b" }, "source": [ "## 설정\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:25.522403Z", "iopub.status.busy": "2022-12-14T21:24:25.521861Z", "iopub.status.idle": "2022-12-14T21:24:27.800781Z", "shell.execute_reply": "2022-12-14T21:24:27.800104Z" }, "id": "AJR558zjAZQu" }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2022-12-14 21:24:26.806384: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer.so.7'; dlerror: libnvinfer.so.7: cannot open shared object file: No such file or directory\n", "2022-12-14 21:24:26.806474: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer_plugin.so.7'; dlerror: libnvinfer_plugin.so.7: cannot open shared object file: No such file or directory\n", "2022-12-14 21:24:26.806484: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Cannot dlopen some TensorRT libraries. If you would like to use Nvidia GPU with TensorRT, please make sure the missing libraries mentioned above are installed properly.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Using TensorFlow version 2.11.0\n" ] } ], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import tensorflow as tf\n", "import tensorflow.experimental.numpy as tnp\n", "import timeit\n", "\n", "print(\"Using TensorFlow version %s\" % tf.__version__)" ] }, { "cell_type": "markdown", "metadata": { "id": "M6tacoy0DU6e" }, "source": [ "### NumPy 동작 사용\n", "\n", "`tnp`를 NumPy로 사용하려면 TensorFlow에 대해 NumPy 동작을 활성화합니다." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:27.804715Z", "iopub.status.busy": "2022-12-14T21:24:27.804017Z", "iopub.status.idle": "2022-12-14T21:24:27.807456Z", "shell.execute_reply": "2022-12-14T21:24:27.806882Z" }, "id": "TfCyofpFDQxm" }, "outputs": [], "source": [ "tnp.experimental_enable_numpy_behavior()" ] }, { "cell_type": "markdown", "metadata": { "id": "et9D5wq0D1H2" }, "source": [ "이 호출은 TensorFlow에서 유형 승격을 활성화하고, 리터럴을 텐서로 변환할 때 유형 추론을 변경하여 NumPy 표준을 보다 엄격하게 따릅니다.\n", "\n", "참고: 이 호출은 `tf.experimental.numpy` 모듈뿐만 아니라 전체 TensorFlow의 동작을 변경합니다." ] }, { "cell_type": "markdown", "metadata": { "id": "yh2BwqUzH3C3" }, "source": [ "## TensorFlow NumPy ND 배열\n", "\n", "**ND 배열**이라는 `tf.experimental.numpy.ndarray`의 인스턴스는 특정 기기에 배치된 주어진 `dtype`의 다차원 고밀도 배열을 나타냅니다. 이것은 `tf.Tensor`에 대한 별칭입니다. `ndarray.T`, `ndarray.reshape`, `ndarray.ravel` 등과 같은 유용한 메서드를 위한 ND 배열 클래스를 확인해 보세요.\n", "\n", "먼저 ND 배열 객체를 만든 다음, 다양한 메서드를 호출합니다. " ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:27.811006Z", "iopub.status.busy": "2022-12-14T21:24:27.810454Z", "iopub.status.idle": "2022-12-14T21:24:31.202574Z", "shell.execute_reply": "2022-12-14T21:24:31.201681Z" }, "id": "-BHJjxigJ2H1" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Created ND array with shape = (5, 3), rank = 2, dtype = on device = /job:localhost/replica:0/task:0/device:GPU:0\n", "\n", "Is `ones` an instance of tf.Tensor: True\n", "\n", "ndarray.T has shape (3, 5)\n", "narray.reshape(-1) has shape (15,)\n" ] } ], "source": [ "# Create an ND array and check out different attributes.\n", "ones = tnp.ones([5, 3], dtype=tnp.float32)\n", "print(\"Created ND array with shape = %s, rank = %s, \"\n", " \"dtype = %s on device = %s\\n\" % (\n", " ones.shape, ones.ndim, ones.dtype, ones.device))\n", "\n", "# `ndarray` is just an alias to `tf.Tensor`.\n", "print(\"Is `ones` an instance of tf.Tensor: %s\\n\" % isinstance(ones, tf.Tensor))\n", "\n", "# Try commonly used member functions.\n", "print(\"ndarray.T has shape %s\" % str(ones.T.shape))\n", "print(\"narray.reshape(-1) has shape %s\" % ones.reshape(-1).shape)" ] }, { "cell_type": "markdown", "metadata": { "id": "Mub8-dvJMUr4" }, "source": [ "### 형식 승격\n", "\n", "TensorFlow NumPy API에는 리터럴을 ND 배열로 변환하고 ND 배열 입력에 대해 형식 승격을 수행하기 위한 잘 정의된 의미 체계가 있습니다. 자세한 내용은 [`np.result_type`](https://numpy.org/doc/1.16/reference/generated/numpy.result_type.html)을 참조하세요." ] }, { "cell_type": "markdown", "metadata": { "id": "vcRznNaMj27J" }, "source": [ "TensorFlow API는 `tf.Tensor` 입력을 변경하지 않고 유형 승격을 수행하지 않는 반면, TensorFlow NumPy API는 NumPy 유형 승격 규칙에 따라 모든 입력을 승격합니다. 다음 예에서는 유형 승격을 수행합니다. 먼저, 서로 다른 유형의 ND 배열 입력에 추가를 실행하고 출력 유형을 기록합니다. 이러한 유형의 승격은 TensorFlow API에서 허용되지 않습니다." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:31.206552Z", "iopub.status.busy": "2022-12-14T21:24:31.205855Z", "iopub.status.idle": "2022-12-14T21:24:31.221643Z", "shell.execute_reply": "2022-12-14T21:24:31.220870Z" }, "id": "uHmBi4KZI2t1" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Type promotion for operations\n", "int32 + int64 => int64\n", "int32 + float32 => float64\n", "int32 + float64 => float64\n", "int64 + float32 => float64\n", "int64 + float64 => float64\n", "float32 + float64 => float64\n" ] } ], "source": [ "print(\"Type promotion for operations\")\n", "values = [tnp.asarray(1, dtype=d) for d in\n", " (tnp.int32, tnp.int64, tnp.float32, tnp.float64)]\n", "for i, v1 in enumerate(values):\n", " for v2 in values[i + 1:]:\n", " print(\"%s + %s => %s\" % \n", " (v1.dtype.name, v2.dtype.name, (v1 + v2).dtype.name))" ] }, { "cell_type": "markdown", "metadata": { "id": "CrpIoOc7oqox" }, "source": [ "마지막으로, `ndarray.asarray`를 사용하여 리터럴을 ND 배열로 변환하고 결과 유형을 확인합니다." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:31.225020Z", "iopub.status.busy": "2022-12-14T21:24:31.224558Z", "iopub.status.idle": "2022-12-14T21:24:31.229662Z", "shell.execute_reply": "2022-12-14T21:24:31.228840Z" }, "id": "1m1cp8_VooNk" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Type inference during array creation\n", "tnp.asarray(1).dtype == tnp.int64\n", "tnp.asarray(1.).dtype == tnp.float64\n", "\n" ] } ], "source": [ "print(\"Type inference during array creation\")\n", "print(\"tnp.asarray(1).dtype == tnp.%s\" % tnp.asarray(1).dtype.name)\n", "print(\"tnp.asarray(1.).dtype == tnp.%s\\n\" % tnp.asarray(1.).dtype.name)" ] }, { "cell_type": "markdown", "metadata": { "id": "kd-_iccXoRL8" }, "source": [ "리터럴을 ND 배열로 변환할 때 NumPy는 `tnp.int64` 및 `tnp.float64`와 같은 넓은 유형을 선호합니다. 반대로 `tf.convert_to_tensor`는 상수를 `tf.Tensor`로 변환하기 위해 `tf.int32` 및 `tf.float32` 유형을 선호합니다. TensorFlow NumPy API는 정수에 대한 NumPy 동작을 준수합니다. 부동 소수점의 경우, `experimental_enable_numpy_behavior`의 `prefer_float32` 인수를 사용하여 `tf.float64`에 비해 `tf.float32`를 선호할지 여부를 제어할 수 있습니다(기본적으로 `False`). 예를 들면 다음과 같습니다." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:31.232997Z", "iopub.status.busy": "2022-12-14T21:24:31.232451Z", "iopub.status.idle": "2022-12-14T21:24:31.239980Z", "shell.execute_reply": "2022-12-14T21:24:31.239253Z" }, "id": "4gKasnH0j84C" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "When prefer_float32 is True:\n", "tnp.asarray(1.).dtype == tnp.float32\n", "tnp.add(1., 2.).dtype == tnp.float32\n", "When prefer_float32 is False:\n", "tnp.asarray(1.).dtype == tnp.float64\n", "tnp.add(1., 2.).dtype == tnp.float64\n" ] } ], "source": [ "tnp.experimental_enable_numpy_behavior(prefer_float32=True)\n", "print(\"When prefer_float32 is True:\")\n", "print(\"tnp.asarray(1.).dtype == tnp.%s\" % tnp.asarray(1.).dtype.name)\n", "print(\"tnp.add(1., 2.).dtype == tnp.%s\" % tnp.add(1., 2.).dtype.name)\n", "\n", "tnp.experimental_enable_numpy_behavior(prefer_float32=False)\n", "print(\"When prefer_float32 is False:\")\n", "print(\"tnp.asarray(1.).dtype == tnp.%s\" % tnp.asarray(1.).dtype.name)\n", "print(\"tnp.add(1., 2.).dtype == tnp.%s\" % tnp.add(1., 2.).dtype.name)" ] }, { "cell_type": "markdown", "metadata": { "id": "MwCCDxSZOfA1" }, "source": [ "### 브로드캐스팅\n", "\n", "TensorFlow와 유사하게 NumPy는 \"브로드캐스팅\" 값에 대한 풍부한 의미 체계를 정의합니다. 자세한 내용은 [NumPy 브로드캐스팅 가이드](https://numpy.org/doc/stable/user/basics.broadcasting.html)를 확인하고 [TensorFlow 브로드캐스팅 의미 체계](https://www.tensorflow.org/guide/tensor#broadcasting)와 비교할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:31.243089Z", "iopub.status.busy": "2022-12-14T21:24:31.242661Z", "iopub.status.idle": "2022-12-14T21:24:31.249544Z", "shell.execute_reply": "2022-12-14T21:24:31.248786Z" }, "id": "qlyOShxIO0s2" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Broadcasting shapes (2, 3), (3,) and (1, 2, 1) gives shape (1, 2, 3)\n" ] } ], "source": [ "x = tnp.ones([2, 3])\n", "y = tnp.ones([3])\n", "z = tnp.ones([1, 2, 1])\n", "print(\"Broadcasting shapes %s, %s and %s gives shape %s\" % (\n", " x.shape, y.shape, z.shape, (x + y + z).shape))" ] }, { "cell_type": "markdown", "metadata": { "id": "LEVr4ctRPrqR" }, "source": [ "### 인덱싱\n", "\n", "NumPy는 매우 정교한 인덱싱 규칙을 정의합니다. [NumPy 인덱싱 가이드](https://numpy.org/doc/stable/reference/arrays.indexing.html)를 참조하세요. 아래 인덱스로 ND 배열을 사용합니다." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:31.252818Z", "iopub.status.busy": "2022-12-14T21:24:31.252335Z", "iopub.status.idle": "2022-12-14T21:24:31.302627Z", "shell.execute_reply": "2022-12-14T21:24:31.301773Z" }, "id": "lRsrtnd3YyMj" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Basic indexing\n", "tf.Tensor(\n", "[[[16 17 18 19]\n", " [20 21 22 23]]], shape=(1, 2, 4), dtype=int64) \n", "\n", "Boolean indexing\n", "tf.Tensor(\n", "[[[ 0 1 2 3]\n", " [ 8 9 10 11]]\n", "\n", " [[12 13 14 15]\n", " [20 21 22 23]]], shape=(2, 2, 4), dtype=int64) \n", "\n", "Advanced indexing\n", "tf.Tensor([12 13 17], shape=(3,), dtype=int64)\n" ] } ], "source": [ "x = tnp.arange(24).reshape(2, 3, 4)\n", "\n", "print(\"Basic indexing\")\n", "print(x[1, tnp.newaxis, 1:3, ...], \"\\n\")\n", "\n", "print(\"Boolean indexing\")\n", "print(x[:, (True, False, True)], \"\\n\")\n", "\n", "print(\"Advanced indexing\")\n", "print(x[1, (0, 0, 1), tnp.asarray([0, 1, 1])])" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:31.305913Z", "iopub.status.busy": "2022-12-14T21:24:31.305425Z", "iopub.status.idle": "2022-12-14T21:24:31.310231Z", "shell.execute_reply": "2022-12-14T21:24:31.309461Z" }, "id": "yRAaiGhlaNw7" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Currently, TensorFlow NumPy does not support mutation.\n" ] } ], "source": [ "# Mutation is currently not supported\n", "try:\n", " tnp.arange(6)[1] = -1\n", "except TypeError:\n", " print(\"Currently, TensorFlow NumPy does not support mutation.\")" ] }, { "cell_type": "markdown", "metadata": { "id": "5XfJ602j-GVD" }, "source": [ "### 예시 모델\n", "\n", "다음으로, 모델을 만들고 추론을 실행하는 방법을 볼 수 있습니다. 이 간단한 모델은 relu 레이어와 직선 투영법(linear projection)을 적용합니다. 이후 섹션에서는 TensorFlow의 `GradientTape`를 사용하여 모델의 그래디언트를 계산하는 방법을 보여줍니다." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:31.313525Z", "iopub.status.busy": "2022-12-14T21:24:31.312943Z", "iopub.status.idle": "2022-12-14T21:24:31.647087Z", "shell.execute_reply": "2022-12-14T21:24:31.646001Z" }, "id": "kR_KCh4kYEhm" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "tf.Tensor(\n", "[[ 0.63546157 -0.58499926]\n", " [ 0.63546157 -0.58499926]], shape=(2, 2), dtype=float32)\n" ] } ], "source": [ "class Model(object):\n", " \"\"\"Model with a dense and a linear layer.\"\"\"\n", "\n", " def __init__(self):\n", " self.weights = None\n", "\n", " def predict(self, inputs):\n", " if self.weights is None:\n", " size = inputs.shape[1]\n", " # Note that type `tnp.float32` is used for performance.\n", " stddev = tnp.sqrt(size).astype(tnp.float32)\n", " w1 = tnp.random.randn(size, 64).astype(tnp.float32) / stddev\n", " bias = tnp.random.randn(64).astype(tnp.float32)\n", " w2 = tnp.random.randn(64, 2).astype(tnp.float32) / 8\n", " self.weights = (w1, bias, w2)\n", " else:\n", " w1, bias, w2 = self.weights\n", " y = tnp.matmul(inputs, w1) + bias\n", " y = tnp.maximum(y, 0) # Relu\n", " return tnp.matmul(y, w2) # Linear projection\n", "\n", "model = Model()\n", "# Create input data and compute predictions.\n", "print(model.predict(tnp.ones([2, 32], dtype=tnp.float32)))" ] }, { "cell_type": "markdown", "metadata": { "id": "kSR7Ou5YcS38" }, "source": [ "## TensorFlow NumPy 및 NumPy\n", "\n", "TensorFlow NumPy는 전체 NumPy 사양의 하위 집합을 구현합니다. 시간이 지남에 따라 더 많은 기호가 추가되지만, 가까운 장래에 지원되지 않는 체계적인 기능이 있습니다. 여기에는 NumPy C API 지원, Swig 통합, Fortran 저장 순서, 뷰 및 `stride_tricks` 및 일부 `dtype`(예: `np.recarray` 및 `np.object`)이 포함됩니다. 자세한 내용은 [TensorFlow NumPy API 설명서](https://www.tensorflow.org/api_docs/python/tf/experimental/numpy)를 참조하세요.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "Jb1KXak2YlNN" }, "source": [ "### NumPy 상호 운용성\n", "\n", "TensorFlow ND 배열은 NumPy 함수와 상호 운용될 수 있습니다. 이러한 객체는 `__array__` 인터페이스를 구현합니다. NumPy는 이 인터페이스를 사용하여 함수 인수를 처리하기 전에 `np.ndarray` 값으로 변환합니다.\n", "\n", "마찬가지로, TensorFlow NumPy 함수는 `np.ndarray`를 포함하여 다양한 형식의 입력을 받을 수 있습니다. 이러한 입력은 `ndarray.asarray`를 호출하여 ND 배열로 변환됩니다.\n", "\n", "ND 배열과 `np.ndarray` 간의 변환은 실제 데이터 복사를 트리거할 수 있습니다. 자세한 내용은 [버퍼 복사본](#Buffer-copies) 섹션을 참조하세요." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:31.651506Z", "iopub.status.busy": "2022-12-14T21:24:31.650728Z", "iopub.status.idle": "2022-12-14T21:24:31.659874Z", "shell.execute_reply": "2022-12-14T21:24:31.658994Z" }, "id": "cMOCgzQmeXRU" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sum = 6.0. Class: \n", "sum = 6.0. Class: \n" ] } ], "source": [ "# ND array passed into NumPy function.\n", "np_sum = np.sum(tnp.ones([2, 3]))\n", "print(\"sum = %s. Class: %s\" % (float(np_sum), np_sum.__class__))\n", "\n", "# `np.ndarray` passed into TensorFlow NumPy function.\n", "tnp_sum = tnp.sum(np.ones([2, 3]))\n", "print(\"sum = %s. Class: %s\" % (float(tnp_sum), tnp_sum.__class__))" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:31.663204Z", "iopub.status.busy": "2022-12-14T21:24:31.662730Z", "iopub.status.idle": "2022-12-14T21:24:31.803592Z", "shell.execute_reply": "2022-12-14T21:24:31.802710Z" }, "id": "ZaLPjzxft780" }, "outputs": [ { "data": { "image/png": "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WocA64e81gcAAKAd8Vk8AADAOAQKAAAwDoECAACMQ6AAAADjdLhAOXv2rObNm6eEhASFhYXpuuuu0/PPP/+D3te/oysvL9f48ePldrsVEBCg1atX++23LEvz589XTEyMwsLClJqaqr1799ozbBu63Do0NDRozpw5GjhwoLp37y632637779f1dXV9g3cRpr7efhfDz/8sAICArR48eJ2m6+9/JB12LNnj+644w45nU51795dw4cP14EDB9p/2DbU3DrU1dUpJydHvXv3VlhYmO9DWjubgoICDR8+XOHh4YqKitKECRNUWVnpd8ypU6eUnZ2tHj166JprrlFWVtZFbxza0TW3DkePHtXMmTPVt29fhYWFKS4uTo8++qjvs+5M0OECZcGCBVq2bJn+/Oc/a8+ePVqwYIEWLlyoJUuW2D1amztx4oQGDx6spUuXNrl/4cKFeuWVV1RYWKitW7eqe/fuSk9P16lTp9p50rZ1uXU4efKktm/frnnz5mn79u169913VVlZqTvuuMOGSdtWcz8P561atUpbtmz5QW8t3RE1tw779+/XyJEjlZiYqE2bNmnnzp2aN2+eunbt2s6Ttq3m1iEvL08lJSV68803tWfPHuXm5ionJ0dr1qxp50nbVllZmbKzs7VlyxatX79eDQ0NSktL04kTJ3zHzJo1S2vXrtXKlStVVlam6upqTZw40capW19z61BdXa3q6motWrRIu3bt0vLly1VSUqJp06bZPPn/sDqYzMxM66GHHvLbNnHiRGvy5Mk2TWQPSdaqVat81xsbGy2Xy2X94Q9/8G07duyYFRoaar311ls2TNg+LlyHpnzyySeWJOvrr79un6FscKl1+Oabb6yf/vSn1q5du6z4+Hjr5ZdfbvfZ2lNT63DPPfdY9913nz0D2aSpdejfv7/13HPP+W372c9+Zj355JPtOFn7O3z4sCXJKisrsyzr3O/F4OBga+XKlb5j9uzZY0myKioq7BqzzV24Dk155513rJCQEKuhoaEdJ7u0DncG5eabb1Zpaam+/PJLSdK///1vbd68WWPHjrV5MntVVVXJ4/EoNTXVt83pdCo5OVkVFRU2Tma/2tpaBQQEKCIiwu5R2lVjY6OmTJmi2bNnq3///naPY4vGxkatW7dON954o9LT0xUVFaXk5OTLPhzWWd18881as2aN/vvf/8qyLG3cuFFffvml0tLS7B6tTZ1/yCIyMlKStG3bNjU0NPj9rkxMTFRcXFyn/l154Tpc6hiHw6GgIDM+R7jDBcrcuXM1adIkJSYmKjg4WEOHDlVubq4mT55s92i28ng8knTRO/FGR0f79l2NTp06pTlz5ujee++96j4gbMGCBQoKCtKjjz5q9yi2OXz4sOrq6vTiiy8qIyND//znP3XXXXdp4sSJKisrs3u8drVkyRIlJSWpd+/eCgkJUUZGhpYuXapRo0bZPVqbaWxsVG5urm655RYNGDBA0rnflSEhIRf9D0tn/l3Z1Dpc6Ntvv9Xzzz+vGTNmtPN0l2ZGJrXAO++8oxUrVqioqEj9+/fXjh07lJubK7fbralTp9o9HgzS0NCgX/7yl7IsS8uWLbN7nHa1bds2/elPf9L27dsVEBBg9zi2aWxslCTdeeedmjVrliRpyJAh+vjjj1VYWKhbb73VzvHa1ZIlS7RlyxatWbNG8fHxKi8vV3Z2ttxut9/ZhM4kOztbu3bt0ubNm+0exVbNrYPX61VmZqaSkpL0zDPPtO9wl9HhAmX27Nm+syiSNHDgQH399dcqKCi4qgPF5XJJkmpqahQTE+PbXlNToyFDhtg0lX3Ox8nXX3+tDRs2XHVnTz766CMdPnxYcXFxvm1nz57VY489psWLF+urr76yb7h21LNnTwUFBSkpKclve79+/a6qP1rff/+9nnjiCa1atUqZmZmSpEGDBmnHjh1atGhRpwyUnJwcFRcXq7y8XL179/Ztd7lcOn36tI4dO+Z3FqWmpsb3e7QzudQ6nHf8+HFlZGQoPDxcq1atUnBwsA1TNq3DPcRz8uRJdeniP3ZgYKDv/5SuVgkJCXK5XCotLfVt83q92rp1q1JSUmycrP2dj5O9e/fqww8/VI8ePeweqd1NmTJFO3fu1I4dO3wXt9ut2bNn64MPPrB7vHYTEhKi4cOHX/Qy0y+//FLx8fE2TdX+Ghoa1NDQcFX87rQsSzk5OVq1apU2bNighIQEv/3Dhg1TcHCw3+/KyspKHThwoFP9rmxuHaRzfyPS0tIUEhKiNWvWGPfKtg53BmX8+PH63e9+p7i4OPXv31//+te/9NJLL+mhhx6ye7Q2V1dXp3379vmuV1VVaceOHYqMjFRcXJxyc3P1wgsv6IYbblBCQoLmzZsnt9utCRMm2Dd0G7jcOsTExOjuu+/W9u3bVVxcrLNnz/oeV46MjFRISIhdY7e65n4eLgyz4OBguVwu9e3bt71HbVPNrcPs2bN1zz33aNSoUbrttttUUlKitWvXatOmTfYN3QaaW4dbb71Vs2fPVlhYmOLj41VWVqY33nhDL730ko1Tt77s7GwVFRXpvffeU3h4uO+/f6fTqbCwMDmdTk2bNk15eXmKjIyUw+HQzJkzlZKSohEjRtg8fetpbh3Ox8nJkyf15ptvyuv1yuv1SpJ69eqlwMBAO8c/x94XEbWc1+u1fvOb31hxcXFW165drWuvvdZ68sknrfr6ertHa3MbN260JF10mTp1qmVZ515qPG/ePCs6OtoKDQ21xowZY1VWVto7dBu43DpUVVU1uU+StXHjRrtHb1XN/TxcqLO+zPiHrMNrr71mXX/99VbXrl2twYMHW6tXr7Zv4DbS3DocOnTIeuCBByy322117drV6tu3r/XHP/7RamxstHfwVnap//5ff/113zHff/+99etf/9r6yU9+YnXr1s266667rEOHDtk3dBtobh0u9fMiyaqqqrJ19vMCLOsqeAtWAADQoXS456AAAIDOj0ABAADGIVAAAIBxCBQAAGAcAgUAABiHQAEAAMYhUAAAgHEIFAAAYBwCBQAAGIdAAQAAxiFQAACAcQgUAABgnP8HnSmQCaKtkrAAAAAASUVORK5CYII=\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# It is easy to plot ND arrays, given the __array__ interface.\n", "labels = 15 + 2 * tnp.random.randn(1, 1000)\n", "_ = plt.hist(labels)" ] }, { "cell_type": "markdown", "metadata": { "id": "kF-Xyw3XWKqJ" }, "source": [ "### 버퍼 복사본\n", "\n", "TensorFlow NumPy와 NumPy 코드를 혼합하면 데이터 복사가 트리거될 수 있습니다. 이는 TensorFlow NumPy가 NumPy보다 메모리 정렬에 대한 요구 사항이 더 엄격하기 때문입니다.\n", "\n", "`np.ndarray`가 TensorFlow Numpy에 전달되면 정렬 요구 사항을 확인하고 필요한 경우 복사본을 트리거합니다. ND 배열 CPU 버퍼를 NumPy에 전달할 때 일반적으로 버퍼는 정렬 요구 사항을 충족하며 NumPy는 복사본을 만들 필요가 없습니다.\n", "\n", "ND 배열은 로컬 CPU 메모리가 아닌 기기에 배치된 버퍼를 참조할 수 있습니다. 이러한 경우, NumPy 함수를 호출하면 필요에 따라 네트워크 또는 기기에서 복사본이 트리거됩니다.\n", "\n", "따라서 NumPy API 호출과의 혼합은 일반적으로 주의해서 수행해야 하며 사용자는 데이터 복사 오버헤드에 주의해야 합니다. TensorFlow NumPy 호출을 TensorFlow 호출과 인터리빙하는 것은 일반적으로 안전하며 데이터 복사를 방지합니다. 자세한 내용은 [tensorflow 상호 운용성](#tensorflow-interoperability) 섹션을 참조하세요." ] }, { "cell_type": "markdown", "metadata": { "id": "RwljbqkBc7Ro" }, "source": [ "### 연산자 우선 순위\n", "\n", "TensorFlow NumPy는 NumPy보다 높은 `__array_priority__`를 정의합니다. 즉, ND 배열과 `np.ndarray`를 둘 다 포함하는 연산자의 경우, 전자가 우선합니다. 즉, `np.ndarray` 입력이 ND 배열로 변환되고 연산자의 TensorFlow NumPy 구현이 호출됩니다." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:31.807267Z", "iopub.status.busy": "2022-12-14T21:24:31.806735Z", "iopub.status.idle": "2022-12-14T21:24:31.812675Z", "shell.execute_reply": "2022-12-14T21:24:31.811915Z" }, "id": "Cbw8a3G_WUO7" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x = tf.Tensor([2. 2.], shape=(2,), dtype=float64)\n", "class = \n" ] } ], "source": [ "x = tnp.ones([2]) + np.ones([2])\n", "print(\"x = %s\\nclass = %s\" % (x, x.__class__))" ] }, { "cell_type": "markdown", "metadata": { "id": "DNEab_Ctky83" }, "source": [ "## TF NumPy 및 TensorFlow\n", "\n", "TensorFlow NumPy는 TensorFlow를 기반으로 하므로 TensorFlow와 원활하게 상호 운용됩니다." ] }, { "cell_type": "markdown", "metadata": { "id": "fCcfgrlOnAhQ" }, "source": [ "### `tf.Tensor` 및 ND 배열\n", "\n", "ND 배열은 `tf.Tensor`에 대한 별칭이므로 실제 데이터 복사를 트리거하지 않고 서로 혼합될 수 있습니다." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:31.816299Z", "iopub.status.busy": "2022-12-14T21:24:31.815831Z", "iopub.status.idle": "2022-12-14T21:24:31.821213Z", "shell.execute_reply": "2022-12-14T21:24:31.820456Z" }, "id": "BkHVauKwnky_" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "tf.Tensor([1 2], shape=(2,), dtype=int32)\n", "tf.Tensor([1 2], shape=(2,), dtype=int32)\n", "tf.Tensor([1 2], shape=(2,), dtype=int32)\n", "[1 2] \n" ] } ], "source": [ "x = tf.constant([1, 2])\n", "print(x)\n", "\n", "# `asarray` and `convert_to_tensor` here are no-ops.\n", "tnp_x = tnp.asarray(x)\n", "print(tnp_x)\n", "print(tf.convert_to_tensor(tnp_x))\n", "\n", "# Note that tf.Tensor.numpy() will continue to return `np.ndarray`.\n", "print(x.numpy(), x.numpy().__class__)" ] }, { "cell_type": "markdown", "metadata": { "id": "_151HQVBooxG" }, "source": [ "### TensorFlow 상호 운용성\n", "\n", "ND 배열은 단지 `tf.Tensor`에 대한 별칭이기 때문에 ND 배열을 TensorFlow API에 전달할 수 있습니다. 앞서 언급했듯이, 이러한 상호 연산은 가속기 또는 원격 기기에 있는 데이터의 경우에도 실제로 데이터 복사를 수행하지 않습니다.\n", "\n", "반대로, `tf.Tensor` 객체는 데이터 복사를 수행하지 않고 `tf.experimental.numpy` API로 전달할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:31.824458Z", "iopub.status.busy": "2022-12-14T21:24:31.823979Z", "iopub.status.idle": "2022-12-14T21:24:31.831319Z", "shell.execute_reply": "2022-12-14T21:24:31.830538Z" }, "id": "-QvxNhrFoz09" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Output = tf.Tensor(6.0, shape=(), dtype=float32)\n", "Output = tf.Tensor(6.0, shape=(), dtype=float32)\n" ] } ], "source": [ "# ND array passed into TensorFlow function.\n", "tf_sum = tf.reduce_sum(tnp.ones([2, 3], tnp.float32))\n", "print(\"Output = %s\" % tf_sum)\n", "\n", "# `tf.Tensor` passed into TensorFlow NumPy function.\n", "tnp_sum = tnp.sum(tf.ones([2, 3]))\n", "print(\"Output = %s\" % tnp_sum)" ] }, { "cell_type": "markdown", "metadata": { "id": "1b4HeAkhprF_" }, "source": [ "### 그래디언트 및 야고비 행렬식: tf.GradientTape\n", "\n", "TensorFlow의 GradientTape는 TensorFlow 및 TensorFlow NumPy 코드를 통한 역전파에 사용할 수 있습니다.\n", "\n", "[예시 모델](#example-model) 섹션에서 생성된 모델을 사용하고 그래디언트와 야고비 행렬식을 계산합니다." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:31.834333Z", "iopub.status.busy": "2022-12-14T21:24:31.834008Z", "iopub.status.idle": "2022-12-14T21:24:31.856516Z", "shell.execute_reply": "2022-12-14T21:24:31.855746Z" }, "id": "T47C9KS8pbsP" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Parameter shapes: [TensorShape([32, 64]), TensorShape([64]), TensorShape([64, 2])]\n", "Gradient shapes: [TensorShape([32, 64]), TensorShape([64]), TensorShape([64, 2])]\n" ] } ], "source": [ "def create_batch(batch_size=32):\n", " \"\"\"Creates a batch of input and labels.\"\"\"\n", " return (tnp.random.randn(batch_size, 32).astype(tnp.float32),\n", " tnp.random.randn(batch_size, 2).astype(tnp.float32))\n", "\n", "def compute_gradients(model, inputs, labels):\n", " \"\"\"Computes gradients of squared loss between model prediction and labels.\"\"\"\n", " with tf.GradientTape() as tape:\n", " assert model.weights is not None\n", " # Note that `model.weights` need to be explicitly watched since they\n", " # are not tf.Variables.\n", " tape.watch(model.weights)\n", " # Compute prediction and loss\n", " prediction = model.predict(inputs)\n", " loss = tnp.sum(tnp.square(prediction - labels))\n", " # This call computes the gradient through the computation above.\n", " return tape.gradient(loss, model.weights)\n", "\n", "inputs, labels = create_batch()\n", "gradients = compute_gradients(model, inputs, labels)\n", "\n", "# Inspect the shapes of returned gradients to verify they match the\n", "# parameter shapes.\n", "print(\"Parameter shapes:\", [w.shape for w in model.weights])\n", "print(\"Gradient shapes:\", [g.shape for g in gradients])\n", "# Verify that gradients are of type ND array.\n", "assert isinstance(gradients[0], tnp.ndarray)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:31.859941Z", "iopub.status.busy": "2022-12-14T21:24:31.859345Z", "iopub.status.idle": "2022-12-14T21:24:32.027748Z", "shell.execute_reply": "2022-12-14T21:24:32.026654Z" }, "id": "TujVPDFwrdqp" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Output shape: (16, 2), input shape: (16, 32)\n", "Batch jacobian shape: (16, 2, 32)\n" ] } ], "source": [ "# Computes a batch of jacobians. Each row is the jacobian of an element in the\n", "# batch of outputs w.r.t. the corresponding input batch element.\n", "def prediction_batch_jacobian(inputs):\n", " with tf.GradientTape() as tape:\n", " tape.watch(inputs)\n", " prediction = model.predict(inputs)\n", " return prediction, tape.batch_jacobian(prediction, inputs)\n", "\n", "inp_batch = tnp.ones([16, 32], tnp.float32)\n", "output, batch_jacobian = prediction_batch_jacobian(inp_batch)\n", "# Note how the batch jacobian shape relates to the input and output shapes.\n", "print(\"Output shape: %s, input shape: %s\" % (output.shape, inp_batch.shape))\n", "print(\"Batch jacobian shape:\", batch_jacobian.shape)" ] }, { "cell_type": "markdown", "metadata": { "id": "MYq9wxfc1Dv_" }, "source": [ "### 추적 컴파일: tf.function\n", "\n", "Tensorflow의 `tf.function`은 코드를 \"추적 컴파일\"한 다음 해당 추적을 최적화하여 훨씬 빠른 성능을 제공합니다. [그래프 및 함수 소개](./intro_to_graphs.ipynb)를 참조하세요.\n", "\n", "`tf.function`은 TensorFlow NumPy 코드를 최적화하는 데에도 사용할 수 있습니다. 다음은 속도 향상을 보여주는 간단한 예입니다. `tf.function` 코드의 본문에는 TensorFlow NumPy API에 대한 호출이 포함됩니다.\n" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:32.031314Z", "iopub.status.busy": "2022-12-14T21:24:32.030836Z", "iopub.status.idle": "2022-12-14T21:24:32.221576Z", "shell.execute_reply": "2022-12-14T21:24:32.220744Z" }, "id": "05SrUulm1OlL" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Eager performance\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "2.3757479999403586 ms\n", "\n", "tf.function compiled performance\n", "0.5533262999961153 ms\n" ] } ], "source": [ "inputs, labels = create_batch(512)\n", "print(\"Eager performance\")\n", "compute_gradients(model, inputs, labels)\n", "print(timeit.timeit(lambda: compute_gradients(model, inputs, labels),\n", " number=10) * 100, \"ms\")\n", "\n", "print(\"\\ntf.function compiled performance\")\n", "compiled_compute_gradients = tf.function(compute_gradients)\n", "compiled_compute_gradients(model, inputs, labels) # warmup\n", "print(timeit.timeit(lambda: compiled_compute_gradients(model, inputs, labels),\n", " number=10) * 100, \"ms\")" ] }, { "cell_type": "markdown", "metadata": { "id": "5w8YxR6ELmo1" }, "source": [ "### 벡터화: tf.vectorized_map\n", "\n", "TensorFlow는 병렬 루프를 벡터화하는 기능을 내장하여 속도를 1~2배 높일 수 있습니다. 이러한 속도 향상은 `tf.vectorized_map` API를 통해 액세스할 수 있으며 TensorFlow NumPy 코드에도 적용됩니다.\n", "\n", "해당 입력 배치 요소에 대해 배치에서 각 출력의 그래디언트를 계산하는 것이 때때로 유용합니다. 이러한 계산은 아래와 같이 `tf.vectorized_map` 을 사용하여 효율적으로 수행할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:32.225314Z", "iopub.status.busy": "2022-12-14T21:24:32.224692Z", "iopub.status.idle": "2022-12-14T21:24:32.449416Z", "shell.execute_reply": "2022-12-14T21:24:32.448366Z" }, "id": "PemSIrs5L-VJ" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/python/autograph/pyct/static_analysis/liveness.py:83: Analyzer.lamba_check (from tensorflow.python.autograph.pyct.static_analysis.liveness) is deprecated and will be removed after 2023-09-23.\n", "Instructions for updating:\n", "Lambda fuctions will be no more assumed to be used in the statement where they are used, or at least in the same block. https://github.com/tensorflow/tensorflow/issues/56089\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Weight shape: (32, 64), batch size: 128, per example gradient shape: (128, 32, 64) \n", "Weight shape: (64,), batch size: 128, per example gradient shape: (128, 64) \n", "Weight shape: (64, 2), batch size: 128, per example gradient shape: (128, 64, 2) \n" ] } ], "source": [ "@tf.function\n", "def vectorized_per_example_gradients(inputs, labels):\n", " def single_example_gradient(arg):\n", " inp, label = arg\n", " return compute_gradients(model,\n", " tnp.expand_dims(inp, 0),\n", " tnp.expand_dims(label, 0))\n", " # Note that a call to `tf.vectorized_map` semantically maps\n", " # `single_example_gradient` over each row of `inputs` and `labels`.\n", " # The interface is similar to `tf.map_fn`.\n", " # The underlying machinery vectorizes away this map loop which gives\n", " # nice speedups.\n", " return tf.vectorized_map(single_example_gradient, (inputs, labels))\n", "\n", "batch_size = 128\n", "inputs, labels = create_batch(batch_size)\n", "\n", "per_example_gradients = vectorized_per_example_gradients(inputs, labels)\n", "for w, p in zip(model.weights, per_example_gradients):\n", " print(\"Weight shape: %s, batch size: %s, per example gradient shape: %s \" % (\n", " w.shape, batch_size, p.shape))" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:32.453109Z", "iopub.status.busy": "2022-12-14T21:24:32.452463Z", "iopub.status.idle": "2022-12-14T21:24:33.029812Z", "shell.execute_reply": "2022-12-14T21:24:33.028895Z" }, "id": "_QZ5BjJmRAlG" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Running vectorized computation\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "0.5938215000242053 ms\n", "\n", "Running unvectorized computation\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "37.777394899967476 ms\n" ] } ], "source": [ "# Benchmark the vectorized computation above and compare with\n", "# unvectorized sequential computation using `tf.map_fn`.\n", "@tf.function\n", "def unvectorized_per_example_gradients(inputs, labels):\n", " def single_example_gradient(arg):\n", " inp, label = arg\n", " return compute_gradients(model,\n", " tnp.expand_dims(inp, 0),\n", " tnp.expand_dims(label, 0))\n", "\n", " return tf.map_fn(single_example_gradient, (inputs, labels),\n", " fn_output_signature=(tf.float32, tf.float32, tf.float32))\n", "\n", "print(\"Running vectorized computation\")\n", "print(timeit.timeit(lambda: vectorized_per_example_gradients(inputs, labels),\n", " number=10) * 100, \"ms\")\n", "\n", "print(\"\\nRunning unvectorized computation\")\n", "per_example_gradients = unvectorized_per_example_gradients(inputs, labels)\n", "print(timeit.timeit(lambda: unvectorized_per_example_gradients(inputs, labels),\n", " number=10) * 100, \"ms\")" ] }, { "cell_type": "markdown", "metadata": { "id": "UOTh-nkzaJd9" }, "source": [ "### 기기 배치\n", "\n", "TensorFlow NumPy는 CPU, GPU, TPU 및 원격 기기에 연산을 배치할 수 있습니다. 기기 배치를 위한 표준 TensorFlow 메커니즘을 사용합니다. 아래의 간단한 예는 모든 기기를 나열한 다음 특정 기기에 계산을 배치하는 방법을 보여줍니다.\n", "\n", "TensorFlow에는 또한 기기 간에 계산을 복제하고 여기에서 다루지 않을 집단 감소(collective reduction)를 수행하기 위한 API가 있습니다." ] }, { "cell_type": "markdown", "metadata": { "id": "-0gHrwYYaTCE" }, "source": [ "#### 기기 나열하기\n", "\n", "`tf.config.list_logical_devices` 및 `tf.config.list_physical_devices`를 사용하여 사용할 기기를 찾을 수 있습니다." ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:33.033640Z", "iopub.status.busy": "2022-12-14T21:24:33.032945Z", "iopub.status.idle": "2022-12-14T21:24:33.038080Z", "shell.execute_reply": "2022-12-14T21:24:33.037335Z" }, "id": "NDEAd9m9aemS" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "All logical devices: [LogicalDevice(name='/device:CPU:0', device_type='CPU'), LogicalDevice(name='/device:GPU:0', device_type='GPU'), LogicalDevice(name='/device:GPU:1', device_type='GPU'), LogicalDevice(name='/device:GPU:2', device_type='GPU'), LogicalDevice(name='/device:GPU:3', device_type='GPU')]\n", "All physical devices: [PhysicalDevice(name='/physical_device:CPU:0', device_type='CPU'), PhysicalDevice(name='/physical_device:GPU:0', device_type='GPU'), PhysicalDevice(name='/physical_device:GPU:1', device_type='GPU'), PhysicalDevice(name='/physical_device:GPU:2', device_type='GPU'), PhysicalDevice(name='/physical_device:GPU:3', device_type='GPU')]\n" ] } ], "source": [ "print(\"All logical devices:\", tf.config.list_logical_devices())\n", "print(\"All physical devices:\", tf.config.list_physical_devices())\n", "\n", "# Try to get the GPU device. If unavailable, fallback to CPU.\n", "try:\n", " device = tf.config.list_logical_devices(device_type=\"GPU\")[0]\n", "except IndexError:\n", " device = \"/device:CPU:0\"" ] }, { "cell_type": "markdown", "metadata": { "id": "fihgfF_tahVx" }, "source": [ "#### 연산 배치하기: **`tf.device`**\n", "\n", "`tf.device` 범위에서 호출하여 기기에 연산을 배치할 수 있습니다.\n" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:33.041000Z", "iopub.status.busy": "2022-12-14T21:24:33.040518Z", "iopub.status.idle": "2022-12-14T21:24:33.047433Z", "shell.execute_reply": "2022-12-14T21:24:33.046849Z" }, "id": "c7ELvLmnazfV" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Using device: LogicalDevice(name='/device:GPU:0', device_type='GPU')\n", "prediction is placed on /job:localhost/replica:0/task:0/device:GPU:0\n" ] } ], "source": [ "print(\"Using device: %s\" % str(device))\n", "# Run operations in the `tf.device` scope.\n", "# If a GPU is available, these operations execute on the GPU and outputs are\n", "# placed on the GPU memory.\n", "with tf.device(device):\n", " prediction = model.predict(create_batch(5)[0])\n", "\n", "print(\"prediction is placed on %s\" % prediction.device)" ] }, { "cell_type": "markdown", "metadata": { "id": "e-LK6wsHbBiM" }, "source": [ "#### 기기 간에 ND 배열 복사하기: **`tnp.copy`**\n", "\n", "특정 기기 범위에 배치된 `tnp.copy`를 호출하면 데이터가 해당 기기에 이미 있는 경우를 제외하고 해당 기기에 데이터를 복사합니다." ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:33.050310Z", "iopub.status.busy": "2022-12-14T21:24:33.049787Z", "iopub.status.idle": "2022-12-14T21:24:33.054858Z", "shell.execute_reply": "2022-12-14T21:24:33.054205Z" }, "id": "CCesyidaa-UT" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/job:localhost/replica:0/task:0/device:GPU:0\n", "/job:localhost/replica:0/task:0/device:CPU:0\n" ] } ], "source": [ "with tf.device(\"/device:CPU:0\"):\n", " prediction_cpu = tnp.copy(prediction)\n", "print(prediction.device)\n", "print(prediction_cpu.device)" ] }, { "cell_type": "markdown", "metadata": { "id": "AiYzRDOtKzAH" }, "source": [ "## 성능 비교\n", "\n", "TensorFlow NumPy는 CPU, GPU, TPU에서 디스패치될 수 있는 고도로 최적화된 TensorFlow 커널을 사용합니다. TensorFlow는 또한 연산 융합과 같은 많은 컴파일러 최적화를 수행하며, 이는 성능 및 메모리 개선으로 이어집니다. 자세한 내용은 [Gradler를 사용한 TensorFlow 그래프 최적화](./graph_optimization.ipynb)를 참조하세요.\n", "\n", "그러나 TensorFlow는 NumPy와 비교하여 디스패치 연산에 대한 오버헤드가 더 높습니다. 소규모 연산(약 10마이크로초 미만)으로 구성된 워크로드의 경우 이러한 오버헤드가 런타임에서 우세할 수 있으며 NumPy가 더 나은 성능을 제공할 수 있습니다. 다른 경우에는 일반적으로 TensorFlow가 더 나은 성능을 제공합니다.\n", "\n", "아래 벤치마크를 실행하여 다양한 입력 크기에서 NumPy와 TensorFlow Numpy의 성능을 비교해 보세요." ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "cellView": "code", "execution": { "iopub.execute_input": "2022-12-14T21:24:33.057785Z", "iopub.status.busy": "2022-12-14T21:24:33.057349Z", "iopub.status.idle": "2022-12-14T21:24:33.062738Z", "shell.execute_reply": "2022-12-14T21:24:33.062204Z" }, "id": "RExwjI9_pJG0" }, "outputs": [], "source": [ "def benchmark(f, inputs, number=30, force_gpu_sync=False):\n", " \"\"\"Utility to benchmark `f` on each value in `inputs`.\"\"\"\n", " times = []\n", " for inp in inputs:\n", " def _g():\n", " if force_gpu_sync:\n", " one = tnp.asarray(1)\n", " f(inp)\n", " if force_gpu_sync:\n", " with tf.device(\"CPU:0\"):\n", " tnp.copy(one) # Force a sync for GPU case\n", "\n", " _g() # warmup\n", " t = timeit.timeit(_g, number=number)\n", " times.append(t * 1000. / number)\n", " return times\n", "\n", "\n", "def plot(np_times, tnp_times, compiled_tnp_times, has_gpu, tnp_times_gpu):\n", " \"\"\"Plot the different runtimes.\"\"\"\n", " plt.xlabel(\"size\")\n", " plt.ylabel(\"time (ms)\")\n", " plt.title(\"Sigmoid benchmark: TF NumPy vs NumPy\")\n", " plt.plot(sizes, np_times, label=\"NumPy\")\n", " plt.plot(sizes, tnp_times, label=\"TF NumPy (CPU)\")\n", " plt.plot(sizes, compiled_tnp_times, label=\"Compiled TF NumPy (CPU)\")\n", " if has_gpu:\n", " plt.plot(sizes, tnp_times_gpu, label=\"TF NumPy (GPU)\")\n", " plt.legend()" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T21:24:33.065802Z", "iopub.status.busy": "2022-12-14T21:24:33.065283Z", "iopub.status.idle": "2022-12-14T21:24:34.022920Z", "shell.execute_reply": "2022-12-14T21:24:34.022195Z" }, "id": "p-fs_H1lkLfV" }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Define a simple implementation of `sigmoid`, and benchmark it using\n", "# NumPy and TensorFlow NumPy for different input sizes.\n", "\n", "def np_sigmoid(y):\n", " return 1. / (1. + np.exp(-y))\n", "\n", "def tnp_sigmoid(y):\n", " return 1. / (1. + tnp.exp(-y))\n", "\n", "@tf.function\n", "def compiled_tnp_sigmoid(y):\n", " return tnp_sigmoid(y)\n", "\n", "sizes = (2 ** 0, 2 ** 5, 2 ** 10, 2 ** 15, 2 ** 20)\n", "np_inputs = [np.random.randn(size).astype(np.float32) for size in sizes]\n", "np_times = benchmark(np_sigmoid, np_inputs)\n", "\n", "with tf.device(\"/device:CPU:0\"):\n", " tnp_inputs = [tnp.random.randn(size).astype(np.float32) for size in sizes]\n", " tnp_times = benchmark(tnp_sigmoid, tnp_inputs)\n", " compiled_tnp_times = benchmark(compiled_tnp_sigmoid, tnp_inputs)\n", "\n", "has_gpu = len(tf.config.list_logical_devices(\"GPU\"))\n", "if has_gpu:\n", " with tf.device(\"/device:GPU:0\"):\n", " tnp_inputs = [tnp.random.randn(size).astype(np.float32) for size in sizes]\n", " tnp_times_gpu = benchmark(compiled_tnp_sigmoid, tnp_inputs, 100, True)\n", "else:\n", " tnp_times_gpu = None\n", "plot(np_times, tnp_times, compiled_tnp_times, has_gpu, tnp_times_gpu)" ] }, { "cell_type": "markdown", "metadata": { "id": "ReK_9k5D8BZQ" }, "source": [ "## 추가 자료\n", "\n", "- [TensorFlow NumPy: Distributed Image Classification Tutorial](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/ops/numpy_ops/g3doc/TensorFlow_Numpy_Distributed_Image_Classification.ipynb)\n", "- [TensorFlow NumPy: Keras and Distribution Strategy](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/ops/numpy_ops/g3doc/TensorFlow_NumPy_Keras_and_Distribution_Strategy.ipynb)\n", "- [Sentiment Analysis with Trax and TensorFlow NumPy](https://github.com/google/trax/blob/master/trax/tf_numpy_and_keras.ipynb)" ] } ], "metadata": { "accelerator": "GPU", "colab": { "collapsed_sections": [], "name": "tf_numpy.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 }