ml-api/models/modelo.pkl:
Modo reglas (fallback)
Sin modelo entrenado.
ClasificadorML aplica umbrales heurísticos sobre los índices T del SENA. Versión 0.1.0-reglas. Disponible desde el primer arranque sin datos históricos.Modo ML (Random Forest)
Con
modelo.pkl presente. El clasificador usa el vector de 25 features calculado desde las 188 respuestas brutas. Versión 1.0.0-ml. Proporciona una puntuación de confianza por predicción.Vector de features
La función_features_desde_respuestas() en clasificador.py convierte la cadena de 188 dígitos en un vector de 25 valores numéricos:
Composición del vector (25 features)
| Posición | Nombre | Descripción |
|---|---|---|
| 0 | dep | Suma de respuestas en los 16 ítems de Depresión |
| 1 | ans | Suma en los 14 ítems de Ansiedad |
| 2 | asc | Suma en los 7 ítems de Ansiedad social |
| 3 | som | Suma en los 10 ítems de Somatización |
| 4 | pst | Suma en los 8 ítems de Estrés postraumático |
| 5 | obs | Suma en los 7 ítems de Obsesivo-compulsivo |
| 6 | ate | Suma en los 10 ítems de Inatención |
| 7 | hip | Suma en los 10 ítems de Hiperactividad |
| 8 | ira | Suma en los 12 ítems de Ira |
| 9 | agr | Suma en los 11 ítems de Agresión |
| 10 | des | Suma en los 8 ítems de Conducta desafiante |
| 11 | ant | Suma en los 10 ítems de Conducta antisocial |
| 12 | sus | Suma en los 6 ítems de Consumo de sustancias |
| 13 | esq | Suma en los 9 ítems de Esquizotipia |
| 14 | ali | Suma en los 8 ítems de Problemas alimentarios |
| 15 | fam | Suma en los 7 ítems de Problemas familiares |
| 16 | esc | Suma en los 7 ítems de Problemas escolares |
| 17 | com | Suma en los 5 ítems de Problemas comunitarios |
| 18 | aut | Suma en los 9 ítems de Autoestima |
| 19 | soc | Suma en los 9 ítems de Integración social |
| 20 | cnc | Suma en los 4 ítems de Conciencia de cambio |
| 21 | total_altos | Número de ítems (de 188) con respuesta ≥ 4 |
| 22 | criticos_count | Número de ítems críticos con respuesta ≥ 3 |
| 23 | edad | Edad del estudiante en años (default 15 si no se provee) |
| 24 | sexo_n | Sexo codificado: MASCULINO=0, FEMENINO=1, OTRO=2 |
Se usan sumas brutas de ítems en lugar de puntuaciones T porque las T requieren tablas de baremos externas y varían por edad y sexo. Las sumas brutas capturan la misma información y hacen el modelo portable sin dependencias externas.
Datos de entrenamiento
El corpus de entrenamiento se almacena en la tablaHistoricoSENA de PostgreSQL. El script consulta únicamente los registros que tienen la cadena de 188 respuestas brutas:
Modelo de datos HistoricoSENA
Parámetros del modelo
El algoritmo elegido esRandomForestClassifier de scikit-learn con los siguientes parámetros fijos en entrenar_sena.py:
| Parámetro | Valor | Motivo |
|---|---|---|
n_estimators | 300 | Balance entre estabilidad y velocidad de entrenamiento |
max_depth | None | Árboles completos; el bosque compensa el sobreajuste individual |
class_weight | "balanced" | Evita que el modelo ignore clases raras como ROJO_URGENTE |
random_state | 42 | Resultados reproducibles con los mismos datos |
n_jobs | -1 | Paralelización en todos los núcleos disponibles |
El README del proyecto indica que XGBoost está en la hoja de ruta para reemplazar Random Forest como clasificador principal. El cambio de algoritmo no requiere modificar la función de features ni el esquema de base de datos; solo el bloque de entrenamiento en
entrenar_sena.py y la dependencia en requirements.txt.Pipeline de entrenamiento
Cargar datos desde PostgreSQL
cargar_datos() conecta a la BD usando DIRECT_URL o DATABASE_URL del .env, ejecuta el SELECT sobre HistoricoSENA y devuelve un DataFrame con columnas respuestas, edad, sexo y semaforo.Calcular el vector de features
preparar_datos() aplica calcular_features() a cada cadena de 188 dígitos y concatena las columnas edad y sexo_n para formar la matriz X de dimensión (N, 25). El vector objetivo y contiene las etiquetas de semáforo.Split estratificado 75/25
train_test_split() divide los datos en 75% entrenamiento y 25% prueba (train_size=0.75), usando stratify=y para mantener la proporción de clases en ambas particiones.Validación cruzada estratificada
StratifiedKFold con hasta 5 folds (limitado por la clase menos frecuente) evalúa el modelo sobre el conjunto de entrenamiento. Se reportan cv_mean y cv_std de accuracy.Entrenamiento y evaluación final
El modelo se ajusta sobre X_train, se evalúa con
accuracy_score y classification_report sobre X_test, y luego se re-entrena sobre todos los datos (clf.fit(X, y)) para producción.Ejecutar el entrenamiento
DIRECT_URL o DATABASE_URL estén definidas en el archivo .env en la raíz del proyecto.
Salida esperada:
Artefactos de salida
| Archivo | Contenido |
|---|---|
ml-api/models/modelo.pkl | RandomForest serializado con joblib (300 árboles) |
ml-api/models/metrics.json | accuracy, cv_mean, cv_std, train_size, n_registros, report, features, n_estimators |
ml-api/models/feature_importance.json | Lista ordenada de {feature, importance} |
Ejemplo de metrics.json
Registro de entrenamientos (EntrenamientoML)
Cada ejecución exitosa del script puede registrarse en la tablaEntrenamientoML de PostgreSQL para trazabilidad:
Carga del modelo en FastAPI
Al arrancar FastAPI,ClasificadorML.__init__() llama a _cargar_modelo(), que verifica la existencia de modelo.pkl y lo carga si está disponible:
modelo.pkl no existe al momento del arranque, el clasificador opera con las reglas heurísticas (version = "0.1.0-reglas") sin interrupciones.
Fallback al motor de reglas
Hay dos situaciones en que el clasificador recurre al motor de reglas aunque el modelo esté cargado:- Sin cadena de respuestas: el campo
respuestasdelTamizajeInputesNone. En ese caso_predecir_ml()delega a_predecir_reglas()automáticamente. - Sin modelo:
modelo.pklno existe en disco;self.modeloesNone.
POST /api/v1/clasificar que incluyen únicamente índices T (sin respuestas) siempre usarán el motor de reglas, con o sin modelo entrenado.