{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Important: This notebook will only work with fastai-0.7.x. Do not try to run any fastai-1.x code from this path in the repository because it will load fastai-0.7.x**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Table of Contents\n",
    " <p><div class=\"lev1 toc-item\"><a href=\"#Linear-Regression-problem\" data-toc-modified-id=\"Linear-Regression-problem-1\"><span class=\"toc-item-num\">1&nbsp;&nbsp;</span>Linear Regression problem</a></div><div class=\"lev1 toc-item\"><a href=\"#Gradient-Descent\" data-toc-modified-id=\"Gradient-Descent-2\"><span class=\"toc-item-num\">2&nbsp;&nbsp;</span>Gradient Descent</a></div><div class=\"lev1 toc-item\"><a href=\"#Gradient-Descent---Classification\" data-toc-modified-id=\"Gradient-Descent---Classification-3\"><span class=\"toc-item-num\">3&nbsp;&nbsp;</span>Gradient Descent - Classification</a></div><div class=\"lev1 toc-item\"><a href=\"#Gradient-descent-with-numpy\" data-toc-modified-id=\"Gradient-descent-with-numpy-4\"><span class=\"toc-item-num\">4&nbsp;&nbsp;</span>Gradient descent with numpy</a></div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "from fastai.learner import *"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this part of the lecture we explain Stochastic Gradient Descent (SGD) which is an **optimization** method commonly used in neural networks. We will illustrate the concepts with concrete examples."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#  Linear Regression problem"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The goal of linear regression is to fit a line to a set of points."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Here we generate some fake data\n",
    "def lin(a,b,x): return a*x+b\n",
    "\n",
    "def gen_fake_data(n, a, b):\n",
    "    x = s = np.random.uniform(0,1,n) \n",
    "    y = lin(a,b,x) + 0.1 * np.random.normal(0,3,n)\n",
    "    return x, y\n",
    "\n",
    "x, y = gen_fake_data(50, 3., 8.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7f8f91c22e10>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.scatter(x,y, s=8); plt.xlabel(\"x\"); plt.ylabel(\"y\"); "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You want to find **parameters** (weights) $a$ and $b$ such that you minimize the *error* between the points and the line $a\\cdot x + b$. Note that here $a$ and $b$ are unknown. For a regression problem the most common *error function* or *loss function* is the **mean squared error**. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def mse(y_hat, y): return ((y_hat - y) ** 2).mean()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Suppose we believe $a = 10$ and $b = 5$ then we can compute `y_hat` which is our *prediction* and then compute our error."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4.1001300495563058"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y_hat = lin(10,5,x)\n",
    "mse(y_hat, y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def mse_loss(a, b, x, y): return mse(lin(a,b,x), y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4.1001300495563058"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mse_loss(10, 5, x, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So far we have specified the *model* (linear regression) and the *evaluation criteria* (or *loss function*). Now we need to handle *optimization*; that is, how do we find the best values for $a$ and $b$? How do we find the best *fitting* linear regression."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Gradient Descent"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For a fixed dataset $x$ and $y$ `mse_loss(a,b)` is a function of $a$ and $b$. We would like to find the values of $a$ and $b$ that minimize that function.\n",
    "\n",
    "**Gradient descent** is an algorithm that minimizes functions. Given a function defined by a set of parameters, gradient descent starts with an initial set of parameter values and iteratively moves toward a set of parameter values that minimize the function. This iterative minimization is achieved by taking steps in the negative direction of the function gradient.\n",
    "\n",
    "Here is gradient descent implemented in [PyTorch](http://pytorch.org/)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "((10000,), (10000,))"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# generate some more data\n",
    "x, y = gen_fake_data(10000, 3., 8.)\n",
    "x.shape, y.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x,y = V(x),V(y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(Variable containing:\n",
       " 1.00000e-02 *\n",
       "   2.9873\n",
       " [torch.FloatTensor of size 1], Variable containing:\n",
       "  0.1116\n",
       " [torch.FloatTensor of size 1])"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Create random weights a and b, and wrap them in Variables.\n",
    "a = V(np.random.randn(1), requires_grad=True)\n",
    "b = V(np.random.randn(1), requires_grad=True)\n",
    "a,b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "89.19391632080078\n",
      "0.6885505318641663\n",
      "0.11982045322656631\n",
      "0.11007291823625565\n",
      "0.10528462380170822\n",
      "0.10161882638931274\n",
      "0.09879907965660095\n",
      "0.09662991762161255\n",
      "0.09496115148067474\n",
      "0.09367774426937103\n"
     ]
    }
   ],
   "source": [
    "learning_rate = 1e-3\n",
    "for t in range(10000):\n",
    "    # Forward pass: compute predicted y using operations on Variables\n",
    "    loss = mse_loss(a,b,x,y)\n",
    "    if t % 1000 == 0: print(loss.data[0])\n",
    "    \n",
    "    # Computes the gradient of loss with respect to all Variables with requires_grad=True.\n",
    "    # After this call a.grad and b.grad will be Variables holding the gradient\n",
    "    # of the loss with respect to a and b respectively\n",
    "    loss.backward()\n",
    "    \n",
    "    # Update a and b using gradient descent; a.data and b.data are Tensors,\n",
    "    # a.grad and b.grad are Variables and a.grad.data and b.grad.data are Tensors\n",
    "    a.data -= learning_rate * a.grad.data\n",
    "    b.data -= learning_rate * b.grad.data\n",
    "    \n",
    "    # Zero the gradients\n",
    "    a.grad.data.zero_()\n",
    "    b.grad.data.zero_()    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nearly all of deep learning is powered by one very important algorithm: **stochastic gradient descent (SGD)**. SGD can be seeing as an approximation of **gradient descent** (GD). In GD you have to run through *all* the samples in your training set to do a single itaration. In SGD you use *only one* or *a subset*  of training samples to do the update for a parameter in a particular iteration. The subset use in every iteration is called a **batch** or **minibatch**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Gradient Descent - Classification"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For a fixed dataset $x$ and $y$ `mse_loss(a,b)` is a function of $a$ and $b$. We would like to find the values of $a$ and $b$ that minimize that function.\n",
    "\n",
    "**Gradient descent** is an algorithm that minimizes functions. Given a function defined by a set of parameters, gradient descent starts with an initial set of parameter values and iteratively moves toward a set of parameter values that minimize the function. This iterative minimization is achieved by taking steps in the negative direction of the function gradient.\n",
    "\n",
    "Here is gradient descent implemented in [PyTorch](http://pytorch.org/)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def gen_fake_data2(n, a, b):\n",
    "    x = s = np.random.uniform(0,1,n) \n",
    "    y = lin(a,b,x) + 0.1 * np.random.normal(0,3,n)\n",
    "    return x, np.where(y>10, 1, 0).astype(np.float32)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x,y = gen_fake_data2(10000, 3., 8.)\n",
    "x,y = V(x),V(y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def nll(y_hat, y):\n",
    "    y_hat = torch.clamp(y_hat, 1e-5, 1-1e-5)\n",
    "    return (y*y_hat.log() + (1-y)*(1-y_hat).log()).mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = V(np.random.randn(1), requires_grad=True)\n",
    "b = V(np.random.randn(1), requires_grad=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "ename": "RuntimeError",
     "evalue": "bool value of Variable objects containing non-empty torch.ByteTensor is ambiguous",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mAttributeError\u001b[0m                            Traceback (most recent call last)",
      "\u001b[0;32m~/anaconda3/lib/python3.6/site-packages/numpy/core/fromnumeric.py\u001b[0m in \u001b[0;36m_wrapfunc\u001b[0;34m(obj, method, *args, **kwds)\u001b[0m\n\u001b[1;32m     56\u001b[0m     \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 57\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0mgetattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     58\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m~/anaconda3/lib/python3.6/site-packages/torch/autograd/variable.py\u001b[0m in \u001b[0;36m__getattr__\u001b[0;34m(self, name)\u001b[0m\n\u001b[1;32m     64\u001b[0m             \u001b[0;32mreturn\u001b[0m \u001b[0mgetattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 65\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0mobject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__getattribute__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     66\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mAttributeError\u001b[0m: 'Variable' object has no attribute 'clip'",
      "\nDuring handling of the above exception, another exception occurred:\n",
      "\u001b[0;31mRuntimeError\u001b[0m                              Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-67-b1668cd3a12f>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      3\u001b[0m     \u001b[0mp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0mlin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mb\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexp\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      4\u001b[0m     \u001b[0my_hat\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0mp\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 5\u001b[0;31m     \u001b[0mloss\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnll\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my_hat\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      6\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0mt\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;36m1000\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      7\u001b[0m         \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mloss\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmean\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mto_np\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m==\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mto_np\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my_hat\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m>\u001b[0m\u001b[0;36m0.5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m<ipython-input-64-fbf65a879f37>\u001b[0m in \u001b[0;36mnll\u001b[0;34m(y_hat, y)\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mnll\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my_hat\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m     \u001b[0my_hat\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my_hat\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1e-5\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1e-5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      3\u001b[0m     \u001b[0;32mreturn\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0my_hat\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlog\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0my_hat\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlog\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmean\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m~/anaconda3/lib/python3.6/site-packages/numpy/core/fromnumeric.py\u001b[0m in \u001b[0;36mclip\u001b[0;34m(a, a_min, a_max, out)\u001b[0m\n\u001b[1;32m   1705\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1706\u001b[0m     \"\"\"\n\u001b[0;32m-> 1707\u001b[0;31m     \u001b[0;32mreturn\u001b[0m \u001b[0m_wrapfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'clip'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ma_min\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ma_max\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1708\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1709\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m~/anaconda3/lib/python3.6/site-packages/numpy/core/fromnumeric.py\u001b[0m in \u001b[0;36m_wrapfunc\u001b[0;34m(obj, method, *args, **kwds)\u001b[0m\n\u001b[1;32m     65\u001b[0m     \u001b[0;31m# a downstream library like 'pandas'.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     66\u001b[0m     \u001b[0;32mexcept\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mAttributeError\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 67\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0m_wrapit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     68\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     69\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m~/anaconda3/lib/python3.6/site-packages/numpy/core/fromnumeric.py\u001b[0m in \u001b[0;36m_wrapit\u001b[0;34m(obj, method, *args, **kwds)\u001b[0m\n\u001b[1;32m     45\u001b[0m     \u001b[0;32mexcept\u001b[0m \u001b[0mAttributeError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     46\u001b[0m         \u001b[0mwrap\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 47\u001b[0;31m     \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mgetattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0masarray\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     48\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0mwrap\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     49\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mresult\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmu\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndarray\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m~/anaconda3/lib/python3.6/site-packages/torch/autograd/variable.py\u001b[0m in \u001b[0;36m__bool__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m    121\u001b[0m             \u001b[0;32mreturn\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    122\u001b[0m         raise RuntimeError(\"bool value of Variable objects containing non-empty \" +\n\u001b[0;32m--> 123\u001b[0;31m                            torch.typename(self.data) + \" is ambiguous\")\n\u001b[0m\u001b[1;32m    124\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    125\u001b[0m     \u001b[0m__nonzero__\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m__bool__\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mRuntimeError\u001b[0m: bool value of Variable objects containing non-empty torch.ByteTensor is ambiguous"
     ]
    }
   ],
   "source": [
    "learning_rate = 1e-2\n",
    "for t in range(3000):\n",
    "    p = (-lin(a,b,x)).exp()\n",
    "    y_hat = 1/(1+p)\n",
    "    loss = nll(y_hat,y)\n",
    "    if t % 1000 == 0:\n",
    "        print(loss.data[0], np.mean(to_np(y)==(to_np(y_hat)>0.5)))\n",
    "#         print(y_hat)\n",
    "    \n",
    "    loss.backward()\n",
    "    a.data -= learning_rate * a.grad.data\n",
    "    b.data -= learning_rate * b.grad.data\n",
    "    a.grad.data.zero_()\n",
    "    b.grad.data.zero_()    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nearly all of deep learning is powered by one very important algorithm: **stochastic gradient descent (SGD)**. SGD can be seeing as an approximation of **gradient descent** (GD). In GD you have to run through *all* the samples in your training set to do a single itaration. In SGD you use *only one* or *a subset*  of training samples to do the update for a parameter in a particular iteration. The subset use in every iteration is called a **batch** or **minibatch**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Gradient descent with numpy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from matplotlib import rcParams, animation, rc\n",
    "from ipywidgets import interact, interactive, fixed\n",
    "from ipywidgets.widgets import *\n",
    "rc('animation', html='html5')\n",
    "rcParams['figure.figsize'] = 3, 3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x, y = gen_fake_data(50, 3., 8.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "65.167827371047636"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a_guess,b_guess = -1., 1.\n",
    "mse_loss(a_guess, b_guess, x, y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "lr=0.01\n",
    "def upd():\n",
    "    global a_guess, b_guess\n",
    "    y_pred = lin(a_guess, b_guess, x)\n",
    "    dydb = 2 * (y_pred - y)\n",
    "    dyda = x*dydb\n",
    "    a_guess -= lr*dyda.mean()\n",
    "    b_guess -= lr*dydb.mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<video width=\"500\" height=\"400\" controls autoplay loop>\n",
       "  <source type=\"video/mp4\" src=\"data:video/mp4;base64,AAAAHGZ0eXBNNFYgAAACAGlzb21pc28yYXZjMQAAAAhmcmVlAABDlW1kYXQAAAKvBgX//6vcRem9\n",
       "5tlIt5Ys2CDZI+7veDI2NCAtIGNvcmUgMTQ4IHIyNjk5IGE1ZTA2YjkgLSBILjI2NC9NUEVHLTQg\n",
       "QVZDIGNvZGVjIC0gQ29weWxlZnQgMjAwMy0yMDE2IC0gaHR0cDovL3d3dy52aWRlb2xhbi5vcmcv\n",
       "eDI2NC5odG1sIC0gb3B0aW9uczogY2FiYWM9MSByZWY9MyBkZWJsb2NrPTE6MDowIGFuYWx5c2U9\n",
       "MHgzOjB4MTEzIG1lPWhleCBzdWJtZT03IHBzeT0xIHBzeV9yZD0xLjAwOjAuMDAgbWl4ZWRfcmVm\n",
       "PTEgbWVfcmFuZ2U9MTYgY2hyb21hX21lPTEgdHJlbGxpcz0xIDh4OGRjdD0xIGNxbT0wIGRlYWR6\n",
       "b25lPTIxLDExIGZhc3RfcHNraXA9MSBjaHJvbWFfcXBfb2Zmc2V0PS0yIHRocmVhZHM9MTIgbG9v\n",
       "a2FoZWFkX3RocmVhZHM9MiBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxh\n",
       "Y2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHly\n",
       "YW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3\n",
       "ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTEwIHNjZW5lY3V0PTQwIGludHJhX3JlZnJl\n",
       "c2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAg\n",
       "cXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAa+GWI\n",
       "hAAQ//73gb8yy18iuslx+ed9LKzPPOQ8cl2JrrjQAAADAAADAnTFsu53XQ2mHYAAAqwv/wWSuLYB\n",
       "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\n",
       "MjUuMTAw\n",
       "\">\n",
       "  Your browser does not support the video tag.\n",
       "</video>"
      ],
      "text/plain": [
       "<matplotlib.animation.FuncAnimation at 0x7f8f8e81ce48>"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fig = plt.figure(dpi=100, figsize=(5, 4))\n",
    "plt.scatter(x,y)\n",
    "line, = plt.plot(x,lin(a_guess,b_guess,x))\n",
    "plt.close()\n",
    "\n",
    "def animate(i):\n",
    "    line.set_ydata(lin(a_guess,b_guess,x))\n",
    "    for i in range(30): upd()\n",
    "    return line,\n",
    "\n",
    "ani = animation.FuncAnimation(fig, animate, np.arange(0, 20), interval=100)\n",
    "ani"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}