{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPython 3.5.5\n",
      "IPython 6.3.0\n",
      "\n",
      "numpy 1.14.2\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,scipy,matplotlib,tensorflow"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**9장 – 텐서플로 시작하기**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_이 노트북은 9장에 있는 모든 샘플 코드와 연습문제 해답을 가지고 있습니다._"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 설정"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "파이썬 2와 3을 모두 지원합니다. 공통 모듈을 임포트하고 맷플롯립 그림이 노트북 안에 포함되도록 설정하고 생성한 그림을 저장하기 위한 함수를 준비합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "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 = \"tensorflow\"\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": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "\n",
    "reset_graph()\n",
    "\n",
    "x = tf.Variable(3, name=\"x\")\n",
    "y = tf.Variable(4, name=\"y\")\n",
    "f = x*x*y + y + 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor 'add_1:0' shape=() dtype=int32>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "42\n"
     ]
    }
   ],
   "source": [
    "sess = tf.Session()\n",
    "sess.run(x.initializer)\n",
    "sess.run(y.initializer)\n",
    "result = sess.run(f)\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "sess.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "with tf.Session() as sess:\n",
    "    x.initializer.run()\n",
    "    y.initializer.run()\n",
    "    result = f.eval()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "42"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "init = tf.global_variables_initializer()\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    init.run()\n",
    "    result = f.eval()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "42"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "init = tf.global_variables_initializer()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "42\n"
     ]
    }
   ],
   "source": [
    "sess = tf.InteractiveSession()\n",
    "init.run()\n",
    "result = f.eval()\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "sess.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "42"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "result"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 그래프 다루기"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "reset_graph()\n",
    "\n",
    "x1 = tf.Variable(1)\n",
    "x1.graph is tf.get_default_graph()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph = tf.Graph()\n",
    "with graph.as_default():\n",
    "    x2 = tf.Variable(2)\n",
    "\n",
    "x2.graph is graph"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x2.graph is tf.get_default_graph()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10\n",
      "15\n"
     ]
    }
   ],
   "source": [
    "w = tf.constant(3)\n",
    "x = w + 2\n",
    "y = x + 5\n",
    "z = x * 3\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    print(y.eval())  # 10\n",
    "    print(z.eval())  # 15"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10\n",
      "15\n"
     ]
    }
   ],
   "source": [
    "with tf.Session() as sess:\n",
    "    y_val, z_val = sess.run([y, z])\n",
    "    print(y_val)  # 10\n",
    "    print(z_val)  # 15"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 선형 회귀"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 정규방정식을 사용해서"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "reset_graph()\n",
    "\n",
    "housing = fetch_california_housing()\n",
    "m, n = housing.data.shape\n",
    "housing_data_plus_bias = np.c_[np.ones((m, 1)), housing.data]\n",
    "\n",
    "X = tf.constant(housing_data_plus_bias, dtype=tf.float32, name=\"X\")\n",
    "y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name=\"y\")\n",
    "XT = tf.transpose(X)\n",
    "theta = tf.matmul(tf.matmul(tf.matrix_inverse(tf.matmul(XT, X)), XT), y)\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    theta_value = theta.eval()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[-3.7185181e+01],\n",
       "       [ 4.3633747e-01],\n",
       "       [ 9.3952334e-03],\n",
       "       [-1.0711310e-01],\n",
       "       [ 6.4479220e-01],\n",
       "       [-4.0338000e-06],\n",
       "       [-3.7813708e-03],\n",
       "       [-4.2348403e-01],\n",
       "       [-4.3721911e-01]], dtype=float32)"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "theta_value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "넘파이와 비교"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[-3.69419202e+01]\n",
      " [ 4.36693293e-01]\n",
      " [ 9.43577803e-03]\n",
      " [-1.07322041e-01]\n",
      " [ 6.45065694e-01]\n",
      " [-3.97638942e-06]\n",
      " [-3.78654265e-03]\n",
      " [-4.21314378e-01]\n",
      " [-4.34513755e-01]]\n"
     ]
    }
   ],
   "source": [
    "X = housing_data_plus_bias\n",
    "y = housing.target.reshape(-1, 1)\n",
    "theta_numpy = np.linalg.inv(X.T.dot(X)).dot(X.T).dot(y)\n",
    "\n",
    "print(theta_numpy)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "사이킷런과 비교"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[-3.69419202e+01]\n",
      " [ 4.36693293e-01]\n",
      " [ 9.43577803e-03]\n",
      " [-1.07322041e-01]\n",
      " [ 6.45065694e-01]\n",
      " [-3.97638942e-06]\n",
      " [-3.78654265e-03]\n",
      " [-4.21314378e-01]\n",
      " [-4.34513755e-01]]\n"
     ]
    }
   ],
   "source": [
    "from sklearn.linear_model import LinearRegression\n",
    "lin_reg = LinearRegression()\n",
    "lin_reg.fit(housing.data, housing.target.reshape(-1, 1))\n",
    "\n",
    "print(np.r_[lin_reg.intercept_.reshape(-1, 1), lin_reg.coef_.T])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 배치 경사 하강법을 사용해서"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "경사 하강법은 먼저 특성 벡터의 스케일을 조정해야 합니다. 텐서플로를 사용해 할 수 있지만 그냥 여기서는 사이킷런을 사용하겠습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "scaler = StandardScaler()\n",
    "scaled_housing_data = scaler.fit_transform(housing.data)\n",
    "scaled_housing_data_plus_bias = np.c_[np.ones((m, 1)), scaled_housing_data]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 1.00000000e+00  6.60969987e-17  5.50808322e-18  6.60969987e-17\n",
      " -1.06030602e-16 -1.10161664e-17  3.44255201e-18 -1.07958431e-15\n",
      " -8.52651283e-15]\n",
      "[ 0.38915536  0.36424355  0.5116157  ... -0.06612179 -0.06360587\n",
      "  0.01359031]\n",
      "0.11111111111111005\n",
      "(20640, 9)\n"
     ]
    }
   ],
   "source": [
    "print(scaled_housing_data_plus_bias.mean(axis=0))\n",
    "print(scaled_housing_data_plus_bias.mean(axis=1))\n",
    "print(scaled_housing_data_plus_bias.mean())\n",
    "print(scaled_housing_data_plus_bias.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 수동으로 그래디언트 계산하기"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "에포크 0 MSE = 9.161542\n",
      "에포크 100 MSE = 0.7145004\n",
      "에포크 200 MSE = 0.56670487\n",
      "에포크 300 MSE = 0.5555718\n",
      "에포크 400 MSE = 0.5488112\n",
      "에포크 500 MSE = 0.5436363\n",
      "에포크 600 MSE = 0.5396291\n",
      "에포크 700 MSE = 0.5365092\n",
      "에포크 800 MSE = 0.53406775\n",
      "에포크 900 MSE = 0.5321473\n"
     ]
    }
   ],
   "source": [
    "reset_graph()\n",
    "\n",
    "n_epochs = 1000\n",
    "learning_rate = 0.01\n",
    "\n",
    "X = tf.constant(scaled_housing_data_plus_bias, dtype=tf.float32, name=\"X\")\n",
    "y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name=\"y\")\n",
    "theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name=\"theta\")\n",
    "y_pred = tf.matmul(X, theta, name=\"predictions\")\n",
    "error = y_pred - y\n",
    "mse = tf.reduce_mean(tf.square(error), name=\"mse\")\n",
    "gradients = 2/m * tf.matmul(tf.transpose(X), error)\n",
    "training_op = tf.assign(theta, theta - learning_rate * gradients)\n",
    "\n",
    "init = tf.global_variables_initializer()\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    sess.run(init)\n",
    "\n",
    "    for epoch in range(n_epochs):\n",
    "        if epoch % 100 == 0:\n",
    "            print(\"에포크\", epoch, \"MSE =\", mse.eval())\n",
    "        sess.run(training_op)\n",
    "    \n",
    "    best_theta = theta.eval()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 2.0685523 ],\n",
       "       [ 0.8874027 ],\n",
       "       [ 0.14401656],\n",
       "       [-0.34770882],\n",
       "       [ 0.36178368],\n",
       "       [ 0.00393811],\n",
       "       [-0.04269556],\n",
       "       [-0.6614529 ],\n",
       "       [-0.6375279 ]], dtype=float32)"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "best_theta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 자동미분 사용하기"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`gradients = ...` 라인만 빼고 위와 동일합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "n_epochs = 1000\n",
    "learning_rate = 0.01\n",
    "\n",
    "X = tf.constant(scaled_housing_data_plus_bias, dtype=tf.float32, name=\"X\")\n",
    "y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name=\"y\")\n",
    "theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name=\"theta\")\n",
    "y_pred = tf.matmul(X, theta, name=\"predictions\")\n",
    "error = y_pred - y\n",
    "mse = tf.reduce_mean(tf.square(error), name=\"mse\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "gradients = tf.gradients(mse, [theta])[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "에포크 0 MSE = 9.161542\n",
      "에포크 100 MSE = 0.71450037\n",
      "에포크 200 MSE = 0.56670487\n",
      "에포크 300 MSE = 0.5555718\n",
      "에포크 400 MSE = 0.54881126\n",
      "에포크 500 MSE = 0.5436363\n",
      "에포크 600 MSE = 0.53962916\n",
      "에포크 700 MSE = 0.5365092\n",
      "에포크 800 MSE = 0.53406775\n",
      "에포크 900 MSE = 0.5321473\n",
      "best_theta:\n",
      "[[ 2.0685523 ]\n",
      " [ 0.8874027 ]\n",
      " [ 0.14401656]\n",
      " [-0.3477088 ]\n",
      " [ 0.36178365]\n",
      " [ 0.00393811]\n",
      " [-0.04269556]\n",
      " [-0.66145283]\n",
      " [-0.6375278 ]]\n"
     ]
    }
   ],
   "source": [
    "training_op = tf.assign(theta, theta - learning_rate * gradients)\n",
    "\n",
    "init = tf.global_variables_initializer()\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    sess.run(init)\n",
    "\n",
    "    for epoch in range(n_epochs):\n",
    "        if epoch % 100 == 0:\n",
    "            print(\"에포크\", epoch, \"MSE =\", mse.eval())\n",
    "        sess.run(training_op)\n",
    "    \n",
    "    best_theta = theta.eval()\n",
    "\n",
    "print(\"best_theta:\")\n",
    "print(best_theta)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`a`와 `b`에 대한 다음 함수의 편도함수를 어떻게 구할 수 있나요?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "def my_func(a, b):\n",
    "    z = 0\n",
    "    for i in range(100):\n",
    "        z = a * np.cos(z + i) + z * np.sin(b - i)\n",
    "    return z"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-0.21253923284754914"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_func(0.2, 0.3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "a = tf.Variable(0.2, name=\"a\")\n",
    "b = tf.Variable(0.3, name=\"b\")\n",
    "z = tf.constant(0.0, name=\"z0\")\n",
    "for i in range(100):\n",
    "    z = a * tf.cos(z + i) + z * tf.sin(b - i)\n",
    "\n",
    "grads = tf.gradients(z, [a, b])\n",
    "init = tf.global_variables_initializer()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$a=0.2$와 $b=0.3$일 때 함수 값을 계산하고 그 다음 $a$와 $b$에 대한 편미분을 구합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-0.21253741\n",
      "[-1.1388495, 0.19671397]\n"
     ]
    }
   ],
   "source": [
    "with tf.Session() as sess:\n",
    "    init.run()\n",
    "    print(z.eval())\n",
    "    print(sess.run(grads))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `GradientDescentOptimizer` 사용하기"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "n_epochs = 1000\n",
    "learning_rate = 0.01\n",
    "\n",
    "X = tf.constant(scaled_housing_data_plus_bias, dtype=tf.float32, name=\"X\")\n",
    "y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name=\"y\")\n",
    "theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name=\"theta\")\n",
    "y_pred = tf.matmul(X, theta, name=\"predictions\")\n",
    "error = y_pred - y\n",
    "mse = tf.reduce_mean(tf.square(error), name=\"mse\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)\n",
    "training_op = optimizer.minimize(mse)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "에포크 0 MSE = 9.161542\n",
      "에포크 100 MSE = 0.7145004\n",
      "에포크 200 MSE = 0.56670487\n",
      "에포크 300 MSE = 0.5555718\n",
      "에포크 400 MSE = 0.54881126\n",
      "에포크 500 MSE = 0.5436363\n",
      "에포크 600 MSE = 0.53962916\n",
      "에포크 700 MSE = 0.5365092\n",
      "에포크 800 MSE = 0.53406775\n",
      "에포크 900 MSE = 0.5321473\n",
      "best_theta:\n",
      "[[ 2.0685523 ]\n",
      " [ 0.8874027 ]\n",
      " [ 0.14401656]\n",
      " [-0.3477088 ]\n",
      " [ 0.36178365]\n",
      " [ 0.00393811]\n",
      " [-0.04269556]\n",
      " [-0.66145283]\n",
      " [-0.6375278 ]]\n"
     ]
    }
   ],
   "source": [
    "init = tf.global_variables_initializer()\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    sess.run(init)\n",
    "\n",
    "    for epoch in range(n_epochs):\n",
    "        if epoch % 100 == 0:\n",
    "            print(\"에포크\", epoch, \"MSE =\", mse.eval())\n",
    "        sess.run(training_op)\n",
    "    \n",
    "    best_theta = theta.eval()\n",
    "\n",
    "print(\"best_theta:\")\n",
    "print(best_theta)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 모멘텀 옵티마이저 사용하기"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "n_epochs = 1000\n",
    "learning_rate = 0.01\n",
    "\n",
    "X = tf.constant(scaled_housing_data_plus_bias, dtype=tf.float32, name=\"X\")\n",
    "y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name=\"y\")\n",
    "theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name=\"theta\")\n",
    "y_pred = tf.matmul(X, theta, name=\"predictions\")\n",
    "error = y_pred - y\n",
    "mse = tf.reduce_mean(tf.square(error), name=\"mse\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = tf.train.MomentumOptimizer(learning_rate=learning_rate,\n",
    "                                       momentum=0.9)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": [
    "training_op = optimizer.minimize(mse)\n",
    "\n",
    "init = tf.global_variables_initializer()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "best_theta:\n",
      "[[ 2.068558  ]\n",
      " [ 0.82962847]\n",
      " [ 0.11875335]\n",
      " [-0.26554456]\n",
      " [ 0.3057109 ]\n",
      " [-0.00450249]\n",
      " [-0.03932662]\n",
      " [-0.8998645 ]\n",
      " [-0.8705207 ]]\n"
     ]
    }
   ],
   "source": [
    "with tf.Session() as sess:\n",
    "    sess.run(init)\n",
    "\n",
    "    for epoch in range(n_epochs):\n",
    "        sess.run(training_op)\n",
    "    \n",
    "    best_theta = theta.eval()\n",
    "\n",
    "print(\"best_theta:\")\n",
    "print(best_theta)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 훈련 알고리즘에 데이터 주입하기"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 플레이스홀더 노드"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[6. 7. 8.]]\n"
     ]
    }
   ],
   "source": [
    "reset_graph()\n",
    "\n",
    "A = tf.placeholder(tf.float32, shape=(None, 3))\n",
    "B = A + 5\n",
    "with tf.Session() as sess:\n",
    "    B_val_1 = B.eval(feed_dict={A: [[1, 2, 3]]})\n",
    "    B_val_2 = B.eval(feed_dict={A: [[4, 5, 6], [7, 8, 9]]})\n",
    "\n",
    "print(B_val_1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 9. 10. 11.]\n",
      " [12. 13. 14.]]\n"
     ]
    }
   ],
   "source": [
    "print(B_val_2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 미니배치 경사 하강법"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_epochs = 1000\n",
    "learning_rate = 0.01"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "X = tf.placeholder(tf.float32, shape=(None, n + 1), name=\"X\")\n",
    "y = tf.placeholder(tf.float32, shape=(None, 1), name=\"y\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [],
   "source": [
    "theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name=\"theta\")\n",
    "y_pred = tf.matmul(X, theta, name=\"predictions\")\n",
    "error = y_pred - y\n",
    "mse = tf.reduce_mean(tf.square(error), name=\"mse\")\n",
    "optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)\n",
    "training_op = optimizer.minimize(mse)\n",
    "\n",
    "init = tf.global_variables_initializer()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_epochs = 10"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "batch_size = 100\n",
    "n_batches = int(np.ceil(m / batch_size))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [],
   "source": [
    "def fetch_batch(epoch, batch_index, batch_size):\n",
    "    np.random.seed(epoch * n_batches + batch_index)  # not shown in the book\n",
    "    indices = np.random.randint(m, size=batch_size)  # not shown\n",
    "    X_batch = scaled_housing_data_plus_bias[indices] # not shown\n",
    "    y_batch = housing.target.reshape(-1, 1)[indices] # not shown\n",
    "    return X_batch, y_batch\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    sess.run(init)\n",
    "\n",
    "    for epoch in range(n_epochs):\n",
    "        for batch_index in range(n_batches):\n",
    "            X_batch, y_batch = fetch_batch(epoch, batch_index, batch_size)\n",
    "            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})\n",
    "\n",
    "    best_theta = theta.eval()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 2.0703337 ],\n",
       "       [ 0.8637145 ],\n",
       "       [ 0.12255152],\n",
       "       [-0.31211877],\n",
       "       [ 0.38510376],\n",
       "       [ 0.00434168],\n",
       "       [-0.0123295 ],\n",
       "       [-0.83376896],\n",
       "       [-0.8030471 ]], dtype=float32)"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "best_theta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 모델의 저장과 복원"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "에포크 0 MSE = 9.161542\n",
      "에포크 100 MSE = 0.7145004\n",
      "에포크 200 MSE = 0.56670487\n",
      "에포크 300 MSE = 0.5555718\n",
      "에포크 400 MSE = 0.54881126\n",
      "에포크 500 MSE = 0.5436363\n",
      "에포크 600 MSE = 0.53962916\n",
      "에포크 700 MSE = 0.5365092\n",
      "에포크 800 MSE = 0.53406775\n",
      "에포크 900 MSE = 0.5321473\n"
     ]
    }
   ],
   "source": [
    "reset_graph()\n",
    "\n",
    "n_epochs = 1000                                                                       # 책에는 없습니다.\n",
    "learning_rate = 0.01                                                                  # 책에는 없습니다.\n",
    "\n",
    "X = tf.constant(scaled_housing_data_plus_bias, dtype=tf.float32, name=\"X\")            # 책에는 없습니다.\n",
    "y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name=\"y\")            # 책에는 없습니다.\n",
    "theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name=\"theta\")\n",
    "y_pred = tf.matmul(X, theta, name=\"predictions\")                                      # 책에는 없습니다.\n",
    "error = y_pred - y                                                                    # 책에는 없습니다.\n",
    "mse = tf.reduce_mean(tf.square(error), name=\"mse\")                                    # 책에는 없습니다.\n",
    "optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)            # 책에는 없습니다.\n",
    "training_op = optimizer.minimize(mse)                                                 # 책에는 없습니다.\n",
    "\n",
    "init = tf.global_variables_initializer()\n",
    "saver = tf.train.Saver()\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    sess.run(init)\n",
    "\n",
    "    for epoch in range(n_epochs):\n",
    "        if epoch % 100 == 0:\n",
    "            print(\"에포크\", epoch, \"MSE =\", mse.eval())                                # 책에는 없습니다.\n",
    "            save_path = saver.save(sess, \"/tmp/my_model.ckpt\")\n",
    "        sess.run(training_op)\n",
    "    \n",
    "    best_theta = theta.eval()\n",
    "    save_path = saver.save(sess, \"/tmp/my_model_final.ckpt\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 2.0685523 ],\n",
       "       [ 0.8874027 ],\n",
       "       [ 0.14401656],\n",
       "       [-0.3477088 ],\n",
       "       [ 0.36178365],\n",
       "       [ 0.00393811],\n",
       "       [-0.04269556],\n",
       "       [-0.66145283],\n",
       "       [-0.6375278 ]], dtype=float32)"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "best_theta"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Restoring parameters from /tmp/my_model_final.ckpt\n"
     ]
    }
   ],
   "source": [
    "with tf.Session() as sess:\n",
    "    saver.restore(sess, \"/tmp/my_model_final.ckpt\")\n",
    "    best_theta_restored = theta.eval() # 책에는 없습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(best_theta, best_theta_restored)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`theta`를 `\"weights\"`와 같은 다른 이름으로 저장하고 복원하는 Saver 객체를 원할 경우엔:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "saver = tf.train.Saver({\"weights\": theta})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "기본적으로 Saver 객체는 `.meta` 확장자를 가진 두 번째 파일에 그래프 구조도 저장합니다. `tf.train.import_meta_graph()` 함수를 사용하여 그래프 구조를 복원할 수 있습니다. 이 함수는 저장된 그래프를 기본 그래프로 로드하고 상태(즉, 변수 값)를 복원할 수 있는 `Saver` 객체를 반환합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Restoring parameters from /tmp/my_model_final.ckpt\n"
     ]
    }
   ],
   "source": [
    "reset_graph()\n",
    "# 빈 그래프로 시작합니다\n",
    "\n",
    "saver = tf.train.import_meta_graph(\"/tmp/my_model_final.ckpt.meta\")  # 그래프 구조를 로드합니다.\n",
    "theta = tf.get_default_graph().get_tensor_by_name(\"theta:0\") # 책에는 없습니다.\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    saver.restore(sess, \"/tmp/my_model_final.ckpt\")  # 그래프 상태를 로드합니다.\n",
    "    best_theta_restored = theta.eval() # 책에는 없습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(best_theta, best_theta_restored)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이를 사용하면 그래프를 만든 파이썬 코드가 없이도 미리 훈련된 모델을 임포트할 수 있습니다. 모델을 저장하고 변경할 때도 매우 편리합니다. 이전에 저장된 모델을 구축한 코드의 버전을 찾지 않아도 로드할 수 있습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 그래프 시각화\n",
    "## 쥬피터에서"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import clear_output, Image, display, HTML\n",
    "\n",
    "def strip_consts(graph_def, max_const_size=32):\n",
    "    \"\"\"graph_def에서 큰 상수 값은 제외시킵니다.\"\"\"\n",
    "    strip_def = tf.GraphDef()\n",
    "    for n0 in graph_def.node:\n",
    "        n = strip_def.node.add() \n",
    "        n.MergeFrom(n0)\n",
    "        if n.op == 'Const':\n",
    "            tensor = n.attr['value'].tensor\n",
    "            size = len(tensor.tensor_content)\n",
    "            if size > max_const_size:\n",
    "                tensor.tensor_content = b\"<stripped %d bytes>\"%size\n",
    "    return strip_def\n",
    "\n",
    "def show_graph(graph_def, max_const_size=32):\n",
    "    \"\"\"텐서플로 그래프 나타내기\"\"\"\n",
    "    if hasattr(graph_def, 'as_graph_def'):\n",
    "        graph_def = graph_def.as_graph_def()\n",
    "    strip_def = strip_consts(graph_def, max_const_size=max_const_size)\n",
    "    code = \"\"\"\n",
    "        <script>\n",
    "          function load() {{\n",
    "            document.getElementById(\"{id}\").pbtxt = {data};\n",
    "          }}\n",
    "        </script>\n",
    "        <link rel=\"import\" href=\"https://tensorboard.appspot.com/tf-graph-basic.build.html\" onload=load()>\n",
    "        <div style=\"height:600px\">\n",
    "          <tf-graph-basic id=\"{id}\"></tf-graph-basic>\n",
    "        </div>\n",
    "    \"\"\".format(data=repr(str(strip_def)), id='graph'+str(np.random.rand()))\n",
    "\n",
    "    iframe = \"\"\"\n",
    "        <iframe seamless style=\"width:1200px;height:620px;border:0\" srcdoc=\"{}\"></iframe>\n",
    "    \"\"\".format(code.replace('\"', '&quot;'))\n",
    "    display(HTML(iframe))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "        <iframe seamless style=\"width:1200px;height:620px;border:0\" srcdoc=\"\n",
       "        <script>\n",
       "          function load() {\n",
       "            document.getElementById(&quot;graph0.3745401188473625&quot;).pbtxt = 'node {\\n  name: &quot;X&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_FLOAT\\n        tensor_shape {\\n          dim {\\n            size: 20640\\n          }\\n          dim {\\n            size: 9\\n          }\\n        }\\n        tensor_content: &quot;<stripped 743040 bytes>&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;y&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_FLOAT\\n        tensor_shape {\\n          dim {\\n            size: 20640\\n          }\\n          dim {\\n            size: 1\\n          }\\n        }\\n        tensor_content: &quot;<stripped 82560 bytes>&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;random_uniform/shape&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_INT32\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_INT32\\n        tensor_shape {\\n          dim {\\n            size: 2\\n          }\\n        }\\n        tensor_content: &quot;\\\\t\\\\000\\\\000\\\\000\\\\001\\\\000\\\\000\\\\000&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;random_uniform/min&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_FLOAT\\n        tensor_shape {\\n        }\\n        float_val: -1.0\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;random_uniform/max&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_FLOAT\\n        tensor_shape {\\n        }\\n        float_val: 1.0\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;random_uniform/RandomUniform&quot;\\n  op: &quot;RandomUniform&quot;\\n  input: &quot;random_uniform/shape&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_INT32\\n    }\\n  }\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;seed&quot;\\n    value {\\n      i: 42\\n    }\\n  }\\n  attr {\\n    key: &quot;seed2&quot;\\n    value {\\n      i: 42\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;random_uniform/sub&quot;\\n  op: &quot;Sub&quot;\\n  input: &quot;random_uniform/max&quot;\\n  input: &quot;random_uniform/min&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;random_uniform/mul&quot;\\n  op: &quot;Mul&quot;\\n  input: &quot;random_uniform/RandomUniform&quot;\\n  input: &quot;random_uniform/sub&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;random_uniform&quot;\\n  op: &quot;Add&quot;\\n  input: &quot;random_uniform/mul&quot;\\n  input: &quot;random_uniform/min&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;theta&quot;\\n  op: &quot;VariableV2&quot;\\n  attr {\\n    key: &quot;container&quot;\\n    value {\\n      s: &quot;&quot;\\n    }\\n  }\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;shape&quot;\\n    value {\\n      shape {\\n        dim {\\n          size: 9\\n        }\\n        dim {\\n          size: 1\\n        }\\n      }\\n    }\\n  }\\n  attr {\\n    key: &quot;shared_name&quot;\\n    value {\\n      s: &quot;&quot;\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;theta/Assign&quot;\\n  op: &quot;Assign&quot;\\n  input: &quot;theta&quot;\\n  input: &quot;random_uniform&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;_class&quot;\\n    value {\\n      list {\\n        s: &quot;loc:@theta&quot;\\n      }\\n    }\\n  }\\n  attr {\\n    key: &quot;use_locking&quot;\\n    value {\\n      b: true\\n    }\\n  }\\n  attr {\\n    key: &quot;validate_shape&quot;\\n    value {\\n      b: true\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;theta/read&quot;\\n  op: &quot;Identity&quot;\\n  input: &quot;theta&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;_class&quot;\\n    value {\\n      list {\\n        s: &quot;loc:@theta&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;predictions&quot;\\n  op: &quot;MatMul&quot;\\n  input: &quot;X&quot;\\n  input: &quot;theta/read&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;transpose_a&quot;\\n    value {\\n      b: false\\n    }\\n  }\\n  attr {\\n    key: &quot;transpose_b&quot;\\n    value {\\n      b: false\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;sub&quot;\\n  op: &quot;Sub&quot;\\n  input: &quot;predictions&quot;\\n  input: &quot;y&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;Square&quot;\\n  op: &quot;Square&quot;\\n  input: &quot;sub&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;Const&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_INT32\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_INT32\\n        tensor_shape {\\n          dim {\\n            size: 2\\n          }\\n        }\\n        tensor_content: &quot;\\\\000\\\\000\\\\000\\\\000\\\\001\\\\000\\\\000\\\\000&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;mse&quot;\\n  op: &quot;Mean&quot;\\n  input: &quot;Square&quot;\\n  input: &quot;Const&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;Tidx&quot;\\n    value {\\n      type: DT_INT32\\n    }\\n  }\\n  attr {\\n    key: &quot;keep_dims&quot;\\n    value {\\n      b: false\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/Shape&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_INT32\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_INT32\\n        tensor_shape {\\n          dim {\\n          }\\n        }\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/grad_ys_0&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_FLOAT\\n        tensor_shape {\\n        }\\n        float_val: 1.0\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/Fill&quot;\\n  op: &quot;Fill&quot;\\n  input: &quot;gradients/Shape&quot;\\n  input: &quot;gradients/grad_ys_0&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;index_type&quot;\\n    value {\\n      type: DT_INT32\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/mse_grad/Reshape/shape&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_INT32\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_INT32\\n        tensor_shape {\\n          dim {\\n            size: 2\\n          }\\n        }\\n        tensor_content: &quot;\\\\001\\\\000\\\\000\\\\000\\\\001\\\\000\\\\000\\\\000&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/mse_grad/Reshape&quot;\\n  op: &quot;Reshape&quot;\\n  input: &quot;gradients/Fill&quot;\\n  input: &quot;gradients/mse_grad/Reshape/shape&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;Tshape&quot;\\n    value {\\n      type: DT_INT32\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/mse_grad/Const&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_INT32\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_INT32\\n        tensor_shape {\\n          dim {\\n            size: 2\\n          }\\n        }\\n        tensor_content: &quot;\\\\240P\\\\000\\\\000\\\\001\\\\000\\\\000\\\\000&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/mse_grad/Tile&quot;\\n  op: &quot;Tile&quot;\\n  input: &quot;gradients/mse_grad/Reshape&quot;\\n  input: &quot;gradients/mse_grad/Const&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;Tmultiples&quot;\\n    value {\\n      type: DT_INT32\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/mse_grad/Const_1&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_FLOAT\\n        tensor_shape {\\n        }\\n        float_val: 20640.0\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/mse_grad/truediv&quot;\\n  op: &quot;RealDiv&quot;\\n  input: &quot;gradients/mse_grad/Tile&quot;\\n  input: &quot;gradients/mse_grad/Const_1&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/Square_grad/Const&quot;\\n  op: &quot;Const&quot;\\n  input: &quot;^gradients/mse_grad/truediv&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_FLOAT\\n        tensor_shape {\\n        }\\n        float_val: 2.0\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/Square_grad/Mul&quot;\\n  op: &quot;Mul&quot;\\n  input: &quot;sub&quot;\\n  input: &quot;gradients/Square_grad/Const&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/Square_grad/Mul_1&quot;\\n  op: &quot;Mul&quot;\\n  input: &quot;gradients/mse_grad/truediv&quot;\\n  input: &quot;gradients/Square_grad/Mul&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/sub_grad/Neg&quot;\\n  op: &quot;Neg&quot;\\n  input: &quot;gradients/Square_grad/Mul_1&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/sub_grad/tuple/group_deps&quot;\\n  op: &quot;NoOp&quot;\\n  input: &quot;^gradients/Square_grad/Mul_1&quot;\\n  input: &quot;^gradients/sub_grad/Neg&quot;\\n}\\nnode {\\n  name: &quot;gradients/sub_grad/tuple/control_dependency&quot;\\n  op: &quot;Identity&quot;\\n  input: &quot;gradients/Square_grad/Mul_1&quot;\\n  input: &quot;^gradients/sub_grad/tuple/group_deps&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;_class&quot;\\n    value {\\n      list {\\n        s: &quot;loc:@gradients/Square_grad/Mul_1&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/sub_grad/tuple/control_dependency_1&quot;\\n  op: &quot;Identity&quot;\\n  input: &quot;gradients/sub_grad/Neg&quot;\\n  input: &quot;^gradients/sub_grad/tuple/group_deps&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;_class&quot;\\n    value {\\n      list {\\n        s: &quot;loc:@gradients/sub_grad/Neg&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/predictions_grad/MatMul&quot;\\n  op: &quot;MatMul&quot;\\n  input: &quot;gradients/sub_grad/tuple/control_dependency&quot;\\n  input: &quot;theta/read&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;transpose_a&quot;\\n    value {\\n      b: false\\n    }\\n  }\\n  attr {\\n    key: &quot;transpose_b&quot;\\n    value {\\n      b: true\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/predictions_grad/MatMul_1&quot;\\n  op: &quot;MatMul&quot;\\n  input: &quot;X&quot;\\n  input: &quot;gradients/sub_grad/tuple/control_dependency&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;transpose_a&quot;\\n    value {\\n      b: true\\n    }\\n  }\\n  attr {\\n    key: &quot;transpose_b&quot;\\n    value {\\n      b: false\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/predictions_grad/tuple/group_deps&quot;\\n  op: &quot;NoOp&quot;\\n  input: &quot;^gradients/predictions_grad/MatMul&quot;\\n  input: &quot;^gradients/predictions_grad/MatMul_1&quot;\\n}\\nnode {\\n  name: &quot;gradients/predictions_grad/tuple/control_dependency&quot;\\n  op: &quot;Identity&quot;\\n  input: &quot;gradients/predictions_grad/MatMul&quot;\\n  input: &quot;^gradients/predictions_grad/tuple/group_deps&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;_class&quot;\\n    value {\\n      list {\\n        s: &quot;loc:@gradients/predictions_grad/MatMul&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;gradients/predictions_grad/tuple/control_dependency_1&quot;\\n  op: &quot;Identity&quot;\\n  input: &quot;gradients/predictions_grad/MatMul_1&quot;\\n  input: &quot;^gradients/predictions_grad/tuple/group_deps&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;_class&quot;\\n    value {\\n      list {\\n        s: &quot;loc:@gradients/predictions_grad/MatMul_1&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;GradientDescent/learning_rate&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_FLOAT\\n        tensor_shape {\\n        }\\n        float_val: 0.009999999776482582\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;GradientDescent/update_theta/ApplyGradientDescent&quot;\\n  op: &quot;ApplyGradientDescent&quot;\\n  input: &quot;theta&quot;\\n  input: &quot;GradientDescent/learning_rate&quot;\\n  input: &quot;gradients/predictions_grad/tuple/control_dependency_1&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;_class&quot;\\n    value {\\n      list {\\n        s: &quot;loc:@theta&quot;\\n      }\\n    }\\n  }\\n  attr {\\n    key: &quot;use_locking&quot;\\n    value {\\n      b: false\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;GradientDescent&quot;\\n  op: &quot;NoOp&quot;\\n  input: &quot;^GradientDescent/update_theta/ApplyGradientDescent&quot;\\n}\\nnode {\\n  name: &quot;init&quot;\\n  op: &quot;NoOp&quot;\\n  input: &quot;^theta/Assign&quot;\\n}\\nnode {\\n  name: &quot;save/Const&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_STRING\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_STRING\\n        tensor_shape {\\n        }\\n        string_val: &quot;model&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;save/SaveV2/tensor_names&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_STRING\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_STRING\\n        tensor_shape {\\n          dim {\\n            size: 1\\n          }\\n        }\\n        string_val: &quot;theta&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;save/SaveV2/shape_and_slices&quot;\\n  op: &quot;Const&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_STRING\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_STRING\\n        tensor_shape {\\n          dim {\\n            size: 1\\n          }\\n        }\\n        string_val: &quot;&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;save/SaveV2&quot;\\n  op: &quot;SaveV2&quot;\\n  input: &quot;save/Const&quot;\\n  input: &quot;save/SaveV2/tensor_names&quot;\\n  input: &quot;save/SaveV2/shape_and_slices&quot;\\n  input: &quot;theta&quot;\\n  attr {\\n    key: &quot;dtypes&quot;\\n    value {\\n      list {\\n        type: DT_FLOAT\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;save/control_dependency&quot;\\n  op: &quot;Identity&quot;\\n  input: &quot;save/Const&quot;\\n  input: &quot;^save/SaveV2&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_STRING\\n    }\\n  }\\n  attr {\\n    key: &quot;_class&quot;\\n    value {\\n      list {\\n        s: &quot;loc:@save/Const&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;save/RestoreV2/tensor_names&quot;\\n  op: &quot;Const&quot;\\n  device: &quot;/device:CPU:0&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_STRING\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_STRING\\n        tensor_shape {\\n          dim {\\n            size: 1\\n          }\\n        }\\n        string_val: &quot;theta&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;save/RestoreV2/shape_and_slices&quot;\\n  op: &quot;Const&quot;\\n  device: &quot;/device:CPU:0&quot;\\n  attr {\\n    key: &quot;dtype&quot;\\n    value {\\n      type: DT_STRING\\n    }\\n  }\\n  attr {\\n    key: &quot;value&quot;\\n    value {\\n      tensor {\\n        dtype: DT_STRING\\n        tensor_shape {\\n          dim {\\n            size: 1\\n          }\\n        }\\n        string_val: &quot;&quot;\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;save/RestoreV2&quot;\\n  op: &quot;RestoreV2&quot;\\n  input: &quot;save/Const&quot;\\n  input: &quot;save/RestoreV2/tensor_names&quot;\\n  input: &quot;save/RestoreV2/shape_and_slices&quot;\\n  device: &quot;/device:CPU:0&quot;\\n  attr {\\n    key: &quot;dtypes&quot;\\n    value {\\n      list {\\n        type: DT_FLOAT\\n      }\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;save/Assign&quot;\\n  op: &quot;Assign&quot;\\n  input: &quot;theta&quot;\\n  input: &quot;save/RestoreV2&quot;\\n  attr {\\n    key: &quot;T&quot;\\n    value {\\n      type: DT_FLOAT\\n    }\\n  }\\n  attr {\\n    key: &quot;_class&quot;\\n    value {\\n      list {\\n        s: &quot;loc:@theta&quot;\\n      }\\n    }\\n  }\\n  attr {\\n    key: &quot;use_locking&quot;\\n    value {\\n      b: true\\n    }\\n  }\\n  attr {\\n    key: &quot;validate_shape&quot;\\n    value {\\n      b: true\\n    }\\n  }\\n}\\nnode {\\n  name: &quot;save/restore_all&quot;\\n  op: &quot;NoOp&quot;\\n  input: &quot;^save/Assign&quot;\\n}\\n';\n",
       "          }\n",
       "        </script>\n",
       "        <link rel=&quot;import&quot; href=&quot;https://tensorboard.appspot.com/tf-graph-basic.build.html&quot; onload=load()>\n",
       "        <div style=&quot;height:600px&quot;>\n",
       "          <tf-graph-basic id=&quot;graph0.3745401188473625&quot;></tf-graph-basic>\n",
       "        </div>\n",
       "    \"></iframe>\n",
       "    "
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_graph(tf.get_default_graph())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 텐서보드 사용하기"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "from datetime import datetime\n",
    "\n",
    "now = datetime.utcnow().strftime(\"%Y%m%d%H%M%S\")\n",
    "root_logdir = \"tf_logs\"\n",
    "logdir = \"{}/run-{}/\".format(root_logdir, now)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_epochs = 1000\n",
    "learning_rate = 0.01\n",
    "\n",
    "X = tf.placeholder(tf.float32, shape=(None, n + 1), name=\"X\")\n",
    "y = tf.placeholder(tf.float32, shape=(None, 1), name=\"y\")\n",
    "theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name=\"theta\")\n",
    "y_pred = tf.matmul(X, theta, name=\"predictions\")\n",
    "error = y_pred - y\n",
    "mse = tf.reduce_mean(tf.square(error), name=\"mse\")\n",
    "optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)\n",
    "training_op = optimizer.minimize(mse)\n",
    "\n",
    "init = tf.global_variables_initializer()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [],
   "source": [
    "mse_summary = tf.summary.scalar('MSE', mse)\n",
    "file_writer = tf.summary.FileWriter(logdir, tf.get_default_graph())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_epochs = 10\n",
    "batch_size = 100\n",
    "n_batches = int(np.ceil(m / batch_size))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [],
   "source": [
    "with tf.Session() as sess:                                                        # 책에는 없습니다.\n",
    "    sess.run(init)                                                                # 책에는 없습니다.\n",
    "\n",
    "    for epoch in range(n_epochs):                                                 # 책에는 없습니다.\n",
    "        for batch_index in range(n_batches):\n",
    "            X_batch, y_batch = fetch_batch(epoch, batch_index, batch_size)\n",
    "            if batch_index % 10 == 0:\n",
    "                summary_str = mse_summary.eval(feed_dict={X: X_batch, y: y_batch})\n",
    "                step = epoch * n_batches + batch_index\n",
    "                file_writer.add_summary(summary_str, step)\n",
    "            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})\n",
    "\n",
    "    best_theta = theta.eval()                                                     # 책에는 없습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [],
   "source": [
    "file_writer.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 2.0703337 ],\n",
       "       [ 0.8637145 ],\n",
       "       [ 0.12255152],\n",
       "       [-0.31211877],\n",
       "       [ 0.38510376],\n",
       "       [ 0.00434168],\n",
       "       [-0.0123295 ],\n",
       "       [-0.83376896],\n",
       "       [-0.8030471 ]], dtype=float32)"
      ]
     },
     "execution_count": 66,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "best_theta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 이름 범위"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "now = datetime.utcnow().strftime(\"%Y%m%d%H%M%S\")\n",
    "root_logdir = \"tf_logs\"\n",
    "logdir = \"{}/run-{}/\".format(root_logdir, now)\n",
    "\n",
    "n_epochs = 1000\n",
    "learning_rate = 0.01\n",
    "\n",
    "X = tf.placeholder(tf.float32, shape=(None, n + 1), name=\"X\")\n",
    "y = tf.placeholder(tf.float32, shape=(None, 1), name=\"y\")\n",
    "theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name=\"theta\")\n",
    "y_pred = tf.matmul(X, theta, name=\"predictions\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [],
   "source": [
    "with tf.name_scope(\"loss\") as scope:\n",
    "    error = y_pred - y\n",
    "    mse = tf.reduce_mean(tf.square(error), name=\"mse\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)\n",
    "training_op = optimizer.minimize(mse)\n",
    "\n",
    "init = tf.global_variables_initializer()\n",
    "\n",
    "mse_summary = tf.summary.scalar('MSE', mse)\n",
    "file_writer = tf.summary.FileWriter(logdir, tf.get_default_graph())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "best_theta:\n",
      "[[ 2.0703337 ]\n",
      " [ 0.8637145 ]\n",
      " [ 0.12255152]\n",
      " [-0.31211877]\n",
      " [ 0.38510376]\n",
      " [ 0.00434168]\n",
      " [-0.0123295 ]\n",
      " [-0.83376896]\n",
      " [-0.8030471 ]]\n"
     ]
    }
   ],
   "source": [
    "n_epochs = 10\n",
    "batch_size = 100\n",
    "n_batches = int(np.ceil(m / batch_size))\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    sess.run(init)\n",
    "\n",
    "    for epoch in range(n_epochs):\n",
    "        for batch_index in range(n_batches):\n",
    "            X_batch, y_batch = fetch_batch(epoch, batch_index, batch_size)\n",
    "            if batch_index % 10 == 0:\n",
    "                summary_str = mse_summary.eval(feed_dict={X: X_batch, y: y_batch})\n",
    "                step = epoch * n_batches + batch_index\n",
    "                file_writer.add_summary(summary_str, step)\n",
    "            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})\n",
    "\n",
    "    best_theta = theta.eval()\n",
    "\n",
    "file_writer.flush()\n",
    "file_writer.close()\n",
    "print(\"best_theta:\")\n",
    "print(best_theta)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss/sub\n"
     ]
    }
   ],
   "source": [
    "print(error.op.name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss/mse\n"
     ]
    }
   ],
   "source": [
    "print(mse.op.name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a\n",
      "a_1\n",
      "param/a\n",
      "param_1/a\n"
     ]
    }
   ],
   "source": [
    "reset_graph()\n",
    "\n",
    "a1 = tf.Variable(0, name=\"a\")      # name == \"a\"\n",
    "a2 = tf.Variable(0, name=\"a\")      # name == \"a_1\"\n",
    "\n",
    "with tf.name_scope(\"param\"):       # name == \"param\"\n",
    "    a3 = tf.Variable(0, name=\"a\")  # name == \"param/a\"\n",
    "\n",
    "with tf.name_scope(\"param\"):       # name == \"param_1\"\n",
    "    a4 = tf.Variable(0, name=\"a\")  # name == \"param_1/a\"\n",
    "\n",
    "for node in (a1, a2, a3, a4):\n",
    "    print(node.op.name)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 모듈화"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "중복이 많습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "n_features = 3\n",
    "X = tf.placeholder(tf.float32, shape=(None, n_features), name=\"X\")\n",
    "\n",
    "w1 = tf.Variable(tf.random_normal((n_features, 1)), name=\"weights1\")\n",
    "w2 = tf.Variable(tf.random_normal((n_features, 1)), name=\"weights2\")\n",
    "b1 = tf.Variable(0.0, name=\"bias1\")\n",
    "b2 = tf.Variable(0.0, name=\"bias2\")\n",
    "\n",
    "z1 = tf.add(tf.matmul(X, w1), b1, name=\"z1\")\n",
    "z2 = tf.add(tf.matmul(X, w2), b2, name=\"z2\")\n",
    "\n",
    "relu1 = tf.maximum(z1, 0., name=\"relu1\")\n",
    "relu2 = tf.maximum(z1, 0., name=\"relu2\")  # Oops, cut&paste error! Did you spot it?\n",
    "\n",
    "output = tf.add(relu1, relu2, name=\"output\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "relu() 함수를 사용해 더 나아졌습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "def relu(X):\n",
    "    w_shape = (int(X.get_shape()[1]), 1)\n",
    "    w = tf.Variable(tf.random_normal(w_shape), name=\"weights\")\n",
    "    b = tf.Variable(0.0, name=\"bias\")\n",
    "    z = tf.add(tf.matmul(X, w), b, name=\"z\")\n",
    "    return tf.maximum(z, 0., name=\"relu\")\n",
    "\n",
    "n_features = 3\n",
    "X = tf.placeholder(tf.float32, shape=(None, n_features), name=\"X\")\n",
    "relus = [relu(X) for i in range(5)]\n",
    "output = tf.add_n(relus, name=\"output\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {},
   "outputs": [],
   "source": [
    "file_writer = tf.summary.FileWriter(\"logs/relu1\", tf.get_default_graph())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이름 범주를 사용하면 훨씬 더 낫습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "def relu(X):\n",
    "    with tf.name_scope(\"relu\"):\n",
    "        w_shape = (int(X.get_shape()[1]), 1)                          # 책에는 없습니다.\n",
    "        w = tf.Variable(tf.random_normal(w_shape), name=\"weights\")    # 책에는 없습니다.\n",
    "        b = tf.Variable(0.0, name=\"bias\")                             # 책에는 없습니다.\n",
    "        z = tf.add(tf.matmul(X, w), b, name=\"z\")                      # 책에는 없습니다.\n",
    "        return tf.maximum(z, 0., name=\"max\")                          # 책에는 없습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_features = 3\n",
    "X = tf.placeholder(tf.float32, shape=(None, n_features), name=\"X\")\n",
    "relus = [relu(X) for i in range(5)]\n",
    "output = tf.add_n(relus, name=\"output\")\n",
    "\n",
    "file_writer = tf.summary.FileWriter(\"logs/relu2\", tf.get_default_graph())\n",
    "file_writer.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 변수 공유"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`threshold` 변수를 공유하는 기본적인 방법은 `relu()` 함수 밖에서 정의한 후 매개변수를 통해 전달하는 것입니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "def relu(X, threshold):\n",
    "    with tf.name_scope(\"relu\"):\n",
    "        w_shape = (int(X.get_shape()[1]), 1)                        # 책에는 없습니다.\n",
    "        w = tf.Variable(tf.random_normal(w_shape), name=\"weights\")  # 책에는 없습니다.\n",
    "        b = tf.Variable(0.0, name=\"bias\")                           # 책에는 없습니다.\n",
    "        z = tf.add(tf.matmul(X, w), b, name=\"z\")                    # 책에는 없습니다.\n",
    "        return tf.maximum(z, threshold, name=\"max\")\n",
    "\n",
    "threshold = tf.Variable(0.0, name=\"threshold\")\n",
    "X = tf.placeholder(tf.float32, shape=(None, n_features), name=\"X\")\n",
    "relus = [relu(X, threshold) for i in range(5)]\n",
    "output = tf.add_n(relus, name=\"output\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "def relu(X):\n",
    "    with tf.name_scope(\"relu\"):\n",
    "        if not hasattr(relu, \"threshold\"):\n",
    "            relu.threshold = tf.Variable(0.0, name=\"threshold\")\n",
    "        w_shape = int(X.get_shape()[1]), 1                          # 책에는 없습니다.\n",
    "        w = tf.Variable(tf.random_normal(w_shape), name=\"weights\")  # 책에는 없습니다.\n",
    "        b = tf.Variable(0.0, name=\"bias\")                           # 책에는 없습니다.\n",
    "        z = tf.add(tf.matmul(X, w), b, name=\"z\")                    # 책에는 없습니다.\n",
    "        return tf.maximum(z, relu.threshold, name=\"max\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "metadata": {},
   "outputs": [],
   "source": [
    "X = tf.placeholder(tf.float32, shape=(None, n_features), name=\"X\")\n",
    "relus = [relu(X) for i in range(5)]\n",
    "output = tf.add_n(relus, name=\"output\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "with tf.variable_scope(\"relu\"):\n",
    "    threshold = tf.get_variable(\"threshold\", shape=(),\n",
    "                                initializer=tf.constant_initializer(0.0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "metadata": {},
   "outputs": [],
   "source": [
    "with tf.variable_scope(\"relu\", reuse=True):\n",
    "    threshold = tf.get_variable(\"threshold\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "metadata": {},
   "outputs": [],
   "source": [
    "with tf.variable_scope(\"relu\") as scope:\n",
    "    scope.reuse_variables()\n",
    "    threshold = tf.get_variable(\"threshold\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "def relu(X):\n",
    "    with tf.variable_scope(\"relu\", reuse=True):\n",
    "        threshold = tf.get_variable(\"threshold\")\n",
    "        w_shape = int(X.get_shape()[1]), 1                          # 책에는 없습니다.\n",
    "        w = tf.Variable(tf.random_normal(w_shape), name=\"weights\")  # 책에는 없습니다.\n",
    "        b = tf.Variable(0.0, name=\"bias\")                           # 책에는 없습니다.\n",
    "        z = tf.add(tf.matmul(X, w), b, name=\"z\")                    # 책에는 없습니다.\n",
    "        return tf.maximum(z, threshold, name=\"max\")\n",
    "\n",
    "X = tf.placeholder(tf.float32, shape=(None, n_features), name=\"X\")\n",
    "with tf.variable_scope(\"relu\"):\n",
    "    threshold = tf.get_variable(\"threshold\", shape=(),\n",
    "                                initializer=tf.constant_initializer(0.0))\n",
    "relus = [relu(X) for relu_index in range(5)]\n",
    "output = tf.add_n(relus, name=\"output\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "metadata": {},
   "outputs": [],
   "source": [
    "file_writer = tf.summary.FileWriter(\"logs/relu6\", tf.get_default_graph())\n",
    "file_writer.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "def relu(X):\n",
    "    with tf.variable_scope(\"relu\"):\n",
    "        threshold = tf.get_variable(\"threshold\", shape=(), initializer=tf.constant_initializer(0.0))\n",
    "        w_shape = (int(X.get_shape()[1]), 1)\n",
    "        w = tf.Variable(tf.random_normal(w_shape), name=\"weights\")\n",
    "        b = tf.Variable(0.0, name=\"bias\")\n",
    "        z = tf.add(tf.matmul(X, w), b, name=\"z\")\n",
    "        return tf.maximum(z, threshold, name=\"max\")\n",
    "\n",
    "X = tf.placeholder(tf.float32, shape=(None, n_features), name=\"X\")\n",
    "with tf.variable_scope(\"\", default_name=\"\") as scope:\n",
    "    first_relu = relu(X)     # 공유 변수를 만든 후\n",
    "    scope.reuse_variables()  # 재사용합니다.\n",
    "    relus = [first_relu] + [relu(X) for i in range(4)]\n",
    "output = tf.add_n(relus, name=\"output\")\n",
    "\n",
    "file_writer = tf.summary.FileWriter(\"logs/relu8\", tf.get_default_graph())\n",
    "file_writer.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()\n",
    "\n",
    "def relu(X):\n",
    "    threshold = tf.get_variable(\"threshold\", shape=(),\n",
    "                                initializer=tf.constant_initializer(0.0))\n",
    "    w_shape = (int(X.get_shape()[1]), 1)                        # 책에는 없습니다.\n",
    "    w = tf.Variable(tf.random_normal(w_shape), name=\"weights\")  # 책에는 없습니다.\n",
    "    b = tf.Variable(0.0, name=\"bias\")                           # 책에는 없습니다.\n",
    "    z = tf.add(tf.matmul(X, w), b, name=\"z\")                    # 책에는 없습니다.\n",
    "    return tf.maximum(z, threshold, name=\"max\")\n",
    "\n",
    "X = tf.placeholder(tf.float32, shape=(None, n_features), name=\"X\")\n",
    "relus = []\n",
    "for relu_index in range(5):\n",
    "    with tf.variable_scope(\"relu\", reuse=(relu_index >= 1)) as scope:\n",
    "        relus.append(relu(X))\n",
    "output = tf.add_n(relus, name=\"output\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "metadata": {},
   "outputs": [],
   "source": [
    "file_writer = tf.summary.FileWriter(\"logs/relu9\", tf.get_default_graph())\n",
    "file_writer.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 추가 내용"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x0: my_scope/x\n",
      "x1: my_scope/x_1\n",
      "x2: my_scope/x_2\n",
      "x3: my_scope/x\n",
      "x4: my_scope_1/x\n",
      "x5: my_scope/x\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "reset_graph()\n",
    "\n",
    "with tf.variable_scope(\"my_scope\"):\n",
    "    x0 = tf.get_variable(\"x\", shape=(), initializer=tf.constant_initializer(0.))\n",
    "    x1 = tf.Variable(0., name=\"x\")\n",
    "    x2 = tf.Variable(0., name=\"x\")\n",
    "\n",
    "with tf.variable_scope(\"my_scope\", reuse=True):\n",
    "    x3 = tf.get_variable(\"x\")\n",
    "    x4 = tf.Variable(0., name=\"x\")\n",
    "\n",
    "with tf.variable_scope(\"\", default_name=\"\", reuse=True):\n",
    "    x5 = tf.get_variable(\"my_scope/x\")\n",
    "\n",
    "print(\"x0:\", x0.op.name)\n",
    "print(\"x1:\", x1.op.name)\n",
    "print(\"x2:\", x2.op.name)\n",
    "print(\"x3:\", x3.op.name)\n",
    "print(\"x4:\", x4.op.name)\n",
    "print(\"x5:\", x5.op.name)\n",
    "print(x0 is x3 and x3 is x5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "첫 번째 `variable_scope()` 블럭은 이름이 `my_scope/x`인 공유 변수 `x0`를 만듭니다. 공유 변수 이외의 모든 연산에 대해서는 (공유되지 않는 변수를 포함하여) 변수 범위가 일반적인 이름 범위처럼 작동합니다. 그래서 두 변수 `x1`과 `x2`에 접두사 `my_scope/`가 붙습니다. 하지만 텐서플로는 이름을 고유하게 만들기 위해 `my_scope/x_1`, `my_scope/x_2`처럼 인덱스를 추가시킵니다.\n",
    "\n",
    "두 번째 `variable_scope()` 블럭은 `my_scope` 범위에 있는 공유 변수를 재사용합니다. 그래서 `x0 is x3`가 참입니다. 여기에서도 공유 변수를 제외한 모든 연산은 이름 범주와 같이 작동합니다. 첫 번째 블럭과 다르기 때문에 텐서플로가 고유한 범주 이름을 만듭니다(`my_scope_1`). 변수 `x4`의 이름은 `my_scope_1/x`가 됩니다.\n",
    "\n",
    "세 번째 블럭은 공유 변수 `my_scope/x`를 다루는 다른 방식을 보여 줍니다. 루트 범위(이름이 빈 문자열입니다)에서 `variable_scope()`를 만들고 공유 변수의 전체 이름(즉, `\"my_scope/x\"`)으로 `get_variable()`을 호출합니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 문자열"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[b'Do' b'you' b'want' b'some' b'caf\\xc3\\xa9?']\n"
     ]
    }
   ],
   "source": [
    "reset_graph()\n",
    "\n",
    "text = np.array(\"Do you want some café?\".split())\n",
    "text_tensor = tf.constant(text)\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    print(text_tensor.eval())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 자작 계산 그래프 구현하기"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "f(x,y) = ((x) * (x)) * (y) + y + 2\n",
      "f(3,4) = 42\n"
     ]
    }
   ],
   "source": [
    "class Const(object):\n",
    "    def __init__(self, value):\n",
    "        self.value = value\n",
    "    def evaluate(self):\n",
    "        return self.value\n",
    "    def __str__(self):\n",
    "        return str(self.value)\n",
    "\n",
    "class Var(object):\n",
    "    def __init__(self, init_value, name):\n",
    "        self.value = init_value\n",
    "        self.name = name\n",
    "    def evaluate(self):\n",
    "        return self.value\n",
    "    def __str__(self):\n",
    "        return self.name\n",
    "\n",
    "class BinaryOperator(object):\n",
    "    def __init__(self, a, b):\n",
    "        self.a = a\n",
    "        self.b = b\n",
    "\n",
    "class Add(BinaryOperator):\n",
    "    def evaluate(self):\n",
    "        return self.a.evaluate() + self.b.evaluate()\n",
    "    def __str__(self):\n",
    "        return \"{} + {}\".format(self.a, self.b)\n",
    "\n",
    "class Mul(BinaryOperator):\n",
    "    def evaluate(self):\n",
    "        return self.a.evaluate() * self.b.evaluate()\n",
    "    def __str__(self):\n",
    "        return \"({}) * ({})\".format(self.a, self.b)\n",
    "\n",
    "x = Var(3, name=\"x\")\n",
    "y = Var(4, name=\"y\")\n",
    "f = Add(Mul(Mul(x, x), y), Add(y, Const(2))) # f(x,y) = x²y + y + 2\n",
    "print(\"f(x,y) =\", f)\n",
    "print(\"f(3,4) =\", f.evaluate())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 그래디언트 계산\n",
    "### 수동 미분"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "df/dx(3,4) = 24\n",
      "df/dy(3,4) = 10\n"
     ]
    }
   ],
   "source": [
    "df_dx = Mul(Const(2), Mul(x, y))  # df/dx = 2xy\n",
    "df_dy = Add(Mul(x, x), Const(1))  # df/dy = x² + 1\n",
    "print(\"df/dx(3,4) =\", df_dx.evaluate())\n",
    "print(\"df/dy(3,4) =\", df_dy.evaluate())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 수치 미분"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "df/dx(3,4) = 24.000400000048216\n",
      "df/dy(3,4) = 10.000000000047748\n"
     ]
    }
   ],
   "source": [
    "def gradients(func, vars_list, eps=0.0001):\n",
    "    partial_derivatives = []\n",
    "    base_func_eval = func.evaluate()\n",
    "    for var in vars_list:\n",
    "        original_value = var.value\n",
    "        var.value = var.value + eps\n",
    "        tweaked_func_eval = func.evaluate()\n",
    "        var.value = original_value\n",
    "        derivative = (tweaked_func_eval - base_func_eval) / eps\n",
    "        partial_derivatives.append(derivative)\n",
    "    return partial_derivatives\n",
    "\n",
    "df_dx, df_dy = gradients(f, [x, y])\n",
    "print(\"df/dx(3,4) =\", df_dx)\n",
    "print(\"df/dy(3,4) =\", df_dy)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 기호 미분"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "df/dx(3,4) = 24.0\n",
      "df/dy(3,4) = 10.0\n"
     ]
    }
   ],
   "source": [
    "Const.derive = lambda self, var: Const(0)\n",
    "Var.derive = lambda self, var: Const(1) if self is var else Const(0)\n",
    "Add.derive = lambda self, var: Add(self.a.derive(var), self.b.derive(var))\n",
    "Mul.derive = lambda self, var: Add(Mul(self.a, self.b.derive(var)), Mul(self.a.derive(var), self.b))\n",
    "\n",
    "x = Var(3.0, name=\"x\")\n",
    "y = Var(4.0, name=\"y\")\n",
    "f = Add(Mul(Mul(x, x), y), Add(y, Const(2))) # f(x,y) = x²y + y + 2\n",
    "\n",
    "df_dx = f.derive(x)  # 2xy\n",
    "df_dy = f.derive(y)  # x² + 1\n",
    "print(\"df/dx(3,4) =\", df_dx.evaluate())\n",
    "print(\"df/dy(3,4) =\", df_dy.evaluate())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 자동 미분 (autodiff) – 전진 방식"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DualNumber(object):\n",
    "    def __init__(self, value=0.0, eps=0.0):\n",
    "        self.value = value\n",
    "        self.eps = eps\n",
    "    def __add__(self, b):\n",
    "        return DualNumber(self.value + self.to_dual(b).value,\n",
    "                          self.eps + self.to_dual(b).eps)\n",
    "    def __radd__(self, a):\n",
    "        return self.to_dual(a).__add__(self)\n",
    "    def __mul__(self, b):\n",
    "        return DualNumber(self.value * self.to_dual(b).value,\n",
    "                          self.eps * self.to_dual(b).value + self.value * self.to_dual(b).eps)\n",
    "    def __rmul__(self, a):\n",
    "        return self.to_dual(a).__mul__(self)\n",
    "    def __str__(self):\n",
    "        if self.eps:\n",
    "            return \"{:.1f} + {:.1f}ε\".format(self.value, self.eps)\n",
    "        else:\n",
    "            return \"{:.1f}\".format(self.value)\n",
    "    def __repr__(self):\n",
    "        return str(self)\n",
    "    @classmethod\n",
    "    def to_dual(cls, n):\n",
    "        if hasattr(n, \"value\"):\n",
    "            return n\n",
    "        else:\n",
    "            return cls(n)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$3 + (3 + 4 \\epsilon) = 6 + 4\\epsilon$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "6.0 + 4.0ε"
      ]
     },
     "execution_count": 97,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "3 + DualNumber(3, 4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$(3 + 4ε)\\times(5 + 7ε) = 3 \\times 5 + 3 \\times 7ε + 4ε \\times 5 + 4ε \\times 7ε = 15 + 21ε + 20ε + 28ε^2 = 15 + 41ε + 28 \\times 0 = 15 + 41ε$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "15.0 + 41.0ε"
      ]
     },
     "execution_count": 98,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "DualNumber(3, 4) * DualNumber(5, 7)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 99,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "42.0"
      ]
     },
     "execution_count": 99,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x.value = DualNumber(3.0)\n",
    "y.value = DualNumber(4.0)\n",
    "\n",
    "f.evaluate()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "metadata": {},
   "outputs": [],
   "source": [
    "x.value = DualNumber(3.0, 1.0)  # 3 + ε\n",
    "y.value = DualNumber(4.0)       # 4\n",
    "\n",
    "df_dx = f.evaluate().eps\n",
    "\n",
    "x.value = DualNumber(3.0)       # 3\n",
    "y.value = DualNumber(4.0, 1.0)  # 4 + ε\n",
    "\n",
    "df_dy = f.evaluate().eps"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "24.0"
      ]
     },
     "execution_count": 101,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_dx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10.0"
      ]
     },
     "execution_count": 102,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_dy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 자동 미분 – 후진 방식"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "f(x,y) = ((x) * (x)) * (y) + y + 2\n",
      "f(3,4) = 42\n",
      "df_dx = 24.0\n",
      "df_dy = 10.0\n"
     ]
    }
   ],
   "source": [
    "class Const(object):\n",
    "    def __init__(self, value):\n",
    "        self.value = value\n",
    "    def evaluate(self):\n",
    "        return self.value\n",
    "    def backpropagate(self, gradient):\n",
    "        pass\n",
    "    def __str__(self):\n",
    "        return str(self.value)\n",
    "\n",
    "class Var(object):\n",
    "    def __init__(self, init_value, name):\n",
    "        self.value = init_value\n",
    "        self.name = name\n",
    "        self.gradient = 0\n",
    "    def evaluate(self):\n",
    "        return self.value\n",
    "    def backpropagate(self, gradient):\n",
    "        self.gradient += gradient\n",
    "    def __str__(self):\n",
    "        return self.name\n",
    "\n",
    "class BinaryOperator(object):\n",
    "    def __init__(self, a, b):\n",
    "        self.a = a\n",
    "        self.b = b\n",
    "\n",
    "class Add(BinaryOperator):\n",
    "    def evaluate(self):\n",
    "        self.value = self.a.evaluate() + self.b.evaluate()\n",
    "        return self.value\n",
    "    def backpropagate(self, gradient):\n",
    "        self.a.backpropagate(gradient)\n",
    "        self.b.backpropagate(gradient)\n",
    "    def __str__(self):\n",
    "        return \"{} + {}\".format(self.a, self.b)\n",
    "\n",
    "class Mul(BinaryOperator):\n",
    "    def evaluate(self):\n",
    "        self.value = self.a.evaluate() * self.b.evaluate()\n",
    "        return self.value\n",
    "    def backpropagate(self, gradient):\n",
    "        self.a.backpropagate(gradient * self.b.value)\n",
    "        self.b.backpropagate(gradient * self.a.value)\n",
    "    def __str__(self):\n",
    "        return \"({}) * ({})\".format(self.a, self.b)\n",
    "\n",
    "x = Var(3, name=\"x\")\n",
    "y = Var(4, name=\"y\")\n",
    "f = Add(Mul(Mul(x, x), y), Add(y, Const(2))) # f(x,y) = x²y + y + 2\n",
    "\n",
    "result = f.evaluate()\n",
    "f.backpropagate(1.0)\n",
    "\n",
    "print(\"f(x,y) =\", f)\n",
    "print(\"f(3,4) =\", result)\n",
    "print(\"df_dx =\", x.gradient)\n",
    "print(\"df_dy =\", y.gradient)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 자동 미분 – 후진 모드 (텐서플로를 사용해서)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(42.0, [24.0, 10.0])"
      ]
     },
     "execution_count": 104,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "reset_graph()\n",
    "\n",
    "x = tf.Variable(3., name=\"x\")\n",
    "y = tf.Variable(4., name=\"y\")\n",
    "f = x*x*y + y + 2\n",
    "\n",
    "gradients = tf.gradients(f, [x, y])\n",
    "\n",
    "init = tf.global_variables_initializer()\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    init.run()\n",
    "    f_val, gradients_val = sess.run([f, gradients])\n",
    "\n",
    "f_val, gradients_val"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 연습문제 해답"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. to 11."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "부록 A 참조."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 12. 텐서플로를 사용한 미니배치 경사 하강법으로 구현한 로지스틱 회귀"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "먼저 사이킷런의 `make_moons()` 함수를 사용해 moons 데이터셋을 만듭니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.datasets import make_moons\n",
    "\n",
    "m = 1000\n",
    "X_moons, y_moons = make_moons(m, noise=0.1, random_state=42)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "데이터를 잠깐 들여다 보겠습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 28384 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(X_moons[y_moons == 1, 0], X_moons[y_moons == 1, 1], 'go', label=\"양성\")\n",
    "plt.plot(X_moons[y_moons == 0, 0], X_moons[y_moons == 0, 1], 'r^', label=\"음성\")\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "모든 샘플에 추가적인 편향 특성($x_0 = 1$)을 추가해야 합니다. 이렇게 하려면 입력 행렬 $\\mathbf{X}$의 왼쪽에 1로 채워진 열을 추가해야 합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_moons_with_bias = np.c_[np.ones((m, 1)), X_moons]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "확인해 보죠:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 1.        , -0.05146968,  0.44419863],\n",
       "       [ 1.        ,  1.03201691, -0.41974116],\n",
       "       [ 1.        ,  0.86789186, -0.25482711],\n",
       "       [ 1.        ,  0.288851  , -0.44866862],\n",
       "       [ 1.        , -0.83343911,  0.53505665]])"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_moons_with_bias[:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "좋네요. 이제 `y_train`의 크기를 바꾸어 열 벡터로 만들겠습니다(즉, 하나의 열이 있는 2D 배열입니다):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "y_moons_column_vector = y_moons.reshape(-1, 1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이제 데이터셋을 훈련 세트와 테스트 세트로 나눕니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_ratio = 0.2\n",
    "test_size = int(m * test_ratio)\n",
    "X_train = X_moons_with_bias[:-test_size]\n",
    "X_test = X_moons_with_bias[-test_size:]\n",
    "y_train = y_moons_column_vector[:-test_size]\n",
    "y_test = y_moons_column_vector[-test_size:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "좋습니다. 이제 훈련 배치를 생성하기 위한 간단한 함수를 만들겠습니다. 이 함수는 각 배치를 위해 훈련 세트에서 랜덤하게 샘플을 선택합니다. 하나의 배치에 동일한 샘플이 여러번 들어갈 수 있고 한 번의 에포크에 모든 훈련 샘플이 포함되지 않을 수 있습니다(사실 샘플의 3분의 2 정도가 포함됩니다). 하지만 실전에서 별 문제가 되지 않고 코드가 간단해 집니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "def random_batch(X_train, y_train, batch_size):\n",
    "    rnd_indices = np.random.randint(0, len(X_train), batch_size)\n",
    "    X_batch = X_train[rnd_indices]\n",
    "    y_batch = y_train[rnd_indices]\n",
    "    return X_batch, y_batch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "작은 배치 하나를 만들어 보겠습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 1.        ,  1.93189866,  0.13158788],\n",
       "       [ 1.        ,  1.07172763,  0.13482039],\n",
       "       [ 1.        , -1.01148674, -0.04686381],\n",
       "       [ 1.        ,  0.02201868,  0.19079139],\n",
       "       [ 1.        , -0.98941204,  0.02473116]])"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_batch, y_batch = random_batch(X_train, y_train, 5)\n",
    "X_batch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1],\n",
       "       [0],\n",
       "       [0],\n",
       "       [1],\n",
       "       [0]])"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y_batch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "좋습니다! 모델에 주입할 데이터가 준비되었으므로 모델을 만들 차례입니다. 간단하게 시작해서 기능을 점차 추가해 보겠습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "먼저  기본 그래프를 리셋합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_moons_ 데이터셋은 두 개의 입력 특성을 가지므로 각 샘플은 평면 위의 한 점입니다(즉, 2차원입니다):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_inputs = 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "로지스틱 회귀 모델을 만들어 보겠습니다. 4장에서 보았던 것처럼 이 모델은 먼저 (선형 회귀 모델과 동일하게) 입력의 가중치 합을 계산하고 그 결과를 시그모이드 함수에 적용하여 양성 클래스에 대한 추정 확률을 만듭니다:\n",
    "\n",
    "$\\hat{p} = h_\\mathbf{\\theta}(\\mathbf{x}) = \\sigma(\\mathbf{\\theta}^T \\cdot \\mathbf{x})$\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$\\mathbf{\\theta}$는 편향 $\\theta_0$와 가중치 $\\theta_1, \\theta_2, \\dots, \\theta_n$를 포함한 파라미터 벡터입니다. 입력 벡터 $\\mathbf{x}$는 상수 항 $x_0 = 1$과 입력 특성 $x_1, x_2, \\dots, x_n$을 포함합니다.\n",
    "\n",
    "한 번에 여러 샘플에 대한 예측을 만들 수 있어야 하므로 하나의 입력 벡터보다는 입력 행렬 $\\mathbf{X}$를 사용합니다. $i^{th}$ 번째 행이 $i^{th}$ 번째 입력 벡터의 전치$(\\mathbf{x}^{(i)})^T$입니다. 다음 식을 사용하여 각 샘플이 양성 클래스에 속할 확률을 추정할 수 있습니다:\n",
    "\n",
    "$ \\hat{\\mathbf{p}} = \\sigma(\\mathbf{X} \\cdot \\mathbf{\\theta})$\n",
    "\n",
    "모델을 만들기 위해 준비를 마쳤습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "X = tf.placeholder(tf.float32, shape=(None, n_inputs + 1), name=\"X\")\n",
    "y = tf.placeholder(tf.float32, shape=(None, 1), name=\"y\")\n",
    "theta = tf.Variable(tf.random_uniform([n_inputs + 1, 1], -1.0, 1.0, seed=42), name=\"theta\")\n",
    "logits = tf.matmul(X, theta, name=\"logits\")\n",
    "y_proba = 1 / (1 + tf.exp(-logits))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "사실 텐서플로는 `tf.sigmoid()` 함수를 가지고 있어 마지막 라인을 다음과 같이 바꿀 수 있습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "y_proba = tf.sigmoid(logits)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "4장에서 보았듯이 로그 손실은 로지스틱 회귀에 사용하기 좋은 비용 함수입니다:\n",
    "\n",
    "$J(\\mathbf{\\theta}) = -\\dfrac{1}{m} \\sum\\limits_{i=1}^{m}{\\left[ y^{(i)} log\\left(\\hat{p}^{(i)}\\right) + (1 - y^{(i)}) log\\left(1 - \\hat{p}^{(i)}\\right)\\right]}$\n",
    "\n",
    "직접 구현하는 것도 한가지 방법입니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "epsilon = 1e-7  # 로그를 계산할 때 오버플로우를 피하기 위해\n",
    "loss = -tf.reduce_mean(y * tf.log(y_proba + epsilon) + (1 - y) * tf.log(1 - y_proba + epsilon))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "하지만 텐서플로의 `tf.losses.log_loss()` 함수를 사용할 수 있습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "loss = tf.losses.log_loss(y, y_proba)  # 기본적으로 epsilon = 1e-7 가 사용됩니다"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "나머지는 아주 기본적입니다. 옵티마이저를 만들고 비용 함수를 최소화시키도록 합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "learning_rate = 0.01\n",
    "optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)\n",
    "training_op = optimizer.minimize(loss)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(이 간단한 예에서) 남은 것은 변수 초기화입니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "init = tf.global_variables_initializer()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "모델을 훈련하고 예측을 만들 준비가 되었습니다!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "다음 코드에는 특별한 것은 없습니다. 앞서 선형 회귀에서 사용했던 것과 사실상 동일합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "에포크: 0 \tLoss: 0.79260236\n",
      "에포크: 100 \tLoss: 0.3434635\n",
      "에포크: 200 \tLoss: 0.30754042\n",
      "에포크: 300 \tLoss: 0.29288897\n",
      "에포크: 400 \tLoss: 0.28533572\n",
      "에포크: 500 \tLoss: 0.2804781\n",
      "에포크: 600 \tLoss: 0.27808297\n",
      "에포크: 700 \tLoss: 0.27615443\n",
      "에포크: 800 \tLoss: 0.27551997\n",
      "에포크: 900 \tLoss: 0.27491236\n"
     ]
    }
   ],
   "source": [
    "n_epochs = 1000\n",
    "batch_size = 50\n",
    "n_batches = int(np.ceil(m / batch_size))\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    sess.run(init)\n",
    "\n",
    "    for epoch in range(n_epochs):\n",
    "        for batch_index in range(n_batches):\n",
    "            X_batch, y_batch = random_batch(X_train, y_train, batch_size)\n",
    "            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})\n",
    "        loss_val = loss.eval({X: X_test, y: y_test})\n",
    "        if epoch % 100 == 0:\n",
    "            print(\"에포크:\", epoch, \"\\tLoss:\", loss_val)\n",
    "\n",
    "    y_proba_val = y_proba.eval(feed_dict={X: X_test, y: y_test})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "노트: 배치를 만들 때 에포크 수를 사용하지 않았으므로 두 개의 `for` 반복을 중첩하지 않고 하나의 `for` 반복을 사용할 수 있습니다. 하지만 훈련 시간을 에포크의 개수로 생각하는게 편리합니다(즉, 알고리즘이 훈련 세트를 모두 훑고 지나가는 횟수)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "테스트 세트에 있는 각 샘플에 대해서 `y_proba_val`은 해당 샘플이 양성 클래스에 속할 모델의 추정 확률을 담고 있습니다. 예를 들어 다음은 첫 번째 다섯 개 샘플의 추정 확률입니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.54895616],\n",
       "       [0.7072437 ],\n",
       "       [0.51900256],\n",
       "       [0.9911136 ],\n",
       "       [0.50859046]], dtype=float32)"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y_proba_val[:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "각 샘플을 분류하기 위해서 최대 가능도 방법(maximum likelihood)을 사용합니다. 추정 확률이 0.5보다 크거나 같으면 양성으로 분류합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ True],\n",
       "       [ True],\n",
       "       [ True],\n",
       "       [ True],\n",
       "       [ True]])"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y_pred = (y_proba_val >= 0.5)\n",
    "y_pred[:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "경우에 따라 0.5말고 다른 임계값을 사용해야 할 수 있습니다. 가령 높은 정밀도(대신 낮은 재현율)를 원한다면 임계값을 높이고 재현율을 높이려면(대신 낮은 정밀도) 임계값을 낮춥니다. 자세한 내용은 3장을 참고하세요."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "모델의 정밀도와 재현율을 계산해 보겠습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.8627450980392157"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sklearn.metrics import precision_score, recall_score\n",
    "\n",
    "precision_score(y_test, y_pred)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.8888888888888888"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "recall_score(y_test, y_pred)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이 예측이 어떻게 보이는지 그래프로 나타내 보겠습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 17012 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "y_pred_idx = y_pred.reshape(-1) # 열 벡터를 1차원 배열로 바꿉니다\n",
    "plt.plot(X_test[y_pred_idx, 1], X_test[y_pred_idx, 2], 'go', label=\"양성\")\n",
    "plt.plot(X_test[~y_pred_idx, 1], X_test[~y_pred_idx, 2], 'r^', label=\"음성\")\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "음 결과가 좋지 않네요. 그렇죠? 하지만 로지스틱 회귀 모델은 선형적인 결정 경계를 가지므로 최선에 가까운 것 같습니다(잠시 후에 보겠지만 특성을 더 추가하지 않는다면 말이죠)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "다시 시작해 보죠. 이번에는 연습문제에 나열된 모든 부가 기능을 추가해 보겠습니다:\n",
    "* 재사용이 용이하도록 `logistic_regression()` 함수 안에서 그래프를 정의합니다.\n",
    "* 훈련하는 동안 일정한 간격으로 `Saver` 객체를 사용해 체크포인트를 저장하고 훈련이 끝날 때 최종 모델을 저장합니다.\n",
    "* 훈련이 중지되고 다시 시작할 때 마지막 체크포인트를 복원합니다.\n",
    "* 텐서보드에서 그래프가 잘 정돈되어 보이도록 이름 범위를 사용하여 그래프를 정의합니다.\n",
    "* 서머리(summary)를 추가해 텐서보드에서 학습 곡선을 나타냅니다.\n",
    "* 학습률, 미니배치 크기 같은 하이퍼파라미터를 바꾸어 보면서 학습 곡선의 모양을 관찰합니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "시작하기 전에 입력에 ${x_1}^2$, ${x_2}^2$, ${x_1}^3$ 그리고 ${x_2}^3$ 네 개의 특성을 추가합니다. 연습문제에 포함되어 있지는 않지만 특성을 추가하면 모델의 성능이 향상되는 것을 확인할 수 있습니다. 여기서는 수동으로 특성을 추가하지만 `sklearn.preprocessing.PolynomialFeatures`을 사용할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_enhanced = np.c_[X_train,\n",
    "                         np.square(X_train[:, 1]),\n",
    "                         np.square(X_train[:, 2]),\n",
    "                         X_train[:, 1] ** 3,\n",
    "                         X_train[:, 2] ** 3]\n",
    "X_test_enhanced = np.c_[X_test,\n",
    "                        np.square(X_test[:, 1]),\n",
    "                        np.square(X_test[:, 2]),\n",
    "                        X_test[:, 1] ** 3,\n",
    "                        X_test[:, 2] ** 3]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "특성이 추가된 훈련 세트는 다음과 같습니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 1.00000000e+00, -5.14696757e-02,  4.44198631e-01,\n",
       "         2.64912752e-03,  1.97312424e-01, -1.36349734e-04,\n",
       "         8.76459084e-02],\n",
       "       [ 1.00000000e+00,  1.03201691e+00, -4.19741157e-01,\n",
       "         1.06505890e+00,  1.76182639e-01,  1.09915879e+00,\n",
       "        -7.39511049e-02],\n",
       "       [ 1.00000000e+00,  8.67891864e-01, -2.54827114e-01,\n",
       "         7.53236288e-01,  6.49368582e-02,  6.53727646e-01,\n",
       "        -1.65476722e-02],\n",
       "       [ 1.00000000e+00,  2.88850997e-01, -4.48668621e-01,\n",
       "         8.34348982e-02,  2.01303531e-01,  2.41002535e-02,\n",
       "        -9.03185778e-02],\n",
       "       [ 1.00000000e+00, -8.33439108e-01,  5.35056649e-01,\n",
       "         6.94620746e-01,  2.86285618e-01, -5.78924095e-01,\n",
       "         1.53179024e-01]])"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_train_enhanced[:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "좋습니다. 이제 기본 그래프를 초기화합니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "reset_graph()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "그래프를 만들기 위해 `logistic_regression()` 함수를 정의합니다. 입력 `X`와 타깃 `y`의 정의를 포함하지 않았습니다. 이 함수에서 정의할 수도 있지만 그렇게 하지 않아야 다양한 경우에 이 함수를 사용할 수 있습니다(예를 들어, 로지스틱 회귀 모델에 주입하기 전에 입력에 대해 전처리 단계를 추가할 수 있습니다)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "def logistic_regression(X, y, initializer=None, seed=42, learning_rate=0.01):\n",
    "    n_inputs_including_bias = int(X.get_shape()[1])\n",
    "    with tf.name_scope(\"logistic_regression\"):\n",
    "        with tf.name_scope(\"model\"):\n",
    "            if initializer is None:\n",
    "                initializer = tf.random_uniform([n_inputs_including_bias, 1], -1.0, 1.0, seed=seed)\n",
    "            theta = tf.Variable(initializer, name=\"theta\")\n",
    "            logits = tf.matmul(X, theta, name=\"logits\")\n",
    "            y_proba = tf.sigmoid(logits)\n",
    "        with tf.name_scope(\"train\"):\n",
    "            loss = tf.losses.log_loss(y, y_proba, scope=\"loss\")\n",
    "            optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)\n",
    "            training_op = optimizer.minimize(loss)\n",
    "            loss_summary = tf.summary.scalar('log_loss', loss)\n",
    "        with tf.name_scope(\"init\"):\n",
    "            init = tf.global_variables_initializer()\n",
    "        with tf.name_scope(\"save\"):\n",
    "            saver = tf.train.Saver()\n",
    "    return y_proba, loss, training_op, loss_summary, init, saver"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "텐서보드를 위해 서머리를 저장할 로그 디렉토리 이름을 생성하는 함수를 만듭니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import datetime\n",
    "\n",
    "def log_dir(prefix=\"\"):\n",
    "    now = datetime.utcnow().strftime(\"%Y%m%d%H%M%S\")\n",
    "    root_logdir = \"tf_logs\"\n",
    "    if prefix:\n",
    "        prefix += \"-\"\n",
    "    name = prefix + \"run-\" + now\n",
    "    return \"{}/{}/\".format(root_logdir, name)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이제 `logistic_regression()` 함수를 사용해 그래프를 만듭니다. 텐서보드용 서머리를 로그 디렉토리에 저장하기 위해 `FileWriter`도 만듭니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_inputs = 2 + 4\n",
    "logdir = log_dir(\"logreg\")\n",
    "\n",
    "X = tf.placeholder(tf.float32, shape=(None, n_inputs + 1), name=\"X\")\n",
    "y = tf.placeholder(tf.float32, shape=(None, 1), name=\"y\")\n",
    "\n",
    "y_proba, loss, training_op, loss_summary, init, saver = logistic_regression(X, y)\n",
    "\n",
    "file_writer = tf.summary.FileWriter(logdir, tf.get_default_graph())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "드디어 모델을 학습시킬 수 있습니다! 이전에 훈련 세션이 중지되었는지부터 검사하고 그렇다면 체크포인트를 로드하고 저장된 에포크 횟수부터 훈련을 이어갑니다. 이 예에서는 별도의 파일에 에포트 횟수를 저장했지만 11장에서 모델에 일부로 훈련 스텝을저장하는 방법을 배우겠습니다. 예를 들어 `global_step`이란 훈련되지 않는 변수를 옵티마이저의 `minimize()` 메서드에 전달합니다.\n",
    "\n",
    "다시 시작할 때 마지막 체크포인트가 제대로 복원되는지 확인하기 위해 훈련을 중지시켜 볼 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "에포크: 0 \t손실: 0.62998503\n",
      "에포크: 500 \t손실: 0.16122366\n",
      "에포크: 1000 \t손실: 0.11903212\n",
      "에포크: 1500 \t손실: 0.097329214\n",
      "에포크: 2000 \t손실: 0.083697945\n",
      "에포크: 2500 \t손실: 0.07437584\n",
      "에포크: 3000 \t손실: 0.067502156\n",
      "에포크: 3500 \t손실: 0.06220691\n",
      "에포크: 4000 \t손실: 0.058026787\n",
      "에포크: 4500 \t손실: 0.05456298\n",
      "에포크: 5000 \t손실: 0.051708277\n",
      "에포크: 5500 \t손실: 0.04923773\n",
      "에포크: 6000 \t손실: 0.047167283\n",
      "에포크: 6500 \t손실: 0.045376636\n",
      "에포크: 7000 \t손실: 0.043818742\n",
      "에포크: 7500 \t손실: 0.042374216\n",
      "에포크: 8000 \t손실: 0.041089155\n",
      "에포크: 8500 \t손실: 0.039970912\n",
      "에포크: 9000 \t손실: 0.038920246\n",
      "에포크: 9500 \t손실: 0.038010743\n",
      "에포크: 10000 \t손실: 0.0371557\n"
     ]
    }
   ],
   "source": [
    "n_epochs = 10001\n",
    "batch_size = 50\n",
    "n_batches = int(np.ceil(m / batch_size))\n",
    "\n",
    "checkpoint_path = \"/tmp/my_logreg_model.ckpt\"\n",
    "checkpoint_epoch_path = checkpoint_path + \".epoch\"\n",
    "final_model_path = \"./my_logreg_model\"\n",
    "\n",
    "with tf.Session() as sess:\n",
    "    if os.path.isfile(checkpoint_epoch_path):\n",
    "        # 체크포인트 파일이 있으면 모델을 복원하고 에포크 횟수를 로드합니다\n",
    "        with open(checkpoint_epoch_path, \"rb\") as f:\n",
    "            start_epoch = int(f.read())\n",
    "        print(\"중지되었던 훈련입니다. 에포크를 이어갑니다.\", start_epoch)\n",
    "        saver.restore(sess, checkpoint_path)\n",
    "    else:\n",
    "        start_epoch = 0\n",
    "        sess.run(init)\n",
    "\n",
    "    for epoch in range(start_epoch, n_epochs):\n",
    "        for batch_index in range(n_batches):\n",
    "            X_batch, y_batch = random_batch(X_train_enhanced, y_train, batch_size)\n",
    "            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})\n",
    "        loss_val, summary_str = sess.run([loss, loss_summary], feed_dict={X: X_test_enhanced, y: y_test})\n",
    "        file_writer.add_summary(summary_str, epoch)\n",
    "        if epoch % 500 == 0:\n",
    "            print(\"에포크:\", epoch, \"\\t손실:\", loss_val)\n",
    "            saver.save(sess, checkpoint_path)\n",
    "            with open(checkpoint_epoch_path, \"wb\") as f:\n",
    "                f.write(b\"%d\" % (epoch + 1))\n",
    "\n",
    "    saver.save(sess, final_model_path)\n",
    "    y_proba_val = y_proba.eval(feed_dict={X: X_test_enhanced, y: y_test})\n",
    "    os.remove(checkpoint_epoch_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "여기에서도 추정 확률이 0.5보다 크거나 같은 샘플을 모두 양성으로 분류하면 예측이 됩니다:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "y_pred = (y_proba_val >= 0.5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.9797979797979798"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "precision_score(y_test, y_pred)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.9797979797979798"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "recall_score(y_test, y_pred)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 17012 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "y_pred_idx = y_pred.reshape(-1) # 열 벡터 대신 1차원 배열\n",
    "plt.plot(X_test[y_pred_idx, 1], X_test[y_pred_idx, 2], 'go', label=\"양성\")\n",
    "plt.plot(X_test[~y_pred_idx, 1], X_test[~y_pred_idx, 2], 'r^', label=\"음성\")\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "훨씬 더 좋아졌네요! 새로 추가한 특성이 확실히 도움이 많이 되었습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "텐서보드 서버를 시작해서 최근 실행을 찾아 학습 곡선을 확인해 보세요(즉, 에포크 횟수에 대해 테스트 세트로 평가한 손실이 얼마나 되는지):\n",
    "\n",
    "```\n",
    "$ tensorboard --logdir=tf_logs\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이제 하이퍼파라미터(가령, `batch_size`나 `learning_rate`)를 조정하면서 훈련을 여러번 실행해 보고 학습 곡선을 비교해 보겠습니다. 그리드 서치나 랜덤 서치를 구현해서 이 과정을 자동화할 수도 있습니다. 다음은 배치 크기와 학습률에 대한 간단한 랜덤 서치 구현입니다. 간단하게 하기 위해서 체크포인트 관리 부분은 제외했습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "반복 0\n",
      "  logdir: tf_logs/logreg-run-20180409085423/\n",
      "  batch_size: 54\n",
      "  learning_rate: 0.004430375245218265\n",
      "  훈련: .....................\n",
      "  정밀도: 0.9797979797979798\n",
      "  재현율: 0.9797979797979798\n",
      "반복 1\n",
      "  logdir: tf_logs/logreg-run-20180409090102/\n",
      "  batch_size: 22\n",
      "  learning_rate: 0.0017826497151386947\n",
      "  훈련: .....................\n",
      "  정밀도: 0.9797979797979798\n",
      "  재현율: 0.9797979797979798\n",
      "반복 2\n",
      "  logdir: tf_logs/logreg-run-20180409091435/\n",
      "  batch_size: 74\n",
      "  learning_rate: 0.00203228544324115\n",
      "  훈련: .....................\n",
      "  정밀도: 0.9696969696969697\n",
      "  재현율: 0.9696969696969697\n",
      "반복 3\n",
      "  logdir: tf_logs/logreg-run-20180409091959/\n",
      "  batch_size: 58\n",
      "  learning_rate: 0.004491523825137997\n",
      "  훈련: .....................\n",
      "  정밀도: 0.9797979797979798\n",
      "  재현율: 0.9797979797979798\n",
      "반복 4\n",
      "  logdir: tf_logs/logreg-run-20180409092604/\n",
      "  batch_size: 61\n",
      "  learning_rate: 0.07963234721775589\n",
      "  훈련: .....................\n",
      "  정밀도: 0.9801980198019802\n",
      "  재현율: 1.0\n",
      "반복 5\n",
      "  logdir: tf_logs/logreg-run-20180409093019/\n",
      "  batch_size: 92\n",
      "  learning_rate: 0.0004634250583294876\n",
      "  훈련: .....................\n",
      "  정밀도: 0.912621359223301\n",
      "  재현율: 0.9494949494949495\n",
      "반복 6\n",
      "  logdir: tf_logs/logreg-run-20180409093334/\n",
      "  batch_size: 74\n",
      "  learning_rate: 0.047706818419354494\n",
      "  훈련: .....................\n",
      "  정밀도: 0.98\n",
      "  재현율: 0.98989898989899\n",
      "반복 7\n",
      "  logdir: tf_logs/logreg-run-20180409093631/\n",
      "  batch_size: 58\n",
      "  learning_rate: 0.0001694044709524274\n",
      "  훈련: .....................\n",
      "  정밀도: 0.9\n",
      "  재현율: 0.9090909090909091\n",
      "반복 8\n",
      "  logdir: tf_logs/logreg-run-20180409094251/\n",
      "  batch_size: 61\n",
      "  learning_rate: 0.04171461199412461\n",
      "  훈련: .....................\n",
      "  정밀도: 0.9801980198019802\n",
      "  재현율: 1.0\n",
      "반복 9\n",
      "  logdir: tf_logs/logreg-run-20180409094921/\n",
      "  batch_size: 92\n",
      "  learning_rate: 0.00010742922968438615\n",
      "  훈련: .....................\n",
      "  정밀도: 0.8823529411764706\n",
      "  재현율: 0.7575757575757576\n"
     ]
    }
   ],
   "source": [
    "from scipy.stats import reciprocal\n",
    "\n",
    "n_search_iterations = 10\n",
    "\n",
    "for search_iteration in range(n_search_iterations):\n",
    "    batch_size = np.random.randint(1, 100)\n",
    "    learning_rate = reciprocal(0.0001, 0.1).rvs(random_state=search_iteration)\n",
    "\n",
    "    n_inputs = 2 + 4\n",
    "    logdir = log_dir(\"logreg\")\n",
    "    \n",
    "    print(\"반복\", search_iteration)\n",
    "    print(\"  logdir:\", logdir)\n",
    "    print(\"  batch_size:\", batch_size)\n",
    "    print(\"  learning_rate:\", learning_rate)\n",
    "    print(\"  훈련: \", end=\"\")\n",
    "\n",
    "    reset_graph()\n",
    "\n",
    "    X = tf.placeholder(tf.float32, shape=(None, n_inputs + 1), name=\"X\")\n",
    "    y = tf.placeholder(tf.float32, shape=(None, 1), name=\"y\")\n",
    "\n",
    "    y_proba, loss, training_op, loss_summary, init, saver = logistic_regression(\n",
    "        X, y, learning_rate=learning_rate)\n",
    "\n",
    "    file_writer = tf.summary.FileWriter(logdir, tf.get_default_graph())\n",
    "\n",
    "    n_epochs = 10001\n",
    "    n_batches = int(np.ceil(m / batch_size))\n",
    "\n",
    "    final_model_path = \"./my_logreg_model_%d\" % search_iteration\n",
    "\n",
    "    with tf.Session() as sess:\n",
    "        sess.run(init)\n",
    "\n",
    "        for epoch in range(n_epochs):\n",
    "            for batch_index in range(n_batches):\n",
    "                X_batch, y_batch = random_batch(X_train_enhanced, y_train, batch_size)\n",
    "                sess.run(training_op, feed_dict={X: X_batch, y: y_batch})\n",
    "            loss_val, summary_str = sess.run([loss, loss_summary], feed_dict={X: X_test_enhanced, y: y_test})\n",
    "            file_writer.add_summary(summary_str, epoch)\n",
    "            if epoch % 500 == 0:\n",
    "                print(\".\", end=\"\")\n",
    "\n",
    "        saver.save(sess, final_model_path)\n",
    "\n",
    "        print()\n",
    "        y_proba_val = y_proba.eval(feed_dict={X: X_test_enhanced, y: y_test})\n",
    "        y_pred = (y_proba_val >= 0.5)\n",
    "        \n",
    "        print(\"  정밀도:\", precision_score(y_test, y_pred))\n",
    "        print(\"  재현율:\", recall_score(y_test, y_pred))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "하이퍼파라미터의 적절한 스케일을 감잡을 수 없을 때 사이파이(SciPy)의 `stats` 모듈의 `reciprocal()` 함수를 사용하여 난수 분포를 얻을 수 있습니다. 좀 더 자세한 내용은 2장의 연습문제 해답을 보세요."
   ]
  }
 ],
 "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": {
   "height": "603px",
   "width": "616px"
  },
  "toc": {
   "navigate_menu": true,
   "number_sections": true,
   "sideBar": true,
   "threshold": 6,
   "toc_cell": false,
   "toc_section_display": "block",
   "toc_window_display": true
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
