Saltar al contenido
2/30Capítulo 2 de 30

De dónde sale una función de pérdida: verosimilitud, no convención

Tres rectas a ojo sobre las mismas veinte mediciones y tres reglas de puntuación con ganadores distintos. El error cuadrático es una elección.

En esta página

La cuchilla que corta las piezas se desgasta. Durante un turno de diez horas pierde filo suficiente como para que las piezas salgan de la cinta una fracción de milímetro más anchas que al principio, y en cuanto superan los 23,5 milímetros inspección las rechaza. Nadie en la planta sabe cuándo ocurre. Lo que tienen es un calibre, una libreta y veinte lecturas del martes pasado: las horas transcurridas desde que se cambió la cuchilla y la anchura de la pieza medida en ese momento.

Alguien traza una recta entre los puntos. Otra persona traza una ligeramente distinta. Una tercera dibuja una tercera. Las tres parecen razonables sobre el papel, y discrepan en varias horas sobre cuándo cambiar la cuchilla: en esta planta, la diferencia entre una semana tranquila y un lote desechado.

¿Qué recta es mejor?

Tal como está planteada, esa pregunta no tiene respuesta. No una respuesta difícil: ninguna respuesta en absoluto. «Mejor» no es una propiedad de una recta como lo es su pendiente; es una propiedad de una recta junto con una regla para puntuar rectas, y hasta que alguien escriba la regla no hay nada que calcular. Este capítulo se toma en serio esa frase y termina con el descubrimiento de que la regla más común en machine learning no es una convención, sino la consecuencia de una afirmación sobre el mundo: una que puedes poner a prueba, y una que a veces es falsa.

Una confesión antes de la primera línea de código. Estas veinte lecturas no proceden de una fábrica real: las generé a partir de una recta que elegí, width=20.00+0.30h\text{width} = 20.00 + 0.30 \cdot h, más ruido aleatorio con una dispersión de alrededor de una décima de milímetro. Eso importa, porque todo lo que sigue trata de si un método recupera una verdad, y la única forma de comprobarlo es conocer la verdad de antemano. Así que: 0,30 milímetros por hora es la respuesta al final del libro. No puedes usarla, solo compararte con ella.

Aquí están las lecturas y las tres rectas, puntuadas de tres maneras: error cuadrático, al que recurre todo el mundo; error absoluto, al que podría recurrir un estadístico; y peor error, al que recurriría el mecánico, porque al inspector no le importa tu media: rechaza la única pieza que está fuera de tolerancia.

NumPy llega aquí, un capítulo después del perceptrón en Python puro, por una razón: al final de este capítulo evaluamos cuatrocientas mil rectas candidatas frente a veinte lecturas cada una, y un bucle de Python es la herramienta equivocada para eso. También es la notación en la que está escrita cada fuente citada más abajo.

loss.pyPYTHON
import numpy as np

# Hours since the blade was changed, and the width of the part measured then.
SHIFT = np.array([
    (0.5, 20.17), (1.0, 20.28), (1.5, 20.53), (2.0, 20.61), (2.5, 20.69),
    (3.0, 20.94), (3.5, 21.21), (4.0, 21.31), (4.5, 21.27), (5.0, 21.35),
    (5.5, 21.58), (6.0, 21.80), (6.5, 21.67), (7.0, 22.07), (7.5, 22.10),
    (8.0, 22.31), (8.5, 22.48), (9.0, 22.66), (9.5, 22.90), (10.0, 23.13),
])
h, y = SHIFT[:, 0], SHIFT[:, 1]

LINES = {"A": (20.10, 0.26), "B": (20.20, 0.28), "C": (20.30, 0.26)}

for name, (a, b) in LINES.items():
    r = y - (a + b * h)                                      
    print(f"{name}   mean square {np.mean(r**2):.5f}"
          f"   mean absolute {np.mean(np.abs(r)):.5f}"
          f"   worst {np.max(np.abs(r)):.3f}")               

La cantidad de las líneas resaltadas es el residuo: lo que dijo la recta menos lo que dijo el calibre, un número por lectura. Cada regla de puntuación de este capítulo, y cada función de pérdida de los veintiocho capítulos posteriores, es alguna forma de comprimir una lista de residuos hasta un solo número. Solo difieren en cómo la comprimen.

TEXT
A   mean square 0.02699   mean absolute 0.12600   worst 0.430
B   mean square 0.02524   mean absolute 0.13700   worst 0.350
C   mean square 0.03179   mean absolute 0.15000   worst 0.320

Lee las columnas, no las filas. El error cuadrático dice B, el error absoluto dice A, el peor error dice C: tres reglas, tres ganadores, sobre los mismos veinte puntos.

Elegí estas tres rectas para que discreparan, y conviene decirlo claramente. Lo importante es lo fácil que fue: unos minutos buscando entre interceptos y pendientes de aspecto sensato producen cientos de ternas así. La clasificación es una propiedad de la regla que elegiste, no un hecho sobre las rectas, así que la regla no es un detalle de implementación: es la definición del problema. Lo cual plantea la pregunta que este capítulo existe para responder: ¿con qué criterio la eliges?

Primero una cuestión menor, porque no hay tres rectas sino infinitas. Toma por ahora el error cuadrático, ya que es lo que toma todo el mundo, y reduce el problema a un solo número usando el truco que ahorró al perceptrón once mil épocas en el capítulo 1: resta la media a ambas columnas. Una vez que la nube de puntos está centrada en el origen, la mejor recta bajo error cuadrático pasa exactamente por el origen; así que el intercepto queda resuelto y solo queda elegir la pendiente.

loss.py (continued)PYTHON
u, v = h - h.mean(), y - y.mean()        # 5.25 hours, 21.553 mm

def mse(theta):
    return np.mean((v - theta * u) ** 2)

grid = np.arange(0.0, 0.6001, 0.001)
curve = np.array([mse(t) for t in grid])
print(grid.size, "candidates ->", f"theta={grid[curve.argmin()]:.3f}", f"mse={curve.min():.6f}")
TEXT
601 candidates -> theta=0.293 mse=0.010115

Seiscientas una pendientes candidatas, una ganadora: 0,293 milímetros por hora frente a una verdad de 0,300. Veinte lecturas ruidosas y un bucle for se quedaron a una centésima de milímetro por hora: un dos coma tres por ciento.

Lo interesante no es la ganadora, sino la forma de la búsqueda. Imprime la curva completa, girada para que la pérdida vaya de izquierda a derecha:

loss.py (continued)PYTHON
ts = np.arange(0.0, 0.6001, 0.04)
ls = np.array([mse(t) for t in ts])
for t, l in zip(ts, ls):
    col = round(l / ls.max() * 50)
    print(f"theta={t:.2f} |{' ' * col}*{' ' * (50 - col)}| mse={l:7.4f}")
TEXT
theta=0.00 |                                              *    | mse= 0.7244
theta=0.04 |                                  *                | mse= 0.5428
theta=0.08 |                        *                          | mse= 0.3878
theta=0.12 |                *                                  | mse= 0.2593
theta=0.16 |          *                                        | mse= 0.1575
theta=0.20 |     *                                             | mse= 0.0822
theta=0.24 |  *                                                | mse= 0.0336
theta=0.28 | *                                                 | mse= 0.0116
theta=0.32 | *                                                 | mse= 0.0161
theta=0.36 |   *                                               | mse= 0.0473
theta=0.40 |       *                                           | mse= 0.1050
theta=0.44 |            *                                      | mse= 0.1894
theta=0.48 |                   *                               | mse= 0.3004
theta=0.52 |                            *                      | mse= 0.4379
theta=0.56 |                                      *            | mse= 0.6021
theta=0.60 |                                                  *| mse= 0.7928

Eso es un valle, visto de lado. Tiene un único fondo, las paredes suben suavemente a ambos lados y —esta es la parte que la escalera del capítulo 1 no podía ofrecer— en cada punto hay una dirección de «cuesta abajo» bien definida. Recuerda esa forma. El capítulo 3 trata por completo de bajar por ella sin visitar los seiscientos un puntos, y de lo que cambia cuando un valle tiene más de un fondo.

Tenemos un valle porque elevamos al cuadrado. El error absoluto habría producido una arista en el fondo; el peor error habría producido tramos planos en los que mover la recta no cambia nada. Elevar al cuadrado es innegablemente cómodo, y la comodidad es más o menos la razón que dan la mayoría de cursos, disfrazada de cuatro formas: hace que los errores sean positivos (también lo hace el valor absoluto); castiga más los errores grandes (¿por qué debería?); es diferenciable (también lo es la cuarta potencia); es lo que usa todo el mundo (lo es, y eso no es un argumento).

Esta es la postura honesta. El error cuadrático eligió la recta B y el error absoluto eligió la recta A. Una de esas es correcta para esta fábrica y la otra es incorrecta, y nada de lo dicho hasta ahora puede decirte cuál. Para elegir la regla necesitas saber algo sobre cómo llegaron las lecturas a diferir de la recta, y esa es una pregunta sobre el mundo, no sobre matemáticas. Responderla necesita una pequeña pieza de maquinaria.

Esta es la afirmación que convierte «qué recta es mejor» en una pregunta con respuesta.

Supón que la anchura de una pieza es la recta más un error aleatorio, y supón que ese error procede de una gaussiana —la campana— con media cero y desviación típica σ\sigma:

yi=θxi+εi,εiN(0,σ2)y_i = \theta x_i + \varepsilon_i, \qquad \varepsilon_i \sim \mathcal{N}(0, \sigma^2)

La densidad de la gaussiana es

p(ε)=1σ2πexp ⁣(ε22σ2)p(\varepsilon) = \frac{1}{\sigma\sqrt{2\pi}} \exp\!\left(-\frac{\varepsilon^2}{2\sigma^2}\right)

Ahora haz algo que el perceptrón no podía. Para una pendiente candidata dada θ\theta, cada lectura tiene un residuo, y la fórmula anterior convierte ese residuo en un número: ¿cuán plausible es un error exactamente de ese tamaño, si esta pendiente es la verdad? Una lectura sobre la recta obtiene un número grande; una lectura a medio milímetro, uno pequeño.

Las lecturas son independientes —el calibre no recuerda la pieza anterior—, así que la regla del producto dice que la plausibilidad de la libreta entera es el producto de las densidades individuales. Ese producto es la verosimilitud de θ\theta.1 Fíjate en la dirección, porque es la dirección de la que trata la regla de Bayes: los datos son fijos y conocidos, y lo que varía es el parámetro. Esto no es «la probabilidad de la pendiente». Es la probabilidad que el modelo asigna a los datos que realmente obtuviste, leída como función de la pendiente.

likelihood.pyPYTHON
SIGMA = 0.12

def gaussian(r, sigma):
    return np.exp(-r ** 2 / (2 * sigma ** 2)) / (sigma * np.sqrt(2 * np.pi))

def likelihood(theta):
    return np.prod(gaussian(v - theta * u, SIGMA))          

for t in (0.25, 0.293, 0.35):
    print(f"theta={t}   likelihood = {likelihood(t):.6g}")
TEXT
theta=0.25   likelihood = 521.952
theta=0.293   likelihood = 2.42028e+07
theta=0.35   likelihood = 0.190312

Una pendiente de 0,293 hace que esta libreta sea cuarenta y seis mil veces más plausible que 0,25, y ciento veintisiete millones de veces más plausible que 0,35. La máxima verosimilitud es el principio por el que eliges el parámetro que hace que lo que realmente observaste sea lo menos sorprendente posible. No es un teorema sino una propuesta sobre lo que debería significar «mejor»: una propuesta con contenido, porque te obliga a declarar tu supuesto sobre el ruido antes de permitirte puntuar nada.

Ejecuta las mismas tres líneas de código sobre un mes de turnos en vez de uno, y el método se viene abajo.

likelihood.py (continued)PYTHON
rng = np.random.default_rng(7)
u_big = rng.uniform(-5.25, 5.25, 2000)                     # 2000 readings, not 20
v_big = 0.30 * u_big + 0.12 * rng.standard_normal(2000)

print("2000 readings, sigma = 0.12 mm :", np.prod(gaussian(v_big - 0.30 * u_big, 0.12)))
noisy = 0.30 * u_big + 2.0 * rng.standard_normal(2000)
print("2000 readings, sigma = 2.00 mm :", np.prod(gaussian(noisy - 0.30 * u_big, 2.0)))
print("largest float64 :", np.finfo(np.float64).max)
TEXT
RuntimeWarning: overflow encountered in reduce
2000 readings, sigma = 0.12 mm : inf
2000 readings, sigma = 2.00 mm : 0.0
largest float64 : 1.7976931348623157e+308

Dos mil multiplicaciones y la respuesta es inf. Cambia una constante —un calibre más impreciso, de modo que las densidades salgan menores que 1 en vez de mayores— y el mismo código devuelve 0.0. Ambas respuestas son erróneas, en direcciones opuestas; ninguna lanza una excepción que puedas capturar, y la segunda ni siquiera imprime una advertencia.

No hay nada mal en las matemáticas. La verosimilitud con esos ajustes es un número finito perfectamente definido: su logaritmo natural es 1400,91, así que el número en sí es aproximadamente 1060810^{608}. El problema es que tu ordenador no tiene ese número, y merece la pena entender exactamente qué números sí tiene, porque esta no será la última vez que decida el resultado.

La solución para el producto que explota es la habitual: tomar logaritmos. El logaritmo convierte productos en sumas, es estrictamente creciente, así que no puede mover la ubicación del máximo, y una suma de dos mil números moderados es algo que float64 maneja sin quejarse. Por convención tomamos la log-verosimilitud negativa, para que mejor signifique más pequeño. Ahora sustituye la densidad gaussiana y mira lo que ocurre.

  1. Empieza por el producto. La verosimilitud es L(θ)=i=1Np(yiθxi)\mathcal{L}(\theta) = \prod_{i=1}^{N} p(y_i - \theta x_i), con pp la densidad gaussiana de arriba.

  2. Toma menos el logaritmo. El producto se convierte en una suma, y la exponencial de la densidad se cancela directamente con el logaritmo:

logL(θ)=N2log ⁣(2πσ2)+12σ2i=1N(yiθxi)2-\log \mathcal{L}(\theta) = \frac{N}{2}\log\!\left(2\pi\sigma^2\right) + \frac{1}{2\sigma^2}\sum_{i=1}^{N}\left(y_i - \theta x_i\right)^2
  1. Descarta todo lo que no contenga θ\theta. El primer término es una constante. El 1/2σ21/2\sigma^2 delante de la suma es una constante positiva, y escalar una función por una constante positiva no puede mover dónde está su mínimo. Lo que queda es
i=1N(yiθxi)2\sum_{i=1}^{N}\left(y_i - \theta x_i\right)^2

que es la suma de residuos al cuadrado: aquello con lo que empezamos el capítulo porque era lo primero que se le ocurre a cualquiera.

Ese es el resultado por el que existe el capítulo, y merece enunciarse sin matices: el error cuadrático no es una convención. Es la log-verosimilitud negativa de una gaussiana, con las constantes eliminadas. Minimizar el error cuadrático es exactamente el mismo acto que afirmar que tus errores son gaussianos y preguntar qué parámetro hace que tus datos sean menos sorprendentes. Estabas haciendo esa afirmación todo el tiempo; simplemente nadie te lo decía.

La equivalencia es comprobable, así que compruébala: recorre las mismas seiscientas una pendientes con la log-verosimilitud negativa completa, constantes incluidas, y con el error cuadrático simple.

likelihood.py (continued)PYTHON
N = v.size

def nll(theta):
    r = v - theta * u
    return N * np.log(SIGMA * np.sqrt(2 * np.pi)) + np.sum(r ** 2) / (2 * SIGMA ** 2)

nlls = np.array([nll(t) for t in grid])
mses = np.array([mse(t) for t in grid])
print(f"argmin of the negative log-likelihood : theta={grid[nlls.argmin()]:.3f}  nll={nlls.min():.6f}")
print(f"argmin of the mean squared error      : theta={grid[mses.argmin()]:.3f}  mse={mses.min():.6f}")
print("same index:", nlls.argmin() == mses.argmin())
TEXT
argmin of the negative log-likelihood : theta=0.293  nll=-17.001977
argmin of the mean squared error      : theta=0.293  mse=0.010115
same index: True

Números distintos en el eje vertical, y uno de ellos es negativo, cosa que una suma de cuadrados nunca es: una log-verosimilitud negativa puede caer por debajo de cero, porque una densidad puede superar 1. El mismo fondo del mismo valle, hasta el último punto de la cuadrícula.

Mostrar el desarrollo completo

¿Qué descartes son seguros, exactamente? La misma maniobra aparece en cada capítulo que deriva una pérdida, y no siempre es inocente.

Eliminar una constante aditiva es seguro siempre que no dependa del parámetro que estás optimizando, y eliminar una constante multiplicativa positiva es seguro porque argminθcf(θ)=argminθf(θ)\arg\min_\theta c\,f(\theta) = \arg\min_\theta f(\theta) para cualquier c>0c > 0. Ambas cosas fallan en el momento en que σ\sigma también se está ajustando: entonces N2log(2πσ2)\frac{N}{2}\log(2\pi\sigma^2) no es ninguna constante, es el término que impide que el modelo afirme σ=0\sigma = 0 y plausibilidad infinita. Esa es exactamente la sección siguiente.

Vuelven a fallar de otra manera en el capítulo 3: una constante multiplicativa no mueve el mínimo, pero sí escala el gradiente, y el gradiente se multiplica por la tasa de aprendizaje. Dividir por NN para obtener el error cuadrático medio en vez de la suma es invisible para la respuesta y muy visible para el entrenamiento: con la suma, duplicar el tamaño del lote duplica cada paso que das.

Fijamos σ\sigma en 0,12 por decreto, y nadie en la planta conoce la dispersión del error de su calibre. Trátala como una segunda incógnita y deja que la máxima verosimilitud la decida también. Aquí vuelve el término constante que acabamos de descartar, porque es lo único que se interpone entre el modelo y una afirmación de precisión perfecta.

likelihood.py (continued)PYTHON
r = v - 0.293 * u
sigmas = np.arange(0.01, 1.0001, 0.0001)
nll_sigma = N * np.log(sigmas * np.sqrt(2 * np.pi)) + np.sum(r ** 2) / (2 * sigmas ** 2)

print("best sigma on the grid       :", round(float(sigmas[nll_sigma.argmin()]), 4))
print("sqrt(mean squared residual)  :", round(float(np.sqrt(np.mean(r ** 2))), 4))
TEXT
best sigma on the grid       : 0.1006
sqrt(mean squared residual)  : 0.1006

Los dos coinciden hasta cuatro decimales, y no por accidente: derivar esa expresión e igualarla a cero da σ^2=1Nri2\hat{\sigma}^2 = \frac{1}{N}\sum r_i^2 exactamente. Así que el error cuadrático medio no es simplemente como una varianza. Bajo este modelo es la estimación de máxima verosimilitud de la varianza del ruido: el número que has estado minimizando todo el tiempo era una estimación de cuán ruidoso es tu sensor.

Un matiz, barato de enunciar y caro de redescubrir más adelante: esa estimación está sesgada a la baja, porque los residuos se midieron contra un ajuste que a su vez fue elegido para hacerlos pequeños. Simúlalo: doscientas mil libretas de veinte lecturas cada una, extraídas de una distribución cuya varianza verdadera es exactamente 1, con el único parámetro del ajuste estimado a partir de las propias lecturas. Dividir la suma de cuadrados por NN da una media de 0,9501; dividir por N1N-1 da 1,0001; y (N1)/N(N-1)/N es 0,95 clavado. Cada parámetro que ajustas cuesta un grado de libertad, y este es el caso visible más pequeño de un problema mucho mayor: un modelo siempre se ve mejor en los datos con los que se ajustó. El capítulo 4 convierte eso en la disciplina de reservar datos, y el capítulo 6 le da nombre al efecto.

Una pérdida es una afirmación sobre el ruido

Enlace a la sección: Una pérdida es una afirmación sobre el ruido

Si el error cuadrático afirma que el ruido es gaussiano, la siguiente pregunta es qué ocurre cuando la afirmación es falsa. No ligeramente falsa: falsa del modo en que las mediciones reales son falsas.

En el taller, la mayoría de lecturas del calibre son buenas hasta una décima de milímetro, y una o dos veces por turno una viruta se mete bajo la mordaza y la lectura se desvía varios milímetros. Errores así tienen colas pesadas: pequeños la mayor parte del tiempo, ocasionalmente enormes, y enormes con mucha más frecuencia de la que permite una campana. La distribución de Cauchy es el modelo limpio estándar de ese comportamiento, y su densidad es tan simple como la de la gaussiana:

p(ε)=1πs(1+(ε/s)2)p(\varepsilon) = \frac{1}{\pi s \left(1 + (\varepsilon/s)^2\right)}

La diferencia está en la cola: la gaussiana cae como eε2e^{-\varepsilon^2}, brutalmente rápido, y la Cauchy como 1/ε21/\varepsilon^2, apenas nada. La consecuencia es más fácil de ver que de decir:

PYTHON
rng = np.random.default_rng(3)
g = 0.12 * rng.standard_normal(10 ** 6)          # Gaussian noise
c = 0.12 * rng.standard_cauchy(10 ** 6)          # Cauchy noise, same scale
for k in (10 ** 2, 10 ** 3, 10 ** 4, 10 ** 5, 10 ** 6):
    print(f"{k:>9,} samples   gaussian var {g[:k].var():.4f}   cauchy var {c[:k].var():10.2f}")
TEXT
      100 samples   gaussian var 0.0164   cauchy var       0.26
    1,000 samples   gaussian var 0.0146   cauchy var      59.88
   10,000 samples   gaussian var 0.0145   cauchy var     358.17
  100,000 samples   gaussian var 0.0144   cauchy var    3097.98
1,000,000 samples   gaussian var 0.0144   cauchy var   32886.10

La varianza muestral de la gaussiana se estabiliza en 0,0144, que es 0.1220.12^2, y se queda ahí. La de la Cauchy sube, y sigue subiendo mientras sigas muestreando, porque no hay nada a lo que converger: la distribución de Cauchy no tiene varianza, y tampoco media. Al error cuadrático, cuya ocupación entera es minimizar una media de cuadrados, se le está pidiendo una cantidad que no existe.

Así que aquí tienes un turno en el que el calibre fue engañado. Las mismas veinte horas, la misma cuchilla, la misma deriva de 0,30 milímetros por hora: solo que ahora el ruido es Cauchy. Ajústalo dos veces: una minimizando residuos al cuadrado, otra minimizando la log-verosimilitud negativa del ruido que realmente generó los datos. El truco de centrar no ayuda aquí —solo fija el intercepto para el error cuadrático—, así que ambos ajustes se hacen por fuerza bruta sobre una cuadrícula de interceptos y pendientes, ya que todavía no tenemos forma de encontrar el fondo de un valle salvo visitándolo.

swarf.pyPYTHON
SWARF = np.array([
    (0.5, 20.08), (1.0, 21.95), (1.5, 20.86), (2.0, 27.51), (2.5, 20.64),
    (3.0, 20.75), (3.5, 21.01), (4.0, 21.03), (4.5, 21.37), (5.0, 20.60),
    (5.5, 22.03), (6.0, 21.95), (6.5, 21.98), (7.0, 22.01), (7.5, 21.73),
    (8.0, 22.97), (8.5, 22.60), (9.0, 22.66), (9.5, 22.44), (10.0, 22.78),
])
hs, ys = SWARF[:, 0], SWARF[:, 1]

A = np.arange(18.0, 22.001, 0.005)      # 801 intercepts
B = np.arange(-0.20, 0.8001, 0.002)     # 501 slopes
R = ys - (A[:, None, None] + B[None, :, None] * hs)      # every line against every point

SCALE = 0.12
square = np.sum(R ** 2, axis=2)                          # least squares          
cauchy = np.sum(np.log(1 + (R / SCALE) ** 2), axis=2)    # Cauchy likelihood      

for name, surface in (("least squares", square), ("Cauchy likelihood", cauchy)):
    i, j = np.unravel_index(surface.argmin(), surface.shape)
    print(f"{name:>18}:  width = {A[i]:.3f} + {B[j]:.4f} * hours"
          f"   -> 23.5 mm at hour {(23.5 - A[i]) / B[j]:.2f}")
print(f"{'the truth':>18}:  width = 20.000 + 0.3000 * hours"
      f"   -> 23.5 mm at hour {(23.5 - 20.0) / 0.30:.2f}")
print(f"{A.size * B.size:,} candidate lines evaluated")

Las dos líneas resaltadas son toda la diferencia entre los ajustes. Toma el logaritmo de la densidad de Cauchy, elimina las constantes exactamente como antes, y log(1+(r/s)2)\sum \log\left(1 + (r/s)^2\right) es lo que sobrevive. Misma receta, distinta afirmación sobre el ruido.

TEXT
     least squares:  width = 21.380 + 0.1080 * hours   -> 23.5 mm at hour 19.63
 Cauchy likelihood:  width = 19.935 + 0.3020 * hours   -> 23.5 mm at hour 11.80
         the truth:  width = 20.000 + 0.3000 * hours   -> 23.5 mm at hour 11.67
401,301 candidate lines evaluated

Mínimos cuadrados informa de una deriva de 0,108 milímetros por hora, aproximadamente un tercio de la tasa real, y concluye que la cuchilla es buena hasta la hora 19,6. La respuesta verdadera es la hora 11,7. Si actúa según ese ajuste, la planta mantiene la prensa funcionando ocho horas extra fabricando piezas fuera de tolerancia, bajo la autoridad de la función de pérdida más estándar del campo. El ajuste de Cauchy, usando las mismas veinte lecturas, la misma cuadrícula y una sola línea de diferencia en el código, cae en la hora 11,8.

Dos objeciones merecen respuesta, porque ambas son lo primero que diría un buen ingeniero.

El valor atípico es obvio: elimínalo. Puedes hacerlo, y ayuda, y no basta. Eliminar la peor lectura mueve la pendiente de mínimos cuadrados de 0,108 a 0,239, lo que todavía coloca el cambio de cuchilla en la hora 13,1, una hora y media tarde; eliminar la peor, volver a ajustar y eliminar la que sea peor ahora te lleva a 0,286. Y fíjate en que esto ya es un procedimiento, no una observación: elimina los dos mayores residuos del ajuste original en vez de eso y aterrizas en 0,223. Pero ahora has hecho juicios que no puedes escribir ni defender, y automatizar la regla no la rescata: eliminar-el-mayor-residuo-y-reajustar, ejecutado sobre mil turnos simulados, tiene una mediana del error de pendiente de 0,0177 frente al 0,0100 del ajuste de verosimilitud, y se equivoca por más de 0,05 en el 14,7% de los turnos frente al 1,3%. La eliminación es un parche sobre una suposición equivocada. La verosimilitud no necesita parche, porque nunca supuso que el valor atípico fuera imposible.

Elegiste un conjunto de datos con suerte. Esa objeción es exactamente correcta, y por eso el último experimento simula mil turnos independientes y reajusta de las dos maneras en cada uno.

swarf.py (continued)PYTHON
A = np.arange(18.0, 22.001, 0.02)        # a coarser grid: a thousand fits to do
B = np.arange(-0.20, 0.8001, 0.005)
lines = A[:, None, None] + B[None, :, None] * hs
rng = np.random.default_rng(2026)
err_sq, err_ca = [], []

for _ in range(1000):                                        # 1000 independent shifts
    ys = 20.00 + 0.30 * hs + SCALE * rng.standard_cauchy(hs.size)
    R = ys - lines
    _, j = np.unravel_index(np.sum(R ** 2, axis=2).argmin(), (A.size, B.size))
    _, q = np.unravel_index(np.sum(np.log1p((R / SCALE) ** 2), axis=2).argmin(), (A.size, B.size))
    err_sq.append(abs(B[j] - 0.30))
    err_ca.append(abs(B[q] - 0.30))

err_sq, err_ca = np.array(err_sq), np.array(err_ca)
for name, e in (("least squares", err_sq), ("Cauchy likelihood", err_ca)):
    print(f"{name:>18}: median slope error {np.median(e):.4f} mm/h"
          f"   off by more than 0.05 in {100 * np.mean(e > 0.05):4.1f}% of shifts"
          f"   worst {e.max():.3f}")
print(f"the likelihood fit is the closer of the two in {100 * np.mean(err_ca < err_sq):.1f}% of shifts")
TEXT
     least squares: median slope error 0.0350 mm/h   off by more than 0.05 in 40.4% of shifts   worst 0.500
 Cauchy likelihood: median slope error 0.0100 mm/h   off by more than 0.05 in  1.3% of shifts   worst 0.090
the likelihood fit is the closer of the two in 75.6% of shifts

Mediana, no media, por la misma razón que todo lo demás en esta sección: los errores de mínimos cuadrados están impulsados por una Cauchy, así que su promedio no es algo estable que informar. Mínimos cuadrados se equivoca gravemente en dos turnos de cada cinco; el ajuste de verosimilitud se equivoca gravemente en un turno de cada setenta y siete, y su peor fallo en mil turnos es menos de una quinta parte del peor fallo de mínimos cuadrados.

Nada de esto hace malo al error cuadrático. Lo hace específico, y la aritmética dice exactamente por qué. Toma un residuo de 0,1 mm y uno de 7 mm. Al cuadrado, la mala lectura contribuye 4.900 veces más al total que la buena, así que la recta es arrastrada entera hacia ella; bajo la log-verosimilitud de Cauchy, esos mismos dos residuos contribuyen 0,527 y 8,133, una relación de 15,4. La mala lectura sigue contando, solo que no decide. Este es el comienzo de la estadística robusta, donde la pérdida de Huber de 1964 parte la diferencia comportándose de forma cuadrática para residuos pequeños y lineal para los grandes,7 y donde Tukey ya había mostrado lo poca contaminación que hace falta para convertir la varianza muestral en una herramienta peor que la desviación absoluta media.8

Una nota histórica, demasiado buena para dejarla fuera. Mínimos cuadrados fue publicado primero por Legendre en 1805, como un dispositivo algebraico práctico sin más justificación que el hecho de que funcionaba.9 Cuatro años después, Gauss hizo el argumento al revés: dio por sentado que la media aritmética es la forma correcta de combinar mediciones repetidas, preguntó qué distribución de errores hace que la media sea el valor más probable y mostró que, esencialmente, solo una lo hace: la que ahora lleva su nombre.10 La derivación de este capítulo es la suya, tiene más de dos siglos, y sigue siendo la parte que la mayoría de cursos omite.

Lo que ahora puedes decir y lo que todavía no puedes hacer

Enlace a la sección: Lo que ahora puedes decir y lo que todavía no puedes hacer

Ganado. Una función de pérdida es una regla de puntuación, y la clasificación que produce es una propiedad de la regla, no de los candidatos. Cada pérdida de este curso es la log-verosimilitud negativa de algún supuesto sobre el ruido, con las constantes eliminadas: la gaussiana da aquí error cuadrático, Bernoulli da entropía cruzada en el capítulo 4, y una distribución categórica sobre un vocabulario da la pérdida de next-token en el capítulo 8. La receta nunca cambia: declara el ruido, escribe la verosimilitud, toma menos el logaritmo. Y cuando el supuesto es erróneo, el modelo no es meramente impreciso: se equivoca en una dirección que puedes predecir.

Todavía falta. Encontramos el fondo del valle visitando cada punto. Eso funcionó para un parámetro y seiscientos candidatos, y sobrevivió a dos parámetros con 401.301 candidatos en una quinta parte de segundo. Tres parámetros con la misma resolución son 201.051.801 candidatos y ya no caben en un array; una red pequeña del capítulo 5 tiene miles de parámetros, y los modelos a los que el capítulo 10 pone precio tienen miles de millones. La fuerza bruta aquí no es lenta: es aritméticamente imposible, y nada en este capítulo sugiere una alternativa.

Pero vuelve a mirar el valle. De pie en θ=0.20\theta = 0.20 con una pérdida de 0,0822, la dirección de «cuesta abajo» no es ningún misterio: puedes verla en la página, la curva desciende hacia la derecha. Si pudieras preguntarle a la función de pérdida hacia dónde se inclina en el punto en el que estás, sin evaluarla en ningún otro lugar, podrías dar un paso en esa dirección, volver a preguntar y repetir hasta que el suelo estuviera plano.

Esa pregunta tiene nombre. La pendiente de una función en un punto es su derivada, y para una función de muchos parámetros, la colección de pendientes en todas las direcciones a la vez es el gradiente. El capítulo 1 no podía usar uno, porque el error del perceptrón era una escalera sin pendiente por la que preguntar. Este capítulo ha construido algo mejor: una pérdida que es suave en todas partes y que procede de un supuesto declarado en vez de una preferencia.

Así que la pregunta para el capítulo 3 ya no es si existe una pendiente. Es cómo calcularla, por qué moverse contra ella baja en vez de subir —un signo que casi todos los cursos te piden aceptar por fe— y cuánto avanzar antes de volver a preguntar, que resulta ser el único número que decide si un entrenamiento converge, oscila alrededor de la respuesta para siempre o sale disparado hacia el infinito.


También merece la pena leer junto a este capítulo: Prince, Understanding Deep Learning §5.1–5.2 y apéndice C, que construye cada pérdida del libro a partir de máxima verosimilitud en el orden usado aquí; Goodfellow, Bengio y Courville, Deep Learning §3.1–3.11 y §5.5, cuya sección de máxima verosimilitud también deriva la divergencia KL que necesita el capítulo 4; Murphy, Probabilistic Machine Learning: An Introduction capítulo 2 y §4.2, sobre lo que la máxima verosimilitud garantiza y lo que no; Deisenroth, Faisal y Ong, Mathematics for Machine Learning §6.1–6.4 para la regla de la suma, la regla del producto y la regla de Bayes hechas como es debido; la breve nota de Tom Mitchell para CMU Estimating Probabilities: MLE and MAP (2016); y §22.7 de Dive into Deep Learning, que llega al mismo resultado en código ejecutable.

  1. Fisher, R. A. On the mathematical foundations of theoretical statistics. Philosophical Transactions of the Royal Society A 222, pp. 309–368 (1922). Donde la verosimilitud se plantea como método general, junto con «parameter», «statistic», suficiencia y eficiencia. El nombre en sí, y la separación respecto de la probabilidad, es de un año antes: Fisher, R. A., On the "probable error" of a coefficient of correlation deduced from a small sample, Metron 1, pp. 3–32 (1921), pp. 24–25.

  2. IEEE Standard for Floating-Point Arithmetic, IEEE 754-2019. Define binary32 y binary16, y las reglas de redondeo que hacen que el experimento de suma salga como sale.

  3. Kalamkar, D. et al. A Study of BFLOAT16 for Deep Learning Training. arXiv:1905.12322 (2019). Los parámetros del formato y el caso a favor de intercambiar bits de mantisa por bits de exponente.

  4. Micikevicius, P. et al. Mixed Precision Training. ICLR 2018, arXiv:1710.03740. Escalado de la pérdida y las magnitudes de gradiente medidas que lo hacen necesario en float16.

  5. Goldberg, D. What Every Computer Scientist Should Know About Floating-Point Arithmetic. ACM Computing Surveys 23(1), pp. 5–48 (1991). Sigue siendo la mejor explicación única de por qué los dos órdenes de suma discrepan.

  6. Kahan, W. Pracniques: further remarks on reducing truncation errors. Communications of the ACM 8(1), p. 40 (1965). Suma compensada en media página.

  7. Huber, P. J. Robust estimation of a location parameter. The Annals of Mathematical Statistics 35(1), pp. 73–101 (1964). La pérdida que es cuadrática cerca de cero y lineal en las colas, derivada en vez de parcheada.

  8. Tukey, J. W. A survey of sampling from contaminated distributions, en Contributions to Probability and Statistics (Stanford University Press, 1960), pp. 448–485.

  9. Legendre, A. M. Nouvelles méthodes pour la détermination des orbites des comètes (París, 1805), apéndice Sur la méthode des moindres quarrés. La primera publicación de mínimos cuadrados, como dispositivo computacional.

  10. Gauss, C. F. Theoria Motus Corporum Coelestium (Hamburgo, 1809), Libro II, §§175–179. El argumento desde la media aritmética hasta la ley normal del error, y de ahí a mínimos cuadrados.

¿Listo para dejar que elija LIA?

Crea con todos los modelos de IA en un mismo sitio. Empieza gratis hoy.