Empezamos por el problema, no por la definición 🌸
En el capítulo 9 saqué el mejor peso de una regresión resolviendo un sistema. Salió 55.34 y salió de una vez, sin buscar.
Eso solo se puede hacer con modelos lineales. En cuanto el modelo se complica un poco, no hay fórmula: hay que buscar. Y para buscar hace falta saber, desde donde estás parada, hacia dónde está el fondo.
Eso es una derivada. No hace falta nada más para entenderla.
Volvemos a abrir el archivo
Cada capítulo arranca de cero, así que estas líneas se repiten. Son las mismas del capítulo 1 y las vas a ver en todos.
import pandas as pd import numpy as np df = pd.read_csv('ventas-miss-yera.csv') for c in ('unidades', 'monto', 'satisfaccion'): df[c] = pd.to_numeric(df[c], errors='coerce') X = df[['unidades', 'monto', 'satisfaccion']].dropna().to_numpy() print('X.shape:', X.shape)
X.shape: (2238, 3)
Una función que quiero minimizar
y = X[:, 1] u = X[:, 0] def error(w): return float(((y - w * u) ** 2).mean()) for w in (0, 25, 50, 55, 60, 100): print(' w =', w, '-> error', round(error(w), 2))
w = 0 -> error 1213444.56 w = 25 -> error 849710.39 w = 50 -> error 698231.02 w = 55 -> error 693405.72 w = 60 -> error 697070.61 w = 100 -> error 1032036.69
el error es el promedio de las diferencias al cuadrado entre lo que pasó y lo que el modelo predijo con ese peso
Mira la forma de esos números: baja, baja, baja, toca fondo entre 55 y 60, y vuelve a subir. Es un valle.
Y el trabajo entero de entrenar un modelo es encontrar el fondo de ese valle. Nada más 🐣
La derivada, medida a lo bruto
Si estoy parada en un punto y quiero saber hacia dónde baja, doy un pasito chiquito para cada lado y miro:
h = 1e-5 def pendiente(w): return (error(w + h) - error(w - h)) / (2 * h) for w in (20.0, 55.0, 90.0): print(' w =', w, '-> pendiente', round(pendiente(w), 4))
w = 20.0 -> pendiente -12002.3093 w = 55.0 -> pendiente -116.0403 w = 90.0 -> pendiente 11770.2287
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
Y ahora léelo, que es todo lo que hay que saber:
- En 20 la pendiente es muy negativa. Estoy en la ladera izquierda, lejos, y el error baja fuerte si avanzo.
- En 55 sigue negativa pero chiquita. Estoy cerca del fondo, casi plano.
- En 90 es positiva. Me pasé, estoy en la otra ladera, y avanzar más me sube el error.
El signo dice la dirección y el tamaño dice la inclinación. Eso es una derivada. Lo del límite y la h que tiende a cero es la forma matemática de decir "un paso tan chiquito que ya da igual".
La misma derivada, calculada de verdad
Lo de arriba funciona pero es caro: dos evaluaciones por cada peso, y un modelo tiene millones. Se puede sacar la fórmula:
def pendiente_exacta(w): return float((-2 * u * (y - w * u)).mean()) for w in (20.0, 55.0, 90.0): print(' w =', w, '| exacta', round(pendiente_exacta(w), 4), '| numerica', round(pendiente(w), 4))
w = 20.0 | exacta -12002.3093 | numerica -12002.3093 w = 55.0 | exacta -116.0403 | numerica -116.0403 w = 90.0 | exacta 11770.2287 | numerica 11770.2287
Clavadas. Y esto que acabas de ver tiene nombre en el oficio: se llama gradient checking. Cuando programas una derivada a mano, la comparas contra la numérica para saber si te equivocaste. Es lo primero que se hace cuando una red no aprende y no sabes por qué 🌟
El fondo es donde la derivada vale cero
optimo = float((u @ y) / (u @ u)) print('minimo exacto :', round(optimo, 4)) print('pendiente ahi :', round(pendiente_exacta(optimo), 4))
minimo exacto : 55.3417 pendiente ahi : -0.0
Y fíjate de dónde salió ese 55.3417: es exactamente el coeficiente de proyección del ejercicio 2 del capítulo 9.
Dos caminos completamente distintos, el mismo número. Por geometría es la sombra más cercana; por cálculo es el fondo del valle. Son la misma cosa mirada de dos maneras, y esa coincidencia es de las más bonitas que hay en este libro 💛
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 la menos catorce, y al redondear se queda el signo. Para lo que nos importa, es el fondo.
Lo que te llevas
- La derivada es a cuánto sube la función por cada paso.
- Signo negativo: el error baja si avanzas. Positivo: sube.
- El tamaño dice la inclinación, no cuánto hay que moverse.
- Se puede medir a lo bruto con dos evaluaciones, y eso es gradient checking.
- En el fondo del valle la derivada vale cero.
- El fondo del valle y la proyección más cercana son el mismo punto.
Comprueba que se entendió
Comprueba que lo tienes
Estás ajustando un peso y la derivada del error en ese punto vale -12002. ¿Qué haces?
- Subir el peso, porque pendiente negativa quiere decir que el error baja yendo hacia la derecha
- Bajar el peso, porque el número es negativo
- Dejarlo, porque el error ya está bajando solo
- Subir el peso 12002 unidades
Ejercicios
1. Baja el valle a mano
Da cinco pasos en contra de la pendiente y mira qué pasa.
y, u = X[:, 1], X[:, 0] def error(w): return float(((y - w * u) ** 2).mean()) def pendiente_exacta(w): return float((-2 * u * (y - w * u)).mean()) w = 0.0 for paso in range(5): w = w - 0.00001 * pendiente_exacta(w) print('paso', paso + 1, '| w =', round(w, 4), '| error =', round(error(w), 2))
paso 1 | w = 0.1879 | error = 1209918.24 paso 2 | w = 0.3753 | error = 1206415.83 paso 3 | w = 0.5619 | error = 1202937.17 paso 4 | w = 0.748 | error = 1199482.1 paso 5 | w = 0.9334 | error = 1196050.45
Ya acabas de programar el descenso de gradiente y todavía faltan cuatro capítulos para que lo llamemos así. Va lento porque el paso es chiquito; subirlo tiene su propio peligro y es el capítulo 18.
2. El paso demasiado chico también miente
Prueba la derivada numérica con varias h.
y, u = X[:, 1], X[:, 0] def error(w): return float(((y - w * u) ** 2).mean()) for h in (1.0, 0.01, 1e-5, 1e-10, 1e-14): d = (error(55.0 + h) - error(55.0 - h)) / (2 * h) print('h =', h, '-> pendiente', round(d, 4))
h = 1.0 -> pendiente -116.0403 h = 0.01 -> pendiente -116.0403 h = 1e-05 -> pendiente -116.0403 h = 1e-10 -> pendiente -115.2512 h = 1e-14 -> pendiente 0.0
Con h de una diezmilbillonésima sale cero, que es una respuesta completamente falsa. Los dos números que restas son tan parecidos que la coma flotante los ve iguales, y cero entre cero es lo que hay. Más chico no es más preciso: hay un punto donde empeora, y para gradient checking el sitio cómodo está por 1e-5.
3. Dibuja el valle sin dibujar nada
Con la tabla de errores basta para ver la forma.
y, u = X[:, 1], X[:, 0] def error(w): return float(((y - w * u) ** 2).mean()) for w in range(40, 76, 5): e = error(float(w)) print(str(w).rjust(3), '|', '#' * int((e - 693000) / 500))
40 | ################################################################################ 45 | ##################################### 50 | ########## 55 | 60 | ######## 65 | ################################ 70 | ######################################################################### 75 | ####################################################################################################################################
Un valle en caracteres, y el fondo se ve solo: en 55 la barra está vacía porque el error ahí ya es menor que los 693.000 que resté para dibujar.
Y ahora la letra chica, que es de lo que quiero que te acuerdes. Ese 693.000 me lo inventé yo para que el dibujo se viera. Si resto 690.000, el valle casi desaparece; si resto 693.400, todas las barras se disparan. La forma del gráfico la elegí yo, no los datos. Es exactamente el truco del eje que no empieza en cero, y se cuela en informes todos los días.
Preguntas frecuentes
¿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.
¿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.
¿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.
¿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í.
¿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.
Practica este capítulo 📓
Todo el código de arriba en un cuaderno que corre de principio a fin, y los ejercicios con una celda vacía para que los hagas tú. Se abre en Google Colab de un clic y no hay que instalar nada. Donde veas %%revisa, escribe tu respuesta y el cuaderno te dice si te salió.
¿Prefieres trabajar en tu máquina? Bájate el cuaderno de práctica o el de soluciones. Todos están también en github.com/soymissyera/MissYeraEjercicios.