Ir al contenido principal

Regresión polinómica para Machine Learning

La regresión lineal es una herramienta extremadamente potente para realizar análisis de datos y construir modelos de Machine Learning capaces de hacer inferencias sobre datos nuevos. Sin embargo, esto es sólo cierto para datos que tienen un comportamiento lineal, lo que no siempre ocurre. Vamos a trabajar con un dataset sencillo, pero cuyos datos no son lineales: https://www.kaggle.com/akram24/position-salaries
Este dataset contiene los salarios según el nivel del empleado en dentro de una empresa.


Tras descargarlo de Kaggle y descomprimir el fichero tenemos un archivo CSV que podemos cargar con Pandas.

import pandas as pd

datos = pd.read_csv('Position_Salaries.csv')
print (datos)

    Position            Level  Salary
0   Business Analyst      1    45000
1  Junior Consultant      2    50000
2  Senior Consultant      3    60000
3            Manager      4    80000
4    Country Manager      5   110000
5     Region Manager      6   150000
6            Partner      7   200000
7     Senior Partner      8   300000
8            C-level      9   500000
9                CEO     10  1000000


Son muy pocos datos, lo que hace a este conjunto de datos interesante para usarlo como ejemplo. Como siempre, empezamos por el principio. Vamos a visualizar qué forma tienen los datos.

from matplotlib import pyplot as plt

x = datos['Level'].values.reshape(-1, 1) # necesitamos un array de 2D para SkLearn
y = datos['Salary'].values.reshape(-1, 1)
plt.scatter(x,y)



Vamos a ver qué tal se ajusta a un modelo lineal, para lo cuál aplicamos una regresión lineal valiéndonos de la librería SkLearn.

from sklearn.linear_model import LinearRegression

model = LinearRegression()
model.fit(x, y)
y_pred = model.predict(x)

plt.scatter(x, y)
plt.plot(x, y_pred, color='r')
plt.show()



No parece que el modelo lineal se ajuste demasiado bien. Una forma de medir la bondad del ajuste es calcular la media de la raíz del error cuadrático (root mean square error) que nos da una medida del error cometido por el modelo. En concreto se calcula la media de la desviación de los valores estimados. Otra métrica interesante es la medida R2 (R cuadrado), cuyo valor está entre 0 y 1, lo que la hace mejor a la hora de interpretar su valor, y es la fracción de la suma total de cuadrados que se 'explica por' la regresión.

import numpy as np
from sklearn.metrics import mean_squared_error, r2_score

rmse = np.sqrt(mean_squared_error(y,y_pred))
r2 = r2_score(y,y_pred)
print ('RMSE: ' + str(rmse))
print ('R2: ' + str(r2))

RMSE: 163388.73519272613
R2: 0.6690412331929895

Si nos fijamos, sobre todo en R2, vemos que el ajuste no es bueno (mientras más cercano a 1, mejor ajuste). Estaremos de acuerdo en que una curva se ajustaría mejor. Si queremos ajustar una curva a los datos tendremos que trabajar con más dimensiones. Es decir, tendremos que intentar ajustar un polinomio (de segundo grado, por ejemplo). Para ello fijémonos en la ecuación de la recta que hemos ajustado con la regresión lineal.
\begin{align}
y=a+bx
\end{align}
Si quisieramos ajustar los datos con una función de segundo grado, de decir, una curva, necesitamos añadir un término cuadrático. Recordemos que x es un atributo de nuestros datos, así que vamos a añadir a la función este mismo atributo pero al cuadrado.
\begin{align}
y=a+bx+cx^2
\end{align}
Esta transformación la podemos realizar fácilmente son SkLearn gracias PolynomialFeatures(). A la que le indicamos el grado de la función que queremos obtener. Haciendo uso del método fit_transform() obtendremos el término cuadrático que andamos buscando para nuestro modelo.

from sklearn.preprocessing import PolynomialFeatures

poly = PolynomialFeatures(degree=2, include_bias=False)
x_poly = poly.fit_transform(x)
print(x)
print(x_poly)

[[ 1]
 [ 2]
 [ 3]
 [ 4]
 [ 5]
 [ 6]
 [ 7]
 [ 8]
 [ 9]
 [10]]

[[  1.   1.]
 [  2.   4.]
 [  3.   9.]
 [  4.  16.]
 [  5.  25.]
 [  6.  36.]
 [  7.  49.]
 [  8.  64.]
 [  9.  81.]
 [ 10. 100.]]

Vemos que lo que ha hecho esta función es transformar una función lineal en otra cuadrática simplemente añadiendo el cuadrado del primer atributo. Vamos a calcular la regresión sobre esta nueva función y a comprobar si se ajusta mejor.

model.fit(x_poly, y)
y_pred = model.predict(x_poly)

plt.scatter(x, y)
plt.plot(x, y_pred, color='r')
plt.show()

rmse = np.sqrt(mean_squared_error(y,y_pred))
r2 = r2_score(y,y_pred)
print ('RMSE: ' + str(rmse))
print ('R2: ' + str(r2))

RMSE: 82212.12400451244
R2: 0.9162082221443943



Definitivamente el ajuste es mucho mejor, y el valor de R2 está mucho más cerca de 1. Si te estás preguntando qué ocurriría si en vez de una función cuadrática usáramos una función cúbica, no te cortes y pruébalo.

poly = PolynomialFeatures(degree=3, include_bias=False)
x_poly = poly.fit_transform(x)

model.fit(x_poly, y)
y_pred = model.predict(x_poly)

plt.scatter(x, y)
plt.plot(x, y_pred, color='r')
plt.show()

rmse = np.sqrt(mean_squared_error(y,y_pred))
r2 = r2_score(y,y_pred)
print ('RMSE: ' + str(rmse))
print ('R2: ' + str(r2))

RMSE: 38931.50401232937
R2: 0.9812097727913365



Mucho mejor. ¿Y si seguimos aumentando el grado?


poly = PolynomialFeatures(degree=4, include_bias=False)
x_poly = poly.fit_transform(x)

model.fit(x_poly, y)
y_pred = model.predict(x_poly)

plt.scatter(x, y)
plt.plot(x, y_pred, color='r')
plt.show()

rmse = np.sqrt(mean_squared_error(y,y_pred))
r2 = r2_score(y,y_pred)
print ('RMSE: ' + str(rmse))
print ('R2: ' + str(r2))

RMSE: 14503.2349096276
R2: 0.9973922891706611



Guau, el ajuste es casi perfecto. Pues ya puestos vamos a usar una función de grado 100 y será mucho mejor ¿no?
En realidad no. Como no tenemos datos de validación, estamos un poco a ciegas en cuanto a comprobar si nuestro modelo está sobreaprendiendo (overfitting), aunque un ajuste tan perfecto nos tiene que hacer sospechar. Si tuviéramos que usar el modelo para hacer predicciones sobre datos nuevos, seguramente las dos medidas que hemos usado para comprobar la bondad del ajuste (RMSE y R2) empezarían a degradarse considerablemente, ya que lo que ha hecho nuestro modelo es "aprenderse" los datos de entrenamiento, lo que significa que generalizará mal con nuevos datos. En un caso como este, es mejor un modelo más genérico (como el cuadrático) que uno que ajuste demasiado bien los datos de entrenamiento.

Como siempre, os dejo un enlace al notebook de Jupyter con el código del artículo: https://github.com/albgarse/InteligenciaArtificial/blob/master/Machine%20Learning/RegresionPolinomica2.ipynb

Comentarios

Entradas populares de este blog

Criptografía en Python con PyCrypto

A la hora de cifrar información con Python, tenemos algunas opciones, pero una de las más fiables es la librería criptográfica PyCrypto, que soporta funciones para cifrado por bloques, cifrado por flujo y cálculo de hash. Además incorpora sus propios generadores de números aleatorios. Seguidamente os presento algunas de sus características y también como se usa.


Regresión lineal y descenso de gradiente con Python

En machine learning, el objetivo principal es encontrar un modelo que explique el comportamiento de un sistema (en el amplio sentido de la palabra). A partir de unos datos de entrenamiento, un sistema de aprendizaje automático ha de ser capaz de inferir un modelo capaz de explicar, al menos en su mayoría, los efectos observados. Pero también aplicar ese aprendizaje. Por ejemplo, un sistema de machine learning muy lucrativo para las empresas anunciantes es aquél que dado un perfil de usuario (datos de entrada A), sea capaz de predecir si pinchará o no (salida B) sobre un anuncio publicitario de, por ejemplo, comida para gatos. No es sencillo crear un modelo capaz de predecir el comportamiento del usuario (o sí), pero en todo caso, existen diferentes técnicas que nos permiten abordar el problema. En el caso del ejemplo que acabamos de ver, el modelo debería ser capaz de clasificar a los usuarios en dos clases diferentes, los que pulsarán y los que no pulsarán el anuncio de comida de ga…

Manipulación de datos con pandas

Cuando uno lee un libro o un artículo sobre machine learning encuentra multitud de explicaciones sobre el algoritmo tal o cual. Sin embargo, no se habla demasiado sobre la manipulación y el limpiado de los datos, que bajo mi punto de vista es tan o incluso más importante que utilizar el algoritmo adecuado. Nuestro aliado en esta tarea es la librería pandas.
En lugar de hacer un recorrido exhaustivo por las funcionalidades de la librería, he preferido hacer uso de ella con un dataset real, para poder ver así, no sólo cuáles son sus funcionalidades, sino también cómo se aplican a datos reales. Así pues, en lugar de usar un dataset de los clásicos, he recurrido a uno real sacado de la web de datos abiertos del Ayuntamiento de Málaga. En concreto vamos a trabajar con el siguiente dataset, que se corresponde con las lecturas energéticas de los cuadros eléctricos durante el mes de marzo de 2017: https://datosabiertos.malaga.eu/dataset/lecturas-cuadros-electricos-marzo-2017.
Como siempre os…