Mínimos Cuadrados ordinarios — Build your own model

Luis Emmanuel Avila Leon
9 min readAug 10, 2023

--

Dentro del campo del Machine Learning, uno de los conceptos más fundamentales y poderosos es la regresión lineal. Un algoritmo simple y fácil de interpretar, pero, a su vez esconde un potente método que permite predecir valores numéricos continuos a partir de variables de entrada. En este artículo nos adentramos en el método de Mínimos Cuadrados Ordinarios (OLS). Aprenderemos cómo este enfoque se adapta a una amplia variedad de problemas del mundo real y como su implementación en la programación utilizando Python es sorprendentemente sencilla y accesible.

Introducción intuitiva

Supongamos que entrevistamos 30 trabajadores de IT con distintos años de experiencia sobre cuál es su salario anual, recogemos estos datos y los presentamos en un gráfico de dispersión.

Fuente: Elaboración propia

A simple vista podemos notar que existe una relación lineal entre los años de experiencia y el salario recibido, lo cual tiene sentido ya que por lo regular mientras más años de experiencia se tiene, el salario percibido tiende a incrementar.

Entonces fácilmente podemos tomar un lápiz y dibujar una línea que capture esta tendencia y nos permita conocer cuál podría ser el salario para aquellos casos que no conocemos.

Fuente: Elaboración propia

Como se aprecia en el grafico anterior, se dibujaron 3 líneas que tratan de explicar cuál es la tendencia de los datos. Pero ¿cuál es mejor?, ¿alguna de estas líneas realmente captura la tendencia de los datos?, ¿cómo podemos definir que lo hace correctamente?

Recordando las clases de matemáticas podemos encontrar que una línea se puede construir con una simple ecuación:

La ecuación y = mx + b es una representación algebraica simple de una línea recta. La pendiente (m) determina la inclinación de la línea y cómo se “sube” o “baja” a medida que avanzamos en el eje x, mientras que el término constante (b) determina la posición vertical de la línea.

Fuente: Elaboración propia

Como se puede observar en el grafico anterior la línea está formada por la ecuación y = mx + b , donde cuando x es 0, el valor de y sería igual al valor de b.

Sabiendo todo esto ya podemos comenzar a esbozar una solución con nuestros datos.

Fuente: Elaboración propia

En el gráfico anterior podemos observar como una recta fue trazada tratando de ajustarse a la tendencia de los datos; a simple vista podemos observar que existe una diferencia entre el valor predicho y el valor observado, a esta diferencia se le conoce como error y se denota de la siguiente forma:

Entonces para conocer el error del modelo, se calcularía sumando todos los errores para cada observación.

Sin embargo, esta formula tiene un inconveniente, si las diferencias entre el valor real y el predicho son aleatorias, podrán ser tanto positivas como negativa, lo cual puede terminar llevando el error a un valor cercano a 0 o negativo. Para esto calcularemos las diferencias elevándolas al cuadrado. Lo que nos da como resultado.

Finalmente, hemos llegado al núcleo del problema, conociendo “x”, la variable predictora, y la variable a predecir “y”, debemos encontrar los valores de m y b que minimicen la suma de los errores al cuadrado. Esta tarea será resuelta a través de la optimización.

A continuación, se hará la demostración de la obtención de los parámetros de la regresión que minimicen la suma de los errores al cuadrado.

Resolvemos:

Entonces podemos definir m y b como:

Otra forma de realizar esta demostración es utilizando la forma matricial, esto nos ayudara en caso donde tengamos más de una variable predictora.

Una vez comprendimos como los parámetros de regresión son obtenidos, es turno de llevarlos a un lenguaje de programación. En este caso usaremos Python para implementar ambos modelos desde 0.

Implementación en Python

Usando el lenguaje de programación Python y las librerías Numpy, pandas Y matplotlib, implementaremos un objeto que nos permita calcular los parámetros m y b de una regresión.

Primero importaremos las librerías necesarias, numpy para las operaciones matemáticas, matriciales y vectoriales, pandas para leer el conjunto de datos al cual aplicaremos las regresión y finalmente matplotlib la cual nos permitiría visualizar los datos.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use('seaborn-v0_8')

Usaremos un pequeño dataset de salario de trabajadores de la plataforma Kaggle “Salary Dataset — Simple linear regression | Kaggle” para realizar esta practica.

data = pd.read_csv('Salary_dataset.csv')
print(data.head())

Lo cual nos dará como resultado:

Bien, ahora es turno de implementar una clase que nos permita realizar la obtención de los parámetros m y b de una regresión.

class OLS():

def __init__(self):
self.b = 0
self.m = 0

def fit(self, X, y):
n = X.shape[0]
self.m = (n*np.sum(X*y) - np.sum(y)*np.sum(X))/(n*np.sum(X**2) - np.sum(X)**2)
self.b = y.mean() - X.mean() * self.m

def predict(self, X):
return X * self.m + self.b

Como se puede ver en código se creo una clase llamada OLS esta clase cuenta con los atributos m y b, el método fit el cumple con la función de realizar el calculo de los valores m y b y el método predict el cual calculara el valor de y con los valores ingresados.

Ahora podemos crear un objeto el cual podremos entrenar con nuestros datos y crear una regresión.

X = data['YearsExperience']
X_copy = X.values.reshape(-1,1)
y = data['Salary']
ols = OLS()
ols.fit(X,y)
print(f'm:{ols.m}, b:{ols.b}')

Al ejecutar esa celda de código, obtenemos los variable X y la variable y, creamos el objeto OLS y ejecutamos el método fit, finalmente mostramos el valor resultante de m y b.

Para hacer esto mas ilustrativo generaremos una grafica con valores predichos, lo cual nos ayudara a ver si el modelo predijo correctamente.

X_ = np.linspace(X.min() - 1.5, X.max() + 1.5, 100)
y_pred = ols.predict(X_)

fig, ax = plt.subplots()
ax.scatter(X,y, color = 'limegreen', marker= 'o')
ax.plot(X_, y_pred, color = 'orangered', label = f'$y = {ols.m:.3f}x + {ols.b:.3f}$')
ax.set_xlim(0, 12)
ax.set_ylim(0, y_pred.max())
ax.set_xlabel(X.name)
ax.set_ylabel(y.name)
ax.legend()
ax.set_title('Salary vs Experience')
plt.show()
Fuente: Elaboración propia

Como puede observarse, la línea se ajusta muy bien a la tendencia de los datos, lo cual nos indica que es un buen modelo, sin embargo, existen mejores formas para poder determinar si un modelo se ajusta correctamente a los datos o no, lo cual veremos en la siguiente sección.

Ahora implementaremos un modelo usando el otro método de obtención de los parámetros de regresión.

class OLSVec():

def __init__(self):
pass

def fit(self, X, y):
X = np.column_stack((np.ones(len(X)), X))
self.b = np.linalg.inv(X.T @ X) @ X.T @ y

def predict(self, X):
return np.dot(X, self.b[1:]) + self.b[0]

En esta implementación se utilizan operaciones vectoriales para obtener los parámetros de regresión, siendo mucho mas optima a nivel computacional y nos abre la posibilidad de agregar mas variables predictoras.

olsvec = OLSVec()
olsvec.fit(X,y)
print(f'b:{olsvec.b}')

Al implementar el modelo y entrenarlo, podemos ver como los parámetros de regresión son exactamente iguales a los calculados con el método tradicional.

Si realizamos la misma operación de graficar los datos y su línea de tendencia nos encontraremos con un grafico similar al método anterior.

X_ = np.linspace(X.min() - 1.5, X.max() + 1.5, 100)
X_ = X_.reshape(-1, 1)
y_pred = olsvec.predict(X_)

fig, ax = plt.subplots()
ax.scatter(X,y, color = 'limegreen', marker= 'o')
ax.plot(X_, y_pred, color = 'orangered', label = f'$y = {olsvec.b[1]:.3f}x + {olsvec.b[0]:.3f}$')
ax.set_xlim(0, 12)
ax.set_ylim(0, y_pred.max())
ax.set_xlabel(X.name)
ax.set_ylabel(y.name)
ax.legend()
ax.set_title('Salary vs Experience')
plt.show()
Fuente: Elaboración propia

¡Enhorabuena!, Hemos implementados nuestros primeros algoritmos de regresión desde 0.

Evaluación de un modelo de regresión

Evaluar un modelo es fundamentar para conocer su rendimiento y que tan bien se ajusta a nuestros datos. Existen muchas métricas para evaluar el rendimiento de una regresión, entre ellas se encuentra:

  • Error medio absoluto (MAE): Calcula la diferencia absoluta promedio entre las predicciones y los valores reales. Su utilidad radica en su capacidad para medir de manera directa y simple la magnitud promedio de los errores.
  • Error cuadrático medio (MSE): Calcula el promedio de las diferencias al cuadrado entre las predicciones y los valores reales. El MSE penaliza los errores mas grandes que el MAE y es muy útil para compararlo con otros modelos. Además, esta forma nos permite utilizarla en algoritmos de optimización los cuales nos ayudaran a encontrar una solución de forma iterativa.
  • Raíz del error cuadrático medio (RMSE): Es la raíz cuadrada del MSE y proporciona una medida de error en la misma unidad que la variable de destino.
  • Coeficiente de determinación (R2): Mide la proporción de la varianza total de la variable dependiente que es explicada por el modelo. Un valor cercano a 1 indica un buen ajuste. Es una forma de evaluar si el modelo captura significativamente la variabilidad de los datos.

Ahora que conocemos las métricas es turno de implementar en Python y probarlas en nuestros modelos creados.

def mse(y_true,y_pred): #Mean Squared Error 
n = len(y_true)
error = y_true - y_pred
return sum(error ** 2) / n

def rmse(y_true,y_pred): #Root Mean Square Error
n = len(y_true)
error = y_true - y_pred
mse = sum(error ** 2) / n
return np.sqrt(mse)

def mae(y_true,y_pred): #Mean Absolute Error
n = len(y_true)
error = y_true - y_pred
return np.sum(np.abs(error)) / n

def r2(y_true, y_pred): #Determination Coefficient
mean_y_true = np.mean(y_true)
ss_total = np.sum((y_true - mean_y_true)**2)
ss_residual = np.sum((y_true - y_pred)**2)
r_squared = 1 - (ss_residual / ss_total)
return r_squared

Ahora que tenemos el código para ejecutar estas métricas es turno de probarlas en nuestros modelos.

y_pred_vec = olsvec.predict(X_copy)
y_pred_ols = ols.predict(X.values)

print('OLS Metrics:\n'
f'MSE: {mse(y, y_pred_ols):.3f}\n'
f'RMSE: {rmse(y, y_pred_ols):.3f}\n'
f'MAE: {mae(y, y_pred_ols):.3f}\n'
f'R2: {r2(y, y_pred_ols):.3f}')

print('OLSVEC Metrics:\n'
f'MSE: {mse(y, y_pred_vec):.3f}\n'
f'RMSE: {rmse(y, y_pred_vec):.3f}\n'
f'MAE: {mae(y, y_pred_vec):.3f}\n'
f'R2: {r2(y, y_pred_vec):.3f}')

Como podemos observar, las métricas obtenidas en ambos modelos son iguales, lo cual era de esperarse, adéntranos mas a los resultados podemos observar como el valor de RMSE y MAE son relativamente pequeños en comparación al rango de valores que maneja la variable y, además, podemos observar como el valor de R2 es de 0.957 lo cual es un excelente indicativo de que nuestro modelo captura la variabilidad de nuestros datos.

Conclusiones

OLS es un algoritmo potente que nos permite introducirnos a los temas de regresión de forma intuitiva, sin embargo, es un algoritmo muy limitado a supuesto que deben cumplirse para usarse como la linealidad de los datos, en muchos casos será necesario recurrir a técnicas mas avanzadas de aprendizaje automático.

Si deseas consultar el código fuente completo de la implementación de este algoritmo, además de su implementación usando librerías de machine learning puedes revisar el repositorio de esta entrada y otras dando clik en el siguiente enlace Repositorio.

Referencias

--

--

Luis Emmanuel Avila Leon

I am a student of computer systems engineering, I have a great taste for data science, artificial intelligence, and machine learning algorithms.