Ir al contenido
wannacode

Regresión lineal

En resumen: La regresión lineal predice un número con una recta y = w · x + b, y el error cuadrático medio (MSE) dice con un solo número lo bien que la recta se ajusta a los datos.

Problema

Dos colegas, dos rectas

El almacén quiere prometer horas, no días. Aquí hay 10 pedidos anteriores: la distancia y cuántas horas tardaron.

// pedidos anteriores: distancia en km y horas hasta la entrega
double[] km = [30, 50, 80, 100, 130, 160, 200, 240, 270, 300];
double[] hours = [10, 11, 17, 17, 23, 24, 31, 33, 40, 44];

Dos colegas miran los puntos y cada uno propone una regla:

  • recta A: cada kilómetro añade 0,08 horas, más 12 horas en el almacén;
  • recta B: cada kilómetro añade 0,12 horas, más 6 horas en el almacén.

En el gráfico las dos se ven bien. Los colegas discuten toda la tarde. La discusión no terminará hasta que tengan un número que diga lo buena que es una recta.

La idea

La recta como modelo

Las dos propuestas son del mismo tipo: horas = w · km + b.

  • w es la pendiente: cuántas horas añade cada kilómetro;
  • b es el intercepto: las horas a 0 km, es decir, el trabajo en el almacén.

Un modelo que predice un número con una recta se llama regresión lineal. Entrenar el modelo significa elegir w y b.

// la recta: horas = w · km + b
static double Predict(double w, double b, double km) =>
w * km + b;

A diferencia de los k vecinos más cercanos, la recta no guarda los pedidos. Son dos números, y una predicción es una multiplicación y una suma. Y sigue su pendiente: para 500 km dice 0,12 · 500 + 6 = 66 horas, mientras que los vecinos de 500 km son los pedidos de 300 km.

La idea

El error de cada pedido

Para cada pedido la recta dice algún número. El error es la diferencia: lo que dice la recta menos lo que pasó de verdad. Es positivo cuando la recta promete más y negativo cuando promete menos.

La recta B, pedido por pedido:

double w = 0.12, b = 6;
for (var i = 0; i < km.Length; i++)
{
var guess = Predict(w, b, km[i]);
Console.WriteLine($"{km[i]} km: real {hours[i]} h, la recta {guess:F2} h, error {guess - hours[i]:F2}");
}
Console.WriteLine($"MSE: {Mse(w, b):F2}");
Salida
30 km: real 10 h, la recta 9.60 h, error -0.40
50 km: real 11 h, la recta 12.00 h, error 1.00
80 km: real 17 h, la recta 15.60 h, error -1.40
100 km: real 17 h, la recta 18.00 h, error 1.00
130 km: real 23 h, la recta 21.60 h, error -1.40
160 km: real 24 h, la recta 25.20 h, error 1.20
200 km: real 31 h, la recta 30.00 h, error -1.00
240 km: real 33 h, la recta 34.80 h, error 1.80
270 km: real 40 h, la recta 38.40 h, error -1.60
300 km: real 44 h, la recta 42.00 h, error -2.00
MSE: 1.83

Diez errores, unos positivos, otros negativos. Necesitamos un número para toda la recta.

La idea

Un número: el cuadrado medio

Cada error se eleva al cuadrado, los cuadrados se suman y se dividen entre el número de pedidos. Este es el error cuadrático medio, MSE. Un número que dice lo mal que está un modelo se llama función de pérdida (pérdida). Menos es mejor, y 0 significa que la recta pasa por todos los puntos.

// la media de los errores al cuadrado (MSE)
double Mse(double w, double b)
{
double sum = 0;
for (var i = 0; i < km.Length; i++)
{
var error = Predict(w, b, km[i]) - hours[i];
sum += error * error;
}
return sum / km.Length;
}

¿Por qué un cuadrado? Compara tres rectas. C es la recta plana por la media, 25 horas para cada pedido:

// el error medio sin el cuadrado
double MeanError(double w, double b)
{
double sum = 0;
for (var i = 0; i < km.Length; i++)
{
sum += Predict(w, b, km[i]) - hours[i];
}
return sum / km.Length;
}
string[] names = ["A", "B", "C"];
double[] ws = [0.08, 0.12, 0];
double[] bs = [12, 6, 25];
for (var i = 0; i < names.Length; i++)
{
Console.WriteLine($"recta {names[i]}: MSE {Mse(ws[i], bs[i]):F2}, error medio {MeanError(ws[i], bs[i]):F2}");
}
Salida
recta A: MSE 17.35, error medio -0.52
recta B: MSE 1.83, error medio -0.28
recta C: MSE 124.00, error medio 0.00

Sin el cuadrado, la recta C tiene un error medio de 0,00: está tanto por encima de los puntos como por debajo, y los positivos anulan a los negativos. La peor recta «gana». Con el cuadrado nada se anula: C es 124, A es 17,35 y B es 1,83. El colega con la recta B gana la discusión.

El cuadrado hace algo más: un error grande pesa más. Un error de 4 horas da 16, mientras que dos errores de 2 horas dan 8 en total. Una recta que se equivoca un poco en todas partes es mejor que una que acierta en la mitad de los pedidos y se equivoca mucho en la otra mitad.

Míralo

Los errores como cuadrados

static double Predict(double w, double b, double km) =>
w * km + b;
double Mse(double w, double b)
{
double sum = 0;
for (var i = 0; i < km.Length; i++)
{
var error = Predict(w, b, km[i]) - hours[i];
sum += error * error;
}
return sum / km.Length;
}
Paso a paso, en palabras
  1. 10 pedidos anteriores: cuántos km y en cuántas horas llegaron.
  2. Recta A: 0,08 horas por km más 12 horas.
  3. El error de cada pedido: lo que dice la recta menos lo que pasó de verdad. Unos puntos están por encima de la recta, otros por debajo.
  4. Cada error al cuadrado es un cuadrado cuyo lado es el error. El área media es el MSE: 17,35.
  5. Recta B: 0,12 horas por km más 6 horas. La recta gira y los cuadrados se encogen.
  6. El MSE de B es 1,83, frente a 17,35 en A.
  7. La recta con el MSE menor está más cerca de los puntos. Un número en vez de una discusión.

Cada cuadrado es un error al cuadrado: su lado es la distancia del punto a la recta. El MSE es su área media. La recta B es mejor porque sus cuadrados son más pequeños, no porque alguien lo piense.

Pruébalo

Predice la salida

El mensajero cobra 2 € por kilogramo. Tres paquetes de 1, 2 y 3 kg cuestan 3 €, 5 € y 7 €. ¿Qué imprimirá el programa?

double[] kg = [1, 2, 3];
double[] price = [3, 5, 7];
double sum = 0;
for (var i = 0; i < 3; i++)
{
var error = 2 * kg[i] - price[i];
sum += error * error;
}
Console.WriteLine($"{sum / 3:F2}");
Respuesta

Pruébalo

Rellena

¿Qué se suma a la suma por cada pedido?

var error = Predict(w, b, km[i]) - hours[i];
sum += ;

Pruébalo

Encuentra el error

Un colega escribe la función de pérdida y compara las rectas. Su programa dice que la mejor es la recta plana C. ¿Qué línea tiene el error?

Elige la línea con el error.

La idea

Cuando la recta miente

La recta es el modelo más simple y a menudo basta. Pero:

  • El mundo no es recto. Si después de 300 km el pedido pasa por otro almacén y espera un día más, los puntos forman un escalón. La recta pasa entre las dos partes y se equivoca en ambas.
  • Un punto raro. Un pedido perdido durante una semana es un error de cientos de horas, y su cuadrado pesa tanto como todos los demás juntos. La recta se inclina hacia él y empieza a mentir sobre los pedidos normales. Esos puntos se revisan y se quitan, o se mide con el error absoluto medio.
  • Muy fuera de los datos. Los pedidos llegan hasta 300 km. Para 2000 km la recta dice 246 horas, pero nadie ha comprobado si existe siquiera una entrega así.
  • Una relación no es una causa. La recta dice que los pedidos más lejanos tardan más. No dice por qué. Si las ciudades más lejanas las atiende simplemente un mensajero más lento, un mensajero más rápido allí romperá la recta.

La recta que los colegas eligieron a ojo es solo una de incontables combinaciones de w y b. Cuál es la mejor y cómo encontrarla sin adivinar está en la lección sobre el descenso del gradiente.

Más precisamente, para curiosos

La fórmula de la mejor recta. Para una recta existe una fórmula exacta (el método de mínimos cuadrados). w es cuánto se mueven juntos los km y las horas, dividido entre cuánto se mueven los km por sí solos. b es tal que la recta pasa por el punto medio:

w = Σ (km − media de km) · (horas − media de horas) / Σ (km − media de km)², b = media de horas − w · media de km.

var n = km.Length;
double meanKm = 0, meanHours = 0;
for (var i = 0; i < n; i++)
{
meanKm += km[i];
meanHours += hours[i];
}
meanKm /= n;
meanHours /= n;
double top = 0, bottom = 0;
for (var i = 0; i < n; i++)
{
top += (km[i] - meanKm) * (hours[i] - meanHours);
bottom += (km[i] - meanKm) * (km[i] - meanKm);
}
var w = top / bottom;
var b = meanHours - w * meanKm;
Console.WriteLine($"la mejor recta: {w:F4} h/km, {b:F2} h");
Console.WriteLine($"MSE: {Mse(w, b):F2}");
Salida
la mejor recta: 0.1241 h/km, 5.64 h
MSE: 1.62

La mejor recta es algo mejor que B (1,62 frente a 1,83). El colega juzgó bien.

¿Por qué no usar siempre la fórmula? Vale para una recta con el error cuadrático. Con muchas características, con otra función de pérdida y con redes neuronales no hay una fórmula exacta, o es demasiado lenta. Por eso se usa el descenso del gradiente.

Más características. La recta puede tener una w por característica: horas = w₁ · km + w₂ · kg + w₃ · envíos + b. La idea y el MSE son los mismos.

El error absoluto medio (MAE) suma |error| en vez del cuadrado. No castiga tanto un error grande y es más tranquilo con los puntos raros. El MSE es más suave y por eso más cómodo para el descenso del gradiente.

En las bibliotecas: LinearRegression de scikit-learn calcula exactamente esta fórmula. En ML.NET es la regresión Sdca u Ols.

Ejercicio

El precio del mensajero

Resuelve la tarea en tu editor.

El mensajero no publica su lista de precios, pero la tienda guarda lo que pagó por ocho paquetes:

// paquetes: peso en kg y precio del mensajero en €
double[] kg = [0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 5.0, 6.0];
double[] eur = [4.10, 4.60, 5.00, 5.60, 6.40, 7.50, 8.30, 9.40];

Los colegas proponen tres rectas para el precio: A es 0,90 € por kg más 3,80 €, B es 1,10 € por kg más 3,20 €, C es 0,70 € por kg más 4,60 €. Escribe la función de pérdida (MSE), imprímela para las tres y di cuál es la mejor.

Salida
recta A (0.90 €/kg + 3.80 €): MSE 0.0144
recta B (1.10 €/kg + 3.20 €): MSE 0.0769
recta C (0.70 €/kg + 4.60 €): MSE 0.2869
menor MSE: recta A

Más tareas

  • El error absoluto medio. Sustituye el cuadrado por el valor absoluto. ¿Cambia el ganador?
  • La fórmula. Calcula la mejor recta con la fórmula de «Con más precisión». ¿Cuánto mejor que A es?

Test

Ponte a prueba

1. La recta es horas = 0,12 · km + 6. ¿Qué significa el 6?
2. ¿Por qué se elevan los errores al cuadrado antes de sumarlos?
3. La recta A tiene un MSE de 17,35 y la recta B un MSE de 1,83. ¿Cuál es mejor para estos pedidos?
4. Un pedido se pierde y llega a las 200 horas en vez de a las 25. ¿Qué le pasa a la recta con el MSE más pequeño?
5. ¿Por qué la recta puede decir algo de un pedido de 500 km y los k vecinos más cercanos no?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·