!
pip install make-spirals
Random Forest
Como hemos comprobado, los árboles de decisión tienden a hacer sobreajuste o overfitting.
Este sobreajuste resulta ser una propiedad general de los árboles de decisión: es muy fácil ir
demasiado profundo en el árbol, y así ajustar los detalles de los datos particulares en lugar de
las propiedades generales de las distribuciones de las que se extraen.
Al mismo tiempo hemos podido comprobar que es fácil generar árboles de decisión diferentes
para un mismo conjunto de datos de entrada. Además, limitando las posibilidades del algoritmo
para añadir nodos al árbol podemos generar modelos que, si bien presentan problemas de
sobreajuste, están focalizados en subconjunto específico del conjunto de datos. Por lo tanto,
parece razonable combinar varios de estos modelos sobreajustados para intentar mitigar el
efecto de sus sobreajustes.
Esta idea de que se pueden combinar múltiples estimadores con overfitting para reducir el
efecto de este sobreajuste es lo que subyace a un método de ensamble llamado "bagging". El
"bagging" hace uso de un conjunto de estimadores paralelos, cada uno de los cuales se ajusta en
exceso (con overfitting) a los datos, y promedia los resultados para encontrar una mejor
clasificación. Un conjunto de árboles de decisión aleatorios se conoce como random forest.
Random Forests 'a mano'
Como ya se ha dicho, un Random Forest no es más que un ensemble bagging de árboles de
decisión aleatorio. Scikit-learn nos proporciona un objeto para instanciar y usar baggings de
algún otro modelo de aprendizaje computacional. En nuestro caso, vamos a usar este método
para definir y entrenar un Random Forest construido a partir de árboles aleatorios de decisión:
## Función auxiliar para pintar las fronteras de decisión de un
clasificador
def visualize_classifier(model, X, y, ax=None, cmap='rainbow'):
plt.figure(figsize=(10, 5))
ax = ax or plt.gca()
# Plot the training points
ax.scatter(X[:, 0], X[:, 1], c=y, s=30, cmap=cmap,
clim=(y.min(), y.max()), zorder=3)
ax.axis('tight')
ax.axis('off')
xlim = ax.get_xlim()
ylim = ax.get_ylim()
# fit the estimator
model.fit(X, y)
xx, yy = np.meshgrid(np.linspace(*xlim, num=200),
np.linspace(*ylim, num=200))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
# Create a color plot with the results
n_classes = len(np.unique(y))
contours = ax.contourf(xx, yy, Z, alpha=0.3,
levels=np.arange(n_classes + 1) - 0.5,
cmap=cmap, clim=(y.min(), y.max()),
zorder=1)
ax.set(xlim=xlim, ylim=ylim)
Primero vamos a generar un dataset sintético con la función make_blobs. Este dataset
constará de 300 observaciones con 4 categorías:
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
X, y = make_blobs(n_samples=300, centers=4, random_state=0,
cluster_std=1.0)
plt.figure()
plt.xlabel('X1')
plt.ylabel('X2')
plt.scatter(X[:, 0], X[:, 1], c=y, cmap='rainbow')
plt.show()
Cuando construimos un Random Forest a partir del BaggingClassifier debemos especificar:
• n_estimators: el número de árboles de que se compondrá nuestro bosque.
• max_samples: el porcentaje de muestras del dataset que se usarán para construir cada
bosque.
Podemos ilustrar gráficamente qué cómo se combinan las árboles comparando las fronteras de
decisión de cada árbol que compone el bosque recuperándolas mediante el atributo
estimators_:
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier
import numpy as np
tree = DecisionTreeClassifier()
bag = BaggingClassifier(tree, n_estimators=8, max_samples=0.3,
random_state=1)
bag.fit(X, y)
fig, axs = plt.subplots(nrows=3, ncols=3, figsize=(15,15))
fig.tight_layout(pad=4.0)
min, max = np.amin(X, axis=0), np.amax(X, axis=0)
diff = max - min
min, max = min - 0.1 * diff, max + 0.1 * diff
for i in range(3):
for j in range(3):
axs[i,j].set_xlabel('X1')
axs[i,j].set_ylabel('X2')
axs[i,j].set_xlim(min[0], max[0])
axs[i,j].set_ylim(min[1], max[1])
xx, yy = np.meshgrid(np.linspace(min[0], max[0], 100),
np.linspace(min[1], max[1], 100))
if i == 0 and j == 0:
axs[i,j].set_title('Random Forest')
Z = bag.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
axs[i,j].contourf(xx, yy, Z, alpha=0.4, cmap='rainbow')
else:
t = 3 * i + j - 1 # tree index
axs[i,j].set_title('Decision Tree #' + str(t+1))
Z = bag.estimators_[t].predict(np.c_[xx.ravel(),
yy.ravel()])
Z = Z.reshape(xx.shape)
axs[i,j].contourf(xx, yy, Z, alpha=0.4, cmap='rainbow')
axs[i,j].scatter(X[:,0], X[:,1], c=y, edgecolor='black', s=20,
cmap='rainbow')
plt.show()
En este ejemplo, hemos aleatorizado los datos ajustando cada modelo con un subconjunto
aleatorio del 30% de los puntos de entrenamiento. En la práctica, los árboles de decisión se
aleatorizan en mayor medida, inyectando cierta aleatoriedad en la forma en que se eligen las
divisiones: de esta manera todos los datos contribuyen al ajuste cada vez, pero los resultados del
ajuste siguen teniendo la aleatoriedad deseada. Por ejemplo, al determinar la característica en la
que se va a dividir, el árbol aleatorio podría seleccionar entre las varias características
principales.
También posible aleatoreizar las características que se van a usar para cada uno de los árboles
mediante el parámetro max_features.
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier
import numpy as np
tree = DecisionTreeClassifier()
bag = BaggingClassifier(tree, n_estimators=8, max_samples=0.3,
max_features=1, random_state=1)
bag.fit(X, y)
fig, axs = plt.subplots(nrows=3, ncols=3, figsize=(15,15))
fig.tight_layout(pad=4.0)
min, max = np.amin(X, axis=0), np.amax(X, axis=0)
diff = max - min
min, max = min - 0.1 * diff, max + 0.1 * diff
for i in range(3):
for j in range(3):
axs[i,j].set_xlabel('X1')
axs[i,j].set_ylabel('X2')
axs[i,j].set_xlim(min[0], max[0])
axs[i,j].set_ylim(min[1], max[1])
x1, x2 = np.meshgrid(np.linspace(min[0], max[0], 100),
np.linspace(min[1], max[1], 100))
grid = np.c_[x1.ravel(), x2.ravel()]
if i == 0 and j == 0:
axs[i,j].set_title('Random Forest')
Z = bag.predict(grid)
Z = Z.reshape(x1.shape)
axs[i,j].contourf(x1, x2, Z, alpha=0.4, cmap='rainbow')
else:
t = 3 * i + j - 1 # tree index
axs[i,j].set_title('Decision Tree #' + str(t+1))
xt = grid[:,bag.estimators_features_[t]]
Z = bag.estimators_[t].predict(xt)
Z = Z.reshape(x1.shape)
axs[i,j].contourf(x1, x2, Z, alpha=0.4, cmap='rainbow')
axs[i,j].scatter(X[:,0], X[:,1], c=y, edgecolor='black', s=20,
cmap='rainbow')
plt.show()
Random Forest con Scikit-Learn
En Scikit-Learn, este conjunto optimizado de árboles de decisión aleatorios se implementa en el
modelo RandomForestClassifier, que se encarga de toda la aleatorización de forma
automática. Al igual que sucedía con el bagging, debemos seleccionar su número de árboles
(n_estimators) así como la profundidad máxima (max_depth), el número de features a usar
(max_features) y el número de muestras (max_samples) de cada árbol.
Veamos su desempeño sobre diferentes conjuntos de datos:
import matplotlib.pyplot as plt
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_blobs, make_moons, make_circles
from make_spirals import make_spirals
def plot_clasification(X, y, axs):
min = np.amin(X, axis=0)
max = np.amax(X, axis=0)
diff = max - min
min = min - 0.1 * diff
max = max + 0.1 * diff
axs[0].set_title('Raw data')
axs[0].set_xlabel('X1')
axs[0].set_ylabel('X2')
axs[0].set_xlim(min[0], max[0])
axs[0].set_ylim(min[1], max[1])
axs[0].scatter(X[:,0], X[:,1], c=y, cmap=plt.cm.bwr)
for i, max_depth in enumerate([1,2,3]):
clf = RandomForestClassifier(n_estimators=100,
max_depth=max_depth, random_state=1)
clf.fit(X, y)
xx, yy = np.meshgrid(np.linspace(min[0], max[0], 100),
np.linspace(min[1], max[1], 100))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
axs[i+1].set_title('max_depth=' + str(max_depth))
axs[i+1].set_xlabel('X1')
axs[i+1].set_ylabel('X2')
axs[i+1].set_xlim(min[0], max[0])
axs[i+1].set_ylim(min[1], max[1])
axs[i+1].contourf(xx, yy, Z, alpha=0.5, cmap=plt.cm.bwr)
axs[i+1].scatter(X[:,0], X[:,1], c=y, edgecolor='black', s=20,
cmap=plt.cm.bwr )
fig, axs = plt.subplots(nrows=4, ncols=4, figsize=(20,20))
fig.tight_layout(pad=4.0)
X, y = make_blobs(n_samples=300, n_features=2, centers=2,
cluster_std=2, random_state=42)
plot_clasification(X, y, axs[0])
X, y = make_moons(n_samples=300, noise=0.1, random_state=42)
plot_clasification(X, y, axs[1])
X, y = make_circles(n_samples=300, noise=0.1, factor=0.5,
random_state=42)
plot_clasification(X, y, axs[2])
X, y = make_spirals(n_samples=1000, random_state=42)
plot_clasification(X, y, axs[3])
Podemos observar que promediando 100 estimadores aleatorios, los cuales probablemente
sufran de overfitting, obtenemos un modelo que mitiga el efecto de dicha sobre-estimación.
Probando el rendimiento de Random Forest
Para probar el rendimiento de este modelo vamos a utilizar un conjunto de datos sobre
imágenes escaneadas de caracteres del alfabeto anglosajón alojado en el repositorio UCI.
Según la descripción del dataset:
el objetivo es identificar imágenes rectangulares en blanco y negro como una de las 26
letras mayúsculas del alfabeto inglés. Las imágenes de los caracteres se basaron en 20
fuentes diferentes y cada letra dentro de estas 20 fuentes fue distorsionada
aleatoriamente para producir un archivo de 20.000 observaciones únicas.
import pandas as pd
features = [
'target', 'x-box', 'y-box', 'width', 'high ', 'onpix', 'x-
bar',
'y-bar', 'x2bar', 'y2bar', 'xybar', 'x2ybr', 'xy2br', 'x-
ege',
'xegvy', 'y-ege', 'yegvx'
]
letras = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-
databases/letter-recognition/letter-recognition.data', names=features)
X = letras.iloc[:,1:]
y = letras.target
Una vez cargados los datos, procedemos a realizar el ajuste y validación del modelo. Para ello
vamos a usar validación cruzada:
from sklearn.model_selection import cross_validate, KFold
cv = KFold(n_splits=5, shuffle=True, random_state=1337)
# Primera aproximación, random forest con parámetros por defecto
rf1 = RandomForestClassifier(n_jobs=-1)
scores = cross_validate(rf1, X, y, cv=cv, scoring=('accuracy',
'precision_weighted', 'recall_weighted', 'f1_weighted'))
scores
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.75)
from sklearn.metrics import plot_confusion_matrix
rf = RandomForestClassifier(n_estimators=50, n_jobs=-1)
rf.fit(X_train, y_train)
plt.rcParams["figure.figsize"] = [20, 20]
plot_confusion_matrix(rf, X_test, y_test, cmap='Blues',
normalize='true')
Creado por Raúl Lara Cabrera ([email protected]) y Fernando Ortega
(
[email protected])