Laboratorio 6 - Parte 1: Reducción de dimensión y Selección de características#
!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="L06.01", varname="student");
#configuración del laboratorio
# Ejecuta esta celda!
from Labs.commons.utils.lab6 import *
_, x, y = part_1()
Este ejercicio tiene como objetivo implementar varias técnicas de selección de características y usar regresion logística para resolver un problema de clasificación multiclase.
Para el problema de clasificación usaremos la siguiente base de datos: https://archive.ics.uci.edu/ml/datasets/Cardiotocography
Analice la base de datos, sus características, su variable de salida y el contexto del problema.
print('Dimensiones de la base de datos de entrenamiento. dim de x: ' + str(np.shape(x)) + '\tdim de y: ' + str(np.shape(y)))
print('Dimensiones de la base de datos de entrenamiento. Dim de x: ' + str(np.shape(x)) + '\tDim de y: ' + str(np.shape(y)))
clases, muestras_por_clase = np.unique(y, return_counts=True)
print("las clases son", clases, "el conteo",muestras_por_clase)
Observación para las librerias sklearn
Llamar explícitamente los parametros de las librerias de sklearn (e.j. si se quiere usar el parametro kernel
del SVC
, se debe llamar SVC(kernel='rbf'
))
Ejercicio 1: Entrenamiento sin selección de características#
En nuestro primer ejercicio debemos completar la función para entrenar una SVM para resolver un problema de clasificación. Debemos completar siguiendo las recomendaciones:
Mantener los parámetros sugeridos de la regresión logística.
Asignar el parametro de StratifiedKFold a los splits
Usar la area bajo la curva ROC como medida de error del modulo metrics de sklearn. Tener en cuenta que esta función recibe un score, que es diferente a la predicción (explorar que método debemos usar para nuestro caso de problema y usar un estrategia One vs One).
Esta función la vamos a usar como base para comparar nuestros métodos de selección de características.
#ejercicio de código
def entrenamiento_sin_seleccion_caracteristicas(X, Y, splits):
"""
Función que ejecuta el entrenamiento del modelo sin una selección particular
de las características
Parámetros:
X: matriz con las características
Y: Matriz con la variable objetivo
splits : número de particiones a realizar
Retorna:
1. El modelo entreando
2. El vector de errores
3. El Intervalo de confianza
4. El tiempo de procesamiento
"""
j = 0
kf = StratifiedKFold...
clases = ...
for train_index, test_index in kf.split(X=X, y=Y):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = Y[train_index], Y[test_index]
# Escalado
scaler = StandardScaler()
X_train = ...
X_test = ...
# Clasificador SVM
clf = SVC(..., random_state=42)
# Entrenamiento el modelo.
# Para calcular el costo computacional
tiempo_i = time.time()
clf.fit(X=X_train,y=y_train)
# Scores para ROC AUC
y_score = clf.decision_function(X_test)
y_test_bin = label_binarize(y_test, classes=clases)
# Scores para ROC AUC
y_score = ...
y_test_bin = label_binarize(y_test, classes=clases)
# Cálculo del AUC con estrategia One-vs-One
auc = roc_auc_score....
Errores[j] = auc
times[j] = time.time() - tiempo_i
j += 1
return ...
Registra tu solución en línea
student.submit_task(namespace=globals(), task_id='T1');
#@markdown En sus palabras ¿Cuál es el proceso ejecutado por la función roc_auc_score que calcula la área sobre la curva roc?
respuesta = '' #@param {type:"string"}
Ejercicio 2: Entrenamiento con selección de características#
La siguiente función “wrapper” nos permite hacer una selección de características utilizando la librería recursive feature elimination de Sci-kit Learn.
Esta librería es un método de selección características wrapper, que usa los coeficientes derivados de un estimador entrenado para estimar que características tienen mayor poder predictivo.
Para completar debemos tener en cuenta lo siguiente:
Para el número de características usar el parámetro feature_numbers
Establecer el paso = 1 para ir eliminando las características
Asumir que el estimador se crea externamente de la función
Entender los campos del RFE disponibles despues de entrenarlo para obtener:
La máscara para saber que características fueron seleccionadas
El ranking de las características
Usar como estimador regresión de vectores de soporte (SVR) como estimador
#ejercicio de código
def recursive_feature_elimination_wrapper(estimator, feature_numbers, X,Y):
"""
Esta función es un envoltorio del objeto RFE de sklearn
Parámetros:
estimator: un modelo entregado como parámetro
feature_numbers(int): el número de características a considerar
X (numpy.array): el arreglo numpy de muestras de entrada
Y (numpy.array): el vector de etiquetas correspondiente
Retorna:
El modelo entrenado
La máscara de características seleccionada, array [longitud de caracterisitcas de X]
El ranking de características, array [longitud de caracterisitcas de X]
El objeto RFE entrenado sobre el set reducido de características
El tiempo de ejecución
"""
rfe = ...(estimator=..., n_features_to_select=..., step=...)
tiempo_i = time.time()
rfe.fit...
time_o = time.time()-tiempo_i
feature_mask = rfe...
features_rank = rfe...
estimator = rfe...
return rfe, feature_mask, features_rank, estimator, time_o
Registra tu solución en línea
student.submit_task(namespace=globals(), task_id='T2');
#@title Preguntas Abierta
#@markdown Explicar ¿Qué diferencia tiene el método implementado con un método que usa un criterio tipo filtro para la selección de características?
respuesta = '' #@param {type:"string"}
Ejercicio 3: Comparación de los resultados del modelo#
Ahora en la siguiente función, vamos a usar la función planteada para realizar experimentos con la selección de características. Para ello:
Utilizar una metodología cross-validation estratificada.
Considerando p características, retorne el modelo entrenado, el vector de errores, el intervalo de confianza y el tiempo de ejecución.
Vamos a retornar un DataFrame con las siguientes columnas:
CON_SEL: indicando si se uso selección de características
NUM_VAR: número de selección de características
NUM_SPLITS: número de particiones realizadas
T_EJECUCION: tiempo de ejecución
EXACTITUD_BALANCEADA
STD_EXACTITUD_BALANCEADA
En la primera fila del dataframe vamos a incluir la evaluación del modelo SVM sin selección de características (usando la función creada en el primer ejercicio) y sin particionar el set de datos.
#ejercicio de código
def experimentar(n_feats, n_sets, X, Y):
"""
Esta función realiza la comparación del desempeño de RFE utilizando diferente
número de feats y particionando el conjunto de datos en diferente número de
subconjuntos
Parámetros:
X (numpy.array), El arreglo numpy de características
Y (numpy.array), El vector de etiquetas
n_feats, Vector de números enteros que indica el número de características
que debe utilizar el modelo
n_sets, Vector de números enteros que indica el número de particiones
Retorna:
- DataFrame con las columnas: DESCRIPCION, T_EJECUCION ERROR_VALIDACION,
y STD_ERROR_VALIDACION
"""
df = pd.DataFrame()
idx = 0
for split_number in n_sets:
#Sin selección de características
# se ignorar las otras salidas
_,err,ic,t_ex = entrenamiento_sin_seleccion_caracteristicas(...)
df.loc[idx,'CON_SEL'] = 'NO'
df.loc[idx,'NUM_VAR'] = X.shape[1]
df.loc[idx,'NUM_SPLITS'] = ...
df.loc[idx,'TIEMPO_EJECUCION'] = ...
df.loc[idx,'EXACTITUD_BALANCEADA'] = ...
df.loc[idx,'EXACTITUD_BALANCEADA'] = ...
idx+=1
print("termina experimentos sin selección")
#Con selección de características
for f in n_feats:
for split_number in n_sets:
#Implemetamos la metodología de validación
Errores = np.ones(split_number)
Score = np.ones(split_number)
times = np.ones(split_number)
kf = ...(n_splits=split_number)
j = 0
for train_index, test_index in kf.split(...,...):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = Y[train_index], Y[test_index]
scaler = ...
X_train = ...
X_test = ...
#Configure el modelo SVM para que usa un kernel lineal,
#retorne probabilidades, use una estrategia all vs all y
#ajuste la semilla a 0.
clf = SVM...
# se ignorar las otras salidas
rfe, _, _, _, t = recursive_feature_elimination_wrapper...
Errores[j]=...
times[j] = t
j+=1
df.loc[idx,'CON_SEL'] = 'SI'
df.loc[idx,'NUM_VAR'] = f
df.loc[idx,'NUM_SPLITS'] = ...
df.loc[idx, 'TIEMPO_EJECUCION'] = ...
df.loc[idx,'EXACTITUD_BALANCEADA'] = ...
df.loc[idx, 'STD_EXACTITUD_BALANCEADA'] = ...
idx+=1
return df
Registra tu solución en línea
student.submit_task(namespace=globals(), task_id='T3');
Ejecuta la celda de codigo para realizar los experimentos
dfr = experimentar(n_feats = [3, 5, 10,15,20], n_sets = [3,6], X= x, Y=y)
Utilizando el DataFrame definido en la función anterior, retorne la mejor configuración del “Número de características” que resultó ,as beneficiosa. Identifique en términos de “Costo computacional” y “Eficiencia del Modelo”.
# podemos cambiar las forma de organizar el df para observar qué diferencias hay
# cuando se cambia la prioridad de alguno de los dos parámetros
dfr.sort_values(['EXACTITUD_BALANCEADA', "TIEMPO_EJECUCION"], ascending=[False, True])
Veamos como se relaciona el tiempo de ejecución con los splits y la selección de caracteristicas
import seaborn as sns
d_toplot = pd.melt(dfr,id_vars=['CON_SEL', 'NUM_VAR', 'NUM_SPLITS'], value_vars=['EXACTITUD_BALANCEADA', 'TIEMPO_EJECUCION'])
sns.relplot(data = d_toplot,
x = 'NUM_VAR',
y = 'value',
hue = 'CON_SEL',
style = 'NUM_SPLITS',
col = 'variable',
kind='scatter',
facet_kws = {'sharey' : False},
aspect=1.2,
s=150)
plt.show()
Ahora use el mejor modelo para entrenar nuevamente el modelo y saber que caracteristicas tienen el mejor poder predictivo. Use el # de caracteristicas que resulto mejor cuando se realizo alguna selección de caracteristicas.
# observemos el mejor modelo cuando se realizo selección de caracteristicas
dfr[dfr['CON_SEL'] == 'SI'].sort_values(["EXACTITUD_BALANCEADA"], ascending=[False]).head(2)
Ahora, completa la siguiente celda de código, para incluir la selección de características, con el número encontrado anteriormente
clf = SVC(...)#Use la misma configuración establecida en la anterior función experimentar
rfe, feature_mask, _, _, _ = recursive_feature_elimination_wrapper(clf, 10 , x,y)
mask_explicada = "\n".join([f"feature {i+1} : {m}" for i,m in zip(range(x.shape[1]), feature_mask)])
print(f"Esta es la máscara (debería contener solo valores True y False) \n {mask_explicada}")
Recordemos el concepto de importancia de variables de los metodos basados en arboles.
El siguiente código implementa el cálculo de la importancia de variables en nuestro problema.
# entrenar random forest
from sklearn.ensemble import RandomForestClassifier
feature_names = [f"feature {i+1}" for i in range(x.shape[1])]
forest = RandomForestClassifier(random_state=0)
forest.fit(x, y)
#obtener la importnacia de variables
importances = forest.feature_importances_
std = np.std([tree.feature_importances_ for tree in forest.estimators_], axis=0)
# Graficar las importancia
fig, ax = plt.subplots(figsize = (12,6))
forest_importances = pd.Series(importances, index=feature_names)
forest_importances.plot.bar(yerr=std, ax=ax)
ax.set_title("Importancia de variables")
ax.set_ylabel("Valor medio en dismunución de impureza")
fig.tight_layout()
#@title Pregunta Abierta
#@markdown ¿Son consistentes los resultados de los métodos?
respuesta = '' #@param {type:"string"}
# completa con el mejor número de características
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.metrics import confusion_matrix
BEST_NUM_CARACTERISTICAS = 10
X_train, X_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=42)
selector = RFE(estimator= LogisticRegression(),
n_features_to_select=BEST_NUM_CARACTERISTICAS ,
step=1)
estimators = [('normalización', StandardScaler()),
('Selección', selector),
('Clasificador', LogisticRegression()),
]
pipe_selector = Pipeline(estimators)
pipe_selector.fit(X=X_train, y=y_train)
# se puede usar para predecir:
confusion_matrix(y_true = y_test , y_pred = pipe_selector.predict(X=X_test), normalize='true').ravel()