Cosa fare quando si riceve un errore
In questa sezione esamineremo alcuni errori comuni che possono verificarsi quando si cerca di generare previsioni dal modello Transformer appena affinato. Questo ti preparerà alla sezione 4, in cui esploreremo come eseguire il debug della fase di training.
Per questa sezione, abbiamo preparato un template repository del modello e, se vuoi eseguire il codice di questo capitolo, dovrai prima copiare il modello nel tuo account su Hugging Face Hub. Per farlo, occorre innanzitutto effettuare il login eseguendo una delle seguenti operazioni in un Jupyter notebook:
from huggingface_hub import notebook_login
notebook_login()
o il seguente nel tuo terminale preferito:
huggingface-cli login
Questo chiederà di inserire il nome utente e la password e salverà un token in ~/.cache/huggingface/. Una volta effettuato l’accesso, è possibile copiare il template repository con la seguente funzione:
from distutils.dir_util import copy_tree
from huggingface_hub import Repository, snapshot_download, create_repo, get_full_repo_name
def copy_repository_template():
# Clone the repo and extract the local path
template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28"
commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4"
template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash)
# Create an empty repo on the Hub
model_name = template_repo_id.split("/")[1]
create_repo(model_name, exist_ok=True)
# Clone the empty repo
new_repo_id = get_full_repo_name(model_name)
new_repo_dir = model_name
repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id)
# Copy files
copy_tree(template_repo_dir, new_repo_dir)
# Push to Hub
repo.push_to_hub()
A questo punto, quando si esegue copy_repository_template()
, verrà creata una copia del template repository nel proprio account.
Fare il debug della pipeline di 🤗 Transformers
Per iniziare il nostro viaggio nel fantastico mondo del debug dei modelli Transformer, considera lo scenario seguente: stai lavorando con un/a collega a un progetto di risposta alle domande per aiutare i clienti di un sito web di e-commerce a trovare risposte sui prodotti di consumo. Il/La collega ti invia un messaggio del tipo:
Ciao! Ho appena fatto un esperimento utilizzando le tecniche del Capitolo 7 del corso di Hugging Face e ho ottenuto ottimi risultati su SQuAD! Penso che possiamo usare questo modello come punto di partenza per il nostro progetto. L’ID del modello sull’Hub è “lewtun/distillbert-base-uncased-finetuned-squad-d5716d28”. Provalo pure :)
e la prima cosa che pensi è di caricare il modello usando la pipeline
di 🤗 Transformers:
from transformers import pipeline
model_checkpoint = get_full_repo_name("distillbert-base-uncased-finetuned-squad-d5716d28")
reader = pipeline("question-answering", model=model_checkpoint)
"""
OSError: Can't load config for 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28'. Make sure that:
- 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models'
- or 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file
"""
Oh no, sembra che qualcosa sia andato storto! Se sei alle prime armi con la programmazione, questo tipo di errori può sembrare un po’ criptico all’inizio (cos’è un OSError
?!). L’errore visualizzato qui è solo l’ultima parte di un messaggio di errore molto più ampio, chiamato Python traceback (anche detto stack trace). Per esempio, se si esegue questo codice su Google Colab, si dovrebbe vedere qualcosa di simile alla seguente schermata:
Questi messaggi contengono molte informazioni, quindi analizziamo insieme le parti principali. La prima cosa da notare è che i traceback devono essere letti dal basso verso l’alto. Questo può sembrare strano se si è abituati a leggere dall’alto verso il basso, ma riflette il fatto che il traceback mostra la sequenza di chiamate delle funzioni che la pipeline
effettua quando scarica il modello e il tokenizer. (Dai un’occhiata al Capitolo 2 per maggiori dettagli su come funziona la pipeline
.)
🚨 Hai notato quel riquadro blu intorno a “6 frames” nel traceback di Google Colab? È una funzionalità speciale di Colab, che comprime il traceback in “frame”. Se non riesci a trovare l’origine di un errore, assicurati di espandere l’intero traceback facendo clic su quelle due piccole frecce.
Ciò significa che l’ultima riga del traceback indica l’ultimo messaggio di errore e fornisce il nome dell’eccezione sollevata. In questo caso, il tipo di eccezione è OSError
, che indica un errore legato al sistema. Leggendo il messaggio di errore, si può notare che sembra esserci un problema con il file config.json del modello e vengono forniti due suggerimenti per risolverlo:
"""
Make sure that:
- 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models'
- or 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file
"""
💡 Se vedi un messaggio di errore che è difficile da capire, copia e incolla il messaggio nella barra di ricerca di Google o di Stack Overflow (sì, davvero!). C’è una buona probabilità che non sei la prima persona a riscontrare l’errore, e questo è un buon modo per trovare le soluzioni pubblicate da altri utenti della community. Ad esempio, cercando OSError: Can't load config for
su Stack Overflow si ottengono diversi risultati che possono essere usati come punto di partenza per risolvere il problema.
Il primo suggerimento ci chiede di verificare se l’ID del modello è effettivamente corretto, quindi la prima cosa da fare è copiare l’identificativo e incollarlo nella barra di ricerca dell’Hub:
Mmm, sembra proprio che il modello del/la nostro/a collega non sia sull’Hub… aha, ma c’è un errore di battitura nel nome del modello! DistilBERT ha solo una “l” nel suo nome, quindi correggiamolo e cerchiamo invece “lewtun/distilbert-base-uncased-finetuned-squad-d5716d28”:
Ok, questo c’è. Ora proviamo a scaricare di nuovo il modello con l’ID corretto:
model_checkpoint = get_full_repo_name("distilbert-base-uncased-finetuned-squad-d5716d28")
reader = pipeline("question-answering", model=model_checkpoint)
"""
OSError: Can't load config for 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28'. Make sure that:
- 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models'
- or 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file
"""
Argh, è fallito ancora - benvenuti nella routine quotidiana di un machine learning engineer! Poiché abbiamo aggiustato l’ID del modello, il problema deve essere nel repository stesso. Un modo rapido per accedere al contenuto di un repository sul 🤗 Hub è la funzione list_repo_files()
della libreria huggingface_hub
:
from huggingface_hub import list_repo_files
list_repo_files(repo_id=model_checkpoint)
['.gitattributes', 'README.md', 'pytorch_model.bin', 'special_tokens_map.json', 'tokenizer_config.json', 'training_args.bin', 'vocab.txt']
Interessante: non sembra esserci un file config.json nel repository! Non c’è da stupirsi che la nostra pipeline
non riesca a caricare il modello; il/la nostro/a collega deve aver dimenticato di inserire questo file nell’Hub dopo averlo affinato. In questo caso, il problema sembra abbastanza semplice da risolvere: potremmo chiedere loro di aggiungere il file, oppure, dato che possiamo vedere dall’ID del modello che il modello pre-addestrato usato è distilbert-base-uncased
, possiamo scaricare la configurazione di questo modello e inserirla nel nostro repository per vedere se questo risolve il problema. Proviamo. Utilizzando le tecniche apprese nel Capitolo 2, possiamo scaricare la configurazione del modello con la classe AutoConfig
:
from transformers import AutoConfig
pretrained_checkpoint = "distilbert-base-uncased"
config = AutoConfig.from_pretrained(pretrained_checkpoint)
🚨 L’approccio che stiamo adottando non è infallibile, poiché il/la nostro/a collega potrebbe aver modificato la configurazione di distilbert-base-uncased
prima di affinare il modello. Nella vita reale, dovremmo verificare prima con loro, ma per lo scopo di questa sezione assumeremo che abbiano usato la configurazione predefinita.
Possiamo quindi inviarlo al nostro repository del modello con la funzione push_to_hub()
della configurazione:
config.push_to_hub(model_checkpoint, commit_message="Add config.json")
Ora possiamo verificare se ha funzionato, caricando il modello dall’ultimo commit del ramo main
:
reader = pipeline("question-answering", model=model_checkpoint, revision="main")
context = r"""
Extractive Question Answering is the task of extracting an answer from a text
given a question. An example of a question answering dataset is the SQuAD
dataset, which is entirely based on that task. If you would like to fine-tune a
model on a SQuAD task, you may leverage the
examples/pytorch/question-answering/run_squad.py script.
🤗 Transformers is interoperable with the PyTorch, TensorFlow, and JAX
frameworks, so you can use your favourite tools for a wide variety of tasks!
"""
question = "What is extractive question answering?"
reader(question=question, context=context)
{'score': 0.38669535517692566,
'start': 34,
'end': 95,
'answer': 'the task of extracting an answer from a text given a question'}
Woohoo, ha funzionato! Riassumiamo quello che hai appena imparato:
- I messaggi di errore in Python sono noti come traceback e vengono letti dal basso verso l’alto. L’ultima riga del messaggio di errore di solito contiene le informazioni necessarie per individuare l’origine del problema.
- Se l’ultima riga non contiene informazioni sufficienti, risali il traceback e vedi se riesci a identificare in quale punto del codice sorgente si verifica l’errore.
- Se nessuno dei messaggi di errore può aiutare a individuare il problema, provare a cercare online una soluzione a un problema simile.
- Il `huggingface_hub’ // 🤗 Hub? fornisce una serie di strumenti che si possono usare per interagire e fare il debug dei repository su Hub.
Ora che sappiamo come eseguire il debug di una pipeline, diamo un’occhiata a un esempio più complicato nel forward pass del modello stesso.
Debug del forward pass del modello
Sebbene la pipeline
sia ottima per la maggior parte delle applicazioni in cui è necessario generare previsioni rapidamente, a volte è necessario accedere ai logit del modello (ad esempio, se si desidera applicare un post-processing personalizzato). Per vedere cosa potrebbe andare storto in questo caso, iniziamo prendendo il modello e il tokenizer dalla nostra pipeline
:
tokenizer = reader.tokenizer model = reader.model
Poi abbiamo bisogno di una domanda, quindi vediamo se i nostri framework preferiti sono supportati:
question = "Which frameworks can I use?"
Come abbiamo visto nel Capitolo 7, i passaggi tipici da svolgere sono la tokenizzazione degli input, l’estrazione dei logit dei token iniziali e finali e la decodifica dell’intervallo di risposta:
import torch
inputs = tokenizer(question, context, add_special_tokens=True)
input_ids = inputs["input_ids"][0]
outputs = model(**inputs)
answer_start_scores = outputs.start_logits
answer_end_scores = outputs.end_logits
# Get the most likely beginning of answer with the argmax of the score
answer_start = torch.argmax(answer_start_scores)
# Get the most likely end of answer with the argmax of the score
answer_end = torch.argmax(answer_end_scores) + 1
answer = tokenizer.convert_tokens_to_string(
tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end])
)
print(f"Question: {question}")
print(f"Answer: {answer}")
"""
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
/var/folders/28/k4cy5q7s2hs92xq7_h89_vgm0000gn/T/ipykernel_75743/2725838073.py in <module>
1 inputs = tokenizer(question, text, add_special_tokens=True)
2 input_ids = inputs["input_ids"]
----> 3 outputs = model(**inputs)
4 answer_start_scores = outputs.start_logits
5 answer_end_scores = outputs.end_logits
~/miniconda3/envs/huggingface/lib/python3.8/site-packages/torch/nn/modules/module.py in _call_impl(self, *input, **kwargs)
1049 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
1050 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1051 return forward_call(*input, **kwargs)
1052 # Do not call functions when jit is used
1053 full_backward_hooks, non_full_backward_hooks = [], []
~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, start_positions, end_positions, output_attentions, output_hidden_states, return_dict)
723 return_dict = return_dict if return_dict is not None else self.config.use_return_dict
724
--> 725 distilbert_output = self.distilbert(
726 input_ids=input_ids,
727 attention_mask=attention_mask,
~/miniconda3/envs/huggingface/lib/python3.8/site-packages/torch/nn/modules/module.py in _call_impl(self, *input, **kwargs)
1049 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
1050 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1051 return forward_call(*input, **kwargs)
1052 # Do not call functions when jit is used
1053 full_backward_hooks, non_full_backward_hooks = [], []
~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, output_attentions, output_hidden_states, return_dict)
471 raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time")
472 elif input_ids is not None:
--> 473 input_shape = input_ids.size()
474 elif inputs_embeds is not None:
475 input_shape = inputs_embeds.size()[:-1]
AttributeError: 'list' object has no attribute 'size'
"""
Oh cielo, sembra che ci sia un bug nel nostro codice! Ma non avere paura di un po’ di debug. Puoi usare il debugger di Python in un notebook:
o dal terminale:
Qui, leggendo il messaggio di errore vediamo che l’oggetto 'list'
non ha attributo 'size'
, e possiamo vedere una -->
freccia che punta alla riga in cui il problema è stato sollevato in model(**inputs)
. Possiamo eseguire il debug interattivo utilizzando il debugger di Python, ma per ora ci limiteremo a stampare una parte di input
per vedere cosa abbiamo:
inputs["input_ids"][:5]
[101, 2029, 7705, 2015, 2064]
Questo sembra certamente una normale list
di Python, ma ricontrolliamo il type
:
type(inputs["input_ids"])
list
Sì, questa è sicuramente una list
di Python. Cos’è andato storto? Ricordiamo dal Capitolo 2 che le classi AutoModelForXxx
in 🤗 Transformers operano su tensori (sia in PyTorch che in TensorFlow), e un’operazione comune è quella di estrarre le dimensioni di un tensore usando Tensor.size()
in PyTorch, per esempio. Diamo un’altra occhiata al traceback, per vedere quale riga ha causato l’eccezione:
~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, output_attentions, output_hidden_states, return_dict)
471 raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time")
472 elif input_ids is not None:
--> 473 input_shape = input_ids.size()
474 elif inputs_embeds is not None:
475 input_shape = inputs_embeds.size()[:-1]
AttributeError: 'list' object has no attribute 'size'
Sembra che il nostro codice abbia provato a chiamare input_ids.size()
, ma questo chiaramente non funziona per una list
di Python, che è solo un container. Come possiamo risolvere questo problema? Cercando il messaggio di errore su Stack Overflow si ottengono alcuni risultati pertinenti. Cliccando sul primo, viene visualizzata una domanda simile alla nostra, con la risposta mostrata nello screenshot seguente:
La risposta raccomanda di aggiungere return_tensors='pt'
al tokenizer, quindi proviamo se funziona:
inputs = tokenizer(question, context, add_special_tokens=True, return_tensors="pt")
input_ids = inputs["input_ids"][0]
outputs = model(**inputs)
answer_start_scores = outputs.start_logits
answer_end_scores = outputs.end_logits
# Get the most likely beginning of answer with the argmax of the score
answer_start = torch.argmax(answer_start_scores)
# Get the most likely end of answer with the argmax of the score
answer_end = torch.argmax(answer_end_scores) + 1
answer = tokenizer.convert_tokens_to_string(
tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end])
)
print(f"Question: {question}")
print(f"Answer: {answer}")
"""
Question: Which frameworks can I use?
Answer: pytorch, tensorflow, and jax
"""
Bene, ha funzionato! Questo è un ottimo esempio di quanto possa essere utile Stack Overflow: identificando un problema simile, abbiamo potuto beneficiare dell’esperienza di altri membri della community. Tuttavia, una ricerca come questa non sempre produce una risposta pertinente, quindi cosa si può fare in questi casi? Fortunatamente, sul forum di Hugging Face c’è un’accogliente community di sviluppatori che può aiutarti! Nella prossima sezione, vedremo come creare bene delle domande sul forum che abbiano buona probabilità di ricevere una risposta.