{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Descenso de gradiente, a mano y después con NumPy\n",
    "\n",
    "El algoritmo con el que se entrena casi todo, escrito en cuatro líneas y corrido sobre la tabla de ventas hasta llegar al óptimo exacto.\n",
    "\n",
    "Cuaderno de práctica del capítulo 17 de **Matemática para machine learning**, de Miss Yera.\n",
    "\n",
    "Corre de arriba abajo. Si lo abres en Google Colab no necesitas instalar nada.\n",
    "\n",
    "Capítulo completo: https://missyera.com/guias/matematica-para-machine-learning/descenso-de-gradiente/\n",
    "\n",
    "Los ejercicios están al final y traen una celda vacía debajo de cada uno. Las\n",
    "respuestas viven en el cuaderno de soluciones, y merece la pena pelearse un\n",
    "rato antes de abrirlo 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Antes de empezar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.request\n",
    "\n",
    "# Este capitulo abre el archivo por su nombre, sin URL, que es lo que se hace\n",
    "# cuando lo tienes al lado. Aqui se baja primero para que exista.\n",
    "for nombre in ['ventas-miss-yera.csv']:\n",
    "    urllib.request.urlretrieve(f\"https://missyera.com/static/datasets/{nombre}\", nombre)\n",
    "    print(nombre, \"lista\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Antes de empezar\n",
    "\n",
    "Esta celda baja el ayudante que corrige tus ejercicios. Después, en cada\n",
    "ejercicio que se pueda corregir solo, vas a ver `%%revisa` arriba de la celda:\n",
    "escribe tu respuesta debajo, ejecuta, y te digo si te salió 💛"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.request\n",
    "\n",
    "# El ayudante de los cuadernos. Trae la corrección de los ejercicios y, en los\n",
    "# capítulos de consola, la celda mágica que ejecuta los comandos. Se baja en\n",
    "# vez de venir pegado aquí para que siempre sea el último.\n",
    "urllib.request.urlretrieve(\n",
    "    \"https://missyera.com/static/cuadernos/revisa.py\", \"revisa.py\")\n",
    "import revisa\n",
    "revisa.carga({\n",
    "    1: \"aXRlciAgMSB8IGxhcmdvIGRlbCBncmFkaWVudGUgMTYwNy4xMzcyCml0ZXIgIDUgfCBsYXJnbyBkZWwgZ3JhZGllbnRlIDY1OC4yODIKaXRlciAxMCB8IGxhcmdvIGRlbCBncmFkaWVudGUgMjE1LjcwNTQKaXRlciAyMCB8IGxhcmdvIGRlbCBncmFkaWVudGUgMjMuMTYxMQppdGVyIDQwIHwgbGFyZ28gZGVsIGdyYWRpZW50ZSAwLjI2Nw==\",\n",
    "    2: \"ZGVzZGUgWzAuIDAuIDAuXSAtPiBsbGVnYSBhIFs4MDMuMzg2MyAgIDIuNjc5ICAtMTYuODMwNV0KZGVzZGUgWzUwMDAuIC0zMDAuICA5MDAuXSAtPiBsbGVnYSBhIFs4MDMuMzg2MyAgIDIuNjc5ICAtMTYuODMwNV0=\",\n",
    "    3: \"cGFzbyAgMTAwIHwgZXJyb3IgNTkzNTA3Ljc5CnBhc28gIDUwMCB8IGVycm9yIDU4NDI3My43MwpwYXNvIDEwMDAgfCBlcnJvciA1OTYyMjUuNzgKcGFzbyA0MDAwIHwgZXJyb3IgNTg1Njc0LjQ1\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuatro líneas y con eso se entrena casi todo 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya tienes las dos piezas. En el capítulo 14 viste\n",
    "que el gradiente apunta cuesta arriba. En el capítulo 13 bajaste\n",
    "un valle a mano sin saber que lo estabas haciendo.\n",
    "\n",
    "Esto es juntarlas y ponerle nombre."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "𝐰←𝐰−η∇E(𝐰)\n",
    "\n",
    "el descenso de gradiente resta el gradiente multiplicado por la tasa de aprendizaje, o sea da un paso en contra de la cuesta arriba y lo repite"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Símbolo | Qué es |\n",
    "|---|---|\n",
    "| **w** | los pesos |\n",
    "| **η** | la tasa de aprendizaje, la letra eta, que decide cuánto avanzas |\n",
    "| **∇E** | el gradiente, que apunta hacia donde el error sube |\n",
    "| **←** | quiere decir \"el nuevo valor de w es esto\" |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Volvemos a abrir el archivo, escalado"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Escalo las columnas desde el principio, y en el capítulo que viene vas a ver\n",
    "por qué no es opcional."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "df = pd.read_csv('ventas-miss-yera.csv')\n",
    "for c in ('unidades', 'monto', 'satisfaccion'):\n",
    "    df[c] = pd.to_numeric(df[c], errors='coerce')\n",
    "X = df[['unidades', 'monto', 'satisfaccion']].dropna().to_numpy()\n",
    "\n",
    "y = X[:, 1]\n",
    "F = X[:, [0, 2]]\n",
    "Zf = (F - F.mean(axis=0)) / F.std(axis=0)\n",
    "A = np.column_stack([np.ones(len(y)), Zf])\n",
    "n = len(y)\n",
    "print('A.shape:', A.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quiero predecir el monto con las unidades y la satisfacción. Ojo con esto:\n",
    "el monto sale de las columnas que predicen, **no** está dentro de\n",
    "ellas. Meter la respuesta entre las preguntas es de los errores más caros que\n",
    "hay, y en mi libro de machine learning tiene un capítulo entero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El algoritmo entero"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def error(w):\n",
    "    return float(((y - A @ w) ** 2).mean())\n",
    "\n",
    "def gradiente(w):\n",
    "    return -2 * (A.T @ (y - A @ w)) / n\n",
    "\n",
    "w = np.zeros(3)\n",
    "for i in range(1, 51):\n",
    "    w = w - 0.1 * gradiente(w)\n",
    "    if i in (1, 2, 5, 10, 25, 50):\n",
    "        print('  iter', str(i).rjust(2), '| error', round(error(w), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso es. Una resta dentro de un bucle.\n",
    "\n",
    "Y mira cómo baja: al principio a saltos enormes, después cada vez menos.\n",
    "Tiene sentido, porque cerca del fondo el gradiente es chiquito, así que el paso\n",
    "también. El algoritmo **frena solo** al llegar 🐣"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ¿Llegó al sitio correcto?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimo = np.linalg.solve(A.T @ A, A.T @ y)\n",
    "print('descenso :', np.round(w, 4))\n",
    "print('exacto   :', np.round(optimo, 4))\n",
    "print('distancia:', round(float(np.linalg.norm(w - optimo)), 6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cincuenta iteraciones y está a una centésima del óptimo que el capítulo 6 calcula de una sola vez con una fórmula.\n",
    "\n",
    "Y aquí la pregunta obvia: si la fórmula da el resultado exacto de un tirón,\n",
    "¿para qué el bucle?\n",
    "\n",
    "Porque la fórmula **solo existe para modelos lineales**. Para una\n",
    "red neuronal, para una regresión logística o para casi cualquier otra cosa, no\n",
    "hay fórmula. Solo queda buscar, y buscar es esto 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La tasa de aprendizaje, que es lo único que hay que elegir"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for tasa in (0.001, 0.01, 0.1, 0.5, 0.9, 1.01):\n",
    "    w = np.zeros(3)\n",
    "    for _ in range(200):\n",
    "        w = w - tasa * gradiente(w)\n",
    "    print('  tasa', tasa, '-> error', round(error(w), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lee esa tabla de arriba abajo, porque es el capítulo entero:\n",
    "\n",
    "- **0.001**: doscientas iteraciones y todavía va por 857.000.\n",
    "Llega, pero de aquí a mañana.\n",
    "\n",
    "- **0.01**: casi. Se queda a doscientos del fondo.\n",
    "\n",
    "- **0.1 a 0.9**: perfecto. Hay un rango cómodo y bastante\n",
    "ancho.\n",
    "\n",
    "- **1.01**: veintinueve mil millones. Explotó.\n",
    "\n",
    "Entre 0.9 y 1.01 no hay casi nada de diferencia, y una funciona y la otra\n",
    "revienta. Por qué existe ese borde tan filoso es el capítulo 18."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Las tres variantes que vas a oír nombrar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo que hicimos usa **todas** las filas en cada paso. Se llama\n",
    "descenso de gradiente por lotes, y con 2.238 filas va sobrado. Con veinte\n",
    "millones no.\n",
    "\n",
    "- **Estocástico**, o SGD: usa una fila cada vez. Cada paso es\n",
    "baratísimo y va dando tumbos, porque una sola fila es una opinión muy parcial\n",
    "sobre hacia dónde está el fondo.\n",
    "\n",
    "- **Por minilotes**: usa un puñado, típicamente entre 32 y 256.\n",
    "Es el que se usa de verdad en todas partes.\n",
    "\n",
    "- **Con momento, Adam y compañía**: le añaden memoria de los pasos\n",
    "anteriores para no rebotar tanto. Adam además le pone tasa propia a cada peso, y\n",
    "eso es lo que intenta arreglar lo que veremos de la curvatura en el capítulo 16.\n",
    "\n",
    "Todas son la misma línea. Lo único que cambia es con cuántas filas se calcula\n",
    "el gradiente y qué se recuerda entre pasos 🙂"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- El descenso de gradiente es restar el gradiente por la tasa, en un\n",
    "bucle.\n",
    "\n",
    "- Frena solo al acercarse, porque el gradiente se hace chiquito.\n",
    "\n",
    "- Llega al mismo sitio que la fórmula exacta, pero funciona donde no hay\n",
    "fórmula.\n",
    "\n",
    "- La tasa es lo único que eliges, y tiene un rango cómodo ancho con un borde\n",
    "filoso.\n",
    "\n",
    "- SGD, minilotes y Adam son la misma línea con distinta cantidad de filas y\n",
    "distinta memoria."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprueba que se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Entrenas con tasa 0.001 y el error baja muy despacio pero baja. Subes a 1.01 y el error se dispara a mil millones. ¿Qué pasó?\n",
    "\n",
    "a) El paso se pasó del fondo y cayó más arriba en la otra ladera, y eso se realimenta cada iteración\n",
    "\n",
    "b) El modelo se sobreajustó\n",
    "\n",
    "c) Los datos tienen valores extremos\n",
    "\n",
    "d) Hay que entrenar más iteraciones"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Mira el gradiente apagarse\n",
    "\n",
    "La prueba de que el algoritmo frena solo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Arranca desde otro sitio\n",
    "\n",
    "En un problema convexo da igual dónde empieces."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Estocástico contra por lotes\n",
    "\n",
    "Una fila por paso, y mira el temblor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 17 de **Matemática para machine learning**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/matematica-para-machine-learning/descenso-de-gradiente/\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
