Preprocesamiento y Predicción de Texto en Modelos de Machine Learning
Resumen
¿Cómo procesamos y predecimos datos de texto en un modelo batch?
Para trabajar con procesamiento por lotes, o batch processing, es esencial estructurar los datos adecuadamente. En este contexto, usaremos la clase "Sentence" que hereda de "BaseModel" y acepta dos parámetros principales: "client name" y "text". Esta estructura nos permitirá iterar a través de los datos del request, que se espera como una lista de strings. A continuación, en un bucle for, recorreremos cada sentencia o string que compone esta lista.
¿Cómo pre-procesamos y vectorizamos las sentencias?
El primer paso en el procesamiento involucra la vectorización. Utilizamos una función de preprocesamiento llamada "preprocessing function" que toma como argumento el texto de cada sentencia: sentence.text. El objetivo es convertir este texto en una matriz dispersa utilizando un Convectorizer y transformaciones basadas en la frecuencia inversa (TF-IDF).
El resultado es una matriz con:
Número de filas: Corresponde al número de documentos, textos o sentencias.
Columnas: Representan las palabras únicas en el conjunto de datos.
Dado que las matrices dispersas suelen incluir muchos ceros, configuramos x_dense como dicha estructura y continuamos apilándola verticalmente para que el modelo interprete correctamente la representación de los datos.
Con nuestra representación de datos en su lugar, podemos proceder con las predicciones. El modelo, previamente cargado, utilizará el método .predict, que procesará la matriz dispersa x_dense. Los resultados se almacenarán en una lista de predicciones.
Luego, decodificamos estas predicciones mediante label_mapping, extrayendo el valor en la primera posición ya que se asume que habrá un único elemento en cada iteración del list.
Una vez obtenidas, las predicciones se encapsulan en un objeto denominado prediction_ticket, el cual contiene información del cliente y la predicción decodificada. Este objeto se imprime y luego todas las predicciones se almacenan en una lista llamada preds_list a través del método append.
Para finalizar, las predicciones se guardan en la base de datos utilizando una sesión previamente definida y el método session.commit para realizar la inserción masiva. Finalmente, se cierra la sesión, asegurando que los datos estén persistentes y listos para su consulta.
Antes de retornar las predicciones, se debe definir el formato de salida, que en la mayoría de los casos será un JSON. Este formato se regresa mediante un método return que estructura dicha lista de predicciones para su fácil consumo.
return{"predictions": preds_list}
En resumen, al inicializar la aplicación, se debe asegurar que la base de datos y estructuras necesarias estén configuradas correctamente. A través de eventos de inicio (startup), la creación de la base de datos y sus tablas se gestionará eficazmente, sentando las bases para operaciones futuras.
A este curso le falta mucha pedagogía, no aporta valor. Paso poco en platzi,pero debo decirlo esperaba mucho de este curso y me encontré con un curso sin método y sin valor
Es que se enfoca más en dictar código que realmente relacionarlo con lo que significan los MLops. Si, es verdad, la profesora enseña muy bien y brindar claridad sobre el concepto del manejo de los modelos ML, pero en este curso se desperdicia mucho por seguir un proyecto con código pre-escrito.
Hola Profe Maria Camila! me queda una duda, a un nivel productivo hasta que punto es recomendable leer el modelo de disco (joblib.load()) cada vez que se realice un request?
Puedes dejar el modelo en algun redis o cargarlo una vez y dejarlo en memoria, esto ultimo cuesta porque se tendra que tener un servicio constantemente.
Creo que debería haber una ruta solita para mlops , ahi la llevo pero me ha costado
Por último, aquí están almacenando las predicciones, lo cual es muy común. El problema es que lo hacen utilizando una conexión síncrona y peor aún, justo antes de que el API haga el return. Eso es un disparo en el pie.
Al hacerlo así estamos amarrando la velocidad de nuestro modelo al tiempo de respuesta de la base de datos. Si la base de datos se pone lenta, el usuario se queda colgado esperando. En producción real el usuario nunca debe pagar el tiempo de nuestra telemetría, él solo quiere su predicción rápido.
Las dos opciones en producción más comunes para evitar esto son simples. La primera es usar tareas en segundo plano usando las BackgroundTasks de FastAPI. De esta forma el endpoint escupe el return inmediatamente y guarda el log por debajo del agua. La segunda ruta para sistemas más pesados es aventar el resultado a una cola de mensajes como Redis o Kafka, para que la API se libere al instante y otro microservicio guarde los datos a su propio ritmo.
Buen explicación, el flujo ya es funcional, pero para llevarlo a un nivel de MLOps maduro, hoy existen criterios más estrictos que aseguran que la API sea estable y no se muera bajo carga real.
Iterar sobre las oraciones con un for y llamar a .predict() n veces para una sola carga batch no es buena práctica. Los modelos de ML están optimizados para operaciones matriciales. Lo correcto es vectorizar toda la lista de textos de una y pasarle esa matriz al modelo. algo como :
all_texts =[s.textfor s in data.sentences]X_transformed=preprocessing_fn(all_texts)preds = app.state.model.predict(X_transformed)
Crear la sesión de la base de datos manualmente y cerrarla al final es peligroso. Si el código truena en medio de la predicción, la sesión se queda abierta y eventualmente saturarán el pool de conexiones, tirando la API. Es mejor abrirla conección dentro de un with/yield para asegurar que la conexión se cierre pase lo que pase.
Hacer .toarray() y np.vstack() sobre matrices dispersas consume muchísima RAM. Muchos algoritmos de Scikit-Learn aceptan matrices dispersas nativamente. Convertir todo a denso en producción es la forma más rápida de obtener un error de Out of Memory y que el contenedor se reinicie.
Para evitar llegar a esto, el problema raíz que se debe mejorar está en la función de preprocesamiento: al iterar texto por texto con un ciclo for, se genera una lista de matrices sueltas que obliga a la API a usar vstack como "parche". La solución es vectorizar toda la lista de textos de un solo golpe, lo cual devuelve una única matriz dispersa nativa lista para el modelo. Como regla de oro en producción: siempre validen la estructura de datos de punta a punta para no forzar conversiones que terminen matando la memoria del servidor.Regla General para Producción : Cualquier llamada a transform(),predict(), predict_proba() debe recibir el payload o batch completo de un solo jalón. Jamás deben meterse estos métodos dentro de un ciclo for.. Si rompen el batch para procesar de uno en uno, anulan esa optimización y obligan al servidor a pagar el peaje del intérprete de Python en cada iteración, creando un cuello de botella masivo en el CPU. PD: Ya que estamos en estas recuerden siempre , cuidado con Pandas en pruducción, por el mismo principio
Usar global label_mapping es una práctica débil de programación.
El curso debió ser una saga siguiendo una misma línea de datos:
1) planeación, requerimientos, así sea inventados, métricas, EDA, lista de modelos que pueden encajar en el proyecto.
2) mlflow + prefect
3) despliegue y monitoreo
Noto también que la estructura de carpetas y archivos de las ramas master (mlflow+prefect) y deploy_serving (despliegue hasta ahora) son muy diferentes, eso choca con el flujo, se presta para confusión y abandono del curso, deben explicar por qué hay menos carpetas en deploy que en master, que al menos yo no trabajo en tecnología aún, al menos no en equipo.