Los procesos e información están en el corazón de todos los negocios. La vulnerabilidad y la oportunidad de este momento es la cuestión de si su negocio puede automatizar sus procesos usando IA y cosechar las recompensas de hacerlo. Chatgpt, una IA de propósito general, nos ha abierto los ojos a lo que AI puede hacer. Lo que importa ahora es dirigir el poder de la IA a sus problemas comerciales y desbloquear el valor de sus datos propietarios. En este documento, te mostraré cómo.
No quieres una IA que solo pueda chatear; Lo que realmente desea son las automatizaciones que realizan el trabajo que mantiene su negocio en funcionamiento, impulsando a través de los procesos comerciales con gran precisión y escala. La forma probada de personalizar la IA a sus procesos comerciales es ajustar un LLM en sus datos y en la acción que desea que realice la IA.
Hablemos específicamente sobre el ajuste fino que vamos a hacer en este documento y la tecnología detrás de él. A continuación se enumeran las cinco herramientas que usaremos ampliamente:
Al final del día, debe quitar dos cosas de este documento:
Voy a describirle un problema duro y del mundo real, lo que los investigadores de aprendizaje automático ya han hecho y la nueva frontera que hemos podido impulsar para usar herramientas poderosas y SOTA. Vamos a entrenar modelos en GPU en la nube. También vamos a poner en práctica poderosas prácticas de MLOPS, utilizando MLFLOW para organizar nuestros experimentos y parámetros. Y en el camino, voy a señalar el patrón de diseño de este proyecto para que pueda personalizar la base de código para sus propios proyectos de aprendizaje profundo.
Comencemos con los antecedentes del problema.
Un buen proceso para encontrar problemas adecuados para el aprendizaje automático y para los conjuntos de datos de calidad es comenzar navegando en sitios con puntos de referencia. Los puntos de referencia proporcionan un marco de referencia para el nivel de dificultad del problema para el aprendizaje automático, que utilizamos para medir nuestro progreso durante el desarrollo del modelo. Un conjunto de datos particular con puntos de referencia bien establecidos son los términos injustos del conjunto de datos de servicio (injusto); Aquí hay una declaración del problema intrigante para ello: use AI para encontrar todas las cláusulas injustas en términos de contratos de servicio. El contexto es que la Ley del Consumidor Europeo sobre contratos injustos establece cuáles son las cláusulas injustas y los diferentes tipos de cláusulas injustas. Lo que hace que los tos injustos sean perfectos para la clasificación de texto es que se ha etiquetado manualmente de acuerdo con lo que se estableció en la ley europea.
Chalkidis et al. (2021) aplicaron ocho métodos de aprendizaje automático diferentes a los tos injustos y obtuvieron macro F1 que varían de 75 a 83, y en la Figura 1 justo debajo, extracto de sus resultados publicados.
| Método | Injusto | |
|---|---|---|
| Micro F1 | macro F1 | |
| TFIDF-SVM | 94.7 | 75.0 |
| Bert | 95.6 | 81.3 |
| Roberta | 95.2 | 79.2 |
| Deberta | 95.5 | 80.3 |
| Formador largo | 95.5 | 80.9 |
| Pájaro grande | 95.7 | 81.3 |
| Legal | 96.0 | 83.0 |
| Casco | 96.0 | 82.3 |
Las cosas interesantes que podemos inferir de esta tabla son:
Mirando los datos, el desequilibrio de clases está ciertamente presente, lo cual es una buena razón para el primer y segundo punto anterior.
Hay ocho tipos diferentes de cláusulas injustas. Los autores de ese documento desarrollaron modelos de clasificación de múltiples etiquetas para los ocho tipos, pero simplemente vamos a construir un modelo de clasificación binaria que clasifica una cláusula como justa o injusta.
Diseñemos cómo lo haremos.
__init__.py expone las API públicas del módulo en el que se encuentra para que podamos acortar convenientemente las importaciones para una funcionalidad profundamente submodulada como este ejemplo: from . fine_tune_clsify_head import TransformerModule La importación anterior en architectures/__init__.py permite que el código fuera de architectures importe TransformerModule sin tener que recordar las migas de pan que conducen a donde se encuentra esta función, así: así:
from architectures import TransformerModuleAsí es como se ve la estructura del proyecto desde la raíz del módulo Lightning_MlFlow:
.
├── architectures
│ ├── __init__.py
│ └── fine_tune_clsify_head.py
├── config
│ ├── __init__.py
│ └── train_config.py
├── data
│ ├── __init__.py
│ └── lex_glue.py
└── train.py
La pregunta clave es por qué el proyecto está estructurado de esta manera. Y la respuesta es que la separación de las preocupaciones se establece entre los 3 submódulos y 1 punto de entrada. Caminemos por cada uno de estos módulos a su vez:
architectures : especifica esencialmente todo lo que Pytorch necesita saber, excepto los datosconfig : contiene todos los parámetros y se puede subdividir en train_config.py e inference_config.py y cualquier otra cosadata : contiene todas las configuraciones y la lógica de procesamiento de datostrain.py : el punto central de control; Este es el único código que puede ver architectures , config y data ; Las llamadas de SDK de mlflow van aquíCon esta breve introducción fuera del camino, enrollemos nuestras mangas e intentemos comprender realmente el código en cada uno de los módulos. Comencemos con arquitecturas/fino_tune_clsify_head.py. Este código está organizado como la Figura 3 justo debajo.

Está claro que los métodos clave dentro de esta clase para comprender son: el constructor, el avance de la red, las métricas y los pasos. Discutiremos cada uno de estos a su vez. Comencemos con el constructor. Este fragmento es cómo se ve el código:
Lo que es realmente crucial aquí es que el constructor carga un modelo previamente provocado de la cara abrazada, y establece un ajuste fino eficiente de los parámetros (PEFT). PEFT es la forma de borde de sangrado de ajustar modelos de idiomas grandes, y es una implementación de la cara abrazada de Qlora, que es la combinación de tres estrategias de eficiencia diferentes: cuantificación, rango bajo y adaptador. La cuantización es la idea de que un entero de un solo byte es de precisión numérica suficiente para los cálculos de gradiente en el aprendizaje profundo. El rango bajo proviene del concepto de álgebra lineal de que el rango de una matriz es el número de dimensiones no redundantes del espacio vectorial. El ajuste del adaptador puede entenderse como una especie de generalización de ajustar la cabeza de la red, al volver a una parte de los pesos en muchas capas.
Veamos el método forward :
Como entrenamiento, una red neuronal implica la alternancia entre backprop y alimentación hacia adelante, esto especifica la parte delantera, y lo notable es que los argumentos de forward son: input_ids , attention_mask y label . Estas son las salidas de un tokenizador facial abrazando, y nos permiten unir la cara abrazada y los rayos de Pytorch.
A continuación, echemos un vistazo al método _compute_metrics :
Hay algunas cosas que vale la pena señalar aquí. Primero, observe cómo se desempaquetan los logits de la evaluación de la función de avance y luego se convierten en probabilidades a través de la función Softmax. Esto, a su vez, conduce a la predicción binaria. Usamos la predicción y el ( batch["label"] real) para calcular la precisión, F1, precisión y recuerdo.
Finalmente, hablemos de training_step , validation_step y test_step .
Se parecen casi iguales, con una diferencia importante que es training_step outputs["loss"] en lugar de metrics . Esto tiene sentido porque Backprop minimiza iterativamente la pérdida; training_step es solo la abstracción de Pytorch Lightning del bucle de entrenamiento.
Ok, ahora hemos terminado con architectures , vamos a sumergirnos en config/trenes_config.py, que se ve como la Figura 7 a continuación.
Este código se explica por sí mismo, pero hay algunas opciones para llamar. El pretrained_model está configurado en roberta-base , y si lo cambiáramos a cualquier nombre de modelo de abrazadera que admite AutoTokenizer, todo debería funcionar (probado). Se descubrió que la precisión del modelo era más sensible a max_length y batch_size , por lo que estos son los principales hiperparámetros para jugar, y ambos intercambian entre precisión y carga computacional. En Azure ML, utilicé la instancia Standard_NC24ADS_A100_V4. El VRAM en la GPU A100 demostró ser el factor limitante para estos dos hiperparámetros. Lo que encontré fue que 256 fue el mayor batch_size que no haría que Cuda arroje un error fuera de memoria.
Determinar max_length de 128 fue más fácil ya que la gran mayoría de las cláusulas estaban bajo este límite de token.
Finalmente, como veremos más adelante, max_epochs de 10 fue elegido por prueba y error, ya que la métrica de validación F1 estábamos rastreando en mlflow fue aplanado en ese momento.
Ok, hablemos de datos. Todo lo que necesita saber está en la clase LexGlueDataModule en Data/Lex_Glue.py, y esta es su estructura:
Hay bastantes detalles de la esgaludos en LexGlueDataModule , pero la idea principal es que el método setup obtiene los datos directamente de abrazar la cara, y luego tiene que suceder algún tipo de tokenización. Los detalles de esa tokenización están en el método _shared_transform , que es solo la interfaz de alto nivel para el método de devolución de llamada _preprocess .
Finalmente, estamos listos para Train.py, el punto central de control para todo. ¿Recuerdas cuando comenzamos esta sección con la estructura del directorio del proyecto? Esa estructura está comunicando un mensaje importante: que las dependencias se resuelven hacia abajo en el árbol de directorio. En otras palabras, restringimos el código para llamar solo otro código que está en el mismo nivel o más bajo. Esta restricción ciertamente no proviene de Python; Es autoimpuesto y ¿por qué? Para eliminar las conjeturas. No hay duda de que las cadenas confusas de dependencia son un problema importante en grandes bases de código. Todo esto significa que train.py va a importar la funcionalidad de cada uno de los otros módulos y luego poner en marcha una ejecución de entrenamiento. Veamos la parte del código en el corazón:
Estamos instanciando una clase de entrenador de rayos de Pytorch, encendiendo la parada temprana y estableciendo la precisión basada en la máquina en la que estamos en precision="bf16-mixed" if torch. cuda.is_available() else "32-true" . Configuramos mlflow para rastrear todo. Para armar todo, importamos la funcionalidad de los otros tres módulos que habíamos descrito anteriormente:
from architectures . fine_tune_clsify_head import TransformerModule
from config import TrainConfig
from data import LexGlueDataModuleLas funcionalidades centrales están envueltas en nuestras subclases de superclase de rayos de Pytorch, por lo que las instancias:
model = TransformerModule (
pretrained_model = config . pretrained_model , num_classes = config . num_classes ,
lr = config . lr , )
datamodule = LexGlueDataModule (
pretrained_model = config . pretrained_model , max_length = config . max_length ,
batch_size = config . batch_size , num_workers = config . num_workers ,
debug_mode_sample = config . debug_mode_sample , ) Y finalmente, invocamos los métodos de fit y test del objeto trainer :
trainer . fit ( model = model , datamodule = datamodule )
best_model_path = checkpoint_callback . best_model_path
# Evaluate the last and the best models on the test sample.
trainer . test ( model = model , datamodule = datamodule )
trainer . test (
model = model , datamodule = datamodule , ckpt_path = best_model_path , ) Habíamos llamado test Dos veces para que estemos probando tanto el último modelo en el momento de la parada como el mejor modelo según nuestra métrica. Este es un ejemplo perfecto de la rica funcionalidad que viene con Pytorch Lightning.
Y eso concluye el tutorial del código. Ahora vamos a sumergirnos en los resultados del modelo.
Aquí hay un tablero que lo reúne todo:
Aprovechando la estrecha integración entre Azure y MLFlow, las métricas de nuestra ejecución de capacitación se han puesto a disposición para un fácil consumo y visualización en la consola web de Azure ML. Mlflow capturó mucho más que las métricas, pero profundizar en su funcionalidad completa y los casos de uso más amplios de MLOP es para otro día. Por ahora, centrémonos en la historia que nuestra visualización de datos nos revela.
Una cosa que sale de la tabla superior izquierda es que el entrenamiento sin duda tiene una pérdida mejorada en la muestra de validación. Además, entre 125 y 190 pasos, la curva comienza a aplanarse, una posible indicación de que un mayor entrenamiento podría ser innecesario. Pero al ser monótono, vemos que el sobreajuste aún no ha ocurrido, y lo que es más, la curva se ha vuelto a emprender de 190 a 210, por lo que tal vez deberíamos haberlo dejado correr para otras 5 épocas.
La trama superior derecha, curiosamente, cuenta una historia similar. La característica más notable es cuán altos son los valores de precisión, y la unidad aquí es porcentaje. El hecho de que las líneas lleguen al 90% desde el primer momento tiene sentido porque el conjunto de datos está desequilibrado en una relación 1: 9 entre injusto y justo. Es exactamente por qué la métrica preferida es F1, que es lo que mostramos en el gráfico inferior izquierdo.
Las curvas F1 aquí exhiben el patrón clásico donde el modelo mejora rápidamente al principio, pero finalmente alcanza el punto de disminución del rendimiento. Una pregunta interesante provocada por las curvas F1 es ¿cómo nos reconciliamos entre la pérdida y la F1, cuando uno sugiere estrechar mientras que el otro deja espacio para ir más allá? El siguiente bit de teoría descifrará este enigma.
Observamos el mundo en forma de eventos discretos, pero las construcciones continuas (p. Ej., Probabilidad) a menudo son más útiles en matemáticas. Los resultados en el conjunto de datos injusto se codifican "justo" o "injusto", y hay una distribución de probabilidad latente que generó esos resultados. Esa distribución de probabilidad latente es con qué puede funcionar la función de pérdida de entropía cruzada. Entonces, en el aprendizaje profundo, optimizamos para la métrica continua, pero juzgamos cuán bueno es nuestro modelo por la métrica discreta, porque continuos (latentes) y discretos (observados) son aproximaciones entre sí. Esa es la idea.
Entonces, cuando el F1 sugiere una historia diferente a la pérdida, solo hay dos explicaciones posibles: aleatoriedad y desequilibrio de clase. De hecho, es por eso que la precisión y la F1 son mucho más volátiles que la pérdida.
La aleatoriedad también parece ser la explicación probable de la brecha entre la mejor puntuación F1 de validación del 70% y la puntuación de prueba F1 del 78% (desde la tabla inferior derecha).
Este análisis de juego por juego de las métricas de evaluación del modelo pone fin a esta sección. A continuación, consigue práctico hacer este proyecto tuyo. La guía de instalación a continuación lo ayudará a comenzar a jugar con el código.
Paso 1. Clonar este repositorio en un directorio de trabajo local:
$ git clone https://github.com/zjohn77/lightning-mlflow-hf.git
$ cd lightning-mlflow-hf Paso 2. Todas las dependencias del proyecto están en environment.yml , y vas a crear un entorno virtual para ello. Las instrucciones a continuación son para conda , pero nada en estas dependencias impide venv o poetry .
$ conda env create -n lightning-mlflow-hf -f environment.yml Paso 3. Al final de la ejecución de entrenamiento, la función copy_dir_to_abs copiará las salidas a Azure Blob Storage. Si Azure también es lo que está usando, simplemente pase credenciales a esta función y está todo configurado. De lo contrario, reemplace con su propio flujo de trabajo.
Paso 4. En su entorno virtual, cambiará o señalará su IDE a dónde está y ejecuta train.py :
$ python train.pySi no hay errores, imprimirá para consolar un mensaje facial abrazando y muchos mensajes de Pytorch Lightning. Después de unos minutos, debería comenzar a imprimir una barra de progreso. Siéntate fuerte y deja que haga lo suyo. Cuando la ejecución finalmente termine, una tabla ASCII que resume las métricas de evaluación para el modelo final se imprimirá en la consola. ¡Eso es todo lo que hay!
Cualquier contribución es bienvenida. Utilizamos solicitudes GitHub Pull para la revisión del código, y utilizamos el formateador negro para garantizar la consistencia del estilo del código.
Las pruebas unitarias y las pruebas DOC también son muy bienvenidas.
Una hoja de ruta es un trabajo en progreso. Vuelve pronto.
Ilias Chalkidis, Abhik Jana, Dirk Hartung, Michael Bommarito, Ion Androutsopoulos, Daniel Martin Katz, Nikolaos Aletras. (2021). LexGlue: un conjunto de datos de referencia para la comprensión del lenguaje legal en inglés . Recuperado de Arxiv: https://arxiv.org/abs/2110.00976