{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "b518b04cbfe0" }, "source": [ "##### Copyright 2020 The TensorFlow Authors." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "cellView": "form", "execution": { "iopub.execute_input": "2022-12-14T22:27:50.704413Z", "iopub.status.busy": "2022-12-14T22:27:50.703844Z", "iopub.status.idle": "2022-12-14T22:27:50.707584Z", "shell.execute_reply": "2022-12-14T22:27:50.707044Z" }, "id": "906e07f6e562" }, "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": "daf323e33b84" }, "source": [ "# 훈련 루프 처음부터 작성하기" ] }, { "cell_type": "markdown", "metadata": { "id": "2440f6e0c5ef" }, "source": [ "\n", " \n", " \n", " \n", " \n", "
TensorFlow.org에서 보기 Google Colab에서 실행GitHub에서 소스 보기노트북 다운로드
" ] }, { "cell_type": "markdown", "metadata": { "id": "8d4ac441b1fc" }, "source": [ "## !pip install -U tf-hub-nightly
import tensorflow_hub as hub

from tensorflow.keras import layers" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:27:50.710752Z", "iopub.status.busy": "2022-12-14T22:27:50.710541Z", "iopub.status.idle": "2022-12-14T22:27:52.618816Z", "shell.execute_reply": "2022-12-14T22:27:52.618056Z" }, "id": "ae2407ad926f" }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2022-12-14 22:27:51.649540: 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 22:27:51.649637: 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 22:27:51.649646: 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" ] } ], "source": [ "import tensorflow as tf\n", "from tensorflow import keras\n", "from tensorflow.keras import layers\n", "import numpy as np" ] }, { "cell_type": "markdown", "metadata": { "id": "0f5a253901f8" }, "source": [ "## 시작하기\n", "\n", "Keras는 기본 학습 및 평가 루프인 `fit()` 및 `evaluate()`를 제공합니다. 이들의 사용법은 [내장 메서드를 사용한 학습 및 평가](https://www.tensorflow.org/guide/keras/train_and_evaluate/) 가이드에서 다룹니다.\n", "\n", "`fit()`의 편리함을 그대로 활용하면서 모델의 학습 알고리즘을 사용자 정의하려면(예: `fit()`을 사용하여 GAN 학습 진행) `Model` 클래스를 하위 클래스로 만들고 `fit()` 중에 반복적으로 호출되는 고유한 `train_step()` 메서드를 구현합니다. 이 내용은 `fit()`의 동작 사용자 정의하기 가이드에서 다룹니다.\n", "\n", "훈련 및 평가에 대한 매우 낮은 수준의 제어를 원하면 자체 훈련 및 평가 루프를 처음부터 작성해야 합니다. 이것이 이 가이드의 내용입니다." ] }, { "cell_type": "markdown", "metadata": { "id": "f4f47351a3ec" }, "source": [ "## `GradientTape` 사용하기: 첫 번째 엔드 투 엔드 예시\n", "\n", "`GradientTape` 범위 내에서 모델을 호출하면 손실 값과 관련하여 레이어의 학습 가능한 가중치의 그래디언트를 가져올 수 있습니다. 옵티마이저 인스턴스를 사용하면 이러한 그래디언트를 사용하여 이러한 변수를 업데이트할 수 있습니다(이러한 변수는 `model.trainable_weights`를 사용하여 가져올 수 있음).\n", "\n", "간단한 MNIST 모델을 살펴보겠습니다." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:27:52.623408Z", "iopub.status.busy": "2022-12-14T22:27:52.622634Z", "iopub.status.idle": "2022-12-14T22:27:56.038256Z", "shell.execute_reply": "2022-12-14T22:27:56.037516Z" }, "id": "aaa775ce7dab" }, "outputs": [], "source": [ "inputs = keras.Input(shape=(784,), name=\"digits\")\n", "x1 = layers.Dense(64, activation=\"relu\")(inputs)\n", "x2 = layers.Dense(64, activation=\"relu\")(x1)\n", "outputs = layers.Dense(10, name=\"predictions\")(x2)\n", "model = keras.Model(inputs=inputs, outputs=outputs)" ] }, { "cell_type": "markdown", "metadata": { "id": "d8b02a5759cf" }, "source": [ "사용자 정의 학습 루프가 있는 미니 배치 그래디언트를 사용하여 모델을 훈련해보겠습니다.\n", "\n", "먼저 옵티마이저, 손실 함수, 데이터세트가 필요합니다." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:27:56.042310Z", "iopub.status.busy": "2022-12-14T22:27:56.042058Z", "iopub.status.idle": "2022-12-14T22:27:56.402583Z", "shell.execute_reply": "2022-12-14T22:27:56.401862Z" }, "id": "f2c6257b8d02" }, "outputs": [], "source": [ "# Instantiate an optimizer.\n", "optimizer = keras.optimizers.SGD(learning_rate=1e-3)\n", "# Instantiate a loss function.\n", "loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)\n", "\n", "# Prepare the training dataset.\n", "batch_size = 64\n", "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n", "x_train = np.reshape(x_train, (-1, 784))\n", "x_test = np.reshape(x_test, (-1, 784))\n", "\n", "# Reserve 10,000 samples for validation.\n", "x_val = x_train[-10000:]\n", "y_val = y_train[-10000:]\n", "x_train = x_train[:-10000]\n", "y_train = y_train[:-10000]\n", "\n", "# Prepare the training dataset.\n", "train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n", "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)\n", "\n", "# Prepare the validation dataset.\n", "val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))\n", "val_dataset = val_dataset.batch(batch_size)" ] }, { "cell_type": "markdown", "metadata": { "id": "5c30285b1a2e" }, "source": [ "우리의 학습 루프는 다음과 같습니다.\n", "\n", "- Epoch를 반복하는 `for` 루프를 엽니다.\n", "- 각 epoch에 대해 데이터세트를 배치 단위로 반복하는 `for` 루프를 엽니다.\n", "- 각 배치에 대해 `GradientTape()` 범위를 엽니다.\n", "- 이 범위 내에서 모델(순방향 전달)을 호출하고 손실을 계산합니다.\n", "- 범위 외부에서 손실에 대한 모델 가중치의 그래디언트를 검색합니다.\n", "- 마지막으로 옵티마이저를 사용하여 그래디언트를 기반으로 모델의 가중치를 업데이트합니다." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:27:56.406826Z", "iopub.status.busy": "2022-12-14T22:27:56.406247Z", "iopub.status.idle": "2022-12-14T22:28:13.579691Z", "shell.execute_reply": "2022-12-14T22:28:13.578905Z" }, "id": "5bf4c10ceb50" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Start of epoch 0\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "WARNING:tensorflow:5 out of the last 5 calls to triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for more details.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "WARNING:tensorflow:6 out of the last 6 calls to triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for more details.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 0: 114.2052\n", "Seen so far: 64 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 200: 1.4316\n", "Seen so far: 12864 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 400: 1.0915\n", "Seen so far: 25664 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 600: 0.7470\n", "Seen so far: 38464 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "Start of epoch 1\n", "Training loss (for one batch) at step 0: 0.5563\n", "Seen so far: 64 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 200: 1.1355\n", "Seen so far: 12864 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 400: 0.8665\n", "Seen so far: 25664 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 600: 0.7169\n", "Seen so far: 38464 samples\n" ] } ], "source": [ "epochs = 2\n", "for epoch in range(epochs):\n", " print(\"\\nStart of epoch %d\" % (epoch,))\n", "\n", " # Iterate over the batches of the dataset.\n", " for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):\n", "\n", " # Open a GradientTape to record the operations run\n", " # during the forward pass, which enables auto-differentiation.\n", " with tf.GradientTape() as tape:\n", "\n", " # Run the forward pass of the layer.\n", " # The operations that the layer applies\n", " # to its inputs are going to be recorded\n", " # on the GradientTape.\n", " logits = model(x_batch_train, training=True) # Logits for this minibatch\n", "\n", " # Compute the loss value for this minibatch.\n", " loss_value = loss_fn(y_batch_train, logits)\n", "\n", " # Use the gradient tape to automatically retrieve\n", " # the gradients of the trainable variables with respect to the loss.\n", " grads = tape.gradient(loss_value, model.trainable_weights)\n", "\n", " # Run one step of gradient descent by updating\n", " # the value of the variables to minimize the loss.\n", " optimizer.apply_gradients(zip(grads, model.trainable_weights))\n", "\n", " # Log every 200 batches.\n", " if step % 200 == 0:\n", " print(\n", " \"Training loss (for one batch) at step %d: %.4f\"\n", " % (step, float(loss_value))\n", " )\n", " print(\"Seen so far: %s samples\" % ((step + 1) * batch_size))" ] }, { "cell_type": "markdown", "metadata": { "id": "d600076b7be0" }, "source": [ "## 메트릭 로우 레벨(low-level) 처리\n", "\n", "이 기본 루프에 메트릭 모니터링을 추가해 보겠습니다.\n", "\n", "처음부터 작성한 이러한 학습 루프에서 내장 메트릭(또는 사용자가 작성한 메트릭)을 쉽게 재사용할 수 있습니다. 흐름은 다음과 같습니다.\n", "\n", "- 루프 시작 시 메트릭 인스턴스화\n", "- 각 배치 후에 `metric.update_state()`를 호출\n", "- 메트릭의 현재 값을 표시해야 하는 경우 {code 0}matric.result(){/code 0}를 호출\n", "- 메트릭의 상태를 삭제해야 할 경우(일반적으로 Epoch 종료 시) `metric.reset_states()`를 호출\n", "\n", "이 지식을 사용하여 각 Epoch가 끝날 때 검증 데이터의 `SparseCategoricalAccuracy`를 계산해 보겠습니다." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:28:13.584233Z", "iopub.status.busy": "2022-12-14T22:28:13.583667Z", "iopub.status.idle": "2022-12-14T22:28:13.726375Z", "shell.execute_reply": "2022-12-14T22:28:13.725624Z" }, "id": "2602509b16c7" }, "outputs": [], "source": [ "# Get model\n", "inputs = keras.Input(shape=(784,), name=\"digits\")\n", "x = layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n", "x = layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n", "outputs = layers.Dense(10, name=\"predictions\")(x)\n", "model = keras.Model(inputs=inputs, outputs=outputs)\n", "\n", "# Instantiate an optimizer to train the model.\n", "optimizer = keras.optimizers.SGD(learning_rate=1e-3)\n", "# Instantiate a loss function.\n", "loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)\n", "\n", "# Prepare the metrics.\n", "train_acc_metric = keras.metrics.SparseCategoricalAccuracy()\n", "val_acc_metric = keras.metrics.SparseCategoricalAccuracy()" ] }, { "cell_type": "markdown", "metadata": { "id": "9111a5cc87dc" }, "source": [ "학습 및 평가 루프는 다음과 같습니다." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:28:13.731051Z", "iopub.status.busy": "2022-12-14T22:28:13.730391Z", "iopub.status.idle": "2022-12-14T22:28:34.566192Z", "shell.execute_reply": "2022-12-14T22:28:34.565461Z" }, "id": "654e2311dbff" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Start of epoch 0\n", "Training loss (for one batch) at step 0: 106.2340\n", "Seen so far: 64 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 200: 1.4773\n", "Seen so far: 12864 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 400: 0.9677\n", "Seen so far: 25664 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 600: 0.4914\n", "Seen so far: 38464 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training acc over epoch: 0.7020\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Validation acc: 0.7567\n", "Time taken: 10.50s\n", "\n", "Start of epoch 1\n", "Training loss (for one batch) at step 0: 0.7326\n", "Seen so far: 64 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 200: 0.9143\n", "Seen so far: 12864 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 400: 0.4277\n", "Seen so far: 25664 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 600: 0.7565\n", "Seen so far: 38464 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training acc over epoch: 0.8232\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Validation acc: 0.8578\n", "Time taken: 10.33s\n" ] } ], "source": [ "import time\n", "\n", "epochs = 2\n", "for epoch in range(epochs):\n", " print(\"\\nStart of epoch %d\" % (epoch,))\n", " start_time = time.time()\n", "\n", " # Iterate over the batches of the dataset.\n", " for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):\n", " with tf.GradientTape() as tape:\n", " logits = model(x_batch_train, training=True)\n", " loss_value = loss_fn(y_batch_train, logits)\n", " grads = tape.gradient(loss_value, model.trainable_weights)\n", " optimizer.apply_gradients(zip(grads, model.trainable_weights))\n", "\n", " # Update training metric.\n", " train_acc_metric.update_state(y_batch_train, logits)\n", "\n", " # Log every 200 batches.\n", " if step % 200 == 0:\n", " print(\n", " \"Training loss (for one batch) at step %d: %.4f\"\n", " % (step, float(loss_value))\n", " )\n", " print(\"Seen so far: %d samples\" % ((step + 1) * batch_size))\n", "\n", " # Display metrics at the end of each epoch.\n", " train_acc = train_acc_metric.result()\n", " print(\"Training acc over epoch: %.4f\" % (float(train_acc),))\n", "\n", " # Reset training metrics at the end of each epoch\n", " train_acc_metric.reset_states()\n", "\n", " # Run a validation loop at the end of each epoch.\n", " for x_batch_val, y_batch_val in val_dataset:\n", " val_logits = model(x_batch_val, training=False)\n", " # Update val metrics\n", " val_acc_metric.update_state(y_batch_val, val_logits)\n", " val_acc = val_acc_metric.result()\n", " val_acc_metric.reset_states()\n", " print(\"Validation acc: %.4f\" % (float(val_acc),))\n", " print(\"Time taken: %.2fs\" % (time.time() - start_time))" ] }, { "cell_type": "markdown", "metadata": { "id": "1c9a16c21790" }, "source": [ "## `tf.function`으로 학습 단계 가속화하기\n", "\n", "TensorFlow 2의 기본 런타임은 [즉시 실행](https://www.tensorflow.org/guide/eager)입니다. 따라서 위의 훈련 루프는 즉시 실행됩니다.\n", "\n", "이것은 디버깅에 매우 유용하지만 그래프 컴파일이 확실한 성능에 이점이 있습니다. 계산을 정적 그래프로 설명하면 프레임워크로 전역 성능 최적화를 적용할 수 있습니다. 이것은 프레임워크가 다음에 무엇이 올지 알지 못한 상태로 탐욕적으로 하나의 작업을 차례로 실행하도록 제한되어 있을 때에는 불가능합니다.\n", "\n", "텐서를 입력으로 사용하는 모든 함수를 정적 그래프로 컴파일할 수 있습니다. 다음과 같이 `@tf.function` 데코레이터를 추가하기만 하면 됩니다." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:28:34.569951Z", "iopub.status.busy": "2022-12-14T22:28:34.569290Z", "iopub.status.idle": "2022-12-14T22:28:34.574090Z", "shell.execute_reply": "2022-12-14T22:28:34.573440Z" }, "id": "fdacc2d48ade" }, "outputs": [], "source": [ "@tf.function\n", "def train_step(x, y):\n", " with tf.GradientTape() as tape:\n", " logits = model(x, training=True)\n", " loss_value = loss_fn(y, logits)\n", " grads = tape.gradient(loss_value, model.trainable_weights)\n", " optimizer.apply_gradients(zip(grads, model.trainable_weights))\n", " train_acc_metric.update_state(y, logits)\n", " return loss_value\n" ] }, { "cell_type": "markdown", "metadata": { "id": "ab61b0bf3126" }, "source": [ "평가 단계에서도 동일하게 수행해 보겠습니다." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:28:34.577325Z", "iopub.status.busy": "2022-12-14T22:28:34.576813Z", "iopub.status.idle": "2022-12-14T22:28:34.580497Z", "shell.execute_reply": "2022-12-14T22:28:34.579927Z" }, "id": "da4828fd8ef7" }, "outputs": [], "source": [ "@tf.function\n", "def test_step(x, y):\n", " val_logits = model(x, training=False)\n", " val_acc_metric.update_state(y, val_logits)\n" ] }, { "cell_type": "markdown", "metadata": { "id": "d552377968f1" }, "source": [ "이제 이 컴파일된 학습 단계로 학습 루프를 다시 실행해 보겠습니다." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:28:34.583718Z", "iopub.status.busy": "2022-12-14T22:28:34.583483Z", "iopub.status.idle": "2022-12-14T22:28:37.656581Z", "shell.execute_reply": "2022-12-14T22:28:37.655864Z" }, "id": "d69d73c94e44" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Start of epoch 0\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 0: 0.4228\n", "Seen so far: 64 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 200: 0.6054\n", "Seen so far: 12864 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 400: 0.2916\n", "Seen so far: 25664 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 600: 0.5421\n", "Seen so far: 38464 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training acc over epoch: 0.8574\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Validation acc: 0.8583\n", "Time taken: 1.75s\n", "\n", "Start of epoch 1\n", "Training loss (for one batch) at step 0: 0.3737\n", "Seen so far: 64 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 200: 0.5216\n", "Seen so far: 12864 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 400: 0.3324\n", "Seen so far: 25664 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training loss (for one batch) at step 600: 0.2431\n", "Seen so far: 38464 samples\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Training acc over epoch: 0.8770\n", "Validation acc: 0.8565\n", "Time taken: 1.31s\n" ] } ], "source": [ "import time\n", "\n", "epochs = 2\n", "for epoch in range(epochs):\n", " print(\"\\nStart of epoch %d\" % (epoch,))\n", " start_time = time.time()\n", "\n", " # Iterate over the batches of the dataset.\n", " for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):\n", " loss_value = train_step(x_batch_train, y_batch_train)\n", "\n", " # Log every 200 batches.\n", " if step % 200 == 0:\n", " print(\n", " \"Training loss (for one batch) at step %d: %.4f\"\n", " % (step, float(loss_value))\n", " )\n", " print(\"Seen so far: %d samples\" % ((step + 1) * batch_size))\n", "\n", " # Display metrics at the end of each epoch.\n", " train_acc = train_acc_metric.result()\n", " print(\"Training acc over epoch: %.4f\" % (float(train_acc),))\n", "\n", " # Reset training metrics at the end of each epoch\n", " train_acc_metric.reset_states()\n", "\n", " # Run a validation loop at the end of each epoch.\n", " for x_batch_val, y_batch_val in val_dataset:\n", " test_step(x_batch_val, y_batch_val)\n", "\n", " val_acc = val_acc_metric.result()\n", " val_acc_metric.reset_states()\n", " print(\"Validation acc: %.4f\" % (float(val_acc),))\n", " print(\"Time taken: %.2fs\" % (time.time() - start_time))" ] }, { "cell_type": "markdown", "metadata": { "id": "8977d77a8095" }, "source": [ "훨씬 빨라지지 않았나요?" ] }, { "cell_type": "markdown", "metadata": { "id": "b5b5a54d339a" }, "source": [ "## 모델에서 추적한 손실의 로우 레벨 처리\n", "\n", "레이어 및 모델은 `self.add_loss(value)`를 호출하는 레이어로 순방향 전달을 수행하는 동안 생성된 손실을 재귀적으로 추적합니다. Scalar 손실 값의 결과 목록은 순방향 전달 종료 시 속성 `model.losses`를 통해 사용할 수 있습니다.\n", "\n", "이러한 손실 구성 요소를 사용하려면 이들을 종합한 후 학습 단계의 기본 손실에 추가해야 합니다.\n", "\n", "활동 정규화 손실을 생성하는 이 레이어를 살펴보겠습니다." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:28:37.661036Z", "iopub.status.busy": "2022-12-14T22:28:37.660303Z", "iopub.status.idle": "2022-12-14T22:28:37.664347Z", "shell.execute_reply": "2022-12-14T22:28:37.663763Z" }, "id": "4ec7c4b16596" }, "outputs": [], "source": [ "class ActivityRegularizationLayer(layers.Layer):\n", " def call(self, inputs):\n", " self.add_loss(1e-2 * tf.reduce_sum(inputs))\n", " return inputs\n" ] }, { "cell_type": "markdown", "metadata": { "id": "6b12260b8bf2" }, "source": [ "이를 사용하는 정말 간단한 모델을 만들어 보겠습니다." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:28:37.667806Z", "iopub.status.busy": "2022-12-14T22:28:37.667214Z", "iopub.status.idle": "2022-12-14T22:28:37.724347Z", "shell.execute_reply": "2022-12-14T22:28:37.723771Z" }, "id": "57afe49e6b93" }, "outputs": [], "source": [ "inputs = keras.Input(shape=(784,), name=\"digits\")\n", "x = layers.Dense(64, activation=\"relu\")(inputs)\n", "# Insert activity regularization as a layer\n", "x = ActivityRegularizationLayer()(x)\n", "x = layers.Dense(64, activation=\"relu\")(x)\n", "outputs = layers.Dense(10, name=\"predictions\")(x)\n", "\n", "model = keras.Model(inputs=inputs, outputs=outputs)" ] }, { "cell_type": "markdown", "metadata": { "id": "aadb58115c13" }, "source": [ "이제 학습 단계는 다음과 같아야 합니다." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:28:37.728116Z", "iopub.status.busy": "2022-12-14T22:28:37.727448Z", "iopub.status.idle": "2022-12-14T22:28:37.735726Z", "shell.execute_reply": "2022-12-14T22:28:37.735150Z" }, "id": "cf674776a0d2" }, "outputs": [], "source": [ "@tf.function\n", "def train_step(x, y):\n", " with tf.GradientTape() as tape:\n", " logits = model(x, training=True)\n", " loss_value = loss_fn(y, logits)\n", " # Add any extra losses created during the forward pass.\n", " loss_value += sum(model.losses)\n", " grads = tape.gradient(loss_value, model.trainable_weights)\n", " optimizer.apply_gradients(zip(grads, model.trainable_weights))\n", " train_acc_metric.update_state(y, logits)\n", " return loss_value\n" ] }, { "cell_type": "markdown", "metadata": { "id": "0af04732fe78" }, "source": [ "## 요약\n", "\n", "이제 내장 학습 루프를 사용하고 처음부터 자체적으로 작성하기 위해 알아야 할 모든 것을 알게 되었습니다.\n", "\n", "결론적으로 다음은 이 가이드에서 배운 모든 것을 하나로 묶는 간단한 엔드 투 엔드 예제인 MNIST 숫자로 학습한 DCGAN 입니다." ] }, { "cell_type": "markdown", "metadata": { "id": "9fb325331a1e" }, "source": [ "## 엔드 투 엔드 예제: GAN 학습 루프 처음부터 수행하기\n", "\n", "GAN(Generative Adversarial Networks)에 대해 잘 알고 있을 것입니다. GAN은 이미지 학습 데이터세트의 잠재 분포(이미지의 \"잠재 공간\")를 훈련하여 거의 실제처럼 보이는 새로운 이미지를 생성할 수 있습니다.\n", "\n", "GAN은 잠재 공간의 지점을 이미지 공간의 지점으로 매핑하는 \"생성기\" 모델과 실제 이미지(학습 데이터 세트)와 가짜 이미지(생성기 네트워크의 출력물)를 구별할 수 있는 분류자인 \"판별기\" 모델의 두 부분으로 구성됩니다.\n", "\n", "GAN 학습 루프는 다음과 같습니다.\n", "\n", "1. 판별기를 훈련합니다.\n", "\n", "- 잠재 공간에 무작위 지점 배치를 샘플링합니다.\n", "- \"생성기\" 모델을 통해 지점을 가짜 이미지로 바꿉니다.\n", "- 실제 이미지 배치를 가져와서 생성된 이미지와 결합합니다.\n", "- 생성된 이미지와 실제 이미지를 분류하기 위해 \"판별기\" 모델을 훈련합니다.\n", "\n", "1. 생성기를 훈련합니다.\n", "\n", "- 잠재 공간에 무작위 지점을 샘플링합니다.\n", "- \"생성기\"를 통해 지점을 가짜 이미지로 바꿉니다.\n", "- 실제 이미지 배치를 가져와서 생성된 이미지와 결합합니다.\n", "- \"생성기\" 모델을 훈련시켜 판별기를 \"속이고\" 가짜 이미지를 진짜로 분류합니다.\n", "\n", "GAN의 작동 방식에 대한 더 자세한 개요는 [Deep Learning with Python(Python으로 딥러닝하기)](https://www.manning.com/books/deep-learning-with-python)을 참조하세요.\n", "\n", "이 학습 루프를 구현해 보겠습니다. 먼저 가짜 숫자와 실제 숫자를 구분하기 위한 판별기를 생성합니다." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:28:37.741276Z", "iopub.status.busy": "2022-12-14T22:28:37.740670Z", "iopub.status.idle": "2022-12-14T22:28:37.801938Z", "shell.execute_reply": "2022-12-14T22:28:37.801341Z" }, "id": "fabf9cef3400" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Model: \"discriminator\"\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "_________________________________________________________________\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " Layer (type) Output Shape Param # \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "=================================================================\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " conv2d (Conv2D) (None, 14, 14, 64) 640 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " leaky_re_lu (LeakyReLU) (None, 14, 14, 64) 0 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " conv2d_1 (Conv2D) (None, 7, 7, 128) 73856 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " leaky_re_lu_1 (LeakyReLU) (None, 7, 7, 128) 0 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " global_max_pooling2d (Globa (None, 128) 0 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " lMaxPooling2D) \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " dense_4 (Dense) (None, 1) 129 \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "=================================================================\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Total params: 74,625\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Trainable params: 74,625\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Non-trainable params: 0\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "_________________________________________________________________\n" ] } ], "source": [ "discriminator = keras.Sequential(\n", " [\n", " keras.Input(shape=(28, 28, 1)),\n", " layers.Conv2D(64, (3, 3), strides=(2, 2), padding=\"same\"),\n", " layers.LeakyReLU(alpha=0.2),\n", " layers.Conv2D(128, (3, 3), strides=(2, 2), padding=\"same\"),\n", " layers.LeakyReLU(alpha=0.2),\n", " layers.GlobalMaxPooling2D(),\n", " layers.Dense(1),\n", " ],\n", " name=\"discriminator\",\n", ")\n", "discriminator.summary()" ] }, { "cell_type": "markdown", "metadata": { "id": "73396eb6daf9" }, "source": [ "그런 다음 잠재 벡터를 형태 `(28, 28, 1)`(MNIST 숫자를 나타냄)의 출력으로 바꾸는 생성기 네트워크를 생성합니다." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:28:37.810810Z", "iopub.status.busy": "2022-12-14T22:28:37.810577Z", "iopub.status.idle": "2022-12-14T22:28:37.883626Z", "shell.execute_reply": "2022-12-14T22:28:37.883052Z" }, "id": "821d203bfb3e" }, "outputs": [], "source": [ "latent_dim = 128\n", "\n", "generator = keras.Sequential(\n", " [\n", " keras.Input(shape=(latent_dim,)),\n", " # We want to generate 128 coefficients to reshape into a 7x7x128 map\n", " layers.Dense(7 * 7 * 128),\n", " layers.LeakyReLU(alpha=0.2),\n", " layers.Reshape((7, 7, 128)),\n", " layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding=\"same\"),\n", " layers.LeakyReLU(alpha=0.2),\n", " layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding=\"same\"),\n", " layers.LeakyReLU(alpha=0.2),\n", " layers.Conv2D(1, (7, 7), padding=\"same\", activation=\"sigmoid\"),\n", " ],\n", " name=\"generator\",\n", ")" ] }, { "cell_type": "markdown", "metadata": { "id": "f0d6d54a78a0" }, "source": [ "여기에 핵심 비트인 훈련 루프가 있습니다. 이는 매우 간단한 것을 확인할 수 있습니다. 훈련 단계 함수는 17개의 라인만 사용합니다." ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:28:37.887528Z", "iopub.status.busy": "2022-12-14T22:28:37.886874Z", "iopub.status.idle": "2022-12-14T22:28:37.896830Z", "shell.execute_reply": "2022-12-14T22:28:37.896206Z" }, "id": "3a11c875142e" }, "outputs": [], "source": [ "# Instantiate one optimizer for the discriminator and another for the generator.\n", "d_optimizer = keras.optimizers.Adam(learning_rate=0.0003)\n", "g_optimizer = keras.optimizers.Adam(learning_rate=0.0004)\n", "\n", "# Instantiate a loss function.\n", "loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)\n", "\n", "\n", "@tf.function\n", "def train_step(real_images):\n", " # Sample random points in the latent space\n", " random_latent_vectors = tf.random.normal(shape=(batch_size, latent_dim))\n", " # Decode them to fake images\n", " generated_images = generator(random_latent_vectors)\n", " # Combine them with real images\n", " combined_images = tf.concat([generated_images, real_images], axis=0)\n", "\n", " # Assemble labels discriminating real from fake images\n", " labels = tf.concat(\n", " [tf.ones((batch_size, 1)), tf.zeros((real_images.shape[0], 1))], axis=0\n", " )\n", " # Add random noise to the labels - important trick!\n", " labels += 0.05 * tf.random.uniform(labels.shape)\n", "\n", " # Train the discriminator\n", " with tf.GradientTape() as tape:\n", " predictions = discriminator(combined_images)\n", " d_loss = loss_fn(labels, predictions)\n", " grads = tape.gradient(d_loss, discriminator.trainable_weights)\n", " d_optimizer.apply_gradients(zip(grads, discriminator.trainable_weights))\n", "\n", " # Sample random points in the latent space\n", " random_latent_vectors = tf.random.normal(shape=(batch_size, latent_dim))\n", " # Assemble labels that say \"all real images\"\n", " misleading_labels = tf.zeros((batch_size, 1))\n", "\n", " # Train the generator (note that we should *not* update the weights\n", " # of the discriminator)!\n", " with tf.GradientTape() as tape:\n", " predictions = discriminator(generator(random_latent_vectors))\n", " g_loss = loss_fn(misleading_labels, predictions)\n", " grads = tape.gradient(g_loss, generator.trainable_weights)\n", " g_optimizer.apply_gradients(zip(grads, generator.trainable_weights))\n", " return d_loss, g_loss, generated_images\n" ] }, { "cell_type": "markdown", "metadata": { "id": "fa6bd6292488" }, "source": [ "이미지 배치에서 `train_step`을 반복적으로 호출하여 GAN을 학습시켜 보겠습니다.\n", "\n", "판별기와 생성기가 ConvNet이므로 GPU에서 이 코드를 실행하고 싶을 것입니다." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "execution": { "iopub.execute_input": "2022-12-14T22:28:37.900468Z", "iopub.status.busy": "2022-12-14T22:28:37.899796Z", "iopub.status.idle": "2022-12-14T22:28:42.781444Z", "shell.execute_reply": "2022-12-14T22:28:42.780644Z" }, "id": "b6a4e3d42262" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Start epoch 0\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "discriminator loss at step 0: 0.68\n", "adversarial loss at step 0: 0.68\n" ] } ], "source": [ "import os\n", "\n", "# Prepare the dataset. We use both the training & test MNIST digits.\n", "batch_size = 64\n", "(x_train, _), (x_test, _) = keras.datasets.mnist.load_data()\n", "all_digits = np.concatenate([x_train, x_test])\n", "all_digits = all_digits.astype(\"float32\") / 255.0\n", "all_digits = np.reshape(all_digits, (-1, 28, 28, 1))\n", "dataset = tf.data.Dataset.from_tensor_slices(all_digits)\n", "dataset = dataset.shuffle(buffer_size=1024).batch(batch_size)\n", "\n", "epochs = 1 # In practice you need at least 20 epochs to generate nice digits.\n", "save_dir = \"./\"\n", "\n", "for epoch in range(epochs):\n", " print(\"\\nStart epoch\", epoch)\n", "\n", " for step, real_images in enumerate(dataset):\n", " # Train the discriminator & generator on one batch of real images.\n", " d_loss, g_loss, generated_images = train_step(real_images)\n", "\n", " # Logging.\n", " if step % 200 == 0:\n", " # Print metrics\n", " print(\"discriminator loss at step %d: %.2f\" % (step, d_loss))\n", " print(\"adversarial loss at step %d: %.2f\" % (step, g_loss))\n", "\n", " # Save one generated image\n", " img = tf.keras.preprocessing.image.array_to_img(\n", " generated_images[0] * 255.0, scale=False\n", " )\n", " img.save(os.path.join(save_dir, \"generated_img\" + str(step) + \".png\"))\n", "\n", " # To limit execution time we stop after 10 steps.\n", " # Remove the lines below to actually train the model!\n", " if step > 10:\n", " break" ] }, { "cell_type": "markdown", "metadata": { "id": "a92959ac630b" }, "source": [ "이게 전부입니다! Colab GPU에서 30초 정도만 훈련하면 멋진 가짜 MNIST 숫자를 얻게 됩니다." ] } ], "metadata": { "colab": { "collapsed_sections": [], "name": "writing_a_training_loop_from_scratch.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 }