sábado, 7 de agosto de 2021

Aprendizaje por conjuntos y bosques aleatorios (Random Forest)

Supongamos que preguntamos una cuestión difícil a miles de personas, si tomamos las respuestas del conjunto, podemos obtener una respuesta mejor que la de un experto. A este fenómeno se le llama sabiduría de la multitud. Del mismo modo si tenemos en cuenta las predicciones de un grupo amplio de predictores (tales como clasificadores o regresores) obtendremos mejores predicciones que con un solo predictor a este grupo de predictores lo llamamos conjunto (Ensemble) y a esta técnica la llamamos Aprendizaje por conjuntos (Ensemble Learning)  y al algoritmo de aprendizaje por conjuntos lo llamamos Método de ensamblaje (Ensemble Method).

Un ejemplo de este método de ensamblaje consiste en el entrenamiento de un conjunto de árboles de decisión, lo que llamamos Bosque Aleatorio (Random Forest) a pesar de su simplicidad son uno de los métodos más potentes de Machine Learning de la actualidad.

Votando clasificadores

Vamos a suponer que entrenamos varios clasificadores y cada uno alcanza un 80% de precisión. Entre ellos tenemos uno por regresión logística, un SVM, un bosque aleatorio y algunos más. Una forma simple de mejorar nuestro clasificador para hacer predicciones consiste en hacer que cada uno de nuestros métodos realice una predicción y luego tomar la clase predicha por la mayoría. De modo que nuestro nuevo clasificador aumenta la precisión del mejor de cada uno de los  predictores individuales.

Primero inicializamos nuestro script de Python.

# Requiere Python ≥3.

import sys

assert sys.version_info >= (3, 5)

# Requiera Scikit-Learn ≥0.20

import sklearn

assert sklearn.__version__ >= "0.20"

# importaciones comunes

import numpy as np

import pandas as pd

import os

# para hacer la salida de este notebook estable

np.random.seed(42)

# Para generar imágenes y gráficos

%matplotlib inline

import matplotlib as mpl

import matplotlib.pyplot as plt

mpl.rc('axes', labelsize=14)

mpl.rc('xtick', labelsize=12)

mpl.rc('ytick', labelsize=12)

Cargamos el Dataset (es este caso es un dataset creado por mi) se pueden utilizar Datasets descargados de librerías, aunque recomiendo que cada uno trabaje con su propio dataset.

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

quinielas.head()

from sklearn.model_selection import train_test_split

#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','Q1','Q2','QGC0','QGC1','QGC2','QGCM','QGF0','QGF1','QGF2','QGFM','timestamp','golesLocal','golesVisitante','fecha','timestamp'])

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

Dejamos las y como un array de una dimensión, esto nos dará advertencias, pero funciona, con tres dimensiones no funciona. En este caso dejamos que calcule las X de la quiniela (quitamos el Q1 y Q2).

Definimos un bosque aleatorio, un clasificador por votación, una regresión logística, y un SVM para evaluar que método acierta mejor las X de la quiniela.

from sklearn.ensemble import RandomForestClassifier

from sklearn.ensemble import VotingClassifier

from sklearn.linear_model import LogisticRegression

from sklearn.svm import SVC

log_clf = LogisticRegression(solver="lbfgs", random_state=42)

rnd_clf = RandomForestClassifier(n_estimators=100, random_state=42)

svm_clf = SVC(gamma="scale", random_state=42)

voting_clf = VotingClassifier(

    estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],

    voting='hard')

Entrenamos los diferentes métodos de clasificación

from sklearn.metrics import accuracy_score

for clf in (log_clf, rnd_clf, svm_clf, voting_clf):

    clf.fit(X_train, y_train)

    y_pred = clf.predict(X_test)

Y le indicamos que nos muestre el número de aciertos de cada método de cara a evaluar cual ha funcionado mejor.

from sklearn.metrics import accuracy_score

for clf in (log_clf, rnd_clf, svm_clf, voting_clf):

    clf.fit(X_train, y_train)

    y_pred = clf.predict(X_test)

    print(clf.__class__.__name__, accuracy_score(y_test, y_pred))

En este caso el bosque aleatorio es el que mejores resultados obtiene con un 73,44 %, aunque con un margen muy pequeño.

 

Bosques aleatorios

Si todos los clasificadores son capaces de estimar las probabilidades con el método predic_proba() podemos indicar a scikit-learn que muestre la clase con la probabilidad más alta, promediada sobre el resto de clasificadores individuales. Esto se llama soft voting. Y suele obtener mejores predicciones que hard voting porque asigna un mayor peso a los votos que obtienen mayores probabilidades. Para cambiar de hard voting a soft voting sólo tenemos que sustituir voting='hard' por voting='soft' Pero antes, debemos asegurarnos de que todos los clasificadores pueden estimar probabilidades. En este caso, tenemos SVC que no tiene ese método, de modo que necesitamos poner su hiperparámetro probability a True (esto hace que la clase SVC utilice validación cruzada para estimar las probabilidades de la clase).

log_clf = LogisticRegression(solver="lbfgs", random_state=42)

rnd_clf = RandomForestClassifier(n_estimators=100, random_state=42)

svm_clf = SVC(gamma="scale", probability=True, random_state=42)

 

voting_clf = VotingClassifier(

    estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],

    voting='soft')

voting_clf.fit(X_train, y_train)

Embolsado y pegado (Bagging and Pasting)

Acabamos de ver la forma de evaluar la precisión de diferentes clasificadores al mismo tiempo, para evaluar cual de ellos es el más adecuado para nuestro propósito. Otra aproximación consiste en utilizar el mismo algoritmo de entrenamiento pero entrenarlo en diferentes subsets aleatorios del set de entrenamiento, pero antes de hablar de embolsado y pegado, tenemos que saber qué es el Bootstrapping.

Bootstrapping, en estadística se refiere a un método de remuestreo que consiste en extraer repetidamente, con reemplazo, muestras de datos para formar otros conjuntos de datos más pequeños, llamados muestras de bootstrapping. Es como si el método bootstrapping estuviera haciendo un montón de simulaciones en nuestro conjunto de datos original, por lo que en algunos casos podemos generalizar la media y la desviación estándar.

Por ejemplo, digamos que tenemos un conjunto de observaciones: [2, 4, 32, 8, 16]. Si queremos que cada muestra de bootstrap contenga n observaciones, las siguientes son muestras válidas:

n = 3: [32, 4, 4], [8, 16, 2], [2, 2, 2]…   Bagging

n = 3: [ 32, 4], [8, 16, 2], [2]…     Pasting

Dado que extraemos datos con duplicidad, las observaciones pueden aparecer más de una vez en una sola muestra. Cuando las muestras permiten duplicidades, este método se llama Embolsado (Bagging) si se ejecutan sin duplicidad se llama pegado (Pasting).

Ambos, el embolsado y el pegado permiten entrenar instancias varias veces contra múltiples predictores pero sólo el embolsado permite entrenar instancias para ser muestreadas varias veces con el mismo predictor.

Una vez que todos los predictores son entrenados, el conjunto puede predecir una nueva instancia simplemente agregando la predicción de todos los predictores. La función de agregación es típicamente el modo estadístico (statistical mode) es decir, la predicción más frecuente (igual que el clasificador hard voting de antes) para clasificación, o media por regresión. Cada predictor individual tiene un sesgo más alto que si fuera entrenado por el conjunto original de entrenamiento, pero la agregación reduce  ambos, el sesgo y la varianza. Generalmente el resultado final es que por conjuntos tenemos un sesgo similar pero una varianza menor que entrenando un predictor simple con el mismo conjunto de datos.

El código expuesto a continuación entrena un conjunto de 500 árboles de decisión clasificadores, cada uno de ellos entrenado con 100 instancias  tomadas aleatoriamente de nuestro set de entrenamiento (con duplicados, pues es un ejemplo de Bagging, pero si queremos hacerlo pasting basta con poner en el código bootstrap =False). El parámetro n_jobs indica el número de CPU´s que utilizaremos, en este caso una.

from sklearn.ensemble import BaggingClassifier

from sklearn.tree import DecisionTreeClassifier

 

bag_clf = BaggingClassifier(

    DecisionTreeClassifier(random_state=42), n_estimators=500,

    max_samples=100, bootstrap=True, random_state=42)

bag_clf.fit(X_train, y_train)

y_pred = bag_clf.predict(X_test)

 

from sklearn.metrics import accuracy_score

print(accuracy_score(y_test, y_pred))

BaggingClassifier automáticamente ejecuta soft voting en lugar de hard voting si el clasificador de base es capaz de estimar probabilidades, lo cual es el caso en los árboles de decisión.

Bosques aleatorios

Como se ha dicho al principio, un bosque aleatorio, consiste en un conjunto de árboles de decisión generalmente entrenados por el método Bagging (o algunas veces Pasting) típicamente indicando con max_samples el valor del set de entrenamiento. En vez de montar un BaggingClassifier y pasarle un DecisionTreeClassifier, en su lugar, podemos pasarle la clase RandomforestClassifier que es más conveniente y está más optimizada para los árboles de decisión (para tareas de regresión existe RandomforestRegressor

from sklearn.ensemble import RandomForestClassifier


rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, random_state=42)

rnd_clf.fit(X_train, y_train)

y_pred_rf = rnd_clf.predict(X_test)

Salvo algunas excepciones RandomForestClassifier tiene todos los parámetros de un árbol de decisión (pues se trata de varios árboles) más los hiperparámetros del propio control  BaggingClassifier.

El algoritmo del bosque aleatorio introduce aleatoriedad extra cuando crecen los árboles; en vez de buscar la mejor característica cuando se divide un nodo, busca la mejor característica entre un subconjunto aleatorio de características. El algoritmo encuentra así una diversidad de árboles de los cuales toma los mejores, resultando así en un mayor rendimiento.

El siguiente  clasificador  BaggingClassifier es equivalente al mostrado más arriba.

 bag_clf = BaggingClassifier(

    DecisionTreeClassifier(splitter="random", max_leaf_nodes=16, random_state=42),

    n_estimators=500, max_samples=1.0, bootstrap=True, random_state=42)

bag_clf.fit(X_train, y_train)

y_pred = bag_clf.predict(X_test)

from sklearn.ensemble import RandomForestClassifier

 

rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, random_state=42)

rnd_clf.fit(X_train, y_train)

 

y_pred_rf = rnd_clf.predict(X_test)

np.sum(y_pred == y_pred_rf) / len(y_pred)

Otra cualidad que tienen los bosques aleatorios, es que hacen posible evaluar la importancia relativa de cada característica de nuestro conjunto de datos.  Scikit-Learn calcula los resultados automáticamente. Podemos acceder a estos resultados con la variable feature_importance_

Impulsores (Boosting)

Se refiere a cualquier método de ensamblado que pueda combinar cualquier algoritmo de aprendizaje débil dentro de un algoritmo de aprendizaje más potente. La idea general consiste en entrenar secuencialmente los diferentes predictores de forma que cada uno trate de corregir a su predecesor. Hay muchos métodos de aprendizaje por boosting, pero el más popular de lejos es AdaBoost.

AdaBoost

Consiste en un clasificador (un árbol de decisión por ejemplo) que se entrena con un dataset y realiza predicciones sobre él. Entonces, sobre estas predicciones se fija en las que ha fallado, dándoles un mayor peso  y ejecutando una nueva predicción con estos nuevos pesos. De modo que un segundo clasificador mejora algunos de estos fallos y aumenta el peso para los nuevos fallos de cara a entrenar un tercer clasificador y así sucesivamente. 

Impulso de gradiente (Gradient Boosting)

Igual que AdaBoost, trabaja secuencialmente añadiendo predictores a un conjunto y cada uno corrigiendo a su predecesor. Sin embargo en vez de ajustar los pesos en cada iteración intenta mejorar el siguiente predictor con los errores residuales generados por el predictor anterior.

Apilado (Stacking)

El último método de conjuntos está basado en una idea simple, en vez de utilizar una función trivial para elegir cual es el mejor, se entrena un modelo para que lo calcule. El modelo se llama Blender (Batidora) por que toma como entradas las salidas de todos los predictores. Por desgracia, Scikit-Learn no tiene funciones para hacer apilado, aunque existen métodos para implementarlo fácilmente de forma manual.


sábado, 31 de julio de 2021

Regresión logística y clasificación para Machine Learning II. Clasificación

En este post veremos cómo utilizar el concepto de regresión logística para resolver problemas de clasificación. En cualquier clasificación, primero observaremos los atributos de los datos que tenemos. Y luego, basándonos en esos atributos, realizaremos una categorización o clasificación. La forma más simple de clasificación es una clasificación binaria, en la que solo tenemos dos resultados posibles. Por ejemplo, si un correo electrónico ingresa en nuestra bandeja de entrada, tendremos que clasificarlo como spam o genuino es decir, como correo electrónico auténtico.

En un problema de regresión logística, lo que estamos tratando de predecir es una probabilidad que toma valores entre cero y uno. (en realidad entre 0 y 100%) esto se puede trazar mediante una curva en S.

Regresión logística y clasificación para Machine Learning
Si queremos utilizar un modelo de regresión logística cuyas salidas son probabilidades para hacer una clasificación, tendremos que establecer algún tipo de umbral. Y  hecho esto, podemos decir que cualquier valor que caiga en un lado de este umbral, cae en una clase y los del otro, caen en otra categoría. Una vez que se hayamos establecido este umbral, podríamos utilizarlo. Por ejemplo, si queremos que un clasificador categorice nuestros correos electrónicos como spam o genuinos, debemos introducir el correo electrónico en nuestro clasificador para que realice una serie de verificaciones y estime la probabilidad, según la cual el correo electrónico se clasificará como spam o genuino. El un modelo clasificador basado en (Machine Learning) ML, tendremos que entrenar nuestro modelo de red neuronal clasificadora con un corpus de datos. Este corpus necesita correos electrónicos preclasificados. Y estos se introducirán en nuestro modelo clasificador, que realizará algún tipo de categorización. Compara la salida de la clasificación con los valores reales y luego estima una pérdida. Y este dato de pérdida o diferencia entre la salida y la realidad, retroalimentarán al clasificador para que pueda ajustarse a sí mismo, con el fin de hacer una mejor predicción en el futuro. 

Una vez completada la capacitación, lo que queda es un modelo de regresión logística, que puede utilizarse para hacer predicciones.  Una vez que tengamos nuestro clasificador basado en aprendizaje automático, podemos asegurarnos de que cuando un correo electrónico llegue a nuestra bandeja de entrada, este se envíe a este clasificador. Basado en lo que ha aprendido durante la capacitación, el modelo tomará una decisión. Si el  correo electrónico es genuino o es spam. 

Regresión logística y clasificación para Machine Learning
Si llega un correo electrónico y resulta que intenta vendernos algún producto. Se trata de un correo electrónico bastante comercial que se envía a varios destinatarios diferentes., y  contiene un saludo genérico. Este correo electrónico se enviará a nuestro clasificador, que debe tomar una decisión. Aplicará el principio de regresión logística y estimará una probabilidad. Si por ejemplo calcula que la probabilidad de que este correo electrónico sea spam es del 55%. Y luego, dependiendo de dónde se haya establecido el umbral para realizar esta clasificación, este correo electrónico se marcará como spam o genuino. 

Esta es la región de regresión logística del modelo, que es muy sensible a cualquier cambio en la entrada. Puede haber algunas características en el correo electrónico que hagan que nuestro modelo de regresión logística decida que hay un 99% de probabilidad de que se trate de spam. Nuestro modelo puede calcular un solo valor de probabilidad. Si necesitamos realizar una clasificación utilizando este modelo de regresión logística, deberemos establecer un umbral. La regla del 50% dicta que, en caso de una clasificación binaria, este umbral debe establecerse en una probabilidad del 50%.

Por lo tanto, cualquier probabilidad de spam inferior al 50% debe clasificarse como genuino, y cualquier probabilidad mayor debe marcarse como spam. Si solo hay dos resultados posibles para este clasificador, cada uno de ellos opera sobre un rango de valores de probabilidad. La combinación de la curva S de regresión logística y el establecimiento de un valor umbral, nos ayudará a realizar la clasificación.