{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MhoQ0WE77laV"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "_ckMIh7O7s6D"
      },
      "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": "jYysdyb-CaWM"
      },
      "source": [
        "# 훈련 루프와 함께 tf.distribute.Strategy 사용하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "S5Uhzt6vVIB2"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/tutorials/distribute/custom_training\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />TensorFlow.org에서 보기</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/tutorials/distribute/custom_training.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />구글 코랩(Colab)에서 실행하기</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/tutorials/distribute/custom_training.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />깃허브(GitHub) 소스 보기</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/tutorials/distribute/custom_training.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Download notebook</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tUP8LMdYtWPz"
      },
      "source": [
        "Note: 이 문서는 텐서플로 커뮤니티에서 번역했습니다. 커뮤니티 번역 활동의 특성상 정확한 번역과 최신 내용을 반영하기 위해 노력함에도\n",
        "불구하고 [공식 영문 문서](https://www.tensorflow.org/?hl=en)의 내용과 일치하지 않을 수 있습니다.\n",
        "이 번역에 개선할 부분이 있다면\n",
        "[tensorflow/docs-l10n](https://github.com/tensorflow/docs-l10n/) 깃헙 저장소로 풀 리퀘스트를 보내주시기 바랍니다.\n",
        "문서 번역이나 리뷰에 참여하려면\n",
        "[docs-ko@tensorflow.org](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs-ko)로\n",
        "메일을 보내주시기 바랍니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FbVhjPpzn6BM"
      },
      "source": [
        "이 예제는 사용자 정의 훈련 루프(custom training loops)와 함께 [`tf.distribute.Strategy`](https://www.tensorflow.org/guide/distributed_training)를 사용하는 법을 보여드립니다. 우리는 간단한 CNN 모델을 패션 MNIST 데이터셋에 대해 훈련을 할 것입니다. 패션 MNIST 데이터셋은 60000개의 28 x 28 크기의 훈련 이미지들과 10000개의 28 x 28 크기의 테스트 이미지들을 포함하고 있습니다.\n",
        "\n",
        "이 예제는 유연성을 높이고, 훈련을 더 잘 제어할 수 있도록 사용자 정의 훈련 루프를 사용합니다. 또한, 사용자 훈련 루프를 사용하는 것은 모델과 훈련 루프를 디버깅하기 쉬워집니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dzLKpmZICaWN"
      },
      "outputs": [],
      "source": [
        "# Import TensorFlow\n",
        "import tensorflow as tf\n",
        "\n",
        "# 헬퍼 라이브러리들\n",
        "import numpy as np\n",
        "import os\n",
        "\n",
        "print(tf.__version__)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MM6W__qraV55"
      },
      "source": [
        "## 패션 MNIST 데이터셋 다운로드"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7MqDQO0KCaWS"
      },
      "outputs": [],
      "source": [
        "fashion_mnist = tf.keras.datasets.fashion_mnist\n",
        "\n",
        "(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()\n",
        "\n",
        "\n",
        "# 하나의 차원을 배열에 추가 -> 새로운 shape == (28, 28, 1)\n",
        "# 이렇게 하는 이유는 우리의 모델에서 첫 번째 층이 합성곱 층이고\n",
        "# 합성곱 층은 4D 입력을 요구하기 때문입니다.\n",
        "# (batch_size, height, width, channels).\n",
        "# batch_size 차원은 나중에 추가할 것입니다.\n",
        "\n",
        "train_images = train_images[..., None]\n",
        "test_images = test_images[..., None]\n",
        "\n",
        "# 이미지를 [0, 1] 범위로 변경하기.\n",
        "train_images = train_images / np.float32(255)\n",
        "test_images = test_images / np.float32(255)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4AXoHhrsbdF3"
      },
      "source": [
        "## 변수와 그래프를 분산하는 전략 만들기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5mVuLZhbem8d"
      },
      "source": [
        "`tf.distribute.MirroredStrategy` 전략이 어떻게 동작할까요?\n",
        "* 모든 변수와 모델 그래프는 장치(replicas, 다른 문서에서는 replica가 분산 훈련에서 장치 등에 복제된 모델을 의미하는 경우가 있으나 이 문서에서는 장치 자체를 의미합니다)에 복제됩니다.\n",
        "* 입력은 장치에 고르게 분배되어 들어갑니다.\n",
        "* 각 장치는 주어지는 입력에 대해서 손실(loss)과 그래디언트를 계산합니다.\n",
        "* 그래디언트들을 전부 더함으로써 모든 장치들 간에 그래디언트들이 동기화됩니다.\n",
        "* 동기화된 후에, 동일한 업데이트가 각 장치에 있는 변수의 복사본(copies)에 동일하게 적용됩니다.\n",
        "\n",
        "노트: 하나의 범위를 지정해서 모든 코드를 집어넣을 수 있습니다. 자, 같이 살펴보시죠!"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "F2VeZUWUj5S4"
      },
      "outputs": [],
      "source": [
        "# 만약 장치들의 목록이 `tf.distribute.MirroredStrategy` 생성자 안에 명시되어 있지 않다면,\n",
        "# 자동으로 장치를 인식할 것입니다.\n",
        "strategy = tf.distribute.MirroredStrategy()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ZngeM_2o0_JO"
      },
      "outputs": [],
      "source": [
        "print ('장치의 수: {}'.format(strategy.num_replicas_in_sync))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "k53F5I_IiGyI"
      },
      "source": [
        "## 입력 파이프라인 설정하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0Qb6nDgxiN_n"
      },
      "source": [
        "그래프와 변수를 플랫폼과 무관한 SavedModel 형식으로 내보냅니다. 모델을 내보냈다면, 모델을 불러올 때 범위(scope)를 지정해도 되고 하지 않아도 됩니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jwJtsCQhHK-E"
      },
      "outputs": [],
      "source": [
        "BUFFER_SIZE = len(train_images)\n",
        "\n",
        "BATCH_SIZE_PER_REPLICA = 64\n",
        "GLOBAL_BATCH_SIZE = BATCH_SIZE_PER_REPLICA * strategy.num_replicas_in_sync\n",
        "\n",
        "EPOCHS = 10"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "J7fj3GskHC8g"
      },
      "source": [
        "분산 데이터셋들을 `strategy.scope` 내에 생성합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "WYrMNNDhAvVl"
      },
      "outputs": [],
      "source": [
        "with strategy.scope():\n",
        "\n",
        "  train_dataset = tf.data.Dataset.from_tensor_slices((train_images, train_labels)).shuffle(BUFFER_SIZE).batch(GLOBAL_BATCH_SIZE) \n",
        "  train_dist_dataset = strategy.experimental_distribute_dataset(train_dataset)\n",
        "  \n",
        "  test_dataset = tf.data.Dataset.from_tensor_slices((test_images, test_labels)).batch(GLOBAL_BATCH_SIZE) \n",
        "  test_dist_dataset = strategy.experimental_distribute_dataset(test_dataset)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bAXAo_wWbWSb"
      },
      "source": [
        "## 모델 만들기\n",
        "\n",
        "`tf.keras.Sequential`을 사용해서 모델을 생성합니다. Model Subclassing API로도 모델 생성을 할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9ODch-OFCaW4"
      },
      "outputs": [],
      "source": [
        "def create_model():\n",
        "  model = tf.keras.Sequential([\n",
        "      tf.keras.layers.Conv2D(32, 3, activation='relu'),\n",
        "      tf.keras.layers.MaxPooling2D(),\n",
        "      tf.keras.layers.Conv2D(64, 3, activation='relu'),\n",
        "      tf.keras.layers.MaxPooling2D(),\n",
        "      tf.keras.layers.Flatten(),\n",
        "      tf.keras.layers.Dense(64, activation='relu'),\n",
        "      tf.keras.layers.Dense(10, activation='softmax')\n",
        "    ])\n",
        "\n",
        "  return model"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9iagoTBfijUz"
      },
      "outputs": [],
      "source": [
        "# 체크포인트들을 저장하기 위해서 체크포인트 디렉토리를 생성합니다.\n",
        "checkpoint_dir = './training_checkpoints'\n",
        "checkpoint_prefix = os.path.join(checkpoint_dir, \"ckpt\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e-wlFFZbP33n"
      },
      "source": [
        "## 손실 함수 정의하기\n",
        "\n",
        "일반적으로, GPU/CPU 비율이 1인 단일 장치에서 손실은 입력 배치(batch)의 샘플 개수로 나누어집니다.\n",
        "\n",
        "*그렇다면, `tf.distribute.Strategy`를 사용할 때, 손실은 어떻게 계산되어야 할까요?*\n",
        "\n",
        "* 예를들면, 4개의 GPU가 있고 입력 배치 크기가 64라고 하죠. 입력 배치 하나가 여러 개의 장치(4개의 GPU)에 분배됩니다. 각 장치는 크기가 16인 입력을 받습니다.\n",
        "\n",
        "* 각 장치에 있는 모델은 해당 입력에 대해 정방향 계산(forward pass)을 수행하고 손실을 계산합니다. 손실을 장치에 할당된 입력 샘플의 수(BATCH_SIZE_PER_REPLICA = 16)로 나누는 것이 아니라 GLOBAL_BATCH_SIZE(64)로 나누어야 합니다.\n",
        "\n",
        "*왜 이렇게 할까요?*\n",
        "\n",
        "* 위와 같이 계산하는 이유는 그래디언트들이 각 장치에서 계산된 다음, 모든 장치를 동기화하기 위해 이 그래디언트 값들을 전부 **더하기** 때문입니다.\n",
        "\n",
        "*이 것을 텐서플로에서는 어떻게 할까요?*\n",
        "\n",
        "\n",
        "* 만약 이 **예제처럼** 사용자 정의 훈련 루프를 **작성한다면**, **다음과 같이 샘플당** 손실을 더하고 GLOBAL_BATCH_SIZE로 **나누어야** 합니다.\n",
        "`scale_loss = tf.reduce_sum(loss) * (1. / GLOBAL_BATCH_SIZE)`\n",
        "또는 `tf.nn.compute_average_loss` **함수를** 사용할 수도 있습니다. 이 함수는 **샘플당 손실과 선택적으로 샘플 가중치, GLOBAL_BATCH_SIZE를 매개변수 값으로 받고** 스케일이 조정된 손실을 반환합니다.\n",
        "\n",
        "* 만약 규제 손실을 사용하는 모델이라면, 장치 개수로 손실 값을 스케일 조정해야 합니다. 이는 `tf.nn_scale_regularization_loss` 함수를 사용하여 처리할 수 있습니다.\n",
        "\n",
        "* `tf.reduce_mean`을 사용하는 것은 추천하지 않습니다. 이렇게 하는 것은 손실을 실제 장치당 배치 크기로 나눕니다. 이 실제 장치당 배치 크기는 아마 각 단계(step)마다 크기가 다를 수 있습니다.\n",
        "\n",
        "* 이런 축소(`reduction`)와 스케일 조정은 케라스의 `model.compile`과 `model.fit`에서 자동적으로 수행됩니다.\n",
        "\n",
        "* 만약 `tf.keras.losses` 클래스(아래의 예제에서처럼)를 사용한다면, reduction 매개변수를 명시적으로 `NONE` 또는 `SUM` 중 하나로 표시해야 합니다. `AUTO`가 허용되지 않는 것은 사용자가 분산 모드에서 어떻게 축소할지 명시적으로 지정하는 것이 바람직하기 때문입니다.\n",
        "현재 `SUM_OVER_BATCH_SIZE`가 장치당 배치 크기로만 나누고 장치 개수로 나누는 것은 사용자에게 위임하기 때문입니다. 그래서 이렇게 하는 것 대신에 사용자가 명시적으로 축소를 수행하도록 만드는 것이 좋습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "R144Wci782ix"
      },
      "outputs": [],
      "source": [
        "with strategy.scope():\n",
        "  # reduction을 `none`으로 설정합니다. 그래서 우리는 축소를 나중에 하고,\n",
        "  # GLOBAL_BATCH_SIZE로 나눌 수 있습니다.\n",
        "  loss_object = tf.keras.losses.SparseCategoricalCrossentropy(\n",
        "      reduction=tf.keras.losses.Reduction.NONE)\n",
        "  # 또는 loss_fn = tf.keras.losses.sparse_categorical_crossentropy를 사용해도 됩니다.\n",
        "  def compute_loss(labels, predictions):\n",
        "    per_example_loss = loss_object(labels, predictions)\n",
        "    return tf.nn.compute_average_loss(per_example_loss, global_batch_size=GLOBAL_BATCH_SIZE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "w8y54-o9T2Ni"
      },
      "source": [
        "## 손실과 정확도를 기록하기 위한 지표 정의하기\n",
        "\n",
        "이 지표(metrics)는 테스트 손실과 훈련 정확도, 테스트 정확도를 기록합니다. `.result()`를 사용해서 누적된 통계값들을 언제나 볼 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zt3AHb46Tr3w"
      },
      "outputs": [],
      "source": [
        "with strategy.scope():\n",
        "  test_loss = tf.keras.metrics.Mean(name='test_loss')\n",
        "\n",
        "  train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(\n",
        "      name='train_accuracy')\n",
        "  test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(\n",
        "      name='test_accuracy')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iuKuNXPORfqJ"
      },
      "source": [
        "## 훈련 루프"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "OrMmakq5EqeQ"
      },
      "outputs": [],
      "source": [
        "# 모델과 옵티마이저는 `strategy.scope`에서 만들어져야 합니다.\n",
        "with strategy.scope():\n",
        "  model = create_model()\n",
        "\n",
        "  optimizer = tf.keras.optimizers.Adam()\n",
        "\n",
        "  checkpoint = tf.train.Checkpoint(optimizer=optimizer, model=model)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3UX43wUu04EL"
      },
      "outputs": [],
      "source": [
        "with strategy.scope():\n",
        "  def train_step(inputs):\n",
        "    images, labels = inputs\n",
        "\n",
        "    with tf.GradientTape() as tape:\n",
        "      predictions = model(images, training=True)\n",
        "      loss = compute_loss(labels, predictions)\n",
        "\n",
        "    gradients = tape.gradient(loss, model.trainable_variables)\n",
        "    optimizer.apply_gradients(zip(gradients, model.trainable_variables))\n",
        "\n",
        "    train_accuracy.update_state(labels, predictions)\n",
        "    return loss \n",
        "\n",
        "  def test_step(inputs):\n",
        "    images, labels = inputs\n",
        "\n",
        "    predictions = model(images, training=False)\n",
        "    t_loss = loss_object(labels, predictions)\n",
        "\n",
        "    test_loss.update_state(t_loss)\n",
        "    test_accuracy.update_state(labels, predictions)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gX975dMSNw0e"
      },
      "outputs": [],
      "source": [
        "with strategy.scope():\n",
        "  # `experimental_run_v2`는 주어진 계산을 복사하고,\n",
        "  # 분산된 입력으로 계산을 수행합니다.\n",
        "  \n",
        "  @tf.function\n",
        "  def distributed_train_step(dataset_inputs):\n",
        "    per_replica_losses = strategy.experimental_run_v2(train_step,\n",
        "                                                      args=(dataset_inputs,))\n",
        "    return strategy.reduce(tf.distribute.ReduceOp.SUM, per_replica_losses,\n",
        "                           axis=None)\n",
        " \n",
        "  @tf.function\n",
        "  def distributed_test_step(dataset_inputs):\n",
        "    return strategy.experimental_run_v2(test_step, args=(dataset_inputs,))\n",
        "\n",
        "  for epoch in range(EPOCHS):\n",
        "    # 훈련 루프\n",
        "    total_loss = 0.0\n",
        "    num_batches = 0\n",
        "    for x in train_dist_dataset:\n",
        "      total_loss += distributed_train_step(x)\n",
        "      num_batches += 1\n",
        "    train_loss = total_loss / num_batches\n",
        "\n",
        "    # 테스트 루프\n",
        "    for x in test_dist_dataset:\n",
        "      distributed_test_step(x)\n",
        "\n",
        "    if epoch % 2 == 0:\n",
        "      checkpoint.save(checkpoint_prefix)\n",
        "\n",
        "    template = (\"에포크 {}, 손실: {}, 정확도: {}, 테스트 손실: {}, \"\n",
        "                \"테스트 정확도: {}\")\n",
        "    print (template.format(epoch+1, train_loss,\n",
        "                           train_accuracy.result()*100, test_loss.result(),\n",
        "                           test_accuracy.result()*100))\n",
        "\n",
        "    test_loss.reset_states()\n",
        "    train_accuracy.reset_states()\n",
        "    test_accuracy.reset_states()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Z1YvXqOpwy08"
      },
      "source": [
        "위의 예제에서 주목해야 하는 부분\n",
        "* 이 예제는 `train_dist_dataset`과 `test_dist_dataset`을 `for x in ...` 구조를 통해서 반복합니다.\n",
        "* 스케일이 조정된 손실은 `distributed_train_step`의 반환값입니다. `tf.distribute.Strategy.reduce` 호출을 사용해서 장치들 간의 스케일이 조정된 손실 값을 전부 합칩니다. 그리고 나서 `tf.distribute.Strategy.reduce` 반환 값을 더하는 식으로 배치 간의 손실을 모읍니다.\n",
        "* `tf.keras.Metrics`는 `tf.distribute.Strategy.experimental_run_v2`에 의해서 실행되는 `train_step`과 `test_step` 함수 안에서 업데이트 되어야 합니다.\n",
        "* `tf.distribute.Strategy.experimental_run_v2`는 그 전략안에 포함된 각 지역 복제 모델로부터 결과값을 반환해 줍니다. 그리고 이 결과를 사용하는 몇 가지 방법들이 있습니다. `tf.distribute.Strategy.reduce`를 이용하여 값들을 합칠 수 있습니다.  `tf.distribute.Strategy.experimental_local_results`를 사용해서 결과값(지역 복제 모델 당 하나의 결과값)에 들어있는 값들의 리스트를 얻을 수도 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-q5qp31IQD8t"
      },
      "source": [
        "## 최신 체크포인트를 불러와서 테스트하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WNW2P00bkMGJ"
      },
      "source": [
        "`tf.distribute.Strategy`를 사용해서 체크포인트가 만들어진 모델은 전략 사용 여부에 상관없이 불러올 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "pg3B-Cw_cn3a"
      },
      "outputs": [],
      "source": [
        "eval_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(\n",
        "      name='eval_accuracy')\n",
        "\n",
        "new_model = create_model()\n",
        "new_optimizer = tf.keras.optimizers.Adam()\n",
        "\n",
        "test_dataset = tf.data.Dataset.from_tensor_slices((test_images, test_labels)).batch(GLOBAL_BATCH_SIZE)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7qYii7KUYiSM"
      },
      "outputs": [],
      "source": [
        "@tf.function\n",
        "def eval_step(images, labels):\n",
        "  predictions = new_model(images, training=False)\n",
        "  eval_accuracy(labels, predictions)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "LeZ6eeWRoUNq"
      },
      "outputs": [],
      "source": [
        "checkpoint = tf.train.Checkpoint(optimizer=new_optimizer, model=new_model)\n",
        "checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))\n",
        "\n",
        "for images, labels in test_dataset:\n",
        "  eval_step(images, labels)\n",
        "\n",
        "print ('전략을 사용하지 않고, 저장된 모델을 복원한 후의 정확도: {}'.format(\n",
        "    eval_accuracy.result()*100))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EbcI87EEzhzg"
      },
      "source": [
        "## 데이터셋에 대해 반복작업을 하는 다른 방법들\n",
        "\n",
        "### 반복자(iterator)를 사용하기\n",
        "\n",
        "만약 주어진 스텝의 수에 따라서 반복하기 원하면서 전체 데이터셋을 보는 것을 원치 않는다면, `iter`를 호출하여 반복자를 만들 수 있습니다. 그 다음 명시적으로 `next`를 호출합니다. 또한, `tf.funtion` 내부 또는 외부에서 데이터셋을 반복하도록 설정 할 수 있습니다. 다음은 반복자를 사용하여 `tf.function` 외부에서 데이터셋을 반복하는 코드 예제입니다.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7c73wGC00CzN"
      },
      "outputs": [],
      "source": [
        "with strategy.scope():\n",
        "  for _ in range(EPOCHS):\n",
        "    total_loss = 0.0\n",
        "    num_batches = 0\n",
        "    train_iter = iter(train_dist_dataset)\n",
        "\n",
        "    for _ in range(10):\n",
        "      total_loss += distributed_train_step(next(train_iter))\n",
        "      num_batches += 1\n",
        "    average_train_loss = total_loss / num_batches\n",
        "\n",
        "    template = (\"에포크 {}, 손실: {}, 정확도: {}\")\n",
        "    print (template.format(epoch+1, average_train_loss, train_accuracy.result()*100))\n",
        "    train_accuracy.reset_states()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GxVp48Oy0m6y"
      },
      "source": [
        "### tf.function 내부에서 반복하기\n",
        "전체 입력 `train_dist_dataset`에 대해서 `tf.function` 내부에서 `for x in ...` 생성자를 사용함으로써 반복을 하거나, 위에서 사용했던 것처럼 반복자를 사용함으로써 반복을 할 수 있습니다. 아래의 예제에서는 `tf.function`로 한 훈련의 에포크를 감싸고 그 함수에서 `train_dist_dataset`를 반복하는 것을 보여 줍니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-REzmcXv00qm"
      },
      "outputs": [],
      "source": [
        "with strategy.scope():\n",
        "  @tf.function\n",
        "  def distributed_train_epoch(dataset):\n",
        "    total_loss = 0.0\n",
        "    num_batches = 0\n",
        "    for x in dataset:\n",
        "      per_replica_losses = strategy.experimental_run_v2(train_step,\n",
        "                                                        args=(x,))\n",
        "      total_loss += strategy.reduce(\n",
        "        tf.distribute.ReduceOp.SUM, per_replica_losses, axis=None)\n",
        "      num_batches += 1\n",
        "    return total_loss / tf.cast(num_batches, dtype=tf.float32)\n",
        "\n",
        "  for epoch in range(EPOCHS):\n",
        "    train_loss = distributed_train_epoch(train_dist_dataset)\n",
        "\n",
        "    template = (\"Epoch {}, Loss: {}, Accuracy: {}\")\n",
        "    print (template.format(epoch+1, train_loss, train_accuracy.result()*100))\n",
        "\n",
        "    train_accuracy.reset_states()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MuZGXiyC7ABR"
      },
      "source": [
        "### 장치 간의 훈련 손실 기록하기\n",
        "\n",
        "노트: 일반적인 규칙으로, `tf.keras.Metrics`를 사용하여 샘플당 손실 값을 기록하고 장치 내부에서 값이 합쳐지는 것을 피해야 합니다.\n",
        "\n",
        "`tf.metrics.Mean`을 사용하여 여러 장치의 훈련 손실을 기록하는 것을 추천하지 *않습니다*. 왜냐하면 손실의 스케일을 조정하는 계산이 수행되기 때문입니다.\n",
        "\n",
        "예를 들어, 다음과 같은 조건의 훈련을 수행한다고 합시다.\n",
        "* 두개의 장치\n",
        "* 두개의 샘플들이 각 장치에 의해 처리됩니다.\n",
        "* 손실 값을 산출합니다: 각각의 장치에 대해 [2, 3]과 [4, 5]\n",
        "* Global batch size = 4\n",
        "\n",
        "손실의 스케일 조정을 하면, 손실 값을 더하고 GLOBAL_BATCH_SIZE로 나누어 각 장치에 대한 샘플당 손실값을 계산할 수 있습니다. 이 경우에는 (2 + 3) / 4 = 1.24와 (4 + 5) / 4 = 2.25입니다.\n",
        "\n",
        "만약 `tf.metrics.Mean`을 사용해서 두 개의 장치에 대해 손실값을 계산한다면, 결과값이 다릅니다. 이 예제에서는, 측정 지표의 `result()`가 메서드가 호출될 때 `total`이 3.50이고 `count`가 2입니다. 결과값은 `total/count`가 1.75가 됩니다. `tf.keras.Metrics`를 이용해서 계산한 손실값이 추가적인 요인에 의해서 크기조정되며, 이 추가적인 요인은 동기화되는 장치의 개수입니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xisYJaV9KZTN"
      },
      "source": [
        "### 예제와 튜토리얼\n",
        "사용자 정의 훈련루프를 포함한 분산 전략을 사용하는 몇 가지 예제가 있습니다.\n",
        "\n",
        "1. `MirroredStrategy`를 사용해서 MNIST를 훈련하는 [Tutorial](custom_training.ipynb)\n",
        "2.  `MirroredStrategy`를 사용하는 [DenseNet](https://github.com/tensorflow/examples/blob/master/tensorflow_examples/models/densenet/distributed_train.py) 예제.\n",
        "1. `MirroredStrategy`와 `TPUStrategy`를 사용해서 훈련하는 [BERT](https://github.com/tensorflow/models/blob/master/official/nlp/bert/run_classifier.py) 예제. 이 예제는 분산 훈련 중에 어떻게 체크포인트로부터 불러오는지와 어떻게 주기적으로 체크포인트들을 생성해 내는지를 이해하기에 정말 좋습니다.\n",
        "2. `keras_use_ctl flag`를 사용해서 활성화 할 수 있는 MirroredStrategy를 이용해서 훈련되는 [NCF](https://github.com/tensorflow/models/blob/master/official/recommendation/ncf_keras_main.py) 예제\n",
        "3. `MirroredStrategy`을 사용해서 훈련되는 [NMT](https://github.com/tensorflow/examples/blob/master/tensorflow_examples/models/nmt_with_attention/distributed_train.py) 예제.\n",
        "\n",
        "더 많은 예제는 여기에 있습니다. [Distribution strategy guide](../../guide/distributed_training.ipynb#examples_and_tutorials)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6hEJNsokjOKs"
      },
      "source": [
        "## 다음단계\n",
        "\n",
        "새로운 `tf.distribute.Strategy` API를 모델에 적용해 보세요."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "custom_training.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
