{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Regularización vista como geometría: por qué L1 hace ceros y L2 no\n",
    "\n",
    "Ridge y lasso explicados desde las normas L1 y L2, con la diferencia geométrica que hace que una ponga ceros y la otra no.\n",
    "\n",
    "Cuaderno de práctica del capítulo 19 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/regularizacion-l1-y-l2/\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: \"YWxwaGEgICAgMC4wIHwgUk1TRSA0MzMuNjggfCBwZXNvIG1heW9yIDEwNDMuOQphbHBoYSAgICAxLjAgfCBSTVNFIDQzMy43NCB8IHBlc28gbWF5b3IgMzA0LjcKYWxwaGEgIDEwMC4wIHwgUk1TRSA0MzMuOTYgfCBwZXNvIG1heW9yIDIzMy40\",\n",
    "    2: \"YWxwaGEgICAgMC4wIHwgY29uZGljaW9uIDQwNDYyLjIKYWxwaGEgICAwLjAxIHwgY29uZGljaW9uIDM2MzY0LjcKYWxwaGEgICAgMS4wIHwgY29uZGljaW9uIDMyOTkuMAphbHBoYSAgMTAwLjAgfCBjb25kaWNpb24gMzYuOQ==\",\n",
    "    3: \"YWxwaGEgICAgMS4wIHwgcXVlZGFuIDUgOiBbJ3VuaWRhZGVzJywgJ21vbnRvJywgJ2Rlc2N1ZW50bycsICdzYXRpc2ZhY2Npb24nLCAnbW9udG9fY2FzaV9pZ3VhbCddCmFscGhhICAgIDUuMCB8IHF1ZWRhbiA0IDogWydtb250bycsICdkZXNjdWVudG8nLCAnc2F0aXNmYWNjaW9uJywgJ21vbnRvX2Nhc2lfaWd1YWwnXQphbHBoYSAgIDIwLjAgfCBxdWVkYW4gNCA6IFsnbW9udG8nLCAnZGVzY3VlbnRvJywgJ3NhdGlzZmFjY2lvbicsICdtb250b19jYXNpX2lndWFsJ10KYWxwaGEgICA2MC4wIHwgcXVlZGFuIDMgOiBbJ21vbnRvJywgJ3NhdGlzZmFjY2lvbicsICdtb250b19jYXNpX2lndWFsJ10KYWxwaGEgIDIwMC4wIHwgcXVlZGFuIDIgOiBbJ21vbnRvJywgJ21vbnRvX2Nhc2lfaWd1YWwnXQ==\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El capítulo donde se juntan casi todos los anteriores 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Acá vuelven las normas del capítulo 7, la redundancia entre\n",
    "columnas del capítulo 8 y la inversa del capítulo 6. Si algo de eso te quedó suelto, este es el\n",
    "capítulo que lo amarra."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Montamos el desastre a propósito"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Necesito dos columnas casi iguales, así que fabrico una: el monto escalado\n",
    "más un poquito de ruido."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "COLS = ['unidades', 'monto', 'descuento', 'satisfaccion',\n",
    "        'monto_final_facturado']\n",
    "df = pd.read_csv('ventas-miss-yera.csv')\n",
    "for c in COLS:\n",
    "    df[c] = pd.to_numeric(df[c], errors='coerce')\n",
    "d = df[COLS].dropna()\n",
    "\n",
    "y = d['monto_final_facturado'].to_numpy()\n",
    "F = d[['unidades', 'monto', 'descuento', 'satisfaccion']].to_numpy()\n",
    "Z = (F - F.mean(axis=0)) / F.std(axis=0)\n",
    "\n",
    "rng = np.random.default_rng(0)\n",
    "Z = np.column_stack([Z, Z[:, 1] + rng.normal(0, 0.01, len(Z))])\n",
    "NOMBRES = ['unidades', 'monto', 'descuento', 'satisfaccion',\n",
    "           'monto_casi_igual']\n",
    "yc = y - y.mean()\n",
    "n, dd = Z.shape\n",
    "\n",
    "print('Z.shape:', Z.shape)\n",
    "print('correlacion monto vs monto_casi_igual:',\n",
    "      round(float(np.corrcoef(Z[:, 1], Z[:, 4])[0, 1]), 6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "0.99995. Prácticamente la misma columna. En el capítulo 8 te\n",
    "dije que el caso feo no era la copia exacta sino la casi copia, porque no\n",
    "revienta. Acá está."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sin regularizar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def ridge(alpha):\n",
    "    return np.linalg.solve(Z.T @ Z + alpha * np.eye(dd), Z.T @ yc)\n",
    "\n",
    "for al in (0.0, 1.0, 10.0, 100.0):\n",
    "    print('alpha', str(al).rjust(6), np.round(ridge(al), 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira la primera fila. El monto tiene coeficiente **-565** y su\n",
    "copia casi idéntica tiene **+1044**.\n",
    "\n",
    "Eso, leído en una reunión, dice \"cuanto más factura una venta, menos factura\".\n",
    "Es un disparate, y sin embargo el modelo predice bien: las dos columnas se\n",
    "cancelan entre sí. El modelo encontró una solución que funciona y no significa\n",
    "nada 🙃\n",
    "\n",
    "Y ahora mira cómo con alpha 1 se convierten en 174 y 305. Los dos positivos,\n",
    "los dos razonables, y sumando parecido a lo que aporta la plata."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "EL2(𝐰)=‖𝐲−X𝐰‖22+α‖𝐰‖22\n",
    "\n",
    "ridge suma al error el cuadrado del largo de los pesos, así que pesos grandes salen caros y el modelo prefiere repartirlos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le sumas al error el largo de los pesos al cuadrado. A partir de ahí, una\n",
    "solución con pesos de 565 y 1044 **cuesta cara**, así que el modelo\n",
    "prefiere repartir."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for al in (0.0, 1.0, 10.0, 100.0):\n",
    "    print('alpha', str(al).rjust(6), 'norma de los pesos:',\n",
    "          round(float(np.linalg.norm(ridge(al))), 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De 1188 a 356 con alpha 1. Eso es lo que hace ridge y no hace nada más:\n",
    "**acortar el vector de pesos**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y fíjate dónde está el alpha"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "𝐰=(X⊤X+αI)−1X⊤𝐲\n",
    "\n",
    "la solución de ridge es la de siempre con alfa sumado en la diagonal, y ese sumando es justo lo que arregla que la matriz no se pueda invertir"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es la fórmula del capítulo 6 con un\n",
    "αI sumado en la diagonal.\n",
    "\n",
    "Y eso no es un detalle de implementación: es **exactamente** lo\n",
    "que arregla el problema. Ahí donde el determinante estaba pegado a cero y la\n",
    "inversa devolvía basura, sumarle algo a la diagonal lo separa del borde. Por eso\n",
    "ridge funciona con matrices que sin él no se pueden invertir 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ahora la otra: lasso"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "EL1(𝐰)=‖𝐲−X𝐰‖22+α‖𝐰‖1\n",
    "\n",
    "lasso suma la norma L1, que es la suma de valores absolutos, y esa forma es la que hace que algunos pesos acaben exactamente en cero"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def blando(x, t):\n",
    "    return np.sign(x) * np.maximum(np.abs(x) - t, 0.0)\n",
    "\n",
    "def lasso(alpha, iters=3000, tasa=0.001):\n",
    "    w = np.zeros(dd)\n",
    "    for _ in range(iters):\n",
    "        g = -2 * (Z.T @ (yc - Z @ w)) / n\n",
    "        w = blando(w - tasa * g, tasa * alpha)\n",
    "    return w\n",
    "\n",
    "for al in (0.0, 1.0, 10.0, 100.0):\n",
    "    w = lasso(al)\n",
    "    print('alpha', str(al).rjust(6), np.round(w, 3),\n",
    "          '| ceros:', int((np.abs(w) < 1e-8).sum()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Ceros exactos.** No 0.0001: cero pelado. Con alpha 10 las\n",
    "unidades desaparecen, y con alpha 100 también el descuento.\n",
    "\n",
    "Eso ridge no lo hace nunca. Encoge y encoge y encoge, y no llega. Y la\n",
    "diferencia está en la geometría."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Por qué L1 hace ceros y L2 no"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Acuérdate de las dos normas del capítulo 7. Piensa en el\n",
    "conjunto de todos los pesos que tienen norma 1, con dos pesos para poder\n",
    "imaginarlo:\n",
    "\n",
    "- Con la **L2**, ese conjunto es una **circunferencia**.\n",
    "Cuadrados y raíz: lisa por todos lados.\n",
    "\n",
    "- Con la **L1**, es un **rombo** con las puntas\n",
    "justo sobre los ejes. Suma de valores absolutos, que es lo que hace las\n",
    "esquinas.\n",
    "\n",
    "Ahora: regularizar es encontrar el punto donde el error es más chico\n",
    "**tocando esa figura**. Y una figura con esquinas se toca por la\n",
    "esquina, porque las esquinas sobresalen.\n",
    "\n",
    "Estar en una esquina del rombo quiere decir que **uno de los pesos vale\n",
    "cero**, porque las puntas están sobre los ejes. En la circunferencia no\n",
    "hay esquinas, así que el punto de contacto cae en cualquier sitio y ninguna\n",
    "coordenada tiene motivo para ser cero exacto.\n",
    "\n",
    "Ahí está toda la diferencia. No es que lasso sea más agresivo: es que su\n",
    "figura tiene puntas 🐣"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuál usar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "|  | Ridge, L2 | Lasso, L1 |\n",
    "|---|---|---|\n",
    "| Qué hace | encoge todos | pone algunos en cero |\n",
    "| Con columnas parecidas | reparte entre las dos | elige una y tira la otra |\n",
    "| Cuándo | todas aportan un poco | sospechas que muchas sobran |\n",
    "| Solución | fórmula cerrada | hay que iterar |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y las dos a la vez existe, se llama elastic net, y es lo que se usa cuando no\n",
    "sabes cuál de las dos historias es la tuya.\n",
    "\n",
    "Una advertencia sobre lasso con columnas casi iguales: **elige una y no\n",
    "te dice por qué**. Cambia una fila y puede elegir la otra. Si vas a\n",
    "presentar \"estas son las cinco variables que importan\", saber eso te ahorra un\n",
    "mal rato."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Regularizar es sumarle al error un castigo por pesos grandes.\n",
    "\n",
    "- Sin regularizar, dos columnas parecidas dan coeficientes enormes y de\n",
    "signos contrarios que se cancelan.\n",
    "\n",
    "- Ridge usa L2 y encoge todos; su fórmula es la de siempre con alpha en la\n",
    "diagonal, y por eso arregla la inversa.\n",
    "\n",
    "- Lasso usa L1 y pone ceros exactos.\n",
    "\n",
    "- La razón es geométrica: el rombo de la L1 tiene puntas sobre los ejes.\n",
    "\n",
    "- Lasso elige una entre columnas parecidas y no te dice por qué."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprueba que se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Sin regularizar, dos columnas casi idénticas te dan coeficientes -565 y +1044. Con ridge y alpha=1 se convierten en 174 y 305. ¿Qué pasó?\n",
    "\n",
    "a) El modelo dejó de compensar una columna con la otra, porque ahora los pesos grandes cuestan\n",
    "\n",
    "b) Ridge eliminó una de las dos columnas\n",
    "\n",
    "c) Ridge corrigió los datos, que estaban mal\n",
    "\n",
    "d) El modelo predice peor, y por eso los coeficientes son más chicos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Predice igual de bien, y eso es lo grave\n",
    "\n",
    "Compara el error de los coeficientes locos contra los\n",
    "sanos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. El alpha que arregla la inversa\n",
    "\n",
    "Mira el número de condición según el alpha."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Sigue el camino de lasso\n",
    "\n",
    "Sube el alpha poco a poco y mira caer las columnas."
   ]
  },
  {
   "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 19 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/regularizacion-l1-y-l2/\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
}
