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