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.
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).
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:
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)
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
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
Random forest es uno de los algoritmos de machine learning más potentes en la actualidad.
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)
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
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.
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.
En este caso, los predictores se entrenan con los errores residuales cometidos por el predictor anterior.