{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPython 3.5.5\n",
      "IPython 6.3.0\n",
      "\n",
      "numpy 1.14.2\n",
      "sklearn 0.19.1\n",
      "scipy 1.0.1\n",
      "matplotlib 2.2.2\n",
      "tensorflow 1.7.0\n"
     ]
    }
   ],
   "source": [
    "%load_ext watermark\n",
    "%watermark -v -p numpy,sklearn,scipy,matplotlib,tensorflow"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**13장 – 합성곱 신경망**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_이 노트북은 13장에 있는 모든 샘플 코드와 연습문제 해답을 가지고 있습니다._"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 설정"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "파이썬 2와 3을 모두 지원합니다. 공통 모듈을 임포트하고 맷플롯립 그림이 노트북 안에 포함되도록 설정하고 생성한 그림을 저장하기 위한 함수를 준비합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 파이썬 2와 파이썬 3 지원\n",
    "from __future__ import division, print_function, unicode_literals\n",
    "\n",
    "# 공통\n",
    "import numpy as np\n",
    "import os\n",
    "\n",
    "# 일관된 출력을 위해 유사난수 초기화\n",
    "def reset_graph(seed=42):\n",
    "    tf.reset_default_graph()\n",
    "    tf.set_random_seed(seed)\n",
    "    np.random.seed(seed)\n",
    "\n",
    "# 맷플롯립 설정\n",
    "%matplotlib inline\n",
    "import matplotlib\n",
    "import matplotlib.pyplot as plt\n",
    "plt.rcParams['axes.labelsize'] = 14\n",
    "plt.rcParams['xtick.labelsize'] = 12\n",
    "plt.rcParams['ytick.labelsize'] = 12\n",
    "\n",
    "# 한글출력\n",
    "plt.rcParams['font.family'] = 'NanumBarunGothic'\n",
    "plt.rcParams['axes.unicode_minus'] = False\n",
    "\n",
    "# 그림을 저장할 폴더\n",
    "PROJECT_ROOT_DIR = \".\"\n",
    "CHAPTER_ID = \"cnn\"\n",
    "\n",
    "def save_fig(fig_id, tight_layout=True):\n",
    "    path = os.path.join(PROJECT_ROOT_DIR, \"images\", CHAPTER_ID, fig_id + \".png\")\n",
    "    if tight_layout:\n",
    "        plt.tight_layout()\n",
    "    plt.savefig(path, format='png', dpi=300)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "흑백 이미지와 컬러 이미지를 그리기 위한 유틸리티 함수입니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_image(image):\n",
    "    plt.imshow(image, cmap=\"gray\", interpolation=\"nearest\")\n",
    "    plt.axis(\"off\")\n",
    "\n",
    "def plot_color_image(image):\n",
    "    plt.imshow(image.astype(np.uint8),interpolation=\"nearest\")\n",
    "    plt.axis(\"off\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "텐서플로를 임포트합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 합성곱 층"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "imread에 관련된 경고는 사이킷런 0.20에서 수정될 예정입니다. 실제 실행에는 영향을 미치지 않으므로 무시합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/sklearn/datasets/base.py:762: DeprecationWarning: `imread` is deprecated!\n",
      "`imread` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.\n",
      "Use ``imageio.imread`` instead.\n",
      "  images = [imread(filename) for filename in filenames]\n",
      "/home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/sklearn/datasets/base.py:762: DeprecationWarning: `imread` is deprecated!\n",
      "`imread` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.\n",
      "Use ``imageio.imread`` instead.\n",
      "  images = [imread(filename) for filename in filenames]\n",
      "/home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/sklearn/datasets/base.py:762: DeprecationWarning: `imread` is deprecated!\n",
      "`imread` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.\n",
      "Use ``imageio.imread`` instead.\n",
      "  images = [imread(filename) for filename in filenames]\n",
      "/home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/sklearn/datasets/base.py:762: DeprecationWarning: `imread` is deprecated!\n",
      "`imread` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.\n",
      "Use ``imageio.imread`` instead.\n",
      "  images = [imread(filename) for filename in filenames]\n"
     ]
    }
   ],
   "source": [
    "from sklearn.datasets import load_sample_image\n",
    "china = load_sample_image(\"china.jpg\")\n",
    "flower = load_sample_image(\"flower.jpg\")\n",
    "image = china[150:220, 130:250]\n",
    "height, width, channels = image.shape\n",
    "image_grayscale = image.mean(axis=2).astype(np.float32)\n",
    "images = image_grayscale.reshape(1, height, width, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPkAAAD+CAYAAAD4SqMoAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAAxZJREFUeJzt2sEJAzEMAMEopP+WlRby8Rk2MwXYwrD4o9ndF9D1vj0AcJbIIU7kECdyiBM5xIkc4kQOcSKHOJFD3OfEoTNjje5htzcXZ+bq/f9od396dD85xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xM3u3p4BOMhPDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnFfY/wO96pqc0MAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPkAAAD+CAYAAAD4SqMoAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAAx9JREFUeJzt3TEKAkAMAEEj/v/LsbUUIQjLTH2Ea5Y0Bze7+wC6nv++AHBL5BAncogTOcSJHOJEDnEihziRQ5zIIe51MXRmPKODY7s735yzySFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEvS6G7u7FWOAHNjnEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BB38j/5zFyMBT7s7lfnbHKIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEihziRQ5zIIU7kECdyiBM5xIkc4kQOcbO7/74DcMgmhziRQ5zIIU7kECdyiBM5xIkc4kQOcSKHOJFDnMghTuQQJ3KIEznEiRziRA5xIoc4kUOcyCFO5BAncogTOcSJHOJEDnEih7g3vP8R+O0K5OMAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fmap = np.zeros(shape=(7, 7, 1, 2), dtype=np.float32)\n",
    "fmap[:, 3, 0, 0] = 1\n",
    "fmap[3, :, 0, 1] = 1\n",
    "plot_image(fmap[:, :, 0, 0])\n",
    "plt.show()\n",
    "plot_image(fmap[:, :, 0, 1])\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "X = tf.placeholder(tf.float32, shape=(None, height, width, 1))\n",
    "feature_maps = tf.constant(fmap)\n",
    "convolution = tf.nn.conv2d(X, feature_maps, strides=[1,1,1,1], padding=\"SAME\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "with tf.Session() as sess:\n",
    "    output = convolution.eval(feed_dict={X: images})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 25740 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_image(images[0, :, :, 0])\n",
    "save_fig(\"china_original_small\", tight_layout=False)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 20272 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_image(output[0, :, :, 0])\n",
    "save_fig(\"china_vertical\", tight_layout=False)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 19160 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_image(output[0, :, :, 1])\n",
    "save_fig(\"china_horizontal\", tight_layout=False)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 간단한 예제"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/sklearn/datasets/base.py:762: DeprecationWarning: `imread` is deprecated!\n",
      "`imread` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.\n",
      "Use ``imageio.imread`` instead.\n",
      "  images = [imread(filename) for filename in filenames]\n",
      "/home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/sklearn/datasets/base.py:762: DeprecationWarning: `imread` is deprecated!\n",
      "`imread` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.\n",
      "Use ``imageio.imread`` instead.\n",
      "  images = [imread(filename) for filename in filenames]\n",
      "/home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/sklearn/datasets/base.py:762: DeprecationWarning: `imread` is deprecated!\n",
      "`imread` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.\n",
      "Use ``imageio.imread`` instead.\n",
      "  images = [imread(filename) for filename in filenames]\n",
      "/home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/sklearn/datasets/base.py:762: DeprecationWarning: `imread` is deprecated!\n",
      "`imread` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.\n",
      "Use ``imageio.imread`` instead.\n",
      "  images = [imread(filename) for filename in filenames]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 94376 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "# imread 함수와 관련된 경고는 scikit-learn 0.20에서 수정될 예정입니다.\n",
    "from sklearn.datasets import load_sample_images\n",
    "\n",
    "# Load sample images\n",
    "china = load_sample_image(\"china.jpg\")\n",
    "flower = load_sample_image(\"flower.jpg\")\n",
    "dataset = np.array([china, flower], dtype=np.float32)\n",
    "batch_size, height, width, channels = dataset.shape\n",
    "\n",
    "# Create 2 filters\n",
    "filters = np.zeros(shape=(7, 7, channels, 2), dtype=np.float32)\n",
    "filters[:, 3, :, 0] = 1  # vertical line\n",
    "filters[3, :, :, 1] = 1  # horizontal line\n",
    "\n",
    "# Create a graph with input X plus a convolutional layer applying the 2 filters\n",
    "X = tf.placeholder(tf.float32, shape=(None, height, width, channels))\n",
    "convolution = tf.nn.conv2d(X, filters, strides=[1,2,2,1], padding=\"SAME\")\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    output = sess.run(convolution, feed_dict={X: dataset})\n",
    "\n",
    "plt.imshow(output[0, :, :, 1], cmap=\"gray\") # plot 1st image's 2nd feature map\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 92492 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 89548 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 73564 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 71668 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "for image_index in (0, 1):\n",
    "    for feature_map_index in (0, 1):\n",
    "        plot_image(output[image_index, :, :, feature_map_index])\n",
    "        plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`tf.layers.conv2d()` 사용하기:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "X = tf.placeholder(shape=(None, height, width, channels), dtype=tf.float32)\n",
    "conv = tf.layers.conv2d(X, filters=2, kernel_size=7, strides=[2,2],\n",
    "                        padding=\"SAME\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "init = tf.global_variables_initializer()\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    init.run()\n",
    "    output = sess.run(conv, feed_dict={X: dataset})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 129228 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.imshow(output[0, :, :, 1], cmap=\"gray\") # 첫 번째 이미지의 두 번째 특성맵 그리기\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## VALID vs SAME 패딩"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "VALID:\n",
      " [[[[184.]\n",
      "   [389.]]]]\n",
      "SAME:\n",
      " [[[[143.]\n",
      "   [348.]\n",
      "   [204.]]]]\n"
     ]
    }
   ],
   "source": [
    "reset_graph()\n",
    "\n",
    "filter_primes = np.array([2., 3., 5., 7., 11., 13.], dtype=np.float32)\n",
    "x = tf.constant(np.arange(1, 13+1, dtype=np.float32).reshape([1, 1, 13, 1]))\n",
    "filters = tf.constant(filter_primes.reshape(1, 6, 1, 1))\n",
    "\n",
    "valid_conv = tf.nn.conv2d(x, filters, strides=[1, 1, 5, 1], padding='VALID')\n",
    "same_conv = tf.nn.conv2d(x, filters, strides=[1, 1, 5, 1], padding='SAME')\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    print(\"VALID:\\n\", valid_conv.eval())\n",
    "    print(\"SAME:\\n\", same_conv.eval())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "VALID:\n",
      "184.0\n",
      "389.0\n",
      "SAME:\n",
      "143.0\n",
      "348.0\n",
      "204.0\n"
     ]
    }
   ],
   "source": [
    "print(\"VALID:\")\n",
    "print(np.array([1,2,3,4,5,6]).T.dot(filter_primes))\n",
    "print(np.array([6,7,8,9,10,11]).T.dot(filter_primes))\n",
    "print(\"SAME:\")\n",
    "print(np.array([0,1,2,3,4,5]).T.dot(filter_primes))\n",
    "print(np.array([5,6,7,8,9,10]).T.dot(filter_primes))\n",
    "print(np.array([10,11,12,13,0,0]).T.dot(filter_primes))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 풀링 층"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "batch_size, height, width, channels = dataset.shape\n",
    "\n",
    "filters = np.zeros(shape=(7, 7, channels, 2), dtype=np.float32)\n",
    "filters[:, 3, :, 0] = 1  # 수직선\n",
    "filters[3, :, :, 1] = 1  # 수평선"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 205208 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "X = tf.placeholder(tf.float32, shape=(None, height, width, channels))\n",
    "max_pool = tf.nn.max_pool(X, ksize=[1,2,2,1], strides=[1,2,2,1],padding=\"VALID\")\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    output = sess.run(max_pool, feed_dict={X: dataset})\n",
    "\n",
    "plt.imshow(output[0].astype(np.uint8))  # 첫 번째 이미지의 출력을 그리기\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 261860 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 212824 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_color_image(dataset[0])\n",
    "save_fig(\"china_original\")\n",
    "plt.show()\n",
    "    \n",
    "plot_color_image(output[0])\n",
    "save_fig(\"china_max_pool\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# MNIST"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "height = 28\n",
    "width = 28\n",
    "channels = 1\n",
    "n_inputs = height * width\n",
    "\n",
    "conv1_fmaps = 32\n",
    "conv1_ksize = 3\n",
    "conv1_stride = 1\n",
    "conv1_pad = \"SAME\"\n",
    "\n",
    "conv2_fmaps = 64\n",
    "conv2_ksize = 3\n",
    "conv2_stride = 2\n",
    "conv2_pad = \"SAME\"\n",
    "\n",
    "pool3_fmaps = conv2_fmaps\n",
    "\n",
    "n_fc1 = 64\n",
    "n_outputs = 10\n",
    "\n",
    "reset_graph()\n",
    "\n",
    "with tf.name_scope(\"inputs\"):\n",
    "    X = tf.placeholder(tf.float32, shape=[None, n_inputs], name=\"X\")\n",
    "    X_reshaped = tf.reshape(X, shape=[-1, height, width, channels])\n",
    "    y = tf.placeholder(tf.int32, shape=[None], name=\"y\")\n",
    "\n",
    "conv1 = tf.layers.conv2d(X_reshaped, filters=conv1_fmaps, kernel_size=conv1_ksize,\n",
    "                         strides=conv1_stride, padding=conv1_pad,\n",
    "                         activation=tf.nn.relu, name=\"conv1\")\n",
    "conv2 = tf.layers.conv2d(conv1, filters=conv2_fmaps, kernel_size=conv2_ksize,\n",
    "                         strides=conv2_stride, padding=conv2_pad,\n",
    "                         activation=tf.nn.relu, name=\"conv2\")\n",
    "\n",
    "with tf.name_scope(\"pool3\"):\n",
    "    pool3 = tf.nn.max_pool(conv2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding=\"VALID\")\n",
    "    pool3_flat = tf.reshape(pool3, shape=[-1, pool3_fmaps * 7 * 7])\n",
    "\n",
    "with tf.name_scope(\"fc1\"):\n",
    "    fc1 = tf.layers.dense(pool3_flat, n_fc1, activation=tf.nn.relu, name=\"fc1\")\n",
    "\n",
    "with tf.name_scope(\"output\"):\n",
    "    logits = tf.layers.dense(fc1, n_outputs, name=\"output\")\n",
    "    Y_proba = tf.nn.softmax(logits, name=\"Y_proba\")\n",
    "\n",
    "with tf.name_scope(\"train\"):\n",
    "    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=y)\n",
    "    loss = tf.reduce_mean(xentropy)\n",
    "    optimizer = tf.train.AdamOptimizer()\n",
    "    training_op = optimizer.minimize(loss)\n",
    "\n",
    "with tf.name_scope(\"eval\"):\n",
    "    correct = tf.nn.in_top_k(logits, y, 1)\n",
    "    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))\n",
    "\n",
    "with tf.name_scope(\"init_and_save\"):\n",
    "    init = tf.global_variables_initializer()\n",
    "    saver = tf.train.Saver()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From /home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/tensorflow/contrib/learn/python/learn/datasets/base.py:198: retry (from tensorflow.contrib.learn.python.learn.datasets.base) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Use the retry module or similar alternatives.\n",
      "Extracting /tmp/data/train-images-idx3-ubyte.gz\n",
      "Extracting /tmp/data/train-labels-idx1-ubyte.gz\n",
      "Extracting /tmp/data/t10k-images-idx3-ubyte.gz\n",
      "Extracting /tmp/data/t10k-labels-idx1-ubyte.gz\n"
     ]
    }
   ],
   "source": [
    "from tensorflow.examples.tutorials.mnist import input_data\n",
    "tf.logging.set_verbosity(tf.logging.ERROR)  # deprecated 경고 메세지를 출력하지 않기 위해 \n",
    "mnist = input_data.read_data_sets(\"/tmp/data/\")\n",
    "tf.logging.set_verbosity(tf.logging.INFO)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0 훈련 정확도: 0.98 검증 세트 정확도: 0.976\n",
      "1 훈련 정확도: 0.98 검증 세트 정확도: 0.9846\n",
      "2 훈련 정확도: 0.99 검증 세트 정확도: 0.9826\n",
      "3 훈련 정확도: 1.0 검증 세트 정확도: 0.9858\n",
      "4 훈련 정확도: 1.0 검증 세트 정확도: 0.9874\n",
      "5 훈련 정확도: 0.99 검증 세트 정확도: 0.9862\n",
      "6 훈련 정확도: 1.0 검증 세트 정확도: 0.9884\n",
      "7 훈련 정확도: 0.99 검증 세트 정확도: 0.9858\n",
      "8 훈련 정확도: 1.0 검증 세트 정확도: 0.9886\n",
      "9 훈련 정확도: 1.0 검증 세트 정확도: 0.9864\n",
      "테스트 세트에서 최종 정확도: 0.9868\n"
     ]
    }
   ],
   "source": [
    "n_epochs = 10\n",
    "batch_size = 100\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    init.run()\n",
    "    for epoch in range(n_epochs):\n",
    "        for iteration in range(mnist.train.num_examples // batch_size):\n",
    "            X_batch, y_batch = mnist.train.next_batch(batch_size)\n",
    "            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})\n",
    "        acc_train = accuracy.eval(feed_dict={X: X_batch, y: y_batch})\n",
    "        acc_val = accuracy.eval(feed_dict={X: mnist.validation.images, y: mnist.validation.labels})\n",
    "        print(epoch, \"훈련 정확도:\", acc_train, \"검증 세트 정확도:\", acc_val)\n",
    "\n",
    "    acc_test = accuracy.eval(feed_dict={X: mnist.test.images,\n",
    "                                        y: mnist.test.labels})\n",
    "    print(\"테스트 세트에서 최종 정확도:\", acc_test)\n",
    "        \n",
    "    save_path = saver.save(sess, \"./my_mnist_model\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "# 연습문제 해답"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. to 6."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "부록 A 참조."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. MNIST를 위한 고성능 CNN\n",
    "_문제: 자신만의 CNN을 만들고 MNIST 데이터셋에서 가능한 최대 정확도를 달성해보세요._"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "다음 CNN은 위에서 만든 것과 비슷하지만 두 번째 합성곱 층에서 스트라이드 1을 사용하고 두 번째 합성곱 층 뒤에 25% 드롭아웃, 완전 연결층 뒤에 50% 드롭아웃을 적용한 것이 다릅니다. 그리고 조기 종료를 사용하여 훈련합니다. MNIST에서 약 99.2% 정확도를 달성합니다. 최고의 수준은 아니지만 나쁘지 않네요. 더 좋게 만들 수 있을까요?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "\n",
    "height = 28\n",
    "width = 28\n",
    "channels = 1\n",
    "n_inputs = height * width\n",
    "\n",
    "conv1_fmaps = 32\n",
    "conv1_ksize = 3\n",
    "conv1_stride = 1\n",
    "conv1_pad = \"SAME\"\n",
    "\n",
    "conv2_fmaps = 64\n",
    "conv2_ksize = 3\n",
    "conv2_stride = 1\n",
    "conv2_pad = \"SAME\"\n",
    "conv2_dropout_rate = 0.25\n",
    "\n",
    "pool3_fmaps = conv2_fmaps\n",
    "\n",
    "n_fc1 = 128\n",
    "fc1_dropout_rate = 0.5\n",
    "\n",
    "n_outputs = 10\n",
    "\n",
    "reset_graph()\n",
    "\n",
    "with tf.name_scope(\"inputs\"):\n",
    "    X = tf.placeholder(tf.float32, shape=[None, n_inputs], name=\"X\")\n",
    "    X_reshaped = tf.reshape(X, shape=[-1, height, width, channels])\n",
    "    y = tf.placeholder(tf.int32, shape=[None], name=\"y\")\n",
    "    training = tf.placeholder_with_default(False, shape=[], name='training')\n",
    "\n",
    "conv1 = tf.layers.conv2d(X_reshaped, filters=conv1_fmaps, kernel_size=conv1_ksize,\n",
    "                         strides=conv1_stride, padding=conv1_pad,\n",
    "                         activation=tf.nn.relu, name=\"conv1\")\n",
    "conv2 = tf.layers.conv2d(conv1, filters=conv2_fmaps, kernel_size=conv2_ksize,\n",
    "                         strides=conv2_stride, padding=conv2_pad,\n",
    "                         activation=tf.nn.relu, name=\"conv2\")\n",
    "\n",
    "with tf.name_scope(\"pool3\"):\n",
    "    pool3 = tf.nn.max_pool(conv2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding=\"VALID\")\n",
    "    pool3_flat = tf.reshape(pool3, shape=[-1, pool3_fmaps * 14 * 14])\n",
    "    pool3_flat_drop = tf.layers.dropout(pool3_flat, conv2_dropout_rate, training=training)\n",
    "\n",
    "with tf.name_scope(\"fc1\"):\n",
    "    fc1 = tf.layers.dense(pool3_flat_drop, n_fc1, activation=tf.nn.relu, name=\"fc1\")\n",
    "    fc1_drop = tf.layers.dropout(fc1, fc1_dropout_rate, training=training)\n",
    "\n",
    "with tf.name_scope(\"output\"):\n",
    "    logits = tf.layers.dense(fc1, n_outputs, name=\"output\")\n",
    "    Y_proba = tf.nn.softmax(logits, name=\"Y_proba\")\n",
    "\n",
    "with tf.name_scope(\"train\"):\n",
    "    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=y)\n",
    "    loss = tf.reduce_mean(xentropy)\n",
    "    optimizer = tf.train.AdamOptimizer()\n",
    "    training_op = optimizer.minimize(loss)\n",
    "\n",
    "with tf.name_scope(\"eval\"):\n",
    "    correct = tf.nn.in_top_k(logits, y, 1)\n",
    "    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))\n",
    "\n",
    "with tf.name_scope(\"init_and_save\"):\n",
    "    init = tf.global_variables_initializer()\n",
    "    saver = tf.train.Saver()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "데이터를 로드합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting /tmp/data/train-images-idx3-ubyte.gz\n",
      "Extracting /tmp/data/train-labels-idx1-ubyte.gz\n",
      "Extracting /tmp/data/t10k-images-idx3-ubyte.gz\n",
      "Extracting /tmp/data/t10k-labels-idx1-ubyte.gz\n"
     ]
    }
   ],
   "source": [
    "from tensorflow.examples.tutorials.mnist import input_data\n",
    "mnist = input_data.read_data_sets(\"/tmp/data/\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`get_model_params()`는 모델의 상태(즉, 모든 변수의 값)를 얻어오고 `restore_model_params()` 함수는 이전 상태를 복원합니다. 이 함수들은 조기 종료의 속도를 높이기 위해 사용합니다. 즉 가장 좋은 모델을 디스크에 저장하는 대신 메모리에 저장합니다. 훈련이 끝난 후에 최상의 모델을 복원합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_model_params():\n",
    "    gvars = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES)\n",
    "    return {gvar.op.name: value for gvar, value in zip(gvars, tf.get_default_session().run(gvars))}\n",
    "\n",
    "def restore_model_params(model_params):\n",
    "    gvar_names = list(model_params.keys())\n",
    "    assign_ops = {gvar_name: tf.get_default_graph().get_operation_by_name(gvar_name + \"/Assign\")\n",
    "                  for gvar_name in gvar_names}\n",
    "    init_values = {gvar_name: assign_op.inputs[1] for gvar_name, assign_op in assign_ops.items()}\n",
    "    feed_dict = {init_values[gvar_name]: model_params[gvar_name] for gvar_name in gvar_names}\n",
    "    tf.get_default_session().run(assign_ops, feed_dict=feed_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "모델을 훈련시켜 보죠! 조기 종료 구현은 다음과 같이 작동합니다:\n",
    "* 100번 훈련 반복마다 검증 세트에서 모델을 평가합니다.\n",
    "* 모델의 성능이 이전에 찾은 최상의 모델보다 낫다면 이 모델을 메모리에 저장합니다.\n",
    "* 연속해서 100번의 평가 동안에도 진전이 없다면 훈련이 중지됩니다.\n",
    "* 훈련이 끝난 후에 최상의 모델을 복원합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "에포크 0, 훈련 정확도: 100.0000%, 검증 세트 정확도: 98.4400%, 검증 세트에서 최선의 손실: 0.059448\n",
      "에포크 1, 훈련 정확도: 100.0000%, 검증 세트 정확도: 98.6000%, 검증 세트에서 최선의 손실: 0.043736\n",
      "에포크 2, 훈련 정확도: 98.0000%, 검증 세트 정확도: 98.7600%, 검증 세트에서 최선의 손실: 0.039157\n",
      "에포크 3, 훈련 정확도: 100.0000%, 검증 세트 정확도: 98.6600%, 검증 세트에서 최선의 손실: 0.039157\n",
      "에포크 4, 훈련 정확도: 100.0000%, 검증 세트 정확도: 99.1600%, 검증 세트에서 최선의 손실: 0.032197\n",
      "에포크 5, 훈련 정확도: 98.0000%, 검증 세트 정확도: 99.1000%, 검증 세트에서 최선의 손실: 0.032197\n",
      "에포크 6, 훈련 정확도: 100.0000%, 검증 세트 정확도: 99.1000%, 검증 세트에서 최선의 손실: 0.032197\n",
      "에포크 7, 훈련 정확도: 100.0000%, 검증 세트 정확도: 99.1000%, 검증 세트에서 최선의 손실: 0.032197\n",
      "에포크 8, 훈련 정확도: 100.0000%, 검증 세트 정확도: 99.0000%, 검증 세트에서 최선의 손실: 0.032197\n",
      "에포크 9, 훈련 정확도: 100.0000%, 검증 세트 정확도: 99.2000%, 검증 세트에서 최선의 손실: 0.032197\n",
      "에포크 10, 훈련 정확도: 100.0000%, 검증 세트 정확도: 99.1000%, 검증 세트에서 최선의 손실: 0.032197\n",
      "에포크 11, 훈련 정확도: 100.0000%, 검증 세트 정확도: 98.9600%, 검증 세트에서 최선의 손실: 0.032197\n",
      "조기 종료!\n",
      "테스트 세트에서 최종 정확도: 0.9852\n"
     ]
    }
   ],
   "source": [
    "n_epochs = 1000\n",
    "batch_size = 50\n",
    "\n",
    "best_loss_val = np.infty\n",
    "check_interval = 500\n",
    "checks_since_last_progress = 0\n",
    "max_checks_without_progress = 20\n",
    "best_model_params = None \n",
    "\n",
    "with tf.Session() as sess:\n",
    "    init.run()\n",
    "    for epoch in range(n_epochs):\n",
    "        for iteration in range(mnist.train.num_examples // batch_size):\n",
    "            X_batch, y_batch = mnist.train.next_batch(batch_size)\n",
    "            sess.run(training_op, feed_dict={X: X_batch, y: y_batch, training: True})\n",
    "            if iteration % check_interval == 0:\n",
    "                loss_val = loss.eval(feed_dict={X: mnist.validation.images,\n",
    "                                                y: mnist.validation.labels})\n",
    "                if loss_val < best_loss_val:\n",
    "                    best_loss_val = loss_val\n",
    "                    checks_since_last_progress = 0\n",
    "                    best_model_params = get_model_params()\n",
    "                else:\n",
    "                    checks_since_last_progress += 1\n",
    "        acc_train = accuracy.eval(feed_dict={X: X_batch, y: y_batch})\n",
    "        acc_val = accuracy.eval(feed_dict={X: mnist.validation.images,\n",
    "                                           y: mnist.validation.labels})\n",
    "        print(\"에포크 {}, 훈련 정확도: {:.4f}%, 검증 세트 정확도: {:.4f}%, 검증 세트에서 최선의 손실: {:.6f}\".format(\n",
    "                  epoch, acc_train * 100, acc_val * 100, best_loss_val))\n",
    "        if checks_since_last_progress > max_checks_without_progress:\n",
    "            print(\"조기 종료!\")\n",
    "            break\n",
    "\n",
    "    if best_model_params:\n",
    "        restore_model_params(best_model_params)\n",
    "    # GPU 메모리가 부족해서 5000개만 평가했습니다. CPU를 사용하거나 GPU 메모리가 크다면 테스트 세트 전체를 평가해 보세요.\n",
    "    acc_test = accuracy.eval(feed_dict={X: mnist.test.images[:5000],\n",
    "                                        y: mnist.test.labels[:5000]})\n",
    "    print(\"테스트 세트에서 최종 정확도:\", acc_test)\n",
    "    save_path = saver.save(sess, \"./my_mnist_model\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## 8.  Inception v3를 사용하여 사이즈가 큰 이미지 분류하기\n",
    "\n",
    "### 8.1.\n",
    "_문제: 여러 종류의 동물 이미지를 내려받으세요. `matplotlib.image.mpimg.imread()` 함수나 `scipy.misc.imread()` 함수를 사용해 파이썬에서 이미지를 로드하세요. 이미지 크기를 바꾸고 잘라내어 299 × 299픽셀로 만들고, 알파 채널을 포함하지 않고 세 개의 채널(RGB)만 있는지 확인합니다. Inception에서 훈련에 사용한 이미지들은 -1.0에서 1.0 사이의 값을 가지도록 전처리되었습니다. 따라서 여기서 준비한 이미지들도 마찬가지로 전처리되어야 합니다._"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "width = 299\n",
    "height = 299\n",
    "channels = 3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 157636 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.image as mpimg\n",
    "test_image = mpimg.imread(os.path.join(\"images\",\"cnn\",\"test_image.png\"))[:, :, :channels]\n",
    "plt.imshow(test_image)\n",
    "plt.axis(\"off\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[0, 1] 범위가 아니라 (미리 학습된 인셉션 모델처럼) [-1, 1] 범위를 가지도록 만듭니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_image = 2 * test_image - 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8.2.\n",
    "_문제: 가장 최근에 미리 훈련된 Inception v3 모델을 내려받습니다. 체크포인트는 https://goo.gl/XgKiSi 에서 내려받을 수 있습니다. 클래스 이름의 목록은 https://goo.gl/brXRtZ 에 있습니다. 하지만 이 목록 맨 위에 background 클래스를 추가해야 합니다._"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "import tarfile\n",
    "from six.moves import urllib\n",
    "\n",
    "TF_MODELS_URL = \"http://download.tensorflow.org/models\"\n",
    "INCEPTION_V3_URL = TF_MODELS_URL + \"/inception_v3_2016_08_28.tar.gz\"\n",
    "INCEPTION_PATH = os.path.join(\"datasets\", \"inception\")\n",
    "INCEPTION_V3_CHECKPOINT_PATH = os.path.join(INCEPTION_PATH, \"inception_v3.ckpt\")\n",
    "\n",
    "def download_progress(count, block_size, total_size):\n",
    "    percent = count * block_size * 100 // total_size\n",
    "    sys.stdout.write(\"\\rDownloading: {}%\".format(percent))\n",
    "    sys.stdout.flush()\n",
    "\n",
    "def fetch_pretrained_inception_v3(url=INCEPTION_V3_URL, path=INCEPTION_PATH):\n",
    "    if os.path.exists(INCEPTION_V3_CHECKPOINT_PATH):\n",
    "        return\n",
    "    os.makedirs(path, exist_ok=True)\n",
    "    tgz_path = os.path.join(path, \"inception_v3.tgz\")\n",
    "    urllib.request.urlretrieve(url, tgz_path, reporthook=download_progress)\n",
    "    inception_tgz = tarfile.open(tgz_path)\n",
    "    inception_tgz.extractall(path=path)\n",
    "    inception_tgz.close()\n",
    "    os.remove(tgz_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "fetch_pretrained_inception_v3()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "import re\n",
    "\n",
    "CLASS_NAME_REGEX = re.compile(r\"^n\\d+\\s+(.*)\\s*$\", re.M | re.U)\n",
    "\n",
    "def load_class_names():\n",
    "    with open(os.path.join(\"datasets\", \"inception\", \"imagenet_class_names.txt\"), \"rb\") as f:\n",
    "        content = f.read().decode(\"utf-8\")\n",
    "        return CLASS_NAME_REGEX.findall(content)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "class_names = [\"background\"] + load_class_names()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['background',\n",
       " 'tench, Tinca tinca',\n",
       " 'goldfish, Carassius auratus',\n",
       " 'great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias',\n",
       " 'tiger shark, Galeocerdo cuvieri']"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "class_names[:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8.3.\n",
    "_문제: 다음과 같이 `inception_v3()` 함수를 호출해 Inception v3 모델을 만듭니다. 반드시 `inception_v3_arg_scope()` 함수로 생성한 이름 범위 안에서 만들어야 합니다. 또한 다음과 같이 is_training=False와 num_classes=1001로 지정해야 합니다._"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "from tensorflow.contrib.slim.nets import inception\n",
    "import tensorflow.contrib.slim as slim\n",
    "\n",
    "reset_graph()\n",
    "\n",
    "X = tf.placeholder(tf.float32, shape=[None, 299, 299, 3], name=\"X\")\n",
    "with slim.arg_scope(inception.inception_v3_arg_scope()):\n",
    "    logits, end_points = inception.inception_v3(\n",
    "        X, num_classes=1001, is_training=False)\n",
    "predictions = end_points[\"Predictions\"]\n",
    "saver = tf.train.Saver()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8.4.\n",
    "_문제: 세션을 열고 Saver를 사용해 앞서 내려받은 미리 훈련된 모델의 체크포인트를 복원하세요._"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Restoring parameters from datasets/inception/inception_v3.ckpt\n"
     ]
    }
   ],
   "source": [
    "with tf.Session() as sess:\n",
    "    saver.restore(sess, INCEPTION_V3_CHECKPOINT_PATH)\n",
    "    # ..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8.5.\n",
    "_문제: 모델을 실행해서 준비한 이미지를 분류해보세요. 이미지마다 상위 다섯 개 예측을 추정 확률과 함께 출력해보세요. 모델이 얼마나 정확한가요?_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Restoring parameters from datasets/inception/inception_v3.ckpt\n"
     ]
    }
   ],
   "source": [
    "X_test = test_image.reshape(-1, height, width, channels)\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    saver.restore(sess, INCEPTION_V3_CHECKPOINT_PATH)\n",
    "    predictions_val = predictions.eval(feed_dict={X: X_test})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "276"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "most_likely_class_index = np.argmax(predictions_val[0])\n",
    "most_likely_class_index"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'African hunting dog, hyena dog, Cape hunting dog, Lycaon pictus'"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "class_names[most_likely_class_index]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "African hunting dog, hyena dog, Cape hunting dog, Lycaon pictus: 93.13%\n",
      "hyena, hyaena: 2.57%\n",
      "European fire salamander, Salamandra salamandra: 0.06%\n",
      "bearskin, busby, shako: 0.05%\n",
      "swimming trunks, bathing trunks: 0.05%\n"
     ]
    }
   ],
   "source": [
    "top_5 = np.argpartition(predictions_val[0], -5)[-5:]\n",
    "top_5 = reversed(top_5[np.argsort(predictions_val[0][top_5])])\n",
    "for i in top_5:\n",
    "    print(\"{0}: {1:.2f}%\".format(class_names[i], 100 * predictions_val[0][i]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이 이미지에서 매우 정확도가 높습니다. 즉 높은 신뢰도로 정확하게 예측합니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 9. 대규모 이미지 분류를  위한 전이 학습\n",
    "\n",
    "### 9.1.\n",
    "_문제: 클래스마다 최소한 100개의 이미지가 들어 있는 훈련 세트를 만드세요. 예를 들어 위치에 따라(해변, 산, 도심 등) 자신의 사진을 분류하거나, 꽃 데이터셋(https://goo.gl/EgJVXZ )이나 MIT의 장소 데이터셋(http://places.csail.mit.edu/) (등록이 필요하고 매우 큽니다) 같은 기존의 데이터셋을 사용할 수도 있습니다._"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "꽃 데이터셋을 사용해 보죠. 먼저 데이터를 다운로드합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "import tarfile\n",
    "from six.moves import urllib\n",
    "\n",
    "FLOWERS_URL = \"http://download.tensorflow.org/example_images/flower_photos.tgz\"\n",
    "FLOWERS_PATH = os.path.join(\"datasets\", \"flowers\")\n",
    "\n",
    "def fetch_flowers(url=FLOWERS_URL, path=FLOWERS_PATH):\n",
    "    if os.path.exists(FLOWERS_PATH):\n",
    "        return\n",
    "    os.makedirs(path, exist_ok=True)\n",
    "    tgz_path = os.path.join(path, \"flower_photos.tgz\")\n",
    "    urllib.request.urlretrieve(url, tgz_path, reporthook=download_progress)\n",
    "    flowers_tgz = tarfile.open(tgz_path)\n",
    "    flowers_tgz.extractall(path=path)\n",
    "    flowers_tgz.close()\n",
    "    os.remove(tgz_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "fetch_flowers()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`flower_photos` 디렉토리의 서브 디렉토리마다 해당 클래스의 이미지가 들어 있습니다. 클래스 목록을 확인해 보겠습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "flowers_root_path = os.path.join(FLOWERS_PATH, \"flower_photos\")\n",
    "flower_classes = sorted([dirname for dirname in os.listdir(flowers_root_path)\n",
    "                  if os.path.isdir(os.path.join(flowers_root_path, dirname))])\n",
    "flower_classes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "클래스별로 모든 이미지 파일의 경로를 리스트로 만듭니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import defaultdict\n",
    "\n",
    "image_paths = defaultdict(list)\n",
    "\n",
    "for flower_class in flower_classes:\n",
    "    image_dir = os.path.join(flowers_root_path, flower_class)\n",
    "    for filepath in os.listdir(image_dir):\n",
    "        if filepath.endswith(\".jpg\"):\n",
    "            image_paths[flower_class].append(os.path.join(image_dir, filepath))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "실행할 때마다 결과를 일정하게 만들기 위해 이미지 경로를 소팅합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "for paths in image_paths.values():\n",
    "    paths.sort()    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "클래스별로 처음 몇 개 이미지를 확인해 보겠습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "클래스: daisy\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 257936 characters]\n",
      "text/plain": [
       "<Figure size 720x360 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "클래스: dandelion\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 215040 characters]\n",
      "text/plain": [
       "<Figure size 720x360 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "클래스: roses\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 251904 characters]\n",
      "text/plain": [
       "<Figure size 720x360 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "클래스: sunflowers\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 295084 characters]\n",
      "text/plain": [
       "<Figure size 720x360 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "클래스: tulips\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 303896 characters]\n",
      "text/plain": [
       "<Figure size 720x360 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.image as mpimg\n",
    "\n",
    "n_examples_per_class = 2\n",
    "\n",
    "for flower_class in flower_classes:\n",
    "    print(\"클래스:\", flower_class)\n",
    "    plt.figure(figsize=(10,5))\n",
    "    for index, example_image_path in enumerate(image_paths[flower_class][:n_examples_per_class]):\n",
    "        example_image = mpimg.imread(example_image_path)[:, :, :channels]\n",
    "        plt.subplot(100 + n_examples_per_class * 10 + index + 1)\n",
    "        plt.title(\"{}x{}\".format(example_image.shape[1], example_image.shape[0]))\n",
    "        plt.imshow(example_image)\n",
    "        plt.axis(\"off\")\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이미지 크기가 서로 다르고 어떤 것은 어려워 보입니다(가령, 두 번째 튤립 이미지)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9.2.\n",
    "_문제: 전처리 단계에서 이미지 크기를 줄이고 잘라서 299 × 299 크기로 만들고 무작위성을 추가하여 데이터 증식을 하세요._"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "먼저 넘파이와 사이파이를 사용해 구현해 보겠습니다:\n",
    "\n",
    "* 넘파이 슬라이싱을 사용해 이미지를 자릅니다.\n",
    "* 넘파이 `fliplr()` 함수를 사용해 이미지를 수평으로 반전합니다(50% 확률로).\n",
    "* 사이파이 `imresize()` 함수로 확대합니다.\n",
    "  * `imresize()` 함수는 파이썬 이미지 라이브러리(PIL)을 사용합니다.\n",
    "  \n",
    "회전 등과 같은 다른 이미지 조작 함수는 [사이파이 문서](https://docs.scipy.org/doc/scipy/reference/ndimage.html)나 [이 페이지](http://www.scipy-lectures.org/advanced/image_processing/)를 참고하세요."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.misc import imresize\n",
    "\n",
    "def prepare_image(image, target_width = 299, target_height = 299, max_zoom = 0.2):\n",
    "    \"\"\"데이터 증식을 위해 이미지를 랜덤하게 자르거나 확대합니다.\"\"\"\n",
    "\n",
    "    # 먼저 이미지에서 타깃 크기에 맞는 최대 비율의 네모 상자 크기를 찾습니다.\n",
    "    height = image.shape[0]\n",
    "    width = image.shape[1]\n",
    "    image_ratio = width / height\n",
    "    target_image_ratio = target_width / target_height\n",
    "    crop_vertically = image_ratio < target_image_ratio\n",
    "    crop_width = width if crop_vertically else int(height * target_image_ratio)\n",
    "    crop_height = int(width / target_image_ratio) if crop_vertically else height\n",
    "        \n",
    "    # 이 상자의 크기를 랜덤한 비율로 줄입니다(1.0에서 1.0+`max_zoom` 사이의 난수로 나눕니다).\n",
    "    resize_factor = np.random.rand() * max_zoom + 1.0\n",
    "    crop_width = int(crop_width / resize_factor)\n",
    "    crop_height = int(crop_height / resize_factor)\n",
    "    \n",
    "    # 다음 이 상자가 놓일 이미지의 위치를 랜덤하게 선택합니다.\n",
    "    x0 = np.random.randint(0, width - crop_width)\n",
    "    y0 = np.random.randint(0, height - crop_height)\n",
    "    x1 = x0 + crop_width\n",
    "    y1 = y0 + crop_height\n",
    "    \n",
    "    # 이제 이미지를 자릅니다.\n",
    "    image = image[y0:y1, x0:x1]\n",
    "\n",
    "    # 50% 확률로 이미지를 수평으로 반전시킵니다.\n",
    "    if np.random.rand() < 0.5:\n",
    "        image = np.fliplr(image)\n",
    "\n",
    "    # 타깃 크기에 맞도록 이미지 크기를 변경합니다.\n",
    "    image = imresize(image, (target_width, target_height))\n",
    "    \n",
    "    # 마지막으로 컬러 값이 0.0에서부터 1.0 사이의 32비트 값이 되도록 만듭니다.\n",
    "    return image.astype(np.float32) / 255"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "노트: 테스트할 때에는 전처리 단계가 가능한 간단해야 합니다. 신경망에 이미지를 주입하기 위해 꼭 필요한 것만 수행합니다. 위 함수에 `training` 매개변수를 추가해서 `False`일 때는 전처리가 기본적인 것만 수행하도록 만들 수 있습니다(즉, 이미지 반전을 제외하고 이미지 중앙 부분만 자릅니다)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "결과 이미지를 확인해 보겠습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 311556 characters]\n",
      "text/plain": [
       "<Figure size 432x576 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.figure(figsize=(6, 8))\n",
    "plt.imshow(example_image)\n",
    "plt.title(\"{}x{}\".format(example_image.shape[1], example_image.shape[0]))\n",
    "plt.axis(\"off\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "위 함수를 적용하면:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/ipykernel_launcher.py:35: DeprecationWarning: `imresize` is deprecated!\n",
      "`imresize` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.\n",
      "Use ``skimage.transform.resize`` instead.\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 391628 characters]\n",
      "text/plain": [
       "<Figure size 576x576 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "prepared_image = prepare_image(example_image)\n",
    "\n",
    "plt.figure(figsize=(8, 8))\n",
    "plt.imshow(prepared_image)\n",
    "plt.title(\"{}x{}\".format(prepared_image.shape[1], prepared_image.shape[0]))\n",
    "plt.axis(\"off\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "같은 이미지를 사용해 랜덤하게 생성된 몇 개를 더 확인해 보겠습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/ipykernel_launcher.py:35: DeprecationWarning: `imresize` is deprecated!\n",
      "`imresize` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.\n",
      "Use ``skimage.transform.resize`` instead.\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 942752 characters]\n",
      "text/plain": [
       "<Figure size 1008x576 with 6 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "rows, cols = 2, 3\n",
    "\n",
    "plt.figure(figsize=(14, 8))\n",
    "for row in range(rows):\n",
    "    for col in range(cols):\n",
    "        prepared_image = prepare_image(example_image)\n",
    "        plt.subplot(rows, cols, row * cols + col + 1)\n",
    "        plt.title(\"{}x{}\".format(prepared_image.shape[1], prepared_image.shape[0]))\n",
    "        plt.imshow(prepared_image)\n",
    "        plt.axis(\"off\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "좋네요!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "또 다른 방법으로는 텐서플로의 `tf.image` 모듈에 있는 함수(전체 함수는 [API 문서](https://www.tensorflow.org/api_docs/python/)를 참고하세요)를 사용해 직접 이미지 전처리 단계를 구현하는 것도 가능합니다. 이 함수는 위와 매우 비슷하지만 실제로 이미지 변환을 수행하지 않고 그래프가 실행될 때 변환을 할 수 있는 텐서플로 연산을 만든다는 것이 다릅니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "def prepare_image_with_tensorflow(image, target_width = 299, target_height = 299, max_zoom = 0.2):\n",
    "    \"\"\"데이터 증식을 위해 이미지를 랜덤하게 자르거나 확대합니다.\"\"\"\n",
    "\n",
    "    # 먼저 이미지에서 타깃 크기에 맞는 최대 비율의 네모 상자 크기를 찾습니다.\n",
    "    image_shape = tf.cast(tf.shape(image), tf.float32)\n",
    "    height = image_shape[0]\n",
    "    width = image_shape[1]\n",
    "    image_ratio = width / height\n",
    "    target_image_ratio = target_width / target_height\n",
    "    crop_vertically = image_ratio < target_image_ratio\n",
    "    crop_width = tf.cond(crop_vertically,\n",
    "                         lambda: width,\n",
    "                         lambda: height * target_image_ratio)\n",
    "    crop_height = tf.cond(crop_vertically,\n",
    "                          lambda: width / target_image_ratio,\n",
    "                          lambda: height)\n",
    "\n",
    "    # 이 상자의 크기를 랜덤한 비율로 줄입니다(1.0에서 1.0+`max_zoom` 사이의 난수로 나눕니다).\n",
    "    resize_factor = tf.random_uniform(shape=[], minval=1.0, maxval=1.0 + max_zoom)\n",
    "    crop_width = tf.cast(crop_width / resize_factor, tf.int32)\n",
    "    crop_height = tf.cast(crop_height / resize_factor, tf.int32)\n",
    "    box_size = tf.stack([crop_height, crop_width, 3])   # 3 = 채널 개수\n",
    "\n",
    "    # 다음 이 상자가 놓일 이미지의 위치를 랜덤하게 선택합니다.\n",
    "    image = tf.random_crop(image, box_size)\n",
    "\n",
    "    # 50% 확률로 이미지를 수평으로 반전시킵니다.\n",
    "    image = tf.image.random_flip_left_right(image)\n",
    "\n",
    "    # resize_bilinear 함수는 4D 텐서를 기대하기 때문에 (이미지 배치) 첫 번째 차원을 추가해야 합니다.\n",
    "    image_batch = tf.expand_dims(image, 0)\n",
    "\n",
    "    # 타깃 크기에 맞도록 이미지 크기를 변경합니다. 이 함수는 float32 텐서를 반환합니다.\n",
    "    image_batch = tf.image.resize_bilinear(image_batch, [target_height, target_width])\n",
    "    image = image_batch[0] / 255  # 이미지 하나를 선택하고 컬러 값이 0.0에서부터 1.0 사이의 32비트 값이 되도록 만듭니다.\n",
    "    return image"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이 함수를 테스트해 보죠!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 350504 characters]\n",
      "text/plain": [
       "<Figure size 432x432 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "reset_graph()\n",
    "\n",
    "input_image = tf.placeholder(tf.uint8, shape=[None, None, 3])\n",
    "prepared_image_op = prepare_image_with_tensorflow(input_image)\n",
    "\n",
    "with tf.Session():\n",
    "    prepared_image = prepared_image_op.eval(feed_dict={input_image: example_image})\n",
    "    \n",
    "plt.figure(figsize=(6, 6))\n",
    "plt.imshow(prepared_image)\n",
    "plt.title(\"{}x{}\".format(prepared_image.shape[1], prepared_image.shape[0]))\n",
    "plt.axis(\"off\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "아주 잘 되네요!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9.3.\n",
    "_문제: 이전 연습문제의 미리 훈련된 Inception v3 모델을 사용해 병목층(즉, 출력층 직전의 마지막 층)까지의 모든 층을 동결하고, 이 분류 문제에 맞는 적절한 출력 개수를 가진 출력층으로 바꾸세요(예를 들어 꽃 데이터셋은 다섯 개의 상호 배타적인 클래스를 가지고 있으므로 출력층은 다섯 개의 뉴런을 가져야 하고 소프트맥스 활성화 함수를 사용해야 합니다)._"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "inception v3 그래프를 다시 불러오겠습니다. 이번에는 텐서플로가 이 신경망을 훈련할지 말지 알려주기 위해 `training` 플레이스홀더를 사용합니다(드롭아웃과 배치 정규화 같은 연산을 위해서)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "from tensorflow.contrib.slim.nets import inception\n",
    "import tensorflow.contrib.slim as slim\n",
    "\n",
    "reset_graph()\n",
    "\n",
    "X = tf.placeholder(tf.float32, shape=[None, height, width, channels], name=\"X\")\n",
    "training = tf.placeholder_with_default(False, shape=[])\n",
    "with slim.arg_scope(inception.inception_v3_arg_scope()):\n",
    "    logits, end_points = inception.inception_v3(X, num_classes=1001, is_training=training)\n",
    "\n",
    "inception_saver = tf.train.Saver()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "그래프에서 새로운 출력층을 추가할 지점을 찾아야 합니다. 이는 현재 출력층 직전의 층입니다. 이 층을 찾는 한가지 방법은 출력층의 입력을 조회하는 것입니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor 'InceptionV3/Logits/Conv2d_1c_1x1/BiasAdd:0' shape=(?, 1, 1, 1001) dtype=float32>"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "logits.op.inputs[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "아 이건 출력층의 일부입니다(편향 덧셈). 그래프를 거꾸로 계속 따라가 보겠습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor 'InceptionV3/Logits/Conv2d_1c_1x1/Conv2D:0' shape=(?, 1, 1, 1001) dtype=float32>"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "logits.op.inputs[0].op.inputs[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이것도 출력층의 일부입니다. 인셉션 층의 마지막 층입니다(텐서보드에서 시각화해 보면 확실히 알 수 있습니다). 계속해서 거꾸로 따라가 봅니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor 'InceptionV3/Logits/Dropout_1b/cond/Merge:0' shape=(?, 1, 1, 2048) dtype=float32>"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "logits.op.inputs[0].op.inputs[0].op.inputs[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "아하! 이거네요. 드롭아웃 층의 출력입니다. 이 층이 Inception v3 네트워크에 있는 출력층 직전의 마지막 층입니다. 여기에 새로운 층을 쌓아야 합니다. 사실 이 층을 찾는 더 간단한 방법이 있는데 `inception_v3()` 함수는 각 층의 종료 지점을 딕셔너리로 리턴해줍니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'AuxLogits': <tf.Tensor 'InceptionV3/AuxLogits/SpatialSqueeze:0' shape=(?, 1001) dtype=float32>,\n",
       " 'Conv2d_1a_3x3': <tf.Tensor 'InceptionV3/InceptionV3/Conv2d_1a_3x3/Relu:0' shape=(?, 149, 149, 32) dtype=float32>,\n",
       " 'Conv2d_2a_3x3': <tf.Tensor 'InceptionV3/InceptionV3/Conv2d_2a_3x3/Relu:0' shape=(?, 147, 147, 32) dtype=float32>,\n",
       " 'Conv2d_2b_3x3': <tf.Tensor 'InceptionV3/InceptionV3/Conv2d_2b_3x3/Relu:0' shape=(?, 147, 147, 64) dtype=float32>,\n",
       " 'Conv2d_3b_1x1': <tf.Tensor 'InceptionV3/InceptionV3/Conv2d_3b_1x1/Relu:0' shape=(?, 73, 73, 80) dtype=float32>,\n",
       " 'Conv2d_4a_3x3': <tf.Tensor 'InceptionV3/InceptionV3/Conv2d_4a_3x3/Relu:0' shape=(?, 71, 71, 192) dtype=float32>,\n",
       " 'Logits': <tf.Tensor 'InceptionV3/Logits/SpatialSqueeze:0' shape=(?, 1001) dtype=float32>,\n",
       " 'MaxPool_3a_3x3': <tf.Tensor 'InceptionV3/InceptionV3/MaxPool_3a_3x3/MaxPool:0' shape=(?, 73, 73, 64) dtype=float32>,\n",
       " 'MaxPool_5a_3x3': <tf.Tensor 'InceptionV3/InceptionV3/MaxPool_5a_3x3/MaxPool:0' shape=(?, 35, 35, 192) dtype=float32>,\n",
       " 'Mixed_5b': <tf.Tensor 'InceptionV3/InceptionV3/Mixed_5b/concat:0' shape=(?, 35, 35, 256) dtype=float32>,\n",
       " 'Mixed_5c': <tf.Tensor 'InceptionV3/InceptionV3/Mixed_5c/concat:0' shape=(?, 35, 35, 288) dtype=float32>,\n",
       " 'Mixed_5d': <tf.Tensor 'InceptionV3/InceptionV3/Mixed_5d/concat:0' shape=(?, 35, 35, 288) dtype=float32>,\n",
       " 'Mixed_6a': <tf.Tensor 'InceptionV3/InceptionV3/Mixed_6a/concat:0' shape=(?, 17, 17, 768) dtype=float32>,\n",
       " 'Mixed_6b': <tf.Tensor 'InceptionV3/InceptionV3/Mixed_6b/concat:0' shape=(?, 17, 17, 768) dtype=float32>,\n",
       " 'Mixed_6c': <tf.Tensor 'InceptionV3/InceptionV3/Mixed_6c/concat:0' shape=(?, 17, 17, 768) dtype=float32>,\n",
       " 'Mixed_6d': <tf.Tensor 'InceptionV3/InceptionV3/Mixed_6d/concat:0' shape=(?, 17, 17, 768) dtype=float32>,\n",
       " 'Mixed_6e': <tf.Tensor 'InceptionV3/InceptionV3/Mixed_6e/concat:0' shape=(?, 17, 17, 768) dtype=float32>,\n",
       " 'Mixed_7a': <tf.Tensor 'InceptionV3/InceptionV3/Mixed_7a/concat:0' shape=(?, 8, 8, 1280) dtype=float32>,\n",
       " 'Mixed_7b': <tf.Tensor 'InceptionV3/InceptionV3/Mixed_7b/concat:0' shape=(?, 8, 8, 2048) dtype=float32>,\n",
       " 'Mixed_7c': <tf.Tensor 'InceptionV3/InceptionV3/Mixed_7c/concat:0' shape=(?, 8, 8, 2048) dtype=float32>,\n",
       " 'PreLogits': <tf.Tensor 'InceptionV3/Logits/Dropout_1b/cond/Merge:0' shape=(?, 1, 1, 2048) dtype=float32>,\n",
       " 'Predictions': <tf.Tensor 'InceptionV3/Predictions/Reshape_1:0' shape=(?, 1001) dtype=float32>}"
      ]
     },
     "execution_count": 59,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "end_points"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "여기에서 볼 수 있듯이 `\"PreLogits\"`가 정확히 우리가 원하는 곳입니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor 'InceptionV3/Logits/Dropout_1b/cond/Merge:0' shape=(?, 1, 1, 2048) dtype=float32>"
      ]
     },
     "execution_count": 60,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "end_points[\"PreLogits\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`tf.squeeze()` 함수를 사용해 두 번째, 세 번째 차원을 제거합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [],
   "source": [
    "prelogits = tf.squeeze(end_points[\"PreLogits\"], axis=[1, 2])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "그런 다음 이 층 위에 최종 완전 연결층을 추가합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_outputs = len(flower_classes)\n",
    "\n",
    "with tf.name_scope(\"new_output_layer\"):\n",
    "    flower_logits = tf.layers.dense(prelogits, n_outputs, name=\"flower_logits\")\n",
    "    Y_proba = tf.nn.softmax(flower_logits, name=\"Y_proba\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "마지막으로 필요한 연산을 추가합니다:\n",
    "\n",
    "* 타깃(`y`)을 위한 플레이스홀더\n",
    "* 분류 작업에 사용되는 크로스엔트로피 손실 함수\n",
    "* 비용 함수를 최소화시키는 훈련 연산을 만드는 옵티마이저\n",
    "* 모델의 정확도를 측정할 몇 개의 연산\n",
    "* 마지막으로 초기화 연산와 `Saver` 객체\n",
    "\n",
    "한 가지 중요한 점이 있습니다. 출력층만 훈련시켜야하므로(다른 모든 층은 동결입니다) 옵티마이저의 `minimize()` 메서드에 훈련시킬 변수 목록을 전달해야 합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [],
   "source": [
    "y = tf.placeholder(tf.int32, shape=[None])\n",
    "\n",
    "with tf.name_scope(\"train\"):\n",
    "    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=flower_logits, labels=y)\n",
    "    loss = tf.reduce_mean(xentropy)\n",
    "    optimizer = tf.train.AdamOptimizer()\n",
    "    flower_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope=\"flower_logits\")\n",
    "    training_op = optimizer.minimize(loss, var_list=flower_vars)\n",
    "\n",
    "with tf.name_scope(\"eval\"):\n",
    "    correct = tf.nn.in_top_k(flower_logits, y, 1)\n",
    "    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))\n",
    "\n",
    "with tf.name_scope(\"init_and_save\"):\n",
    "    init = tf.global_variables_initializer()\n",
    "    saver = tf.train.Saver() "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['flower_logits/kernel:0', 'flower_logits/bias:0']"
      ]
     },
     "execution_count": 64,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[v.name for v in flower_vars]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "새로운 출력층을 추가하기 전에 `inceptioin_saver`를 만든 것을 주목하세요. 이를 사용해 이전에 훈련된 모델 상태를 복원하는데 새로운 변수를 복원하려 시도하는 것을 원치 않습니다(새로운 변수를 알지 못하기 때문에 실패할 것입니다). 두 번째 `saver`는 사전에 학습된 변수와 새로운 변소를 포함한 최종 모델을 저장하는데 사용됩니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9.4.\n",
    "_문제: 준비한 데이터셋을 훈련 세트와 테스트 세트로 나누세요. 훈련 세트에서 모델을 훈련시키고 테스트 세트에서 모델을 평가해보세요._"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "먼저 클래스를 문자열대신 숫자로 표현합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'daisy': 0, 'dandelion': 1, 'roses': 2, 'sunflowers': 3, 'tulips': 4}"
      ]
     },
     "execution_count": 65,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "flower_class_ids = {flower_class: index for index, flower_class in enumerate(flower_classes)}\n",
    "flower_class_ids"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "파일 경로/클래스 쌍으로 리스트가 구성되어 있으면 데이터를 섞기가 용이합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [],
   "source": [
    "flower_paths_and_classes = []\n",
    "for flower_class, paths in image_paths.items():\n",
    "    for path in paths:\n",
    "        flower_paths_and_classes.append((path, flower_class_ids[flower_class]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "다음 데이터셋을 섞고 훈련 세트와 테스트 세트로 나눕니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_ratio = 0.2\n",
    "train_size = int(len(flower_paths_and_classes) * (1 - test_ratio))\n",
    "\n",
    "np.random.shuffle(flower_paths_and_classes)\n",
    "\n",
    "flower_paths_and_classes_train = flower_paths_and_classes[:train_size]\n",
    "flower_paths_and_classes_test = flower_paths_and_classes[train_size:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "훈련 세트에서 처음 3개의 샘플을 확인해 보죠:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('datasets/flowers/flower_photos/daisy/19834392829_7d697871f6.jpg', 0),\n",
       " ('datasets/flowers/flower_photos/roses/7345657862_689366e79a.jpg', 2),\n",
       " ('datasets/flowers/flower_photos/dandelion/7719263062_3c8a307a5d.jpg', 1)]"
      ]
     },
     "execution_count": 68,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "flower_paths_and_classes_train[:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "그다음 이미지를 전처리하는 함수를 만듭니다. 이 함수는 테스트 세트를 전처리할 때도 사용하고 훈련할 때는 배치를 만들 수도 있습니다. 여기서는 간단하게 넘파이/사이파이 구현을 사용하겠습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {},
   "outputs": [],
   "source": [
    "from random import sample\n",
    "\n",
    "def prepare_batch(flower_paths_and_classes, batch_size):\n",
    "    batch_paths_and_classes = sample(flower_paths_and_classes, batch_size)\n",
    "    images = [mpimg.imread(path)[:, :, :channels] for path, labels in batch_paths_and_classes]\n",
    "    prepared_images = [prepare_image(image) for image in images]\n",
    "    X_batch = 2 * np.stack(prepared_images) - 1 # Inception expects colors ranging from -1 to 1\n",
    "    y_batch = np.array([labels for path, labels in batch_paths_and_classes], dtype=np.int32)\n",
    "    return X_batch, y_batch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/ipykernel_launcher.py:35: DeprecationWarning: `imresize` is deprecated!\n",
      "`imresize` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.\n",
      "Use ``skimage.transform.resize`` instead.\n"
     ]
    }
   ],
   "source": [
    "X_batch, y_batch = prepare_batch(flower_paths_and_classes_train, batch_size=4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(4, 299, 299, 3)"
      ]
     },
     "execution_count": 71,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_batch.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "dtype('float32')"
      ]
     },
     "execution_count": 72,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_batch.dtype"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(4,)"
      ]
     },
     "execution_count": 73,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y_batch.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "dtype('int32')"
      ]
     },
     "execution_count": 74,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y_batch.dtype"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "잘 된 것 같습니다. 이제 이 함수를 사용해 테스트 세트를 준비해 보죠:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/ipykernel_launcher.py:35: DeprecationWarning: `imresize` is deprecated!\n",
      "`imresize` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.\n",
      "Use ``skimage.transform.resize`` instead.\n"
     ]
    }
   ],
   "source": [
    "X_test, y_test = prepare_batch(flower_paths_and_classes_test, batch_size=len(flower_paths_and_classes_test))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(734, 299, 299, 3)"
      ]
     },
     "execution_count": 76,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_test.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "같은 방식으로 테스트 세트를 준비할 수 있지만 이미지마다 하나의 데이터만 증식합니다. 대신 훈련하는 동안 그때 그때 훈련 배치를 생성하는 것이 더 좋습니다. 그래야 이미지마다 변종 데이터가 많아 데이터 증식의 효과를 누릴 수 있습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이제 신경망을 훈련시킬 준비를 마쳤습니다(조금 더 정확하게는 출력층만 추가했고 다른 모든 층은 동결되었습니다). 이 훈련은 매우 오랜 시간이 걸립니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Restoring parameters from datasets/inception/inception_v3.ckpt\n",
      "에포크 0."
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/haesun/anaconda3/envs/handson-ml/lib/python3.5/site-packages/ipykernel_launcher.py:35: DeprecationWarning: `imresize` is deprecated!\n",
      "`imresize` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.\n",
      "Use ``skimage.transform.resize`` instead.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "........................................................................  훈련 정확도: 0.575\n",
      "에포크 1.........................................................................  훈련 정확도: 0.525\n",
      "에포크 2.........................................................................  훈련 정확도: 0.65\n",
      "에포크 3.........................................................................  훈련 정확도: 0.675\n",
      "에포크 4.........................................................................  훈련 정확도: 0.55\n",
      "에포크 5.........................................................................  훈련 정확도: 0.7\n",
      "에포크 6.........................................................................  훈련 정확도: 0.575\n",
      "에포크 7.........................................................................  훈련 정확도: 0.775\n",
      "에포크 8.........................................................................  훈련 정확도: 0.6\n",
      "에포크 9.........................................................................  훈련 정확도: 0.875\n"
     ]
    }
   ],
   "source": [
    "n_epochs = 10\n",
    "batch_size = 40\n",
    "n_iterations_per_epoch = len(flower_paths_and_classes_train) // batch_size\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    init.run()\n",
    "    inception_saver.restore(sess, INCEPTION_V3_CHECKPOINT_PATH)\n",
    "\n",
    "    for epoch in range(n_epochs):\n",
    "        print(\"에포크\", epoch, end=\"\")\n",
    "        for iteration in range(n_iterations_per_epoch):\n",
    "            print(\".\", end=\"\")\n",
    "            X_batch, y_batch = prepare_batch(flower_paths_and_classes_train, batch_size)\n",
    "            sess.run(training_op, feed_dict={X: X_batch, y: y_batch, training: True})\n",
    "\n",
    "        acc_train = accuracy.eval(feed_dict={X: X_batch, y: y_batch})\n",
    "        print(\"  훈련 정확도:\", acc_train)\n",
    "\n",
    "        save_path = saver.save(sess, \"./my_flowers_model\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Restoring parameters from ./my_flowers_model\n",
      "테스트 세트에서 최종 정확도를 계산합니다 (시간이 좀 걸립니다)...\n",
      "테스트 정확도: 0.7084228\n"
     ]
    }
   ],
   "source": [
    "n_test_batches = 10\n",
    "X_test_batches = np.array_split(X_test, n_test_batches)\n",
    "y_test_batches = np.array_split(y_test, n_test_batches)\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    saver.restore(sess, \"./my_flowers_model\")\n",
    "\n",
    "    print(\"테스트 세트에서 최종 정확도를 계산합니다 (시간이 좀 걸립니다)...\")\n",
    "    acc_test = np.mean([\n",
    "        accuracy.eval(feed_dict={X: X_test_batch, y: y_test_batch})\n",
    "        for X_test_batch, y_test_batch in zip(X_test_batches, y_test_batches)])\n",
    "    print(\"테스트 정확도:\", acc_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "네 70.8% 정확도는 좋지 않습니다(사실 이는 나쁜 편입니다). 하지만 이는 출력층을 제외하고 모든 층을 동결하여 10번의 에포크만 실행한 것입니다. GPU를 가지고 있다면 더 오랫동안 훈련을 시켜보세요(가령, 조기 종료 기법을 사용해). 이미지 전처리 함수를 개선하여 이미지를 더 다양하게 변형시킬 수도 있습니다(가령, 명암이나 색조를 바꾸거나 이미지를 조금 회전시킵니다). 이렇게 하면 95% 정확도 이상을 달성할 수 있습니다. 조금 더 심각하게 파고들고 싶다면 96% 정확도를 달성한 [훌륭한 이 포스트](https://kwotsin.github.io/tech/2017/02/11/transfer-learning.html)에서 자세한 내용을 참고하세요."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 10.\n",
    "_문제: 텐서플로의 딥드림 설명서(https://goo.gl/4b2s6g )를 살펴보세요. CNN으로 학습한 패턴을 시각화하거나 딥러닝을 사용해 그림을 생성하는 재미있는 방법을 배울 수 있습니다._"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "노트북을 다운로드하고 설명을 따라하면 됩니다. 더 재미있게 만들려면 여러개의 이미지를 시리즈로 만들고 딥드림 알고리즘을 반복적으로 실행하면서 확대해 보세요. [ffmpeg](https://ffmpeg.org/)와 같은 도구를 사용하면 이런 이미지로 영상을 만들 수 있습니다. 예를 들어 [DeepDream video](https://www.youtube.com/watch?v=l6i_fDg30p0)는 제가 만든 것입니다. 보면 알 수 있지만 금방 호러물이 됩니다. ;-) 웹에서 쉽게 (훨씬 예술적인) [비슷한 영상](https://www.youtube.com/results?search_query=+deepdream)을 많이 찾을 수 있습니다."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "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.5.5"
  },
  "nav_menu": {},
  "toc": {
   "navigate_menu": true,
   "number_sections": true,
   "sideBar": true,
   "threshold": 6,
   "toc_cell": false,
   "toc_section_display": "block",
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
