{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Qué mide una derivada, con un caso tuyo\n",
    "\n",
    "La derivada sin límites ni epsilones: a cuánto sube el error por cada paso, calculada sobre el ajuste de un peso de verdad.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 13 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/que-mide-una-derivada/\n",
    "\n",
    "Este es el cuaderno de **soluciones**. Trae el código de cada ejercicio, la\n",
    "explicación de la trampa y la respuesta del quiz. Si vienes del cuaderno de\n",
    "práctica sin haberlo intentado, vuelve 🙂"
   ]
  },
  {
   "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": [
    "## Empezamos por el problema, no por la definición 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 9 saqué el mejor peso de una regresión resolviendo un\n",
    "sistema. Salió 55.34 y salió de una vez, sin buscar.\n",
    "\n",
    "Eso solo se puede hacer con modelos lineales. En cuanto el modelo se\n",
    "complica un poco, no hay fórmula: hay que **buscar**. Y para\n",
    "buscar hace falta saber, desde donde estás parada, hacia dónde está el\n",
    "fondo.\n",
    "\n",
    "Eso es una derivada. No hace falta nada más para entenderla."
   ]
  },
  {
   "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": [
    "## Una función que quiero minimizar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y = X[:, 1]\n",
    "u = X[:, 0]\n",
    "\n",
    "def error(w):\n",
    "    return float(((y - w * u) ** 2).mean())\n",
    "\n",
    "for w in (0, 25, 50, 55, 60, 100):\n",
    "    print('  w =', w, '-> error', round(error(w), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "E(w)=1n∑i=1n(yi−wxi)2\n",
    "\n",
    "el error es el promedio de las diferencias al cuadrado entre lo que pasó y lo que el modelo predijo con ese peso"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira la forma de esos números: baja, baja, baja, toca fondo entre 55 y 60, y\n",
    "vuelve a subir. Es un valle.\n",
    "\n",
    "Y el trabajo entero de entrenar un modelo es **encontrar el fondo de\n",
    "ese valle**. Nada más 🐣"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La derivada, medida a lo bruto"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si estoy parada en un punto y quiero saber hacia dónde baja, doy un pasito\n",
    "chiquito para cada lado y miro:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "h = 1e-5\n",
    "\n",
    "def pendiente(w):\n",
    "    return (error(w + h) - error(w - h)) / (2 * h)\n",
    "\n",
    "for w in (20.0, 55.0, 90.0):\n",
    "    print('  w =', w, '-> pendiente', round(pendiente(w), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "f′(w)=limh→0f(w+h)−f(w−h)2h\n",
    "\n",
    "la derivada es a cuánto sube la función por cada paso que das, midiendo con un paso tan pequeño que ya no cambia el resultado"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora léelo, que es todo lo que hay que saber:\n",
    "\n",
    "- En 20 la pendiente es **muy negativa**. Estoy en la ladera\n",
    "izquierda, lejos, y el error baja fuerte si avanzo.\n",
    "\n",
    "- En 55 sigue negativa pero **chiquita**. Estoy cerca del fondo,\n",
    "casi plano.\n",
    "\n",
    "- En 90 es **positiva**. Me pasé, estoy en la otra ladera, y\n",
    "avanzar más me sube el error.\n",
    "\n",
    "**El signo dice la dirección y el tamaño dice la inclinación.**\n",
    "Eso es una derivada. Lo del límite y la h que tiende a cero es la forma\n",
    "matemática de decir \"un paso tan chiquito que ya da igual\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La misma derivada, calculada de verdad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo de arriba funciona pero es caro: dos evaluaciones por cada peso, y un\n",
    "modelo tiene millones. Se puede sacar la fórmula:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def pendiente_exacta(w):\n",
    "    return float((-2 * u * (y - w * u)).mean())\n",
    "\n",
    "for w in (20.0, 55.0, 90.0):\n",
    "    print('  w =', w, '| exacta', round(pendiente_exacta(w), 4),\n",
    "          '| numerica', round(pendiente(w), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Clavadas. Y esto que acabas de ver tiene nombre en el oficio: se llama\n",
    "**gradient checking**. Cuando programas una derivada a mano, la\n",
    "comparas contra la numérica para saber si te equivocaste. Es lo primero que se\n",
    "hace cuando una red no aprende y no sabes por qué 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El fondo es donde la derivada vale cero"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimo = float((u @ y) / (u @ u))\n",
    "print('minimo exacto        :', round(optimo, 4))\n",
    "print('pendiente ahi        :', round(pendiente_exacta(optimo), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y fíjate de dónde salió ese 55.3417: es **exactamente** el\n",
    "coeficiente de proyección del ejercicio 2 del capítulo 9.\n",
    "\n",
    "Dos caminos completamente distintos, el mismo número. Por geometría es la\n",
    "sombra más cercana; por cálculo es el fondo del valle. Son la misma cosa mirada\n",
    "de dos maneras, y esa coincidencia es de las más bonitas que hay en este\n",
    "libro 💛\n",
    "\n",
    "Y ese menos cero de la pendiente es coma flotante otra vez, lo mismo del capítulo 6: el número no es cero exacto, es algo como diez a\n",
    "la menos catorce, y al redondear se queda el signo. Para lo que nos importa, es\n",
    "el fondo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- La derivada es a cuánto sube la función por cada paso.\n",
    "\n",
    "- Signo negativo: el error baja si avanzas. Positivo: sube.\n",
    "\n",
    "- El tamaño dice la inclinación, no cuánto hay que moverse.\n",
    "\n",
    "- Se puede medir a lo bruto con dos evaluaciones, y eso es gradient\n",
    "checking.\n",
    "\n",
    "- En el fondo del valle la derivada vale cero.\n",
    "\n",
    "- El fondo del valle y la proyección más cercana son el mismo punto."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprueba que se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Estás ajustando un peso y la derivada del error en ese punto vale -12002. ¿Qué haces?\n",
    "\n",
    "a) Subir el peso, porque pendiente negativa quiere decir que el error baja yendo hacia la derecha\n",
    "\n",
    "b) Bajar el peso, porque el número es negativo\n",
    "\n",
    "c) Dejarlo, porque el error ya está bajando solo\n",
    "\n",
    "d) Subir el peso 12002 unidades\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* El signo del número no es la dirección en la que hay que ir: es la dirección en la que el error SUBE. Para bajar hay que ir al revés.\n",
    "\n",
    "*c)* El error no baja solo. La derivada te dice hacia dónde bajaría si tú movieras el peso, y moverlo es cosa tuya.\n",
    "\n",
    "*d)* La derivada dice la dirección y la inclinación, no cuánto moverse. Cuánto moverse lo decide la tasa de aprendizaje, y eso es el capítulo 17.\n",
    "\n",
    "Pendiente negativa quiere decir"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Baja el valle a mano\n",
    "\n",
    "Da cinco pasos en contra de la pendiente y mira qué pasa."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y, u = X[:, 1], X[:, 0]\n",
    "\n",
    "def error(w):\n",
    "    return float(((y - w * u) ** 2).mean())\n",
    "\n",
    "def pendiente_exacta(w):\n",
    "    return float((-2 * u * (y - w * u)).mean())\n",
    "\n",
    "w = 0.0\n",
    "for paso in range(5):\n",
    "    w = w - 0.00001 * pendiente_exacta(w)\n",
    "    print('paso', paso + 1, '| w =', round(w, 4), '| error =', round(error(w), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "paso 1 | w = 0.1879 | error = 1209918.24\n",
    "paso 2 | w = 0.3753 | error = 1206415.83\n",
    "paso 3 | w = 0.5619 | error = 1202937.17\n",
    "paso 4 | w = 0.748 | error = 1199482.1\n",
    "paso 5 | w = 0.9334 | error = 1196050.45\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya acabas de programar el descenso de gradiente y todavía faltan cuatro\n",
    "capítulos para que lo llamemos así. Va lento porque el paso es chiquito; subirlo\n",
    "tiene su propio peligro y es el capítulo 18."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. El paso demasiado chico también miente\n",
    "\n",
    "Prueba la derivada numérica con varias h."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y, u = X[:, 1], X[:, 0]\n",
    "\n",
    "def error(w):\n",
    "    return float(((y - w * u) ** 2).mean())\n",
    "\n",
    "for h in (1.0, 0.01, 1e-5, 1e-10, 1e-14):\n",
    "    d = (error(55.0 + h) - error(55.0 - h)) / (2 * h)\n",
    "    print('h =', h, '-> pendiente', round(d, 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "h = 1.0 -> pendiente -116.0403\n",
    "h = 0.01 -> pendiente -116.0403\n",
    "h = 1e-05 -> pendiente -116.0403\n",
    "h = 1e-10 -> pendiente -115.2512\n",
    "h = 1e-14 -> pendiente 0.0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con h de una diezmilbillonésima sale cero, que es una respuesta\n",
    "completamente falsa. Los dos números que restas son tan parecidos que la coma\n",
    "flotante los ve iguales, y cero entre cero es lo que hay. Más chico no es más\n",
    "preciso: hay un punto donde empeora, y para gradient checking el sitio cómodo\n",
    "está por 1e-5."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Dibuja el valle sin dibujar nada\n",
    "\n",
    "Con la tabla de errores basta para ver la forma."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y, u = X[:, 1], X[:, 0]\n",
    "\n",
    "def error(w):\n",
    "    return float(((y - w * u) ** 2).mean())\n",
    "\n",
    "for w in range(40, 76, 5):\n",
    "    e = error(float(w))\n",
    "    print(str(w).rjust(3), '|', '#' * int((e - 693000) / 500))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    " 40 | ################################################################################\n",
    " 45 | #####################################\n",
    " 50 | ##########\n",
    " 55 | \n",
    " 60 | ########\n",
    " 65 | ################################\n",
    " 70 | #########################################################################\n",
    " 75 | ####################################################################################################################################\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un valle en caracteres, y el fondo se ve solo: en 55 la barra está vacía\n",
    "porque el error ahí ya es menor que los 693.000 que resté para dibujar.\n",
    "\n",
    "Y ahora la letra chica, que es de lo que quiero que te acuerdes. Ese 693.000\n",
    "me lo inventé yo para que el dibujo se viera. Si resto 690.000, el valle casi\n",
    "desaparece; si resto 693.400, todas las barras se disparan. **La forma del\n",
    "gráfico la elegí yo, no los datos.** Es exactamente el truco del eje que\n",
    "no empieza en cero, y se cuela en informes todos los días."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preguntas frecuentes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Qué es la pendiente de una recta?Cuánto sube la recta por cada paso que avanza. Si vale 2, cada vez que avanzas uno hacia la derecha la recta sube dos.\n",
    "\n",
    "¿Cuál es la fórmula de la pendiente?La resta de las alturas dividida entre la resta de las posiciones: lo que sube entre lo que avanza. Con dos puntos ya se puede calcular.\n",
    "\n",
    "¿Cómo se calcula la pendiente entre dos puntos?Se resta la segunda altura menos la primera, se resta la segunda posición menos la primera, y se dividen. El orden no importa mientras sea el mismo arriba y abajo.\n",
    "\n",
    "¿Qué es una derivada?La pendiente de una curva en un punto. Como una curva cambia de inclinación todo el rato, la derivada es la pendiente de la recta que la toca justo ahí.\n",
    "\n",
    "¿Para qué sirve una derivada en machine learning?Para saber hacia dónde mover un parámetro para que el error baje. Un modelo entrenando es alguien bajando una cuesta a oscuras, y la derivada es tantear con el pie hacia dónde baja."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 13 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/que-mide-una-derivada/\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
}
