{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Por qué a veces no converge: tasa, mínimos locales y convexidad\n",
    "\n",
    "Los tres motivos por los que un entrenamiento no llega, con el número exacto que separa la tasa que funciona de la que explota.\n",
    "\n",
    "Cuaderno de práctica del capítulo 18 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/por-que-no-converge/\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: \"bGltaXRlOiAwLjk4NjI4NwogIHRhc2EgMC45NzY0MjQgLT4gZXJyb3IgNTY3NzIzLjM5MTU0NTc2ODIKICB0YXNhIDAuOTk2MTUgLT4gZXJyb3IgMjg0NzExNjUuNTI3NTg0MjY=\",\n",
    "    2: \"aXRlciAxIHwgcGVzb3MgW25hbiBuYW4gbmFuXSB8IGN1YW50b3MgbmFuOiAzCml0ZXIgMiB8IHBlc29zIFtuYW4gbmFuIG5hbl0gfCBjdWFudG9zIG5hbjogMwppdGVyIDMgfCBwZXNvcyBbbmFuIG5hbiBuYW5dIHwgY3VhbnRvcyBuYW46IDM=\",\n",
    "    3: \"ZXNjYWxhZG8gIHwgY29uZGljaW9uIDEuMCB8IGl0ZXJhY2lvbmVzIDU0CmEgbWVkaWFzICB8IGNvbmRpY2lvbiAyNTAwLjUgfCBpdGVyYWNpb25lcyAxODg3OA==\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El borde filoso del capítulo anterior 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 17 la tasa 0.9 funcionaba y la 1.01 mandaba el\n",
    "error a veintinueve mil millones. Ese borde no es aleatorio: tiene un número\n",
    "exacto y se puede calcular."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Volvemos a abrir el archivo"
   ]
  },
  {
   "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": [
    "## Motivo uno: la tasa se pasó del límite"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "H = 2 * (A.T @ A) / n\n",
    "vals = np.linalg.eigvalsh(H)\n",
    "print('autovalores de la hessiana:', np.round(vals, 4))\n",
    "print('limite de la tasa         :', round(float(2 / vals.max()), 6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "η<2λmax\n",
    "\n",
    "la tasa tiene que ser menor que dos partido por el autovalor mayor de la hessiana, y si te pasas de ahí el error crece en vez de bajar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "0.9863. Ahí está el borde. La tasa 0.9 estaba justo debajo y la 1.01 justo\n",
    "encima, y por eso una funcionaba y la otra no.\n",
    "\n",
    "La intuición es la de una pelota en un tazón: si el paso es más del doble de\n",
    "lo que hace falta para llegar al fondo, acabas más arriba que donde\n",
    "empezaste. Y como el gradiente ahí es mayor, el siguiente paso es peor\n",
    "todavía. Se realimenta 🙃"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Motivo dos, y es el que te va a pasar: no escalaste"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "An = np.column_stack([np.ones(len(y)), F])\n",
    "Hn = 2 * (An.T @ An) / n\n",
    "vn = np.linalg.eigvalsh(Hn)\n",
    "print('sin escalar, autovalores:', np.round(vn, 4))\n",
    "print('limite de la tasa       :', float(2 / vn.max()))\n",
    "print('numero de condicion     :', round(float(vn.max() / vn.min()), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El límite pasó de 0.9863 a **0.0056**. Ciento setenta y seis\n",
    "veces más chico.\n",
    "\n",
    "Y ahora míralo pasar:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def error_n(w):\n",
    "    return float(((y - An @ w) ** 2).mean())\n",
    "\n",
    "for tasa in (0.1, 0.01, 0.001):\n",
    "    w = np.zeros(3)\n",
    "    roto = False\n",
    "    for _ in range(200):\n",
    "        w = w - tasa * (-2 * (An.T @ (y - An @ w)) / n)\n",
    "        if not np.all(np.isfinite(w)):\n",
    "            roto = True\n",
    "            break\n",
    "    print('  tasa', tasa, '->', 'exploto' if roto else round(error_n(w), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La tasa 0.1, que escalado iba perfecta, acá revienta. La 0.01 devuelve diez\n",
    "elevado a 169, que técnicamente no es infinito y para efectos prácticos lo es. Y\n",
    "la 0.001, que sí está por debajo del límite, llega a 633.065 cuando el óptimo es\n",
    "567.723.\n",
    "\n",
    "Ninguna de las tres sirve. Los mismos datos, el mismo modelo, la misma\n",
    "fórmula, y lo único que cambia es en qué unidades están medidas las\n",
    "columnas.\n",
    "\n",
    "**Escalar no es limpieza de datos.** Es lo que convierte un cañón\n",
    "estrecho en un tazón redondo, y esa es la diferencia entre poder entrenar y no\n",
    "poder. Es la cuarta vez en este libro que escalar arregla algo y ya no es\n",
    "coincidencia: es que la mitad de los problemas de este oficio son problemas de\n",
    "escala 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Motivo tres: la superficie no tiene un solo fondo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los dos motivos de arriba son de configuración y se arreglan. El tercero es\n",
    "de la forma del problema."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('autovalores escalado:', np.round(vals, 4))\n",
    "print('todos positivos     :', bool(np.all(vals > 0)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todos positivos en todo el espacio quiere decir **convexo**: un\n",
    "solo fondo, y llegas siempre, vengas de donde vengas. Eso lo comprobaste en el\n",
    "ejercicio 2 del capítulo 17.\n",
    "\n",
    "Toda regresión lineal con error cuadrático es convexa. La logística también.\n",
    "Y por eso con esos modelos el entrenamiento o converge o está mal configurado,\n",
    "no hay tercera opción.\n",
    "\n",
    "Una red neuronal **no** es convexa, y ahí aparecen dos cosas:\n",
    "\n",
    "- **Mínimos locales**: hoyos que no son el hoyo más hondo. Tienen\n",
    "mucha peor fama de la que merecen, porque en dimensiones altas son raros.\n",
    "\n",
    "- **Puntos de silla**: sube en unas direcciones y baja en otras,\n",
    "como el ejercicio 3 del capítulo 16. El gradiente ahí vale cero y\n",
    "el entrenamiento se queda quieto sin haber terminado. Estos sí son el problema\n",
    "de verdad, y son muchísimo más frecuentes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La lista para cuando no converja"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- **¿Escalaste?** Nueve de cada diez veces es esto.\n",
    "\n",
    "- **Baja la tasa entre diez.** Si con eso baja el error, era la\n",
    "tasa.\n",
    "\n",
    "- **Mira el largo del gradiente.** Si es cero pero el error sigue\n",
    "alto, es un punto de silla, no el fondo.\n",
    "\n",
    "- **Comprueba el gradiente contra el numérico**, como en el capítulo 13. Si no coinciden, la derivada está mal programada\n",
    "y no hay tasa que lo arregle.\n",
    "\n",
    "- **Mira si hay nan.** Un solo nan en los datos se propaga a todos\n",
    "los pesos en una iteración."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- La tasa tiene un límite exacto: dos partido por el autovalor mayor de la\n",
    "hessiana.\n",
    "\n",
    "- Sin escalar ese límite se hace diminuto, y ninguna tasa razonable\n",
    "funciona.\n",
    "\n",
    "- Escalar cambia la forma del valle, no los datos.\n",
    "\n",
    "- Los modelos lineales son convexos: convergen o están mal\n",
    "configurados.\n",
    "\n",
    "- En redes, los puntos de silla dan más problemas que los mínimos\n",
    "locales.\n",
    "\n",
    "- Cuando no converja: escalar, bajar la tasa, mirar el gradiente, buscar\n",
    "nan."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprueba que se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Con las columnas escaladas tu entrenamiento converge con tasa 0.1. Sin escalar, la misma tasa explota. ¿Por qué?\n",
    "\n",
    "a) Porque sin escalar el autovalor mayor de la hessiana es enorme, y el límite de la tasa se hace diminuto\n",
    "\n",
    "b) Porque sin escalar hay más ruido en los datos\n",
    "\n",
    "c) Porque escalar quita los valores extremos\n",
    "\n",
    "d) Porque escalar hace el modelo más simple"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Cruza el límite y míralo explotar\n",
    "\n",
    "Justo debajo y justo encima del número exacto."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Un nan se come el modelo entero\n",
    "\n",
    "Mete un hueco y mira cuánto tarda en contaminarlo todo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. El número de condición predice cuántas iteraciones\n",
    "\n",
    "Compara escalado contra a medio escalar."
   ]
  },
  {
   "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 18 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/por-que-no-converge/\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
}
