Laboratorio 3 - Parte 1. GMM para clasificación y clustering#

!wget -nc --no-cache -O init.py -q https://raw.githubusercontent.com/jdariasl/Intro_ML_2025/master/init.py
import init; init.init(force_download=False); init.get_weblink()
from local.lib.rlxmoocapi import submit, session
import inspect
session.LoginSequence(endpoint=init.endpoint, course_id=init.course_id, lab_id="L03.01", varname="student");
#configuración del laboratorio
# Ejecuta esta celda!
from Labs.commons.utils.lab3 import *
_ = part_1()

Ejercicio 1: Contextualización del problema#

A continuación se leen los datos de un problema de clasificación. El problema corresponde a la clasificación de dígitos escritos a mano. Los datos fueron preprocesados para reducir el número de características y solo se van usar los digitos 0 al 4. La técnica usada para realizar la reducción de dimensión es PCA, la cual ya fue usada en el curso de Modelos y Simulación I, y será nuevamente analizada en detalle más adelante en este curso. Al ejecutar la celda de código, también se podrá visualizar una muestra de los datos usados.

digits = load_digits(n_class=5)
#--------- preprocesamiento--------------------
pca = PCA(0.99, whiten=True)
data = pca.fit_transform(digits.data)
#---------- Datos a usar ----------------------
x = data[:-1]
y = digits.target[:-1]

plot_digits(digits.data)
#Echemos un vistazo a la distribución de las clases
from local.lib.util import plot_samples_per_class
plot_samples_per_class(y)

Para poder realizar la validación de nuestro algoritmo, realizaremos una partición de los datos. Como sabemos, existen dos principales alternativas:

  1. KFold

  2. ShuffleSplit

En el siguiente ejercicio, instancie dos objetos de cada una de las clases de objetos listadas antes. En los dos casos se desea repetir el experimento 4 veces. Cuando instancie los objetos, defina los parámetros para que, aunque las técnicas de validación sean diferentes, los resultados sean estadísticamente equivalentes. Ajuste la semilla de los generadores en 0 para que los resultados sean reproducibles.

#ejercicio de código
from sklearn.model_selection import KFold
from sklearn.model_selection import ShuffleSplit

def get_cv_objects():
    cv1 = KFold...
    cv2 = ShuffleSplit...
    return cv1, cv2

Registra tu solución en línea

student.submit_task(namespace=globals(), task_id='T1');

Ejercicio 2: Mezclas de gaussinas#

En la siguiente celda vamos a crear una clase para definir un modelo de clasificación basado en el modelo GMM. Para ello vamos a usar la función GaussianMixture de sklearn. Consultar aquí: http://scikit-learn.org/stable/modules/generated/sklearn.mixture.GaussianMixture.html

Los parámetros que deben ser tenidos en cuenta para en el entrenamiento del modelo, ya están definidos en el constructor de la clase. Con el propósito de seguir la estructura de los modelos definidos en sklearn, deben completar tres métodos:

  • train

  • predict

  • predict_proba

En el notebook, ya se encuentra cargada la libreria que vamos a usar:

from sklearn.mixture import GaussianMixture
#ejercicio de código
def get_GMM_classifier(n_components,covariance_type,max_iter=100,n_init=5,init_params='kmeans',random_state=0):
  """Función para instanciar un modelo de clasificación basado en Mezclas de Gaussianas
  retorna: objeto de la clase GMMClassifierTrain
  """
  class GMMClassifier():
    def __init__(self, n_components=1, covariance_type='full', max_iter=100, n_init=1, init_params='kmeans', random_state=None):
        self.n_components = n_components
        self.covariance_type = covariance_type
        self.max_iter = max_iter
        self.n_init = n_init
        self.init_params = init_params
        self.random_state = random_state
        self.models = {}
        self.class_labels =[]

    def fit(self,X,Y):
      """
      retorna: almacena el diccionario de modelos entrenados y las etiquetas de
      las clases. Las etiquetas de las clases se usan como keys en el diccionario
      """
      ...
      return self

    def predict(self,X):
      """
      retorna: matriz de predicciones de tamaño (n_muestras,)
      """

      self.models = ...
      self.class_labels = ...
      return self

    def predict(self,X):
      """
      retorna: matriz de predicciones de tamaño (n_muestras,)
      """
      ...
      return Yest

    def predict_proba(self,X):
      """
      retorna: matriz de probabilidades de tamaño (n_muestras, n_clases)
      """
      ...
      return Yproba

  return GMMClassifier(
      n_components = n_components,
      covariance_type = covariance_type,
      max_iter = max_iter,
      n_init = n_init,
      init_params=init_params,
      random_state=random_state
      )

Registra tu solución en línea

student.submit_task(namespace=globals(), task_id='T2');
#@title Pregunta Abierta
#@markdown En sus palabras, ¿por qué debemos entrenar un modelo por cada clase?
respuesta = "" #@param {type:"string"}

Ejercicio 3: Experimentos#

Con el código completado, vamos a realizar experimentos. Complete la siguiente función para poder obtener los resultados de los experimentos. Tenga en cuenta lo siguiente:

  1. Retornar errores de entrenamiento y pruebas

  2. Retornar intervalos de confianza (desviacion estandar) para cada una de las configuraciones de hiperparámetros

  3. En el código ya se sugiere la metodologia de validación (usando 4 folds) y se incluye, como es usual, el standard scaler para normalizar los datos.

#ejercicio de código
def experimentar(X, Y, covariance_types,num_components):
    """función que realiza experimentos con el GMM

    X: matriz con las muestras de entrada
    Y: matriz de numpy con las etiquetas de salida
    covariance_types: list[str] con las matrices de covarianza a probar
    num_components: list[int] con el numero de componente a probar

    retorna: dataframe con:
        - matriz de covarianza
        - numero de componentes
        - eficiencia de entrenamiento
        - desviacion de estandar eficiencia de entrenamiento
        - eficiencia de prueba
        - desviacion estandar eficiencia de prueba
    """
    # Definir metodología de validación
    skf, _ = get_cv_objects()
    resultados = pd.DataFrame()
    idx = 0

    for cov_tipo in covariance_types:
        for M in num_components:
            ## para almacenar los errores intermedios
            EficienciaTrain = []
            EficienciaVal = []
            for train, test in skf.split(X, Y):
                Xtrain = X[train,:]
                Ytrain = Y[train]
                Xtest = X[test,:]
                Ytest = Y[test]
                #Normalizamos los datos
                scaler = StandardScaler()
                scaler.fit(Xtrain)
                Xtrain= scaler.transform(Xtrain)
                Xtest = scaler.transform(Xtest)
                #Haga el llamado a la función para crear y entrenar el modelo usando los datos de entrenamiento
                gmms = ...
                #Validación
                Ytrain_pred = ...
                Yest = ...
                #Evaluamos las predicciones del modelo con los datos de test
                EficienciaTrain.append(np.mean(Ytrain_pred.ravel() == Ytrain.ravel()))
                EficienciaVal.append(np.mean(Yest.ravel() == Ytest.ravel()))

            resultados.loc[idx,'matriz de covarianza'] = cov_tipo
            resultados.loc[idx,'numero de componentes'] = M
            resultados.loc[idx,'eficiencia de entrenamiento'] =
            resultados.loc[idx,'desviacion estandar entrenamiento'] =
            resultados.loc[idx,'eficiencia de prueba'] =
            resultados.loc[idx,'desviacion estandar prueba'] =
            idx= idx +1
            print("termina para", cov_tipo, M)

    return (resultados)

Registra tu solución en línea

student.submit_task(namespace=globals(), task_id='T3');

Ahora vamos a ejecutar nuestros experimentos:

matrices = ['full', 'tied', 'diag', 'spherical']
componentes = [1,2,3]
resultados = experimentar(x, y, matrices, componentes)
# para ver la tabla
resultados
#@title Pregunta Abierta
#@markdown ¿A qué corresponde el parámetro init_params de la función GaussianMixture y cuál fue el valor usado en nuestros experimentos?
respuesta = "" #@param {type:"string"}

Ejercicio 4 Kmeans y experimentos#

Como sabemos, el modelo GMM puede usarse también para resolver problemas no supervisados. Aunque el dataset que estamos usando contiene etiquetas, vamos a usarlo como si el problema fuera de tipo no supervisado. Usaremos el modelo GMM para resolver el problema y lo compararemos con el algoritmo K-means.

Consultar todo lo relacionado al llamado del método KMeans de la librería scikit-learn en el siguiente enlace: http://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html.

En el notebook, ya se encuentra cargada la libreria:

from sklearn.cluster import KMeans

Por ello vamos a implementar una función que defina y entre los modelos de clustering KMeans y GMM. Los hiparámetros que se deben usar para instanciar los modelos ya están definidos en la función.

#ejercicio de código
def entrenar_clustering(numero_clusters,Xtrain,max_iter=100,n_init=5,random_state=0):

  """función que realiza experimentos no supervisados: GMM y Kmeans
  numero_clusters: int número de clusters a usar en los modelos
  Xtrain: matriz con las muestras de entrenamiento

  """
  #Define el modelo de clustering basado en kmeans con el conjunto de hiperparámetros apropiado
  kmeans =
  #¿qué metodo debes llamar para entrenar kmeans?
  kmeans

  # Ahora usa el modelo GMM para hacer clustering (Use matriz de covarianza 'tied')
  gmm = GaussianMixture(...)
  #Entrena el gmm
  gmm
  return  kmeans, gmm

Como sabemos, la evaluación de los algoritmos de agrupamiento no supervisados se hace a partir de medidas que evalúan la cohesión de los grupos encontrados, como el coeficiente Silhouette. Creemos una función que realice la experimentación para diferente número de grupos de acuerdo con dicha medida.

#ejercicio de código
from sklearn.metrics import silhouette_score
def experimentar_clustering(numero_clusters,X):
    """función que realiza experimentos no supervisados: GMM y Kmeans
    numero_clusters: list[int] numero cluster para realizar experimentos
    X: matriz con las caractersiticas
    Y: matriz de numpy con etiquetas
    retorna: dataframe con:
        - numero_clusters
        - el error de entrenamiento
        - desviacion de estandar del error entrenamiento
        - error de prueba
        - desviacion estandar eror de prueba
    """
    _, skf = get_cv_objects()
    resultados = pd.DataFrame()
    idx = 0

    for n_cluster in numero_clusters:
        ## para almacenar los errores intermedios
        DesempenoTrain_kmeans = []
        DesempenoVal_kmeans = []
        DesempenoTrain_gmm = []
        DesempenoVal_gmm = []
        EficienciaVal = []
        for train, test in skf.split(X, X):
            Xtrain = X[train,:]
            Xtest = X[test,:]
            #Normalizamos los datos
            scaler = StandardScaler()
            scaler.fit(Xtrain)
            Xtrain= scaler.transform(Xtrain)
            Xtest = scaler.transform(Xtest)

            kmeans, gmm = ...

            #¿Cómo determinar los grupos a los que fueron asignadas las muestras de entrenamiento?
            Gtrain_pred_k = kmeans...
            Gtrain_pred_g = gmm...
            #¿Cómo determinar los grupos a los que fueron asignadas las muestras de prueba?
            Gest_k = kmeans...
            Gest_g = gmm...

            #Evaluamos las predicciones del modelo con los datos de test
            DesempenoTrain_kmeans.append(silhouette_score(Xtrain, Gtrain_pred_k))
            DesempenoVal_kmeans.append(silhouette_score(Xtest, Gest_k))
            DesempenoTrain_gmm.append(silhouette_score(Xtrain, Gtrain_pred_g))
            DesempenoVal_gmm.append(silhouette_score(Xtest, Gest_g))


        resultados.loc[idx,'numero de clusters'] = n_cluster
        resultados.loc[idx,'Silhouette de entrenamiento kmeans'] = np.mean(np.stack(DesempenoTrain_kmeans))
        resultados.loc[idx,'Silhouette de test kmeans'] = np.mean(np.stack(DesempenoVal_kmeans))
        resultados.loc[idx,'Silhouette de entrenamiento gmm'] = np.mean(np.stack(DesempenoTrain_gmm))
        resultados.loc[idx,'Silhouette de test gmm'] = np.mean(np.stack(DesempenoVal_gmm))
        idx= idx +1
        print("termina para", n_cluster)

    return (resultados)

Registra tu solución en línea

student.submit_task(namespace=globals(), task_id='T4');
# ejecuta los experimentos y ve los resultados
resultados_clustering = experimentar_clustering([3,5,6,8],x)
resultados_clustering
#@title Pregunta Abierta
#@markdown ¿Qué método podría usar para determinar el grado de pertenencia de una muestra a cada grupo según el modelo GMM ?
respuesta = "" #@param {type:"string"}
#@title Pregunta Abierta
#@markdown ¿Es el número de grupos óptimo coherente con el número de clases en el problema?
respuesta = "" #@param {type:"string"}

Teniendo en cuenta que los métodos que hemos usados son no supervisados, el orden asignado a los grupos no tiene porqué coincidir con las etiquetas de clase. Vamos a graficar los centroides de las clases para ver con cuál de las clases coinciden.

# ejercicio de código

n_cluster = ... #Escoja un número de clusters de acuerdo con el resultado anterior
_, skf = get_cv_objects()
train, test = next(skf.split(x, x))
Xtrain = x[train,:]
Xtest = x[test,:]

#Normalizamos los datos
scaler = StandardScaler()
scaler.fit(Xtrain)
Xtrain= scaler.transform(Xtrain)
Xtest = scaler.transform(Xtest)

modelo = ....fit(Xtrain) #Seleccione uno de los dos modelos de clustering
centroids = pca.inverse_transform(....) #Extraiga los centroides del modelo y haga la transformación inversa de PCA

fig, ax = plt.subplots(1, n_cluster, figsize=(4, 8),subplot_kw=dict(xticks=[], yticks=[]))
fig.subplots_adjust(hspace=0.05, wspace=0.05)
for i, axi in enumerate(ax.flat):
    im = axi.imshow(centroids[i].reshape(8, 8), cmap='binary')
    im.set_clim(0, 16)
#@title Pregunta Abierta
#@markdown ¿Corresponden los centroides a las clases del problema?
respuesta = "" #@param {type:"string"}