junio 10, 2022
~ 14 MIN
ML - Ensemble Learning
< Blog RSSML - Ensemble Learning
La técnica de aprendizaje ensamblado, o Ensemble Learning en inglés, consiste en combinar diferentes modelos de ML para obtener mejores predicciones que las que nos proporcionarían los diferentes modelos por separado. Existen multitud de técnicas de ensamblado, y en este post veremos las siguientes:
- Clasificadores por votación
- Bagging y Pasting
- Random Forest
- Bossting
- Stacking
Clasificadores por votación
Esta es una de las técnicas de ensamblado más sencillas, y como su propio nombre indica simplemente agregaremos las predicciones de diferentes clasificadores quedándonos con la clase más votada.
En Scikit-Learn puedes usar el objeto VotingClassifier, que usamos en el siguiente ejemplo en el que ensamblamos un modelos de regresión logística, un SVM y un árbol de decisión (todos explicados en posts anteriores).
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=500, noise=0.30, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
log_clf = LogisticRegression(solver="lbfgs", random_state=42)
svm_clf = SVC(gamma="scale", random_state=42)
tree_cf = DecisionTreeClassifier(max_depth=2, random_state=42)
voting_clf = VotingClassifier(
estimators=[('lr', log_clf), ('svc', svm_clf), ('rf', tree_cf)],
voting='hard')
voting_clf.fit(X_train, y_train)
VotingClassifier(estimators=[('lr', LogisticRegression(random_state=42)),
('svc', SVC(random_state=42)),
('rf',
DecisionTreeClassifier(max_depth=2,
random_state=42))])
Cada modelos por separado es capaz de dar su predicción, el modelo ensamblado asignará, para cada instancia de test, aquella clase con mayor número de votos. De esta manera, el modelo resultante tiene mejor prestaciones que ninguno de los modelos por sí mismos ya que los errores que un modelo pueda cometer, los otros lo pueden aliviar. Es por esta motivo que cuanto mayor diversidad de modelos tengamos en el ensamblado (mayor diversidad en los errores), mejores prestaciones tendrá el ensamblado final.
from sklearn.metrics import accuracy_score
for clf in (log_clf, tree_cf, 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))
LogisticRegression 0.864
DecisionTreeClassifier 0.896
SVC 0.896
VotingClassifier 0.904
Este método de clasificación por votación se conoce como hard voting, escoger la clase más votada. Sin embargo, promediar las probabilidades que obtenemos de los diferentes modelos y escoger aquella clase con mayor probabilidad suele dar mejores resultados. Este proceso se conoce como soft voting.
log_clf = LogisticRegression(solver="lbfgs", random_state=42)
svm_clf = SVC(gamma="scale", random_state=42, probability=True)
tree_cf = DecisionTreeClassifier(max_depth=2, random_state=42)
voting_clf = VotingClassifier(
estimators=[('lr', log_clf), ('svc', svm_clf), ('rf', tree_cf)],
voting='soft')
from sklearn.metrics import accuracy_score
for clf in (log_clf, tree_cf, 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))
LogisticRegression 0.864
DecisionTreeClassifier 0.896
SVC 0.896
VotingClassifier 0.904
Bagging y Pasting
Otra alternativa a la hora de aplicar aprendizaje ensamblado consiste en usar el mismo modelo, pero entrenado con diferentes subsets del dataset de entrenamiento (como ya hicimos en el post anterior en una primera introducción a los Random Forest). Si cada modelo es entrenado con un subset único, la técnica se llama pasting. En caso contrario, se llama bagging (abreviación de bootstrap aggregating), lo cual es más común y la técnica usada en los Random Forest (útil sobretodo en datasets pequeños).
Una vez los modelos han sido entrenados, se pueden sacar predicciones agregando las predicciones de todos los modelos mediante un mecanismo de votación como el que hemos visto en la sección anterior (o promediando los valores en una tarea de regresión).
Veamos un ejemplo usando el objeto BaggingClassifier de Scikit-Learn para entrenar un ensamblado de árboles de decisión con esta técnica.
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, oob_score=True)
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))
0.904
from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt
import numpy as np
def plot_decision_boundary(clf, X, y, axes=[-1.5, 2.45, -1, 1.5], alpha=0.5, contour=True):
x1s = np.linspace(axes[0], axes[1], 100)
x2s = np.linspace(axes[2], axes[3], 100)
x1, x2 = np.meshgrid(x1s, x2s)
X_new = np.c_[x1.ravel(), x2.ravel()]
y_pred = clf.predict(X_new).reshape(x1.shape)
custom_cmap = ListedColormap(['#fafab0','#9898ff','#a0faa0'])
plt.contourf(x1, x2, y_pred, alpha=0.3, cmap=custom_cmap)
if contour:
custom_cmap2 = ListedColormap(['#7d7d58','#4c4c7f','#507d50'])
plt.contour(x1, x2, y_pred, cmap=custom_cmap2, alpha=0.8)
plt.plot(X[:, 0][y==0], X[:, 1][y==0], "yo", alpha=alpha)
plt.plot(X[:, 0][y==1], X[:, 1][y==1], "bs", alpha=alpha)
plt.axis(axes)
plt.xlabel(r"$x_1$", fontsize=18)
plt.ylabel(r"$x_2$", fontsize=18, rotation=0)
tree_clf = DecisionTreeClassifier(random_state=42)
tree_clf.fit(X_train, y_train)
y_pred_tree = tree_clf.predict(X_test)
print(accuracy_score(y_test, y_pred_tree))
0.856
fix, axes = plt.subplots(ncols=2, figsize=(10,4), sharey=True)
plt.sca(axes[0])
plot_decision_boundary(tree_clf, X, y)
plt.title("Decision Tree", fontsize=14)
plt.sca(axes[1])
plot_decision_boundary(bag_clf, X, y)
plt.title("Decision Trees with Bagging", fontsize=14)
plt.ylabel("")
plt.show()
Random Forest
Como ya vimos en el post anterior, un Random Forest no es más que un ensamblado de árboles de decisión entrenados usando la técnica de bagging. En Scikit-Learn, puedes usar el modelo RandomForestClassifier directamente.
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)
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)
np.sum(y_pred == y_pred_rf) / len(y_pred) # almost identical predictions
0.976
Una de las cualidades más interesantes de los Random Forest es la capacidad de medir la importancia relativa de cada característica, midiendo la cantidad en la que los nodos de los árboles reducen, en promedio, la impureza (concept detallado en el post anterior). Ésto hace que los Random Forest sean muy útiles para la tarea de feature engineering o aplicaciones en las que la explicabilidad del modelo sea de importancia vital.
from sklearn.datasets import load_iris
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
Vamos a ver un ejemplo más ilustrativo usando el dataset MNIST. ¿Podemos saber cuáles son los píxeles más importantes a la hora de clasificar un dígito en una imagen?
from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1)
mnist.target = mnist.target.astype(np.uint8)
rnd_clf = RandomForestClassifier(n_estimators=100, random_state=42)
rnd_clf.fit(mnist["data"], mnist["target"])
RandomForestClassifier(random_state=42)
import matplotlib as mpl
def plot_digit(data):
image = data.reshape(28, 28)
plt.imshow(image, cmap = mpl.cm.hot,
interpolation="nearest")
plt.axis("off")
plot_digit(rnd_clf.feature_importances_)
cbar = plt.colorbar(ticks=[rnd_clf.feature_importances_.min(), rnd_clf.feature_importances_.max()])
cbar.ax.set_yticklabels(['Not important', 'Very important'])
plt.show()
Boosting
La técnica de Boosting consiste en entrenar modelos de manera secuencial, cada uno intentando corregir los errores del modelos predecesor. Las técnicas de boosting más populares son AdaBoost y Gradient Boosting.
AdaBoost
En la técnica de AdaBoost cada modelo intenta corregir a su predecesor prestando más atención a las muestras de entrenamiento a las cuales el modelo anterior hizo underfitting. Empieza entrenando un clasificador básico, el cual es utilizado para hacer predicciones en los datos de entrenamiento. Entonces, el algoritmo aumenta la importancia relativa de aquellas muestras que han sido clasificadas erróneamente. Después, se entrena un segundo clasificador utilizando estos nuevos pesos y así sucesivamente. Una vez todos los modelos han sido entrenados, se hace una predicción en ensamblado de votación con la diferencia de que cada predicción lleva asociada un peso que depende de su precisión promedia en el dataset modificado. En Scikit-Learn podemos usar el objeto AdaBoostClassifier.
from sklearn.ensemble import AdaBoostClassifier
ada_clf = AdaBoostClassifier(
DecisionTreeClassifier(max_depth=1), n_estimators=200,
algorithm="SAMME.R", learning_rate=0.5, random_state=42)
ada_clf.fit(X_train, y_train)
AdaBoostClassifier(base_estimator=DecisionTreeClassifier(max_depth=1),
learning_rate=0.5, n_estimators=200, random_state=42)
plot_decision_boundary(ada_clf, X, y)
El siguiente código ilustra una simplificación del funcionamiento del algoritmo, entrenando un SVC de manera consecutiva ajustando los pesos relativos de aquellas muestras clasificadas erróneamente.
m = len(X_train)
fix, axes = plt.subplots(ncols=2, figsize=(10,4), sharey=True)
for subplot, learning_rate in ((0, 1), (1, 0.5)):
sample_weights = np.ones(m)
plt.sca(axes[subplot])
for i in range(5):
svm_clf = SVC(kernel="rbf", C=0.05, gamma="scale", random_state=42)
svm_clf.fit(X_train, y_train, sample_weight=sample_weights)
y_pred = svm_clf.predict(X_train)
sample_weights[y_pred != y_train] *= (1 + learning_rate)
plot_decision_boundary(svm_clf, X, y, alpha=0.2)
plt.title("learning_rate = {}".format(learning_rate), fontsize=16)
if subplot == 0:
plt.text(-0.7, -0.65, "1", fontsize=14)
plt.text(-0.6, -0.10, "2", fontsize=14)
plt.text(-0.5, 0.10, "3", fontsize=14)
plt.text(-0.4, 0.55, "4", fontsize=14)
plt.text(-0.3, 0.90, "5", fontsize=14)
else:
plt.ylabel("")
plt.show()
Gradient Boosting
De la misma manera que AdaBoost, Gradient Boosting consiste en entrenar modelos de manera secuencial, cada uno corrigiendo los errores del anterior. La diferencia es que, en vez de ajustar la importancia de las muestras mal clasificadas, cada modelo se entrena con el error residual del modelo anterior. Para ilustrar este algoritmo, primero entrenamos un árbol de decisión en el siguiente dataset para la tarea de regresión.
X = np.random.rand(100, 1) - 0.5
y = 3*X[:, 0]**2 + 0.05 * np.random.randn(100)
plt.plot(X,y,'.g')
plt.show()
from sklearn.tree import DecisionTreeRegressor
tree_reg1 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg1.fit(X, y)
DecisionTreeRegressor(max_depth=2, random_state=42)
Ahora, podemos calcular el error residual de la siguiente manera.
y2 = y - tree_reg1.predict(X)
plt.plot(X,y2,'.g')
El nuevo modelo será entrenado con este error residual.
tree_reg2 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg2.fit(X, y2)
DecisionTreeRegressor(max_depth=2, random_state=42)
Y así de manera consecutiva.
y3 = y2 - tree_reg2.predict(X)
plt.plot(X,y3,'.g')
[<matplotlib.lines.Line2D at 0x7fbe49c66190>]
tree_reg3 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg3.fit(X, y3)
DecisionTreeRegressor(max_depth=2, random_state=42)
La predicción final será la suma de las predicciones de todos los modelos por separado.
def plot_predictions(regressors, X, y, axes, label=None, style="r-", data_style="b.", data_label=None):
x1 = np.linspace(axes[0], axes[1], 500)
y_pred = sum(regressor.predict(x1.reshape(-1, 1)) for regressor in regressors)
plt.plot(X[:, 0], y, data_style, label=data_label)
plt.plot(x1, y_pred, style, linewidth=2, label=label)
if label or data_label:
plt.legend(loc="upper center", fontsize=16)
plt.axis(axes)
plt.figure(figsize=(11,11))
plt.subplot(321)
plot_predictions([tree_reg1], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h_1(x_1)$", style="g-", data_label="Training set")
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.title("Residuals and tree predictions", fontsize=16)
plt.subplot(322)
plot_predictions([tree_reg1], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h(x_1) = h_1(x_1)$", data_label="Training set")
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.title("Ensemble predictions", fontsize=16)
plt.subplot(323)
plot_predictions([tree_reg2], X, y2, axes=[-0.5, 0.5, -0.5, 0.5], label="$h_2(x_1)$", style="g-", data_style="k+", data_label="Residuals")
plt.ylabel("$y - h_1(x_1)$", fontsize=16)
plt.subplot(324)
plot_predictions([tree_reg1, tree_reg2], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h(x_1) = h_1(x_1) + h_2(x_1)$")
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.subplot(325)
plot_predictions([tree_reg3], X, y3, axes=[-0.5, 0.5, -0.5, 0.5], label="$h_3(x_1)$", style="g-", data_style="k+")
plt.ylabel("$y - h_1(x_1) - h_2(x_1)$", fontsize=16)
plt.xlabel("$x_1$", fontsize=16)
plt.subplot(326)
plot_predictions([tree_reg1, tree_reg2, tree_reg3], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="$h(x_1) = h_1(x_1) + h_2(x_1) + h_3(x_1)$")
plt.xlabel("$x_1$", fontsize=16)
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.show()
En Sickit-Learn podemos usar el objeto GradientBoostingRegressor
from sklearn.ensemble import GradientBoostingRegressor
gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0, random_state=42)
gbrt.fit(X, y)
GradientBoostingRegressor(learning_rate=1.0, max_depth=2, n_estimators=3,
random_state=42)
gbrt_slow = GradientBoostingRegressor(max_depth=2, n_estimators=200, learning_rate=0.1, random_state=42)
gbrt_slow.fit(X, y)
GradientBoostingRegressor(max_depth=2, n_estimators=200, random_state=42)
fix, axes = plt.subplots(ncols=2, figsize=(10,4), sharey=True)
plt.sca(axes[0])
plot_predictions([gbrt], X, y, axes=[-0.5, 0.5, -0.1, 0.8], label="Ensemble predictions")
plt.title("learning_rate={}, n_estimators={}".format(gbrt.learning_rate, gbrt.n_estimators), fontsize=14)
plt.xlabel("$x_1$", fontsize=16)
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.sca(axes[1])
plot_predictions([gbrt_slow], X, y, axes=[-0.5, 0.5, -0.1, 0.8])
plt.title("learning_rate={}, n_estimators={}".format(gbrt_slow.learning_rate, gbrt_slow.n_estimators), fontsize=14)
plt.xlabel("$x_1$", fontsize=16)
plt.show()
Los modelos de Gradient Boosting son muy propensos al overfitting, por lo que usar técnicas de regularización como el early stopping para encontrar el número óptimo de árboles de decision en el modelo es aconsejable.
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
X_train, X_val, y_train, y_val = train_test_split(X, y, random_state=49)
gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=400, random_state=42)
gbrt.fit(X_train, y_train)
errors = [mean_squared_error(y_val, y_pred)
for y_pred in gbrt.staged_predict(X_val)]
bst_n_estimators = np.argmin(errors) + 1
gbrt_best = GradientBoostingRegressor(max_depth=2, n_estimators=bst_n_estimators, random_state=42)
gbrt_best.fit(X_train, y_train)
GradientBoostingRegressor(max_depth=2, n_estimators=235, random_state=42)
min_error = np.min(errors)
plt.figure(figsize=(15,5))
plt.subplot(121)
plt.plot(errors, "b.-")
plt.plot([bst_n_estimators, bst_n_estimators], [0, min_error], "k--")
plt.plot([0, 400], [min_error, min_error], "k--")
plt.plot(bst_n_estimators, min_error, "ko")
plt.text(bst_n_estimators, min_error*1.2, "Minimum", ha="center", fontsize=14)
plt.axis([0, 400, 0, 0.01])
plt.xlabel("Number of trees")
plt.ylabel("Error", fontsize=16)
plt.title("Validation error", fontsize=14)
plt.subplot(122)
plot_predictions([gbrt_best], X, y, axes=[-0.5, 0.5, -0.1, 0.8])
plt.title("Best model (%d trees)" % bst_n_estimators, fontsize=14)
plt.ylabel("$y$", fontsize=16, rotation=0)
plt.xlabel("$x_1$", fontsize=16)
plt.show()
Estos algoritmos son muy potentes y populares, sobretodo en competiciones en Kaggle y plataformas similares. En ellas, una implementación popular de estos algoritmos se encuentra en la librería XGBoost, diseñada y optimizada para este tipo de modelos de ML.
Para instalar la librería, usa el comando
pip install xgboost
.
import xgboost
xgb_reg = xgboost.XGBRegressor(random_state=42)
xgb_reg.fit(X_train, y_train)
y_pred = xgb_reg.predict(X_val)
val_error = mean_squared_error(y_val, y_pred)
print("Validation MSE:", val_error)
Validation MSE: 0.00362960384654974
xgb_reg = xgboost.XGBRegressor(random_state=42, early_stopping_rounds=2)
xgb_reg.fit(X_train, y_train, eval_set=[(X_val, y_val)])
y_pred = xgb_reg.predict(X_val)
val_error = mean_squared_error(y_val, y_pred)
print("Validation MSE:", val_error)
[0] validation_0-rmse:0.25768
[1] validation_0-rmse:0.19282
[2] validation_0-rmse:0.14833
[3] validation_0-rmse:0.11595
[4] validation_0-rmse:0.09498
[5] validation_0-rmse:0.08094
[6] validation_0-rmse:0.07331
[7] validation_0-rmse:0.06765
[8] validation_0-rmse:0.06501
[9] validation_0-rmse:0.06304
[10] validation_0-rmse:0.06245
[11] validation_0-rmse:0.06130
[12] validation_0-rmse:0.06098
[13] validation_0-rmse:0.06082
[14] validation_0-rmse:0.06070
[15] validation_0-rmse:0.06034
[16] validation_0-rmse:0.06037
[17] validation_0-rmse:0.06024
[18] validation_0-rmse:0.06023
[19] validation_0-rmse:0.06005
[20] validation_0-rmse:0.06013
[21] validation_0-rmse:0.06029
Validation MSE: 0.003605974288875822
Stacking
El último método de ensamblado que veremos en este post es el conocido como stacking. Todos los métodos de ensamblado vistos hasta ahora usan algún tipo de mecanismo de votación para generar las predicciones finales. El stacking consiste, sin embargo, en usar un modelo de ML para generar las predicciones finales, usando las predicciones de los modelos individuales como entradas.
Es posible incluso usar múltiples capas de modelos en el stacking, lo cual nos recuerda a las redes neuronales.
Deafortunadamente, Scikit-Learn no soporta esta modalidad de ensamblado, así que te toca a ti implementar esta funcionalidad si quieres probarla 😛. Vamos a ver un ejemplo sencillo usando el dataset MNIST.
from sklearn.datasets import fetch_openml
import numpy as np
mnist = fetch_openml('mnist_784', version=1)
mnist.target = mnist.target.astype(np.uint8)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(mnist.data, mnist.target,
test_size = 10000, random_state = 42, stratify=mnist.target)
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train,
test_size = 10000, random_state = 42, stratify=y_train)
X_train.shape, X_val.shape, X_test.shape
((50000, 784), (10000, 784), (10000, 784))
Primero, entrenaremos varios clasificadores.
Usaremos un ExtraTreeClassifier, una variante del Random Forest en la que los valores usados para dividir los datos en cada nodo son aleatorios en vez de calculados. Esto hace que sean mucho más rápidos de entrenar y menos susceptibles al overfitting.
from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier
from sklearn.svm import LinearSVC
svm_clf = LinearSVC(random_state=42)
rf_clf = RandomForestClassifier(n_estimators=100, random_state=42)
et_clf = ExtraTreesClassifier(n_estimators=100, random_state=42)
estimators = [svm_clf, rf_clf, et_clf]
for estimator in estimators:
estimator.fit(X_train, y_train)
/home/juan/miniconda3/lib/python3.8/site-packages/sklearn/svm/_base.py:1206: ConvergenceWarning: Liblinear failed to converge, increase the number of iterations.
warnings.warn(
for estimator in estimators:
print(estimator.score(X_val, y_val))
0.878
0.9709
0.9731
Vamos ahora a combinar los modelos. En primer lugar, podemos usar un clasificador por votación simple.
from sklearn.ensemble import VotingClassifier
voting_clf = VotingClassifier(
estimators=[('et', et_clf), ('rf', rf_clf), ('svc', svm_clf)],
voting="hard"
)
voting_clf.fit(X_train, y_train)
/home/juan/miniconda3/lib/python3.8/site-packages/sklearn/svm/_base.py:1206: ConvergenceWarning: Liblinear failed to converge, increase the number of iterations.
warnings.warn(
VotingClassifier(estimators=[('et', ExtraTreesClassifier(random_state=42)),
('rf', RandomForestClassifier(random_state=42)),
('svc', LinearSVC(random_state=42))])
voting_clf.score(X_val, y_val)
0.9712
Y, por último, vamos a hacer el stacking. Para ello, deberemos generar predicciones en el dataset de validación para generar el dataset de entrenamiento del modelo que generará la predicción final.
X_val_predictions = np.zeros((len(X_val), len(estimators)))
for i, estimator in enumerate(estimators):
X_val_predictions[:,i] = estimator.predict(X_val)
Usaremos un Random Forest en la última capa del stacking (el blender o mezclador).
rf_blender = RandomForestClassifier(n_estimators=200, oob_score=True, random_state=42)
rf_blender.fit(X_val_predictions, y_val)
RandomForestClassifier(n_estimators=200, oob_score=True, random_state=42)
rf_blender.oob_score_
0.9717
Ahora ya podemos evaluar nuestro modelos ensamblado con los datos de test.
X_test_predictions = np.zeros((len(X_test), len(estimators)))
for i, estimator in enumerate(estimators):
X_test_predictions[:,i] = estimator.predict(X_test)
y_pred = rf_blender.predict(X_test_predictions)
from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_pred)
0.9682
voting_clf.score(X_test, y_test)
0.9669
Resumen
En este post hemos visto varias técnicas de ensamblado para ML, consistentes en combinar varios modelos en vez de usar sólo uno para obtener así mejores resultados. Esta técnica es muy usada en competiciones ya que permite obtener unas prestaciones extra gratis simplemente combinando modelos. De entre las diferentes técnicas de ensamblado hemos introducido los clasificadores por votación, que combinan las predicciones de varios modelos quedándose con aquella más común, técnicas de bagging y los Random Forest, que consisten en entrenar diferentes modelos con diferentes subsets de los datos de entrenamiento para luego generar una única predicción mediante un mecanismo de votación, técnicas de boosting (como AdaBoost o Gradient Boosting) que se basan en la idea de entrenar modelos de manera consecutiva, cada uno corrigiendo los errores del anterior, y, por último, la técnica del stacking que, a diferencia de los mecanismos de votación, utilizan otros modelos de ML para combinar las predicciones de los modelos del ensamblado.