Ajuste fino de Alpaca y LLaMA en un conjunto de datos personalizado

Este artículo presentará el ajuste fino de Alpaca y LLaMA en una máquina local usando LoRa, recorreremos todo el proceso de ajuste fino de Alpaca LoRa en un conjunto de datos específico, este artículo cubrirá el procesamiento de datos, el entrenamiento de modelos y el uso del lenguaje natural popular Se evaluaron bibliotecas de procesamiento como Transformers y hugs Face). También cubre cómo implementar y probar modelos usando la aplicación Grado.

configuración

Primero, el repositorio de GitHub alpaca-lora1 proporciona un script (finetune.py) para entrenar el modelo. En este artículo, tomaremos este código y lo haremos funcionar sin problemas en el entorno de Google Colab.

Primero instale las dependencias necesarias:

 !pip install -U pip
 !pip install accelerate==0.18.0
 !pip install appdirs==1.4.4
 !pip install bitsandbytes==0.37.2
 !pip install datasets==2.10.1
 !pip install fire==0.5.0
 !pip install git+https://github.com/huggingface/peft.git
 !pip install git+https://github.com/huggingface/transformers.git
 !pip install torch==2.0.0
 !pip install sentencepiece==0.1.97
 !pip install tensorboardX==2.6
 !pip install gradio==3.23.0

Después de instalar las dependencias, continúe e importe todas las bibliotecas necesarias y configure los ajustes para el trazado de matplotlib:

 import transformers
 import textwrap
 from transformers import LlamaTokenizer, LlamaForCausalLM
 import os
 import sys
 from typing import List
 
 from peft import (
     LoraConfig,
     get_peft_model,
     get_peft_model_state_dict,
     prepare_model_for_int8_training,
 )
 
 import fire
 import torch
 from datasets import load_dataset
 import pandas as pd
 
 import matplotlib.pyplot as plt
 import matplotlib as mpl
 import seaborn as sns
 from pylab import rcParams
 
 %matplotlib inline
 sns.set(rc={'figure.figsize':(10, 7)})
 sns.set(rc={'figure.dpi':100})
 sns.set(style='white', palette='muted', font_scale=1.2)
 
 DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
 DEVICE

datos

Aquí usamos el conjunto de datos de sentimiento de tweets de BTC4, que está disponible en Kaggle y contiene aproximadamente 50 000 tweets relacionados con Bitcoin. Para limpiar los datos, se eliminaron todos los tuits que comenzaban con "retuits" o contenían enlaces.

Use Pandas para cargar CSV:

 df = pd.read_csv("bitcoin-sentiment-tweets.csv")
 df.head()

El conjunto de datos limpio tiene alrededor de 1900 tweets.

Las etiquetas de opinión se representan numéricamente, donde -1 representa una opinión negativa, 0 representa una opinión neutral y 1 representa una opinión positiva. Veamos su distribución:

 df.sentiment.value_counts()
 
 
 # 0.0    860
 # 1.0    779
 # -1.0    258
 # Name: sentiment, dtype: int64

La cantidad de datos es casi la misma. Aunque hay menos comentarios negativos, puede tratarse simplemente como datos equilibrados:

 df.sentiment.value_counts().plot(kind='bar');

Cree un conjunto de datos JSON

El formato dataset5 en el repositorio original de Alpaca consta de un archivo JSON con una lista de objetos con directivas, cadenas de entrada y salida.

Convirtamos el DF de Pandas en un archivo JSON que siga el formato del repositorio original de Alpaca:

 def sentiment_score_to_name(score: float):
     if score > 0:
         return "Positive"
     elif score < 0:
         return "Negative"
     return "Neutral"
 
 dataset_data = [
     {
         "instruction": "Detect the sentiment of the tweet.",
         "input": row_dict["tweet"],
         "output": sentiment_score_to_name(row_dict["sentiment"])
     }
     for row_dict in df.to_dict(orient="records")
 ]
 
 dataset_data[0]

El resultado es el siguiente:

 {
   "instruction": "Detect the sentiment of the tweet.",
   "input": "@p0nd3ea Bitcoin wasn't built to live on exchanges.",
   "output": "Positive"
 }

Luego simplemente guarde el archivo JSON resultante para que pueda usarlo para entrenar el modelo más adelante:

 import json
 with open("alpaca-bitcoin-sentiment-dataset.json", "w") as f:
    json.dump(dataset_data, f)

peso del modelo

Aunque los pesos del modelo Llama original no están disponibles, se filtraron y posteriormente se adaptaron para su uso en la biblioteca HuggingFace Transformers. Usaremos decapoda-research6:

 BASE_MODEL = "decapoda-research/llama-7b-hf"
 
 model = LlamaForCausalLM.from_pretrained(
     BASE_MODEL,
     load_in_8bit=True,
     torch_dtype=torch.float16,
     device_map="auto",
 )
 
 tokenizer = LlamaTokenizer.from_pretrained(BASE_MODEL)
 
 tokenizer.pad_token_id = (
     0  # unk. we want this to be different from the eos token
 )
 tokenizer.padding_side = "left"

Este código carga un modelo Llama previamente entrenado usando la clase LlamaForCausalLM de la biblioteca de Transformers. El parámetro load_in_8bit=True carga el modelo usando cuantificación de 8 bits para reducir el uso de memoria y mejorar la velocidad de inferencia.

El código también carga un tokenizador para el mismo modelo de Llama utilizando la clase LlamaTokenizer y establece algunas propiedades adicionales para completar el token. Específicamente, establece pad_token_id en 0 para un token desconocido y padding_side en "left" para rellenar la secuencia de la izquierda.

Carga de conjunto de datos

Ahora que hemos cargado el modelo y el tokenizador, el siguiente paso es cargar el archivo JSON previamente guardado, utilizando la función load_dataset() de la biblioteca de conjuntos de datos HuggingFace:

 data = load_dataset("json", data_files="alpaca-bitcoin-sentiment-dataset.json")
 data["train"]

El resultado es el siguiente:

 Dataset({
     features: ['instruction', 'input', 'output'],
     num_rows: 1897
 })

A continuación, debemos crear sugerencias a partir del conjunto de datos cargado y etiquetarlas:

 def generate_prompt(data_point):
     return f"""Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.  # noqa: E501
 ### Instruction:
 {data_point["instruction"]}
 ### Input:
 {data_point["input"]}
 ### Response:
 {data_point["output"]}"""
 
 
 def tokenize(prompt, add_eos_token=True):
     result = tokenizer(
         prompt,
         truncation=True,
         max_length=CUTOFF_LEN,
         padding=False,
         return_tensors=None,
     )
     if (
         result["input_ids"][-1] != tokenizer.eos_token_id
         and len(result["input_ids"]) < CUTOFF_LEN
         and add_eos_token
     ):
         result["input_ids"].append(tokenizer.eos_token_id)
         result["attention_mask"].append(1)
 
     result["labels"] = result["input_ids"].copy()
 
     return result
 
 def generate_and_tokenize_prompt(data_point):
     full_prompt = generate_prompt(data_point)
     tokenized_full_prompt = tokenize(full_prompt)
     return tokenized_full_prompt

La primera función generate_prompt toma un punto de datos del conjunto de datos y genera un mensaje combinando instrucciones, valores de entrada y salida. La segunda función tokenize toma las sugerencias generadas y las tokeniza utilizando el tokenizador definido anteriormente. También agrega un marcador de fin de secuencia a la secuencia de entrada y establece la etiqueta para que sea la misma que la secuencia de entrada. La tercera función generate_and_tokenize_prompt combina las dos primeras funciones para generar y tokenizar el aviso.

El paso final en la preparación de datos es dividir el conjunto de datos en conjuntos separados de entrenamiento y validación:

 train_val = data["train"].train_test_split(
     test_size=200, shuffle=True, seed=42
 )
 train_data = (
     train_val["train"].map(generate_and_tokenize_prompt)
 )
 val_data = (
     train_val["test"].map(generate_and_tokenize_prompt)
 )

También necesitamos codificar los datos y obtener 200 muestras como conjunto de validación. La función generate_and_tokenize_prompt() se aplica a cada ejemplo en los conjuntos de entrenamiento y validación, generando avisos tokenizados.

tren

El proceso de entrenamiento requiere varios parámetros, principalmente del script de ajuste en el repositorio original:

 LORA_R = 8
 LORA_ALPHA = 16
 LORA_DROPOUT= 0.05
 LORA_TARGET_MODULES = [
     "q_proj",
     "v_proj",
 ]
 
 BATCH_SIZE = 128
 MICRO_BATCH_SIZE = 4
 GRADIENT_ACCUMULATION_STEPS = BATCH_SIZE // MICRO_BATCH_SIZE
 LEARNING_RATE = 3e-4
 TRAIN_STEPS = 300
 OUTPUT_DIR = "experiments"

Lo siguiente prepara el modelo para el entrenamiento:

 model = prepare_model_for_int8_training(model)
 config = LoraConfig(
     r=LORA_R,
     lora_alpha=LORA_ALPHA,
     target_modules=LORA_TARGET_MODULES,
     lora_dropout=LORA_DROPOUT,
     bias="none",
     task_type="CAUSAL_LM",
 )
 model = get_peft_model(model, config)
 model.print_trainable_parameters()
 
 #trainable params: 4194304 || all params: 6742609920 || trainable%: 0.06220594176090199

Usamos el algoritmo LORA para inicializar y preparar el modelo para el entrenamiento, y la cuantificación reduce el tamaño del modelo y el uso de la memoria sin reducir significativamente la precisión.

LoraConfig7 es una clase que especifica hiperparámetros para el algoritmo LORA, como la fuerza de regularización (lora_alpha), la probabilidad de abandono (lora_dropout) y los módulos de destino para comprimir (target_modules).

Luego puede usar directamente la biblioteca de Transformers para el entrenamiento:

 training_arguments = transformers.TrainingArguments(
     per_device_train_batch_size=MICRO_BATCH_SIZE,
     gradient_accumulation_steps=GRADIENT_ACCUMULATION_STEPS,
     warmup_steps=100,
     max_steps=TRAIN_STEPS,
     learning_rate=LEARNING_RATE,
     fp16=True,
     logging_steps=10,
     optim="adamw_torch",
     evaluation_strategy="steps",
     save_strategy="steps",
     eval_steps=50,
     save_steps=50,
     output_dir=OUTPUT_DIR,
     save_total_limit=3,
     load_best_model_at_end=True,
     report_to="tensorboard"
 )

Este código crea un objeto TrainingArguments que especifica las distintas configuraciones e hiperparámetros usados ​​para entrenar el modelo. Éstas incluyen:

  • gradiente_accumulación_pasos: número de pasos de actualización para acumular gradientes antes de realizar actualizaciones/hacia atrás.
  • warmup_steps: el número de pasos de calentamiento para el optimizador.
  • max_steps: el número total de entrenamientos a realizar.
  • learning_rate: tasa de aprendizaje.
  • fp16: utiliza precisión de 16 bits para el entrenamiento.

DataCollatorForSeq2Seq es una clase en la biblioteca de transformadores que crea un lote de secuencias de entrada/salida para modelos de secuencia a secuencia (seq2seq). En este código, se crea una instancia del objeto DataCollatorForSeq2Seq con los siguientes parámetros:

 data_collator = transformers.DataCollatorForSeq2Seq(
     tokenizer, pad_to_multiple_of=8, return_tensors="pt", padding=True
 )

pad_to_multiple_of: un número entero que representa la longitud máxima de la secuencia, redondeado al múltiplo más cercano de este valor.

relleno: un valor booleano que indica si se debe rellenar la secuencia hasta la longitud máxima especificada.

Lo anterior es toda la preparación del código para el entrenamiento, lo siguiente es el entrenamiento

 trainer = transformers.Trainer(
     model=model,
     train_dataset=train_data,
     eval_dataset=val_data,
     args=training_arguments,
     data_collator=data_collator
 )
 model.config.use_cache = False
 old_state_dict = model.state_dict
 model.state_dict = (
     lambda self, *_, **__: get_peft_model_state_dict(
         self, old_state_dict()
     )
 ).__get__(model, type(model))
 
 model = torch.compile(model)
 
 trainer.train()
 model.save_pretrained(OUTPUT_DIR)

Después de instanciar el entrenador, el código establece use_cache en False en la configuración del modelo y crea un state_dict para el modelo mediante la función get_peft_model_state_dict(), que prepara el modelo para el entrenamiento con un algoritmo de baja precisión.

Luego llame a la función torch.compile() en el modelo, que compila el gráfico computacional del modelo y lo prepara para el entrenamiento con PyTorch 2.

El proceso de formación duró unas 2 horas en la A100. Echemos un vistazo a los resultados en Tensorboard:

La pérdida de capacitación y la pérdida de evaluación muestran una tendencia constante a la baja. Parece que nuestro ajuste fino está funcionando.

Si quieres subir el modelo a Hugging Face, puedes usar el siguiente código,

 from huggingface_hub import notebook_login
 
 notebook_login()
 model.push_to_hub("curiousily/alpaca-bitcoin-tweets-sentiment", use_auth_token=True)

razonamiento

Podemos usar el script generate.py para probar el modelo:

 !git clone https://github.com/tloen/alpaca-lora.git
 %cd alpaca-lora
 !git checkout a48d947

La aplicación de gradio iniciada por nuestro script

 !python generate.py \
     --load_8bit \
     --base_model 'decapoda-research/llama-7b-hf' \
     --lora_weights 'curiousily/alpaca-bitcoin-tweets-sentiment' \
     --share_gradio

La sencilla interfaz es la siguiente:

Resumir

Hemos ajustado con éxito el modelo Llama usando el método LoRa y también demostramos cómo usarlo en la aplicación Gradio.

Si está interesado en este artículo, consulte el texto original:

https://evitar.overfit.cn/post/34b6eaf7097a4929b9aab7809f3cfeaa

Supongo que te gusta

Origin blog.csdn.net/m0_46510245/article/details/131759494
Recomendado
Clasificación