Herramientas de usuario

Herramientas del sitio


clase:ia:saa:2eval:sml_clasificacion_2

11 - SML Clasificación: Tipos de clasificadores

En el punto anterior, vimos un clasificador básico que distinguía entre dos etiquetas. En este punto, vamos a ver otros tipos posibles de clasificadores (multiclase y multietiqueta).

Clasificadores multiclase

Vamos a seguir con el conjunto de datos mnist, pero en este caso, nuestro clasificador será capaz de clasificar dígitos del 0 al 9. Para eso, cargamos los datos y separamos train y test como siempre:

import pandas as pd
import numpy as np

from sklearn.linear_model import LogisticRegression
from sklearn.linear_model import SGDClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split

import matplotlib.pyplot as plt

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

df = pd.read_csv('./mnist.csv', header = None)
df.rename(columns={0: 'target'}, inplace=True)

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

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

Existen clasificadores que son capaces de clasificar varias clases de forma nativa, por ejemplo SGD y RandomForest. Vamos a ver primero SGD. Como siempre, definimos el modelo y lo entrenamos:

sgd_clf = SGDClassifier(random_state = 42)

sgd_clf.fit(X_train, y_train)

Guardamos el primer número para hacer la predicción (acuérdate que el primer número corresponde a un 5):

n = 0
digit_n = X.loc[n, :]

sgd_clf.predict([digit_n])

array([5])

En este caso, nuestro clasificador ha acertado la clase (número 5). Puedes modificar la n para comprobar si es capaz de clasificar otros dígitos. Si miramos lo que devuelve la función decision_function(), vemos que nos devuelve 10 puntuaciones (una por cada clase de la etiqueta):

sgd_clf.decision_function([digit_n])

array([[-29971.9150807 , -33957.19380802, -17610.9497977 ,
         -1713.07889876, -47927.43234059,   2399.67452617,
        -44143.47187723, -18694.63277894, -10228.82799588,
        -14859.26628549]])

Vamos a hacer lo mismo con RandomForest:

rf_clf  = RandomForestClassifier(random_state=42)

rf_clf.fit(X_train, y_train)

rf_clf.predict([digit_n])

array([5])

En este caso, vemos que la función predict_proba() devuelve 10 probabilidades en lugar de puntuaciones (de nuevo, una por cada clase de nuestra etiqueta):

rf_clf.predict_proba([digit_n])

array([[0.01, 0.  , 0.  , 0.12, 0.  , 0.82, 0.  , 0.01, 0.01, 0.03]])

¿Qué pasa con los clasificadores que no son capaces de clasificar entre más de dos clases de forma nativa como SVM o regresión logística? Para solucionarlo, tenemos dos posibles estrategias:

  • Entrenar 10 clasificadores binarios (uno para cada dígito) y seleccionar la clase cuyo clasificador genere la puntuación (o probabilidad) más alta. Esta estrategia se conoce como OvR (one-versus-the-rest)
  • Entrenar un clasificador binario para cada par de dígitos (0-1, 0-2, 1-2…). Esta estrategia es la llamada OvO (one-versus-one). El número de clasificadores necesarios es: $\frac{N * (N-1)}{2}$

La principal ventaja de OvO es que cada clasificador solo necesita entrenarse en la parte del conjunto de entrenamiento para las dos clases que debe distinguir.

Aunque, en general, para la mayoría de clasificadores binarios se prefiere OvR, algunos algoritmos (SVM) escalan mal con el tamaño del conjunto de entrenamiento. En ese caso, es mejor usar OvO.

Sklearn ejecuta de manera automática OvR u OvO dependiendo del algoritmo usado.

Vamos a usar SVM para clasificar nuestros dígitos:

svm_clf = SVC()

svm_clf.fit(X_train, y_train)

svm_clf.predict([digit_n])

array([5])

svm_clf.decision_function([digit_n])

array([[ 2.72999913,  1.7197734 ,  7.24519062,  8.30666798, -0.30958744,
         9.31225376,  0.71135381,  3.77595107,  6.24183919,  4.8684599 ]])

Como hemos dicho, Sklearn usa por defecto OvO cuando utilizamos SVM como clasificador multiclase. Podemos cambiar la estrategia utilizando la función OneVsRestClassifier (o al contrario, usando OneVsOneClassifier). A estas funciones, les pasamos el clasificador que queremos usar utilizando esa estrategia:

from sklearn.multiclass import OneVsRestClassifier

svm_ovr_clf = OneVsRestClassifier(SVC())

svm_ovr_clf.fit(X_train, y_train)

svm_ovr_clf.predict([digit_n])

array([5])

svm_ovr_clf.decision_function([digit_n])

array([[-2.11492902, -2.65730473, -1.48726137, -0.89943177, -3.62018156,
         1.11959236, -2.9115367 , -1.85285278, -2.47992001, -3.0623506 ]])

Clasificadores multietiqueta

En este caso, queremos que nuestros clasificadores sean capaces de clasificar datos en función de varias etiquetas. Por ejemplo, vamos a crear un clasificador que nos diga si un dígito es un número grande (mayor que 7) y si es impar.

Lo primero será crear nuestro target multietiqueta:

y_train_large = (y_train >= 7)
y_train_odd = (y_train % 2 == 1)
# np.c_ concatena dos arrays en formato columnas
y_multilabel = np.c_[y_train_large, y_train_odd]

Si miramos el target de entrenamiento, veremos que ahora tenemos dos columnas en lugar de una:

y_multilabel

array([[False, False],
       [ True, False],
       [ True,  True],
       ...,
       [False, False],
       [False,  True],
       [False,  True]])

Vamos a usar un clasificador KNN.

knn_clf = KNeighborsClassifier()

knn_clf.fit(X_train, y_multilabel)

Si hacemos una predicción sobre un número cualquiera, vemos que ahora nos da dos resultados (si es grande y si es impar):

knn_clf.predict([digit_n])

array([[False,  True]])

Para evaluar este tipo de clasificadores podemos usar varias técnicas. Por ejemplo, calcular $F_{1}$ para cada etiqueta individual (o cualquier otra métrica) y calcular la puntuación media.

y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_multilabel, cv = 3)

f1_score(y_multilabel, y_train_knn_pred, average = "macro")

0.9739045810268242

En el caso de un clasificador multietiqueta, f1_score() calcula la métrica para cada etiqueta y la puntuación final en función del parámetro average (si queremos que todas las métricas tengan el mismo peso ponemos el valor macro).

Si quisiéramos, por ejemplo, dar más peso a las etiquetas con más instancias positivas, podemos poner el valor weighted en el parámetro average:

f1_score(y_multilabel, y_train_knn_pred, average = "weighted")

0.9756131054304887

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