Categorías
Informática Universitario Videojuego

Ejercicios

Estos son algunos ejercicios que pueden realizarse para practicar lo aprendido en el tema de Aprendizaje No Supervisado, usando la biblioteca Scikit-Learn desde Python, y también combinándolo con ideas vistas en el tema de Aprendizaje Supervisado.

⚠️Salvo que se indique lo contrario, estos ejercicios pueden resolverse en condiciones de prueba final, esto es, examen individual en el laboratorio, sin acceso a Internet y sin usar IA generativa. Tan sólo hace falta la plantilla o el contexto que proporcione el profesor junto al enunciado, y un servidor de lenguaje como Pylance que viene por defecto en la extensión oficial de Python para Visual Studio Code y que permite explorar la documentación del API.

  1. Completa el siguiente código para ajustar un modelo de agrupamiento jerárquico aglomerativo a los datos de telemetría de jugadores y obtener 3 grupos basándote en un dendrograma previo.
import pandas as pd
from sklearn.cluster import AgglomerativeClustering

# Datos de entrada: nivel y horas jugadas por usuario
data = pd.DataFrame({'nivel': [12, 14, 50, 52, 90, 88], 'horas': [5, 8, 40, 45, 120, 115]})

# 1. Instancia el modelo para obtener exactamente 3 clusters
modelo = ...

# 2. Obtén las etiquetas asignadas a cada muestra de data
etiquetas = ...

print("Etiquetas asignadas:", etiquetas)
  1. En el algoritmo $K$-Medias, la elección de $K$ es clave. Rellena los huecos para calcular y almacenar el Silhouette Score para diferentes valores de K.
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
import numpy as np

X = np.array([[1, 2], [1, 4], [1, 0], [10, 2], [10, 4], [10, 0]])
silhouette_scores = []

for k in range(2, 4):
    # Instancia K-Means con k clusters y random_state=42
    kmeans = ....
    
    # Calcula el coeficiente de silueta pasándole los datos y las etiquetas obtenidas
    score = ...
    silhouette_scores.append(score)

print("Silhouette Scores:", silhouette_scores)
  1. El algoritmo DBSCAN agrupa por regiones de alta densidad y clasifica automáticamente los puntos alejados como ruido o anomalías (etiquetándolos con el valor -1). Completa el código e identifica qué representa la etiqueta -1. Responde a esto: ¿Qué índice/punto del array X recibe la etiqueta -1 y por qué razón técnica?
from sklearn.cluster import DBSCAN
import numpy as np

# Datos con dos grupos densos y un valor atípico
X = np.array([[1, 2], [2, 2], [2, 3], [8, 7], [8, 8], [25, 80]])

# Instancia DBSCAN fijando el radio eps=3.0 y min_samples=2
dbscan = ...
labels = dbscan.fit_predict(X)

print("Etiquetas de DBSCAN:", labels)
  1. PCA exige estandarizar previamente las características para no sesgar las varianzas hacia las variables de mayor escala. Rellena los pasos para reducir una matriz a 2 componentes principales.
from ...
import numpy as np

X = np.array([[100, 0.1], [200, 0.2], [150, 0.15], [300, 0.3], [500, 0.5]])

# 1. Estandariza los datos con media 0 y varianza unitaria
scaler = ...

# 2. Instancia PCA para reducir a 2 componentes principales
pca = ...
X_pca = pca.fit_transform(X_scaled)

print("Ratio de varianza explicada por cada componente:", ...)
  1. Dada la siguiente salida de código tras ejecutar PCA sobre un conjunto de 10 características de comportamiento de usuarios: [0.65, 0.20, 0.08, 0.03, 0.01, 0.01, 0.01, 0.005, 0.003, 0.002]
    ¿Cuántos componentes principales debemos retener si queremos conservar al menos el 90% de la información (varianza) original de los datos? Justifica el cálculo. Explica brevemente por qué PCA no garantiza conservar la interpretabilidad original de las características.
  1. Imagina un conjunto de datos en 3D con forma de «rollo suizo» (Swiss Roll). Explica por qué PCA no es adecuado para «desplegar» este tipo de geometrías no lineales. Compara el uso de t-SNE y UMAP indicando cuál es más adecuado para transformar nuevos datos de test que lleguen en tiempo real y cuál preserva mejor la estructura global del espacio.
  1. Completa el siguiente script para detectar jugadores sospechosos de usar cheats según el número de acciones por minuto y precisión de tiro.
from sklearn.ensemble import IsolationForest
import numpy as np

# Datos: APM y Precisión de tiro
X = np.array([[120, 45], [130, 48], [115, 42], [140, 50], [900, 99]])

# Instancia IsolationForest asumiendo una tasa de contaminación del 20% (0.2)
detector = ...

# En Scikit-Learn, pred == 1 indica Normal y pred == -1 indica Anomalía
...
        print(f"Jugador {i} detectado como SOSPECHOSO (Anomalía)")
  1. El algoritmo OneClassSVM intenta envolver los datos de comportamiento «normal» en una hiperesfera o hiperplano no lineal para dejar fuera valores atípicos, siendo muy útil cuando apenas se dispone de datos de anomalías previas.
from sklearn.svm import OneClassSVM
import numpy as np

# Transacciones normales registradas
X_train = np.array([[10.0], [12.5], [11.0], [9.5], [10.5]])
# Nuevas transacciones a verificar
X_test = np.array([[11.2], [500.0]])

# Instancia OneClassSVM con kernel RBF y nu=0.1
oc_svm = ...
oc_svm.fit(X_train)

# Genera las predicciones sobre el conjunto de test
preds = ...
print("Predicciones para X_test (1: Normal, -1: Anomalía):", preds)
  1. Analizando una base de datos con $N = 1000$ partidas de un juego de cartas en la tienda virtual:
    – 200 partidas compraron el paquete «Poción de Vida» (X).
    – 300 partidas compraron el paquete «Escudo Mágico» (Y).
    – 100 partidas compraron ambos paquetes a la vez (X unión Y).
    – Calcula el Soporte (Support(X -> Y)) y la Confianza (Confidence(X -> Y)).
    – Calcula la Elevación (Lift(X -> Y)) e interpreta si la relación entre comprar la poción y el escudo es de asociación positiva, negativa o si ambas son totalmente independientes
  1. Scikit-Learn no incluye algoritmos directos de reglas de asociación. Completa el código usando la biblioteca mlxtend para extraer ítems frecuentes y filtrar reglas por confianza.
import pandas as pd
from mlxtend.frequent_patterns import apriori, association_rules

# Matriz binaria codificada (One-Hot) de compras en tienda
df_compras = pd.DataFrame({
    'Pocion': [1, 1, 1, 0, 1],
    'Espada': [1, 1, 0, 1, 0],
    'Escudo': [1, 1, 1, 0, 1]
})

# 1. Obtén los conjuntos de ítems frecuentes con soporte mínimo de 0.5
items_frecuentes = ...

# 2. Genera las reglas de asociación usando la métrica 'confidence' con umbral mínimo de 0.7
reglas = association_rules(items_frecuentes, metric="...", min_threshold=...)

print(reglas[['antecedents', 'consequents', 'support', 'confidence', 'lift']])
  1. Crea un conjunto sintético de clasificación de alta dimensionalidad y construye un Pipeline de Scikit-Learn que integre StandardScaler, PCA y RandomForestClassifier.
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.ensemble import RandomForestClassifier

# Generación de datos sintéticos: 500 muestras, 30 características (muchas redundantes)
X, y = make_classification(n_samples=500, n_features=30, n_informative=5, random_state=42)

# 1. Construye el Pipeline encadenando: Estandarización, PCA (retener 5 componentes) y RandomForest
pipeline = ...

# 2. Evalúa el pipeline mediante validación cruzada de 5 pliegues midiendo la métrica 'accuracy'
scores = cross_val_score(...)

print(f"Accuracy Media en Validación Cruzada: {scores.mean():.4f} (+/- {scores.std():.4f})")
  1. Una técnica avanzada de ingeniería de características consiste en utilizar la distancia a los centroides de los clusters (o las etiquetas de los clusters en formato Una-Activa) obtenidos mediante un algoritmo no supervisado como K-Medias para enriquecer las variables de entrada de un modelo supervisado. Completa el código para generar distancias a clusters mediante KMeans.transform() y concatenarlas a las características originales antes de clasificar.
import numpy as np
from sklearn.datasets import make_moons
from sklearn.cluster import KMeans
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# Datos sintéticos no lineales (dos semicírculos)
X, y = make_moons(n_samples=400, noise=0.2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_score=0.3, random_state=42)

# 1. Entrena K-Medias con K=5 clusters sobre el conjunto de entrenamiento
kmeans = KMeans(n_clusters=5, random_state=42)
kmeans.fit(X_train)

# 2. Genera las distancias de cada punto a los 5 centroides (transformación no supervisada)
X_train_dist = ...
X_test_dist = ...

# 3. Concatena horizontalmente (axis=1) las características originales con las distancias a los clusters
X_train_ext = np.hstack([X_train, X_train_dist])
X_test_ext = np.hstack([X_test, X_test_dist])

# 4. Entrena un clasificador supervisado lineal (Regresión Logística) sobre el espacio extendido
clf = LogisticRegression()
...

y_pred = clf.predict(X_test_ext)
print("Accuracy del modelo con características de clusters:", accuracy_score(y_test, y_pred))
  1. Los datos de entrenamiento a menudo contienen ruido o errores de registro (anomalías) que distorsionan el ajuste de los modelos supervisados de regresión. Escribe un script que genere datos sintéticos de regresión con valores atípicos intencionados, utilice IsolationForest para filtrar las muestras anómalas del conjunto de entrenamiento y compare el rendimiento de una Regresión Lineal entrenada con y sin filtrado.
import numpy as np
from sklearn.datasets import make_regression
from sklearn.ensemble import IsolationForest
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

# Datos sintéticos de regresión
X, y = make_regression(n_samples=200, n_features=1, noise=10.0, random_state=42)

# Introducimos 10 atípicos severos artificiales
np.random.seed(42)
y[:10] += 500.0

# 1. Usa IsolationForest para identificar las anomalías en X (contaminación = 0.05)
iso = IsolationForest(contamination=0.05, random_state=42)
inliers_mask = ... 
# Selecciona solo las muestras normales (etiquetadas como 1)

# 2. Filtra X e y dejando solo las muestras "normales"
X_clean, y_clean = ...

# 3. Compara el MSE de dos modelos de Regresión Lineal
reg_ruidosa = LinearRegression().fit(X, y)
reg_limpia = LinearRegression().fit(X_clean, y_clean)

print("MSE con datos ruidosos:", mean_squared_error(y, reg_ruidosa.predict(X)))
print("MSE tras eliminar anómalos:", mean_squared_error(y_clean, reg_limpia.predict(X_clean)))
  1. En escenarios reales, etiquetar datos es costoso, disponiendo de muchas muestras sin etiquetar y muy pocas etiquetadas. Una estrategia híbrida consiste en agrupar la totalidad de los datos con $K$-Medias, asignar a cada cluster la etiqueta de clase más frecuente (modo) entre las muestras etiquetadas que caen en él, y propagar esa «pseudo-etiqueta» a las muestras sin etiquetar para entrenar posteriormente un modelo supervisado. Completa la lógica de propagación de etiquetas.
import numpy as np
from scipy.stats import mode
from ...

# Generamos 300 muestras, pero asumimos que solo conocemos las etiquetas del 10% (30 muestras)
X, y = make_classification(n_samples=300, n_classes=2, random_state=42)
X_labeled, y_labeled = X[:30], y[:30]
X_unlabeled = X[30:]

# 1. Agrupamos TODO el conjunto de datos X en K=10 clusters usando K-Medias
kmeans = KMeans(n_clusters=10, random_state=42)
cluster_labels_all = ...

# 2. Averiguamos la clase supervisada mayoritaria para cada uno de los 10 clusters
pseudo_labels_map = {}
cluster_labels_labeled = cluster_labels_all[:30]

for k in range(10):
    # Selecciona las etiquetas supervisadas reales de las muestras que cayeron en el cluster k
    y_in_cluster = y_labeled[cluster_labels_labeled == k]
    if len(y_in_cluster) > 0:
        # Asigna la moda de la clase al diccionario del cluster
        pseudo_labels_map[k] = mode(y_in_cluster, keepdims=False).mode
    else:
        pseudo_labels_map[k] = 0  # Valor por defecto si un cluster no tiene muestras etiquetadas

# 3. Propaga las pseudo-etiquetas a todas las muestras sin etiquetar
...

# 4. Une los datos etiquetados originales con los pseudo-etiquetados para entrenar un Árbol de Decisión
X_train_full = np.vstack([X_labeled, X_unlabeled])
y_train_full = np.concatenate([y_labeled, y_unlabeled_pseudo])

tree = DecisionTreeClassifier()
tree.fit(X_train_full, y_train_full)
print("Árbol entrenado con dataset pseudo-etiquetado exitosamente.")
  1. Construye un script completo que combine las siguientes fases sobre un dataset de juguete:
    – Genera un dataset sintético complejo mediante make_classification.
    – Aplica un transformador personalizado o pipeline que realice estandarización (StandardScaler) y reducción de dimensionalidad con PCA conservando el 95% de varianza.
    – Ejecuta en paralelo el agrupamiento por densidad con DBSCAN sobre el subespacio reducido para extraer la variable de cluster como un factor categórico.
    – Entrena un clasificador supervisado (GradientBoostingClassifier o SVC) y muestra un reporte de clasificación completo (classification_report).
import pandas as pd
from sklearn.datasets import make_classification
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.cluster import DBSCAN
from sklearn.svm import SVC
from sklearn.metrics import classification_report

# 1. Generación de datos
X, y = make_classification(n_samples=400, n_features=15, n_informative=8, random_state=42)

# 2. Preprocesamiento No Supervisado: Estandarización y PCA reteniendo el 95% de la varianza acumulada
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

pca = ...  # Pasa un float entre 0 y 1 para indicar varianza explicada acumulada
X_pca = pca.fit_transform(X_scaled)

# 3. Descubrimiento de Estructura No Supervisada: Agrupamiento con DBSCAN
dbscan = DBSCAN(eps=1.5, min_samples=5)
clusters = dbscan.fit_predict(X_pca)

# 4. Combinación: Añade la etiqueta del cluster como una característica numérica más al subespacio PCA
X_final = pd.DataFrame(X_pca)
X_final['cluster_feature'] = clusters

# 5. Entrenamiento Supervisado y Evaluación
clf = ...
clf.fit(X_final, y)

y_pred = clf.predict(X_final)
print("Reporte de Clasificación del Sistema Híbrido:")
...

Esta página está licenciada bajo CC BY-NC-SA 4.0 por Laboratorios Narratech.