sábado, 28 de agosto de 2021

Regresión logística y clasificación para Machine Learning IV. Preparación de datos

Estos post están pensados para que el lector realice las pruebas con sus propios Datasets, pues ya estoy harto de seguir ejemplos siguiendo las instrucciones para tratar un dataset previamente descargado de internet con datos que ni entendemos ni nos sirven de mucho. Creo que se aprende mucho más creando tu propio dataset y adaptando las instrucciones a tus necesidades en vez de ejecutar como un robot las instrucciones del script. 

Vamos a crear un modelo de clasificación utilizando los conocimientos que hemos visto sobre regresión logística,

RL1RL2RL3, y scikit-learn.

import matplotlib.pyplot as plt

import numpy as np

import pandas as pd

from sklearn import linear_model,  preprocessing

from sklearn.linear_model import LogisticRegression

Estas líneas llaman a las librería que necesitaremos. También vamos a suprimir las advertencias en el notebook, ya que algunas de las bibliotecas dependientes de scikit-learn pueden emitir advertencias.

import warnings

warnings.filterwarnings('ignore')

 import pandas as pd

quinielas = pd.read_csv('Completo_Etiquetado_Puntuado.csv') #tiene goles quiniela y quinigol

quinielas.head()

Regresión logística. Preparación de datos
Una vez que hayamos cargado el conjunto de datos en el DataFrame, podemos mezclarlo haciendo uso de la función de muestra Pandas DataFrame. Y le especificamos una fracción de 1, de modo que se mezcle todo el conjunto de datos.

quinielas = quinielas.sample(frac = 1).reset_index(drop = True)

En nuestro caso no sería necesario hacer esto, pues ya son datos suficientemente aleatorios, pero con otro tipo de dataset puede venir bien hacerlo. Después, verificamos la longitud de este DataFrame en particular y obtenemos la información de que contiene 37147 filas de datos.

len(quinielas)

Si nuestra columna de etiquetas viene como cadena texto, es necesario realizar una codificación nuestro DataFrame.

No podemos usar valores de cadena cuando se introducen en nuestro modelo y debemos convertirlo en etiquetas específicas. Después de importar LabelEncoder, que es una de las herramientas de preprocesamiento de scikit-learn, lo aplicaremos a nuestra columna de etiquetas del DataFrame, usando la función fit_transform de LabelEncoder.

from sklearn.preprocessing import LabelEncoder

lb_make = LabelEncoder()

quinielas["etiqueta"] = lb_make.fit_transform(quinielas["etiqueta"])

En nuestro dataset tampoco es necesario pues todos estos pasos ya se siguieron en este curso pero dejo el código por si alguien lo necesita.

Esto reemplaza los valores actuales usados en la columna de la etiqueta con los valores codificados. 

Este conjunto de datos (dataset) incluye actualmente un total de 16 columnas, incluyendo la etiqueta. Lo que significa que hay 15 características potenciales que se pueden incorporar a nuestro modelo. Si tuviéramos que usar esas 15 columnas como características, esa técnica se denominaría regresión del sumidero de la cocina (kitchen sink regression). Ya que literalmente estamos utilizando todo en nuestro modelo. Sin embargo, estos modelos tienden a sobreajustarse (overfitting) con los datos de entrenamiento. Y terminan haciendo predicciones deficientes cuando se usan con datos reales. Una cosa que podemos hacer para evitar tal escenario es ser selectivos en las características que se utilizan para nuestro modelo. Es decir, realizamos una selección de funciones. Y para eso, usaremos un mapa de calor de Seaborn.

import seaborn as sns

import matplotlib.pyplot as plt

corrmat = quinielas.corr()

f,ax = plt.subplots(figsize = (15,15))

sns.heatmap(corrmat, vmax=.8, square=True, annot=True, fmt='.2f')

plt.show()

Importamos la biblioteca Seaborn y pyplot de matplotlib. Un mapa de calor tiene varios usos. Pero uno de ellos es visualizar la correlación entre varias columnas en nuestro  DataFrame. Si dos columnas están muy fuertemente correlacionadas entre sí, no tiene mucho sentido incluir ambas como características para nuestro modelo. Y probablemente podamos conformarnos con solo una. Para obtener toda la correlación entre las distintas columnas, usamos la función corr de Pandas.

Esto devolverá una matriz de correlación, que incluye los valores de correlación entre todos los posibles pares de columnas en nuestro DataFrame. Después, introducimos esta matriz de correlación en un mapa de calor de Seaborn, lo que nos facilitará la visualización de las correlaciones. Lo que nos permitirá identificar rápidamente qué columnas están fuertemente correlacionadas y potencialmente pueden eliminarse de nuestro conjunto de funciones. Inmediatamente algunas cosas saltan a la vista.

mapa de calor seaborn

Los valores cercanos a 1 nos indicarán que no necesitamos una de esas dos columnas correlacionadas. En el mapa aparecen con tonos más claros en este mapa de calor en particular representan una correlación positiva muy alta, y los más oscuros representan correlaciones negativas muy altas. Después de analizar este mapa de calor, podemos llegar a las características que usaremos para nuestro modelo. Hay que tener en cuenta que la gráfica es simétrica por la diagonal, así que sólo tenemos que fijarnos en la parte inferior o en la superior pero no en ambas. En este caso elegimos la parte inferior. Otra cosa a destacar la he marcado en amarillo. Vemos una fuerte correlación entre los goles metidos por el equipo local cuando son más de 2 y por el visitante cuando se queda a 0, con el número de goles respectivo. Es lógico, las cuatro columnas son etiquetas y no aparecerán cuando pretendamos hacer predicciones. ¡ya quisiéramos saber los goles que va a meter cada equipo antes de hacer una quiniela!.

En este caso concreto lo que nos interesa son los equipos, junto con los puntos de cada equipo, su diferencia y sobre todo la diferencia de puntos normalizada, de cara a predecir el resultado de cada partido.

#quito todas las etiquetas excepto la Q1

#quito todos los puntos y dejo sólo la diferencia normalizada

x_data = quinielas.drop(columns = ['idPartido','temporada','golesLocal','golesVisitante','fecha','QX','Q2','QGC0','QGC1','QGC2','QGCM','QGF0','QGF1','QGF2','QGFM'])

x_data.head()

Así que hemos reducido de 15 columnas potenciales a 10 para rellenar nuestro x_data. Ahora configuramos nuestro y_data para que sea la columna de etiqueta de nuestro DataFrame.

y_data = quinielas ['Q1']

Y lo siguiente que hacemos es convertir los datos x e y en matrices NumPy.

import numpy as np

x_data = np.array(x_data)

y_data = np.array(y_data)

Una vez que se han convertido en matrices NumPy, podemos dividir nuestros datos x e y en conjuntos de prueba y de entrenamiento.

Debemos asegurarnos de que no estemos sobreajustado a los datos de entrenamiento, para lo cual creamos el conjunto de prueba por separado. Y aunque la división se puede realizar de varias maneras, usamos el 80% de los datos para entrenamiento y reservamos el 20% restante para propósitos de prueba. Una vez que nuestro x_data se ha dividido, también debemos aplicar una división correspondiente para y_data.

train_data = x_data[:29718]

test_data = x_data[29718:]

train_labels = y_data [:29718]

test_labels = y_data [29718:]

Dado que nuestro conjunto de datos contiene un total de 37147 filas, nuestros datos de entrenamiento incluyen todas menos las últimas 7429 filas que se convierten en nuestros datos de prueba. Y con eso, todos los datos que necesitamos para construir nuestro modelo de regresión lineal ya están listos.

Clasificación mediante un modelo de regresión logística

Ahora que hemos preparado todos los datos que necesitamos entrenar y evaluar nuestro modelo.

Hemos inicializado nuestro modelo de regresión logística con todos los parámetros predeterminados. Y después comenzamos el entrenamiento de este modelo llamándolo con la función de ajuste.

logisticRegr = LogisticRegression()

Para eso, analizamos todos nuestros datos x, es decir, los datos de entrenamiento, así como las etiquetas de entrenamiento. Esta formación debería ser razonablemente rápida, ya que no tenemos demasiadas filas de datos. Y una vez hecho esto, veremos que  hemos creado nuestro modelo de regresión logística y podemos echar un vistazo a todos los parámetros predeterminados para este modelo.

logisticRegr.fit(train_data, train_labels)

Entonces, ¿es correcto este modelo de regresión logística? solo hay una

forma de averiguarlo, y es usar test_data para obtener las predicciones.

predictions = logisticRegr.predict(test_data)

Para esto, hacemos uso de la función de predicción del modelo de regresión logística, a la que pasamos el test_data y obtenemos las predicciones. Podemos comparar los valores en estas predicciones con los valores reales y calcular una puntuación de precisión  nosotros mismos. O alternativamente, podemos hacer uso de la característica de puntuación de este modelo de regresión logística.

score = logisticRegr.score(test_data, test_labels)

print(score)

Clasificación mediante un modelo de regresión logística
Para esto, alimentamos test_data y test_labels. Esto calculará las predicciones usando test_data y nuestro modelo de regresión logística y luego las comparará con test_labels para dar una puntuación de precisión. Y aquí vemos que nuestro modelo de regresión logística ha funcionado razonablemente bien, incluso con los parámetros predeterminados al obtener una puntuación de precisión del 50%. Lo que pone de manifiesto que se trata de datos estrictamente aleatorios.


sábado, 21 de agosto de 2021

Bosques aleatorios con Python (Random Forest)

El bosque aleatorio (Random Forest)  crea un bosque o un conjunto de árboles de decisión. Entrenados mediante el método de embolsado (Bagging). La ventaja conceptual de este método es que una combinación de modelos de aprendizaje debería mejorar el resultado general. Un bosque aleatorio crea varios árboles de decisión y luego los combina para obtener una predicción más precisa y un mejor resultado general.

Estos post están pensados para que el lector realice las pruebas con sus propios Datasets, pues no veo de mucha utilizad seguir ejemplos siguiendo las instrucciones para tratar un dataset previamente descargado de internet con datos que ni entendemos ni nos sirven de mucho. Creo que se aprende mucho más creando nuestro propio dataset y adaptando las instrucciones a nuestras necesidades en vez de ejecutar como un robot las instrucciones del script. 

Aquí tenemos como crear nuestro propio dataset 

import numpy as np

import matplotlib.pyplot as plt

%matplotlib inline

import pandas as pd

quinielas = pd.read_csv('Completo_Etiquetado_Puntuado_3.csv') #tiene goles quiniela y quinigol

from sklearn.model_selection import train_test_split

from sklearn import tree

from sklearn.ensemble import RandomForestClassifier

En las líneas de arriba hemos importado algunas librerías, nuestro Dataset y  un clasificador de bosque aleatorio de la librería de scikit-learn.

A continuación cargamos una variable ‘X’ con los datos de nuestro Dataset y una ‘y’ con las etiquetas. Y generamos un subconjunto de entrenamiento y otro de pruebas.

#las X contienen los datos relevantes para hacer predicciones quitamos todas las etiquetas

X = quinielas.drop(columns=['idPartido','temporada','Q1','QX','Q2','QGC0','QGC1','QGC2','QGCM','QGF0','QGF1','QGF2','QGFM','timestamp','golesLocal','golesVisitante','fecha'])

#las Y son las etiquetas a predecir, en este caso quitamos todo excepto 'Q1','QX','Q2'

y = quinielas.drop(columns=['division','jornada','idPartido','temporada','PuntosLocal','PuntosVisitante','EquipoLocal','Puntos_Normalizados','EquipoVisitante','QGC0','QGC1','QGC2','QGCM','QGF0','QGF1','QGF2','QGFM','timestamp','golesLocal','golesVisitante','fecha','timestamp'])

 

#dividimos nuestros datos en un conjunto de entrenamiento y otro de pruebas

X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.33,random_state=42)    

Ahora, creamos una instancia del clasificador del árbol de decisiones, y la entrenamos con la intención de obtener algunas predicciones.

arbol_decision = tree.DecisionTreeClassifier()

arbol_decision = arbol_decision.fit(X_train, y_train)

prediccion_arbol_decision= arbol_decision.predict(X_test)

Creamos una instancia del clasificador de bosque aleatorio, estoy especificando el número de estimadores como 5, podemos variar eso como queramos. También entrenamos el bosque aleatorio.

Bosque_aleatorio = RandomForestClassifier(n_estimators=5)

Bosque_aleatorio.fit(X_train, y_train)

Bosques aleatorios

Hacemos algunas predicciones

prediciones_bosque_aleatorio = bosque_aleatorio.predict(X_test)

E imprimimos los resultados de la predicción del arbol de decisión primero.

from sklearn.metrics import classification_report

print(classification_report(y_test, prediccion_arbol_decision))

 

Classification Report de un bosque aleatorio

A continuación imprimimos los resultados del bosque aleatorio

from sklearn.metrics import classification_report

print(classification_report(y_test,prediciones_bosque_aleatorio))

 

bosque aleatorio

Y los números son claramente inferiores en el bosque aleatorio. Lo cual resulta extraño, pues debería ser lo contrario, aunque tengo la impresión de que los aciertos del árbol de decisión no son realistas. En ambos casos, la tabla tiene los encabezados de columna: precisión, recuperación, puntuación f1 y soporte. Incluido un resumen al final.

Así que ahora y variamos el número de estimadores a 10. Cambiamos el valor de 5 a 10 en el código

Bosque_aleatorio = RandomForestClassifier(n_estimators=10)

También variaremos el tamaño de la prueba. Hagamos que el tamaño de la prueba sea del 60%. Eso significa que solo tendremos el 40% para entrenamiento, así que veremos si eso tiene un impacto.

#dividimos nuestros datos en un conjunto de entrenamiento y otro de pruebas

X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.60,random_state=42)    

 

Random Forest (bosque aleatorio)

Aquí vemos que los resultados varían. En realidad no hay garantía de que el  bosque aleatorio supere al árbol de decisiones en todo momento. Entonces, ¿qué dice esto? los bosques aleatorios que constan de varios árboles individuales, cada uno basado en una muestra aleatoria de datos de entrenamiento, suelen ser más precisos que los árboles de decisión únicos. Los árboles de decisión únicos tienen árboles que se podan, mientras que los bosques aleatorios no se podan. Son diversos, simplemente tienen una resolución más alta en el espacio de funciones. Por lo que también tienden a manejar mejor el sobreajuste. De hecho, en la mayoría de los casos, encontraremos que los bosques aleatorios suelen ser más precisos que los árboles de decisión únicos.