{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Jacobiana y hessiana, lo justo\n",
    "\n",
    "Qué son las derivadas de segundo orden, qué mide la curvatura, y por qué saberlo explica que el entrenamiento vaya lento.\n",
    "\n",
    "Cuaderno de práctica del capítulo 16 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/jacobiana-y-hessiana/\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: \"W1sgIDEuOTk5NCAgMjMuMzUgIF0KIFsgMjMuMzUgICAzMzkuNjA5N11dCmNvaW5jaWRlIGNvbiBsYSBleGFjdGE6IFRydWU=\",\n",
    "    2: \"c2luIGVzY2FsYXIgLT4gYXV0b3ZhbG9yZXMgWyAgMC4zOTI0IDM0MS4yMTUzXSB8IGNvbmRpY2lvbiA4NjkuNTUKZXNjYWxhZG8gICAtPiBhdXRvdmFsb3JlcyBbMi4gMi5dIHwgY29uZGljaW9uIDEuMA==\",\n",
    "    3: \"YXV0b3ZhbG9yZXM6IFstMi4gIDIuXQpoYXkgbWluaW1vIDogRmFsc2U=\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Este capítulo es corto a propósito 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Jacobiana y hessiana tienen fama de tema avanzado, y para lo que tú necesitas\n",
    "son dos ideas simples. Te las doy y seguimos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Volvemos a abrir el archivo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cada capítulo arranca de cero, así que estas líneas se repiten. Son las\n",
    "mismas del capítulo 1 y las vas a ver en todos."
   ]
  },
  {
   "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",
    "print('X.shape:', X.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La jacobiana: cuando la función devuelve varios números"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El gradiente del capítulo 14 es de una función\n",
    "que devuelve **un** número, el error. Si la función devuelve varios,\n",
    "hace falta una derivada por cada entrada y cada salida. Eso es una matriz, y se\n",
    "llama **jacobiana**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = X.std(axis=0)\n",
    "J = np.diag(1 / s)\n",
    "print('desviaciones:', np.round(s, 4))\n",
    "print('jacobiana del escalado:')\n",
    "print(np.round(J, 6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esa es la jacobiana de escalar tus columnas: entran tres números y salen tres.\n",
    "Sale diagonal porque cada columna se escala sola, sin mirar a las otras.\n",
    "\n",
    "Y hay un caso que ya conoces: la jacobiana de multiplicar por una matriz\n",
    "**es esa misma matriz**. Si tu función es Wx, su jacobiana es W. Por\n",
    "eso en una red los pesos aparecen tal cual en la retropropagación del capítulo 15: la cadena va multiplicando jacobianas, y las\n",
    "jacobianas de las capas lineales son los propios pesos 🐣"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La hessiana: la curvatura del valle"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La derivada dice la pendiente. La **derivada segunda** dice\n",
    "cuánto cambia esa pendiente, o sea si el valle es cerrado y empinado o abierto y\n",
    "plano."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y = X[:, 1]\n",
    "A = np.column_stack([np.ones(len(y)), X[:, 0]])\n",
    "H = 2 * (A.T @ A) / len(y)\n",
    "print('hessiana:')\n",
    "print(np.round(H, 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hij=∂2E∂wi∂wj\n",
    "\n",
    "la hessiana guarda las derivadas segundas: cuánto cambia la pendiente en una dirección cuando te mueves en otra, o sea la curvatura del valle"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es simétrica, como X⊤X en el capítulo 6, y por el mismo motivo. Y eso quiere decir que le\n",
    "podemos sacar autovalores con `eigh`, como en el capítulo 10."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sus autovalores dicen dos cosas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vals = np.linalg.eigvalsh(H)\n",
    "print('autovalores        :', np.round(vals, 4))\n",
    "print('todos positivos    :', bool(np.all(vals > 0)))\n",
    "print('numero de condicion:', round(float(vals.max() / vals.min()), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Todos positivos** quiere decir que en todas las direcciones la\n",
    "superficie sube. O sea que ahí hay un fondo de verdad y no una silla de montar.\n",
    "Cuando eso pasa en todo el espacio, el problema se llama\n",
    "**convexo**, y convexo quiere decir que hay un solo mínimo y no te\n",
    "puedes quedar atrapada.\n",
    "\n",
    "**El cociente entre el mayor y el menor** es el número de\n",
    "condición, el mismo del capítulo 8. Aquí vale 869, y traducido\n",
    "quiere decir que el valle es 869 veces más empinado en una dirección que en la\n",
    "otra.\n",
    "\n",
    "Imagínate un cañón largo y estrecho. Si caminas siempre en la dirección de\n",
    "máxima pendiente, rebotas de una pared a la otra y avanzas poquito hacia el\n",
    "fondo. Eso es el zigzag del descenso de gradiente, y ese 869 es el número que\n",
    "lo predice antes de que pase 🙃"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y por eso existe el método de Newton"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "w = np.array([500.0, 30.0])\n",
    "g = -2 * (A.T @ (y - A @ w)) / len(y)\n",
    "paso = np.linalg.solve(H, g)\n",
    "nuevo = w - paso\n",
    "optimo = np.linalg.solve(A.T @ A, A.T @ y)\n",
    "\n",
    "print('gradiente    :', np.round(g, 4))\n",
    "print('un paso da   :', round(float(nuevo[0]), 4), round(float(nuevo[1]), 4))\n",
    "print('el optimo es :', round(float(optimo[0]), 4), round(float(optimo[1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "𝐰nuevo=𝐰−H−1∇E\n",
    "\n",
    "el paso de Newton corrige el gradiente con la curvatura, y por eso llega al fondo mucho más rápido, aunque calcular la hessiana cuesta caro"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Un paso.** No cien, ni mil: uno, y clavado.\n",
    "\n",
    "Newton corrige el gradiente con la curvatura, así que sabe estirar el paso\n",
    "donde el valle es plano y acortarlo donde es empinado. Sin zigzag.\n",
    "\n",
    "Y entonces la pregunta obvia: si esto es tan bueno, ¿por qué las redes se\n",
    "entrenan con descenso de gradiente y no con Newton?\n",
    "\n",
    "Por el tamaño. La hessiana de un modelo con un millón de pesos tiene un billón\n",
    "de casillas, y además hay que resolver un sistema con ella en cada paso. No cabe\n",
    "ni en la memoria ni en el presupuesto.\n",
    "\n",
    "Por eso existen los métodos que están en medio, como L-BFGS, que se inventan\n",
    "una hessiana aproximada, o como Adam, que guarda un resumen barato de la\n",
    "curvatura por cada peso. Todos intentan lo mismo: algo de Newton sin pagar\n",
    "Newton 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- La jacobiana es la matriz de derivadas primeras cuando salen varios\n",
    "números.\n",
    "\n",
    "- La jacobiana de multiplicar por W es W.\n",
    "\n",
    "- La hessiana son las derivadas segundas y mide la curvatura.\n",
    "\n",
    "- Autovalores todos positivos: hay un mínimo de verdad y el problema es\n",
    "convexo.\n",
    "\n",
    "- El número de condición dice cuánto vas a zigzaguear.\n",
    "\n",
    "- Newton llega en un paso y no se usa porque la hessiana no cabe."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprueba que se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Los autovalores de la hessiana de tu error son [0.39, 341.22]. ¿Qué esperas que le pase al descenso de gradiente?\n",
    "\n",
    "a) Que zigzaguee, porque el valle es 869 veces más empinado en una dirección que en la otra\n",
    "\n",
    "b) Que vaya derecho al fondo, porque los dos son positivos\n",
    "\n",
    "c) Que no converja nunca, porque uno es muy grande\n",
    "\n",
    "d) Que se quede en un mínimo local"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. La hessiana a lo bruto\n",
    "\n",
    "Sácala midiendo, como hicimos con la derivada."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Escala y mira cómo se arregla el cañón\n",
    "\n",
    "El número de condición no es del problema: es de cómo lo\n",
    "escribiste."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Una silla de montar\n",
    "\n",
    "Cuando los autovalores tienen signos distintos, no hay ni\n",
    "mínimo ni máximo."
   ]
  },
  {
   "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 16 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/jacobiana-y-hessiana/\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
}
