# machine-learning-clasico-con-python
Machine Learning con scikit-learn y XGBoost
Volver a la portada del cursoProyecto final: predictor de hits
Sección 5 · Árboles, boosting con XGBoost y ajuste de hiperparámetros
Proyecto final: el predictor de hits de PyRitmo
Durante el curso preparaste el problema, entrenaste regresiones y clasificadores, comparaste árboles con ensambles, limpiaste datos sucios, validaste con pliegues y ajustaste hiperparámetros. Ahora juntas todo en un solo modelo, desde el CSV sucio hasta una evaluación honesta. Se usan Python 3.12 o superior, scikit-learn 1.8 o superior, pandas y xgboost 2.1 o superior (pip install xgboost). El notebook proyecto-predictor-de-hits contiene una solución de referencia: consúltalo al terminar, no antes.
Contexto
PyRitmo, una app de streaming de música, recibe cada semana decenas de canciones nuevas de los sellos. El equipo editorial puede promover solo algunas. Quiere un modelo que, antes del lanzamiento, estime la probabilidad de que una canción entre al Top 50 semanal en su primer mes. Dejar pasar un hit cuesta más que promover una canción que no despega, pero si el modelo marca casi todo como hit, el equipo deja de confiar en él.
Datos
Copia las funciones generar_canciones y ensuciar y las listas NUMERICAS y CATEGORICAS de la primera celda de cualquier notebook del curso. Trabaja solo con la versión sucia:
df, _ = generar_canciones(n=1500, semilla=42)
sucio = ensuciar(df, semilla=7)
print(sucio.shape) # (1525, 15)
print(sucio.duplicated().sum()) # 25
print(sucio["genero"].nunique()) # 17
Si no obtienes esas tres cifras, revisa las semillas. El CSV trae filas duplicadas, géneros mal escritos, algunas duraciones en milisegundos y valores faltantes en tempo_bpm y seguidores_miles. El objetivo es top50 (1 si la canción entró al Top 50). El segundo valor que devuelve generar_canciones son los estilos latentes: no los uses como entrada.
Requisitos mínimos
- Limpieza. Escribe una función
limpiar(sucio)que quite duplicados, unifique los géneros en 5 categorías y pase a segundos las duraciones en milisegundos. Comprueba el resultado: 1500 filas, 412 hits (27.5 %), duración máxima de 446 segundos, y aún 85 faltantes entempo_bpmy 59 enseguidores_miles. Estos faltantes no se borran. - X e y sin fuga. Usa
X = limpio[NUMERICAS + CATEGORICAS]ey = limpio["top50"]. Explica en una frase por quéreproducciones_mes1ypopularidadno pueden ser columnas de entrada. Parte contrain_test_split(X, y, test_size=0.25, stratify=y, random_state=42): 1125 canciones en train y 375 en test. El test no se toca hasta el requisito 7. - Pipeline con imputación. Un
ColumnTransformerconSimpleImputer(strategy="median")yStandardScalerpara las numéricas, ySimpleImputer(strategy="most_frequent")yOneHotEncoder(handle_unknown="ignore")para el género, todo dentro de unPipelinecon el modelo. - Línea base y comparación. Un
DummyClassifiery, como mínimo, un modelo lineal (regresión logística), uno por distancia o SVM (KNN oSVC), un Random Forest y unXGBClassifier, todos con el mismo preprocesamiento y comparados concross_val_score,scoring="roc_auc"yStratifiedKFold(5, shuffle=True, random_state=42)sobre train. Reporta media y desviación en una sola tabla. - XGBoost: early stopping y búsqueda de hiperparámetros. Aparta una partición de validación dentro de train (no uses el test) y entrena XGBoost con
early_stopping_rounds=50yeval_setpara conocer el orden de magnitud den_estimators. Después ajusta XGBoost conRandomizedSearchCV(scoring="roc_auc"), buscandon_estimatorsylearning_ratejuntos. Justifica los rangos y reporta el número de ajustes,best_params_ybest_score_. - Umbral de decisión o
scale_pos_weight. Con predicciones de validación cruzada sobre train, elige el umbral más alto que detecte al menos el 70 % de los hits y muestra precision y recall en al menos tres umbrales. Compara con la otra palanca,scale_pos_weight= negativos / positivos con umbral 0.5, y justifica cuál usas según el costo de dejar pasar un hit. No uses las dos a la vez sin medir. - Evaluación final, una vez. En test: AUC, matriz de confusión, precision y recall con tu umbral y con 0.5, y la comparación con el Dummy.
- Una canción nueva. Predice la probabilidad de Top 50 de esta canción: energía 0.78, bailabilidad 0.84, acústica 0.10, 96 bpm, 185 segundos, 850 mil seguidores, con colaboración, no explícita, en playlist editorial, reguetón.
Pistas de implementación
En xgboost 2.1 o superior, early_stopping_rounds y eval_metric van en el constructor. El eval_set debe pasar por el mismo preprocesamiento, así que ajusta el pre con la parte de ajuste y transforma ambas:
X_fit, X_val, y_fit, y_val = train_test_split(
X_tr, y_tr, test_size=0.2, stratify=y_tr, random_state=42)
pre_es = crear_pre().fit(X_fit)
xgb_es = XGBClassifier(
n_estimators=2000, learning_rate=0.05, max_depth=4,
eval_metric="logloss", early_stopping_rounds=50,
random_state=42)
xgb_es.fit(pre_es.transform(X_fit), y_fit,
eval_set=[(pre_es.transform(X_val), y_val)],
verbose=False)
print(xgb_es.best_iteration)
Dentro de RandomizedSearchCV no uses early stopping: con un eval_set fijo, los pliegues dejan de ser independientes. Usa loguniform para learning_rate y reg_lambda, randint para n_estimators y max_depth y uniform para subsample y colsample_bytree.
Para el umbral, cross_val_predict devuelve, para cada canción de train, la probabilidad que le dio un modelo que no la vio al entrenar:
mejor = busqueda.best_estimator_
proba = cross_val_predict(mejor, X_tr, y_tr, cv=cv,
method="predict_proba")[:, 1]
for u in [0.25, 0.30, 0.35, 0.50]:
pred = (proba >= u).astype(int)
p = precision_score(y_tr, pred)
r = recall_score(y_tr, pred)
print(u, round(p, 3), round(r, 3))
Al final, aplica el mismo umbral a busqueda.predict_proba(X_te)[:, 1]. No elijas el umbral mirando el test.
Lo que deberías obtener aproximadamente
Estas son las cifras de la solución de referencia. Las tuyas pueden variar si eliges otros modelos o rangos, pero deberían quedar cerca:
| Paso | Referencia |
|---|---|
| AUC de validación cruzada (train) | Dummy 0.5, logística 0.828, SVC 0.825, Random Forest 0.851, gradient boosting 0.848, XGBoost sin ajustar 0.841 |
Early stopping (validación de 225 canciones, learning_rate 0.05) | best_iteration 97, log-loss de validación 0.4336 |
| Búsqueda aleatoria de XGBoost (30 combinaciones, 150 ajustes) | learning_rate 0.0336, 332 árboles, max_depth 2, subsample 0.927, colsample_bytree 0.649, reg_lambda 0.715; best_score_ 0.8594 |
| Umbral en validación cruzada | 0.25: precision 0.528, recall 0.793; 0.30: 0.563 y 0.748; 0.35: 0.614 y 0.699; 0.50: 0.722 y 0.537. Umbral elegido: 0.30 |
scale_pos_weight 2.64 con umbral 0.5 | precision 0.556, recall 0.751: un punto equivalente al umbral 0.30 |
| Test con umbral 0.5 | AUC 0.871, exactitud 0.827, precision 0.721, recall 0.602, matriz [[248, 24], [41, 62]] |
| Test con umbral 0.30 | exactitud 0.776, precision 0.568, recall 0.767, matriz [[212, 60], [24, 79]] |
| Dummy en test | exactitud 0.725, sin detectar ningún hit |
| Canción nueva | probabilidad de Top 50: 0.951 |
Fíjate en el intercambio: con umbral 0.30 el modelo encuentra 79 de los 103 hits del test en lugar de 62, a cambio de 60 falsas alarmas en lugar de 24.
Extras opcionales
- Regresión de popularidad. Con los mismos datos limpios, predice
popularidadcon una regresión lineal usandolog10(seguidores_miles)en lugar de la columna cruda (partición sinstratify). La referencia obtiene MAE 4.69 y $R^2$ 0.716 en test, contra MAE 8.68 delDummyRegressor. - Estilo como característica. Ajusta KMeans con k=4 sobre los rasgos de audio escalados, dentro del pipeline, y agrega el cluster como columna. Compara el AUC de validación cruzada con y sin ella.
- Importancia por permutación. Calcula
permutation_importancesobre test conscoring="roc_auc"y comenta qué columnas pesan más.
Entregables
proyecto.ipynb, ejecutado de principio a fin sin errores, con la funciónlimpiary una celda de texto por requisito.INFORME.md(una página): la tabla de comparación, el resultado del early stopping, los hiperparámetros elegidos, el umbral (oscale_pos_weight) y su justificación, las cifras de test, la predicción de la canción nueva y dos limitaciones del modelo. Los datos son sintéticos: describe lo que ves sin inventar causas.
Rúbrica (100 puntos, más 10 de extras)
- Limpieza (15 puntos): duplicados, géneros y unidades corregidos, con las cifras de control.
- Datos y partición (10 puntos): sin columnas de fuga, partición estratificada con la semilla indicada.
- Pipeline con imputación (15 puntos): imputación y codificación dentro del pipeline, sin fuga.
- Comparación de modelos (15 puntos): Dummy, modelo lineal, distancia o SVM, Random Forest y XGBoost con validación cruzada en una sola tabla.
- XGBoost (20 puntos): early stopping con validación separada de train (nunca el test), búsqueda aleatoria con rangos razonables, nombres
paso__parametroy costo reportado. - Umbral y evaluación (15 puntos): umbral o
scale_pos_weightelegido sin mirar el test y justificado por el costo de perder un hit, test usado una sola vez, métricas correctas. - Informe (10 puntos): conclusiones con cifras, la justificación del umbral y limitaciones honestas.
- Extras (hasta 10 puntos): cualquiera de los tres, con su comparación.