Guia rápida sobre Ensemble Learning
Tags: tutorial, python, machinelearning, datascience
Published: 2020-12-03 16:49:03

En muchas ocasiones, los cientificos de datos nos enfrentamos a la decisión sobre qué modelo usar para una determinada tarea de regresión o clasificación. Pero ante este dilema hay una respuesta sencilla que puede encajar muy bien en la mayoría de los proyectos. 

Esta respuesta es nada mas ni nada menos que el Ensemble Learning (o aprendizaje en conjunto en su traducción al español).

# Introducción a Ensemble Learning

> En el campo del aprendizaje automático, si agrupamos las predicciones de un conjunto de algoritmos (tanto para clasificación como para regresión) vamos a obtener mejores resultados que con el mejor predictor individual.

Por ejemplo:

Entrenamos un grupo de Decision Trees, cada uno con un sub-grupo diferente de datos de entrenamiento. Luego, para realizar predicciones lo que hacemos es obtener los resultados de cada arbol y predecir la clase que obtiene mas votos (en caso de regresión, es el promedio de todas las predicciones).

![Decision Trees](https://dev-to-uploads.s3.amazonaws.com/i/1icv0mhei3lgllenleu0.png)

---

Veamos un ejemplo rapido para ver como funciona este metodo usando Scikit-Learn:

```python
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.ensemble import VotingClassifier
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split

log_clf = LogisticRegression()
rnd_clf = RandomForestClassifier()
svm_clf = SVC()

voting_clf = VotingClassifier(
    estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
    voting='hard')

X, y = make_moons(n_samples=1000, noise=0.3, random_state=42)

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

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))
```
```
LogisticRegression 0.8514285714285714
RandomForestClassifier 0.9142857142857143
SVC 0.9028571428571428
VotingClassifier 0.9057142857142857
```

Lo primero que hacemos es definir los modelos que vamos a agrupar para crear nuestro algoritmo. En este caso, vamos a usar `RandomForestClassifier`, `LogisticRegression` y `SVC` (Support Vector Classifier). Luego, agrupamos los tres modelos usando el metodo `VotingClassifier` y lo entrenamos.

> El Ensemble Learning funciona mejor si los algoritmos a agrupar son los mas independientes posibles unos de otros. De esta manera, van a realizar distintos tipos de errores, mejorando la precisión del conjunto. 

---

## Diferencias entre `hard` y `soft` voting.

Si todos los clasificadores son capaces de estimar las probabilidades de cada clase (es decir, tienen disponible el metodo `predict_proba()`), entonces podemos configurar nuestro algoritmo grupal para que prediga la clase con la mayor probabilidad por clase, promediando los resultados dados por cada algoritmo. Esta tecnica es conocida como *soft voting*. En algunas ocasiones es capaz de obtener mejores resultados que el hard voting porque le da mayor peso a los votos mas probables de ser correctos. Lo unico que hay que hacer es reemplazar `voting="hard"` por `voting="soft"` y asegurarnos que todos los clasificadores estiman las probabilidades por clase. De manera predeterminada `SVC` no tiene esta opcion activada, por lo que debes configurar el hiper-parámetro `probability` como `True` (lo que hará que `SVC` use `cross-validation` para estimar las probabilidades de cada clase, volviendo mas lento el entrenamiento, y añadiendo el metodo `predict_proba()`).

Veamos como queda nuestro modelo en conjunto luego de estas modificaciones:

```python
log_clf = LogisticRegression()
rnd_clf = RandomForestClassifier()
svm_clf = SVC(probability=True)

voting_clf = VotingClassifier(
    estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
    voting='soft')

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))
```
```
LogisticRegression 0.8514285714285714
RandomForestClassifier 0.92
SVC 0.9028571428571428
VotingClassifier 0.9142857142857143
```

Como podemos ver, nuestro modelo obtuvo unos resultados ligeramente mejores con soft voting. 

---

## Bagging y Pasting

Como ya vimos, una manera de tener un diverso grupo de clasificadores es usar diferentes algoritmos en la etapa de entrenamiento. Pero otra forma de obtener esto es usando un solo algoritmo durante todo el proceso pero entrenarlos con distintas porciones del set de datos de entrenamiento. Cuando el muestreo de datos se realiza con reemplazamientos, este metodo se llama *bagging* (abreviatura de *bootstrap aggregating*). Y cuando el muestreo es sin reemplazamiento, es llamado *pasting*.

En otras palabras, tanto el bagging como el pasting permiten muestrear instancias de entrenamiento varias veces en varios predictores, pero solo el bagging permite muestrear instancias de entrenamiento varias veces para el mismo predictor.

---

![GitHub Button](https://dev-to-uploads.s3.amazonaws.com/i/10fuvo5td5ewb3699x09.png)
<figure>
  <figcaption>[Link a GitHub](https://github.com/ruizleandro/Ensemble-Learning-and-Boosting/blob/main/Ensemble_Learning_in_Scikit_Learn.ipynb)</figcaption>
</figure>

---

Una vez que todos los predictores son entrenados, el conjunto puede realizar una predicción para una nueva instancia agregando las predicciones de todos los predictores. La función de agregación es normalmente el modo estadístico (es decir, la predicción más frecuente, como un clasificador hard voting) para clasificación, o el promedio para regresión. Cada predictor individual tiene un sesgo mayor que si hubiera sido entrenado con el set de datos original, pero la agregación reduce el sesgo y la varianza. Generalmente, el resultado final es que el conjunto tiene un sesgo similar pero una varianza menor que un solo predictor entrenado con el set original. 

---

### Bagging y Pasting en Scikit-Learn

Se puede usar `BaggingClassifier` para tareas de clasificación, y `BaggingRegressor` para regresión.

Algunos parámetros para bagging son:

* `n_estimators`: número de predictores en el conjunto.
* `max_samples`: número de muestras del set de entrenamiento para entrenar cada predictor.
* `bootstrap`: `True` para usar bagging, `False` para usar pasting.
* `n_jobs`: número de núcleos del CPU para usar en el entrenamiento (`-1` usa todos los núcleos disponibles).

Acá tenemos un ejemplo de bagging:

```python
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier

bag_clf = BaggingClassifier(
    DecisionTreeClassifier(), n_estimators=1000,
    max_samples=200, bootstrap=True, n_jobs=-1, random_state=0)
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)

print(accuracy_score(y_test, y_pred))
```
```
0.9057142857142857
```

Y por acá un ejemplo de pasting:

```python
pas_clf = BaggingClassifier(
    DecisionTreeClassifier(), n_estimators=1000,
    max_samples=200, bootstrap=False, n_jobs=-1, random_state=0)
pas_clf.fit(X_train, y_train)
y_pred = pas_clf.predict(X_test)

print(accuracy_score(y_test, y_pred))
```
```
0.9085714285714286
```
Y por el simple hecho de comparar, veamos como resulta un solo Decision Tree haciendo todo el trabajo:

```python
tree = DecisionTreeClassifier()
tree.fit(X_train, y_train)
y_pred = tree.predict(X_test)
print(accuracy_score(y_test, y_pred))
```
```
0.8714285714285714
```

Tal como se esperaba, tenemos mejores resultados usando 1000 arboles en lugar de 1.

# Conclusión

Espero que este articulo les haya ayudado tanto como me ayudó a mi mientras lo confeccionaba. Muchas gracias por llegar hasta aquí y ¡hasta la proxima!

## Fin

---

¡Sorpresa para quienes llegaron al final!

---

### Evaluación Out-of-Bag

Cuando usamos bagging, algunas instancias pueden ser seleccionadas varias veces para cualquier predictor, mientras que otros pueden no ser seleccionados en absoluto. Predeterminadamente, `BaggingClassifier` selecciona *m* instancias de entrenamiento con reemplazamiento (`bootstrap=True`), donde *m* es el tamaño del set de entrenamiento. Esto significa que en promedio solo el 63 % de las instancias de entrenamiento son seleccionadas para cada predictor. El 37 % restante (que no son las mismas para todos los predictores) de las instancias que no son muestreadas son llamadas instancias *out-of-bag* (oob).

Ya que un predictor nunca ve las instancias oob durante el entrenamiento, pueden ser utilizadas para evaluar sin la necesidad de un set de validación. Puedes evaluar al conjunto en si al promediar las evaluaciones oob para cada predictor. 

En Scikit-Learn, puedes configurar `oob_score=True` cuando estas creando un `BaggingClassifier` para pedir una evaluación oob luego del entrenamiento. El resultado de la evaluación esta disponible a través de la variable `oob_score`:

```python
bag_clf = BaggingClassifier(
    DecisionTreeClassifier(), n_estimators=500,
    bootstrap=True, n_jobs=-1, oob_score=True)

bag_clf.fit(X_train, y_train)
bag_clf.oob_score_
```
```
0.9061538461538462
```

---

De acuerdo a esta evaluación oob, este `BaggingClassifier` es capaz de alcanzar un 90.6 % de precisión en el test set. Vamos a verificarlo:

```python
y_pred = bag_clf.predict(X_test)
accuracy_score(y_test, y_pred)
```
```
0.9142857142857143
```

¡Obtuvimos un 91.4 % de precision en el set de prueba! Bastante acertado.

Incluso tambien tenemos disponible la función de decisión oob:

```python
bag_clf.oob_decision_function_
```
```
array([[0.        , 1.        ],
       [0.97206704, 0.02793296],
       [1.        , 0.        ],
       ...,
       [0.99408284, 0.00591716],
       [0.88333333, 0.11666667],
       [0.        , 1.        ]])
```

---

Ahora sí es el final, muchas gracias por leerme. ¡Hasta luego!