Perfeziona un modello Mistral-7b con l’ottimizzazione delle preferenze dirette |  di Maxime Labonne |  Gennaio 2024

 | Intelligenza-Artificiale

Migliora le prestazioni dei tuoi modelli ottimizzati supervisionati

Immagine dell’autore

I Large Language Models (LLM) preaddestrati possono solo eseguire la previsione del token successivo, rendendoli incapaci di rispondere alle domande. Questo è il motivo per cui questi modelli base vengono poi perfezionati su coppie di istruzioni e risposte per fungere da utili assistenti. Tuttavia, questo processo può ancora essere imperfetto: gli LLM ottimizzati possono essere distorti, tossici, dannosi, ecc. È qui che entra in gioco l’apprendimento per rinforzo dal feedback umano (RLHF).

RLHF fornisce diverse risposte al LLM, che sono classificate in base al comportamento desiderato (utilità, tossicità, ecc.). Il modello impara a fornire la risposta migliore tra questi candidati, imitando quindi il comportamento che vogliamo instillare. Spesso visto come un modo per censurare i modelli, questo processo è recentemente diventato popolare per migliorare le prestazioni, come mostrato in neural-chat-7b-v3–1.

In questo articolo creeremo NeuralHermes-2.5mediante messa a punto OpenHermes-2.5 utilizzando una tecnica simile a RLHF: Direct Preference Optimization (DPO). A questo scopo, introdurremo un set di dati sulle preferenze, descriveremo come funziona l’algoritmo DPO e lo applicheremo al nostro modello. Vedremo che migliora significativamente le prestazioni del modello base nella classifica Open LLM.

Come al solito, il codice è disponibile su GitHub E Google Co.

I set di dati sulle preferenze non sono standardizzati, ma in genere consistono in una raccolta di risposte classificate da esseri umani. Questa classificazione è essenziale, poiché il processo RLHF mette a punto gli LLM per produrre la risposta preferita. Ecco un esempio di Antropico/hh-rlhfun set di dati sulle preferenze popolare:

Immagine dell’autore

La struttura del set di dati è semplice: per ogni riga c’è una risposta scelta (preferita) e una risposta rifiutata. L’obiettivo di RLHF è guidare il modello per produrre la risposta preferita.

I set di dati sulle preferenze sono notoriamente costosi e difficili da realizzare, poiché richiedono la raccolta di feedback manuale da parte degli esseri umani. Questo feedback è anche soggettivo e può facilmente essere distorto verso risposte sicure (ma sbagliate) o contraddirsi (annotatori diversi hanno valori diversi). Nel corso del tempo sono state proposte diverse soluzioni per affrontare questi problemi, come la sostituzione del feedback umano con il feedback dell’intelligenza artificiale (RLAIF).

Questi set di dati tendono anche ad essere molto più piccoli dei set di dati di fine tuning. Per illustrare questo, l’eccellente neural-chat-7b-v3–1 (miglior LLM 7B sul Apri la classifica LLM quando è stato rilasciato) utilizza 518.000 campioni per la messa a punto (Open-Orca/SlimOrca) ma solo 12,9k campioni per RLHF (Intel/orca_dpo_pairs). In questo caso, gli autori hanno generato risposte con GPT-4/3.5 per creare le risposte preferite, e con Lama 2 13b chiacchierata per creare le risposte rifiutate. È un modo intelligente per aggirare il feedback umano e fare affidamento solo su modelli con diversi livelli di prestazioni.

Sebbene il concetto di RLHF sia stato utilizzato per molto tempo nella robotica, è stato reso popolare per i LLM nel documento di OpenAI Ottimizzazione dei modelli linguistici a partire dalle preferenze umane. In questo articolo, gli autori presentano un quadro in cui un modello di ricompensa viene addestrato per approssimare il feedback umano. Questo modello di ricompensa viene quindi utilizzato per ottimizzare la politica del modello perfezionato utilizzando il Ottimizzazione della politica prossimale (PPO) algoritmo.

Immagine dell’autore

Il concetto centrale del PPO ruota attorno alla realizzazione di aggiornamenti più piccoli e incrementali alla politica, poiché aggiornamenti più grandi possono portare a instabilità o soluzioni non ottimali. Per esperienza, questa tecnica è purtroppo ancora instabile (le perdite divergono), difficile da riprodurre (numerosi iperparametri, sensibili ai semi casuali) e computazionalmente costosa.

È qui che entra in gioco la Direct Preference Optimization (DPO). Il DPO semplifica il controllo trattando l’attività come un problema di classificazione. Concretamente utilizza due modelli: l’ modello addestrato (o modello politico) e una copia di esso denominata modello di riferimento. Durante l’addestramento, l’obiettivo è assicurarsi che il modello addestrato produca probabilità più elevate per le risposte preferite rispetto al modello di riferimento. Al contrario, vogliamo anche che produca probabilità inferiori per le risposte rifiutate. Significa che stiamo penalizzando il LLM per le risposte sbagliate e premiandolo per quelle buone.

Immagine dell’autore

Utilizzando lo stesso LLM come modello di ricompensa e impiegando obiettivi binari di entropia incrociata, DPO allinea in modo efficiente gli output del modello con le preferenze umane senza la necessità di campionamenti estesi, adattamento del modello di ricompensa o intricati aggiustamenti degli iperparametri. Il risultato è un processo più stabile, più efficiente e meno impegnativo dal punto di vista computazionale.

In questo esempio, metteremo a punto l’eccellente OpenHermes-2.5-Mistral-7Bche è un modello Mistral-7b che è stato solo supervisionato e messo a punto. A tal fine, utilizzeremo il file Intel/orca_dpo_pairs set di dati per allineare il nostro modello e migliorarne le prestazioni. Chiamiamo questo nuovo modello NeuralHermes-2.5-Mistral-7B.

Il primo passo consiste nell’installare le librerie richieste come segue.

pip install -q datasets trl peft bitsandbytes sentencepiece wandb

Una volta terminato, possiamo importare le librerie. Sto anche utilizzando la scheda segreti in Google Colab per archiviare il mio token Hugging Face.

import os
import gc
import torch

import transformers
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, BitsAndBytesConfig
from datasets import load_dataset
from peft import LoraConfig, PeftModel, get_peft_model, prepare_model_for_kbit_training
from trl import DPOTrainer
import bitsandbytes as bnb
from google.colab import userdata
import wandb

# Defined in the secrets tab in Google Colab
hf_token = userdata.get('huggingface')
wb_token = userdata.get('wandb')
wandb.login(key=wb_token)

model_name = "teknium/OpenHermes-2.5-Mistral-7B"
new_model = "NeuralHermes-2.5-Mistral-7B"

OpenHermes-2.5-Mistral-7B utilizza un modello di chat specifico, chiamato ChatML. Ecco un esempio di conversazione formattata con questo modello:

<|im_start|>system
You are a helpful chatbot assistant.<|im_end|>
<|im_start|>user
Hi<|im_end|>
<|im_start|>assistant
Hi, how can I help you?<|im_end|>

Come puoi vedere, ChatML definisce diversi ruoli (sistema, utente, assistente) e aggiunge token speciali (<|im_start|> E <|im_end|>) per separarli. Inoltre, DPOTrainer richiede anche un formato specifico con tre colonne: richiesta, scelta e rifiutata.

Il nostro set di dati contiene quattro colonne: system, question, chatgpt e llama2–13b-chat. Concateneremo semplicemente le colonne del sistema e delle domande alla colonna del prompt. Inoltre mapperemo la colonna chatgpt su “scelto” e llama2–13b-chat su “rifiutato”. Per formattare il set di dati in modo affidabile, utilizzeremo il tokenizer apply_chat_template() funzione, che già utilizza ChatML.

def chatml_format(example):
# Format system
if len(example('system')) > 0:
message = {"role": "system", "content": example('system')}
system = tokenizer.apply_chat_template((message), tokenize=False)
else:
system = ""

# Format instruction
message = {"role": "user", "content": example('question')}
prompt = tokenizer.apply_chat_template((message), tokenize=False, add_generation_prompt=True)

# Format chosen answer
chosen = example('chosen') + "<|im_end|>\n"

# Format rejected answer
rejected = example('rejected') + "<|im_end|>\n"

return {
"prompt": system + prompt,
"chosen": chosen,
"rejected": rejected,
}

# Load dataset
dataset = load_dataset("Intel/orca_dpo_pairs")('train')

# Save columns
original_columns = dataset.column_names

# Tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "left"

# Format dataset
dataset = dataset.map(
chatml_format,
remove_columns=original_columns
)

Stampiamo un campione del set di dati formattato per confermare che tutto funziona come previsto:

{'prompt': '<|im_start|>system\nYou are an AI assistant. You will be given a task. You must generate a detailed and long answer.<|im_end|>\n<|im_start|>user\nGenerate an approximately fifteen-word sentence that describes all this data: Midsummer House eatType restaurant; Midsummer House food Chinese; Midsummer House priceRange moderate; Midsummer House customer rating 3 out of 5; Midsummer House near All Bar One<|im_end|>\n<|im_start|>assistant\n',
'chosen': 'Midsummer House is a moderately priced Chinese restaurant with a 3/5 customer rating, located near All Bar One.<|im_end|>\n',
'rejected': ' Sure! Here\'s a sentence that describes all the data you provided:\n\n"Midsummer House is a moderately priced Chinese restaurant with a customer rating of 3 out of 5, located near All Bar One, offering a variety of delicious dishes."<|im_end|>\n'}

Possiamo vedere che il prompt combina le istruzioni di sistema e quelle dell’utente. Grazie al add_generation_prompt=True argomento, aggiunge anche l’inizio della risposta dell’assistente. Se vuoi saltare questo passaggio, puoi utilizzare direttamente il set di dati preelaborato come mlabonne/chatml_dpo_pairs.

Successivamente, definiamo le configurazioni LoRA per addestrare il modello. Come descritto in Post del blog di Intelimpostiamo il valore del rango in modo che sia uguale a lora_alphail che è insolito (2 * r come regola generale). Puntiamo anche a tutti i moduli lineari con adattatori.

# LoRA configuration
peft_config = LoraConfig(
r=16,
lora_alpha=16,
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
target_modules=('k_proj', 'gate_proj', 'v_proj', 'up_proj', 'q_proj', 'o_proj', 'down_proj')
)

Ora siamo pronti per caricare il modello che vogliamo ottimizzare con DPO. In questo caso sono necessari due modelli: il modello da mettere a punto e il modello di riferimento. Questo è principalmente per motivi di leggibilità, poiché il file DPOTrainer L’oggetto crea automaticamente un modello di riferimento se non ne viene fornito nessuno.

# Model to fine-tune
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
load_in_4bit=True
)
model.config.use_cache = False

# Reference model
ref_model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
load_in_4bit=True
)

Il passaggio finale consiste nel fornire tutti gli iperparametri a TrainingArguments E DPOTrainer:

  • Tra questi, il beta Il parametro è unico per DPO poiché controlla la divergenza dalla politica iniziale (0,1 è un valore tipico per questo).
  • Rispetto ai valori descritti in Post del blog di Intelabbassiamo il tasso di apprendimento (da 5e-4 a 5e-5) e il numero di passaggi (da 1.000 a 200). Ho ottimizzato manualmente questi valori dopo alcune corse per stabilizzare l’allenamento e ottenere i migliori risultati.

Ora possiamo iniziare ad addestrare il modello. Tieni presente che richiede una GPU A100 e richiede circa 1 ora per completare la formazione.

# Training arguments
training_args = TrainingArguments(
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
gradient_checkpointing=True,
learning_rate=5e-5,
lr_scheduler_type="cosine",
max_steps=200,
save_strategy="no",
logging_steps=1,
output_dir=new_model,
optim="paged_adamw_32bit",
warmup_steps=100,
bf16=True,
report_to="wandb",
)

# Create DPO trainer
dpo_trainer = DPOTrainer(
model,
ref_model,
args=training_args,
train_dataset=dataset,
tokenizer=tokenizer,
peft_config=peft_config,
beta=0.1,
max_prompt_length=1024,
max_length=1536,
)

# Fine-tune model with DPO
dpo_trainer.train()

Il nostro modello è ora messo a punto. Puoi controllare il progetto su Weights & Biases a questo indirizzo. Ecco alcune metriche interessanti da analizzare:

Immagine dell’autore

È interessante notare che la perdita di allenamento scende rapidamente a zero (prima di 50 passi), nonostante 100 passi di riscaldamento. Nel frattempo, gli altri parametri continuano ad evolversi.

I grafici treno/premi/scelti e treno/premi/rifiutati corrispondono alla differenza media tra le probabilità logaritmiche restituite dai modelli addestrati e da quelli di riferimento. È logico che, nel tempo, divergano man mano che il nostro modello addestrato apprende le risposte preferite. Anche il grafico treno/premi/margini mostra la differenza tra questi due grafici. Infine, il grafico treno/ricompensa/accuratezza mostra la frequenza con cui si sceglie la risposta preferita. Il modello addestrato raggiunge rapidamente un punteggio di precisione perfetto, il che è un buon segno ma potrebbe anche significare che la differenza tra le risposte preferite e quelle rifiutate è troppo evidente.

Ora che è stato addestrato, possiamo unire l’adattatore con il modello originale. Successivamente, salviamo il modello unito e il tokenizzatore prima di inviarlo all’Hugging Face Hub.

# Save artifacts
dpo_trainer.model.save_pretrained("final_checkpoint")
tokenizer.save_pretrained("final_checkpoint")

# Flush memory
del dpo_trainer, model, ref_model
gc.collect()
torch.cuda.empty_cache()

# Reload model in FP16 (instead of NF4)
base_model = AutoModelForCausalLM.from_pretrained(
model_name,
return_dict=True,
torch_dtype=torch.float16,
)
tokenizer = AutoTokenizer.from_pretrained(model_name)

# Merge base model with the adapter
model = PeftModel.from_pretrained(base_model, "final_checkpoint")
model = model.merge_and_unload()

# Save model and tokenizer
model.save_pretrained(new_model)
tokenizer.save_pretrained(new_model)

# Push them to the HF Hub
model.push_to_hub(new_model, use_temp_dir=False, token=hf_token)
tokenizer.push_to_hub(new_model, use_temp_dir=False, token=hf_token)

Vediamo come si comporta il nostro modello in un test reale. Formatteremo il prompt per porre una domanda di base: “Cos’è un modello linguistico di grandi dimensioni?”

# Format prompt
message = (
{"role": "system", "content": "You are a helpful assistant chatbot."},
{"role": "user", "content": "What is a Large Language Model?"}
)
tokenizer = AutoTokenizer.from_pretrained(new_model)
prompt = tokenizer.apply_chat_template(message, add_generation_prompt=True, tokenize=False)

# Create pipeline
pipeline = transformers.pipeline(
"text-generation",
model=new_model,
tokenizer=tokenizer
)

# Generate text
sequences = pipeline(
prompt,
do_sample=True,
temperature=0.7,
top_p=0.9,
num_return_sequences=1,
max_length=200,
)
print(sequences(0)('generated_text'))

Ecco la risposta del modello:

A large language model is a type of artificial intelligence (AI) system that has been trained on vast amounts of text data. These models are designed to understand and generate human language, allowing them to perform various natural language processing tasks, such as text generation, language translation, and question answering. Large language models typically use deep learning techniques, like recurrent neural networks (RNNs) or transformers, to learn patterns and relationships in the data, enabling them to generate coherent and contextually relevant responses. The size of these models, in terms of the number of parameters and the volume of data they are trained on, plays a significant role in their ability to comprehend and produce complex language structures.

Sembra che tutto funzioni, ora possiamo valutare il modello unificato. Poiché si tratta di un modello generico, possiamo sfruttare il lm-imbracatura-di-valutazione per valutarlo. Poiché il processo richiede molte risorse, possiamo anche sottoporlo direttamente alla valutazione sul sito Apri la classifica LLM. Ci sono voluti alcuni giorni, ma ecco i risultati rispetto ad altri modelli OpenHermes:

Immagine dell’autore

Rispetto al modello originale, il modello NeuralHermes-2–5-Mistral-7B ha migliorato il punteggio medio di 6,7 punti (in particolare su GSM8K). Si tratta di un miglioramento inaspettatamente ampio, che mette in mostra la potenza dell’ottimizzazione delle preferenze dirette.

In questo articolo, abbiamo messo a punto un modello già supervisionato utilizzando DPO e ne abbiamo creato uno nostro NeuralHermes-2.5 modello. Sfruttando un set di dati sulle preferenze di alta qualità, abbiamo creato una pipeline di perfezionamento efficiente a campione che ha prodotto un miglioramento significativo nella classifica Open LLM. Se vuoi provarlo, puoi trovare varianti quantizzate di questo modello o usarlo Abbracciare lo spazio del viso.

Tieni presente che la nostra pipeline di perfezionamento può ancora essere migliorata in diversi modi. Ad esempio, il set di dati sulle preferenze è ancora piuttosto grezzo e potrebbe essere migliorato con ulteriori filtri e utilizzando modelli diversi. Inoltre, numerosi iperparametri possono ancora essere modificati per ottenere risultati migliori. In particolare, il tasso di apprendimento può ancora essere ridotto per addestrare il modello su più passaggi e inserire più dati sulle preferenze.

Fonte: towardsdatascience.com

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *