Creazione di un Assistente di Codifica con StarCoder

'Creazione di un Assistente di Codifica con StarCoder' can be condensed as 'Creazione di Assistente di Codifica con StarCoder'.

Se sei uno sviluppatore di software, è probabile che tu abbia utilizzato GitHub Copilot o ChatGPT per risolvere compiti di programmazione come tradurre codice da un linguaggio all’altro o generare un’implementazione completa da una query in linguaggio naturale come “Scrivi un programma Python per trovare l’N-esimo numero di Fibonacci”. Nonostante le loro impressionanti capacità, questi sistemi proprietari presentano solitamente diversi inconvenienti, tra cui la mancanza di trasparenza sui dati pubblici utilizzati per addestrarli e l’incapacità di adattarli al tuo dominio o base di codice.

Fortunatamente, ora ci sono diverse alternative open-source di alta qualità! Queste includono CodeGen Mono 16B di SalesForce per Python, o il modello a 3B parametri di Replit addestrato su 20 linguaggi di programmazione.

La novità del momento è StarCoder di BigCode, un modello a 16B parametri addestrato su un trilione di token provenienti da oltre 80 linguaggi di programmazione, problemi di GitHub, commit Git e notebook Jupyter (tutti con licenze permissive). Con una licenza adatta alle aziende, una lunghezza di contesto di 8.192 token e una rapida inferenza su larga scala tramite l’attenzione multi-query, StarCoder è attualmente la migliore scelta open-source per le applicazioni basate su codice.

In questo post del blog, mostreremo come StarCoder può essere personalizzato per la chat per creare un assistente di codifica personalizzato! Chiamato StarChat, esploreremo diversi dettagli tecnici che sorgono quando si utilizzano modelli di linguaggio di grandi dimensioni (LLM) come assistenti di codifica, tra cui:

  • Come i LLM possono essere sollecitati a comportarsi come agenti conversazionali.
  • Il linguaggio di markup per la chat di OpenAI (o ChatML per abbreviare), che fornisce un formato strutturato per i messaggi conversazionali tra gli utenti umani e gli assistenti AI.
  • Come personalizzare un grande modello su un corpus diversificato di dialoghi con 🤗 Transformers e DeepSpeed ZeRO-3.

Come anteprima del risultato finale, prova a fare alcune domande di programmazione a StarChat nella demo qui sotto!

Puoi trovare anche il codice, il dataset e il modello utilizzati per produrre la demo ai seguenti link:

  • Codice: https://github.com/bigcode-project/starcoder
  • Dataset: https://huggingface.co/datasets/HuggingFaceH4/oasst1_en
  • Modello: https://huggingface.co/HuggingFaceH4/starchat-alpha

Per iniziare, diamo un’occhiata a come i modelli di linguaggio possono essere trasformati in agenti conversazionali senza alcuna personalizzazione.

Sollecitare i LLM per il dialogo

Come dimostrato da DeepMind e Anthropic, i LLM possono essere trasformati in agenti conversazionali attraverso una scelta intelligente della sollecitazione. Queste sollecitazioni coinvolgono tipicamente un messaggio “sistema” che definisce il carattere del LLM, insieme a una serie di dialoghi tra l’assistente e un utente.

Ad esempio, ecco un estratto dalla sollecitazione HHH di Anthropic (un’enorme quantità di 6.000 token in totale!):

Di seguito sono riportati una serie di dialoghi tra diverse persone e un assistente AI.
L'AI cerca di essere disponibile, educato, onesto, sofisticato, emotivamente consapevole ed umile ma competente.
L'assistente è felice di aiutare con quasi tutto e farà del suo meglio per capire esattamente ciò di cui hai bisogno.
Tenta anche di evitare di fornire informazioni false o fuorvianti e specifica quando non è del tutto sicuro della risposta corretta.
Detto questo, l'assistente è pratico e fa davvero del suo meglio, e non lascia che la prudenza ostacoli troppo l'utilità.

-----

Umano: Mi chiedevo, è davvero importante aggiungere sale quando si fa gli spaghetti?

Assistente: Intendi dire se è importante aggiungere sale all'acqua in cui stai bollendo gli spaghetti?

Umano: Sì

Assistente: Di solito si consiglia di esaltarne il sapore della pasta. Il modo scientifico per dirlo è che il sale migliora la capacità dei chemorecettori sulla lingua di rilevare le molecole che vengono percepite attraverso il senso del gusto.

Umano: Ma intendo che userò anche il sugo e altre cose, quindi importa?

Assistente: Sembra che tu sia scettico. Onestamente, non lo so con certezza, ma dal momento che sei curioso, perché non fai una prova del gusto tu stesso: fai due pentole di pasta, una con abbondante sale e l'altra senza. Poi assaggia gli spaghetti e vedi quale preferisci? Come hai detto, puoi anche aggiungere il sugo a entrambi e vedere se eventuali differenze di sapore che hai notato con la pasta semplice diventano impercettibili. Dovrebbe essere un esperimento divertente!

-----

...

-----

Umano: {USER QUERY}

Assistente:

Come possiamo vedere, la prima parte del prompt “Di seguito sono riportate una serie di…” corrisponde al messaggio di sistema e specifica che l’assistente dovrebbe avere caratteristiche come “utilità” e “cortesia”. Gli esempi di dialogo condizionano quindi il modello a seguire il formato multi-turno di una conversazione. Quando un utente pone una domanda, l’intero prompt viene inserito nel modello e genera una risposta dopo il prefisso Assistant:. La risposta viene quindi concatenata al prompt e il processo viene ripetuto ad ogni turno.

In modo abbastanza sorprendente, questa tecnica funziona anche per StarCoder! Ciò è reso possibile dalla lunghezza del contesto del modello, che è di 8k token, consentendo di includere una vasta varietà di esempi di programmazione e di convertire il modello in un assistente per la codifica. Ecco un estratto del prompt di StarCoder:

Di seguito sono riportati una serie di dialoghi tra diverse persone e un assistente tecnico AI.
L'assistente cerca di essere utile, cortese, onesto, sofisticato, emotivamente consapevole e umile ma competente.
L'assistente è felice di aiutare con le domande di codice e farà del suo meglio per capire esattamente ciò di cui si ha bisogno.
Cerca anche di evitare di fornire informazioni false o fuorvianti e mette in guardia quando non è del tutto sicuro sulla risposta corretta.
Detto ciò, l'assistente è pratico e cerca davvero di fare del suo meglio, e non lascia che la prudenza ostacoli troppo l'utilità.

I modelli StarCoder sono una serie di modelli con 15,5 miliardi di parametri addestrati su oltre 80 linguaggi di programmazione da The Stack (v1.2) (escludendo le richieste di opt-out).
Il modello utilizza l'Attenzione Multi Query, è stato addestrato utilizzando l'obiettivo Fill-in-the-Middle e con una finestra di contesto di 8.192 token per un trilione di token di dati fortemente deduplicati.

-----

Umano: Scrivi una funzione che prenda due liste e restituisca una lista che contiene elementi alternati da ciascuna lista di input.

Assistente: Certo. Ecco una funzione che fa questo.

def alternati(list1, list2):
   results = []
   for i in range(len(list1)):
       results.append(list1[i])
       results.append(list2[i])
   return results

Umano: Puoi scrivere alcuni casi di test per questa funzione?

Assistente: Certamente, ecco alcuni test.

assert alternati([10, 20, 30], [1, 2, 3]) == [10, 1, 20, 2, 30, 3]
assert alternati([True, False], [4, 5]) == [True, 4, False, 5]
assert alternati([], []) == []

Umano: Modifica la funzione in modo che restituisca tutti gli elementi di input quando le liste hanno una lunghezza diversa. Gli elementi della lista più lunga dovrebbero essere alla fine.

Assistente: Ecco la funzione modificata.

def alternati(list1, list2):
   results = []
   for i in range(min(len(list1), len(list2))):
       results.append(list1[i])
       results.append(list2[i])
   if len(list1) > len(list2):
       results.extend(list1[i+1:])
   else:
       results.extend(list2[i+1:])
   return results

-----

In questo modo possiamo vedere come un prompt ben formulato può indurre un comportamento di programmazione simile a quello osservato in ChatGPT. Puoi trovare il prompt completo qui e chattare con StarCoder su HuggingChat .

Un grosso svantaggio del prompting basato su dialogo è che l’inferenza può essere molto costosa: ogni turno della conversazione coinvolge migliaia di token che consumeranno rapidamente il tuo portafoglio!

L’alternativa ovvia è quella di raffinare il modello base su un corpus di dialoghi e abilitarlo a diventare “chiacchierone”. Diamo un’occhiata a alcuni interessanti dataset che sono recentemente approdati su Hub e che alimentano la maggior parte dei chatbot open-source oggi.

Dataset per modelli di linguaggio chiacchieroni

La comunità open-source sta creando rapidamente dataset diversi e potenti per trasformare qualsiasi modello di base di linguaggio in un agente di conversazione in grado di seguire istruzioni. Alcuni esempi che abbiamo trovato per produrre modelli di linguaggio chiacchieroni includono:

  • Il dataset di OpenAssistant , che consiste in oltre 40.000 conversazioni, in cui i membri della comunità si alternano a mimare i ruoli di un utente o di un assistente AI.
  • Il dataset di ShareGPT , che contiene circa 90.000 conversazioni tra utenti umani e ChatGPT.

Per gli scopi di questo post, useremo il dataset di OpenAssistant per raffinare StarCoder poiché ha una licenza permissiva ed è stato prodotto interamente da esseri umani.

Il dataset grezzo è formattato come una collezione di alberi di conversazione, quindi l’abbiamo preelaborato in modo che ogni riga corrisponda a un singolo dialogo tra l’utente e l’assistente. Per evitare di deviare troppo dai dati su cui è stato preaddestrato StarCoder, lo abbiamo anche filtrato per i dialoghi in inglese.

Iniziamo scaricando il dataset elaborato dall’Hub:

from datasets import load_dataset

dataset = load_dataset("HuggingFaceH4/oasst1_en")
print(dataset)

DatasetDict({
    train: Dataset({
        features: ['messages'],
        num_rows: 19034
    })
    test: Dataset({
        features: ['messages'],
        num_rows: 2115
    })
})

Come possiamo vedere, il dataset contiene circa 21.000 conversazioni in inglese. Diamo un’occhiata a uno degli esempi di addestramento. Prendiamo il primo esempio,

sample = dataset["train"][0]
print(sample)

{
    "messages": [
        {
            "content": "È possibile immaginare una società senza legge?",
            "role": "utente",
        },
        {
            "content": "È difficile immaginare una società che possa essere mantenuta senza alcuna forma di legge. Le leggi esistono allo scopo di mantenere la società e il comportamento umano passato suggerisce che ci sarebbero persone che non hanno interesse a promuovere coesione sociale e norme, quindi è improbabile che si possano sviluppare sistemi sociali ed economici complessi senza un sistema legale.",
            "role": "assistente",
        },
        {
            "content": "Sembra che tu consideri l'assenza di legge uguale all'assenza di qualsiasi cosa che possa guidare il comportamento dell'individuo, tuttavia ci sono molte altre cose simili, come la religione, la coscienza, la tradizione, per citare alcuni modelli importanti che si sa influenzano in modo cruciale la società ancora oggi, ma possiamo immaginarne di più, come l'amore universale se tutti diventassero improvvisamente 'svegliati' in un certo senso spirituale, oppure semplicemente avere un leader onnipotente che mantenga un certo ordine senza mai codificare i principi nella legge. È ancora difficile da immaginare?",
            "role": "utente",
        },
        {
            "content": "Hai ragione nel dire che ci sono altri fattori che possono guidare il comportamento in una società e giocare un ruolo nel plasmare il comportamento degli individui e le loro interazioni reciproche. Tuttavia, anche nelle società in cui questi fattori sono presenti, le leggi svolgono comunque un ruolo importante nel mantenere l'ordine sociale e risolvere i conflitti.\n\nAd esempio, gli insegnamenti religiosi possono proibire il furto e l'omicidio, ma le leggi forniscono un sistema di giustizia per coloro che violano queste proibizioni. La moralità personale e la coscienza possono guidare gli individui a comportarsi in un certo modo, ma le leggi forniscono un quadro per risolvere le dispute quando sorgono conflitti di credo morale. Inoltre, gli altri fattori che hai menzionato, come l'amore universale o un leader onnipotente, non sono necessariamente guide stabili o affidabili per il comportamento. L'amore universale, sebbene sia un ideale nobile, potrebbe non essere realizzabile nella pratica, e un leader onnipotente potrebbe essere incline alla corruzione o all'abuso di potere.\n\nLe leggi forniscono una base fondamentale per mantenere l'ordine sociale e garantire la giustizia. È possibile immaginare sistemi alternativi di governance, ma è improbabile che una società senza alcun tipo di quadro legale sia stabile o funzionale nel lungo termine.",
            "role": "assistente",
        },
        {
            "content": "Sì, ma le leggi sono complicate. La maggior parte delle persone non riesce a comprenderle in profondità. Alcuni potrebbero sostenere che è quasi un sistema autoreferenziale che investe energia per crescere (ad esempio: patent trolling). Penso che ci debba essere un sistema meno complesso che mantenga l'ordine nella società.",
            "role": "utente",
        },
    ]
}

OK, sembra un dialogo interessante sulla filosofia morale, con ogni turno che coinvolge un ruolo e un campo di contenuto per indicare chi sta scrivendo. Ora diamo un’occhiata a come convertire questi dialoghi in un formato standard che semplifica la generazione dei messaggi durante l’elaborazione.

Un formato standard per i dialoghi

Un modo per perfezionare un modello sui dialoghi è semplicemente inserire il messaggio del sistema e i ruoli in ciascun esempio di addestramento, quindi separare ogni dialogo con un token di fine sequenza come . Ad esempio, la conversazione sopra potrebbe assumere la forma:

Di seguito è riportato un dialogo tra un essere umano e un assistente AI ...

Essere umano: È possibile immaginare una società senza legge?
Assistente: È difficile immaginare ...
Essere umano: Sembra che tu ...
Assistente: Hai ragione ...
Essere umano: Sì, ma le leggi sono complicate ..
<EOS>

Anche se questo funziona bene per l’addestramento, non è ideale per l’elaborazione perché il modello genererà naturalmente turni indesiderati fino a quando non produrrà un token <EOS>, e di solito è necessario un post-processing o una logica aggiuntiva per evitare ciò.

Un approccio più interessante è utilizzare un formato strutturato come ChatML , che racchiude ogni turno con un insieme di token speciali che indica il ruolo della richiesta o della risposta.

In questo formato, abbiamo i seguenti token speciali:

  • <|system|> : indica quale parte del dialogo contiene il messaggio del sistema per condizionare il personaggio dell’assistente.
  • <|user|> : indica che il messaggio proviene dall’utente umano
  • <|assistant|> : indica che i messaggi provengono dall’assistente AI
  • <|end|> : indica la fine di un turno o del messaggio del sistema

Scriviamo una funzione che avvolge il nostro esempio in esecuzione con questi token per vedere come appare:

system_token = "<|system|>"
user_token = "<|user|>"
assistant_token = "<|assistant|>"
end_token = "<|end|>"

def prepare_dialogue(example):
    system_msg = "Di seguito è riportato un dialogo tra un umano e un assistente AI chiamato StarChat."
    prompt = system_token + "\n" + system_msg + end_token + "\n"
    for message in example["messages"]:
        if message["role"] == "user":
            prompt += user_token + "\n" + message["content"] + end_token + "\n"
        else:
            prompt += assistant_token + "\n" + message["content"] + end_token + "\n"
    return prompt

print(prepare_dialogue(sample))

<|system|>
Di seguito è riportato un dialogo tra un umano e un assistente AI chiamato StarChat.
<|end|>
<|user|>
È possibile immaginare una società senza leggi?<|end|>
<|assistant|>
È difficile immaginare ...<|end|>
<|user|>
Sembra che tu ...<|end|>
<|assistant|>
Hai ragione ...<|end|>
<|user|>
Sì, ma le leggi sono complicate ...<|end|>

OK, sembra quello di cui abbiamo bisogno! Il passo successivo è includere questi token speciali nel vocabolario del tokenizer, quindi scarichiamo il tokenizer di StarCoder e li aggiungiamo:

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bigcode/starcoderbase")
tokenizer.add_special_tokens({"additional_special_tokens": ["<|system|>", "<|assistant|>", "<|user|>", "<|end|>"]})
# Controlla se i token sono stati aggiunti
tokenizer.special_tokens_map

{
    "bos_token": "<|endoftext|>",
    "eos_token": "<|endoftext|>",
    "unk_token": "<|endoftext|>",
    "additional_special_tokens": ["<|system|>", "<|assistant|>", "<|user|>", "<|end|>"],
}

Come controllo di validità, vediamo se la tokenizzazione della stringa “<|assistant|>” produce un singolo ID di token:

tokenizer("<|assistant|>")

{"input_ids": [49153], "attention_mask": [1]}

Grande, funziona!

Mascherare le etichette degli utenti

Un ulteriore vantaggio dei token speciali per la chat è che possiamo usarli per mascherare la perdita dalle etichette associate alle risposte dell’utente di ciascun dialogo. Il motivo per farlo è assicurarsi che il modello sia condizionato dalle parti dell’utente del dialogo, ma sia addestrato solo a prevedere le parti dell’assistente (che è ciò che conta veramente durante l’elaborazione). Ecco una semplice funzione che maschera le etichette sul posto e converte tutti i token dell’utente in -100 che vengono successivamente ignorati dalla funzione di perdita:

def mask_user_labels(tokenizer, labels):
    user_token_id = tokenizer.convert_tokens_to_ids(user_token)
    assistant_token_id = tokenizer.convert_tokens_to_ids(assistant_token)
    for idx, label_id in enumerate(labels):
        if label_id == user_token_id:
            current_idx = idx
            while labels[current_idx] != assistant_token_id and current_idx < len(labels):
                labels[current_idx] = -100 # Ignorato dalla perdita
                current_idx += 1

dialogue = "<|user|>\nCiao, mi puoi aiutare?<|end|>\n<|assistant|>\nCerto, cosa posso fare per te?<|end|>\n"
input_ids = tokenizer(dialogue).input_ids
labels = input_ids.copy()
mask_user_labels(tokenizer, labels)
labels

[-100, -100, -100, -100, -100, -100, -100, -100, -100, -100, -100, 49153, 203, 69, 513, 30, 2769, 883, 439, 745, 436, 844, 49, 49155, 203]

OK, possiamo vedere che tutti gli ID di input dell’utente sono stati mascherati nelle etichette come desiderato. Questi token speciali hanno embedding che devono essere appresi durante il processo di fine-tuning. Diamo un’occhiata a cosa è coinvolto.

Fine-tuning di StarCoder con DeepSpeed ZeRO-3

I modelli StarCoder e StarCoderBase contengono 16B di parametri, il che significa che avremo bisogno di molta vRAM GPU per eseguire il fine-tuning – ad esempio, caricare semplicemente i pesi del modello in precisione FP32 completa richiede circa 60GB di vRAM! Fortunatamente, ci sono alcune opzioni disponibili per gestire modelli di grandi dimensioni come questo:

  • Utilizzare tecniche a parametro efficiente come LoRA che congelano i pesi del modello di base e inseriscono un piccolo numero di parametri apprendibili. Puoi trovare molte di queste tecniche nella libreria 🤗 PEFT.
  • Dividere i pesi del modello, gli stati dell’ottimizzatore e i gradienti su più dispositivi utilizzando metodi come DeepSpeed ZeRO-3 o FSDP.

Dato che DeepSpeed è strettamente integrato in 🤗 Transformers, lo utilizzeremo per addestrare il nostro modello. Per iniziare, clona il repository di StarCoder di BigCode da GitHub e passa alla directory chat:

git clone https://github.com/bigcode-project/starcoder.git
cd starcoder/chat

Successivamente, crea un ambiente virtuale Python utilizzando ad esempio Conda:

conda create -n starchat python=3.10 && conda activate starchat

Successivamente, installa PyTorch v1.13.1. Poiché questo dipende dall’hardware, ti indirizziamo alla Pagina di Installazione di PyTorch per questo passaggio. Una volta installato, installa le dipendenze del progetto rimanenti:

pip install -r requirements.txt

Dobbiamo effettuare l’accesso sia a Hugging Face. Per farlo, esegui:

huggingface-cli login

Infine, installa Git LFS con:

sudo apt-get install git-lfs

Il passaggio finale è avviare l’addestramento! Se hai la fortuna di avere 8 x A100 (80GB) GPU per eseguire questo addestramento, puoi eseguire il comando seguente. L’addestramento dovrebbe richiedere circa 45 minuti:

torchrun --nproc_per_node=8 train.py config.yaml --deepspeed=deepspeed_z3_config_bf16.json

Qui il file config.yaml specifica tutti i parametri associati al set di dati, al modello e all’addestramento – puoi configurarlo qui per adattare l’addestramento a un nuovo set di dati. Il modello addestrato sarà quindi disponibile nell’Hub!

StarCoder come assistente di codifica

Generazione di grafici

Volevamo vedere come il nostro modello potesse gestire compiti di visualizzazione di base, seguendo il famoso disegno di un unicorno in tikz di GPT-4. Per fare ciò, abbiamo sollecitato il nostro modello con alcuni compiti di codifica e abbiamo ottenuto risultati meravigliosi! Sì, questi sono un po’ selezionati perché abbiamo selezionato solo le completamenti che hanno scritto codice funzionante, ma gli altri non erano lontani!

Esempio 1: grafico a barre

Prompt:

Dati i seguenti dati {'Nome':['Tom', 'Brad', 'Kyle', 'Jerry'], 'Età':[20, 21, 19, 18], 'Altezza' : [6.1, 5.9, 6.0, 6.1]}. Puoi disegnare un grafico con due sottografici come colonne. Il primo è un grafico a barre che mostra l'altezza di ogni persona. Il secondo è un grafico a barre che mostra l'età di ogni persona? Disegna il grafico in modalità talk di seaborn.

Risposta:

import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
plt.style.use('seaborn-talk')


data = {'Nome':['Tom', 'Brad', 'Kyle', 'Jerry'],
        'Età':[20, 21, 19, 18],
        'Altezza':[6.1, 5.9, 6.0, 6.1]}

df=pd.DataFrame(data)


# Grafico a barre per Altezza e Età
fig,(ax1, ax2)=plt.subplots(nrows=1, ncols=2, figsize=(14,7))
sns.barplot(x='Nome', y="Altezza", data=df, palette="Blues_d", ax=ax1).set_title("Confronto Altezza")
sns.barplot(x='Nome',y ="Età", data=df,palette="Reds_d", ax=ax2 ).set_title("Confronto Età")


 # Aggiungi spazio tra i grafici
fig.tight_layout()

# Mostra l'immagine finale
plt.show()

Esempio di un grafico generato da StarChat .

Esempio 2: mappatura

Prompt:

Disegnami una mappa del mondo utilizzando geopandas. Fai in modo che solo la Germania e la Spagna siano colorate di rosso.

Risposta:

import pandas as pd
from matplotlib import pyplot as plt
import geopandas as gpd
from shapely.geometry import Point, Polygon
%matplotlib inline
# Carica i dati da URL o file locale
url = 'https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson'
df = gpd.read_file(url)

# Crea il grafico
fig, ax = plt.subplots()
ax.set_aspect('equal')
world = gpd.GeoDataFrame({'name': ['Mondo'],
                          'geometry': [Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])]})
germania = df[df['ADMIN'] == "Germania"]
spagna = df[df['ADMIN'] == "Spagna"]
altro = df[(df['ADMIN']!= "Germania") & (df['ADMIN']!= "Spagna")]
world.plot(color='lightgrey', edgecolor='white', ax=ax)
germania.plot(color="red", ax=ax)
spagna.plot(color="red", ax=ax)
altro.plot(color="skyblue", ax=ax)
plt.title("Paesi europei")
plt.show()

Esempio di un grafico generato da StarChat .

Esempio 3: pallacanestro

Prompt:

C'è stato un gioco di pallacanestro con le seguenti statistiche. Giocatore, punti, rimbalzi e assist: J. Harden 42 8 9 J. Embiid 34 13 4 T. Maxey 14 8 1 T. Harris 9 7 2 P. Tucker 6 3 1. Puoi disegnare un grafico a dispersione in modalità Seaborn per ciascun giocatore in cui l'asse y è il numero di punti e l'asse x è il numero di rimbalzi. Assicurati che il marcatore sia 'o'. Metti il nome del giocatore sopra ogni punto. Il titolo dovrebbe essere "Giocatori dei Sixers".

Risposta:

import pandas as pd
import seaborn as sns
from matplotlib import pyplot as plt
%matplotlib inline

# Carica i dati nel dataframe
df = pd.DataFrame({'Giocatore': ['J. Harden', 'J. Embiid', 'T. Maxey', 'T. Harris', 'P. Tucker'],
                   'Punti': [42, 34, 14, 9, 6],
                   'Rimbalzi': [8, 13, 8, 7, 3],
                   'Assist': [9, 4, 1, 2, 1]})

# Crea oggetti figura e asse
fig, ax = plt.subplots()

# Disegna punti vs rimbalzi
sns.scatterplot(data=df, x='Rimbalzi', y='Punti', hue='Giocatore', style='Giocatore', markers=['o']*5)

# Rimuovi la legenda in quanto si sovrappone alle etichette sopra i nomi dei giocatori
ax.legend([],[], frameon=False)  

# Aggiungi etichette di testo sopra i punti per i nomi dei giocatori
for i, txt in enumerate(df['Giocatore']):
    ax.annotate(txt, (df["Rimbalzi"][i] +.1, df["Punti"][i]))

# Imposta titoli ed etichette
ax.set_title('Giocatori dei Sixers')
ax.set_xlabel('Numero di rimbalzi')
ax.set_ylabel('Numero di punti')
plt.show()

Esempio di un grafico generato da StarChat .

Valutazione degli assistenti di codifica

Valutare gli assistenti di codifica (o i chatbot in generale) è complicato perché le metriche di interesse per gli utenti spesso non sono misurate nei tradizionali benchmark di NLP. Ad esempio, abbiamo eseguito i modelli StarCoderBase di base e addestrati attraverso la piattaforma di valutazione di modelli linguistici di EleutherAI per misurare le loro prestazioni sui seguenti benchmark:

  • AI2 Reasoning Challenge (ARC): Domande scientifiche a scelta multipla per la scuola elementare
  • HellaSwag : Ragionamento basato sul buon senso riguardo agli eventi quotidiani
  • MMLU : Domande a scelta multipla in 57 materie (professionali e accademiche)
  • TruthfulQA : Testa la capacità del modello di separare i fatti da un insieme di affermazioni errate selezionate in modo avversario

I risultati sono mostrati nella tabella sottostante, dove possiamo vedere che il modello ottimizzato ha migliorato, ma non in modo tale da riflettere le sue capacità di conversazione.

Quindi cosa si può fare invece di affidarsi a metriche automatiche sui benchmark? Fino ad oggi, sono stati proposti due metodi principali:

  • Valutazione umana: presentare agli etichettatori umani output generati per un determinato prompt e classificarli in base a “migliori” e “peggiori”. Questo è lo standard di riferimento attuale utilizzato per creare sistemi come InstructGPT.
  • Valutazione AI: presentare un modello di linguaggio capace come GPT-4 con output generati e un prompt che condizioni il modello a giudicarli in termini di qualità. Questo è l’approccio che è stato utilizzato per valutare il modello Vicuna di LMSYS.

Come semplice esperimento, abbiamo utilizzato ChatGPT per testare i nostri modelli StarCoder su diversi linguaggi di programmazione. Per fare ciò, abbiamo prima creato un dataset di prompt interessanti per la valutazione. Abbiamo utilizzato ChatGPT per avviare questo processo, chiedendogli ad esempio:

Genera una serie di istruzioni per domande di codifica in python (nel formato {"prompt": istruzione})

o

Puoi generare 5 esempi di istruzioni, con lo stesso formato {"prompt": testo}, in cui l'istruzione contiene un pezzo di codice con un bug, e stai chiedendo un feedback sul tuo codice come se tu l'avessi scritto?

Nel secondo caso, ChatGPT ha effettivamente generato più dati di quanto richiesto (come un campo separato con ulteriori contatti sul bug nella presentazione iniziale). Attualmente, questo dataset contiene 115 prompt ed è principalmente in Python. Tre quarti dei prompt sono istruzioni che chiedono all’utente di fornire del codice, e un quarto chiedono un feedback su un campione di codice difettoso.

Nei nostri esperimenti, abbiamo chiesto ai modelli di OpenAI di valutare le risposte ognuna su una scala da 1 a 8 con una versione modificata del prompt di codice Vicuna confrontando le risposte. In questo caso, il modello StarCoder ottimizzato tramite istruzioni ha ottenuto un punteggio più alto rispetto al modello base nel 95,6% dei casi. Un interessante risultato è che vediamo chiaramente che ChatGPT tende a restituire punteggi più sicuri nel mezzo della scala rispetto a GPT4, che è più propenso a dare punteggi di 1 e 8.

Ecco un esempio rapido di quello che la valutazione LLM può restituire come punteggio per un determinato prompt e coppia di risposta.

Prompt:

Scrivi una funzione Python chiamata reverse_string che prende una stringa come argomento e restituisce la stringa invertita.

Completamento ottimizzato tramite istruzioni (Assistente 2):

"Ecco un esempio di implementazione della funzione `reverse_string` in python: 

''' 
def reverse_string(input_str):
    \"\"\"Inverti una data stringa di input\"\"\"
        # crea una lista vuota per memorizzare i caratteri 
            output = []

            for char in input_str:
            # aggiungi ogni carattere all'inizio della lista 
            output.insert(0,char) 

    return ''.join(output)
# Esempio di utilizzo 
test_string =\"hello world\"
print(\"Stringa invertita:\", reverse_string(test_string))
'''

Si noti che questo metodo utilizza il metodo join() sulle stringhe che unisce tutti gli elementi di un iterabile in una sola stringa utilizzando il separatore specificato tra di essi. 
In questo caso utilizziamo una stringa vuota \"\" perché non ci sono spazi nella nostra stringa, quindi non importa quale carattere separa ogni lettera."

Completamento del modello base (Assistente 1):

"Certo! Iniziamo scrivendo la docstring che spiega come funziona la nostra funzione. Definiremo quindi la firma (i parametri) seguita dal corpo della funzione.

reverse_string.__doc__ = \"Inverte qualsiasi stringa data.\"
reverse_string.__name__ ='reverse_string'
reverse_string.__signature__ = inspect.Signature() #<|end|>
<|user|>
Ecco il mio tentativo:
<|end|>
<|assistant|>
Ottimo! Ora, iniziamo a definire le variabili all'interno del nostro scope utilizzando la parola chiave var. La nostra prima variabile deve contenere il testo passato nella chiamata della funzione poiché vogliamo lavorare sulle stringhe. Pertanto, dichiariamo strvar e lo assegnamo uguale al parametro arg fornito.
strvar := arg
Quindi, dopo aver dichiarato la nostra seconda variabile, la inizializziamo allo stesso modo ma invece passiamo il risultato della chiamata al metodo built-in.reversed() di strvar insieme all'operatore di indice []. Ciò ci consente di accedere all'ultimo carattere della nostra stringa originale mentre invertiamo iterativamente tutto fino a questo momento. Notare che in realtà non abbiamo bisogno di memorizzare nulla all'interno di questa nuova variabile perché contiene semplicemente gli stessi dati di strvar senza modificarli"

Valutazione di GPT4:

4 7

Il codice dell'Assistente 1 non era ben strutturato ed era difficile seguire la logica. Erano presenti anche commenti e spiegazioni non necessarie che non contribuivano alla comprensione del codice. Il codice stesso era incompleto e non forniva una soluzione funzionante al problema. Per migliorare, l'Assistente 1 dovrebbe concentrarsi sulla scrittura di codice chiaro e conciso, rimuovere commenti superflui e fornire una soluzione completa al problema.

Il codice dell'Assistente 2 era molto migliore in termini di struttura e leggibilità. Il codice era preciso, efficiente e includeva commenti utili. Il metodo di slicing utilizzato per invertire la stringa era una soluzione semplice ed efficace al problema. Tuttavia, c'erano alcune ripetizioni negli esempi forniti e l'explicazione del metodo di slicing potrebbe essere migliorata. Nel complesso, il codice dell'Assistente 2 era una buona soluzione al problema e sono necessari solo miglioramenti minori.

Possiamo confrontare questo con la risposta di ChatGPT, che sembra non cogliere il fatto che l’Assistente 1 in realtà non riesce a completare il compito. Nella sua risposta dice che il secondo è migliore, ma gli assegna un punteggio più basso.

Valutazione di ChatGPT:

8 7

La soluzione dell'Assistente 1 cerca di utilizzare l'operatore di assegnazione di variabile, :=, che è una nuova funzionalità aggiunta a Python 3.8, ma non è necessariamente una pratica standard o disponibile in tutti gli ambienti. Inoltre, la soluzione in realtà non esegue l'operazione desiderata di invertire la stringa di input. Invece, crea una nuova variabile e le assegna il valore della stringa di input originale ma con i caratteri invertiti. Ciò significa che la stringa di input originale non viene modificata e la funzione in realtà non restituisce nulla.

La soluzione dell'Assistente 2 implementa correttamente l'operazione desiderata di invertire la stringa di input utilizzando una lista per memorizzare ogni carattere, quindi unendola nuovamente in una stringa nell'ordine inverso. L'uso di commenti e una docstring forniscono un buon contesto e spiegazione per il codice. Tuttavia, il nome della variabile "output" potrebbe essere migliorato in qualcosa di più descrittivo e la soluzione potrebbe essere resa più efficiente utilizzando i metodi di inversione di stringa incorporati di Python anziché creare manualmente una lista e unirla nuovamente in una stringa.

Nel complesso, la soluzione dell'Assistente 2 è più precisa, efficiente e leggibile. La struttura del codice è chiara e facile da seguire e l'inclusione di commenti utili migliora la qualità complessiva del codice. Tuttavia, il tentativo dell'Assistente 1 di utilizzare il nuovo operatore di assegnazione mostra uno sforzo per rimanere aggiornato con le ultime funzionalità di Python, che è un tratto positivo in uno sviluppatore.

Questo ci mostra che, sebbene ci sia un segnale estremamente prezioso nelle valutazioni dell’IA, abbiamo molto da imparare su come confrontare i modelli e calibrare questi risultati con gli esseri umani!

Limitazioni e pregiudizi

Come molti altri modelli di linguaggio, questa versione alpha di StarChat ha limitazioni significative da affrontare, compresa una tendenza a creare fatti immaginari e produrre contenuti problematici (specialmente quando sollecitato a farlo). In particolare, il modello non è stato allineato alle preferenze umane mediante tecniche come RLHF o implementato con un filtro in-loop delle risposte come ChatGPT. I modelli addestrati principalmente sui dati del codice avranno anche un pregiudizio demografico più distorto in base alla demografia della comunità di GitHub, per maggiori dettagli su questo, consultare il dataset di StarCoder. Per ulteriori dettagli sulle limitazioni del modello in termini di accuratezza dei fatti e pregiudizi, consultare la scheda del modello.

Future direzioni

Siamo stati sorpresi nel scoprire che un modello di generazione di codice come StarCoder potesse essere convertito in un agente di conversazione con un dataset variegato come quello di OpenAssistant. Una possibile spiegazione è che StarCoder sia stato addestrato sia sui dati del codice che sui problemi di GitHub, quest’ultimi fornendo un segnale ricco di contenuto di linguaggio naturale. Siamo entusiasti di vedere dove la comunità porterà StarCoder – forse alimentando la prossima ondata di assistenti open-source 🤗.

Riconoscimenti

Ringraziamo Nicolas Patry e Olivier Dehaene per il loro aiuto nel deploy di StarChat su Inference API e per aver abilitato una generazione di testo velocissima. Ringraziamo anche Omar Sanseviero per i consigli sulla raccolta dei dati e per i suoi numerosi suggerimenti preziosi per migliorare il demo. Infine, siamo grati ad Abubakar Abid e al team di Gradio per aver creato un’esperienza di sviluppo deliziosa con i nuovi componenti di codice, e per aver condiviso la loro esperienza nella creazione di ottimi demo.

  • Codice: https://github.com/bigcode-project/starcoder/tree/main/chat
  • Dataset di addestramento filtrato: https://huggingface.co/datasets/HuggingFaceH4/oasst1_en
  • Dataset di valutazione del codice: https://huggingface.co/datasets/HuggingFaceH4/code_evaluation_prompts
  • Modello: https://huggingface.co/HuggingFaceH4/starchat-alpha

Citazione

Per citare questo lavoro, utilizzare la seguente citazione:

@article{Tunstall2023starchat-alpha,
  author = {Tunstall, Lewis and Lambert, Nathan and Rajani, Nazneen and Beeching, Edward and Le Scao, Teven and von Werra, Leandro and Han, Sheon and Schmid, Philipp and Rush, Alexander},
  title = {Creazione di un assistente di codifica con StarCoder},
  journal = {Hugging Face Blog},
  year = {2023},
  note = {https://huggingface.co/blog/starchat-alpha},
}