LLM Course documentation
Răspuns la întrebări
Răspuns la întrebări
Este timpul să analizăm răspunsul la întrebări! Această sarcină are mai multe variante, dar cea pe care ne vom concentra în această secțiune se numește răspuns extractiv la întrebări. Aceasta presupune formularea de întrebări cu privire la un document și identificarea răspunsurilor ca intervale de text în documentul în sine.
Vom face fine-tuning unui-model BERT pe [datasetul SQuAD] (https://rajpurkar.github.io/SQuAD-explorer/), care constă din întrebări adresate de mulțimea de lucrători pe un set de articole Wikipedia. Acest lucru ne va oferi un model capabil să calculeze predicții precum aceasta:
Aceasta este de fapt o prezentare a modelului care a fost antrenat și încărcat în Hub folosind codul prezentat în această secțiune. Puteți să-l găsiți și să verificați predicțiile aici.
💡 Modelele bazate doar pe encoding, cum ar fi BERT, tind să fie foarte bune la extragerea răspunsurilor la întrebări de tip factoid, cum ar fi “Cine a inventat arhitectura Transformer?”, dar nu se descurcă prea bine atunci când primesc întrebări deschise, cum ar fi “De ce este cerul albastru?” În aceste cazuri mai dificile, modelele encoder-decoder precum T5 și BART sunt utilizate de obicei pentru a sintetiza informațiile într-un mod destul de similar cu rezumarea textului. Dacă sunteți interesat de acest tip de răspuns generativ la întrebări, vă recomandăm să consultați demo-ul nostru bazat pe datasetul ELI5.
Pregătirea datelor
Datasetul care este cel mai utilizat ca referință academică pentru răspunderea extractivă la întrebări este SQuAD, deci acesta este cel pe care îl vom utiliza aici. Există, de asemenea, un benchmark mai dificil SQuAD v2, care include întrebări care nu au un răspuns. Atât timp cât propriul dataset conține o coloană pentru contexte, o coloană pentru întrebări și o coloană pentru răspunsuri, ar trebui să puteți adapta pașii de mai jos.
Datasetul SQuD
Ca de obicei, putem descărca și stoca în cache datasetul într-un singur pas datorită funcției load_dataset()
:
from datasets import load_dataset
raw_datasets = load_dataset("squad")
Ne putem uita apoi la acest obiect pentru a afla mai multe despre datasetul SQuAD:
raw_datasets
DatasetDict({
train: Dataset({
features: ['id', 'title', 'context', 'question', 'answers'],
num_rows: 87599
})
validation: Dataset({
features: ['id', 'title', 'context', 'question', 'answers'],
num_rows: 10570
})
})
Se pare că avem tot ce ne trebuie cu câmpurile context
, question
și answers
, așa că să le afișăm pentru primul element al datasetului nostru de antrenare:
print("Context: ", raw_datasets["train"][0]["context"])
print("Question: ", raw_datasets["train"][0]["question"])
print("Answer: ", raw_datasets["train"][0]["answers"])
Context: 'Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend "Venite Ad Me Omnes". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive (and in a direct line that connects through 3 statues and the Gold Dome), is a simple, modern stone statue of Mary.'
Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?'
Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]}
Câmpurile context
și question
sunt foarte simplu de utilizat. Câmpul answers
este un pic mai complicat, deoarece conține un dicționar cu două câmpuri care sunt ambele liste. Acesta este formatul care va fi așteptat de metrica squad
în timpul evaluării; dacă utilizați propriile date, nu trebuie neapărat să vă faceți griji cu privire la plasarea răspunsurilor în același format. Câmpul text
este destul de evident, iar câmpul answer_start
conține indicele caracterului de început al fiecărui răspuns din context.
În timpul antrenamentului, există un singur răspuns posibil. Putem verifica acest lucru folosind metoda Dataset.filter()
:
raw_datasets["train"].filter(lambda x: len(x["answers"]["text"]) != 1)
Dataset({
features: ['id', 'title', 'context', 'question', 'answers'],
num_rows: 0
})
Cu toate acestea, pentru evaluare, există mai multe răspunsuri posibile pentru fiecare sample, care pot fi identice sau diferite:
print(raw_datasets["validation"][0]["answers"])
print(raw_datasets["validation"][2]["answers"])
{'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]}
{'text': ['Santa Clara, California', "Levi's Stadium", "Levi's Stadium in the San Francisco Bay Area at Santa Clara, California."], 'answer_start': [403, 355, 355]}
Nu ne vom aprofunda în scriptul de evaluare, deoarece totul va fi încorporat de o metrică 🤗 Datasets pentru noi, dar versiunea scurtă este că unele dintre întrebări au mai multe răspunsuri posibile, iar acest script va compara un răspuns prezis cu toate răspunsurile acceptabile și va lua cel mai bun scor. Dacă ne uităm la sampleul de la indexul 2, de exemplu:
print(raw_datasets["validation"][2]["context"])
print(raw_datasets["validation"][2]["question"])
'Super Bowl 50 was an American football game to determine the champion of the National Football League (NFL) for the 2015 season. The American Football Conference (AFC) champion Denver Broncos defeated the National Football Conference (NFC) champion Carolina Panthers 24–10 to earn their third Super Bowl title. The game was played on February 7, 2016, at Levi\'s Stadium in the San Francisco Bay Area at Santa Clara, California. As this was the 50th Super Bowl, the league emphasized the "golden anniversary" with various gold-themed initiatives, as well as temporarily suspending the tradition of naming each Super Bowl game with Roman numerals (under which the game would have been known as "Super Bowl L"), so that the logo could prominently feature the Arabic numerals 50.'
'Where did Super Bowl 50 take place?'
putem vedea că răspunsul poate fi într-adevăr una dintre cele trei posibilități pe care le-am văzut anterior.
Procesarea datelor de antrenare
Să începem cu preprocesarea datelor de antrenare. Partea dificilă va fi generarea labelurilor pentru răspunsul la întrebare, care vor fi pozițiile de început și de sfârșit ale tokenilor corespunzătoare răspunsului în context.
Dar să nu ne grăbim. În primul rând, trebuie să convertim textul din datele de intrare în ID-uri pe care modelul să le poată înțelege, utilizând un tokenizer:
from transformers import AutoTokenizer
model_checkpoint = "bert-base-cased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
După cum am menționat anterior, vom face fine-tune unui model BERT, dar puteți utiliza orice alt tip de model, atâta timp cât are implementat un tokenizer rapid. Puteți vedea toate arhitecturile care vin cu o versiune rapidă în [acest tabel mare] (https://huggingface.co/transformers/#supported-frameworks), iar pentru a verifica dacă obiectul tokenizer
pe care îl utilizați este într-adevăr susținut de 🤗 Tokenizers, vă puteți uita la atributul său is_fast
:
tokenizer.is_fast
True
Putem transmite împreună întrebarea și contextul către tokenizerul nostru, iar acesta va introduce în mod corespunzător tokenii speciali pentru a forma o propoziție ca aceasta:
[CLS] question [SEP] context [SEP]
Hai să verificăm de două ori:
context = raw_datasets["train"][0]["context"]
question = raw_datasets["train"][0]["question"]
inputs = tokenizer(question, context)
tokenizer.decode(inputs["input_ids"])
'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Architecturally, '
'the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin '
'Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms '
'upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred '
'Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a '
'replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette '
'Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 statues '
'and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]'
Labelurile vor fi apoi indexul tokenilor care încep și termină răspunsul, iar modelul va fi însărcinat să prezică un logit de început și de sfârșit pentru fiecare token din intrare, labelurile teoretice fiind următoarele:
În acest caz, contextul nu este prea lung, dar unele dintre exemplele din dataset au contexte foarte lungi care vor depăși lungimea maximă pe care am stabilit-o (care este de 384 în acest caz). După cum am văzut în Capitolul 6 când am explorat elementele interne ale pipelineului question-answering
, vom trata contextele lungi prin crearea mai multor caracteristici de antrenare dintr-un sample din datasetul nostru, cu un sliding window între ele.
Pentru a vedea cum funcționează acest lucru folosind exemplul curent, putem limita lungimea la 100 și putem utiliza un sliding window de 50 de tokeni. Vă reamintim că folosim:
max_length
pentru a stabili lungimea maximă (aici 100)truncation="only_second"
pentru a trunchia contextul (care este în poziția a doua) atunci când întrebarea cu contextul său este prea lungăstride
pentru a seta numărul de tokeni care se suprapun între două bucăți succesive (aici 50)return_overflowing_tokens=True
pentru ca tokenizerul să știe că dorim tokenii care se suprapun
inputs = tokenizer(
question,
context,
max_length=100,
truncation="only_second",
stride=50,
return_overflowing_tokens=True,
)
for ids in inputs["input_ids"]:
print(tokenizer.decode(ids))
'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basi [SEP]'
'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin [SEP]'
'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 [SEP]'
'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP]. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 statues and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]'
După cum se poate observa, exemplul nostru a fost împărțit în patru inputuri, fiecare dintre acestea conținând întrebarea și o parte din context. Rețineți că răspunsul la întrebare (“Bernadette Soubirous”) apare doar în al treilea și ultimul input, astfel încât, prin tratarea contextelor lungi în acest mod, vom crea câteva exemple de antrenament în care răspunsul nu este inclus în context. Pentru aceste exemple, labelurile vor fi start_position = end_position = 0
(deci vom prezice tokenul [CLS]
). Vom seta aceste labeluri și în cazul nefericit în care răspunsul a fost trunchiat, astfel încât avem doar începutul (sau sfârșitul) acestuia. Pentru exemplele în care răspunsul este complet în context, labelurile vor fi indicele tokenului în care începe răspunsul și indicele tokenului în care se termină răspunsul.
Datasetul ne oferă caracterul de început al răspunsului în context, iar prin adăugarea lungimii răspunsului, putem găsi caracterul de sfârșit în context. Pentru a le corela cu indicii tokenilor, va trebui să folosim offset mapping pe care le-am studiat în Capitolul 6. Putem face ca tokenizatorul nostru să le returneze trecând return_offsets_mapping=True
:
inputs = tokenizer(
question,
context,
max_length=100,
truncation="only_second",
stride=50,
return_overflowing_tokens=True,
return_offsets_mapping=True,
)
inputs.keys()
dict_keys(['input_ids', 'token_type_ids', 'attention_mask', 'offset_mapping', 'overflow_to_sample_mapping'])
După cum putem vedea, primim înapoi ID-urile obișnuite de intrare, ID-urile tipului de token și attention maskul, precum și offset mapping necesar și o cheie suplimentară, overflow_to_sample_mapping
. Valoarea corespunzătoare ne va fi de folos atunci când vom tokeniza mai multe texte în același timp (ceea ce ar trebui să facem pentru a beneficia de faptul că tokenizerul nostru este susținut de Rust). Deoarece un sample poate oferi mai multe caracteristici, aceasta mapează fiecare caracteristică la exemplul din care provine. Deoarece aici am tokenizat un singur exemplu, obținem o listă de 0
:
inputs["overflow_to_sample_mapping"]
[0, 0, 0, 0]
Dar dacă vom tokeniza mai multe exemple, acest lucru va deveni mai util:
inputs = tokenizer(
raw_datasets["train"][2:6]["question"],
raw_datasets["train"][2:6]["context"],
max_length=100,
truncation="only_second",
stride=50,
return_overflowing_tokens=True,
return_offsets_mapping=True,
)
print(f"The 4 examples gave {len(inputs['input_ids'])} features.")
print(f"Here is where each comes from: {inputs['overflow_to_sample_mapping']}.")
'The 4 examples gave 19 features.'
'Here is where each comes from: [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3].'
După cum se poate observa, primele trei exemple (la indicii 2, 3 și 4 din setul de antrenare) au dat fiecare câte patru caracteristici, iar ultimul exemplu (la indicele 5 din setul de antrenare) a dat 7 caracteristici.
Aceste informații vor fi utile pentru a corela fiecare caracteristică obținută cu labeul corespunzător. După cum am menționat anterior, aceste labelurile sunt:
(0, 0)
dacă răspunsul nu se află în intervalul corespunzător al contextului(start_position, end_position)
dacă răspunsul se află în intervalul corespunzător al contextului, custart_position
fiind indicele tokenului (în ID-urile de intrare) la începutul răspunsului șiend_position
fiind indicele tokenului (în ID-urile de intrare) unde se termină răspunsul
Pentru a determina care dintre acestea este cazul și, dacă este relevant, pozițiile tokenilor, vom găsi mai întâi indicii care încep și termină contextul în ID-urile de intrare. Am putea folosi ID-urile tipului de token pentru a face acest lucru, dar deoarece acestea nu există neapărat pentru toate modelele (DistilBERT nu le solicită, de exemplu), vom folosi în schimb metoda sequence_ids()
a BatchEncoding
pe care tokenizerul nostru o returnează.
Odată ce avem indicii tokenilor, ne uităm la offseturile corespunzătoare, care sunt tupeluri de două numere întregi reprezentând intervalul de caractere din contextul original. Astfel, putem detecta dacă bucățica de context din această caracteristică începe după răspuns sau se termină înainte de începerea răspunsului (caz în care eticheta este (0, 0)
). Dacă nu este cazul, facem o buclă pentru a găsi primul și ultimul token al răspunsului:
answers = raw_datasets["train"][2:6]["answers"]
start_positions = []
end_positions = []
for i, offset in enumerate(inputs["offset_mapping"]):
sample_idx = inputs["overflow_to_sample_mapping"][i]
answer = answers[sample_idx]
start_char = answer["answer_start"][0]
end_char = answer["answer_start"][0] + len(answer["text"][0])
sequence_ids = inputs.sequence_ids(i)
# Find the start and end of the context
idx = 0
while sequence_ids[idx] != 1:
idx += 1
context_start = idx
while sequence_ids[idx] == 1:
idx += 1
context_end = idx - 1
# If the answer is not fully inside the context, label is (0, 0)
if offset[context_start][0] > start_char or offset[context_end][1] < end_char:
start_positions.append(0)
end_positions.append(0)
else:
# Otherwise it's the start and end token positions
idx = context_start
while idx <= context_end and offset[idx][0] <= start_char:
idx += 1
start_positions.append(idx - 1)
idx = context_end
while idx >= context_start and offset[idx][1] >= end_char:
idx -= 1
end_positions.append(idx + 1)
start_positions, end_positions
([83, 51, 19, 0, 0, 64, 27, 0, 34, 0, 0, 0, 67, 34, 0, 0, 0, 0, 0],
[85, 53, 21, 0, 0, 70, 33, 0, 40, 0, 0, 0, 68, 35, 0, 0, 0, 0, 0])
Să aruncăm o privire la câteva rezultate pentru a verifica dacă abordarea noastră este corectă. Pentru prima caracteristică găsim (83, 85)
ca labeluri, așa că comparăm răspunsul teoretic cu intervalul decodat de tokeni de la 83 la 85 (inclusiv):
idx = 0
sample_idx = inputs["overflow_to_sample_mapping"][idx]
answer = answers[sample_idx]["text"][0]
start = start_positions[idx]
end = end_positions[idx]
labeled_answer = tokenizer.decode(inputs["input_ids"][idx][start : end + 1])
print(f"Theoretical answer: {answer}, labels give: {labeled_answer}")
'Theoretical answer: the Main Building, labels give: the Main Building'
Deci, asta e o potrivire! Acum să verificăm indexul 4, unde am setat labelurile la (0, 0)
, ceea ce înseamnă că răspunsul nu se află în chunkul de context al acelei caracteristici:
idx = 4
sample_idx = inputs["overflow_to_sample_mapping"][idx]
answer = answers[sample_idx]["text"][0]
decoded_example = tokenizer.decode(inputs["input_ids"][idx])
print(f"Theoretical answer: {answer}, decoded example: {decoded_example}")
'Theoretical answer: a Marian place of prayer and reflection, decoded example: [CLS] What is the Grotto at Notre Dame? [SEP] Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grot [SEP]'
Într-adevăr, nu vedem răspunsul în interiorul contextului.
✏️ E rândul tău! Atunci când se utilizează arhitectura XLNet, paddingul este aplicat la stânga, iar întrebarea și contextul sunt schimbate. Adaptați tot codul pe care tocmai l-am văzut la arhitectura XLNet (și adăugați padding=True
). Fiți conștienți de faptul că tokenul [CLS]
ar putea să nu se afle la poziția 0 în cazul aplicării paddingului.
Acum că am văzut pas cu pas cum să preprocesăm datele de antrenare, le putem grupa într-o funcție pe care o vom aplica întregului dataset de antrenare. Vom umple fiecare caracteristică la lungimea maximă pe care am stabilit-o, deoarece majoritatea contextelor vor fi lungi (iar sampleurile corespunzătoare vor fi împărțite în mai multe caracteristici), astfel încât nu există niciun beneficiu real pentru aplicarea paddingului dinamic aici:
max_length = 384
stride = 128
def preprocess_training_examples(examples):
questions = [q.strip() for q in examples["question"]]
inputs = tokenizer(
questions,
examples["context"],
max_length=max_length,
truncation="only_second",
stride=stride,
return_overflowing_tokens=True,
return_offsets_mapping=True,
padding="max_length",
)
offset_mapping = inputs.pop("offset_mapping")
sample_map = inputs.pop("overflow_to_sample_mapping")
answers = examples["answers"]
start_positions = []
end_positions = []
for i, offset in enumerate(offset_mapping):
sample_idx = sample_map[i]
answer = answers[sample_idx]
start_char = answer["answer_start"][0]
end_char = answer["answer_start"][0] + len(answer["text"][0])
sequence_ids = inputs.sequence_ids(i)
# Find the start and end of the context
idx = 0
while sequence_ids[idx] != 1:
idx += 1
context_start = idx
while sequence_ids[idx] == 1:
idx += 1
context_end = idx - 1
# If the answer is not fully inside the context, label is (0, 0)
if offset[context_start][0] > start_char or offset[context_end][1] < end_char:
start_positions.append(0)
end_positions.append(0)
else:
# Otherwise it's the start and end token positions
idx = context_start
while idx <= context_end and offset[idx][0] <= start_char:
idx += 1
start_positions.append(idx - 1)
idx = context_end
while idx >= context_start and offset[idx][1] >= end_char:
idx -= 1
end_positions.append(idx + 1)
inputs["start_positions"] = start_positions
inputs["end_positions"] = end_positions
return inputs
Rețineți că am definit două constante pentru a determina lungimea maximă utilizată, precum și lungimea al sliding window, și că am adăugat o mică curățare înainte de tokenizare: unele dintre întrebările din datasetul SQuAD au spații suplimentare la început și la sfârșit care nu adaugă nimic (și ocupă spațiu atunci când sunt tokenizate dacă utilizați un model precum RoBERTa), așa că am eliminat aceste spații suplimentare.
Pentru a aplica această funcție întregului set de antrenare, folosim metoda Dataset.map()
cu flagul batched=True
. Acesta este necesar aici, deoarece modificăm lungimea datasetului (deoarece un exemplu poate oferi mai multe caracteristici de antrenare):
train_dataset = raw_datasets["train"].map(
preprocess_training_examples,
batched=True,
remove_columns=raw_datasets["train"].column_names,
)
len(raw_datasets["train"]), len(train_dataset)
(87599, 88729)
După cum putem vedea, preprocesarea a adăugat aproximativ 1.000 de caracteristici. Setul nostru de antrenare este acum gata de utilizare - să trecem la preprocesarea setului de validare!
Procesarea datelor de validare
Preprocesarea datelor de validare va fi puțin mai ușoară, deoarece nu trebuie să generăm labeluri (cu excepția cazului în care dorim să calculăm o pierdere de validare, dar acest număr nu ne va ajuta să înțelegem cât de bun este modelul). Adevărata bucurie va fi să interpretăm predicțiile modelului în intervale ale contextului original. Pentru aceasta, va trebui doar să stocăm atât offset mappings, cât și o modalitate de a corela fiecare caracteristică creată cu exemplul original din care provine. Deoarece există o coloană ID în datasetul original, vom utiliza acel ID.
Singurul lucru pe care îl vom adăuga aici este o mică curățare a offset mappings. Acestea vor conține offseturi pentru întrebare și context, dar odată ajunși în etapa de postprocesare nu vom avea nicio modalitate de a ști care parte a ID-urilor de intrare corespunde contextului și care parte este întrebarea (metoda sequence_ids()
pe care am folosit-o este disponibilă doar pentru ieșirea tokenizerului). Prin urmare, vom seta offseturile corespunzătoare întrebării la None
:
def preprocess_validation_examples(examples):
questions = [q.strip() for q in examples["question"]]
inputs = tokenizer(
questions,
examples["context"],
max_length=max_length,
truncation="only_second",
stride=stride,
return_overflowing_tokens=True,
return_offsets_mapping=True,
padding="max_length",
)
sample_map = inputs.pop("overflow_to_sample_mapping")
example_ids = []
for i in range(len(inputs["input_ids"])):
sample_idx = sample_map[i]
example_ids.append(examples["id"][sample_idx])
sequence_ids = inputs.sequence_ids(i)
offset = inputs["offset_mapping"][i]
inputs["offset_mapping"][i] = [
o if sequence_ids[k] == 1 else None for k, o in enumerate(offset)
]
inputs["example_id"] = example_ids
return inputs
Putem aplica această funcție pe întregul dataset de validare, ca și înainte:
validation_dataset = raw_datasets["validation"].map(
preprocess_validation_examples,
batched=True,
remove_columns=raw_datasets["validation"].column_names,
)
len(raw_datasets["validation"]), len(validation_dataset)
(10570, 10822)
În acest caz, am adăugat doar câteva sute de sampleuri, astfel încât se pare că contextele din datasetul de validare sunt un pic mai scurte.
Acum că am preprocesat toate datele, putem trece la antrenare.
Fine-tuningul modelului cu API-ul Trainer
Codul de antrenare pentru acest exemplu va semăna foarte mult cu codul din secțiunile anterioare - cel mai greu lucru va fi să scriem funcția compute_metrics()
. Deoarece am făcut padding tuturor sampleurilor la lungimea maximă pe care am stabilit-o, nu trebuie definit niciun data collator, astfel încât acest calcul al metricii este singurul lucru de care trebuie să ne facem griji. Partea dificilă va fi să postprocesăm predicțiile modelului în intervale de text în exemplele originale; odată ce am făcut acest lucru, metrica din biblioteca 🤗 Datasets va face cea mai mare parte a muncii pentru noi.
Post-procesare
Modelul va produce logits pentru pozițiile de început și de sfârșit ale răspunsului în ID-urile de intrare, așa cum am văzut în timpul explorării question-answering
pipeline. Etapa de post-procesare va fi similară cu ceea ce am făcut acolo, așa că iată vă reamintim ce acțiuni am luat:
- Am mascat logiturile de început și de sfârșit corespunzătoare tokenilor din afara contextului.
- Am convertit apoi logiturile de început și de sfârșit în probabilități utilizând un softmax.
- Am atribuit un scor fiecărei perechi
(start_token, end_token)
prin calcularea produsului celor două probabilități corespunzătoare. - Am căutat perechea cu scorul maxim care a dat un răspuns valid (de exemplu,
start_token
mai mic decâtend_token
).
Aici vom schimba ușor acest proces, deoarece nu trebuie să calculăm scorurile reale (doar răspunsul prezis). Aceasta înseamnă că putem sări peste etapa softmax. De asemenea, pentru a merge mai repede, nu vom puncta toate perechile posibile (start_token, end_token)
, ci doar pe cele care corespund celor mai mari n_best
logits (cu n_best=20
). Deoarece vom trece peste softmax, aceste scoruri vor fi scoruri logit și vor fi obținute prin însumarea logiturilor de început și de sfârșit (în loc de produs, datorită regulii).
Pentru a demonstra toate acestea, vom avea nevoie de un fel de predicții. Deoarece nu ne-am antrenat încă modelul, vom utiliza modelul implicit pentru pipelineuri QA pentru a genera unele predicții pe o mică parte a setului de validare. Putem utiliza aceeași funcție de procesare ca înainte; deoarece se bazează pe constanta globală tokenizer
, trebuie doar să schimbăm acest obiect cu tokenizerul modelului pe care dorim să îl utilizăm temporar:
small_eval_set = raw_datasets["validation"].select(range(100))
trained_checkpoint = "distilbert-base-cased-distilled-squad"
tokenizer = AutoTokenizer.from_pretrained(trained_checkpoint)
eval_set = small_eval_set.map(
preprocess_validation_examples,
batched=True,
remove_columns=raw_datasets["validation"].column_names,
)
Acum că preprocesarea este terminată, schimbăm tokenizerul înapoi la cel pe care l-am ales inițial:
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
Apoi eliminăm coloanele din eval_set
care nu sunt așteptate de model, construim un batch cu întregul set de validare și îl trecem prin model. Dacă este disponibil un GPU, îl folosim pentru a merge mai repede:
import torch
from transformers import AutoModelForQuestionAnswering
eval_set_for_model = eval_set.remove_columns(["example_id", "offset_mapping"])
eval_set_for_model.set_format("torch")
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
batch = {k: eval_set_for_model[k].to(device) for k in eval_set_for_model.column_names}
trained_model = AutoModelForQuestionAnswering.from_pretrained(trained_checkpoint).to(
device
)
with torch.no_grad():
outputs = trained_model(**batch)
Deoarece Trainer
ne va oferi predicții sub formă de matrici NumPy, preluăm logiturile de început și de sfârșit și le convertim în acest format:
start_logits = outputs.start_logits.cpu().numpy() end_logits = outputs.end_logits.cpu().numpy()
Acum, trebuie să găsim răspunsul prezis pentru fiecare exemplu din small_eval_set
. Este posibil ca un exemplu să fi fost împărțit în mai multe caracteristici în eval_set
, astfel încât primul pas constă în maparea fiecărui exemplu din small_eval_set
la caracteristicile corespunzătoare din eval_set
:
import collections
example_to_features = collections.defaultdict(list)
for idx, feature in enumerate(eval_set):
example_to_features[feature["example_id"]].append(idx)
Cu acest lucru în mână, ne putem apuca de treabă trecând prin toate exemplele și, pentru fiecare exemplu, prin toate caracteristicile asociate. Așa cum am spus mai devreme, ne vom uita la scorurile logit pentru n_cele mai bune
logits de început și de sfârșit, excluzând pozițiile care dau:
- Un răspuns care nu ar fi în interiorul contextului
- Un răspuns cu lungime negativă
- Un răspuns care este prea lung (limităm posibilitățile la
max_answer_length=30
)
Odată ce avem toate răspunsurile posibile scored pentru un exemplu, îl alegem pe cel cu cel mai bun scor logit:
import numpy as np
n_best = 20
max_answer_length = 30
predicted_answers = []
for example in small_eval_set:
example_id = example["id"]
context = example["context"]
answers = []
for feature_index in example_to_features[example_id]:
start_logit = start_logits[feature_index]
end_logit = end_logits[feature_index]
offsets = eval_set["offset_mapping"][feature_index]
start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist()
end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist()
for start_index in start_indexes:
for end_index in end_indexes:
# Skip answers that are not fully in the context
if offsets[start_index] is None or offsets[end_index] is None:
continue
# Skip answers with a length that is either < 0 or > max_answer_length.
if (
end_index < start_index
or end_index - start_index + 1 > max_answer_length
):
continue
answers.append(
{
"text": context[offsets[start_index][0] : offsets[end_index][1]],
"logit_score": start_logit[start_index] + end_logit[end_index],
}
)
best_answer = max(answers, key=lambda x: x["logit_score"])
predicted_answers.append({"id": example_id, "prediction_text": best_answer["text"]})
Formatul final al răspunsurilor prezise este cel care va fi așteptat de metrica pe care o vom utiliza. Ca de obicei, o putem încărca cu ajutorul bibliotecii 🤗 Evaluate:
import evaluate
metric = evaluate.load("squad")
Această metrică așteaptă răspunsurile prezise în formatul pe care l-am văzut mai sus (o listă de dicționare cu o cheie pentru ID-ul exemplului și o cheie pentru textul prezis) și răspunsurile teoretice în formatul de mai jos (o listă de dicționare cu o cheie pentru ID-ul exemplului și o cheie pentru răspunsurile posibile):
theoretical_answers = [
{"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set
]
Acum putem verifica dacă obținem rezultate rezonabile analizând primul element din ambele liste:
print(predicted_answers[0])
print(theoretical_answers[0])
{'id': '56be4db0acb8001400a502ec', 'prediction_text': 'Denver Broncos'}
{'id': '56be4db0acb8001400a502ec', 'answers': {'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]}}
Nu-i deloc rău! Acum putem arunca o privire la scorul pe care ni-l oferă metrica:
metric.compute(predictions=predicted_answers, references=theoretical_answers)
{'exact_match': 83.0, 'f1': 88.25}
Din nou, acest lucru este destul de bun, având în vedere că, în conformitate cu [documentul acestuia] (https://arxiv.org/abs/1910.01108v2), DistilBERT fine-tuned pe SQuAD obține 79,1 și 86,9 pentru aceste scoruri pe întregul dataset.
Acum să punem tot ce am făcut într-o funcție compute_metrics()
pe care o vom folosi în Trainer
. În mod normal, această funcție compute_metrics()
primește doar un tuple eval_preds
cu logiți și labels. Aici vom avea nevoie de ceva mai mult, deoarece trebuie să căutăm în dataset de caracteristici pentru offset și în datasetul de exemple pentru contextele originale, astfel încât nu vom putea utiliza această funcție pentru a obține rezultate de evaluare regulate în timpul antrenării. O vom utiliza doar la sfârșitul antrenamentului pentru a verifica rezultatele.
Funcția compute_metrics()
grupează aceiași pași ca înainte; adăugăm doar o mică verificare în cazul în care nu obținem niciun răspuns valid (caz în care prezicem un șir gol).
from tqdm.auto import tqdm
def compute_metrics(start_logits, end_logits, features, examples):
example_to_features = collections.defaultdict(list)
for idx, feature in enumerate(features):
example_to_features[feature["example_id"]].append(idx)
predicted_answers = []
for example in tqdm(examples):
example_id = example["id"]
context = example["context"]
answers = []
# Loop through all features associated with that example
for feature_index in example_to_features[example_id]:
start_logit = start_logits[feature_index]
end_logit = end_logits[feature_index]
offsets = features[feature_index]["offset_mapping"]
start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist()
end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist()
for start_index in start_indexes:
for end_index in end_indexes:
# Skip answers that are not fully in the context
if offsets[start_index] is None or offsets[end_index] is None:
continue
# Skip answers with a length that is either < 0 or > max_answer_length
if (
end_index < start_index
or end_index - start_index + 1 > max_answer_length
):
continue
answer = {
"text": context[offsets[start_index][0] : offsets[end_index][1]],
"logit_score": start_logit[start_index] + end_logit[end_index],
}
answers.append(answer)
# Select the answer with the best score
if len(answers) > 0:
best_answer = max(answers, key=lambda x: x["logit_score"])
predicted_answers.append(
{"id": example_id, "prediction_text": best_answer["text"]}
)
else:
predicted_answers.append({"id": example_id, "prediction_text": ""})
theoretical_answers = [{"id": ex["id"], "answers": ex["answers"]} for ex in examples]
return metric.compute(predictions=predicted_answers, references=theoretical_answers)
Putem verifica dacă funcționează pe baza predicțiilor noastre:
compute_metrics(start_logits, end_logits, eval_set, small_eval_set)
{'exact_match': 83.0, 'f1': 88.25}
Arată bine! Acum să folosim acest lucru pentru a face fine-tune modelului nostru.
Fine-tuningul modelului
Acum suntem gata să antrenăm modelul. Să-l creăm mai întâi, folosind clasa AutoModelForQuestionAnswering
ca și înainte:
model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint)
Ca de obicei, primim un avertisment că unele weighturi nu sunt utilizate (cele din headul de preantrenare), iar altele sunt inițializate aleatoriu (cele pentru headul pentru răspuns la întrebări). Ar trebui să fiți obișnuiți cu acest lucru până acum, dar înseamnă că acest model nu este încă pregătit pentru a fi utilizat și trebuie să fie fine-tuned - bine că suntem pe cale să facem asta!
Pentru a putea trimite modelul nostru către Hub, va trebui să ne conectăm la Hugging Face. Dacă executați acest cod într-un notebook, puteți face acest lucru cu următoarea funcție utilitară, care afișează un widget în care puteți introduce datele voastre de autentificare:
from huggingface_hub import notebook_login
notebook_login()
Dacă nu lucrați într-un notebook, tastați următoarea linie în terminal:
huggingface-cli login
Odată făcut acest lucru, ne putem defini TrainingArguments
. Așa cum am spus atunci când am definit funcția noastră pentru a calcula metrica, nu vom putea avea o buclă de evaluare obișnuită din cauza parametrilor funcției compute_metrics()
. Am putea scrie propria noastră subclasă a Trainer
pentru a face acest lucru (o abordare pe care o puteți găsi în scriptul exemplu pentru răspunderea la întrebări), dar este un pic prea lung pentru această secțiune. În schimb, aici vom evalua modelul doar la sfârșitul antrenării și vă vom arăta cum să efectuați o evaluare obișnuită în secțiunea “O buclă de antrenare personalizată” de mai jos.
Acesta este într-adevăr locul în care API-ul Trainer
își arată limitele și biblioteca 🤗 Accelerate strălucește: personalizarea clasei pentru un caz de utilizare specific poate fi greu de implementat, dar modificarea unei bucle de antrenare complet expuse este ușoară.
Să aruncăm o privire la TrainingArguments
:
from transformers import TrainingArguments
args = TrainingArguments(
"bert-finetuned-squad",
evaluation_strategy="no",
save_strategy="epoch",
learning_rate=2e-5,
num_train_epochs=3,
weight_decay=0.01,
fp16=True,
push_to_hub=True,
)
Am mai văzut cele mai multe dintre acestea: stabilim niște hiperparametrii (cum ar fi learning rate, numărul de epoci pentru antrenament și o anumită scădere a weighturilor) și indicăm că dorim să salvăm modelul la sfârșitul fiecărei epoci, să sărim peste evaluare și să încărcăm rezultatele noastre în Model Hub. De asemenea, activăm antrenarea cu precizie mixtă cu fp16=True
, deoarece aceasta poate accelera foarte mult antrenarea pe un GPU recent.
În mod implicit, repositoriul utilizat va fi în namespaceul vostru și numit după foldrul de ieșire pe care l-ați stabilit, deci în cazul nostru va fi în "sgugger/bert-finetuned-squad"
. Putem trece peste acest lucru prin trecerea unui hub_model_id
; de exemplu, pentru a încărca modelul în organizația huggingface_course
, am folosit hub_model_id="huggingface_course/bert-finetuned-squad"
(care este modelul la care am făcut legătura la începutul acestei secțiuni).
💡 Dacă folderul de ieșire pe care îl utilizați există, acesta trebuie să fie o clonă locală a repositoriul în care doriți să faceți push (deci setați un nume nou dacă primiți o eroare la definirea Trainer
).
În cele din urmă, trecem totul în clasa Trainer
și lansăm antrenarea:
from transformers import Trainer
trainer = Trainer(
model=model,
args=args,
train_dataset=train_dataset,
eval_dataset=validation_dataset,
tokenizer=tokenizer,
)
trainer.train()
Rețineți că, în timpul antrenamentului, de fiecare dată când modelul este salvat (aici, la fiecare epocă), acesta este încărcat în Hub pe fundal. În acest fel, veți putea să reluați antrenarea pe un alt device, dacă este necesar. Întreaga pregătire durează ceva timp (puțin peste o oră pe un Titan RTX), așa că puteți să vă luați o cafea sau să recitiți unele dintre părțile cursului care vi s-au părut mai dificile în timp ce se desfășoară. De asemenea, rețineți că, de îndată ce se termină prima epocă, veți vedea câteva weighturu încărcate în Hub și puteți începe să vă jucați cu modelul vostru pe pagina acestuia.
Odată ce antrenamentul este complet, putem în cele din urmă să evaluăm modelul (și să ne rugăm să nu fi petrecut tot timpul de calcul degeaba). Metoda predict()
a Trainer
va returna un tuple în care primele elemente vor fi predicțiile modelului (aici o pereche cu logiturile de început și de sfârșit). Trimitem acest rezultat funcției noastre compute_metrics()
:
predictions, _, _ = trainer.predict(validation_dataset)
start_logits, end_logits = predictions
compute_metrics(start_logits, end_logits, validation_dataset, raw_datasets["validation"])
{'exact_match': 81.18259224219489, 'f1': 88.67381321905516}
Super! Ca o comparație, scorurile de bază raportate în articolul BERT pentru acest model sunt 80,8 și 88,5, deci suntem exact unde ar trebui să fim.
În final, folosim metoda push_to_hub()
pentru a ne asigura că încărcăm cea mai recentă versiune a modelului:
trainer.push_to_hub(commit_message="Training complete")
Aceasta returnează URL-ul commit-ului pe care tocmai l-a făcut, dacă doriți să îl inspectați:
'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68'
De asemenea, Trainer
redactează un model card cu toate rezultatele evaluării și o încarcă.
În această etapă, puteți utiliza widgetul de inferență de pe Model Hub pentru a testa modelul și pentru a-l oferi prietenilor, familia și animalele de companie preferate. Ați făcut fine-tune cu succes unui model pentru o sarcină de răspundere a unei întrebari - felicitări!
✏️ E rândul tău! Încearcă un alt model de arhitectură pentru a vedea dacă are performanțe mai bune la această sarcină!
Dacă doriți să pătrundeți puțin mai adânc în bucla de antrenare, vă vom arăta acum cum să faceți același lucru folosind 🤗 Accelerate.
O buclă de antrenare personalizată
Să aruncăm acum o privire la bucla de antrenare completă, astfel încât să puteți personaliza cu ușurință părțile de care aveți nevoie. Aceasta va semăna foarte mult cu bucla de antrenare din Capitolul 3, cu excepția buclei de evaluare. Vom putea evalua modelul în mod regulat, deoarece nu mai suntem constrânși de clasa Trainer
.
Pregătirea pentru antrenament
Mai întâi trebuie să construim DataLoader
s din dataseturile noastre. Am setat formatul acestor dataseturi la "torch"
și am eliminat coloanele din setul de validare care nu sunt utilizate de model. Apoi, putem utiliza default_data_collator
furnizat de Transformers ca un collate_fn
și să amestecăm setul de antrenare, dar nu și setul de validare:
from torch.utils.data import DataLoader
from transformers import default_data_collator
train_dataset.set_format("torch")
validation_set = validation_dataset.remove_columns(["example_id", "offset_mapping"])
validation_set.set_format("torch")
train_dataloader = DataLoader(
train_dataset,
shuffle=True,
collate_fn=default_data_collator,
batch_size=8,
)
eval_dataloader = DataLoader(
validation_set, collate_fn=default_data_collator, batch_size=8
)
În continuare, reinițializăm modelul, pentru a ne asigura că nu continuăm fine-tuningul de dinainte, ci pornim din nou de la modelul preantrenat BERT:
model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint)
Atunci vom avea nevoie de un optimizator. Ca de obicei, folosim clasicul AdamW
, care este ca Adam, dar cu o corecție în modul în care se aplică scăderea weighturilor:
from torch.optim import AdamW
optimizer = AdamW(model.parameters(), lr=2e-5)
Odată ce avem toate aceste obiecte, le putem trimite metodei accelerator.prepare()
. Amintiți-vă că, dacă doriți să vă antrenați pe un TPU într-un notebook Colab, va trebui să mutați tot acest cod într-o funcție de antrenament, care nu ar trebui să execute nicio celulă care inițializează un Accelerator
. Putem forța antrenarea cu precizie mixtă trecând fp16=True
la Accelerator
(sau, dacă executați codul ca un script, asigurați-vă că completați config
în 🤗 Accelerate în mod corespunzător).
from accelerate import Accelerator
accelerator = Accelerator(fp16=True)
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
model, optimizer, train_dataloader, eval_dataloader
)
După cum ar trebui să știți din secțiunile anterioare, putem utiliza lungimea train_dataloader
pentru a calcula numărul de pași de antrenare numai după ce a trecut prin metoda accelerator.prepare()
. Utilizăm același program liniar ca în secțiunile anterioare:
from transformers import get_scheduler
num_train_epochs = 3
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch
lr_scheduler = get_scheduler(
"linear",
optimizer=optimizer,
num_warmup_steps=0,
num_training_steps=num_training_steps,
)
Pentru a trimite modelul nostru către Hub, va trebui să creăm un obiect Repository
într-un folder de lucru. În primul rând, conectați-vă la Hugging Face Hub, dacă nu sunteți deja conectat. Vom determina numele repositoriul pornind de la ID-ul modelului pe care dorim să îl atribuim modelului nostru (nu ezitați să înlocuiți repo_name
cu propria alegere; acesta trebuie doar să conțină numele vostru de utilizator, ceea ce face funcția get_full_repo_name()
):
from huggingface_hub import Repository, get_full_repo_name
model_name = "bert-finetuned-squad-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
'sgugger/bert-finetuned-squad-accelerate'
Apoi putem clona acel repositoriu într-un folder local. Dacă există deja, acest folder local ar trebui să fie o clonă a repositoriului cu care lucrăm:
output_dir = "bert-finetuned-squad-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
Acum putem încărca orice salvăm în output_dir
prin apelarea metodei repo.push_to_hub()
. Acest lucru ne va ajuta să încărcăm modelele intermediare la sfârșitul fiecărei epoci.
Bucla de antrenare
Acum suntem pregătiți să scriem bucla de antrenare completă. După definirea unei bare de progres pentru a urmări modul în care decurge antrenamentul, bucla are trei părți:
- Pregătirea în sine, care este iterația clasică peste
train_dataloader
, trecerea înainte prin model, apoi trecerea înapoi și pasul optimizatorului. - Evaluarea, în care adunăm toate valorile pentru
start_logits
șiend_logits
înainte de a le converti în matrici NumPy. Odată ce bucla de evaluare este terminată, concatenăm toate rezultatele. Rețineți că trebuie să truncăm deoareceAccelerator
ar fi putut adăuga câteva exemple la sfârșit pentru a ne asigura că avem același număr de exemple în fiecare proces. - Salvarea și încărcarea, unde mai întâi salvăm modelul și tokenizatorul, apoi apelăm
repo.push_to_hub()
. Ca și înainte, folosim argumentulblocking=False
pentru a spune bibliotecii 🤗 Hub să efectueze push-ul într-un proces asincron. În acest fel, antrenamentul continuă normal, iar această instrucțiune (lungă) este executată pe fundal.
Iată codul complet pentru bucla de antrenare:
from tqdm.auto import tqdm
import torch
progress_bar = tqdm(range(num_training_steps))
for epoch in range(num_train_epochs):
# Training
model.train()
for step, batch in enumerate(train_dataloader):
outputs = model(**batch)
loss = outputs.loss
accelerator.backward(loss)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)
# Evaluation
model.eval()
start_logits = []
end_logits = []
accelerator.print("Evaluation!")
for batch in tqdm(eval_dataloader):
with torch.no_grad():
outputs = model(**batch)
start_logits.append(accelerator.gather(outputs.start_logits).cpu().numpy())
end_logits.append(accelerator.gather(outputs.end_logits).cpu().numpy())
start_logits = np.concatenate(start_logits)
end_logits = np.concatenate(end_logits)
start_logits = start_logits[: len(validation_dataset)]
end_logits = end_logits[: len(validation_dataset)]
metrics = compute_metrics(
start_logits, end_logits, validation_dataset, raw_datasets["validation"]
)
print(f"epoch {epoch}:", metrics)
# Save and upload
accelerator.wait_for_everyone()
unwrapped_model = accelerator.unwrap_model(model)
unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
if accelerator.is_main_process:
tokenizer.save_pretrained(output_dir)
repo.push_to_hub(
commit_message=f"Training in progress epoch {epoch}", blocking=False
)
În cazul în care este prima dată când vedeți un model salvat cu 🤗 Accelerate, să ne oprim puțin pentru a inspecta cele trei linii de cod care îl însoțesc:
accelerator.wait_for_everyone() unwrapped_model = accelerator.unwrap_model(model) unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
Prima linie se explică de la sine: aceasta spune tuturor proceselor să aștepte până când toată lumea se află în etapa respectivă înainte de a continua. Acest lucru are rolul de a ne asigura că avem același model în fiecare proces înainte de salvare. Apoi luăm unwrapped_model
, care este modelul de bază pe care l-am definit. Metoda accelerator.prepare()
modifică modelul pentru a funcționa în antrenarea distribuită, deci nu va mai avea metoda save_pretrained()
; metoda accelerator.unwrap_model()
anulează acest pas. În cele din urmă, apelăm metoda save_pretrained()
, dar îi spunem să folosească metoda accelerator.save()
în loc de torch.save()
.
Odată făcut acest lucru, ar trebui să aveți un model care produce rezultate destul de asemănătoare cu cel antrenat cu Trainer
. Puteți verifica modelul pe care l-am antrenat folosind acest cod pe [huggingface-course/bert-finetuned-squad-accelerate] (https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). Și dacă doriți să testați orice modificări ale buclei de antrenare, le puteți implementa direct prin editarea codului prezentat mai sus!
Utilizarea modelului fine-tuned
V-am arătat deja cum puteți utiliza modelul căruia i-am făcut fine-tune pe Model Hub cu widgetul de inferență. Pentru a-l utiliza local într-un pipeline
, trebuie doar să specificați identificatorul modelului:
from transformers import pipeline
# Înlocuiți acest checkpoint cu propriul checkpoint
model_checkpoint = "huggingface-course/bert-finetuned-squad"
question_answerer = pipeline("question-answering", model=model_checkpoint)
context = """
🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch and TensorFlow — with a seamless integration
between them. It's straightforward to train your models with one before loading them for inference with the other.
"""
question = "Which deep learning libraries back 🤗 Transformers?"
question_answerer(question=question, context=context)
{'score': 0.9979003071784973,
'start': 78,
'end': 105,
'answer': 'Jax, PyTorch and TensorFlow'}
Grozav! Modelul nostru funcționează la fel de bine ca cel implicit pentru aceast pipeline!
< > Update on GitHub