English Русский 中文 Deutsch 日本語 Português
preview
Añadimos un LLM personalizado a un robot comercial (Parte 5): Desarrollar y probar la estrategia de negociación con LLMs (I) Ajuste fino

Añadimos un LLM personalizado a un robot comercial (Parte 5): Desarrollar y probar la estrategia de negociación con LLMs (I) Ajuste fino

MetaTrader 5Trading | 31 enero 2025, 09:17
383 0
Yuqiang Pan
Yuqiang Pan

Tabla de contenido


Introducción

En nuestro artículo anterior, presentamos cómo usar la aceleración de GPU para entrenar modelos de lenguaje grandes, pero no la usamos para formular estrategias comerciales o realizar pruebas retrospectivas. Sin embargo, el objetivo final de entrenar nuestro modelo es usarlo y que nos sirva. Entonces, a partir de este artículo, utilizaremos paso a paso el modelo de lenguaje entrenado para formular estrategias comerciales y probar nuestras estrategias en pares de divisas extranjeras. Por supuesto, este no es un proceso sencillo. Requiere que adoptemos medios técnicos correspondientes para lograr este proceso. Así que vamos a implementarlo paso a paso.

Todo el proceso puede requerir varios artículos para completarse.

  • El primer paso es formular una estrategia comercial. 
  • El segundo paso es crear un conjunto de datos según la estrategia y ajustar el modelo (o entrenar el modelo), de modo que las entradas y la salidas del modelo se ajusten a nuestra estrategia comercial. Hay muchos métodos diferentes para lograr este proceso, y proporcionaré tantos ejemplos como sea posible.
  • El tercer paso es la inferencia del modelo y la fusión de las salidas con la estrategia comercial y crear un EA de acuerdo con nuestra estrategia comercial. Por supuesto, todavía tenemos trabajo que hacer en la fase de inferencia del modelo (elegir el marco de inferencia y los métodos de optimización adecuados: por ejemplo, atención instantánea, cuantificación del modelo, aceleración, etc.). 
  • El cuarto paso es utilizar backtesting histórico para probar nuestro EA en el lado del cliente. 

Al ver esto, algunos amigos pueden preguntarse: ya he entrenado un modelo con mis propios datos, ¿por qué necesito ajustarlo? La respuesta a esta pregunta la daremos en este artículo.

Por supuesto, los métodos disponibles no se limitan a ajustar el gran modelo. También se pueden utilizar otras técnicas, como la tecnología RAG (una técnica que utiliza información de recuperación para ayudar al gran modelo de lenguaje a generar contenido) y la tecnología de agente (un cuerpo inteligente creado por la inferencia del gran modelo de lenguaje). Si todos estos contenidos se completan en un solo artículo, la extensión del artículo será demasiado larga y no será suficiente para leer las reglas y parecerá caótico, por eso las discutiremos en varias partes. En este artículo, hablaremos principalmente de nuestros primeros y segundos pasos, formularemos estrategias de negociación y daremos un ejemplo de puesta a punto de un gran modelo lingüístico (GPT-2).


Ajuste fino de modelos de lenguaje de gran tamaño

Antes de empezar, debemos entender este ajuste. Algunos amigos pueden tener dudas: Ya hemos entrenado un modelo en los artículos anteriores, ¿por qué necesitamos afinarlo? ¿Por qué no utilizar directamente el modelo entrenado? Para entender esta cuestión, debemos fijarnos primero en la diferencia entre los grandes modelos lingüísticos y los modelos tradicionales de redes neuronales: En esta fase, los grandes modelos lingüísticos se basan básicamente en la arquitectura transformadora, que incluye complejos mecanismos de atención. El modelo es complejo y tiene un gran número de parámetros, por lo que cuando se entrenan grandes modelos lingüísticos, generalmente se requiere una gran cantidad de datos para el entrenamiento, así como un soporte informático de alto rendimiento, y el tiempo de entrenamiento suele ser de decenas de horas a varios días o incluso decenas de días. Así que, para los desarrolladores individuales, es relativamente difícil entrenar un modelo lingüístico desde cero (por supuesto, si tienes una mina de oro en casa, es otra historia).

En este momento, utilizar nuestro propio conjunto de datos para afinar el gran modelo lingüístico ya entrenado nos ofrece más opciones. Y el modelo de lenguaje grande que ha sido entrenado con una gran cantidad de datos en computación en clúster a gran escala tiene mejor compatibilidad y capacidad de generalización. Esto no significa que el modelo entrenado directamente con datos específicos no sea lo suficientemente bueno. Siempre que el volumen y la calidad de los datos sean lo suficientemente buenos y grandes, y el equipo de hardware sea lo suficientemente potente, puede utilizar completamente su propio conjunto de datos para entrenar un modelo desde cero y el efecto puede ser mejor.

Esto simplemente significa que el ajuste (fino) nos da más opciones. Por lo tanto, el paradigma principal de los modelos de lenguaje de gran tamaño es entrenar previamente el modelo de lenguaje en una gran cantidad de datos generales y luego ajustarlo para tareas posteriores específicas para lograr el propósito de la adaptación del dominio. El ajuste fino mencionado aquí es esencialmente el mismo que el aprendizaje por transferencia o el ajuste fino de las redes neuronales tradicionales, pero también existen diferencias muy considerables. A continuación se presentan específicamente los métodos de ajuste fino comúnmente utilizados en modelos de lenguaje grandes.


El ajuste fino de modelos grandes se puede dividir en métodos de ajuste fino de aprendizaje supervisado, métodos de ajuste fino de aprendizaje no supervisado y métodos de ajuste fino de aprendizaje de refuerzo:

  • Método de ajuste fino de aprendizaje supervisado: Esta es la forma más común, es decir, entrenar el modelo utilizando datos etiquetados. Por ejemplo, puede recopilar algunos conjuntos de datos de preguntas y respuestas de diálogo. En este proceso, establezca la entrada y la salida de destino como un par de ejemplos emparejados para optimizar el modelo.
  • Método de ajuste fino de aprendizaje no supervisado: Cuando no hay suficiente texto marcado, el modelo de lenguaje grande puede continuar con el entrenamiento previo en una gran cantidad de texto sin marcar, lo que ayuda al modelo a comprender mejor la estructura del lenguaje.
  • Método de ajuste fino del aprendizaje de refuerzo: Al igual que en el aprendizaje de refuerzo tradicional, primero construya un modelo de comparación de calidad de texto (equivalente a un Critor) como modelo de recompensa y clasifique la calidad de múltiples resultados diferentes proporcionados por el modelo de preentrenamiento para la misma palabra clave. Al mismo tiempo, este modelo de recompensa puede utilizar un modelo de clasificación binaria para juzgar los pros y contras entre los dos resultados de entrada. Luego, de acuerdo con los datos de muestra de palabras de indicación proporcionados, utilice el modelo de recompensa para brindar la evaluación de calidad del resultado de finalización de la palabra de indicación del usuario del modelo de preentrenamiento y obtenga mejores resultados con el modelo de lenguaje objetivo. El ajuste fino del aprendizaje de refuerzo hará que el texto resultante generado por LLM basado en el modelo de preentrenamiento obtenga mejores resultados. Los métodos de aprendizaje de refuerzo comúnmente utilizados incluyen DPO, ORPO, PPO, etc.

El ajuste fino de modelos grandes, específicamente los métodos más utilizados, se dividen principalmente en dos categorías: Ajuste de modelos y ajuste por indicaciones:

1. Ajuste fino de parámetros completo

La forma más directa es ajustar todo el modelo de lenguaje grande, lo que significa que todos los parámetros se actualizarán para adaptarse al nuevo conjunto de datos. Este método también se denomina ajuste fino ineficiente, porque a medida que el volumen de parámetros del gran modelo de lenguaje actual se hace cada vez más grande, los recursos de hardware necesarios aumentan exponencialmente. Para dar un ejemplo: por ejemplo, para ajustar un modelo de lenguaje grande con una escala de parámetros de 8B, se pueden requerir 2 memorias de video de 80G o un total de entrenamiento de aceleración de múltiples tarjetas de aproximadamente 160G. Se cree que esta inversión en hardware desalienta a la mayoría de los desarrolladores comunes.

2. Ajuste del adaptador

El ajuste del adaptador es un método de ajuste fino PEFT para BERT propuesto por los investigadores de Google por primera vez y que también marcó el preludio de la investigación PEFT. Cuando se enfrenta una tarea posterior específica, si se realiza un ajuste fino completo (es decir, se ajustan con precisión todos los parámetros del modelo de preentrenamiento), es demasiado ineficiente; y si se utiliza el modelo de preentrenamiento fijo, solo se ajustan con precisión unas pocas capas de parámetros cercanos a la tarea posterior y es difícil lograr un mejor efecto. Entonces, diseñaron la estructura del Adaptador, la integraron en la estructura del Transformador y, durante el entrenamiento, arreglaron los parámetros del modelo original de preentrenamiento y solo ajustaron la estructura del Adaptador recién agregada. Al mismo tiempo, para garantizar la eficiencia del entrenamiento (es decir, introducir la menor cantidad posible de parámetros), diseñaron el Adaptador con una estructura tal: Primero, una capa de proyección descendente asigna características de alta dimensión a características de baja dimensión y luego pasa una capa no lineal. Después de eso, utilice una estructura de proyecto superior para mapear las características de baja dimensión a las características originales de alta dimensión. Al mismo tiempo, también se diseña una estructura de conexión de salto para garantizar que pueda degradarse a identidad en el peor de los casos.

Artículo: "Parameter-Efficient Transfer Learning for NLP" (https://arxiv.org/pdf/1902.00751)

Código: https://github.com/google-research/adapter-bert

3. Ajuste rápido y eficiente de parámetros

pt

El ajuste rápido de parámetros eficiente es un método de ajuste fino de modelos eficiente y práctico. Al agregar vectores de incrustación continuos relacionados con tareas antes de la entrada para el entrenamiento, se puede reducir la cantidad de cálculo y parámetros y acelerar el proceso de entrenamiento. Al mismo tiempo, solo se necesita una pequeña cantidad de datos para un ajuste fino efectivo, lo que reduce la dependencia de una gran cantidad de datos etiquetados. Además, se pueden personalizar diferentes indicaciones para diferentes tareas, lo que proporciona una gran adaptabilidad a las tareas. En aplicaciones prácticas, el ajuste rápido y eficiente de parámetros puede ayudarnos a adaptarnos rápidamente a diversas necesidades de tareas y mejorar el rendimiento del modelo. Para implementar un ajuste de indicaciones con parámetros eficientes, generalmente necesitamos los siguientes pasos:

  • Definir vectores de incrustación relacionados con la tarea: defina vectores de incrustación continuos relacionados con la tarea según las necesidades de la tarea. Estos vectores pueden diseñarse manualmente o pueden aprenderse automáticamente a través de otros métodos.
  • Modificar el prefijo de entrada: Agregue el vector de incrustación definido como prefijo antes de los datos de entrada. Estos prefijos se pasarán al modelo para entrenamiento junto con la entrada original.
  • Ajuste el modelo: Utilice los datos de entrada con el prefijo para realizar el ajuste. En este proceso, solo se actualizarán los parámetros de la parte del prefijo y los parámetros del modelo de preentrenamiento original permanecerán sin cambios.
  • Evaluación y optimización: Evaluar el rendimiento del modelo en el conjunto de validación y realizar ajustes de optimización. Mediante la iteración y la optimización continuas, podemos obtener un modelo afinado adecuado para tareas específicas.

Artículo: “The Power of Scale for Parameter-Efficient Prompt Tuning Official” (https://arxiv.org/pdf/2104.08691.pdf)

Código: https://github.com/google-research/prompt-tuning

4. Ajuste de prefijo

pft

El método "ajuste de prefijo" propone agregar un vector de incrustación continuo relacionado con la tarea a cada entrada durante el entrenamiento. El ajuste de prefijo sigue siendo un parámetro fijo de preentrenamiento, pero además de agregar una o más incrustaciones para cada tarea, utiliza un perceptrón multicapa para codificar el prefijo (tenga en cuenta que el perceptrón multicapa es el codificador del prefijo) y ya no continúa ingresando LLM como el ajuste de indicaciones.

Aquí, continuo (continuous) es relativo a discreto (discrete) de los tokens de solicitud de texto definidos manualmente. Por ejemplo, una matriz de tokens de solicitud definida manualmente es ['The', 'movie', 'is', '[MASK]'], si el token allí se reemplaza con un vector de incrustación como entrada, la incrustación es una expresión continua (continuous). Al volver a entrenar la tarea posterior, corrija todos los parámetros del modelo grande original y solo vuelva a entrenar el vector de prefijo (incrustación de prefijo) relacionado con la tarea posterior. Para los modelos LM autorregresivos (como GPT-2 utilizado en nuestro ejemplo actual), se agregará un prefijo antes del mensaje original (z = [PREFIX; x; y]); para el modelo LM de codificador+decodificador (como BART), se agregará un prefijo a la entrada del codificador y del decodificador respectivamente (z = [PREFIX; x; PREFIX'; y],).

Artículo: “Prefix-Tuning: Optimizing Continuous Prompts for Generation, P-Tuning v2: Prompt Tuning Can Be Comparable to Fine-tuning Universally Across Scales and Tasks” (https://aclanthology.org/2021.acl-long.353)

Código: https://github.com/XiangLi1999/PrefixTuning

5. Ajuste de prefijo y Ajuste de prefijo V2

Ptv

El ajuste de prefijo puede mejorar significativamente el rendimiento de los modelos de lenguaje en entornos multitarea y de bajos recursos. Mejora las funciones de entrada al introducir una subred frontal de pequeña escala y fácil de calcular, mejorando así el rendimiento del modelo base. El ajuste de prefijo aún corrige los parámetros LLM, utiliza un perceptrón multicapa y LSTM para codificar el mensaje y, después de la codificación, normalmente se ingresa a LLM después de concatenarlo con otros vectores. Obsérvese que, tras el entrenamiento, sólo se conserva el vector después de la codificación del prompt, y ya no se conserva el codificador. Este método no sólo puede mejorar la precisión y robustez del modelo en diversas tareas, sino también reducir significativamente la cantidad de datos y el coste computacional necesarios durante el ajuste fino. El problema del ajuste de prefijo es que funciona mal en modelos de parámetros pequeños, por lo que existe una versión V2, similar a LoRA, en la que se incrustan nuevos parámetros en cada capa (denominada Deep FT).

En concreto, el ajuste de prefijo v2 es una versión mejorada basada en el ajuste de prefijo v1. La mejora principal es la adopción de un método de poda más eficiente, que puede reducir aún más el volumen de parámetros del ajuste fino del modelo. Estrictamente hablando, el ajuste de prefijo no es un método nuevo, es una versión optimizada de Deep Prompt Tuning (Li y Liang, 2021; Qin y Eisner, 2021).

El ajuste de prefijo v2 está diseñado para la generación y exploración de conocimiento, pero una de las mejoras más importantes es aplicar indicaciones continuas a cada capa del modelo de preentrenamiento, no solo a la capa de entrada. Este método sólo necesita afinar entre el 0,1% y el 3% de los parámetros, y puede estar a la altura del modelo de ajuste fino, ¡lo que demuestra su potencia!

Artículo sobre el ajuste de prefijo: “GPT Understands, Too” (https://arxiv.org/pdf/2103.10385).

6. LoRA

lr

El método LoRA congela primero los parámetros del modelo de preentrenamiento y añade parámetros adicionales de dropout+Linear+Conv1d en cada capa del descodificador. De hecho, fundamentalmente hablando, LoRA no puede lograr el rendimiento de un ajuste fino completo de los parámetros. Según los experimentos, el ajuste fino completo de los parámetros es mucho mejor que el método LoRA, pero en situaciones de bajos recursos, LoRA se convierte en una mejor opción. LoRA nos permite entrenar indirectamente algunas capas densas en la red neuronal optimizando la matriz de descomposición de rangos de los cambios en la capa densa durante la adaptación, mientras se mantienen congelados los pesos previos al entrenamiento.

Características de LoRA:

  • Un modelo bien entrenado previamente se puede compartir y utilizar para construir muchos módulos LoRA pequeños para diferentes tareas. Podemos congelar el modelo compartido y cambiar tareas de manera efectiva reemplazando las matrices A y B en la Figura 1, reduciendo así en gran medida los requisitos de almacenamiento y la sobrecarga de cambio de tareas.
  • LoRA hace que el entrenamiento sea más eficiente. Al utilizar optimizadores adaptativos, el umbral de hardware se reduce 3 veces porque no necesitamos calcular gradientes ni mantener el estado del optimizador de la mayoría de los parámetros. Por el contrario, solo optimizamos la matriz inyectada, de rango bajo y mucho más pequeña.
  • Nuestro diseño lineal simple nos permite fusionar la matriz entrenable con los pesos congelados durante la implementación y no introduce retrasos de inferencia en la estructura en comparación con el modelo completamente ajustado.
  • LoRA es irrelevante para muchos métodos anteriores y se puede combinar con muchos métodos.

Artículo: “LoRA: Low-Rank Adaptation of Large Language Models” (https://arxiv.org/pdf/2106.09685.pdf).

Código: https://github.com/microsoft/LoRA.

7. AdaLoRA

alr

Hay muchas formas de decidir qué parámetros LoRA son más importantes que otros, y AdaLoRA es una de ellas, y los autores de AdaLoRA recomiendan considerar el valor singular de la matriz LoRA como un indicador de su importancia.

Una diferencia importante con el LoRA-drop mencionado anteriormente es que los adaptadores en la capa intermedia del LoRA-drop están completamente entrenados o no están entrenados en absoluto. AdaLoRA puede decidir que diferentes adaptadores tengan diferentes rangos (en el método LoRA original, todos los adaptadores tienen el mismo rango).

AdaLoRA tiene un total del mismo número de parámetros en comparación con el LoRA estándar del mismo rango, pero la distribución de estos parámetros es diferente. En LoRA, el rango de todas las matrices es el mismo, mientras que en AdaLoRA, algunas matrices tienen un rango más alto y algunas matrices tienen un rango más bajo, por lo que el número total final de parámetros es el mismo. Los experimentos han demostrado que AdaLoRA produce mejores resultados que los métodos LoRA estándar, lo que indica que hay una mejor distribución de los parámetros entrenables en las partes del modelo, lo que es particularmente importante para una tarea determinada, y las capas más cercanas al final del modelo proporcionan un rango más alto, lo que indica que adaptarse a estos es más importante.

AdaLoRA descompone la matriz de peso en una matriz incremental a través de la descomposición en valores singulares y ajusta dinámicamente el tamaño de los valores singulares en cada matriz incremental, de modo que solo aquellos parámetros que contribuyen más o son necesarios para el rendimiento del modelo se actualizan durante el proceso de ajuste fino, para mejorar el rendimiento del modelo y la eficiencia de los parámetros.

Artículo: “AdaLoRA: Adaptive Budget Allocation for Parameter-Efficient Fine-Tuning” (https://arxiv.org/pdf/2303.10512).

Código: https://github.com/QingruZhang/AdaLoRA.

8. QLoRA 

qlr

QLoRA retropropaga el gradiente al adaptador de bajo rango (LoRA) a través de un modelo de lenguaje preentrenado cuantificado de 4 bits congelado, que puede reducir en gran medida el uso de memoria y ahorrar recursos informáticos mientras mantiene el rendimiento de la tarea completa de ajuste fino de 16 bits.

Características técnicas de QLoRA:

  • Retropropagación de gradientes en adaptadores de orden bajo (LoRA) a través de un modelo de lenguaje preentrenado, cuantificado y congelado de 4 bits.
  • Presentar 4-bit NormalFloat (NF4), que es un tipo de datos teóricamente óptimo para cuantificar información para datos distribuidos normalmente, que puede producir mejores resultados empíricos que los números enteros de 4 bits y los números de punto flotante de 4 bits.
  • Aplicar doble cuantificación, un método de cuantificación de constantes de cuantificación, ahorrando alrededor de 0,37 bits por parámetro en promedio.
  • Utilice un optimizador de paginación con NVIDIA Unified Memory para evitar picos de memoria durante los puntos de control de gradiente al procesar lotes pequeños con longitudes de secuencia largas. Requisitos de memoria significativamente reducidos, lo que permite ajustar con precisión un modelo de 65B de parámetros en una única GPU de 48 GB sin degradar el tiempo de ejecución ni predecir el rendimiento en comparación con un punto de referencia completamente ajustado de 16 bits.

Artículo: “QLORA: Efficient Finetuning of Quantized LLMs” (https://arxiv.org/pdf/2305.14314.pdf).

Código: https://github.com/artidoro/qlora.

Este artículo enumera solo algunos métodos representativos comúnmente utilizados, así como algunas variantes basadas en la tecnología LoRA: LoRA+, VeRA, LoRA-fa, LoRA-drop, DoRA y Delta-LoRA, etc. Este artículo no los presentará uno por uno, los interesados pueden consultar la literatura relevante.

Por supuesto, existen otras ingenierías rápidas que también cubren nuestras necesidades técnicas (como la tecnología RAG), en artículos posteriores os las presentaré.

A continuación, le mostraremos un ejemplo de ajuste fino de GPT-2 con parámetros completos.


Formulación de la estrategia comercial

Respecto de la estrategia comercial, utilizamos un ejemplo simple para guiar el ajuste fino del modelo de lenguaje grande, que temporalmente no involucra la implementación del EA (la implementación específica debe esperar hasta que nuestra estrategia de inferencia del modelo de lenguaje grande esté completa antes de que podamos crear razonablemente el EA). Primero, obtenga los precios de cierre de las últimas 20 cotizaciones de un período determinado de un par de divisas determinado del cliente y defina su promedio como A. Luego, use el modelo de lenguaje grande para predecir los precios de cierre de las próximas 40 cotizaciones del mismo período y defina su promedio como B. Luego, juzgue si comprar o vender a continuación según el valor predicho:

  • Si el valor promedio B de los 40 valores previstos es mayor que el valor promedio A de los últimos 20 precios de cierre actuales, entonces compre.
  • Si el valor promedio B de los 40 valores previstos es menor que el valor promedio A de los últimos 20 precios de cierre actuales, entonces venda.
  • Si A y B son iguales o muy cercanos, entonces no opere.

Ahora hemos completado la formulación de la estrategia comercial. Esta es una estrategia comercial bastante simple para demostración, que puede idealizarse. También puede reemplazar esta estrategia según lo necesite, como cambiar la entrada a dinámica, la longitud total de los resultados de la predicción es 60 menos la longitud de la entrada. O utilice directamente otras lógicas comerciales, como la formulación de reglas basadas en la estrategia de ondas, la estrategia de cocodrilo o la estrategia de tortuga. Por supuesto, su modelo también debe realizar los ajustes correspondientes. A continuación, comenzamos a crear un conjunto de datos de acuerdo con la estrategia y a ajustar el modelo de lenguaje grande.


Creación de conjunto de datos

Ya hemos creado un conjunto de datos cuando analizamos el entrenamiento de modelos de lenguaje grandes anteriormente, que es el contenido incluido en el archivo “llm_data.csv”. Este conjunto de datos solo contiene las cotizaciones de un par de divisas en un ciclo de 5 millones y se ha procesado en consecuencia, con un total de 2442 filas de datos, cada una con 64 columnas. Para conocer el proceso de procesamiento específico, consulte la parte de entrenamiento de modelos de lenguaje grandes con CPU o GPU en esta serie de artículos (el enlace específico es "Añadimos un LLM personalizado a un robot comercial (Parte 3): Entrenando tu propio LLM utilizando la CPU"). Por supuesto, también puede utilizar el script proporcionado en el artículo para volver a personalizar el conjunto de datos o convertir su propia excelente idea en un conjunto de datos (como convertir la correlación entre los datos fiscales del gobierno y el tipo de cambio en un conjunto de datos, etc.). En resumen, este conjunto de datos puede tener cualquier formato, no solo citas numéricas. 

1. Preprocesamiento

Primero, importamos las librerías que necesitamos:

import pandas as pd

from transformers import GPT2LMHeadModel, GPT2Tokenizer, GPT2Config

from transformers import TextDataset, DataCollatorForLanguageModeling

from transformers import Trainer, TrainingArguments

import torch

Leer el archivo de datos:

df = pd.read_csv('llm_data.csv')

Actualicé este conjunto de datos, este conjunto de datos ahora contiene 60 precios de cierre de un par de divisas en un ciclo de 5M en cada línea, en lugar de los 64 originales, y los datos se procesan en formato de texto:

sentences = [' '.join(map(str, prices)) for prices in df.iloc[:-10,1:].values]

Esta línea de código lee principalmente todo el archivo de clase de conjunto de datos, recorre sus elementos y convierte cada línea en una cadena, tratándolas como una oración; cada oración contiene 60 precios de cierre. Es decir, lo convertimos a: “0,6119 0,61197 0,61201…0,61196”, no: “0,6119” “0,61197”…“0,61196”. Esto es para permitir que el modelo de lenguaje recuerde la longitud de la secuencia que configuramos, por ejemplo, si ingresamos 20 datos, el modelo completará los 40 datos restantes por nosotros, en lugar de generar contenido que no podemos controlar.

También hay un lugar especial en esta línea de código que necesita ser explicado, que es “df.iloc[:-10,1:].values”. El “:-10” significa llevar el comienzo del archivo CSV hasta las últimas 10 líneas, y dejamos las 10 líneas restantes para probar; “1:” es para eliminar la primera columna de cada línea, esta columna es el valor del índice en el archivo CSV, no lo necesitamos.

A continuación, concatenamos todas las secuencias en un conjunto de datos y lo guardamos como “train.txt”, de modo que no necesitemos procesar el archivo CSV varias veces la próxima vez, solo leer el archivo procesado directamente.

with open('train.txt', 'w') as f:

    for sentence in sentences:

        f.write(sentence + '\n')

2. Cargar los datos como una clase de conjunto de datos

Después de completar la preprocesamiento de datos, aún necesitamos utilizar el tokenizador para procesar los datos aún más y cargarlos en el formato de datos “Dataset” de PyTorch. Ahora algunas clases de uso común están integradas en la biblioteca Transformers para completar este trabajo directamente. En el ejemplo de este artículo, puedes usar directamente TextDataset para lograr esta función, lo cual es muy simple, pero primero debemos usar GPT-2 para instanciar el tokenizador. Si no ha cargado GPT-2 antes, la primera vez que lo use, la biblioteca Transformers descargará el archivo de preentrenamiento de Huggingface, asegúrese de que la red esté desbloqueada. Especialmente para los amigos que usan Docker o WSL, asegúrese de que su configuración de red sea correcta, de lo contrario, la carga fallará.

tokenizer = GPT2Tokenizer.from_pretrained('gpt2')



train_dataset = TextDataset(tokenizer=tokenizer,

                            file_path="train.txt",     

                            block_size=60)

3. Cargar datos para el modelo de lenguaje

Aquí, utilizamos directamente la clase DataCollatorForLanguageModeling en la biblioteca Transformer para instanciar los datos y ya no necesitamos hacer trabajo adicional.

data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)

A continuación, carguemos el modelo previamente entrenado y ajustémoslo.


Ajuste fino del modelo

Una vez que hemos preparado nuestro conjunto de datos para el ajuste, podemos comenzar el proceso de ajuste de nuestro gran modelo de lenguaje.

1. Carga del modelo preentrenado

El primer paso para ajustar un modelo es cargar el modelo previamente entrenado. Ya hemos cargado el tokenizador, así que aquí solo necesitamos cargar el modelo:

model = GPT2LMHeadModel.from_pretrained('gpt2')

A continuación, debemos establecer los parámetros de entrenamiento. La biblioteca Transformers también nos proporciona una clase muy conveniente para implementar esta función, sin necesidad de archivos de configuración adicionales:

training_args = TrainingArguments(output_dir="./gpt2_stock",     

                                  overwrite_output_dir=True,    

                                  num_train_epochs=3,     

                                  per_device_train_batch_size=32,   

                                  save_steps=10_000,    

                                  save_total_limit=2,

                                  load_best_model_at_end=True,

                                  )

2. Inicialización de parámetros de ajuste fino

Al crear una instancia de TrainingArguments, utilizamos los siguientes parámetros:

  • output_dir: La ubicación para guardar los resultados de las predicciones y los puntos de control. Definimos la carpeta “gpt2_stock” en el directorio actual como la ruta de salida.
  • overwrite_output_dir: Si desea sobrescribir el archivo de salida. Elegimos sobrescribir.
  • num_train_epochs: El número de épocas de entrenamiento. Elegimos 3 épocas.
  • per_device_train_batch_size: El tamaño del lote de entrenamiento. Elegimos 32, que como presentamos anteriormente, es mejor que sea una potencia de 2.
  • save_steps=10_000: La cantidad de pasos de actualización antes de guardar dos puntos de control si save_strategy="steps". Debe ser un número entero o flotante en el rango (0 o 1). Si es menor que 1, se interpretará como una proporción del total de pasos de entrenamiento.
  • save_total_limit: Si se pasa un valor, limitará la cantidad total de puntos de control. Elimina los puntos de control más antiguos en output_dir.
  • load_best_model_at_end: Si se debe cargar el mejor modelo durante el proceso de entrenamiento, en lugar de utilizar los pesos del modelo en el último paso del entrenamiento.

Hay muchos parámetros que no configuramos y usamos los valores predeterminados porque solo somos un ejemplo, por lo que no definimos esta clase en detalle, por ejemplo:

  • deepspeed: Si se debe utilizar Deepspeed para acelerar el entrenamiento.
  • eval_steps: Número de pasos de actualización entre dos evaluaciones.
  • dataloader_pin_memory: Si desea anclar la memoria en los cargadores de datos o no.

Puede ver que esta clase TrainingArguments es muy poderosa, casi incluye la mayoría de los parámetros de entrenamiento, es muy conveniente de usar y es muy recomendable que los lectores echen un vistazo a la documentación oficial.

3. Ajuste fino

Ahora volvamos a nuestro proceso de ajuste fino, y ahora podemos definir nuestro proceso de ajuste fino. Hemos detallado el proceso de entrenamiento del modelo de lenguaje en el artículo anterior. El proceso de ajuste fino no es muy diferente del proceso de entrenamiento. Creo que los lectores ya están muy familiarizados con esto, por lo que el ejemplo de este artículo ya no define en detalle el proceso de ajuste del modelo de lenguaje, sino que utiliza directamente la clase Trainer proporcionada en la biblioteca Transformer para implementarlo. Ahora pasamos el modelo, training_args, data_collator, train_dataset que hemos definido como parámetros a la clase Trainer para instanciar Trainer:

trainer = Trainer(model=model,

                  args=training_args,

                  data_collator=data_collator,

                  train_dataset=train_dataset,)

La clase Trainer también tiene otros parámetros que no configuramos, como las devoluciones de llamadas importantes: puede usar devoluciones de llamadas para personalizar el comportamiento del bucle de entrenamiento, estas devoluciones de llamadas pueden verificar el estado del bucle de entrenamiento (para informes de progreso, registro en TensorBoard u otras plataformas de ML, etc.) y tomar decisiones (como detención anticipada, etc.). La razón por la que no los establecemos en este artículo es porque solo somos un ejemplo y las configuraciones de parámetros del modelo en el proceso de ajuste fino son relativamente conservadoras. Si desea que su modelo funcione mejor, recuerde que esta opción no debe ignorarse. Llame al método tran() de la clase Trainer instanciada y podrá ejecutar directamente el proceso de ajuste fino:

trainer.train()

Una vez finalizado el entrenamiento, guarde el modelo, de modo que podamos usar directamente el método from_pretrained() para cargar el modelo ajustado cuando inferimos:

trainer.save_model("./gpt2_stock")

A continuación, hagamos una inferencia para comprobar si el ajuste fino es efectivo:

prompt = ' '.join(map(str, df.iloc[:,1:20].values[-1])) 

generated = tokenizer.decode(model.generate(tokenizer.encode(prompt, return_tensors='pt').to("cuda"), 
		do_sample=True, 
		max_length=200)[0], 
		skip_special_tokens=True)

print(f"test the model:{generated}")

En esta parte del código, “prompt = ' '.join(map(str, df.iloc[:,1:20].values[-1]))” convierte la última línea de nuestro conjunto de datos en un formato de cadena (string). “tokenizer.encode(prompt, return_tensors='pt')” Esta parte del código convierte el texto de entrada (prompt) en un formato que el modelo pueda entender, es decir, convierte el texto en una serie de tokens. “return_tensors='pt'” indica que el tipo de datos devuelto es un tensor de PyTorch. “do_sample=True” indica que se utiliza un muestreo aleatorio en el proceso de generación y “max_length=200” limita la longitud máxima del texto generado. Ahora echemos un vistazo a los resultados de todo el código ejecutándose: 

ft


Se puede observar que el modelo preentrenado y ajustado produjo con éxito los resultados que queríamos.

El código completo es el siguiente, el nombre del script en el archivo adjunto es “Fin-tuning.py”:

import pandas as pd
from transformers import GPT2LMHeadModel, GPT2Tokenizer, GPT2Config
from transformers import TextDataset, DataCollatorForLanguageModeling
from transformers import Trainer, TrainingArguments
import torch

dvc='cuda' if torch.cuda.is_available() else 'cpu'

print(dvc)

df = pd.read_csv('llm_data.csv')

sentences = [' '.join(map(str, prices)) for prices in df.iloc[:-10,1:].values]
with open('train.txt', 'w') as f:
    for sentence in sentences:
        f.write(sentence + '\n')

tokenizer = GPT2Tokenizer.from_pretrained('gpt2')

train_dataset = TextDataset(tokenizer=tokenizer,
                            file_path="train.txt", 
                            block_size=60)

data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)



model = GPT2LMHeadModel.from_pretrained('gpt2')

training_args = TrainingArguments(output_dir="./gpt2_stock",     
                                  overwrite_output_dir=True,    
                                  num_train_epochs=3,     
                                  per_device_train_batch_size=32,   
                                  save_steps=10_000,    
                                  save_total_limit=2,
                                  load_best_model_at_end=True,
                                  )

trainer = Trainer(model=model,
                  args=training_args,
                  data_collator=data_collator,
                  train_dataset=train_dataset,)
trainer.train()
trainer.save_model("./gpt2_stock")

prompt = ' '.join(map(str, df.iloc[:,1:20].values[-1])) 
generated = tokenizer.decode(model.generate(tokenizer.encode(prompt, return_tensors='pt').to(dvc), do_sample=True, max_length=200)[0], skip_special_tokens=True)
print(f"test the model:{generated}")


Pruebas

Una vez completado el proceso de ajuste, todavía necesitamos probar el modelo, verificar la brecha entre la salida del modelo y el valor verdadero original, y el método más simple es calcular el error cuadrático medio (MSE) entre el valor verdadero y el valor predicho.

Ahora recreamos un script para implementar el proceso de prueba, primero importamos las bibliotecas requeridas, cargamos el modelo GPT-2 que hemos ajustado y los datos:

import pandas as pd

from transformers import GPT2LMHeadModel, GPT2Tokenizer, GPT2Config

from sklearn.metrics import mean_squared_error

import torch

import numpy as np

df = pd.read_csv('llm_data.csv')

dvc='cuda' if torch.cuda.is_available() else 'cpu'

model = GPT2LMHeadModel.from_pretrained('./gpt2_stock')

tokenizer = GPT2Tokenizer.from_pretrained('gpt2')

Este proceso no es muy diferente de nuestro proceso de ajuste fino, excepto que la ruta del modelo se cambia a la ruta donde guardamos el peso del modelo durante el ajuste fino. Después de cargar el modelo y el tokenizador, necesitamos procesar el valor verdadero y el valor predicho después de la inferencia. Esta parte también es la misma que los pasos de nuestro script de ajuste fino:

prompt = ' '.join(map(str, df.iloc[:,1:20].values[-1]))

generated = tokenizer.decode(model.generate(tokenizer.encode(prompt, return_tensors='pt'), do_sample=True, max_length=200)[0], skip_special_tokens=True)

Ahora tomamos los últimos 40 precios de cierre de la última línea del conjunto de datos como el valor verdadero, y convertimos el valor verdadero y el valor predicho en una forma de lista, y la longitud es consistente:

true_prices= df.iloc[-1:,21:].values.tolist()[0]

generated_prices=generated.split('\n')[0]

generated_prices=list(map(float,generated_prices.split()))

generated_prices=generated_prices[0:len(true_prices)]

def trim_lists(a, b):

    min_len = min(len(a), len(b))

    return a[:min_len], b[:min_len]

true_prices,generated_prices=trim_lists(true_prices,generated_prices)

Para mantener el valor verdadero y el valor predicho con la misma longitud, necesitamos cortar otra lista de acuerdo con la longitud de la lista más pequeña, por lo que definimos "trim_lists(a, b)" para completar esta tarea. Luego imprimimos el valor real y el valor previsto para ver si cumplen las expectativas:

print(f"true_prices:{true_prices}")

print(f"generated_prices:{generated_prices}")

Puedes ver que los resultados son los siguientes:

true_prices: [0.6119, 0.61197, 0.61201, 0.61242, 0.61237, 0.6123, 0.61229, 0.61242, 0.61212, 0.61197, 0.61201, 0.61213, 0.61212, 

0.61206, 0.61203, 0.61206, 0.6119, 0.61193, 0.61191, 0.61202, 0.61197, 0.6121, 0.61211, 0.61214, 0.61203, 0.61203, 0.61213, 0.61218,

 0.61227, 0.61226, 0.61227, 0.61231, 0.61228, 0.61227, 0.61233, 0.61211, 0.6121, 0.6121, 0.61195, 0.61196]

generated_prices:[0.61163, 0.61162, 0.61191, 0.61195, 0.61209, 0.61231, 0.61224, 0.61207, 0.61187, 0.61184, 0.6119, 0.61169, 0.61168, 

0.61162, 0.61181, 0.61184, 0.61184, 0.6118, 0.61176, 0.61169, 0.61191, 0.61195, 0.61204, 0.61188, 0.61205, 0.61188, 0.612, 0.61208, 

0.612, 0.61192, 0.61168, 0.61165, 0.61164, 0.61179, 0.61183, 0.61192, 0.61168, 0.61175, 0.61169, 0.61162]

A continuación, podemos calcular su error cuadrático medio (MSE) y luego imprimir los resultados para comprobarlo:

mse = mean_squared_error(true_prices, generated_prices)

print('MSE:', mse)

El resultado MSE es: 2.1906250000000092e-07.

Como puedes ver, el MSE es muy pequeño, pero ¿significa esto realmente que nuestro modelo es muy preciso? ¡No olvide que nuestros datos originales tenían un valor muy pequeño! Por lo tanto, aunque el MSE es muy pequeño, debido a que nuestros valores originales también son relativamente pequeños, el MSE no puede reflejar con precisión la precisión del modelo en este momento. Necesitamos calcular además el error cuadrático medio (RMSE) y el error cuadrático medio normalizado (NRMSE) entre el valor previsto y el valor original, para determinar además el tamaño del error de predicción en relación con el rango de valores observados, para determinar además la precisión del modelo:

rmse=np.sqrt(mse)

nrmse=rmse/(np.max(true_prices)-np.min(generated_prices))

print(f"RMSE:{rmse},NRMSE:{nrmse}")

El resultado es:

  • RMSE:0.00046804113067122735
  • NRMSE:0.5850514133390986

t

Podemos observar que aunque los valores MSE y RMSE son muy pequeños, el valor NRMSE es 0.5850514133390986, lo que significa que el error de predicción representa aproximadamente el 58,5% del rango de valores observados. Esto demuestra que, aunque el valor absoluto de RMSE es muy pequeño, en relación con el rango de valores observados, el error de predicción sigue siendo relativamente grande.

Entonces, ¿cómo podemos hacer que nuestro modelo sea más preciso? Aquí hay algunas opciones:

  1. Aumentar las épocas durante el ajuste fino
  2. Aumentar la cantidad de datos
  3. Optimizar adecuadamente los parámetros de ajuste fino
  4. Reemplazar con un modelo a mayor escala

Estos métodos no son difíciles de implementar, este artículo no los verificará uno por uno, puedes elegir uno o varios de ellos según tus propias ideas para practicar, ¡creo que los resultados definitivamente serán mucho mejores que el ejemplo de este artículo!

El código completo, el nombre del script en el archivo adjunto es test.py:

import pandas as pd
from transformers import GPT2LMHeadModel, GPT2Tokenizer, GPT2Config
from sklearn.metrics import mean_squared_error
import torch
import numpy as np

df = pd.read_csv('llm_data.csv')
dvc='cuda' if torch.cuda.is_available() else 'cpu'

model = GPT2LMHeadModel.from_pretrained('./gpt2_stock')
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')

prompt = ' '.join(map(str, df.iloc[:,1:20].values[-1]))
generated = tokenizer.decode(model.generate(tokenizer.encode(prompt, return_tensors='pt'), do_sample=True, max_length=200)[0], skip_special_tokens=True)
true_prices= df.iloc[-1:,21:].values.tolist()[0]
generated_prices=generated.split('\n')[0]
generated_prices=list(map(float,generated_prices.split()))
generated_prices=generated_prices[0:len(true_prices)]
def trim_lists(a, b):
    min_len = min(len(a), len(b))
    return a[:min_len], b[:min_len]
true_prices,generated_prices=trim_lists(true_prices,generated_prices)
print(f"true_prices:{true_prices}")
print(f"generated_prices:{generated_prices}")
mse = mean_squared_error(true_prices, generated_prices)
print('MSE:', mse)
rmse=np.sqrt(mse)
nrmse=rmse/(np.max(true_prices)-np.min(generated_prices))
print(f"RMSE:{rmse},NRMSE:{nrmse}")


Conclusión

Este artículo presenta principalmente los requisitos previos para utilizar modelos de lenguaje grandes para estrategias comerciales, es decir, la salida del modelo de lenguaje grande debe cumplir con los requisitos de nuestra estrategia comercial. Discutimos algunos métodos técnicos que pueden completar esta tarea. Aunque debido a limitaciones de espacio, no proporcionamos ejemplos de código relevantes de todos los métodos, solo se da un ejemplo de ajuste fino de GPT-2 con parámetros completos (por supuesto, este conjunto de datos no es aplicable a todos los métodos de ajuste fino mencionados en el texto, pero los ejemplos detallados en los artículos posteriores brindarán el método de creación del conjunto de datos que coincide con el método). Pero no te preocupes, seleccionaré algunos métodos representativos en los siguientes artículos para proporcionar ejemplos de código relevantes y ejemplos de EAs que coincidan con los ejemplos. En cuanto a la tecnología RAG y la tecnología Agent que simplemente se mencionan en el texto, también habrá artículos especiales para brindarle debates detallados e implementaciones de código relacionadas.

¿Estás listo? ¡Nos vemos en el próximo artículo!


Referencias

https://alexqdh.github.io/posts/2183061656/

http://note.iawen.com/note/llm/finetune

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/13497

Archivos adjuntos |
train.txt (1121.04 KB)
llm_data.csv (1139.04 KB)
Fine-tuning.py (1.73 KB)
test.py (1.18 KB)
Gestión de Riesgo (Parte 2): Implementando el Cálculo de Lotes en una Interfaz Gráfica Gestión de Riesgo (Parte 2): Implementando el Cálculo de Lotes en una Interfaz Gráfica
En este artículo exploraremos cómo mejorar y aplicar de manera más efectiva los conceptos abordados en el artículo anterior, utilizando las poderosas librerías de controles gráficos de MQL5. Te guiaré paso a paso en la creación de una interfaz gráfica completamente funcional, explicando el plan de diseño detrás de ella, así como el propósito y funcionamiento de cada método empleado. Además, al final del artículo, pondremos a prueba el panel que desarrollaremos, asegurándonos de que funcione correctamente y cumpla con los objetivos planteados.
Aprendizaje automático y Data Science (Parte 28): Predicción de múltiples futuros para el EURUSD mediante IA Aprendizaje automático y Data Science (Parte 28): Predicción de múltiples futuros para el EURUSD mediante IA
Es una práctica común que muchos modelos de Inteligencia Artificial predigan un único valor futuro. Sin embargo, en este artículo profundizaremos en la poderosa técnica de utilizar modelos de aprendizaje automático para predecir múltiples valores futuros. Este enfoque, conocido como pronóstico de múltiples pasos, nos permite predecir no sólo el precio de cierre de mañana, sino también el de pasado mañana y más allá. Al dominar la previsión en varios pasos, los operadores y los científicos de datos pueden obtener conocimientos más profundos y tomar decisiones más informadas, mejorando significativamente sus capacidades de predicción y planificación estratégica.
Construya Asesores Expertos Auto-Optimizables con MQL5 y Python (Parte II): Ajuste de redes neuronales profundas Construya Asesores Expertos Auto-Optimizables con MQL5 y Python (Parte II): Ajuste de redes neuronales profundas
Los modelos de aprendizaje automático vienen con varios parámetros ajustables. En esta serie de artículos, exploraremos cómo personalizar sus modelos de IA para que se adapten a su mercado específico utilizando la biblioteca SciPy.
Reimaginando las estrategias clásicas (Parte III): Predicción de máximos crecientes y mínimos decrecientes Reimaginando las estrategias clásicas (Parte III): Predicción de máximos crecientes y mínimos decrecientes
En esta serie de artículos, analizaremos empíricamente las estrategias comerciales clásicas para ver si podemos mejorarlas utilizando IA. En la discusión de hoy, intentamos predecir máximos más altos y mínimos más bajos utilizando el modelo de análisis discriminante lineal.