Herramientas de usuario

Herramientas del sitio


clase:ia:saa:2eval:clasificacion_modelos_5

12 - Modelos clasificación 5: Ensamblaje y Random Forest

A menudo, se obtienen predicciones mejores si sumamos las predicciones de un grupo de predictores (clasificadores o regresores). Un grupo de predictores se denomina ensamble y la técnica de juntarlos ensamblaje.

En este tema vamos a ver varios de los métodos de ensamblaje más populares.

Clasificadores de votación

Vamos a crear varios clasificadores (árbol de decisión, Knn y regresión logística) para el conjunto de datos cancer de Sklearn:

import pandas as pd
import numpy as np

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import VotingClassifier


from sklearn.metrics import accuracy_score

# Configuración warnings
# ==============================================================================
import warnings
warnings.filterwarnings('ignore')

cancer = load_breast_cancer()

df = pd.DataFrame(data = cancer.data, columns = cancer.feature_names)
df['target'] = cancer.target

X = df.drop(columns = ['target'], axis = 1)
y = df['target']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=25)

tree_clf = DecisionTreeClassifier(max_depth=5)
knn_clf = KNeighborsClassifier(n_neighbors=5, weights='uniform')
log_clf = LogisticRegression()

Vemos la exactitud de cada uno de ellos:

for clf in (tree_clf, knn_clf, log_clf):
    clf.fit(X_train, y_train)
    y_pred=clf.predict(X_test)
    print(clf.__class__.__name__, float("{0:.4f}".format(accuracy_score(y_test, y_pred))))

DecisionTreeClassifier 0.9357
KNeighborsClassifier 0.924
LogisticRegression 0.924

Para crear un clasificador de votación, usamos la clase VotingClassifier de Sklearn. Para crear el clasificador, le indicamos los estimadores que vamos a usar (en nuestro caso, los 3 clasificadores que hemos creado) mediante el parámetro estimators y el tipo de votación (ya veremos más adelante que tipos hay):

voting_clf_hard = VotingClassifier(
    estimators=[('dt', tree_clf), ('knn', knn_clf), ('lr', log_clf)],
    voting = 'hard' 
)

voting_clf_hard.fit(X_train, y_train)

Mostramos ahora la exactitud de los 4 clasificadores:

for clf in (tree_clf, knn_clf, log_clf, voting_clf_soft):
    clf.fit(X_train, y_train)
    y_pred=clf.predict(X_test)
    print(clf.__class__.__name__, float("{0:.4f}".format(accuracy_score(y_test, y_pred))))

DecisionTreeClassifier 0.9357
KNeighborsClassifier 0.924
LogisticRegression 0.924
VotingClassifier 0.9415

Nuestro clasificador de votación consigue una exactitud mayor que el mejor clasificador del ensamble. De hecho, aunque un clasificador sea débil (el resultado es sólo un poco mejor que una suposición aleatoria), el ensamble puede ser un clasificador fuerte siempre y cuando haya un número suficiente de aprendices débiles y sean lo bastante diversos.

El clasificador de votación que hemos creado suma las predicciones de cada clasificador y predice la clase que obtiene más votos. Esto se llama hard voting.

Si todos los clasificadores del ensamble pueden estimar probabilidades de clase (tienen el método predict_proba()), podemos indicar a Sklearn que prediga la clase con la probabilidad de clase más alta, promediada sobre todos los clasificadores individuales. Esto se denomina soft voting. A menudo, consigue mejores resultados que el hard voting, ya que da más peso a los votos que son más seguros. Lo único que tenemos que hacer es sustituir voting=“hard” por voting=“soft” cuando creamos el clasificador de votación (y asegurarte que todos los clasificadores individuales pueden hacer predicciones de clase).

Bagging y pasting

Hemos visto como crear un clasificador como un conjunto de clasificadores diferentes. Otra forma es crear un clasificador como un conjunto de clasificadores iguales, pero entrenados con datos aleatorios distintos del conjunto de entrenamiento. Cuando se realiza un muestreo con reemplazo, este método se llama bagging. Cuando es sin reemplazo se denomina pasting.

Para ver como funciona, vamos a usar la clase BaggingClassifier de Sklearn. A la hora de crear el clasificador, definiremos los siguientes hiperparámetros:

  • base_estimator: El clasificador individual que vamos a utilizar.
  • n_estimators: Número de clasificadores individuales que vamos a crear.
  • max_samples: Número de instancias de entrenamiento con las que vamos a entrenar cada clasificador (hay que tener en cuenta que cada conjunto de instancias será diferente en cada clasificador individual). Puede configurarse también como un float que va de 0.0 a 1.0, en cuyo caso indica la proporción de instancias de entrenamiento a muestrear.
  • bootstrap: Indica si vamos a usar reemplazo (bagging) a la hora de seleccionar el conjunto de instancias o no (pasting).
  • n_jobs: Número de núcleos de CPU que debe utilizar para el entrenamiento y las predicciones (-1 indica que utilice todos los núcleos disponibles)

from sklearn.ensemble import BaggingClassifier

bag_clf = BaggingClassifier(
    base_estimator=DecisionTreeClassifier(), n_estimators=500,
    max_samples=100, bootstrap=True, n_jobs=-1
)

bag_clf.fit(X_train, y_train)

En este caso, hemos creado un ensamble de 500 clasificadores de árboles de decisión. Cada uno se entrena con 100 instancias de entrenamiento, seleccionadas aleatoriamente del conjunto de entrenamiento con reemplazo. Si quisiéramos usar pasting en lugar de bagging, sólo deberíamos modificar el hiperparámetro bootstrap = False:

from sklearn.ensemble import BaggingClassifier

bag_clf = BaggingClassifier(
    base_estimator=DecisionTreeClassifier(), n_estimators=500,
    max_samples=100, bootstrap=False, n_jobs=-1
)

bag_clf.fit(X_train, y_train)

Evaluación fuera de la bolsa

Si usamos la técnica de bagging, al haber reemplazo en la selección de instancias habrá algunas que no se seleccionen nunca (aproximadamente un 37%). Esas instancias se denominan fuera de la bolsa (Out Of Bag), y podemos utilizarlas para la validación de nuestro modelo sin necesidad de crear un conjunto de validación aparte. Para ello, debemos establecer oob_score = True a la hora de crear el clasificador bagging (obviamente, esto no lo podemos hacer si usamos un clasificador pasting, ya que no hay reemplazo en la selección de instancias):

bag_clf_3 = BaggingClassifier(
    base_estimator=DecisionTreeClassifier(), n_estimators=500,
    max_samples=100, bootstrap=True, n_jobs=-1, oob_score=True
)

bag_clf_3.fit(X_train, y_train)

Ahora podemos ver la estimación del modelo con mediante la variable oob_score_:

bag_clf_3.oob_score_

0.9597989949748744

La clase BaggingClassifier soporta también el submuestreo de características. Es decir, se puede entrenar cada clasificador individual con características diferentes. El muestreo está controlado por dos hiperparámetros: max_features y bootstrap_features, que funcionan igual que max_samples y bootstrap, pero para el muestreo de características. Esta técnica es especialmente útil cuando tenemos entradas de alta dimensión (por ejemplo, imágenes).

Muestrear tanto características como instancias se denomina parches aleatorios. Si muestreamos sólo características y mantenemos todas las instancias de entrenamiento (con bootstrap=False y max_samples=1.0) se llama método de subespacios aleatorios.

Random forest

Un random forest es un ensamble formado por un conjunto de árboles de decisión, entrenado, por lo general, mediante bagging, habitualmente con max_samples igual al tamaño del conjunto de entrenamiento. Además, introduce una aleatoriedad extra cuando hace crecer los árboles: en lugar de buscar la mejor característica cuando divide un nodo, busca la mejor característica de un subconjunto aleatorio.

Se puede utilizar un BaggingClassifier con los parámetros anteriores para crear un random forest. Para hacer que la elección de las características sea sobre un conjunto aleatorio, podemos usar el hiperparámetro splitter = “random”:

bag_clf = BaggingClassifier(
    DecisionTreeClassifier(splitter="random", max_leaf_nodes=16),
    n_estimators=500, max_samples=1.0, bootstrap=True, n_jobs=-1
)

Aunque el modelo anterior funcionaría de forma similar a un random forest, podemos usar la clase RandomForestClassifier, que es más conveniente y está optimizada para los árboles de decisión:

rnd_clf = RandomForestClassifier(n_estimators = 500, max_leaf_nodes=16, n_jobs=-1)
rnd_clf.fit(X_train, y_train)

y_pred_rf = rnd_clf.predict(X_test)

print(rnd_clf.__class__.__name__, float("{0:.4f}".format(accuracy_score(y_test, y_pred_rf))))

RandomForestClassifier 0.9532

También existe la clase RandomForestRegressor para tareas de regresión

Random forest es uno de los algoritmos de machine learning más potentes en la actualidad.

Extra-Trees

Como hemos visto, los random forest utilizan un subconjunto aleatorio de características a la hora de seleccionar las características para dividir un nodo. Podemos hacer que los árboles de decisión sean todavía más aleatorios al utilizar también umbrales aleatorios para cada característica, en vez de buscar los mejores umbrales posibles.

Este tipo de random forest se llaman ensamble de bosques extremadamente aleatorios (Extra-Tress, Extremely randomized trees).

Los Extra-trees se entrenan con mucha más rapidez que los random forest corrientes, ya que la tarea de encontrar el mejor umbral posible para cada característica es una de las tareas más costosas.

Podemos utilizar la clase ExtraTreesClassifier de sklearn para crear un clasificador de este tipo:

ext_clf = ExtraTreesClassifier(n_estimators = 500, max_leaf_nodes=16, n_jobs=-1)
ext_clf.fit(X_train, y_train)

No hay forma de saber, a priori, cuál de los dos modelos tendrá un rendimiento mejor (random forest o Extra-trees). Por lo general, la única forma de saberlo es probar los dos y comprobarlos mediante validación cruzada.

Importancia de las características

Una cualidad bastante útil de los random forest es que hacer muy fácil medir la importancia relativa de cada característica. Sklearn calcula esta puntuación (escalando los resultados de manera que la suma sea igual a 1) de forma automática después del entrenamiento. Para ver los resultados, usamos la variable feature_importances_:

iris = load_iris()
rnd_clf = RandomForestClassifier(n_estimators=500, random_state=42)
rnd_clf.fit(iris["data"], iris["target"])
for name, score in zip(iris["feature_names"], rnd_clf.feature_importances_):
    print(name, score)

sepal length (cm) 0.11249225099876375
sepal width (cm) 0.02311928828251033
petal length (cm) 0.4410304643639577
petal width (cm) 0.4233579963547682

Boosting

Los métodos de ensamble que hemos visto hasta ahora entrenan los modelos individuales de forma independiente. Existe otra técnica donde estos modelos están ensamblados de manera secuencial, de forma que cada uno de ellos intenta corregir a su predecesor. Ésto se conoce como Boosting.

Existen muchos métodos de boosting, entre los cuales, los más populares son AdaBoost y Gradient Boosting.

AdaBoost

En este método, cada predictor intenta corregir a su predecesor prestando más atención a las instancias de entrenamiento que el predecesor ha subajustado.

Por ejemplo, el algoritmo entrena primero un clasificador base y hace predicciones con el conjunto de entrenamiento. El algoritmo, después, incrementa el peso relativo de las instancias mal clasificadas y entrena un segundo predictor utilizando esos pesos actualizados, y así sucesivamente.

Gradient Boosting

En este caso, los predictores se entrenan con los errores residuales cometidos por el predictor anterior.

clase/ia/saa/2eval/clasificacion_modelos_5.txt · Última modificación: 2023/05/04 08:29 por cesguiro