{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c000",
   "metadata": {},
   "source": [
    "# L2 In-Class Activity: Gradient Descent \n",
    "\n",
    "### E190BO — Fall 2026\n",
    "\n",
    "This is the **Exercise** from the L2 slides, made executable. Same model, same loss,\n",
    "same three parts:\n",
    "\n",
    "> **Model**: $\\hat{y}_i = a x_i + b$\n",
    "> **Loss**: $\\text{MSE} = \\dfrac{1}{N} \\sum_{i=1}^{N} (y_i - \\hat{y}_i)^2$\n",
    ">\n",
    "> (a) Derive the gradient   (b) Implement in this notebook   (c) Plot MSE vs iteration for several $\\gamma$\n",
    "\n",
    "The data is `testScores.csv`: 25 students, three midterm scores and a final exam score.\n",
    "We will use **midterm 1 only** to predict the final.\n",
    "\n",
    "Work with a partner. Get through (a)-(c); the challenges at the end are there if you finish early."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c001",
   "metadata": {},
   "source": [
    "## Setup (provided — just run it)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "c002",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "N = 25 students\n"
     ]
    },
    {
     "data": {
      "image/png": "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rWiWn/I9/PDD3n31c3jVVVdZq1evtqKRp8t41sVTHv2p5cv6mmbNmpnyaSDu+/tn57KNGTPGSk5ONkGr/o516NDB/Ee4IGL0n5DUSQEAANgM4wgBAADXIhACAACuRSAEAABci0AIAAC4FoEQAABwLQIhAADgWgRCAADAtQiEAESUzhDtO11DOI9du3ZtmThxooTLzp07JSYmxjt1BgD7IRACXE4nHfXMy+Q0P/74o98M8IVBgzMNlrIu9913n9n+r3/9y8yR9emnn/q97qOPPpL4+HjZsGFDtrnmdP9GjRoFfD/P8ZcvX+63/uTJk1KhQgWzbcmSJd71o0aNMpMblyxZ0m/SYsAtCIQA2IoOhq8zvwdDJ1TVL/jC1q9fP0lJSfFbXnjhBbOtadOmMnToUBOw6WS6Sme610Bp+PDh2QKemTNnys033+ydoDaQGjVqmAmLfX388cdy3nnnZdv31KlT0rNnz4hNdAtEGwIhwAX+8Y9/SOPGjaVEiRKmVqBTp05y7NgxeeaZZ+TNN9+UTz75xFuT4KktePzxx+XCCy80gUSdOnXk6aefltOnT3uPqa/VWePffvtt0wSVkJAgt956q6Snp3v30fe46667zBewzgo9bty4bOemr2/VqpWULl3azEbfq1cvEwh46PnoeX355ZdmZvG4uDj57rvvgjq2b9OYBhCBama0HB7Tp0+XBg0amJqY+vXry8svv+x3vJUrV0rz5s3Ndj3nNWvWBHX99Rpq2XyXMmXKeLcPGTJEatasKffff795/pe//EUuuOACefTRR7MFgRrg3HnnneY6vf766wHfr3fv3vLee+/JiRMnvOveeOMNsz4rDbYeeeQR8/kAXClUE6YBiE579+41s4frbM46MajO8q6zUOuEobrcfPPNZmZ1naRWl5MnT5rXjRw50sw8rq/59NNPzYSUOtGhh85CrhOQ3njjjWb256VLl1qJiYnWE0884d2nf//+ZkJLnRFa3/eaa66xSpcubWaK9nj99detL774wsz+vWzZMqtt27ZW9+7ds03G2KRJE+urr74ys7rrBKjBHFsnqdVZqj0T2HrKqMu7775rroseU73zzjtW1apVrY8++sjM2q0/dTJHncVc6bXSyW979eplbdiwwZo3b56ZxFLPTWfJzolOFOl7TjnRmdJ1Akmd2LVEiRIBJ59dtGiRucZnzpwx11zLe/ToUb999Hw+/vhjc73efvtts05n8NaJX3/55ZdsE+F66GScCQkJeZ4n4DQEQoDDrVq1ynz57dy5M+B2ndX5uuuuy/M4Y8eOtVq2bOkXCJUsWdI6cuSId53Ort6mTRtv4KAzXuvM5R4awOiXfG6BwY8//mjO1zOzuycQmjt3rnefYI/tGwj50mBKg5wXXnjBu05nsp49e7bffhoMamCmXnnlFatChQrWiRMnvNunTp0aVCBUrFgxq1SpUn6LBl5ZDR482BzPN+D0pUGYzpbu0bRp02yziXsCoYkTJ1odO3Y064YPH27dcMMN1qFDhwiEgCxoGgMcTnNQrrzyStP0obkgr732mhw6dCjP173//vtyySWXmGYcbX566qmnZPfu3dmanrRJy0ObqDzNWtu2bTP5J23atPFuL1++vNSrV8/vGKtWrZIePXqYpiE91uWXX27WZ30vbYryCPbYgaSlpck111wjV199tTz22GNmnTaz6TH79u1ryupZnn32WbNebdq0SZo0aWKaxTzatm0rwbj99ttNzzLf5dprr/Xb5+jRo+aaazPaP//5z2zHOHz4sMyZM0fuuOMO7zp9nFPzmG5btmyZbN++3TQL/vnPfw7qXAG3KVrYJwAgvIoUKSILFy6UH374Qb766iuZPHmyPPnkkybRNikpKeBr9AtUv7w1f6Rr164m/0dzTrLm4RQrVszvuebcZGZmBn1uGoDo8XWZNWuWSW7WAEifa6Djq1SpUnKuzp49K7fccovJz3n11Vf9ghClQaJvcOW5fudKr1/dunVz3UeDMg2y9D5dfPHF8tZbb5kcKI/Zs2dLRkaG3/lpBZBe719++cXkc/nSXDAN+DS409d1797dL38LwH9QIwS4gAYoWrujgY0m+Gr3a+1FpPSxBgi+9Mu4Vq1aJmDSmhhN3N21a1e+3jM5OdkESr49m7QmSr+0PTZv3mx6Sj3//PNy2WWXmQRl30Tpczl2IJoUvH79epk7d65fzU6VKlWkWrVqpvZEAxbfxRMsahL1unXrTFDhkbWLekFpoKqJ2pq4rjV4WhOl4yFp7zIPrfkZOHCgX62Sdr3X66aJ0IFoLZAmm2tAFYqADnAiaoQAh9NgYdGiRdKlSxepXLmyeX7gwAHzxe5p3lqwYIFs2bLF1CJo7YUGPlozo7VAf/rTn+Tzzz/3Bk7B0qYlrY3Qmg49rr63Blaxsf/7/5c2h2kgprVU2l1cx8wZOXJkSI6dlfa20l5gWg4NDFNTU73H0kWDxL/97W+m/N26dTPj7vz0008mwBowYIDppaXvoV3htZeXDqb44osvBnUtjh8/7n0/D+39Vq5cOdMN3lMWvdaegE3PU7vUz5s3zwQ9q1evNrVmGiz6uu2222TEiBEmeCpa1P9PupZD77VvD7Ws9D4fPHjQ/NSA2DM4pAaBgbrbA46TNWkIgLNob6SuXbuaHk/ac+jCCy+0Jk+e7N2+f/9+q3PnzqYHmG+PIk181uRgXX/LLbeYpGPfXkWaLK3Jur50H01Q9k1qvuOOO0xStfY60+TkrL2oNEG5du3a5tw0MVl7qPkmIHuSpTXR11cwx/ZNltakcD1O1kXL4TFr1iyrWbNmJhG7XLlyVvv27a05c+Z4t2uvNi2zbtf9tGdZMMnSgd5X74m6++67rUaNGnl763loDy8t25tvvmk98MADVsOGDQMeX3vAxcbGWp988olfsnQggZKlc7ougXqWAU4Uo/8UdjAGAABQGMgRAgAArkUgBAAAXItACAAAuBaBEAAAcC0CIQAA4FoEQgAAwLUIhAAAgGsRCAEAANciEAIAAK5FIAQAAFyLQAgAALgWgRAAABC3+n/45+3slLCedwAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# --- PROVIDED --- load the data and standardize every column to zero mean, unit variance\n",
    "df = pd.read_csv(\"testScores.csv\").dropna(axis=1, how=\"all\")\n",
    "z  = (df - df.mean()) / df.std(ddof=0)\n",
    "\n",
    "x = z[\"EXAM1\"].values   # standardized midterm 1 score, shape (N,)\n",
    "y = z[\"FINAL\"].values   # standardized final exam score, shape (N,)\n",
    "N = len(x)\n",
    "\n",
    "print(f\"N = {N} students\")\n",
    "plt.scatter(x, y)\n",
    "plt.xlabel(\"standardized EXAM1\"); plt.ylabel(\"standardized FINAL\")\n",
    "plt.title(\"The data we are trying to fit\"); plt.grid(alpha=.3); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c003",
   "metadata": {},
   "source": [
    "## (a) Derive the gradient\n",
    "\n",
    "On paper, work out $\\dfrac{\\partial e}{\\partial a}$ and $\\dfrac{\\partial e}{\\partial b}$ for\n",
    "\n",
    "$$e = \\frac{1}{N} \\sum_{i=1}^{N} (y_i - a x_i - b)^2$$\n",
    "\n",
    "Do this **before** you write any code. You cannot implement a gradient you have not derived."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c004",
   "metadata": {},
   "source": [
    "*Write your two partial derivatives here (or show them to your instructor on paper).*\n",
    "\n",
    "$$\\frac{\\partial e}{\\partial a} = $$\n",
    "\n",
    "$$\\frac{\\partial e}{\\partial b} = $$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c005",
   "metadata": {},
   "source": [
    "## Plotting helpers (provided — just run it)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c006",
   "metadata": {},
   "outputs": [],
   "source": [
    "# --- PROVIDED --- plotting helpers. You do not need to modify these.\n",
    "\n",
    "def mse(a, b):\n",
    "    \"\"\"Mean squared error of the model yhat = a*x + b.\"\"\"\n",
    "    return np.mean((y - (a * x + b)) ** 2)\n",
    "\n",
    "def plot_loss(histories, labels):\n",
    "    \"\"\"histories: list of 1-D arrays of MSE vs iteration.\"\"\"\n",
    "    plt.figure(figsize=(7, 4))\n",
    "    for h, lab in zip(histories, labels):\n",
    "        plt.semilogy(np.clip(h, 1e-12, 1e12), label=lab)\n",
    "    plt.xlabel(\"iteration\"); plt.ylabel(\"MSE (log scale)\")\n",
    "    plt.legend(); plt.grid(alpha=.3); plt.title(\"MSE vs iteration\"); plt.show()\n",
    "\n",
    "def plot_trajectory(w_hist):\n",
    "    \"\"\"w_hist: array of shape (numIterations+1, 2) holding (a, b) at each step.\"\"\"\n",
    "    w_hist = np.asarray(w_hist)\n",
    "    lo, hi = -0.4, 1.4\n",
    "    A, B = np.meshgrid(np.linspace(lo, hi, 120), np.linspace(-1, 1, 120))\n",
    "    Z = np.array([[mse(aa, bb) for aa in A[0]] for bb in B[:, 0]])\n",
    "    plt.figure(figsize=(6, 5))\n",
    "    plt.contour(A, B, Z, levels=np.logspace(-1.2, 1.2, 20), alpha=.6)\n",
    "    plt.plot(w_hist[:, 0], w_hist[:, 1], \"o-\", ms=3, lw=1, color=\"crimson\")\n",
    "    plt.plot(w_hist[0, 0], w_hist[0, 1], \"ks\", ms=8, label=\"start\")\n",
    "    plt.plot(w_hist[-1, 0], w_hist[-1, 1], \"g*\", ms=14, label=\"end\")\n",
    "    plt.xlabel(\"a\"); plt.ylabel(\"b\"); plt.legend()\n",
    "    plt.title(\"trajectory of (a, b) over the loss surface\"); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c007",
   "metadata": {},
   "source": [
    "## (b) Implement gradient descent\n",
    "\n",
    "Fill in the three `TODO` lines below. They correspond exactly to the three blanks in the\n",
    "L2 slide skeleton. Start from $a = 0$, $b = 0$.\n",
    "\n",
    "*Hint: `x` and `y` are numpy arrays, so `y - a*x - b` computes all $N$ residuals at once —\n",
    "no `for` loop over data points needed.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c008",
   "metadata": {},
   "outputs": [],
   "source": [
    "def gradient_descent(gamma, numIterations=200):\n",
    "    \"\"\"Fit yhat = a*x + b by batch gradient descent, starting from a = b = 0.\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    w_hist    : (numIterations+1, 2) array of [a, b] at every iteration\n",
    "    mse_hist  : (numIterations+1,)   array of the MSE at every iteration\n",
    "    \"\"\"\n",
    "    w = np.array([0.0, 0.0])          # [a, b]\n",
    "    w_hist, mse_hist = [w.copy()], [mse(w[0], w[1])]\n",
    "\n",
    "    for i in range(numIterations):\n",
    "        a, b = w\n",
    "\n",
    "        # ============ TODO 1: the two partial derivatives from part (a) ============\n",
    "        agrad = ...\n",
    "        bgrad = ...\n",
    "\n",
    "        # ============ TODO 2: the gradient descent update rule =====================\n",
    "        w = ...\n",
    "        # ==========================================================================\n",
    "\n",
    "        w_hist.append(w.copy())\n",
    "        mse_hist.append(mse(w[0], w[1]))\n",
    "\n",
    "    return np.array(w_hist), np.array(mse_hist)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c009",
   "metadata": {},
   "source": [
    "Now run it with a reasonable learning rate and look at the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c010",
   "metadata": {},
   "outputs": [],
   "source": [
    "w_hist, mse_hist = gradient_descent(gamma=0.1, numIterations=200)\n",
    "a_hat, b_hat = w_hist[-1]\n",
    "print(f\"a = {a_hat:.4f},  b = {b_hat:.4f},  final MSE = {mse_hist[-1]:.4f}\")\n",
    "\n",
    "plot_loss([mse_hist], [\"gamma = 0.1\"])\n",
    "plot_trajectory(w_hist)\n",
    "\n",
    "# the fitted line on top of the data\n",
    "plt.scatter(x, y, label=\"data\")\n",
    "xs = np.linspace(x.min(), x.max(), 50)\n",
    "plt.plot(xs, a_hat * xs + b_hat, \"r-\", lw=2, label=f\"fit: y = {a_hat:.3f}x + {b_hat:.3f}\")\n",
    "plt.xlabel(\"standardized EXAM1\"); plt.ylabel(\"standardized FINAL\")\n",
    "plt.legend(); plt.grid(alpha=.3); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c011",
   "metadata": {},
   "source": [
    "## (c) The learning rate $\\gamma$\n",
    "\n",
    "Run gradient descent for several learning rates and plot all the MSE curves together.\n",
    "\n",
    "Use $\\gamma = 0.01,\\ 0.1,\\ 1.0,\\ 1.1$ and **only 60 iterations** (large $\\gamma$ blows up fast)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c012",
   "metadata": {},
   "outputs": [],
   "source": [
    "gammas = [0.01, 0.1, 1.0, 1.1]\n",
    "\n",
    "# ============ TODO 3: run gradient_descent for each gamma, collect the MSE ============\n",
    "#   Build a list `histories` of the mse_hist arrays, then call\n",
    "#   plot_loss(histories, [f\"gamma = {g}\" for g in gammas])\n",
    "# ====================================================================================="
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c013",
   "metadata": {},
   "source": [
    "### Discuss with your partner, then write your answers here\n",
    "\n",
    "1. Describe the shape of the loss curve for each $\\gamma$. Which one is \"too small\",\n",
    "   which is \"about right\", and which one fails?\n",
    "2. $\\gamma = 1.0$ does something unusual — it neither converges nor blows up. What is it\n",
    "   doing to the estimate at each step? *(Try plotting the trajectory for this case.)*\n",
    "3. **The important one:** suppose you are fitting a model where you do **not** know the right\n",
    "   answer in advance. Looking only at the MSE-vs-iteration plot, how would you diagnose\n",
    "   each of these three failure modes — too small, too large, and just right?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c014",
   "metadata": {},
   "source": [
    "*Your answers here.*\n",
    "\n",
    "1.\n",
    "\n",
    "2.\n",
    "\n",
    "3."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c015",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "# Challenges (optional)\n",
    "\n",
    "Only if you have finished (a)-(c)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c016",
   "metadata": {},
   "source": [
    "## C1 — A different loss function\n",
    "\n",
    "Redo part (b) with **mean absolute error** instead of mean squared error:\n",
    "\n",
    "$$e = \\frac{1}{N}\\sum_{i=1}^{N} |y_i - a x_i - b|$$\n",
    "\n",
    "- Derive the gradient. Where is it undefined, and what should your code do there?\n",
    "- How does the convergence behavior differ from MSE? Does the same $\\gamma$ still work?\n",
    "- Change one $y$ value to a wild outlier (say, $+10$). Refit with both losses.\n",
    "  Which fit moves more, and why?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c017",
   "metadata": {},
   "outputs": [],
   "source": [
    "# C1: your code here"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c018",
   "metadata": {},
   "source": [
    "## C2 — A different model\n",
    "\n",
    "Fit a **quadratic** $\\hat{y}_i = a x_i^2 + b x_i + c$ by gradient descent.\n",
    "\n",
    "- Derive all three partial derivatives.\n",
    "- Find a $\\gamma$ that works. Is the usable range of $\\gamma$ wider or narrower than it was\n",
    "  for the straight line?\n",
    "- Now try it on the **raw, unstandardized** EXAM1 scores (values around 70-100, so $x^2$ is\n",
    "  around 5000). What happens, and what does that tell you about why we standardized the data\n",
    "  in the first place?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c019",
   "metadata": {},
   "outputs": [],
   "source": [
    "# C2: your code here"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.13.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
