From 6b5a6f9fb28f2106e7a0a5703183517eab8cbd18 Mon Sep 17 00:00:00 2001 From: Angroys <120798951+Angroys@users.noreply.github.com> Date: Sat, 28 Dec 2024 18:56:57 +0200 Subject: [PATCH 01/37] Changed romanian folder name from rum to ro --- chapters/{rum => ro}/_toctree.yml | 0 chapters/{rum => ro}/chapter0/1.mdx | 0 chapters/{rum => ro}/chapter2/1.mdx | 0 chapters/{rum => ro}/chapter2/2.mdx | 0 chapters/{rum => ro}/chapter2/3.mdx | 0 chapters/{rum => ro}/chapter2/4.mdx | 0 chapters/{rum => ro}/chapter2/5.mdx | 0 chapters/{rum => ro}/chapter2/6.mdx | 0 chapters/{rum => ro}/chapter2/7.mdx | 0 chapters/{rum => ro}/chapter2/8.mdx | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename chapters/{rum => ro}/_toctree.yml (100%) rename chapters/{rum => ro}/chapter0/1.mdx (100%) rename chapters/{rum => ro}/chapter2/1.mdx (100%) rename chapters/{rum => ro}/chapter2/2.mdx (100%) rename chapters/{rum => ro}/chapter2/3.mdx (100%) rename chapters/{rum => ro}/chapter2/4.mdx (100%) rename chapters/{rum => ro}/chapter2/5.mdx (100%) rename chapters/{rum => ro}/chapter2/6.mdx (100%) rename chapters/{rum => ro}/chapter2/7.mdx (100%) rename chapters/{rum => ro}/chapter2/8.mdx (100%) diff --git a/chapters/rum/_toctree.yml b/chapters/ro/_toctree.yml similarity index 100% rename from chapters/rum/_toctree.yml rename to chapters/ro/_toctree.yml diff --git a/chapters/rum/chapter0/1.mdx b/chapters/ro/chapter0/1.mdx similarity index 100% rename from chapters/rum/chapter0/1.mdx rename to chapters/ro/chapter0/1.mdx diff --git a/chapters/rum/chapter2/1.mdx b/chapters/ro/chapter2/1.mdx similarity index 100% rename from chapters/rum/chapter2/1.mdx rename to chapters/ro/chapter2/1.mdx diff --git a/chapters/rum/chapter2/2.mdx b/chapters/ro/chapter2/2.mdx similarity index 100% rename from chapters/rum/chapter2/2.mdx rename to chapters/ro/chapter2/2.mdx diff --git a/chapters/rum/chapter2/3.mdx b/chapters/ro/chapter2/3.mdx similarity index 100% rename from chapters/rum/chapter2/3.mdx rename to chapters/ro/chapter2/3.mdx diff --git a/chapters/rum/chapter2/4.mdx b/chapters/ro/chapter2/4.mdx similarity index 100% rename from chapters/rum/chapter2/4.mdx rename to chapters/ro/chapter2/4.mdx diff --git a/chapters/rum/chapter2/5.mdx b/chapters/ro/chapter2/5.mdx similarity index 100% rename from chapters/rum/chapter2/5.mdx rename to chapters/ro/chapter2/5.mdx diff --git a/chapters/rum/chapter2/6.mdx b/chapters/ro/chapter2/6.mdx similarity index 100% rename from chapters/rum/chapter2/6.mdx rename to chapters/ro/chapter2/6.mdx diff --git a/chapters/rum/chapter2/7.mdx b/chapters/ro/chapter2/7.mdx similarity index 100% rename from chapters/rum/chapter2/7.mdx rename to chapters/ro/chapter2/7.mdx diff --git a/chapters/rum/chapter2/8.mdx b/chapters/ro/chapter2/8.mdx similarity index 100% rename from chapters/rum/chapter2/8.mdx rename to chapters/ro/chapter2/8.mdx From d13ac2dd8d67d3ccf519fb36867e20123422af8f Mon Sep 17 00:00:00 2001 From: Angroys <120798951+Angroys@users.noreply.github.com> Date: Wed, 1 Jan 2025 22:44:41 +0200 Subject: [PATCH 02/37] added chapter 3 first 3 sections --- .codegpt/head | 1 + chapters/ro/chapter3/1.mdx | 25 +++ chapters/ro/chapter3/2.mdx | 393 ++++++++++++++++++++++++++++++++++ chapters/ro/chapter3/3_tf.mdx | 206 ++++++++++++++++++ 4 files changed, 625 insertions(+) create mode 100644 .codegpt/head create mode 100644 chapters/ro/chapter3/1.mdx create mode 100644 chapters/ro/chapter3/2.mdx create mode 100644 chapters/ro/chapter3/3_tf.mdx diff --git a/.codegpt/head b/.codegpt/head new file mode 100644 index 000000000..61e0708bd --- /dev/null +++ b/.codegpt/head @@ -0,0 +1 @@ +d92002a2-d67d-44e7-9370-14bf913c3669 \ No newline at end of file diff --git a/chapters/ro/chapter3/1.mdx b/chapters/ro/chapter3/1.mdx new file mode 100644 index 000000000..87d1e54dd --- /dev/null +++ b/chapters/ro/chapter3/1.mdx @@ -0,0 +1,25 @@ + + +# Introduction[[introduction]] + + + +În [Chapter 2](/course/chapter2) am explorat cum să folosim tokenizeri și modele pretrained pentru a face predicții. Dar dacă vrei să faci fine-tune un model pre-trained pentru propriul dataset? Acesta e subiectul acestui capitol. Vei învăța: + +{#if fw === 'pt'} +* Cum să pregătiți un dataset mare din Hub +* Cum să folosiți API-ul `Trainer` pentru a face fine-tune unui model +* Cum să creați o buclă de antrenare personalizată +* Cum să beneficiați de biblioteca 🤗 Accelerate pentru a rula ușor această bucla de antrenare personalizare pe orice configurare distribuită. + +{:else} +* Cum să pregătiți un datasetmare din Hub +* Cum să folosiți Keras să faceți fine-tune unui model +* Cum să folosiți Keras pentru a obține predicții +* Cum să folosiți un indicator custom + +{/if} +Pentru a încărca checkpoint-urile antrenate pe Hugging Face Hub, veți avea nevoie de un cont la huggingface.co: [creați un cont](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/ro/chapter3/2.mdx b/chapters/ro/chapter3/2.mdx new file mode 100644 index 000000000..8d6cc6c4c --- /dev/null +++ b/chapters/ro/chapter3/2.mdx @@ -0,0 +1,393 @@ +`` + +# Procesarea datelor [[processing-the-data]] + +{#if fw === 'pt'} + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +Continuând cu exemplul din [capitol anterior](/course/chapter2), mai jos vedeți cum putem să antrenăm un sequence classifier pe un singur batch în PyTorch: + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequences = [ + "Am așteptat toată viața mea pentru un curs HuggingFace.", + "Acest curs este fascinant!", +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# Acesta este nou +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} +Continuând cu exemplul din [capitolul anterior](/course/chapter2), mai jos vedeți cum continuăm antrenarea unui sequence classifier pe un batch în TensorFlow: + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Același lucru ca dinainte +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "Am așteptat toată viața mea pentru un curs HuggingFace.", + "Acest curs este fascinant!", +] +batch = dict(tokenizer(sequence, padding=True, truncate=True, return_tensors="tf")) + + +# Aceasta este nou +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +etichete = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, etichete) +``` + +De obicei, antrenarea modelului pe două propoziții nu va da rezultate foarte bune. Pentru a obține rezultate mai bune, veți avea nevoie să pregătiți un dataset mai mare. + +În această secțiune vom folosi ca exemplu datasetul MRPC (Microsoft Research Paraphrase Corpus), introdus într-un [articol](https://www.aclweb.org/anthology/I05-5002.pdf) de William B. Dolan și Chris Brockett. Datasetul constă din 5,801 perechi de propoziții, cu o etichetă care indică dacă sunt paraphraseuri sau nu (adică dacă ambele propoziții au același înțeles). Am selectat-o pentru acest capitoldeoare că este un dataset mic, așa încât să fie ușor de experimentat cu antrenarea pe el. + +### Încărcarea unui dataset din Hub[[loading-a-dataset-from-the-hub]] + +{#if fw === 'ro'} + +{:else} + +{/if} + +Hub-ul nu conține doar modele; de asemenea, conține multiple dataseturi înm mai multe limbi. Puteți naviga prin seturile de date [aici](https://huggingface.co/datasets), și vă recomandăm să încercați să încărcați și procurați un nou dataset odată ce veți fi trecut prin această secțiune (vedeți documentația generală [aici](https://huggingface.co/docs/datasets/loading)). Dar pentru moment, să ne concentrăm pe datasetul MRPC! Este una dintre cele 10 seturi de date care compun benchmark-ul [GLUE](https://gluebenchmark.com/), care este un benchmark academic folosit pentru a măsura performanța modelelor ML în 10 diferite sarcini de clasificare a textului. + +Biblioteca 🤗 Datasets oferă o comandă foarte simplă pentru a descărca și a stoca un dataset din Hub. Putem să instalăm datasetul MRPC astfel: + + +⚠️ **Avertizare** Asigurați-vă că `datasets` este instalat prin rularea `pip install datasets`. Apoi, încărcați dataset-ul MRPC și printați-l pentru a vedea ce conține. + + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +Ai observat că am obținut un obiect `DatasetDict` care conține setul de antrenare, setul de validare și setul de testare. Fiecare dintre acestea conține câteva coloane (`sentence1`, `sentence2`, `label`, și `idx`) și o numpăr de rânduri variabil, care reprezintă numărul de elemente în fiecare set (astfel, există 3.668 perechi de fraze în setul de antrenare, 408 în setul de validare și 1.725 în setul de testare). + +Acest comandă descarcă și face cache datasetului, în *~/.cache/huggingface/datasets*(folderul implicit de salvare). În capitolul 2, am văzut cum putem personaliza folderul de cache stabilind variabila de mediu `HF_HOME`. + +Putem accesa fiecare pereche de fraze din obiectul `raw_datasets` prin indexare, asemenea unui dicționar: + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi l-a acuzat pe fratele său, pe care l-a numit „martorul” , că distorsionează intenționat probele sale.', + 'sentence2': 'Referindu-se la el doar sub numele de " martorul ", Amrozi l-a acuzat pe fratele său că distorsionează intenționat probele sale.'} +``` + +Putem vedea că labels sunt numere întregi, așadar nu vom trebui să facem nicio procesare în acest sens. Pentru a ști care număr întreg corespunde cu care label, putem inspecta variabila `features` al 'raw_train_dataset'. Acest lucru ne va spune tipul fiecărui coloană. + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +În spatele, `label` este obiect de tip `ClassLabel`, iar maparea numerelor întregi la numele label-ului este stocat în folderele *names*. Astfel, `0` corespunde cu `not_equivalent`, iar `1` corespunde cu `equivalent`. + + + +✏️ **Încercați-o!** Arată elementul 15 din setul de antrenament și elementul 87 din setul de validare. Care sunt label-urile acesotra? + + + +### Preprocessing un datset[[preprocessing-a-dataset]] + +{#if fw === 'pt'} + +{:else} + +{/if} + +Pentru a prelucra datele, trebuie să convertim textul în numere pe care modelul le poate înțelege. Cum ai văzut în [capitol anterior](/course/chapter2), acest lucru se face cu ajutorul unui tokenizer. Putem să oferim tokenizerului o propoziție sau o listă de propoziții, așa că putem să tokenizăm direct toate primele propoziții și toate cele de-a doua propoziții ale fiecărei perechi în felul următor: + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +Însă nu putem să transmitem doar două propoziții către model și să obținem o predicție dacă cele două propoziții sunt parafraze sau nu. Trebuie să controlăm cele două secvențe ca o pereche, apoi să le aplicăm preprocesarea adecvată. Norocul este că tokenizer-ul poate lua o pereche de propoziții și le poate pregăti în felul în care modelul BERT îl așteaptă: + +```py +inputs = tokenizer("Aceasta este prima propoziție.", "Aceasta este a doua.") +inputs + +``` + +```python out +{ + 'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102], + 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], + 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] +} +``` + +Am discutat despre `input_ids` și `attention_mask` cheii în [Capitolul 2](/course/chapter2), dar am întârziat cu abordarea cheii `token_type_ids`. În acest exemplu, această cheie este ceea ce spune modelului care parte a inputului este prima propoziție și care este a doua. + + +✏️ **Încercați!** Luați elementul 15 din setul de antrenare și tokenizați cele două propoziții separat și ca o pereche. Care este diferența dintre rezultatele celor două? + + +Dacă facem decode ID-urilor din `input_ids` înapoi la cuvinte: + +```python +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +Noi vom obține următorul rezultat: + +```python +['[CLS]', 'acesta', 'este', 'prima', 'propoziție', '.', '[SEP]', 'acesta', 'este', 'a', 'doua', '.', '[SEP]'] +``` + +Prin urmare, modelul așteaptă ca inputul să fie în forma `[CLS] sentence1 [SEP] sentence2 [SEP]` atunci când există două propoziții. Aliniind această informație cu `token_type_ids`, obținem: + +```python +['[CLS]', 'acesta', 'este', 'prima', 'propoziție', '.', '[SEP]', 'acesta', 'este', 'a', 'doua', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +Observăm,că inputul corespunzător `[CLS] sentence1 [SEP]` au toate un token type ID de `0`, în timp ce celelalte părți, corespunzătoare `sentence2 [SEP]`, au toate un token type ID de `1`. + +Înțelegem că dacă selectăm alt checkpoint al modelului, nu vom avea neapărat `token_type_ids` în inputurile tokenizate (de exemplu, ele nu sunt returnate dacă folosim un model DistilBERT). Acestea sunt returnate doar atunci când modelul știe ce să facă cu ele, pentru că le-a văzut în timpul pretrainingului. + +În acest caz, BERT este antrenat cu token type IDs și pe lângă the masked language modeling objective despre care am vorbit în [Capitolul 1](/course/chapter1), are un objective suplimentar numit _next sentence prediction_. Objectiveul este destinat modelării relației între perechi de propoziții. + +Cu următoare predicție a propoziției, modelului i se prezintă perechi de propoziții (cu masked tokens aleatoriu) și i se cere să prezică dacă a doua propoziție urmează primei. Pentru a face sarcina non-trivial, în jumătate dintre cazuri propizițiie urmează una pe alta în documentul original din care au fost extrase și cealaltă jumătate ele vin de la două documente diferite. + +În general, nu trebuie să vă faceți griji dacă există sau nu `token_type_ids` în inputurile tokenizate: atât timp cât folosiți aceelași checkpoint al modelului și a tokenizer-ului, totul va fi bine, pentru că tokenizer-ul știe ce are de oferit modelului. + +Acum că am văzut cum poate prelucra tokenizer-ul o pereche de propoziții, îl putem folosi acest lucru pentru a tokeniza întregul nostru dataset: exact ca în [capitolul anterior](/course/chapter2), putem să oferim tokenizer-ului o listă de perechi de propoziții oferindu-i prima listă de propoziții și apoi lista a doua. Acest lucru este compatibil cu padding-ul și truncation-ul pe care le-am văzut în [Capitolul 2](/course/chapter2). În felul acesta putem prelucra datasetul de antrenare astfel: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +Această metodă funcționează bine, dar are dezavantajul că returnează un dicționar (cu key-urile noastre, `input_ids`, `attention_mask`, și `token_type_ids` și valori care sunt liste de liste). Va funcționa doar dacă aveți destul de RAM pentru a stoca întreg dataset în tokenizării acestuia (iar dataset-urile din biblioteca 🤗 Datasets sunt fișiere [Apache Arrow](https://arrow.apache.org/) stocate pe disc, deci tu stochezi numai sample-urile pe care le ceri să se păstreaze în memorie). + +Acum că am înțeles modul în care tokenizer-ul nostru poate gestiona o pereche de fraze, putem folosi el pentru a tokenize întregul nostru set de date: la fel cum am făcut în [capitolul anterior](/course/chapter2), putem alimenta tokenizer-ul cu lista de prime fraze și apoi lista de fraze secunde. Această metodă este compatibilă și cu opțiunile de umplere și tăiere pe care le-am văzut în [Capitolul 2](/course/chapter2). Astfel, una dintre modalitățile prin care putem preprocesa dataset-ul de antrenare este: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +Această metodă funcționează bine, dar are dezavantajul că returnează un dicționar (cu cheile noastre, `input_ids`, `attention_mask`, și `token_type_ids`, și valori care sunt liste de liste). Va fi disponibilă doar dacă aveți suficient spațiu de memorie pentru a stoarca întregul set de date în timpul tokenizării (în timp ce dataset-urile din biblioteca 🤗 Datasets se salvează sub forma [Apache Arrow](https://arrow.apache.org/), fișiere stocate pe disch, astfel încât doar acele fragmente ale datelor pe care le cerem să fie încărcate în memorie). + +Pentru a păstra datele în forma de dataset, vom folosi metoda [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map). Aceasta ne permite puțin mai multă flexibilitate, dacă ne trebuie mai multă preprocesare pe lângă tokenization. Metoda `map()` aplică o funcție asupra fiecărui element al datasetului, astfel putem să definim o funcție care tokenizează input-urile noastre: + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +Această funcție i-a un dicționar (ca și elementele datasetului) și returnează un nou dicționar cu key-urile `input_ids`, `attention_mask`, și `token_type_ids`. Acesta va lucra chiar și dacă dicționarul "example" conține câteva sample-uri(fiecare key ca o list de propoziții), întrucât "tokenizer"-ul lucrează și cu liste cu perechi de propoziții cum am văzut mai devreme. Aceasta va permite folosirea opțiunii `batched=True` în apelul funcției `map()`, ceea ce va accelera semnificativ tokenizarea. `Tokenizer`-ul nostru este susținut de un tokenizer scris în Rust din biblioteca [🤗 Tokenizers](https://github.com/huggingface/tokenizers). Acest tokenizer poate fi extrem de rapid, dar numai dacă îi dăm multe inputuri odată. + +*Atenție*:Am omis argumentul `padding` din funcția noastră de tokenizare pentru moment. Acest lucru este datorat faptului că aplicarea padding-ului tuturor elementelor cu lungimea maximă nu este eficientă: în schimb, este mai bine să faceți padding elementelor atunci când creați un batch, astfel încât să vă trebuiască doar să faceți padding până la lungimea maximă doar în acest batch și nu întregului dataset. Acest lucru poate salva mult timp și resurse atunci când input-urile au lungimi foarte variabile. + +Aici este modul în care aplicăm funcția de tokenizare asupra tuturor dataset-urilor noastre. Folosim `batched=True` în apelul funcției `map` astfel încât funcția să fie aplicată asupra mai multor elemente ale datasetului odată. Aceasta permite preprocesarea accelerată. + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +Modul în care biblioteca 🤗 Datasets aplică această procesare este prin adăugarea de noi câmpuri la dataset-uri, unul pentru fiecare key din dicționarul returnat de funcția de preprocesare: + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +Puteți chiar și folosi multiprocessing când aplicați funcția de preprocesare cu `map()` prin folosirea unui argument `num_proc`. Nu am făcut acest lucru aici pentru că biblioteca 🤗 Tokenizers utilizează deja mai multe thread-uri pentru a tokeniza sample-urile mai rapid, dar dacă nu folosiți un tokenizer rapid cu această bibliotecă, acest lucru v-ar putea accelera preprocesarea. + +Funcția `tokenize_function` returnează un dicționar cu key-urile `input_ids`, `attention_mask`, și `token_type_ids`, astfel că aceste trei câmpuri sunt adăugate tuturor split-urilor în dataset-urile noastre. Notați-vă că am putea chiar schimba câmpurile existente dacă funcția de preprocesare ar returna o nouă valoare pentru un key din dataset-ul pe care aplicăm `map()`. + +Ultimul lucru pe care trebuie să îl facem este să facem padding tuturor exemplelor până la lungimea celui mai mare element atunci când facem batch elementelor împreună – o tehnică pe care o denumim *dinamic padding*. + +### Dinamic Padding[[dynamic-padding]] + +{#if fw === 'pt'} + +Funcția care este responsabilă de a pune împreună sample-urile într-un batch se numește *collate function*. Este un argument default pe care puteți să-l transmiteți când construiți un `DataLoader`, valoarea implicită fiind o funcție care va converti sample-urile în tensors PyTorch și le va concatena (recursiv dacă elementele dumneavoastră sunt liste, tuple-uri sau dicționare). Acest lucru nu este posibil în cazul nostru deoarece input-urile noastre nu vor avea toate aceeași dimensiune. Am amânat intenționat padding-ul pentru a o aplica numai atunci când e nevoie, pe fiecare batch și să evităm astfel exemplele cu lungimi prea mari cu multe padding-uri. Acest lucru va accelera antrenarea, dar notați-vă că dacă antrenați pe un TPU, acest lucru poate cauza probleme – TPU-urile preferă forme fixe, chiar dacă aceasta înseamnă padding suplimentar. + +{:else} + +Funcția care este responsabilă de a pune împreună sample-urile într-un batch se numește *collate function*. Collator-ul default este o funcție ce va transforma sample-urile în tf. Faceți Tensor și concatenațile (recursiv dacă elementele dumneavoastră sunt liste, tuple-uri sau dicționare). Acest lucru nu este posibil în cazul nostru deoarece input-urile noastre nu vor avea toate aceeași dimensiune. Am amânat intenționat padding-ul pentru a o aplica numai atunci când e nevoie, pe fiecare batch și să evităm astfel exemplele cu lungimi prea mari cu multe padding-uri. Acest lucru va accelera antrenarea, dar notați-vă că dacă antrenați pe un TPU, acest lucru poate cauza probleme – TPU-urile preferă forme fixe, chiar dacă aceasta înseamnă padding suplimentar. + +{/if} + +Pentru a face acest lucru, trebuie să definim o funcție collate care va aplica cantitatea corectă de padding pentru elementele din dataset-ul pe care vrem să-i facem batch. Norocul este că biblioteca 🤗 Transformers ne oferă o asemenea funcție prin `DataCollatorWithPadding`. Aceasta i-a un tokenizer atunci când o inițializați(pentru a ști care padding token să folosim și dacă modelul se așteaptă ca padding-ul să fie pe stânga sau dreapta inputu-lui) și va face tot ce este nevoie: +{#if fw === 'pt'} +```py +from transformers import DataCollatorWithPadding +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) +``` +{:else} +```py +from transformers import DataCollatorWithPadding +data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") +``` +{/if} + +Pentru a testa acest nou jucărie, să luăm câteva sample-uri din setul nostru de antrenare pe care am dori să le facem batch împreună. Aici, ștergem coloanele `idx`, `sentence1` și `sentence2` deoarece nu vor fi necesare și conțin stringuri (și nu putem crea tensore cu stringuri) și uitați-vă la lungimile fiecărui element din lotul nostru: + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +Nu e de mirare că obținem sample-uri de lungimi diferite, între 32 și 67. Dynamic padding înseamnă că sample-urilor din acest batch le-ar trebui aplicate padding cu o lungime de 67, lungimea cea mai mare din batch. Fără Dynamic padding, toate sample-urile ar fi avut padding cu maximul lungimii din întregul dataset sau maximul lungimii pe care modelul poate accepta. Să verificăm încă o dată dacă `data_collator`-ul nostru face dynamic padding pe batch: + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +Looking good! Acum că am trecut de la textul raw la batch-uri pe care modelul nostru poate să le proceseze, suntem gata să îi facem fine-tune! + +{/if} + + + +✏️ **Încearcă-ți norocul!** Implementează preprocesarea datelor pe dataset-ul GLUE SST-2. Acesta se diferență puțin deoarece nu conține perechi de propoziții, ci doar câte o propoziție, dar ceea ce am făcut ar trebui să rămână neschimbat. Pentru o provocare mai grea, încearcă să scrii o funcție de preprocesare care se aplică oricărui task GLUE. + + + +{#if fw === 'tf'} + +Acum că avem dataset-ul nostru și un `data_collator`, este timpul să-i punem la lucru. În loc să încărcăm manual batch-urile și să le facem collate, asta însemnând prea mult lucru și, nu ar fi nici prea performantă. În schimb, există o metodă simplă care oferă o soluție performantă la acest problemă: `to_tf_dataset()`. Acest lucru va face wrap unui `tf.data.Dataset` în jurul datasetului nostru, cu o fucție opțională de collation. `tf.data.Dataset` este un format nativ TensorFlow pe care Keras-ul o poate folosi pentru `model.fit()`, astfel încât această metodă face convert unui 🤗 Dataset la un format gata să fie antrenat. Hai să-l vedem în acțiune cu datasetul nostru! + +```py +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) + +tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=False, + collate_fn=data_collator, + batch_size=8, +) +``` + +Și asta e tot! Putem lua aceste dataset-uri pentru următoarea lecție, unde antrenamentul va fi ușor după toată munca depusă pentru prelucrarea datelor. + +{/if} \ No newline at end of file diff --git a/chapters/ro/chapter3/3_tf.mdx b/chapters/ro/chapter3/3_tf.mdx new file mode 100644 index 000000000..5b1636851 --- /dev/null +++ b/chapters/ro/chapter3/3_tf.mdx @@ -0,0 +1,206 @@ + + +# Aplicarea fine-tuningului asupra unui model cu Keras[[fine-tuning-a-model-with-keras]] + + + +Odată ce ai finalizat toate operațiile de preprocesare a datelor din ultima secțiune, ai doar câțiva pași rămași pentru a antrena modelul. Însă, observați că `model.fit()` va rula foarte lent pe un CPU. Dacă nu aveți un GPU configurat, puteți accesa GPU-uri sau TPUs pe gratis pe [Google Colab](https://colab.research.google.com/). + +Exemplele de cod de mai jos presupun că ați executat deja exemplele din secțiunea precedentă. Aici este o scurtă sumarizare care recapitulează ceea ce trebuie să faceți: + +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding +import numpy as np + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") + +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) + +tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=False, + collate_fn=data_collator, + batch_size=8, +) +``` + + +### Antrenare[[antrenare]] + + +Modelele TensorFlow importate din 🤗 Transformers sunt deja modele Keras. Aici este o scurtă introducere în Keras. + + + + +Astfel, odată ce avem datele noastre, putem începe antrenarea lor foarte ușor. + + + + + +În același mod ca și în [capitolul anterior](/course/chapter2), vom folosi clasa `TFAutoModelForSequenceClassification`, cu două labeluri: + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Veți observa că în comparație cu [Capitolul 2](/course/chapter2), veți primi o avertizare după ce inițializați acest model preantrenat. Acest lucru se datorează faptului că BERT nu a fost antrenat pentru clasificarea perechilor de propoziții, astfel încât începutul preantrenat al modelului a fost eliminat și un nou început potrivit pentru clasificarea secvențelor a fost adăugat în locul lui. Avertizările indică faptul că anumite weights nu au fost folosite (cele corespunzătoare începutului de preantrenare eliminat) și că altele au fost inițializate aleatoriu (cele pentru noul capăt). Avertizarea se încheie prin încurajarea antrenării modelului, ceea ce este exact ceea ce vom face acum. + + +Pentru a face fine-tune modelului pe datasetul nostru, avem de făcut `compile()` modelului și apoi să trasnmitem datele noastre spre metoda `fit()`. Acest lucru va porni procesul de fine-tuning (care ar trebui să dureze câteva minute pe un GPU) și va raporta training loss, plus validation loss la sfârșitul fiecărei epoci. + + + +Observați că modelele 🤗 Transformers au o abilitate specială care nu este disponibilă pentru cele mai multe modele Keras - ele pot folosi automat un appropriate loss pe care le calculează intern. Ele vor utiliza această pierdere implicit dacă nu veți specifica un argument de pierdere în `compile()`. Pentru a folosi internal loss, va trebui să transmiteți labelurile ca parte din input, și nu ca nu label separat, ceea ce este modul normal de utilizare a labelurilor cu modelele Keras. Veți vedea exemple de acest tip în Partea 2 a cursului, unde definirea funcției de loss corecte poate fi dificilă. În cazul sequence classification, însă, o funcție Keras standard pentru loss se va dovedi suficient, astfel o vom folosi în acest context. + + + + +```py +from tensorflow.keras.losses import SparseCategoricalCrossentropy + +model.compile( + optimizer="adam", + loss=SparseCategoricalCrossentropy(from_logits=True), + metrics=["accuracy"], +) +model.fit( + tf_train_dataset, + validation_data=tf_validation_dataset, +) +``` + + + + +Observați un pericol comun aici — puteți transmite doar numele pierderii ca un string în Keras, dar implicit Keras va presupune că deja ați aplicat un softmax la outputurile voastre. Multe modele, însă, produc outputurile direct înainte de aplicarea softmaxului, cunoscute și sub numele de *logit*. Trebuie să spuneți funcției loss că asta este ceea ce produce modelul tău, iar singura modalitate de a face acest lucru este de a-o chema direct, în loc de a o specifica numai cu un string. + + + +### Îmbunătățirea performanței de antrenare + + + +Dacă încerci codul de mai sus, sigur o să ruleze, dar vei constata că lossul scade doar lent sau doar sporadic. Principala cauză +este *learning rateul*. Așa cum și lossul, când trimitem către Keras numele unui omptimizer ca uin string, Keras inițiază +acel optimizer cu valori default pentru toți parametrii, inclusiv learning rateul. Din experiență, însă, știm că +modelele transformer se bucură de un learning rate mult mai mic decât cel default pentru Adam, care este 1e-3, scris și ca +10 la puterea -3 sau 0.001. 5e-5 (0.00005), care este aproximativ douăzeci de ori mai mică, reprezintă o bază mult mai bună. + +În plus față de reducerea learning rateului, avem un al doilea truc la dispoziție: putem reduce treptat learning rateul +pe parcursul antrenării. În documentație, vei găsi uneori această practică denumită *decaying* sau *annealing* +learning rateul. În Keras, cel mai bun mod de a face acest lucru este să folosim un *learning rate scheduler*. O opțiune bună este +`PolynomialDecay` — deși numele ar putea sugera altceva, cu setările default, el doar face decay liniar learning rateului din valoarea inițială +până la valoarea finală pe parcursul antrenării; exact ceea ce ne-am dori. Pentru a utiliza corect un scheduler, +însă, trebuie să-i spunem cât timp va dura antrenarea. Calculăm acest lucru sub forma `num_train_steps` de mai jos. + +```py +from tensorflow.keras.optimizers.schedules import PolynomialDecay + +batch_size = 8 +num_epochs = 3 +# Numărul de pași de antrenare este numărul de sampleuri din dataset, împărțit la dimensiunea batchului și apoi înmulțit +# cu numărul total de epoci. Observăm că `tf_train_dataset` aici este un batched tf.data.Dataset`, +# nu datasetul original Hugging Face, astfel încât len() să fie deja num_samples // batch_size. +num_train_steps = len(tf_train_dataset) * num_epochs +lr_scheduler = PolynomialDecay( + initial_learning_rate=5e-5, end_learning_rate=0.0, decay_steps=num_train_steps +) +from tensorflow.keras.optimizers import Adam + +opt = Adam(learning_rate=lr_scheduler) +``` + + + +Biblioteca 🤗 Transformers are funcția `create_optimizer()` care va crea un optimizer `AdamW` cu learning rate decay. Aceasta este o metodă convenabilă pe care o veți vedea în detaliu în viitoarele sec'iuni ale cursului. + + + +Acum, avem noul nostru optimizer și putem încerca să antrenăm cu el. Începem cu reloadul modelului pentru a reseta modificările pe weighturole de la ultima rundă de antrenare ;o apoi putem face compile cu noul optimizer: + +```py +import tensorflow as tf + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) +model.compile(optimizer=opt, loss=loss, metrics=["accuracy"]) +``` + +Acum putem face fit din nou: + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 Dacă doriți să încărcați automat modelul dumneavoastră pe Hub în timpul antrenării, puteți transmite `PushToHubCallback` în metoda `model.fit()`. Mai multe despre acest lucru veți învăța în [Capitol 4](/course/chapter4/3). + + + +### Model prediction + + + + +Încheierea antrenării și observarea lossului scăzând este foarte frumos, dar ce se întămplă dacă doriți să obțineți efectiv outputuruke din modelul antrenat, fie pentru a calcula câteva metrice, fie pentru utilizarea modelului în producție? Pentru asta putem folosi metoda `predict()`. Aceasta va returna *logits* de la începutul outputului modelului, câte unul pe clasă. + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +Puteți converti aceste logituri în predicțiile clasei modelului prin utilizarea `argmax` pentru a găsi logit-ul cel mai mare, care corespunde clasei celei mai probabile: + +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +```python out +(408, 2) (408,) +``` + +Acum, să folosim aceste `preds` pentru a calcula câteva metrici! Puteți încărca metricele asociate cu datasetul MRPC în același mod cum am încărcat și datasetul, dar de data aceasta cu ajutorul funcției `evaluate.load()`. Obiectul returnat are o metodă de calculare `compute()` pe care o putem utiliza pentru a face metric calculation: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"]) +``` + +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +Rezultatele exacte pe care le veți obține pot varia, deoarece inițializarea aleatoare a începutului modelului care poate schimba metricele pe care le-a atins. Aici putem vedea că modelul nostru are o precizie de 85.78% în setul de validare și un scor F1 de 89.97. Acestea sunt exact aceleași metrice folosite pentru evaluarea rezultatelor pe datasetul MRPC pentru GLUE benchmark. Tabelul din [BERT Paper](https://arxiv.org/pdf/1810.04805.pdf) a raportat un scor F1 de 88.9 pentru modelul de bază. Acela a fost un `uncased` model, dar noi utilizăm acum un `cased` model, ceea ce explică rezultatul mai bun. + +Acesta este punctul unde vă prezentăm introducerea la folosirea fine-tuningului cu ajutorul APIului Keras. Un exemplu bun pentru majoritatea sarcinilor NLP va fi dat în [Capitol 7](/course/chapter7). Dacă doriți să dezvoltați abilitățile dumneavoastră pe API-ul Keras, încercați să faceți fine-tune unui model pe datasetul GLUE SST-2 folosind procesarea de date din secvența 2. \ No newline at end of file From ade28a8f64a97a97fc7b0069c8d583b59dbb8238 Mon Sep 17 00:00:00 2001 From: Angroys <120798951+Angroys@users.noreply.github.com> Date: Thu, 2 Jan 2025 02:40:15 +0200 Subject: [PATCH 03/37] Finished translating chapter 3 --- chapters/ro/chapter3/3.mdx | 174 ++++++++++++++++++ chapters/ro/chapter3/4.mdx | 361 +++++++++++++++++++++++++++++++++++++ chapters/ro/chapter3/5.mdx | 28 +++ chapters/ro/chapter3/6.mdx | 301 +++++++++++++++++++++++++++++++ 4 files changed, 864 insertions(+) create mode 100644 chapters/ro/chapter3/3.mdx create mode 100644 chapters/ro/chapter3/4.mdx create mode 100644 chapters/ro/chapter3/5.mdx create mode 100644 chapters/ro/chapter3/6.mdx diff --git a/chapters/ro/chapter3/3.mdx b/chapters/ro/chapter3/3.mdx new file mode 100644 index 000000000..0a47ba072 --- /dev/null +++ b/chapters/ro/chapter3/3.mdx @@ -0,0 +1,174 @@ + + + +# Fine-tuningul unui model cu API-ul Trainer[[fine-tuning-a-model-with-the-trainer-api]] + + + + + +🤗 Transformers oferă o clasă `Trainer` pentru a vă ajuta să faceți fine-tune pe oricare dintre modelurile preantrenate pe care le oferă pe datasetul dvs. Odată ce ați terminat preprocesarea datelor din ultima secțiune, mai aveți doar câteva pași rămași pentru a defini `Trainerul`. Partea cea mai grea este probabil pregătirea environmentul pentru a rula `Trainer.train()`, deoarece va lua mult timp pe un CPU. Dacă nu aveți niciun GPU configurat, puteți accesa gratuit GPUuri sau TPUuri pe [Google Colab](https://colab.research.google.com/). + +Exemplele de cod de mai jos presupune că ați executat exemplele din secțiunea anterioară. Aici este o scurtă recapitulare despre ce aveți nevoie: + +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) +``` + +### Antrenarea[[training]] + +Înainte de a putea defini `Trainerul`, trebuie să definim o clasă `TrainingArguments` care va conține toți hyperparameters pe care `Trainer` le va folosi pentru antrenare și evaluare. Singurul argument pe care trebuie să-l oferiți este un folder în care modelul anternat va fi salvat, precum și checkpointurile de-a lungul drumului. Toate celelalte pot fi lăsate ca valori default, care ar trebui să funcționeze destul de bine pentru un fine-tune de bază. + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 Dacă doriți să încărcați automat modelul pe Hub în timpul antrenării, transmiteți `push_to_hub=True` în `TrainingArguments`. Ne vom întoarce la acest subiect în [Capitolul 4](/course/chapter4/3) + + + +A doua etapă este definiția modelului nostru. Ca în capitolul anterior, vom folosi clasa `AutoModelForSequenceClassification`, cu două labeluri: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Observăm că, spre deosebire de [Capitolul 2](/course/chapter2), se emite o avertizare după crearea acestui model preantrenat. Acest lucru este datorit faptului că BERT nu a fost antrenat pentru clasificarea perechilor de propoziții, astfel încât începutul modelului preantrenat a fost eliminat și un nou început adecvat pentru clasificarea secvențelor a fost adăugat în loc. Avertizările indică faptul că anumite weights nu au fost utilizate (cele care corespundeau începutul eliminat) și celelalte au fost inițializate aleatoriu (cele pentru noul început). Aceasta se termină cu o recomandare de a antrena modelul, ceea ce vom face acum. + +Odată ce am definit modelul nostru, putem defini `Trainer` prin transmiterea tuturor obiectelor construite până acum — `model`, `training_args`, a training și validation datasets, `data_collator`, și `tokenizer`: + +```py +from transformers import Trainer + +trainer = Trainer( + model, + training_args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, +) +``` + +Observăm că, dacă transmitem `tokenizer`, atunci defaultul `data_collator` folosit de `Trainer` va fi un `DataCollatorWithPadding` similar celui definit mai devreme. Din acest motiv, puteți omite linia `data_collator=data_collator` aici. A fost important să vă arătăm această parte în secțiunea 2! + +Pentru a face fine-tune modelului pe datasetul nostru, trebuie să apelăm metoda `train()` a `Trainerului`: + +```py +trainer.train() +``` + +Acest lucru va începe fine-tuningul (care ar trebui să dureze câteva minute pe un GPU) și va raporta training loss la fiecare 500 de pași. Totuși, nu va raporta cât de bine sau rău se descurcă modelul. Acest lucru este datorat: + +1. Nu am transmis `Trainerului` să efectueze evaluarea în timpul antrenării, prin setarea `evaluation_strategy` la `"steps"` (evaluați la fiecare `eval_steps`) sau `"epoch"` (evaluați la finalul fiecărei epoch). +2. Nu am oferit `Trainerului` o funcție `compute_metrics()` pentru a calcula metricele în timpul evaluări(altminter evalurea ar fi printat doar lossul, care nu este un număr foarte intuitiv). + +### Evaluare[[evaluation]] + +Să vedem cum putem construi o funcție `compute_metrics()` folositoare și să o utilizăm la următoarea antrenare. Funcția trebuie să primească obiectul `EvalPrediction` (un named tuple cu fieldul `predictions` și altul `label_ids`) și să returneze un dicționar ce le asociază făcând mapping valorilor string la valori float (stringurile fiind denumirile metricelor returnate, și valorile floats al acestora). Pentru a obține câteva predicții din model, putem folosi comanda `Trainer.predict()`: + +```py +predictions = trainer.predict(tokenized_datasets["validation"]) +print(predictions.predictions.shape, predictions.label_ids.shape) +``` + +```python out +(408, 2) (408,) +``` + +Outputul metodei `predict()` este un named tuple cu trei fielduri: `predictions`, `label_ids` și `metrics`. Câmpul `metrics` va conține doar lossul asupra datasetului transmis, precum și o serie de metrice de timp (cât de mult a luat prezicerea și timpul mediu). Odată ce vom completa funcția `compute_metrics()` și îl vom oferi `Trainerului`, atunci acel field va conține și metricele returnate de `compute_metrics()`. + +După cum puteți vedea, `predictions` este un array bi-dimensional cu shapeul 408x2 (408 fiind numărul de elemente în datasetul folosit). Acestea sunt logiturile pentru fiecare element al datasetului pe care le-am oferit funcției `predict()` (cum ați văzut în [capitolul anterior](/course/chapter2), toate modelel Transformer returneă logituri). Pentru a le transforma în predicții pe care să le comparăm cu labelurile noastre, noi trebui să luăm indexul cu cea mai mare valoare pe axa a doua: + + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +Acum putem compara `preds` cu labelurile. Ca să construim funcțioa noastră `compute_metric()`, o să ne bazăm pe metricele de la librăria 🤗 [Evaluate](https://github.com/huggingface/evaluate/). Putem încărca metricele asociate cu datasetul MRPC la fel de ușor cum am încărcat datasetul, de data asta cu funcția `evaluate.load()`. Obiectul returnat are o metodă `compute()` pe care o putem folosi ca să facem calcularea metricelor: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +metric.compute(predictions=np.argmax(predictions.predictions, axis=-1), references=predictions.label_ids) +``` + +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +Rezultatele exacte pe care le veți obține pot varia, deoarece inițializarea aleatoare a începutului modelului care poate schimba metricele pe care le-a atins. Aici putem vedea că modelul nostru are o precizie de 85.78% în setul de validare și un scor F1 de 89.97. Acestea sunt exact aceleași metrice folosite pentru evaluarea rezultatelor pe datasetul MRPC pentru GLUE benchmark. Tabelul din [BERT Paper](https://arxiv.org/pdf/1810.04805.pdf) a raportat un scor F1 de 88.9 pentru modelul de bază. Acela a fost un `uncased` model, dar noi utilizăm acum un `cased` model, ceea ce explică rezultatul mai bun. + +Pentru a reuni toate acestea într-o singură funcție `compute_metrics()`, putem scrie: + +```py +def compute_metrics(eval_preds): + metric = evaluate.load("glue", "mrpc") + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + return metric.compute(predictions=predictions, references=labels) +``` + +Să vedem acum cum putem utiliza această funcție pentru a raporta metricele la sfârșitu fiecărei epoch, mai jos puteți vedea cum definim un nou `Trainer` cu funcția compute: + +```py +training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch") +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + +trainer = Trainer( + model, + training_args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, + compute_metrics=compute_metrics, +) +``` + +Observăm că am creat un nou `TrainingArguments` cu `evaluation_strategy` setat la `"epoch"` și o nouă instanță a modelului. În cazul nostru, ar fi fost suficient să continuam antrenarea prezentată anterior. + +Pentru a lansa o nouă antrenare, putem executa: + +```py +trainer.train() +``` + +De data aceasta, acesta va raporta validation loss și metricele la sfârșitul fiecărui epocă pe lângă training loss. Dar acuratețea/scorul F1 pe care îl atingeți poate fi puțin diferit față de ceea ce am găsit, datorită inițializării aleatoare a începutului modelului, dar ar trebui să nu difere foarte mult. + +`Trainerul` va funcționa în mod automat pe mai multe GPU-uri sau TPU-uri și oferă multe opțiuni, cum ar fi mixed-precision training(folosiți `fp16 = True` în argumentele de antrenare). Vom discuta despre toate opțiunile pe care le are în Capitolul 10. + +Cu aceasta terminăm introducerea fine-tuningului folosind API-ul `Trainer`. Un exemplu de a face acest lucru pentru majoritatea sarcinilor NLP va fi dat în [Capitolul 7](/course/chapter7), dar pentru moment să vedem cum putem face același lucru doar cu PyTorch. + + + +✏️ **Încearcă!** Fă fine-tune unui model pe datasetul GLUE SST-2, folosind procesarea de date efectuată în secțiunea 2. + + diff --git a/chapters/ro/chapter3/4.mdx b/chapters/ro/chapter3/4.mdx new file mode 100644 index 000000000..4c703260b --- /dev/null +++ b/chapters/ro/chapter3/4.mdx @@ -0,0 +1,361 @@ +# O antrenare completă [[a-full-training]] + + + + + +Acum vom vedea cum putem atinge același rezultat pe care l-am obținut în secțiune anterioară fără a utiliza clasa `Trainer`. Din nou, noi presupunem că ai procesat datele în secțiunea 2. În continuare, găsiți o scurtă sinteză asupra tuturor informațiilor de care aveți nevoie: + +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) +``` + +### Pregătirea pentru antrenare[[prepare-for-training]] + +Înainte de a scrie propriul nostru loop de antrenament, vom avea nevoie să definim câteva obiecte. Primele dintre ele sunt dataloader-urile pe care le vom utiliza pentru a itera peste batch-uri. Dar înainte de a putea defini aceste datealoader-uri, trebuie să aplicăm o anumită postprocesare pe `tokenized_datasets`, pentru a avea grijă de unele lucruri care au fost automatizate de către `Trainer`. În special: + +- Eliminăm coloanele corespunzătoare valorilor pe care modelul nu le așteaptă (ca de exemplu, `sentence1` și `sentence2`). +- Redenumește coloana `label` în `labels`, deoarece modelul așteaptă argumentul să fie numit `labels`. +- Setăm formatul dataseturilor astfel încât ele să returneze tensore PyTorch în loc de liste. + +Metodele noastre `tokenized_datasets` au fiecare unul dintre acești pași: + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +Vom verifica că rezultatul conține doar coloanele pe care vor modelul le acceptă: + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +Acum că am făcut asta, putem defini cu ușurință dataloader-urile noastre: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +Pentru a verifica rapid că nu există nici o greșeală în procesarea datelor, putem inspecta un batch-ul astfel: + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +Observați că shapeurile actuale vor fi probabil ușor diferite pentru tine, deoarece am setat `shuffle=True` pentru dataloader-ul de antrenare și am făcut padding batch-urilor la lungime maximă în interiorul lor. + +Acum că am terminat complet procesarea datelor (un obiectiv satisfăcător, dar înșelător pentru orice specialist ML), să trecem la model. Îl inițializăm exact ca în secțiunea precedentă: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Pentru a ne asigura că totul va merge bine în timpul antrenamentului, vom transmite batch-ul nostru modelului acesta: + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +Toate modelele 🤗 Transformers vor returna lossul atunci când `labels` sunt furnizate și vom primi de asemenea logiturile (două pentru fiecare input în batch-ul nostru, deci un tensor cu dimensiunea 8 x 2). + +Suntem aproape gata să scriem loopul nostru de antrenament! Doar că ne lipsesc două lucruri: un optimizer și learning rate scheduler. Deoarece încercăm să replicăm ceea ce a făcut `Trainer` manual, vom folosi aceleași argumente default. Optimizerul utilizat de `Trainer` este `AdamW`, care este similar cu Adam, dar cu un twist pentru weight decay regularization (vedeți ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) de Ilya Loshchilov și Frank Hutter): + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +La final, learning rate schedulerul utilizat este un linear decay de la valoarea maximă (5e-5) la 0. Pentru a-l defini cu adevărat, avem nevoie să cunoaștem numărul de pași de antrenament pe care îi vom face, care este numărul de epoci pe care dorim să le rulăm înmulțit cu numărul de batch-uri de antrenare (care este lungimea dataloader-ului de antrenare). `Trainer` folosește trei epoci ca default, deci vom continua cu asta: + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### Training Loop[[the-training-loop]] + +Un ultim lucru: vom dori să folosim un GPU dacă avem acces la unul (pe un CPU, instruirea poate dura câteva ore în loc de câteva minute). Pentru a face acest lucru, definim un `device` pe care vom plasa modelul și batch-urile noastre: + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +Suntem acum gata pentru antrenare! Pentru a avea o idee când va fi finalizată antrenarea, adăugăm un progress bar peste numărul nostru de pași de antrenare, folosind biblioteca `tqdm`: + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Poți să vezi că nucleul loopului de instruire are o formă similară cu cea din introducere. Nu am cerut nicio raportare, deci acest loop de antrenare nu ne va spune nimic despre cum se descurcă modelul. Trebuie să adăugăm un evaluation loop pentru a face acest lucru. + + +### Evaluation Loop[[the-evaluation-loop]] + +La fel ca înainte, vom folosi o metrică oferită de librăria 🤗 Evaluate. Am văzut deja metoda `metric.compute()`, dar metricile pot acumula batch-urile pentru noi pe măsură ce mergem peste loopurile de predicțoie cu metoda `add_batch()`. Când am acumulat toate batch-urile, putem să obținem rezultatul final cu `metric.compute()`. Acesta este modul în care trebuie să implementăm acest lucru într-un loop de evaluare: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +Rezultatele tale vor fi ușor diferite datorită randomizării în inițializarea începutului modelului și a data shuffling, dar ele nu ar trebui să fie foart diferite. + + + + +✏️ **Încearcă!** Modifica loopul de antrenare dinainte pentru a face fine-tune modelul pe dataset-ul SST-2. + + + + +### Supercharge Training Loopul cu 🤗 Accelerate[[supercharge-your-training-loop-with-accelerate]] + + + +Loopul de antrenare pe care l-am definit anterior funcționează bine pe un singur CPU sau GPU. Dar folosind libraria [🤗 Accelerate](https://github.com/huggingface/accelerate), cu câteva ajustări putem activa distributed training pe multiple GPU-uri sau TPU-uri. Începând de la crearea training și validation dataloaders, aceasta este este loopul manual de antrenare: + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Și aici sunt schimbările: + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Prima linie de adăugat este importarea librăriei. A doua linie creează un obiect `Accelerator` care va evalua environmentul și va inițializa proper distributed setup. 🤗 Accelerate gestionează automat poziționarea deviceului pentru tine, așa că poți șterge rândurile care pun modelul pe device (sau dacă preferi, poți să le schimbi cu `accelerator.device` în loc de `device`). + +Partea principală a lucrului se face în linia ce trimite dataloader-urile, modelul și optimizerul la `accelerator.prepare()`. Acaeasta va face wrap acestor obiecte în containerul potrivit pentru a asigura o antrenare distribuită corespunzătoare. Ultimele schimbări sunt ștergerea linei cep une batch-ul pe device(din nou, dacă vvrei să lași acest lucru poți să le schimbi cu `accelerator.device`) și schimbarea `loss.backward()` cu `accelerator.backward(loss)`. + + +⚠️ Pentru a beneficia de viteza oferită de Cloud TPUs, recomandăm să faceți padding sampleurilor la o lungime fixă folosind argumentele `padding="max_length"` și `max_length` ale tokenizerului. + + +Dacă vrei să copiezi codul pentru a-l testa, aici este loopul complet de antrenare cu Accelerate: + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Scriind în `train.py` aceste modificări, vor face scriptul executabil pe orice tip de distributed setup. Pentru a testa codul în mediul tău distribuit, rulează următoarea comandă: + +```bash +accelerate config +``` + +Ceea ce îți va oferi să răspunzi la o serie de întrebări și să salvezi răspunsurile într-un fișier de configurare folosit de acest modul: + +```bash +accelerate launch train.py +``` + +Această comandă va lansa loopul de antrenare pe dispozitivele distribuite. + +Dacă doriți să încercați acest lucru într-un Jupyter Notebook (de exemplu, pentru a testa TPU-urile de pe Colab), înlocuiți codul cu următoarea funcție: + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +Pentru mai multe exemple consultați [repo-ul 🤗 Accelerate](https://github.com/huggingface/accelerate/tree/main/examples). \ No newline at end of file diff --git a/chapters/ro/chapter3/5.mdx b/chapters/ro/chapter3/5.mdx new file mode 100644 index 000000000..9afa55a7d --- /dev/null +++ b/chapters/ro/chapter3/5.mdx @@ -0,0 +1,28 @@ +```mdx chapters/en/chapter3/5.mdx (1-26) + + +# Fine-tuning, Check![[fine-tuning-check]] + + + + +A fost distractiv! În primele două capitole ai învățat despre modele și tokenizeri, iar acum știi cum să aplici fine-tune pentru datele proprii. Pentru a rezuma, în acest capitol tu: + +{#if fw === 'pt'} +* Ai învățat despre dataseturi din [Hub](https://huggingface.co/datasets) +* Ai învățat cum să încarci și să preprocesezi dataseturi, inclusiv folosind dynamic padding și collators +* Ai implementat fine-tuningul și evaluarea unui model +* Ai implementat un training loop la nivel de bază +* Ai utilizat 🤗 Accelerate pentru a adapta ușor training loopul astfel încât să funcționeze cu mai multe GPU-uri sau TPU-uri + +{:else} +* Ai învățat despre dataseturi din [Hub](https://huggingface.co/datasets) +* Ai învățat cum să încarci și să preprocesezi dataseturi +* Ai învățat cum să faci fine-tune și să evaluezi un model cu Keras +* Ai implementat o metrică personalizată + +{/if} +``` \ No newline at end of file diff --git a/chapters/ro/chapter3/6.mdx b/chapters/ro/chapter3/6.mdx new file mode 100644 index 000000000..070acdd2e --- /dev/null +++ b/chapters/ro/chapter3/6.mdx @@ -0,0 +1,301 @@ + + + + +# Quiz pentru finalizarea capitolului[[end-of-chapter-quiz]] + + + +Să testăm ceea ce ai învățat în acest capitol! + +### 1. La ce sunt limitate modelele din Hub? + + + +### 2. Cum poți gestiona modelele pe Hub? + +git-lfs pentru fișiere mari.", + corect: true + } + ] } +/> + +### 3. Ce poți face utilizând interfața web a Hugging Face Hub? + + + +### 4. Ce este un model card? + + + +Chapitru 4 + +### 5. Câte dintre obiectele bibliotecii 🤗 Transformers pot fi împărtășite direct pe Hub cu `push_to_hub()`? + +{#if fw === 'pt'} +push_to_hub, și utilizând-o, vor împărtăși toate fișierele tokenizerului (vocabular, arhitectură tokenizerului, etc.) către un repo. Însă acesta nu este singura soluție corectă!", + correct: true + }, + { + text: "O configurare a modelului", + explain: "Corect! Toate configurațiile modelelor au metoda push_to_hub, și utilizând-o, vor împărtăși configurația către un repo. Și ce altceva poți oferi?", + correct: true + }, + { + text: "Un model", + explain: "Corect! Toate modelele au metoda push_to_hub, și utilizând-o, vor împărtăși ei, precum și fișierele de configurare către un repo. Și nu numai asta!", + correct: true + }, + { + text: "Un Trainer", + explain: "Corect — Trainer implementează metoda push_to_hub, și utilizând-o, vor încărca modelul, configurarea sa, tokenizerul, precum și un draft a unui model card către un repo. Încearcă și altă opțiune!", + correct: true + } + ]} +/> + +{:else} +push_to_hub, și utilizând-o, vor împărtăși toate fișierele tokenizerului (vocabular, arhitectură tokenizerului, etc.) către un repo. Însă acesta nu este singura soluție corectă!", + correct: true + }, + { + text: "O configurare a modelului", + explain: "Corect! Toate configurațiile modelelor au metoda push_to_hub, și utilizând-o, vor împărtăși configurația către un repo. Și ce altceva poți oferi?", + correct: true + }, + { + text: "Un model", + explain: "Corect! Toate modelele au metoda push_to_hub, și utilizând-o, vor împărtăși ei, precum și fișierele de configurare către un repo. Și nu numai asta!", + correct: true + }, + { + text: "Toate cele trei cu un callback dedicat", + explain: "Corect — PushToHubCallback va trimite regular toate aceste obiecte către un repo în timpul antrenării.", + correct: true + } + ]} +/> +{/if} + +### 6. Care este primul pas atunci când utilizați metoda `push_to_hub()` sau instrumentele CLI? + + + + +### 7. Aveți un model și un tokenizer, cum le puteți încărca pe ambele în Hub? + +huggingface_hub", + explain: "Modelele și tokenizerii deja beneficiază de utilitățile huggingface_hub: nu vă trebuie nici un wrapping suplimentar!" + }, + { + text: "Prin salvarea lor pe disc și apelarea transformers-cli upload-model", + explain: "Comanda upload-model nu există." + } + ]} +/> + +### 8. Carele operații git poți face cu clasa `Repository`? + +git_commit() este acolo pentru a face commit.", + correct: true + }, + { + text: "Un pull", + explain: "Acesta este scopul metodei git_pull()", + correct: true + }, + { + text: "Un push", + explain: "Metoda git_push() face acest lucru.", + correct: true + }, + { + text: "Un merge", + explain: "Nu, această operație nu va fi niciodată posibilă cu acest API." + } + ]} +/> \ No newline at end of file From 1c9d20a761c0883404096211aac071e86a27953d Mon Sep 17 00:00:00 2001 From: Angroys <120798951+Angroys@users.noreply.github.com> Date: Sun, 5 Jan 2025 01:42:24 +0200 Subject: [PATCH 05/37] Finished chapter 5 for the ro language --- chapters/en/chapter5/3.mdx | 1 - chapters/ro/chapter5/1.mdx | 22 ++ chapters/ro/chapter5/2.mdx | 173 +++++++++ chapters/ro/chapter5/3.mdx | 748 +++++++++++++++++++++++++++++++++++++ chapters/ro/chapter5/4.mdx | 287 ++++++++++++++ chapters/ro/chapter5/5.mdx | 404 ++++++++++++++++++++ chapters/ro/chapter5/6.mdx | 517 +++++++++++++++++++++++++ chapters/ro/chapter5/7.mdx | 16 + chapters/ro/chapter5/8.mdx | 228 +++++++++++ 9 files changed, 2395 insertions(+), 1 deletion(-) create mode 100644 chapters/ro/chapter5/1.mdx create mode 100644 chapters/ro/chapter5/2.mdx create mode 100644 chapters/ro/chapter5/3.mdx create mode 100644 chapters/ro/chapter5/4.mdx create mode 100644 chapters/ro/chapter5/5.mdx create mode 100644 chapters/ro/chapter5/6.mdx create mode 100644 chapters/ro/chapter5/7.mdx create mode 100644 chapters/ro/chapter5/8.mdx diff --git a/chapters/en/chapter5/3.mdx b/chapters/en/chapter5/3.mdx index 9e6e738bc..8bde80552 100644 --- a/chapters/en/chapter5/3.mdx +++ b/chapters/en/chapter5/3.mdx @@ -526,7 +526,6 @@ train_df = drug_dataset["train"][:] - From here we can use all the Pandas functionality that we want. For example, we can do fancy chaining to compute the class distribution among the `condition` entries: ```py diff --git a/chapters/ro/chapter5/1.mdx b/chapters/ro/chapter5/1.mdx new file mode 100644 index 000000000..7fd54a2ad --- /dev/null +++ b/chapters/ro/chapter5/1.mdx @@ -0,0 +1,22 @@ +# Introducere[[introduction]] + + + +În [Capitolul 3](/course/chapter3) ați încercat a biblioteca 🤗Datasets și ați văzut că existau trei pași principali atunci când vine vorba de fine-tuningul unui model: + +1. Încărcați un dataset din Hugging Face Hub. +2. Preprocesați datele cu `Dataset.map()`. +3. Încărcați și calculați metricele. + +Dar acesta este doar o mică parte a ceea ce poate face 🤗 Datasets! În acest capitol, vom trece mai in deep în această bibliotecă. Pe parcurs, vom găsi răspunsuri la următoarele întrebări: + +* Ce faceți atunci când datasetul tău nu este pe Hub? +* Cum puteți tăia și împărți un dataset? (Și ce dacă tu _really_ trebuie să folosești Pandas?) +* Ce faceți atunci când datasetul este uriaș și va topi RAM-ul laptopului dumneavoastră? +* Ce este "memory mapping" și Apache Arrow? +* Cum puteți crea propriul dataset și să-l trimiteți pe Hub? + +Tehnicile pe care le veți învăța aici vă vor pregăti pentru sarcinile avansate de tokenizare și fine-tuning din [Capitolul 6](/course/chapter6) și [Capitolul 7](/course/chapter7) -- deci luați o cafea sau două și să începem! diff --git a/chapters/ro/chapter5/2.mdx b/chapters/ro/chapter5/2.mdx new file mode 100644 index 000000000..1f78a1a05 --- /dev/null +++ b/chapters/ro/chapter5/2.mdx @@ -0,0 +1,173 @@ +# Ce-ar fi dacă dataset-ul meu nu este pe Hub?[[what-if-my-dataset-isnt-on-the-hub]] + + + +Ai învățat să folosești [Hugging Face Hub](https://huggingface.co/datasets) pentru a descărca dataseturi, dar vei găsi adesea că lucrați cu date care sunt stocate fie pe laptopul dumneavoastră, fie pe un server. În această secțiune vă vom arăta cum poate fi utilizat 🤗 Datasets pentru a încărca dataseturi care nu sunt disponibile pe Hugging Face Hub. + + +## Lucrând cu dataseturi locale și remote[[working-with-local-and-remote-datasets]] + +🤗 Datasets oferă loading scripts pentru a gestiona încărcarea dataseturilor locale și remote. Suportă mai multe data formats , cum ar fi: + +| Data format | Loading script | Example | +| :----------------: | :------------: | :-----------------------------------------------------: | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Text files | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| Pickled DataFrames | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | + +As shown in the table, for each data format we just need to specify the type of loading script in the `load_dataset()` function, along with a `data_files` argument that specifies the path to one or more files. Let's start by loading a dataset from local files; later we'll see how to do the same with remote files. + +## Loading a local dataset[[loading-a-local-dataset]] + +For this example we'll use the [SQuAD-it dataset](https://github.com/crux82/squad-it/), which is a large-scale dataset for question answering in Italian. + +The training and test splits are hosted on GitHub, so we can download them with a simple `wget` command: + +În tabelele de mai sus, pentru fiecare format de date trebuie doar să specificăm tipul scriptului de încărcare din funcția `load_dataset()`, alături de un argument `data_files` care specifică calea către un sau mai multe fișiere. Începem cu încărcarea datasetului din fișierele locale; apoi vom vedea cum să faceți același lucru cu fișierele remote. + +## Încărcarea unui dataset local[[loading-a-local-dataset]] + +În acest exemplu, vom folosi [datasetul SQuAD-it](https://github.com/crux82/squad-it/), care este un large-scale dataset pentru întrebări și răspunsuri în italiană. + +Spliturile de training și test sunt disponibile pe GitHub, deci putem descarca cu o comanda `wget`: +```python +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz +``` + +This will download two compressed files called *SQuAD_it-train.json.gz* and *SQuAD_it-test.json.gz*, which we can decompress with the Linux `gzip` command: + +```python +!gzip -dkv SQuAD_it-*.json.gz +``` + +```bash +SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json +SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json +``` + + + +Putem observa că fișierele comprimate au fost înlocuite cu _SQuAD_it-train.json_ și _SQuAD_it-test.json_, iar datele sunt stocate în formatul JSON. + + +✎ Dacă vă întrebați de ce există un caracter `!` în comenziile shell din exemplu, acest lucru se întâmplă pentru că le-am rulat în cadrul unui jupyter notebook. Simplu să ștergeți prefixul dacă doriți să descărcați și să faceți unzip datasetului într-un terminal. + + +Să încarcăm acum un fișier JSON cu funcția `load_dataset()`. Ne trebuie doar să știm dacă ne confruntăm cu un JSON obișnuit (similar cu un nested dictionary) sau JSON Lines (lines-separated JSON). Cum multe dataset-uri pentru întrebări și răspunsuri, SQuAD-it folosește formatul nested, în care toate textele sunt stocate într-un câmp numit `data`. Acest lucru ne permite să încărcăm datasetul specificând argumentul `field` astfel: + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +By default, loading local files creates a `DatasetDict` object with a `train` split. We can see this by inspecting the `squad_it_dataset` object: + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +Acest lucru ne arată numărul de rânduri și numele coloanelor asociate cu setul de antrenare. Putem vizualiza un exemplu prin a indexa în `train` split astfel: +```py +squad_it_dataset["train"][0] +``` + +```python out +{ + "title": "Terremoto del Sichuan del 2008", + "paragraphs": [ + { + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + "qas": [ + { + "answers": [{"answer_start": 29, "text": "2008"}], + "id": "56cdca7862d2951400fa6826", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", + }, + ... + ], + }, + ... + ], +} +``` + +Excelent, am reușit să încarcăm datasetul nostru local! Dar, deși acest lucru a funcționat pentru setul de antrenare, ceea ce dorim cu adevărat este să includem ambele splituri, `train` și `test`, într-un singur obiect `DatasetDict` astfel încât să putem aplica funcțiile `Dataset.map()` asupra ambelor splituri în același timp. Pentru a face acest lucru, putem furniza un dicționar pentru argumentul `data_files` care va face maps fiecărui spilt name cu fișierul asociat acelui split: + +```py +data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) + test: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 48 + }) +}) +``` + +Astfel obținem exact ceea ce am dori. Acum putem aplica diverse tehnici de preprocesare pentru a curăța datele, să facem tokenize recenziilor și așa mai departe. + + + +Argumentul `data_files` al funcției `load_dataset()` este destul de flexibil și poate fi fie un singur file paths, o listă de file paths, sau un dicționar care face maps split nameurilor cu file pathurile. De asemenea putem să folosim glob files care se potrivest unui model specificat conform regulilor folosite de Unix shell (de exemplu putem să facem glob tuturor fișierelor JSON într-un folder ca un singur split setând `data_files="*.json"`). Pentru mai multe detalii consultați [documentația](https://huggingface.co/docs/datasets/loading#local-and-remote-files) 🤗 Datasets. + + + +Scripturile de încărcare din 🤗 Datasets suportă automat decomprimarea fișierelor de intrare, astfel putem să evităm folosirea `gzip` arătând argumentul `data_files` direct către fișierele comprimate: + +```py +data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Acest lucru poate fi util dacă nu dorim să manualizăm dezcomprimarea mai multe fișiere GZIP. Automatic decompression se aplică și altor formate precum ZIP și TAR, astfel putem să indicăm `data_files` către fișierele comprimate și suntem gata! + +Acum că știm cum să încărcăm fișierele locale pe laptop sau desktop, să vedem cum să încarcăm fișiere remote. + +## Încărcarea unui dataset remote[[loading-a-remote-dataset]] + +Dacă lucrați ca Data Scientist sau programatori într-o companie, există șanse mari că dataseturile pe care doriți să le analizați sunt stocate pe un anumit server. Din fericire, încărcarea fișierelor remote este la fel de simplă ca încărcarea celor locale! În schimb putem să oferim pathul la fișiere locale, noi oferim argumentului `data_files` de la metoda `load_dataset()` către una sau mai multe adrese URL unde sunt stocate fișierele remote. De exemplu pentru datasetul SQuAD-it găzduit pe GitHub, putem să oferitum argumentului `data_files`, URL-urile _SQuAD_it-*.json.gz_ astfel: + +```py +url = "https://github.com/crux82/squad-it/raw/master/" +data_files = { + "train": url + "SQuAD_it-train.json.gz", + "test": url + "SQuAD_it-test.json.gz", +} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Astfel obținem același obiect `DatasetDict` ca mai sus, dar economisim timp, deoarece nu descărcăm și decomprimăm fișierele _SQuAD_it-*.json.gz_. Asta încheie aventura noastră în diversele modalități de încarcare a dataseturilor care nu sunt găzduite pe Hugging Face Hub. Acum că am avem un dataset la dispoziție, hai să îl prelucrăm! + + + +✏️ **Încearcă!** Alegeți alt dataset găzduit pe GitHub sau [UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php) și încercați să îl încărcați atât local cât și remote folosind tehniciile introduse mai sus. Pentru puncte bonus, încercați să încărcați un dataset care este stocat în format CSV sau text (consultați [documentația](https://huggingface.co/docs/datasets/loading#local-and-remote-files) pentru detalii suplimentare despre aceste formate). + + + + + + diff --git a/chapters/ro/chapter5/3.mdx b/chapters/ro/chapter5/3.mdx new file mode 100644 index 000000000..dcc99f474 --- /dev/null +++ b/chapters/ro/chapter5/3.mdx @@ -0,0 +1,748 @@ +# E timpul să facem slice și dice[[time-to-slice-and-dice]] + + + +În cea mai mare parte, datele cu care lucrezi nu vor fi perfect pregătite pentru antrenarea modelelor. În această secțiune vom explora features variate pe care 🤗 Datasets le oferă pentru curățirea dataseturilor. + + + +## Slicing și dicing asupra datelor[[slicing-and-dicing-our-data]] + +Asemenea Pandas, 🤗 Datasets oferă mai multe funcții pentru a manipula conținutul obiectelor `Dataset` și `DatasetDict`. Am întâlnit deja metoda `Dataset.map()` în [Capitolul 3](/course/chapter3), iar în această secțiune vom explora alte funcții de care dispunem. + +În acest exemplu, vom folosi [Drug Review Dataset](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) găzduit pe [UC Irvine Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php), care conține reviewurile pacienților privind diverse medicamente, alături de bolile care sunt tratate și o evaluare de 10 stele a satisfacției pacientului. + +În primul rând trebuie să descărcăm și să extragem datele, ceea ce se poate de făcut cu comenzile `wget` și `unzip`: + +```py +!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" +!unzip drugsCom_raw.zip +``` + +Deoarece TSV este o variantă a CSV care folosește taburi în loc de virgulă ca separator, putem încărca aceste fișiere prin folosirea scriptului de încărcare `csv` și specificarea argumentului `delimiter` în funcția `load_dataset()` astfel: + +```py +from datasets import load_dataset + +data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} +# \t este caracterul tab de Python +drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") +``` + +O practică bună atunci când faceți orice fel de analiză a datelor este să vă luați un mic random sample pentru a înțelege cu ce tip de date lucrați. În 🤗 Datasets, putem crea o colecție aleatorie prin legarea funcțiilor `Dataset.shuffle()` și `Dataset.select()`: + +```py +drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) +# Vizualizați primele câteva exemple +drug_sample[:3] +``` + +```python out +{'Unnamed: 0': [87571, 178045, 80482], + 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], + 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], + 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', + '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', + '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], + 'rating': [9.0, 3.0, 10.0], + 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], + 'usefulCount': [36, 13, 128]} +``` + +Atrageți atenția că am fixat seedul în `Dataset.shuffle()` pentru posibilitatea de reproducere. `Dataset.select()` se așteaptă la un iterabil cu indices, deci noi am scris `range(1000)` pentru a primi primele 1000 de exemple din datasetul amestecat. Din acest sample putem vedea câteva ciudățenii în datasetul nostru: + +* Coloana `Unnamed: 0` are un aspect neobișnuit, care sugerează că este un anonymized ID a fiecărui pacient. +* Coloana `condition` conține labeluri majuscule și minuscule. +* Recenziile au lungimi variate și conțin caractere Python precum `\r\n` şi caractere HTML ca `&\#039;`. + +Hai să vedem cum putem folosi 🤗 Datasets pentru a face față fiecărei dintre aceste probleme. Pentru a testa ipoteza identificării pacientului pentru coloana `Unnamed: 0`, putem folosi funcția `Dataset.unique()` pentru a verifica dacă numărul de ID-uri corespunde cu numărul de rânduri în fiecare split: + +```py +for split in drug_dataset.keys(): + assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) +``` + +Aceasta pare să confirme ipoteza, deci putem curăța datasetul puțin, redenumind coloana `Unnamed: 0` pentru a-i da un nume mai interpretabil. Putem folosi funcția `DatasetDict.rename_column()` pentru a renumea coloana în același timp în ambele splituri: + +```py +drug_dataset = drug_dataset.rename_column( + original_column_name="Unnamed: 0", new_column_name="patient_id" +) +drug_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 161297 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 53766 + }) +}) +``` + + + +✏️ **Încearcă!** Folosiți funcția `Dataset.unique()` pentru a găsi numărul de medicamente și condiții unice în seturile de antrenare și testare. + + + +În continuare, vom normaliza toate `condition` labels folosind `Dataset.map()`. La fel cum am făcut cu tokenizarea în [Capitolul 3](/course/chapter3), putem defini o funcție simplă care poate fi aplicată pe toate rândurile fiecărui split din `drug_dataset`: + +```py +def lowercase_condition(example): + return {"condition": example["condition"].lower()} + + +drug_dataset.map(lowercase_condition) +``` + +```python out +AttributeError: 'NoneType' object has no attribute 'lower' +``` + +Oh no, am întâmpinat o problemă cu funcția map! Din eroarea noastră se poate deduce că unele intrări din coloana `condition` sunt `None`, care nu pot fi convertite la caracterul mic pentru că nu sunt string-uri. Vom elimina aceste rânduri folosind `Dataset.filter()`, care funcționează în mod similar cu `Dataset.map()` și se așteaptă o funcție care primește un exemplu al datasetului. + +În loc de a scrie o funcție explicită ca: + +```py +def filter_nones(x): + return x["condition"] is not None +``` + +și apoi să rulăm `drug_dataset.filter(filter_nones)`, putem face acest lucru într-o linie folosind o _funcție lambda_. În Python, funcțiile lambda sunt funcții mici care pot fi definite fără a le numi. Ele au forma generală: + +```py +lambda : +``` + +unde `lambda` este unul dintre [cuvintele cheie](https://docs.python.org/3/reference/lexical_analysis.html#keywords) Python, `` reprezintă o listă/set de valori separate prin virgulă care definesc inputurile funcției și `` reprezintă operațiile pe care dorim să le executăm. De exemplu, putem defini o funcție lambda care ridică un număr la pătrat: + +```py +lambda x : x * x +``` + +Pentru a aplica această funcție la un input, trebuie să îi facem wrap și pe să punem inputul în paranteze: + +```py +(lambda x: x * x)(3) +``` + +```python out +9 +``` + +La fel, putem defini funcții lambda cu mai multe argumente prin separarea acestora prin virgulă. De exemplu, putem calcula suprafața unui triunghi ca: + +```py +(lambda base, height: 0.5 * base * height)(4, 8) +``` + +```python out +16.0 +``` + +Funcțiile lambda sunt utile atunci când dorim să definim funcții mici, pentru o singură folosire (pentru mai multe informații despre ele, recomandăm citirea excelentului [Real Python tutorial](https://realpython.com/python-lambda/) scris de Andre Burgaud). În contextul 🤗 Datasets, putem utiliza funcțiile lambda pentru a defini operații simple de map și filter, astfel încât să eliminăm intrările `None` din datasetul nostru: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) +``` + +Cu intrările `None` eliminate, putem normaliza coloana `condition`: + +```py +drug_dataset = drug_dataset.map(lowercase_condition) +# Verificăm dacă lowercasing a funcționat +drug_dataset["train"]["condition"][:3] +``` + +```python out +['left ventricular dysfunction', 'adhd', 'birth control'] +``` + +Funcționează! Acum că am curățat labelurile, să vedem cum putem curăți și recenziile. + +## Crearea de noi coloane + +Atunci când lucrați cu recenziile clienților, o practică bună este să verificați numărul de cuvinte în fiecare recenzie. O recenzie poate fi doar un singur cuvânt, cum ar fi "Excelent!" sau un eseu complet care are sute de cuvinte și depinde de cazul pe care îl aveți la vedere, aici trebuie să vă asigurați că faceți față acestor extreme diferit. Pentru a calcula numărul de cuvinte în fiecare recenzie, vom folosi un heuristic aproximativ bazat pe splittingul textului prin spații. + +Vom defini o funcție simplă care numără numărul de cuvinte din fiecare recenzie: + +```py +def compute_review_length(example): + return {"review_length": len(example["review"].split())} +``` + +Spre deosebire de funcția `lowercase_condition()`, `compute_review_length()` returnează un dicționar ale cărui key nu corespund uneia dintre numele coloanelor din dataset. În acest caz, atunci când `compute_review_length()` este transmis în `Dataset.map()`, el va fi aplicat pe toate rândurile din dataset pentru a crea o nouă coloană `review_length`: + +```py +drug_dataset = drug_dataset.map(compute_review_length) +# Inspectăm primul exemplu de training +drug_dataset["train"][0] +``` + +```python out +{'patient_id': 206461, + 'drugName': 'Valsartan', + 'condition': 'left ventricular dysfunction', + 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', + 'rating': 9.0, + 'date': 'May 20, 2012', + 'usefulCount': 27, + 'review_length': 17} +``` + +Așa cum era de așteptat, putem vedea o nouă coloană `review_length` adăugată la setul de antrenare. Putem sorta această nouă coloană cu `Dataset.sort()` pentru a vedea cum valorile extreme arată: + +```py +drug_dataset["train"].sort("review_length")[:3] +``` + +```python out +{'patient_id': [103488, 23627, 20558], + 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], + 'condition': ['birth control', 'muscle spasm', 'pain'], + 'review': ['"Excellent."', '"useless"', '"ok"'], + 'rating': [10.0, 1.0, 6.0], + 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], + 'usefulCount': [5, 2, 10], + 'review_length': [1, 1, 1]} +``` + +Precum am presupus, unele recenii conțin doar un singur cuvânt, ceea ce, deși ar putea fi OK pentru analiza sentimentului, nu ar fi informativ dacă vrem să prezicem condiției. + + + +🙋 O alternativă la adăugarea unei noi coloane într-un dataset este funcția `Dataset.add_column()`. Aceasta permite să oferiți coloana ca o listă Python sau array NumPy și poate fi utilă în situații în care `Dataset.map()` nu este bine adaptat pentru analiza dumneavoastră. + + + +Hai să folosim funcția `Dataset.filter()` pentru a elimina recenziile care conțin mai puțin de 30 de cuvinte. Similar cum am făcut în cazul coloanei `condition`, putem elimina recenziile foarte scurte cerând ca recenziile să aibă o lungime mai mare decât acest prag: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) +print(drug_dataset.num_rows) +``` + +```python out +{'train': 138514, 'test': 46108} +``` + +După cum vedeți, aceasta a eliminat aproximativ 15% din recenziile noastrem, din seturile originale de antrenare și testare. + + + +✏️ **Încercați!** Folosiți funcția `Dataset.sort()` pentru a inspecta recenziile cu cele mai mari numere de cuvinte. Vezi [documentația](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.sort) pentru a vedea ce argument trebuie să folosești pentru a sorta recenziile în ordine descrescătoare. + + + +Ultima chestie de care trebuie să ne ocupăm este prezența caracterelor HTML în recenziile noastre. Putem folosi modulul `html` din Python pentru a face unescape acestor caractere: + +```py +import html + +text = "I'm a transformer called BERT" +html.unescape(text) +``` + +```python out +"I'm a transformer called BERT" +``` + +Vom folosi `Dataset.map()` pentru a face unescape toate caracterele HTML din corpus: + +```py +drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) +``` + +În mod evident, metoda `Dataset.map()` este foarte utilă pentru procesarea datelor – și nu am abordat decât o mică parte din ceea ce poate face! + +## Superputerile metodei `map()`[[the-map-methods-superpowers]] + +Metoda `Dataset.map()` acceptă un argument `batched` care, dacă este setat pe `True`, cauzează ca ea să trimită un batch de exemple la funcția map în același timp (dimensiunea batchului poate fi configurată dar defaultul este 1.000). De exemplu, anterior am folosit o funcție map care a făcut unescaped toate caracterele HTML din recenziile noastre și i-a luat câteva secunde să execute (puteți citi timpul pe progress bars). Putem accelera acest lucru prin procesarea mai multor elemente în același timp folosind list comprehension. + +Când specificați `batched=True` funcția primește un dicționar cu câmpurile datasetului, dar fiecare valoare este acum _list of values_ și nu doar o singură valoare. Valoarea de return a `Dataset.map()` ar trebui să fie la fel: un dicționar cu câmpurile pe care dorim să le actualizăm sau adăugăm în datasetul nostru, și o listă de valori. De exemplu, mai jos este alt mod de a face unescape tuturor caracterelor HTML din recenziile noastre, folosind `batched=True`: + +```py +new_drug_dataset = drug_dataset.map( + lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True +) +``` + +Dacă executați acest cod într-un notebook, veți vedea că această comandă se execută mult mai rapid decât cea anterioră. Și nu pentru că recenziile noastre au fost deja HTML-unescaped – dacă reexecutați instrucția precedentă (fără `batched=True`), ea va lua același timp ca înainte. Acest lucru se datorează faptului că list comprehension sunt mai rapide decât executarea aceluiași cod într-un `for` loop, și am câștigat, de asemenea, puțină performanțp accesând multe elemente în același timp, în loc unul câte unul. + +Folosirea `Dataset.map()` cu `batched=True` este esențială pentru a obțina viteza "rapidă" a tokenizerilor pe care îi vom întâlni în [Capitolul 6](/course/chapter6), care pot repede să facă tokenize listelor mari de texte. De exemplu, pentru a face tokenize tuturor recenziilor medicamentelor cu un tokenizer rapid, putem folosi o funcție ca aceasta: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + + +def tokenize_function(examples): + return tokenizer(examples["review"], truncation=True) +``` + +Așa cum am văzut în [Capitolul 3](/course/chapter3), putem transmite un singur sau câteva exemple către tokenizer, așadar putem folosi această funcție cu sau fără `batched=True`. Hai să ne folosim de această oportunitate și să comparăm performanța diferitelor opțiuni. Într-un notebook puteți măsura timpul unei instrucțiie de o line prin adăugarea `%time` înaintea acelei linii de cod pe care doriți să o măsurați: + +```python no-format +%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) +``` + +Puteți și să măsurați un întreg cell prin scrierea `%%time` la începutul celulei. În hardware-ul pe care l-am executat, acest lucru a arătat 10.8s pentru această instrucție (este numărul scris după "Wall time"). + + + +✏️ **Încercați!** Executați aceeași instrucție cu și fără `batched=True`, apoi încercați-o cu un tokenizer lent (adaugați `use_fast=False` în metoda `AutoTokenizer.from_pretrained()`), astfel să puteți vedea ce numere obțineți pe hardwareul vostru. + + + +Aici sunt rezultatele pe care le-am obținut cu și fără batching, folosind un tokenizer rapid și lent: + +Options | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + +Aceasta înseamnă că utilizarea unui tokenizer rapid cu opțiunea `batched=True` este de 30 de ori mai rapidă decât varianta lentă fără batching - acest lucru este pur și simplu uimitor! Acesta este motivul principal pentru care tokenizerii rapizi sunt setați implicit când se utilizează `AutoTokenizer` (și de ce sunt numiți "rapizi"). Ei pot atinge o asemenea accelerație datorită faptului că codul de tokenizare este executat în Rust, care este un limbaj care facilitează paralelizarea execuției. + +Parallelization este și motivul pentru care tokenizerul rapid realizează o accelerare de aproape 6 ori cu batching: nu puteți paraleliza o singură operație de tokenizare, dar atunci când doriți să tokenizați multe texte în același timp, puteți să faceți split execuției pe mai multe procese, fiecare răspunzând pentru propriile texte. + +`Dataset.map()` are și o capacitate de parallelization proprie. Deoarece nu sunținute de Rust, nu pot să le ofere aceeași accelerație tokenizerilori înceți ca tokenizerilor rapizi, dar pot încă fi utili (în special dacă utilizați un tokenizer care nu are o variantă rapidă). Pentru a activa multiprocessingul, folosiți argumentul `num_proc` și specificați numărul de procese să fie utilizate în apelul `Dataset.map()`: + +```py +slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) + + +def slow_tokenize_function(examples): + return slow_tokenizer(examples["review"], truncation=True) + + +tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) +``` + +Puteți experimenta puțin cu timpii pentru a determina numărul de procese optime; în cazul nostru 8 s-a dovedit a produce cea mai mare accelerație. Aici sunt rezultatele pe care le-am obținut cu și fără multiprocessing: + +Options | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s +`batched=True`, `num_proc=8` | 6.52s | 41.3s +`batched=False`, `num_proc=8` | 9.49s | 45.2s + +Aceste rezultate sunt mult mai bune pentru tokenizerul lent, dar și performanța tokenizerului rapid a fost semnificativ îmbunătățită. Cu toate acestea, trebuie să reamintim că aceasta nu va fi întotdeauna cazul - testele noastre au arătat că este mai rapid să utilizați `batched=True` fără acest argument în cazurile în care valoarea lui `num_proc` diferă de 8. În general, nu vă recomandăm utilizarea multiplicării proceselor pentru tokenizorii rapizi cu `batched=True`. + + + +Utilizarea `num_proc` pentru a accelera procesarea este de obicei o idee excelentă, atâta timp cât funcția pe care o utilizați nu utilizează deja multiprocessing. + + + +Toate aceste funcționalități condensate într-o singură metodă este foarte impresionant, dar asta nu e totul! Cu `Dataset.map()` și `batched=True` puteți modifica numărul de elemente din datasetul dumneavoastră. Acesta este extrem de util în numeroase situații în care doriți să creați mai multe caracteristici de antrenare dintr-un singur exemplu, și vom avea nevoie de acest lucru ca parte a preprocesării pentru câteva dintre sarcinile NLP pe care le vom discuta în [Capitolul 7](/course/chapter7). + + + +💡 În machine learning, un _exemplu_ este de obicei definit ca fiind un set de _features_ care se oferă modelului. În unele contexte, acestea vor fi seturile de coloane dintr-un `Dataset`, dar în altele (ca și aici și pentru răspunderea la întrebări) mai multe caracteristici pot fi extrase dintr-un singur exemplu și să aparțină unei singure coloane. + + + +Hai să vedem cum funcționează! Aici vom tokeniza exemplele și le vom face truncatela lungimea maximă de 128, dar vom cere tokenizerului să returneze *toate* chunkurile de text în loc de prima. Acest lucru poate fi făcut cu `return_overflowing_tokens=True`: + +```py +def tokenize_and_split(examples): + return tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) +``` + +Hai să testăm acest lucru pe un exemplu înainte de a folosi `Dataset.map()` pentru întreg datasetul: + +```py +result = tokenize_and_split(drug_dataset["train"][0]) +[len(inp) for inp in result["input_ids"]] +``` + +```python out +[128, 49] +``` + +Așadar, primul nostru exemplu din setul de antrenare a devenit două features pentru că a fost tokenizat mai mult decât lungimea maximă de tokenuri pe care am specificat-o: prima cu lungimea 128 și a doua cu lungimea 49. Acum trebuie să facem acest lucru pentru toate elementele din dataset! + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +``` + +```python out +ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 +``` + +Oh no! Acesta nu a funcționat! De ce? Citind eroarea vom afla motivul: existența unei incompatibilități în lungimea uneia dintre coloane - una cu o lungime de 1,463 și alta de 1,000. Dacă ați privit [documentația](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map) Dataset.map(), puteți să vă reamintiți că este numărul de sampleuri care sunt oferite funcției pe care noi le facem mapping; aici aceste 1,000 exemplare creează 1,463 features noi, ceea ce duce la un shape error. + +Problema este că încercăm să amestecăm două dataseturi cu mărimi diferite: coloanele `drug_dataset` vor avea un număr determinat de exemple (cele 1,000 din eroare), dar `tokenized_dataset` pe care îl construim va fi mai mare (cel cu 1,463 din eroare; el este mai mare decât 1,000 pentru că tokenizăm reviewrile lungi în mai multe exemple folosind `return_overflowing_tokens=True`). Acest lucru nu funcționează pentru un `Dataset`, așadar trebuie să eliminăm sau să modificămm coloanele din datasetul vechi pentru a se potrivi dimensiunea cu cea din noul dataset. Putem face ultima din cele două opțiuni folosind argumentul `remove_columns`: + +```py +tokenized_dataset = drug_dataset.map( + tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names +) +``` + +Acum acest lucru funcționează fără erori. Putem verifica că noul dataset are mai multe elemente decât datasetul original prin compararea lungimilor: + +```py +len(tokenized_dataset["train"]), len(drug_dataset["train"]) +``` + +```python out +(206772, 138514) +``` + +Am menționat că putem rezolva problema lungimilor diferite are coloanelor prin schimbarea vechilor coloane la aceeași dimensiune cu cele noi. Pentru aceasta, vom avea nevoie de câmpul `overflow_to_sample_mapping` returnat de tokenizer atunci când setăm `return_overflowing_tokens=True`. El ne oferă un mapping de la un nou feature index la indicele sampleului din care a provenit. Prin intermediul acesta, putem asocia fiecărei key prezentă în datasetul original cu o listă de valori de dimensiune corectă prin repetarea valorilor fiecărui exemplu atâta timp cât produce caracteristici noi: + +```py +def tokenize_and_split(examples): + result = tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) + # Extragem maparea între noul și vechiul indice + sample_map = result.pop("overflow_to_sample_mapping") + for key, values in examples.items(): + result[key] = [values[i] for i in sample_map] + return result +``` + +Putem observa că funcționează cu `Dataset.map()` fără ca noi să avem nevoie să eliminăm coloanele vechi: + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +tokenized_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 206772 + }) + test: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 68876 + }) +}) +``` + +Obținem același număr de features training ca și înainte, dar acum am păstrat toate câmpurile vechi. Dacă ai nevoie de ele pentru post-procesare după aplicarea modelului, ar fi util să folosiți această abordare. + +Acum ați învățat cum pot fi utilizate 🤗 Datasets pentru preprocesarea datelor prin metode diferite. Deși funcțiile de preprocesare ale 🤗 Datasets vor acoperi majoritatea nevoilor de antrenare a modelului, +există momente în care veți avea nevoie să treceți la Pandas pentru accesul la caracteristici mai puternice, cum ar fi `DataFrame.groupby()` sau high-level APIs pentru vizualizare. Din fericire, 🤗 Dataset a fost proiectat astfel încât să fie interoperabil cu biblioteci precum Pandas, NumPy, PyTorch, TensorFlow și JAX. Hai să vedem cum se face acest lucru. + +## De la `Dataset`s la `DataFrame`s și înapoi[[from-datasets-to-dataframes-and-back]] + + + +Pentru a permite conversia între diferite biblioteci, 🤗 Datasets oferă o funcțiune `Dataset.set_format()`. Această funcție schimbă doar _output format_ al datasetului, deci puteți ușor să treceți la un alt format fără a afecta _data format_ de bază, care este Apache Arrow. Formatarea se face direct. Pentru a demonstra acest lucru, hai să convertim datasetul nostru în Pandas: + +```py +drug_dataset.set_format("pandas") +``` + +Acum, atunci când accesăm elementele din dataset, obținem `pandas.DataFrame` în loc de un dicționar: + +```py +drug_dataset["train"][:3] +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
patient_iddrugNameconditionreviewratingdateusefulCountreview_length
095260Guanfacineadhd"My son is halfway through his fourth week of Intuniv..."8.0April 27, 2010192141
192703Lybrelbirth control"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."5.0December 14, 200917134
2138000Ortho Evrabirth control"This is my first time using any form of birth control..."8.0November 3, 20151089
+ +În continuare, vom crea un `pandas.DataFrame` pentru întregul set de antrenare prin selectarea tuturor elementelor din `drug_dataset["train"]`: + +```py +train_df = drug_dataset["train"][:] +``` + + + +🚨 În spatele scenei, `Dataset.set_format()` schimbă formatul returnat pentru `__getitem__()` dunder method a datasetului. Asta înseamnă că atunci când dorim să creăm un nou obiect ca `train_df` dintr-un `Dataset` în formatul `"pandas"`, trebuie să tăiem întreg datasetul pentru a obține un `pandas.DataFrame`. Puteți verifica voi înşivă că tipul lui `drug_dataset["train"]` este `Dataset`, indiferent de output format. + + + +Acum putem utiliza toate funcționalitățile Pandas pe care le dorim. De exemplu, putem face fancy chaining pentru a calcula distribuția clasei printre intrările `condition`: + +```py +frequencies = ( + train_df["condition"] + .value_counts() + .to_frame() + .reset_index() + .rename(columns={"index": "condition", "condition": "frequency"}) +) +frequencies.head() +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
conditionfrequency
0birth control27655
1depression8023
2acne5209
3anxiety4991
4pain4744
+ + +Și odată ce suntem gata cu analiza noastră Pandas, putem crea întotdeauna un nou obiect `Dataset` prin utilizarea funcției `Dataset.from_pandas()`: + + +```py +from datasets import Dataset + +freq_dataset = Dataset.from_pandas(frequencies) +freq_dataset +``` + +```python out +Dataset({ + features: ['condition', 'frequency'], + num_rows: 819 +}) +``` + + + + +✏️ **Încercați!** Calculați media ratingului per medicament și salvați rezultatul într-un nou `Dataset`. + + + + +Acest lucru completează turul nostru de tehnici de preprocesare disponibile în 🤗 Datasets. Pentru a finisa secțiunea, vom crea un set de validare pentru a pregăti datasetul pentru antrenarea unui clasificator. Înainte de a face asta, noi vom reseta output formatul `drug_dataset` de la `"pandas"` la `"arrow" : + + +```python +drug_dataset.reset_format() +``` + +## Crearea unui set de validare [[creating-a-validation-set]] + +Deși avem deja un set de testare pe care îl putem folosi pentru evaluare, este o bună practică să lăsăm setul de test neschimbat și să creăm un set de validare în timpul developmentului. Odată ce sunteți fericiți cu performanța modelului pe setul de validare, puteți face o verificare finală a setului de test. Acest proces ajută la reducerea riscului ca să vă adaptați prea mult setul de test și să depuneți un model care poate eșua analizând date reale. + +🤗 Datasets oferă o funcție `Dataset.train_test_split()` bazată pe funcționalitatea celebră din `scikit-learn`. O vom folosi pentru a împărți setul nostru de antrenare în split-uri de `train` și `validation` (setăm argumentul `seed` pentru reproductabilitate): + +```python +drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) +# Renumește implicit "test" split-ul la "valide" +drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") +# Adaugă setul de test în `DatasetDict` +drug_dataset_clean["test"] = drug_dataset["test"] +drug_dataset_clean +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 46108 + }) +}) +``` + +Foarte bine, am pregătit acum un dataset care este gata pentru antrenarea unor modele! În [Secțiunea 5](/course/chapter5/5) vom arăta cum puteți încărca seturile de date în Hugging Face Hub, dar în acest moment hai să punem capăt analizei noastre prin examinarea a câteva modalități de salvare a seturilor de date pe dispozitivele locale. + +## Salvarea unui dataset[[saving-a-dataset]] + + + +Deși 🤗 Datasets va face cache fiecărui dataset și a operațiilor efectuate asupra acestuia, există momente în care veți dori să salvați un dataset pe disc (de exemplu, în cazul în care cache-ul se șterge). După cum vedeți în tabelul de mai jos, 🤗 Datasets oferă trei funcții principale pentru salvarea datelor în formate diferite: + +| Format de date | Funcție | +| :---------: | :--------------------: | +| Arrow | `Dataset.save_to_disk()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | + +Spre exemplu, hai să salvăm datasetul nostru curățat în formatul Arrow: + +```python +drug_dataset_clean.save_to_disk("drug-reviews") +``` + +Acest lucru va crea un folder cu următoarea structură: + +``` +drug-reviews/ +├── dataset_dict.json +├── test +│ ├── dataset.arrow +│ ├── dataset_info.json +│ └── state.json +├── train +│ ├── dataset.arrow +│ ├── dataset_info.json +│ ├── indices.arrow +│ └── state.json +└── validation + ├── dataset.arrow + ├── dataset_info.json + ├── indices.arrow + └── state.json +``` + +unde se poate vedea că fiecare split este asociat cu propriul său tabel `dataset.arrow`, iar unele metadate în `dataset_info.json` și `state.json`. Poți să te gândești la formatul Arrow ca fiind un tabel facny de coloane și rânduri, optimizat pentru construirea aplicațiilor high-performance care procesează și transportă dataseturi mari. + +Odată ce setul de date este salvat, putem încărca-o folosind funcția `load_from_disk()` următoarea: + +```py +from datasets import load_from_disk + +drug_dataset_reloaded = load_from_disk("drug-reviews") +drug_dataset_reloaded +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 46108 + }) +}) +``` + +Pentru formatele CSV și JSON, trebuie să stocați fiecare split într-un fișier separat. Un mod de a face acest lucru estw iterarea asupra cheilor și valorilor obiectului `DatasetDict`: + +```py +for split, dataset in drug_dataset_clean.items(): + dataset.to_json(f"drug-reviews-{split}.jsonl") +``` + +Acesta salvează fiecare split în [JSON Lines format](https://jsonlines.org), unde fiecare rând din setul de date este stocat ca o singură linie JSON. Aici puteți vedea cum arată primul exemplu: + +```bash +!head -n 1 drug-reviews-train.jsonl +``` + +```python out +{"patient_id":141780,"drugName":"Escitalopram","condition":"depression","review":"\"I seemed to experience the regular side effects of LEXAPRO, insomnia, low sex drive, sleepiness during the day. I am taking it at night because my doctor said if it made me tired to take it at night. I assumed it would and started out taking it at night. Strange dreams, some pleasant. I was diagnosed with fibromyalgia. Seems to be helping with the pain. Have had anxiety and depression in my family, and have tried quite a few other medications that haven't worked. Only have been on it for two weeks but feel more positive in my mind, want to accomplish more in my life. Hopefully the side effects will dwindle away, worth it to stick with it from hearing others responses. Great medication.\"","rating":9.0,"date":"May 29, 2011","usefulCount":10,"review_length":125} +``` + +Putem apoi folosi tehnicile de la [Secțiunea 2](/course/chapter5/2) pentru încărcarea fișierelor JSON: + +```py +data_files = { + "train": "drug-reviews-train.jsonl", + "validation": "drug-reviews-validation.jsonl", + "test": "drug-reviews-test.jsonl", +} +drug_dataset_reloaded = load_dataset("json", data_files=data_files) +``` + +Și ăsta este finalul excursiei noastre în lumea manipulării datelor cu 🤗 Datasets! Acum că avem un dataset curat, pregătit pentru antrenarea unui model, aici sunt câteva idei pe care le puteți încerca: + +1. Folosiți tehnicile din [Capitolul 3](/course/chapter3) pentru a antrena un classifier care poate prezice starea pacientului pe baza recenziei medicamentului. +2. Folosiți pipelineul `summarization` din [Capitolul 1](/course/chapter1) pentru a genera rezumate ale recenziilor. + +În următoarea secțiune, vom vedea cum 🤗 Datasets vă permite să lucrați cu dataseturi mari fără ca laptopul tău să explodeze :)! \ No newline at end of file diff --git a/chapters/ro/chapter5/4.mdx b/chapters/ro/chapter5/4.mdx new file mode 100644 index 000000000..a1261153b --- /dev/null +++ b/chapters/ro/chapter5/4.mdx @@ -0,0 +1,287 @@ +# Big data?🤗 Datasets îți vin în ajutor![[big-data-datasets-to-the-rescue]] + + + +În prezent, nu este de neașteptat să te confrunți cu dataseturi de câțiva gigabytes, mai ales dacă planifici să preantreenezi un transformer ca BERT sau GPT-2 de la zero. În aceste cazuri, chiar _loading_ a datelor poate fi o provocare. De exemplu, corpusul WebText folosit pentru a preantreena GPT-2 conține peste 8 milioane de documente și 40 GB de text – încărcarea acestuia în memoria RAM a laptopului tău este probabil să-i facă un atac cardiac! + +Norocul este că Datasets 🤗 a fost proiectat pentru a depăși aceste limitări. El te eliberează de problemele de gestionare a memoriei, tratarea dataseturilor ca fișiere _memory-mapped_, și limitele hard driveului prin _streamingul_ intrărilor dintr-un corpus. + + + +În această secțiune vom explora aceste caracteristici ale 🤗Datasets cu un corpus de 825 GB numit [Pile](https://pile.eleuther.ai). Să începem! + +## Ce este Pile?[[what-is-the-pile]] + +Pile este un corpus de text englezesc creat de [EleutherAI](https://www.eleuther.ai) pentru antrenarea large-scale language models. Acesta include o varietate diversă de dataseturi, cuprinzând articole științifice, repositoriuri GitHub și texte web filtrate. Corpusul de antrenare este disponibil în chunkuri de [14 GB](https://the-eye.eu/public/AI/pile/), dar în același timp puteți descărca și câteva dintre [componenetele +individuale](https://the-eye.eu/public/AI/pile_preliminary_components/). Să începem prin examinarea datasetului PubMed Abstracts, care este un corpus de rezumate din 15 milioane de publicații științifice biomedicale de pe [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Datasetul este în format JSON și este comprimat cu librăria `zstandard`, așadar prima dată ne trebuie să o instalăm pe aceasta: + +```py +!pip install zstandard +``` + +În continuare, putem încărca datasetul utilizând metoda pentru fișierele remote pe care am învățat-o în [secțiunea 2](/course/chapter5/2): + +```py +from datasets import load_dataset + +# Acest lucru durează câteva minute, așadar poți să te duci să îți iei un ceai sau o cafea între timp :)) +data_files = "https://the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +pubmed_dataset = load_dataset("json", data_files=data_files, split="train") +pubmed_dataset +``` + +```python out +Dataset({ + features: ['meta', 'text'], + num_rows: 15518009 +}) +``` + +Putem observa că există 15.518.009 de linii și două colonne în datasetul nostru – e foarte mult! + + + +✎ De abia acum, 🤗 Datasets va descompresa fișierele necesare pentru încărcarea datasetului. Dacă doriți vreei să salvezi spațiu pe hard driveul tău, puteți transmite `DownloadConfig(delete_extracted=True)` la argumentul `download_config` al `load_dataset()`. Vedeți mai multe detalii în [documentație](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig). + + + +Acum hai să analizăm conținutul primei linii: + +```py +pubmed_dataset[0] +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection.\nTo determine the prevalence of hypoxaemia in children aged under 5 years suffering acute lower respiratory infections (ALRI), the risk factors for hypoxaemia in children under 5 years of age with ALRI, and the association of hypoxaemia with an increased risk of dying in children of the same age ...'} +``` + +Okay, acesta pare a fi un rezumat dintr-un articol medical. Să vedem cât de mult spațiu RAM am folosit pentru încărcarea datasetului! + +## Magia memory mappingului[[the-magic-of-memory-mapping]] + +Una dintre modalitățile simple de măsurare a utilizării memoriei în Python este cu biblioteca `psutil`, care poate fi instalată cu `pip`: + +```python +!pip install psutil +``` + +Biblioteca oferă o clasă `Process` ce ne permite să verificăm utilizarea memoriei procesului curent astfel: + +```py +import psutil + +# Process.memory_info este exprimat în bytes, așadar convertim la megabyte +print(f"Utilizare RAM: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +``` + +```python out +Utilizarea RAM: 5678.33 MB +``` + +Aici `rss` se referă la _resident set size_ , care este partea memoriei ocupată de proces în RAM. Această măsurare include și memoria folosită de Python interpreter și librariile pe care le-am încărcat, așadar cantitatea reală de memorie utilizată pentru încărcarea datelor este puțin mai mică. Pentru comparație, putem să vedem cât de mare este datasetul pe disc folosind atributul `dataset_size`. Deoarece rezultatul este exprimat în bytes, putem să îl convertim manual la gigabyte: + +```py +print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") +size_gb = pubmed_dataset.dataset_size / (1024**3) +print(f"Dataset size (cache file) : {size_gb:.2f} GB") +``` + +```python out +Number of files in dataset : 20979437051 +Dataset size (cache file) : 19.54 GB +``` + + +Nice – deși este aproximativ 20 GB, putem încărca și accesa datasetul cu mult mai puțin RAM! + + + +✏️ **Încercați!** Alegeți una dintre [subseturile](https://the-eye.eu/public/AI/pile_preliminary_components/) din Pile care este mai mare decât memoria RAM a laptopului sau dispozitivului tău, încărcați-o cu 🤗 Datasets și măsurați cantitatea de memorie folosită. Pentru o măsurare precisă, veți dori să faceți acest lucru într-un proces nou. Puteți găsi dimensiunile decomprimate ale fiecărui subset în Tabelul 1 din [Pile paper](https://arxiv.org/abs/2101.00027). + + + +Dacă sunteți familiarizați cu Pandas, rezultatul acesta poate veni ca o surpriză din cauza celebrei [rule of thumbă](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) al lui Wes Kinney, care spune că în mod normal aveți nevoie de 5 până la 10 ori mai mult spațiu pe RAM decât mărimea datasetului. Deci 🤗 Datasets această problemă de memory management? 🤗 Datasets tratează fiecare dataset ca un [memory-mapped file](https://en.wikipedia.org/wiki/Memory-mapped_file), care oferă un mapping între spațiul RAM și stocarea pe sistem, ceea ce permite bibliotecii să acceseze și să opereze asupra elementelor datasetului fără a trebui să-l încarce în totalitate în memorie. + +Memory-mapped files pot fi și distribuite între mai multe procese, ceea ce permite metodelor cum ar fi `Dataset.map()` să fie parallelized fără necesitatea mutării sau copierii datasetului. În spatele acestor facilități se află [formatul de memorie Apache Arrow](https://arrow.apache.org) și biblioteca [`pyarrow`](https://arrow.apache.org/docs/python/index.html), care realizează încărcarea datelor și procesarea la viteze fulgerătoare. (Pentru mai multe detalii despre Apache Arrow și compararea sa cu Pandas, vă rugăm să citiți [blogul lui Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) Pentru a vedea acest lucru în acțiune, hai să încercăm un speed test prin iterarea asupra tuturor elementelor din datasetul PubMed Abstracts: + +```py +import timeit + +code_snippet = """batch_size = 1000 + +for idx in range(0, len(pubmed_dataset), batch_size): + _ = pubmed_dataset[idx:idx + batch_size] +""" + +time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) +print( + f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " + f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" +) +``` + +```python out +'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' +``` + +Aici am folosit modulul `timeit` al Python pentru a măsura timpul de execuție necesar pentru a rula `code_snippet`. În mod normal veți putea trece peste un dataset la viteze de câteva sute de MB/s până la câțiva GB/s. Acest lucru funcționează bine pentru majoritatea aplicațiilor, dar uneori veți avea nevoie să lucrați cu un dataset care este prea mare ca să încapă pe hard driveul laptopului tău. De exemplu, dacă am încerca să descarcăm Pile în întregime, am avea nevoie de 825 GB de spațiu liber! Pentru a vă ajuta cu astfel de cazuri, 🤗 Datasets oferă o feature de streaming care permite accesarea și descărcarea elementelor, fără a trebui să descărcați întregul dataset. Hai să vedem cum funcționează! + + + +💡În Jupyter notebooks poți să măsori timpul unei celule utilizând [funcția magică `%%timeit`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit). + + + +## Streamingul dataseturilor[[streaming-datasets]] + +Pentru a activa dataset streaming este suficient să dați argumentul `streaming=True` funcției `load_dataset()`. De exemplu, să încercăm să încărcăm datasetul PubMed Abstracts în streaming mode: + +```py +pubmed_dataset_streamed = load_dataset( + "json", data_files=data_files, split="train", streaming=True +) +``` + +În locul `Dataset`-ului obișnuit pe care l-am întâlnit până acum în acest capitol, obiectul returnat cu `streaming=True` este un `IterableDataset`. După nume putem deduce că pentru a accesa elementele dintr-un `IterableDataset`, trebuie să iterăm prin el. Prin urmare, putem accesa primul element al streamed datased astfel: + +```py +next(iter(pubmed_dataset_streamed)) +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection.\nTo determine the prevalence of hypoxaemia in children aged under 5 years suffering acute lower respiratory infections (ALRI), the risk factors for hypoxaemia in children under 5 years of age with ALRI, and the association of hypoxaemia with an increased risk of dying in children of the same age ...'} +``` + +Elementele unui streamed dataset pot fi procesate din mers folosind `IterableDataset.map()`, ceea ce este util în timpul antrenării dacă aveți nevoie să tokenizeți inputurile. Procesarea se face exact la fel ca și în [Capitolul 3](/course/chapter3), cu singura deosebire fiind că rezultatele sunt returnate una câte una: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") +tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"])) +next(iter(tokenized_dataset)) +``` + +```python out +{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]} +``` + + + +💡 Pentru a accelera tokenizarea cu streaming puteți seta `batched=True`, ca și în secțiunea precedentă. Acest lucru va procesa exemplele, batch cu batch; dimensiunea implicită a batchului este de 1,000 și poate fi specificată cu argumentul `batch_size`. + + + +De asemenea, puteți amesteca un streamed dataset utilizând `IterableDataset.shuffle()`, dar față de `Dataset.shuffle()` acest lucru va amesteca doar elementele dintr-un `buffer_size` predefinit: + +```py +shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) +next(iter(shuffled_dataset)) +``` + +```python out +{'meta': {'pmid': 11410799, 'language': 'eng'}, + 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'} +``` + +În acest exemplu, am selectat un exemplu aleatoriu din primele 10,000 exemple din buffer. Odată ce un exemplu este accesat, locul lui în buffer este completat cu următorul exemplu din corpus (adica exemplarul 10,001 în cazul de mai sus). Puteți selecta elemente dintr-un streamed dataset utilizând funcțiile `IterableDataset.take()` și `IterableDataset.skip()`, care acționează în mod similar cu `Dataset.select()`. De exemplu, pentru a selecta primele 5 exemple din PubMed Abstracts dataset putem face următorul lucru: + +```py +dataset_head = pubmed_dataset_streamed.take(5) +list(dataset_head) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'pmid': 11409575, 'language': 'eng'}, + 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'}, + {'meta': {'pmid': 11409576, 'language': 'eng'}, + 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."}, + {'meta': {'pmid': 11409577, 'language': 'eng'}, + 'text': 'Oxygen concentrators and cylinders ...'}, + {'meta': {'pmid': 11409578, 'language': 'eng'}, + 'text': 'Oxygen supply in rural africa: a personal experience ...'}] +``` + +În mod similar puteți utiliza funcția `IterableDataset.skip()` pentru a crea splituri de antrenare și validare dintr-un set de date amestecat astfel: + +```py +# Săriți primele 1,000 exemple și includeți restul în setul de antrenare +train_dataset = shuffled_dataset.skip(1000) +# Luați primele 1,000 de exemple pentru setul de validare +validation_dataset = shuffled_dataset.take(1000) +``` + +Hai să terminăm explorarea streamingului asupra datasetului cu o aplicație comună: combinarea multiplelor dataseturi împreună pentru a crea un singur corpus. 🤗 Datasets oferă o funcție `interleave_datasets()` care convertește o listă de obiecte `IterableDataset` într-un singur `IterableDataset`, unde elementele noului datasetsunt obținute prin alternarea între exemplele sursă. Această funcție este utilă, în special atunci când încercați să combinați dataseturi mari, ca exemplu vom face stream al subsetul FreeLaw din Pile, care reprezintă un dataset de 51 GB de opinii juridice din instanțe din SUA: + +```py +law_dataset_streamed = load_dataset( + "json", + data_files="https://the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + split="train", + streaming=True, +) +next(iter(law_dataset_streamed)) +``` + +```python out +{'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} +``` + +Această dataset este suficient de mare încât să pună presiune asupra RAM-ului a majorității laptopurilor, dar am reușit să îl încarcăm și să îl accesăm fără să ne facem griji. Acum hai să combinăm exemplele din dataseturile FreeLaw și PubMed Abstracts cu funcția `interleave_datasets()`: + +```py +from itertools import islice +from datasets import interleave_datasets + +combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed]) +list(islice(combined_dataset, 2)) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] +``` + +Aici am folosit funcția `islice()` din modulul Python `itertools` pentru a selecta primele două exemple din datasetul combinat, și putem vedea că acestea corespund primelor exemple din fiecare dintre cele două dataseturi originale. + +În final, dacă doriți să faceți streaming la Pile în întregime (825 GB), puteți rula toate fișierele pregătite după în acest mod: + +```py +base_url = "https://the-eye.eu/public/AI/pile/" +data_files = { + "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], + "validation": base_url + "val.jsonl.zst", + "test": base_url + "test.jsonl.zst", +} +pile_dataset = load_dataset("json", data_files=data_files, streaming=True) +next(iter(pile_dataset["train"])) +``` + +```python out +{'meta': {'pile_set_name': 'Pile-CC'}, + 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'} +``` + + + +✏️ **Încercați!** Utilizați unul dintre cele mari corpusuri Common Crawl ca [`mc4`](https://huggingface.co/datasets/mc4) sau [`oscar`](https://huggingface.co/datasets/oscar) pentru a crea un streaming dataset multilingv care reprezintă proporția limbii vorbite într-o țară aleasă de tine. De exemplu, cele patru limbi naționale din Elveția sunt germana, franceza, italiana și romansha, așadar puteți încerca să creați un corpus elvețian prin samplingul subseturilor Oscar în funcție de proporția lor vorbită. + + + +Acum aveți toate instrumentele necesare pentru a încărca și procesa dataseturi de orice formă și dimensiune – dar, din păcate, va veni un moment în care veți trebui să creați voi înșivă un dataset pentru a rezolva problema pe care o aveți. Acesta este subiectul următoarei secțiuni! diff --git a/chapters/ro/chapter5/5.mdx b/chapters/ro/chapter5/5.mdx new file mode 100644 index 000000000..b200af7d8 --- /dev/null +++ b/chapters/ro/chapter5/5.mdx @@ -0,0 +1,404 @@ +# Crearea propriului dataset[[creating-your-own-dataset]] + + + +Uneori, datasetul necesar pentru a construi o aplicație NLP nu există, astfel încât veți trebui să-l creați singuri. În această secțiune vom arăta cum să creați un corpus de [GitHub issues](https://github.com/features/issues/), care sunt utilizate în mod obișnuit pentru a urmări erorile sau feature-urile din repositoriurile GitHub. Acest corpus poate fi folosit pentru diverse scopuri, inclusiv: + +* Explorarea timpului necesar pentru închiderea unor issues deschise sau pull requesturi +* Antrenarea unui _multilabel classifier_ care poate eticheta issue-urile cu metadate pe baza descrierii issue-urilor (de exemplu, "bug", "enhancement" sau "question") +* Crearea unui motor de căutare semantică pentru a găsi care issues se potrivesc query-ului utilizatorului + +În această secțiune ne vom focusa pe crearea corpusului, și în următoarea vom aborda aplicația motorului de căutare semantic. Pentru a păstra lucrurile meta, vom folosi issue-urile GitHub asociate cu un proiect open source popular: 🤗 Datasets! Să vedem cum să obținem datele și să explorăm informațiile conținute în aceste issue-uri. + +## Obținerea datelor[[getting-the-data]] + +Puteți găsi toate issue-urile din 🤗 Datasets navigând către tabul [Issues](https://github.com/huggingface/datasets/issues) al repositorului. Așa cum arată următorul screenshot, la momentul scrierii acestui text existau 331 de issues deschise și 668 închise. + +
+Issue-urile GitHub asociate cu 🤗 Datasets. +
+ +Dacă ați da clic pe una dintre aceste issue-uri veți găsi că aceasta conține un titlu, o descriere și un set de labeluri care caracterizează issue-ul. Un exemplu este prezentat în screenshotul următor. + +
+Un issue tipic în GitHub din repositoriul 🤗 Datasets. +
+ +Pentru a descărca toate issue-urile din repositoriu, vom folosi [GitHub REST API](https://docs.github.com/en/rest) pentru a enumera [`Issues` endpoint](https://docs.github.com/en/rest/reference/issues#list-repository-issues). Aceast endpoint returnează o listă de obiecte JSON, cu fiecare obiect conținând un număr mare de câmpuri care includ titlul și descrierea precum și metadata despre starea issue-ului și așa mai departe. + +Un mod convenabil de descărcare a issue-urilor este prin utilizarea librăriei `requests`, care este modalitatea standard pentru a face cereri HTTP în Python. Puteți instala libraria rulând comanda: + +```python +!pip install requests +``` + +Odată cu instalarea librariei, puteți face cereri GET la `Issues` endpoint prin invocarea funcției `requests.get()`. De exemplu, puteți rula următorul cod pentru a obține primul issue din prima pagină: + +```py +import requests + +url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1" +response = requests.get(url) +``` + +Obiectul `response` conține o cantitate mare de informații utile despre requestul efectuat, inclusiv HTTP status code: + +```py +response.status_code +``` + +```python out +200 +``` + +unde statusul `200` înseamnă că cererea a fost reușită (puteți găsi o listă completă de status coduri [aici](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)). De ceea ce suntem însă interesați este _payload_, care poate fi accesat în diverse formaturi precum bytes, string sau JSON. Deoarece știm că issue-urile noastre sunt în format JSON, să inspectăm payload-ul astfel: + +```py +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'repository_url': 'https://api.github.com/repos/huggingface/datasets', + 'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}', + 'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments', + 'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'id': 968650274, + 'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0', + 'number': 2792, + 'title': 'Update GooAQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'labels': [], + 'state': 'open', + 'locked': False, + 'assignee': None, + 'assignees': [], + 'milestone': None, + 'comments': 1, + 'created_at': '2021-08-12T11:40:18Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'closed_at': None, + 'author_association': 'CONTRIBUTOR', + 'active_lock_reason': None, + 'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff', + 'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'}, + 'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.', + 'performed_via_github_app': None}] +``` + +Uau, aceasta e o cantitate mare de informație! Putem vedea câmpuri utile cum ar fi `title`, `body` și `number` care descriu problema, precum și informații despre utilizatorul GitHub care a deschis issue-ul. + + + +✏️ **Încercați!** Faceți clic pe câteva dintre URL-urile din payload-ul JSON de mai sus pentru a vă familiariza cu tipul de informații către care se face referire pentru fiecare GitHub issue. + + + +După cum este descris în [documentația](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) GitHub, solicitările neautentificate sunt limitate la 60 de solicitări pe oră. Deși puteți crește `per_page` query parameter pentru a reduce numărul de solicitări pe care le faceți, oricum veți atinge limita pentru orice repository care are mai mult de câteva mii de issues. Prin urmare, ar trebui să urmați [instrucțiunile](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) GitHub pentru crearea unui _personal access token_ astfel încât să puteți crește limita la 5.000 de solicitări pe oră. Odată ce aveți tokenul, îl puteți include ca parte a request header: + +```py +GITHUB_TOKEN = xxx # Copy your GitHub token here +headers = {"Authorization": f"token {GITHUB_TOKEN}"} +``` + + + +⚠️ Nu oferiți nimănui un notebook cu `GITHUB_TOKEN` în el . Vă recomandăm să ștergeți ultima celulă odată ce ați executat-o pentru a evita scurgerea accidentală a acestor informații. Chiar mai bine, stocați tokenul într-un fișier *.env* și utilizați biblioteca `python-dotenv` pentru a îl încărca automat ca variabilă de mediu. + + + +Acum că avem tokenul de acces, hai să creăm o funcție care să poată descărca toate issue-urile dintr-un repositoriu GitHub: + +```py +import time +import math +from pathlib import Path +import pandas as pd +from tqdm.notebook import tqdm + + +def fetch_issues( + owner="huggingface", + repo="datasets", + num_issues=10_000, + rate_limit=5_000, + issues_path=Path("."), +): + if not issues_path.is_dir(): + issues_path.mkdir(exist_ok=True) + + batch = [] + all_issues = [] + per_page = 100 # Number of issues to return per page + num_pages = math.ceil(num_issues / per_page) + base_url = "https://api.github.com/repos" + + for page in tqdm(range(num_pages)): + # Query with state=all to get both open and closed issues + query = f"issues?page={page}&per_page={per_page}&state=all" + issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) + batch.extend(issues.json()) + + if len(batch) > rate_limit and len(all_issues) < num_issues: + all_issues.extend(batch) + batch = [] # Flush batch for next time period + print(f"Reached GitHub rate limit. Sleeping for one hour ...") + time.sleep(60 * 60 + 1) + + all_issues.extend(batch) + df = pd.DataFrame.from_records(all_issues) + df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True) + print( + f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl" + ) +``` + +Acum când apelăm `fetch_issues()` va descărca toate problemele în batch-uri pentru a evita depășirea limitei GitHub pe numărul de solicitări pe oră; rezultatul va fi stocat într-un fișier `_repository_name-issues.jsonl`, unde fiecare linie este un obiect JSON care reprezintă un issue. Mai jos folosim această funcție pentru a obține toate issue-urile de la 🤗 Datasets: + +```py +# În dependență de conexiunea ta la internet, acest lucru poate dura câteva minute... +fetch_issues() +``` + +Odată ce issue-urile sunt descărcate, le putem încărca local utilizând abilitățile noastre dobândite în [secțiunea 2](/course/chapter5/2): + +```py +issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'], + num_rows: 3019 +}) +``` + +Great, am creat primul nostru dataset de la zero! Dar de ce sunt mai mult de câteva mii de issue-uri atunci când tabul de issue-uri al repositoriului 🤗 Datasets afișează doar aproximativ 1.000 de issue-uri în total 🤔? Conform descris în [documentația](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user) GitHub, acest lucru s-a întâmplat pentru că am descărcat și toate pull requesturile: + +> GitHub's REST API v3 considers every pull request an issue, but not every issue is a pull request. For this reason, "Issues" endpoints may return both issues and pull requests in the response. You can identify pull requests by the `pull_request` key. Be aware that the `id` of a pull request returned from "Issues" endpoints will be an issue id. + +Deoarece conținutul issue-urilor și pull requesturilor este destul de diferit, hai să preprocesăm puțin datele pentru a ne permite să le diferențiem între ele. + +## Curățarea datelor[[cleaning-up-the-data]] + +Fragmentul de mai sus din documentația GitHub ne spune că coloana `pull_request` poate fi utilizată pentru a diferenția între issues și pull requests. Să analizăm un sampple aleatoriu pentru a vedea care este diferența. Așa cum am făcut în [secțiunea 3](/course/chapter5/3), vom înlănțui `Dataset.shuffle()` și `Dataset.select()` pentru a crea un sample aleatoriu și apoi vom împerechea coloanele `html_url` și `pull_request` pentru a putea compara diversele URL-uri: + +```py +sample = issues_dataset.shuffle(seed=666).select(range(3)) + +# Print out the URL and pull request entries +for url, pr in zip(sample["html_url"], sample["pull_request"]): + print(f">> URL: {url}") + print(f">> Pull request: {pr}\n") +``` + +```python out +>> URL: https://github.com/huggingface/datasets/pull/850 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'} + +>> URL: https://github.com/huggingface/datasets/issues/2773 +>> Pull request: None + +>> URL: https://github.com/huggingface/datasets/pull/783 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} +``` + +Aici putem vedea că fiecare pull request este asociat cu diverse URL-uri, în timp ce issue-urile obișnuite au o intrare `None`. Putem utiliza această distincție pentru a crea o nouă coloană `is_pull_request` care verifică dacă câmpul `pull_request` este `None` sau nu: + +```py +issues_dataset = issues_dataset.map( + lambda x: {"is_pull_request": False if x["pull_request"] is None else True} +) +``` + + + +✏️ **Încercați!** Calculați timpul mediu necesar pentru închiderea issue-urilor în Datasets. Vă poate fi utilă funcția `Dataset.filter()` pentru a filtra pull requesturile și issue-urile deschise, și puteți utiliza funcția `Dataset.set_format()` pentru a converti datasetul într-un `DataFrame` astfel încât să puteți manipula cu ușurință timestampurile `created_at` și `closed_at`. Pentru puncte bonus, calculați timpul mediu necesar pentru închiderea pull requesturilor. + + + +Deși am putea continua să curățăm datasetul prin eliminarea sau redenumirea unor coloane, este, în general, o practică bună să păstrăm datasetul cât mai "raw" posibil la acest stadiu, astfel încât să poată fi utilizat ușor în multiple aplicații. + +Înainte de a încărca datasetul în Hugging Face Hub, trebuie să rezolvăm chestie care lipsește din el: comentariile asociate fiecărui issue și pull request. Le vom adăuga în continuare cu-- ați ghicit -- GitHub REST API! + +## Îmbunătățirea datasetului[[augmenting-the-dataset]] + +După cum se vede în următorul screenshot, comentariile asociate unui issue sau pull request oferă o sursă bogată de informații, în special dacă suntem interesați să construim un motor de căutare pentru a răspunde la întrebările utilizatorilor despre bibliotecă. + +
+Comentariile asociate unei probleme despre 🤗 Datasets. +
+ +GitHub REST API oferă un endpoint [`Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) care returnează toate comentariile asociate numărului problemei. Să testăm endpointul pentru a vedea ce returnează: + +```py +issue_number = 2792 +url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" +response = requests.get(url, headers=headers) +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128', + 'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'id': 897594128, + 'node_id': 'IC_kwDODunzps41gDMQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'created_at': '2021-08-12T12:21:52Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'author_association': 'CONTRIBUTOR', + 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", + 'performed_via_github_app': None}] +``` + +Putem vedea că comentariul este stocat în câmpul `body`, așa că putem scrie o funcție simplă care returnează toate comentariile asociate unei probleme prin extragerea conținutului `body` pentru fiecare element în `response.json()`: + +```py +def get_comments(issue_number): + url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" + response = requests.get(url, headers=headers) + return [r["body"] for r in response.json()] + + +# Testăm dacă funcția lucrează cum ne dorim +get_comments(2792) +``` + +```python out +["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] +``` + +Arată bine. Acum hai să folosim `Dataset.map()` pentru a adăuga noi coloane `comments` fiecărui issue în datasetul nostru: + +```py +# Depending on your internet connection, this can take a few minutes... +issues_with_comments_dataset = issues_dataset.map( + lambda x: {"comments": get_comments(x["number"])} +) +``` + +Ultimul pas este să facem push datasetului nostru pe Hub. Să vedem cum putem face asta. + +## Încărcarea datasetului pe Hugging Face Hub[[uploading-the-dataset-to-the-hugging-face-hub]] + + + +💡 De asemenea, puteți încărca un dataset pe Hugging Face Hub direct din terminal utilizând `huggingface-cli` și puțină magie Git. Consultați [ghidul 🤗 Datasets](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) pentru detalii despre cum puteți face asta. + + + +## Crearea unei dataset card[[creating-a-dataset-card]] + +Datasetiroșe bine documentate sunt mai probabil să fie utile altora (inclusiv ție din viitor!), deoarece furnizează contextul pentru a permite utilizatorilor să decidă dacă datasetul este relevant pentru taskul lor și să evalueze eventualele biasuri sau riscurile asociate cu utilizarea datasetului. + +Pe Hugging Face Hub, această informație este stocată în fișierul *README.md* al fiecărui dataset repository. Sunt doi pași principali pe care trebuie să îi efectuați înainte de a crea acest fișier: + +1. Utilizați aplicația [`datasets-tagging`](https://huggingface.co/datasets/tagging/) pentru a crea etichete de metadate în format YAML. Aceste taguri sunt utilizate pentru o varietate de funcționalități de căutare pe Hugging Face Hub și asigură că datasetul poate fi găsit ușor de membrii comunității. Deoarece am creat un dataset custom aici, veți fi nevoiți să clonați repositoriul `datasets-tagging` și să rulați aplicația local. Iată cum arată interfața: + +
+Interfața `datasets-tagging`. +
+ +2. Citiți [ghidul 🤗 Datasets](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) despre crearea de dataset cards informative și utilizați-l ca șablon. + +Puteți crea fișierul *README.md* direct pe Hub și puteți găsi un template pentru dataset card în repositoriul `lewtun/github-issues`. Un screenshot a dataset card completată este afișată mai jos. + +
+Dataset card. +
+ + + +✏️ **Încercați!** Utilizați aplicația `dataset-tagging` și [ghidul 🤗 Datasets](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) pentru a completa fișierul *README.md* pentru datasetul de probleme GitHub. + + + +Astfel, am văzut în această secțiune că crearea unui dataset bun poate fi destul de complicată, dar, spre norocul nsotru, încărcarea și oferirea acestuia comunității nu sunt. În secțiunea următoare, vom utiliza datasetul nou pentru a crea un motor de căutare semantic cu 🤗 Datasets care poate să asocieze întrebări cu cele mai relevante issues și comentarii. + + + +✏️ **Încercați!** Treceți prin pașii pe care i-am făcut în această secțiune pentru a crea un dataset de issues GitHub pentru o biblioteca open source care îți place(alegeți altceva înafară de 🤗 Datasets, desigur!). Pentru puncte bonus, faceți fine-tune unui multilabel classifier pentru a prezice tagurile prezente în câmpul `labels`. + + diff --git a/chapters/ro/chapter5/6.mdx b/chapters/ro/chapter5/6.mdx new file mode 100644 index 000000000..7587c5ef5 --- /dev/null +++ b/chapters/ro/chapter5/6.mdx @@ -0,0 +1,517 @@ + + +# Căutare semantică cu FAISS[[semantic-search-with-faiss]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +În [secțiunea 5](/course/chapter5/5), am creat un dataset cu issues și comentarii din repositoriul 🤗 Datasets. În această secțiune, vom utiliza aceste informații pentru a construi un motor de căutare care ne poate ajuta să găsim răspunsurile la cele mai importante cele mai importante întrebări despre bibliotecă! + + + +## Utilizarea embeddings pentru căutare semantică[[using-embeddings-for-semantic-search]] + +După cum am văzut în [Capitolul 1](/course/chapter1), Transformer-based language models reprezintă fiecare token într-un fragment de text ca un _embedding vector_. S-a dovedit că se poate face "pool" embeddingurilor individuale pentru a crea o reprezentare vectorială pentru fraze întregi, paragrafe sau (în anumite cazuri) documente. Aceste embeddings pot fi apoi utilizate pentru a găsi documente similare în corpus prin calcularea dot-product similarity (sau a unei alte metrice de similaritate) între fiecare embedding și returnarea documentelor cu cea mai mare suprapunere. + +În această secțiune, vom utiliza embeddings pentru a dezvolta un motor de căutare semantică. Aceste motoare de căutare oferă mai multe avantaje față de abordările convenționale bazate pe căutarea de cuvinte cheie într-un query cu documente. + +
+Căutare semantică. + +
+ +## Încărcarea și pregătirea datasetului[[loading-and-preparing-the-dataset]] + +Prima lucru pe care trebuie să îl facem este să descărcăm datasetul nostru cu GitHub issues, așa că folosim funcția `load_dataset()` ca de obicei: + +```py +from datasets import load_dataset + +issues_dataset = load_dataset("lewtun/github-issues", split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Aici am specificat splitul default `train` în `load_dataset()`, astfel încât returnează un `Dataset` în loc de `DatasetDict`. Primul lucru care treubuie făcut este să filtrăm pull requesturile, deoarece acestea rareori tind să fie utilizate pentru a răspunde la întrebările utilizatorilor și vor introduce noise în motorul nostru de căutare. Așa cum ar trebuie deja să știți, putem utiliza funcția `Dataset.filter()` pentru a exclude aceste rânduri din datasetul nostru. În timp ce suntem aici, putem să filtrăm și rândurile fără comentari, deoarece acestea nu oferă niciun răspuns la întrebările utilizatorilor: + +```py +issues_dataset = issues_dataset.filter( + lambda x: (x["is_pull_request"] == False și len(x["comments"]) > 0) +) +issues_dataset +``` + +```python out +Dataset({ + caracteristici: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 771 +}) +``` + +Putem vedea că există multe coloane în datasetul nostru, majoritatea dintre care nu sunt necesare pentru a construi motorul nostru de căutare. Din perspectiva căutării, cele mai informative coloane sunt `title`, `body` și `comments`, în timp ce `html_url` ne oferă un link înapoi la problema sursă. Hai să utilizăm funcția `Dataset.remove_columns()` pentru a elimina restul: + +```py +columns = issues_dataset.column_names +columns_to_keep = ["title", "body", "html_url", "comments"] +columns_to_remove = set(columns_to_keep).symmetric_difference(columns) +issues_dataset = issues_dataset.remove_columns(columns_to_remove) +issues_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 771 +}) +``` + +Pentru a crea embeddedurile noastre, vom completa fiecare comentariu cu titlul și body-ul problemei, deoarece aceste câmpuri adesea includ informații contextuale utile. Deoarece coloana noastră `comments` este în prezent o listă de comentarii pentru fiecare issue, trebuie să "explodăm" coloana, astfel încât fiecare rând să fie format dintr-un tuple `(html_url, title, body, comment)`. În Pandas, putem face acest lucru cu funcția [`DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), care creează un rând nou pentru fiecare element dintr-o coloană asemănătoare cu o listă, în timp ce copiază toate celelalte valori ale coloanelor. Pentru a vedea acest lucru în acțiune, să trecem la formatul pandas `DataFrame` main întâi: + +```py +issues_dataset.set_format("pandas") +df = issues_dataset[:] +``` + +Dacă inspectăm primul rând din acest `DataFrame`, putem vedea că există patru comentarii asociate acestei probleme: + +```py +df["comments"][0].tolist() +``` + +```python out +['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', + 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', + 'cannot connect,even by Web browser,please check that there is some problems。', + 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] +``` + +Când facem explode `df`, ne așteptăm să obținem un rând pentru fiecare dintre aceste comentarii. Haideți să verificăm dacă ăsta e cazul: + +```py +comments_df = df.explode("comments", ignore_index=True) +comments_df.head(4) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
html_urltitlecommentsbody
0https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comthe bug code locate in :\r\n if data_args.task_name is not None...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
1https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comHi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
2https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comcannot connect,even by Web browser,please check that there is some problems。Hello,\r\nI am trying to run run_glue.py and it gives me this error...
3https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comI can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
+ +Great, putem vedea că rândurile au fost reproduse, cu coloanele `comments` incluzând și comentariile individuale. Acum că am terminat cu Pandas, noi putem să schimăm rapid înapoi la un `Dataset` înărcând `DataFrame` în memorie: + +```py +from datasets import Dataset + +comments_dataset = Dataset.from_pandas(comments_df) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 2842 +}) +``` + +Okay, acest lucru ne-a oferit câteva mii de comentarii cu care să lucrăm! + + + + +✏️ **Încercați!** Vezi dacă poți utiliza `Dataset.map()` pentru a exploda coloana `comments` din `issues_dataset` _fără_ a recurge la utilizarea Pandas. Acest lucru este puțin dificil; s-ar putea să găsiți utilă secțiunea ["Mapping batch"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) din documentația 🤗 Datasets pentru această sarcină. + + + +Acum că avem un singur comentariu pe rând, să creăm o nouă coloană `comments_length` care conține numărul de cuvinte din fiecare comentariu: + +```py +comments_dataset = comments_dataset.map( + lambda x: {"comment_length": len(x["comments"].split())} +) +``` + +Putem utiliza această nouă coloană pentru a filtra comentariile scurte, care de obicei includ lucruri precum "cc @lewtun" sau "Mulțumesc!" care nu sunt relevante pentru motorul nostru de căutare. Nu există un număr precis care trebuie selectat pentru filtru, dar aproximativ 15 cuvinte pare a fi un bun punct de plecare: + +```py +comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body', 'comment_length'], + num_rows: 2098 +}) +``` + +După ce am curățat puțin setul nostru de date, putem să concatenăm titlul, descrirea și comentariile problemei împreună într-o nouă coloană `text`. Ca de obicei, vom scrie o funcție simplă pe care o putem transmite în `Dataset.map()`: + +```py +def concatenate_text(examples): + return { + "text": examples["title"] + + " \n " + + examples["body"] + + " \n " + + examples["comments"] + } + + +comments_dataset = comments_dataset.map(concatenate_text) +``` + +Suntem în final pregătiți să creăm niște embeddings! Haideți să vedem cum facem acest lucru. + +## Crearea embeddings-urilor de text [[creating-text-embeddings]] + +Am văzut în [Capitolul 2](/course/chapter2) că putem obține token embeddings prin utilizarea clasei `AutoModel`. Tot ce trebuie să facem este să alegem un checkpoint potrivit pentru a încărca modelul. Din fericire, există o bibliotecă numită `sentence-transformers` care se ocupă de crearea embeddingurilor. Așa cum se descrie în [documentația](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search) bibliotecii, cazul nostru este un exemplu de _căutare semantică asimetrică_ deoarece avem o întrebare scurtă al cărei răspuns ne-ar plăcea să îl găsim într-un document mai lung, precum un comentariu la un issue. [Tabelul] (https://www.sbert.net/docs/pretrained_models.html#model-overview) util de prezentare a modelului din documentație indică faptul că checkpointul `multi-qa-mpnet-base-dot-v1` are cea mai bună performanță pentru căutarea semantică, așa că îl vom folosi acesta pentru aplicația noastră. De asemenea, vom încărca tokenizer-ul folosind același checkpoint: + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = AutoModel.from_pretrained(model_ckpt) +``` + +Pentru a accelera procesul de embedding, este util să punem modelul și inputurile pe un GPU, deci hai să facem asta acum: + +```py +import torch + +device = torch.device("cuda") +model.to(device) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) +``` + +Notăm că am setat `from_pt=True` ca argument al metodei `from_pretrained()`. Acest lucru se datorează faptului că checkpoint-ul `multi-qa-mpnet-base-dot-v1` are doar PyTorch weights, astfel încât setarea `from_pt=True` le va converti automat în format TensorFlow pentru noi. După cum puteți vedea, este foarte simplu să treci de la un framework la altul în 🤗 Transformers! + +{/if} + +După cum am menționat anterior, dorim să reprezentăm fiecare intrare din corpusul nostru de GitHub issues sub forma unui vector, astfel încât avem nevoie să "agregăm" sau să facem media la tokem embeddings într-un anumit mod. O abordare populară este de a efectua *CLS pooling* pe outputurile modelului nostru, unde pur și simplu colectăm ultimul stadiu ascuns pentru tokenul special `[CLS]`. Următoarea funcție face acest lucru pentru noi: + +```py +def cls_pooling(model_output): + return model_output.last_hidden_state[:, 0] +``` + +În continuare, vom crea o funcție ajutătoare care va tokeniza o listă de documente, va plasa tensorii pe GPU, îi va alimenta în model și, în final, va aplica CLS pooling la outputuri: + +{#if fw === 'pt'} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="pt" + ) + encoded_input = {k: v.to(device) for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Putem testa funcția oferindui prima intrare de text în corpusul nostru și analizând output shapeul: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +Minunat, am transformat prima intrare din corpusul nostru într-un vector de 768 de dimensiuni! Putem utiliza `Dataset.map()` pentru a aplica funcția noastră `get_embeddings()` la fiecare rând din corpusul nostru, astfel încât să creăm o nouă coloană `embeddings` în acest mod: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]} +) +``` + +{:else} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="tf" + ) + encoded_input = {k: v for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Putem testa funcția oferindui prima intrare de text în corpusul nostru și analizând output shapeul: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +TensorShape([1, 768]) +``` + +Minunat, am transformat prima intrare din corpusul nostru într-un vector de 768 de dimensiuni! Putem utiliza `Dataset.map()` pentru a aplica funcția noastră `get_embeddings()` la fiecare rând din corpusul nostru, astfel încât să creăm o nouă coloană `embeddings` în acest mod: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} +) +``` + +{/if} + +Observăm că am transformat embeddingurile în matrice NumPy -- acest lucru este necesar deoarece biblioteca 🤗 Datasets cere acest format atunci când încercăm să indexăm cu FAISS, ceea ce vom face în continuare. + +## Utilizarea FAISS pentru căutare de similaritate eficientă[[using-faiss-for-efficient-similarity-search]] + +Acum că avem un dataset de embeddings, avem nevoie de o modalitate de a căuta printre ele. Pentru a face acest lucru, vom utiliza o structură de date specială din 🤗 Datasets, numită _FAISS index_. [FAISS](https://faiss.ai/) (prescurtat de la Facebook AI Similarity Search) este o bibliotecă care oferă algoritmi eficienți pentru căutarea rapidă și clusteringul al embedding vectors. + +Ideea de bază din spatele FAISS este crearea unei structuri de date speciale numite _index_, care permite găsirea embeddingurilor similare cu un input embedding. Crearea unui index FAISS în 🤗 Datasets este simplu -- utilizăm funcția `Dataset.add_faiss_index()` și specificăm care coloană a datasetului nostru dorim să indexăm: + +```py +embeddings_dataset.add_faiss_index(column="embeddings") +``` + +Acum putem efectua queries pe acest index realizând o căutare a celor mai apropiați vecini cu funcția `Dataset.get_nearest_examples()`. Haideți să testăm acest lucru prin încorporarea unei întrebări astfel: + +{#if fw === 'pt'} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).cpu().detach().numpy() +question_embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +{:else} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).numpy() +question_embedding.shape +``` + +```python out +(1, 768) +``` + +{/if} + +La fel ca și în cazul documentelor, acum avem un vector de 768 de dimensiuni care reprezintă query-ul, pe care îl putem compara cu întregul corpus pentru a găsi embeddingurile cele mai similare: + +```py +scores, samples = embeddings_dataset.get_nearest_examples( + "embeddings", question_embedding, k=5 +) +``` + +Funcția `Dataset.get_nearest_examples()` returnează un tuple cu scorurile care clasifică suprapunerea dintre query și document, și un set corespunzător de sampleuri (în acest caz, cele 5 match-uri). Haideți să colectăm acestea într-un `pandas.DataFrame` pentru a le putea sorta cu ușurință: + +```py +import pandas as pd + +samples_df = pd.DataFrame.from_dict(samples) +samples_df["scores"] = scores +samples_df.sort_values("scores", ascending=False, inplace=True) +``` + +Acum putem itera peste primele rânduri pentru a veadea cât de bine un query se potrivește cu comentariile disponibile: + +```py +for _, row in samples_df.iterrows(): + print(f"COMMENT: {row.comments}") + print(f"SCORE: {row.scores}") + print(f"TITLE: {row.title}") + print(f"URL: {row.html_url}") + print("=" * 50) + print() +``` + +```python out +""" +COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine. + +@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? +SCORE: 25.505046844482422 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :) +You can now use them offline +\`\`\`python +datasets = load_dataset("text", data_files=data_files) +\`\`\` + +We'll do a new release soon +SCORE: 24.555509567260742 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet. + +Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :) + +I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature. + +---------- + +> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? + +Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones. +For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do +\`\`\`python +load_dataset("./my_dataset") +\`\`\` +and the dataset script will generate your dataset once and for all. + +---------- + +About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded. +cf #1724 +SCORE: 24.14896583557129 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine +> +> 1. (online machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_dataset(...) +> +> data.save_to_disk(/YOUR/DATASET/DIR) +> +> ``` +> +> 2. copy the dir from online to the offline machine +> +> 3. (offline machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_from_disk(/SAVED/DATA/DIR) +> +> ``` +> +> +> +> HTH. + + +SCORE: 22.893993377685547 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: here is my way to load a dataset offline, but it **requires** an online machine +1. (online machine) +\`\`\` +import datasets +data = datasets.load_dataset(...) +data.save_to_disk(/YOUR/DATASET/DIR) +\`\`\` +2. copy the dir from online to the offline machine +3. (offline machine) +\`\`\` +import datasets +data = datasets.load_from_disk(/SAVED/DATA/DIR) +\`\`\` + +HTH. +SCORE: 22.406635284423828 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== +""" +``` + +Nu-i rău! A doua încercare se pare că se potrivește cu query-ul! + + + +✏️ **Încearcă!** Creează propriul tău query și vezi dacp poți găsi un răspuns și să extragi documentele. S-ar putea să trebuiești să crești parametrul `k` în `Dataset.get_nearest_examples()` pentru a mări căutarea. + + \ No newline at end of file diff --git a/chapters/ro/chapter5/7.mdx b/chapters/ro/chapter5/7.mdx new file mode 100644 index 000000000..ca66adcd9 --- /dev/null +++ b/chapters/ro/chapter5/7.mdx @@ -0,0 +1,16 @@ +# 🤗 Datasets, check![[datasets-check]] + + + +Ei bine, a fost un tur palpitant prin biblioteca 🤗 Datasets -- felicitări pentru că ai ajuns până aici! Cu cunoștințele pe care le-ai dobândit din acest capitol, ar trebui să fii capabil să: + +- Încarci dataseturi de oriunde, fie Hugging Face Hub, laptopul tău sau un server remote de la compania ta. +- Modelezi datele tale folosind o combinație a funcțiilor `Dataset.map()` și `Dataset.filter()`. +- Schimbi rapid între data formats precum Pandas și NumPy folosind `Dataset.set_format()`. +- Creezi propriul tău dataset și să îl publici pe Hugging Face Hub. +- Încorporezi documentele tale folosind un model Transformer și construiești un motor de căutare semantică folosind FAISS. + +În [Capitolul 7](/course/chapter7), vom pune toate acestea în practică, făcând o examinare amănunțită a principalelor sarcini NLP pentru care modelele Transformer sunt excelente. Înainte de a trece mai departe, puneți-vă cunoștințele despre 🤗 Datasets la încercare cu un quiz rapid! \ No newline at end of file diff --git a/chapters/ro/chapter5/8.mdx b/chapters/ro/chapter5/8.mdx new file mode 100644 index 000000000..6b1b08936 --- /dev/null +++ b/chapters/ro/chapter5/8.mdx @@ -0,0 +1,228 @@ +# Testul de sfârșit de capitol[[end-of-chapter-quiz]] + + + +Acest capitol a acoperit o mulțime de subiecte! Nu vă faceți griji dacă nu ați înțeles toate detaliile; capitolele următoare vă vor ajuta să înțelegeți cum funcționează lucrurile mai aprofundat. + +Înainte de a trece mai departe, totuși trebuie să testăm ce ați învățat în acest capitol. + +### 1. Funcția `load_dataset()` din 🤗 Datasets vă permite să încărcați un dataset din care dintre următoarele locații? + +load_dataset('emotion').", + correct: true + }, + { + text: "Un server remote", + explain: "Corect! Puteți trece URL-uri ca argument al `data_files` al `load_dataset()` pentru a încărca fișiere remote.", + correct: true + }, + ]} +/> + +### 2. Presupunem că încărcați una dintre sarcinile GLUE astfel: + +```py +from datasets import load_dataset + +dataset = load_dataset("glue", "mrpc", split="train") +``` + +Care dintre următoarele comenzi va produce un exemplu aleatoriu de 50 de elemente din `dataset`? + +dataset.sample(50)", + explain: "Acest lucru este incorect -- nu există o metodă `Dataset.sample()`." + }, + { + text: "dataset.shuffle().select(range(50))", + explain: "Corect! Așa cum ați văzut în acest capitol, mai întâi faceți shuffle datasetului și apoi selectați exemplele din el.", + correct: true + }, + { + text: "dataset.select(range(50)).shuffle()", + explain: "Acest lucru este incorect -- deși codul va rula, va amesteca doar primele 50 de elemente din setul de date." + } + ]} +/> + +### 3. Presupunem că aveți un set de date despre animale de companie numit `pets_dataset`, care are o coloană `name` care denotă numele fiecărui animal de companie. Care dintre următoarele abordări v-ar permite să filtrați setul de date pentru toate animalele de companie ale căror nume încep cu litera "L"? + +pets_dataset.filter(lambda x : x['name'].startswith('L'))", + explain: "Corect! Utilizarea unei funcții lambda python pentru aceste filtre rapide este o idee grozavă. Vă puteți gândi și la o altă soluție?", + correct: true + }, + { + text: "pets_dataset.filter(lambda x['name'].startswith('L'))", + explain: "Acest lucru este incorect -- o funcție lambda are forma generală lambda *arguments* : *expression*, deci trebuie să furnizați argumente în acest caz." + }, + { + text: "Creați o funcție ca def filter_names(x): return x['name'].startswith('L') și rulați pets_dataset.filter(filter_names).", + explain: "Corect! La fel ca și cu Dataset.map(), puteți trece funcții explicite la Dataset.filter(). Acest lucru este util atunci când aveți o logică complexă care nu este potrivită pentru o funcție lambda. Care dintre celelalte soluții ar mai funcționa?", + correct: true + } + ]} +/> + +### 4. Ce este memory mapping? + + + +### 5. Care sunt principalele beneficii ale memory-mapping? + + + +### 6. De ce codul următor eșuează? + +```py +from datasets import load_dataset + +dataset = load_dataset("allocine", streaming=True, split="train") +dataset[0] +``` + +IterableDataset.", + explain: "Corect! Un IterableDataset este un generator, nu un container, deci ar trebui să accesați elementele sale utilizând next(iter(dataset)).", + correct: true + }, + { + text: "Datasetul allocine nu are o un split train.", + explain: "Acest lucru este incorect -- consultați cardul datasetului allocine de pe Hub pentru a vedea ce splituri conține." + } + ]} +/> + +### 7. Care sunt principalele beneficii ale creării unui dataset card? + + + +### 8. Ce este căutarea semantică? + + + +### 9. Pentru căutarea semantică asimetrică, de obicei aveți: + + + +### 10. Pot utiliza 🤗 Datasets pentru a încărca date pentru utilizare în alte domenii, cum ar fi speech processing? + +MNIST de pe Hub pentru a vedea un exemplu de computer vision." + }, + { + text: "Da", + explain: "Corect! Consultați dezvoltările interesante cu privire la speech și vision în biblioteca 🤗 Transformers pentru a vedea cum 🤗 Datasets este utilizat în aceste domenii.", + correct: true + }, + ]} +/> \ No newline at end of file From bfe0bafa2f22e816b7b28f0474d8462984212bd5 Mon Sep 17 00:00:00 2001 From: Angroys <120798951+Angroys@users.noreply.github.com> Date: Wed, 8 Jan 2025 02:53:50 +0200 Subject: [PATCH 06/37] Done chapter 6 --- chapters/ro/chapter 6/1.mdx | 19 ++ chapters/ro/chapter 6/10.mdx | 283 +++++++++++++++ chapters/ro/chapter 6/2.mdx | 257 ++++++++++++++ chapters/ro/chapter 6/3.mdx | 473 ++++++++++++++++++++++++++ chapters/ro/chapter 6/3b.mdx | 643 +++++++++++++++++++++++++++++++++++ chapters/ro/chapter 6/4.mdx | 122 +++++++ chapters/ro/chapter 6/5.mdx | 360 ++++++++++++++++++++ chapters/ro/chapter 6/6.mdx | 375 ++++++++++++++++++++ chapters/ro/chapter 6/7.mdx | 381 +++++++++++++++++++++ chapters/ro/chapter 6/8.mdx | 566 ++++++++++++++++++++++++++++++ chapters/ro/chapter 6/9.mdx | 16 + 11 files changed, 3495 insertions(+) create mode 100644 chapters/ro/chapter 6/1.mdx create mode 100644 chapters/ro/chapter 6/10.mdx create mode 100644 chapters/ro/chapter 6/2.mdx create mode 100644 chapters/ro/chapter 6/3.mdx create mode 100644 chapters/ro/chapter 6/3b.mdx create mode 100644 chapters/ro/chapter 6/4.mdx create mode 100644 chapters/ro/chapter 6/5.mdx create mode 100644 chapters/ro/chapter 6/6.mdx create mode 100644 chapters/ro/chapter 6/7.mdx create mode 100644 chapters/ro/chapter 6/8.mdx create mode 100644 chapters/ro/chapter 6/9.mdx diff --git a/chapters/ro/chapter 6/1.mdx b/chapters/ro/chapter 6/1.mdx new file mode 100644 index 000000000..9cfd151ed --- /dev/null +++ b/chapters/ro/chapter 6/1.mdx @@ -0,0 +1,19 @@ +# Introducere[[introducere]] + + + +În [Capitolul 3](/course/chapter3), am examinat cum să facem fine-tune unui model pentru o anumită sarcină. Când facem acest lucru, utilizăm același tokenizer cu care modelul a fost antrenat - dar ce facem când dorim să antrenăm un model de la zero? În așa cazuri, utilizarea unui tokenizer care a fost antrenat pe un corpus dintr-un alt domeniu sau limbă este, de obicei, suboptimal. De exemplu, un tokenizer antrenat pe un corpus în limba engleză va funcționa rău pe un corpus de texte în limba japoneză, deoarece utilizarea spațiilor și a punctuației este foarte diferită în cele două limbi. + +În acest capitol, veți învăța cum să antrenați un tokenizer complet nou pe un corpus de texte, astfel încât să poată fi utilizat pentru a antrena un model de limbaj. Acest lucru va fi realizat cu ajutorul bibliotecii [🤗 Tokenizers](https://github.com/huggingface/tokenizers), care oferă tokenizerii "rapizi" din biblioteca [🤗 Transformers](https://github.com/huggingface/transformers). Vom examina îndeaproape caracteristicile pe care această bibliotecă le oferă și vom explora cum tokenizatorii rapizi diferă de versiunile "lente". + +Subiectele pe care le vom acoperi includ: + +* Cum să antrenați un tokenizer nou similar celui utilizat de un anumit checkpoint pe un corpus nou de texte +* Caracteristicile speciale ale tokenizerilor rapizi +* Diferențele dintre cei trei algoritmi principali de subword tokenization utilizate în NLP în prezent +* Cum să construiți un tokenizer de la zero cu biblioteca 🤗 Tokenizers și să îl antrenați pe anumite date + +Tehnicile prezentate în acest capitol vă vor pregăti pentru secțiunea din [Capitolul 7](/course/chapter7/6), unde vom examina crearea unui model de limbaj pentru codul sursă Python. Să începem prin a explora ce înseamnă să "antrenați" un tokenizer în primul rând. diff --git a/chapters/ro/chapter 6/10.mdx b/chapters/ro/chapter 6/10.mdx new file mode 100644 index 000000000..f3fcf6dc7 --- /dev/null +++ b/chapters/ro/chapter 6/10.mdx @@ -0,0 +1,283 @@ + + +# Quiz de sfârșit de capitol[[end-of-chapter-quiz]] + + + +Hai să testăm ceea ce ai învățat în acest capitol! + +### 1. Când ar trebui să antrenezi un nou tokenizer? + + + +### 2. Care este avantajul utilizării unui generator de liste de texte în comparație cu o listă de liste de texte atunci când utilizați `train_new_from_iterator()`? + +train_new_from_iterator() îl acceptă.", + explain: "O listă de liste de texte este un tip special de generator de liste de texte, astfel încât metoda o va accepta și pe aceasta. Încercați din nou!" + }, + { + text: "Veți evita încărcarea întregului dataset în memorie.", + explain: "Corect! Fiecare batch de texte va fi eliberat din memorie atunci când iterați, iar câștigul va fi vizibil mai ales dacă utilizați 🤗 Datasets pentru a stoca textele.", + correct: true + }, + { + text: "Acest lucru va permite bibliotecii 🤗 Tokenizers să utilizeze multiprocessing.", + explain: "Nu, oricum va folosi multiprocessing." + }, + { + text: "Tokenizerul pe care îl vei antrena va genera texte mai bune.", + explain: "Tokenizerul nu generează text - îl confundați cu un model lingvistic?" + } + ]} +/> + +### 3. Care sunt avantajele utilizării unui tokenizer "rapid"? + + + +### 4. Cum tratează pipelineul `token-classification` entitățile care se întind pe mai mulți tokeni? + + + +### 5. Cum gestionează pipelineul `question-answering` contextele lungi? + + + +### 6. Ce este normalizarea? + + + +### 7. Ce este pre-tokenizarea pentru un subword tokenizer? + + + +### 8. Selectați propozițiile care se aplică modelului de tokenizare BPE. + + + +### 9. Selectați propozițiile care se aplică modelului de tokenizare WordPiece. + + + +### 10. Selectați propozițiile care se aplică modelului de tokenizare Unigram. + + diff --git a/chapters/ro/chapter 6/2.mdx b/chapters/ro/chapter 6/2.mdx new file mode 100644 index 000000000..ef36da15c --- /dev/null +++ b/chapters/ro/chapter 6/2.mdx @@ -0,0 +1,257 @@ +# Antrenarea unui nou tokenizer dintr-unul vechi[[training-a-new-tokenizer-from-an-old-one]] + + + +Dacă un model de limbaj nu este disponibil în limba dorită sau dacă corpusul tău este foarte diferit de cel pe care modelul de limbaj a fost antrenat, este probabil că veți dori să antrenați modelul de la zero, folosind un tokenizer adaptat datelor tale. Acest lucru va necesita antrenarea unui nou tokenizer pe datasetul tău. Dar ce înseamnă exact asta? Când am examinat pentru prima dată tokenizatorii în [Capitolul 2](/course/chapter2), am văzut că majoritatea modelelor Transformer folosesc un _algoritm de subword tokenization_. Pentru a identifica care subcuvinte sunt de interes și apar cel mai frecvent în corpusul respectiv, tokenizerul trebuie să examineze cu atenție toate textele din corpus - un proces pe care îl numim *antrenare*. Regulile exacte care conduc această antrenare depind de tipul de tokenizer utilizat și vom prezenta cei trei algoritmi principali mai târziu în acest capitol. + + + + + +⚠️ Antrenarea unui tokenizer nu este același lucru ca antrenarea unui model! Antrenarea modelului folosește stochastic gradient descent pentru a face pierderea puțin mai mică pentru fiecare batch. Este randomizată prin natură (ceea ce înseamnă că trebuie să setați niște seeduri pentru a obține aceleași rezultate atunci când faceți aceeași antrenare de două ori). Antrenarea unui tokenizer este un proces statistic care încearcă să identifice care subcuvinte sunt cele mai bune pentru a fi selectate pentru un anumit corpus, și regulile exacte utilizate pentru a le selecta depind de algoritmul de tokenizare. Este determinist, ceea ce înseamnă că întotdeauna obțineți aceleași rezultate atunci când antrenați cu același algoritm pe același corpus. + + + +## Asamblarea unui corpus[[assembling-a-corpus]] + +Există o interfață API foarte simplă în 🤗 Transformers pe care o puteți utiliza pentru a antrena un nou tokenizer cu aceleași caracteristici ca unul existent: `AutoTokenizer.train_new_from_iterator()`. Pentru a vedea acest lucru în acțiune, să zicem că vrem să antrenăm GPT-2 de la zero, dar într-o altă limbă decât engleza. Prima noastră sarcină va fi să adunăm multe date în acea limbă într-un corpus de antrenare. Pentru a oferi exemple pe care toată lumea le poate înțelege, nu vom folosi o limbă ca rusă sau chineza aici, ci mai degrabă o limbă engleză specializată: codul Python. + +Biblioteca [🤗 Datasets](https://github.com/huggingface/datasets) ne poate ajuta să asamblăm un corpus de cod sursă Python. Vom folosi funcția obișnuită `load_dataset()` pentru a descărca și a păstra în cache dataseul [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Acest dataset a fost creat pentru [Provocarea CodeSearchNet](https://wandb.ai/github/CodeSearchNet/benchmark) și conține milioane de funcții din biblioteci open-source de pe GitHub în mai multe limbaje de programare. Aici, vom încărca partea Python a acestui dataset: + +```py +from datasets import load_dataset + +# Acest lucru poate dura câteva minute pentru a încărca, așa că luați o pauză și beți o ceașcă de cafea sau ceai în timp ce așteptați! +raw_datasets = load_dataset("code_search_net", "python") +``` + +Putem să ne uităm la splitul de antrenare pentru a vedea la care coloane avem acces: + +```py +raw_datasets["train"] +``` + +```python out +Dataset({ + features: ['repository_name', 'func_path_in_repository', 'func_name', 'whole_func_string', 'language', + 'func_code_string', 'func_code_tokens', 'func_documentation_string', 'func_documentation_tokens', 'split_name', + 'func_code_url' + ], + num_rows: 412178 +}) +``` + +Putem vedea că dataset-ul separă docstringurile de cod și sugerează o tokenizare a ambelor. Aici, vom folosi doar coloana `whole_func_string` pentru a antrena tokenizerul nostru. Putem să ne uităm la un exemplu al unei astfel de funcții prin indexarea în splitul de antrenare: + +```py +print(raw_datasets["train"][123456]["whole_func_string"]) +``` + +care ar trebui să printeze următorul lucru: + +```out +def handle_simple_responses( + self, timeout_ms=None, info_cb=DEFAULT_MESSAGE_CALLBACK): + """Accepts normal responses from the device. + + Args: + timeout_ms: Timeout in milliseconds to wait for each response. + info_cb: Optional callback for text sent from the bootloader. + + Returns: + OKAY packet's message. + """ + return self._accept_responses('OKAY', info_cb, timeout_ms=timeout_ms) +``` + +Primul lucru pe care trebuie să-l facem este să transformăm setul de date într-un _iterator_ de liste de texte - de exemplu, o listă de liste de texte. Utilizarea listelor de texte va permite tokenizerului nostru să funcționeze mai rapid (antrenându-se pe batch-uri de texte în loc de a procesa texte individuale unul câte unul), iar acesta ar trebui să fie un iterator dacă dorim să evităm să avem tot în memoria RAM deodată. Dacă corpusul tău este uriaș, veți dori să profitați de faptul că 🤗 Datasets nu încarcă totul în memoria RAM, ci stochează elementele datasetului pe disc. + +Următoarea operație ar crea o listă de liste de 1.000 de texte fiecare, dar ar încărca totul în memorie: + +```py +# Nu faceți uncomment următoarei linii dacă datasetul vostru este mare, ci doar dacă este mic! +# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)] +``` + +Utilizând un generator Python, putem evita ca Python să încarce orice în memorie până când este realmente necesar. Pentru a crea un astfel de generator, trebuie doar să înlocuiți parantezele pătrate cu paranteze rotunde: + +```py +training_corpus = ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) +) +``` + +Această linie de cod nu extrage niciun element al setului de date; doar creează un obiect pe care îl puteți utiliza într-un `for` loop din Python. Textele vor fi încărcate doar atunci când veți avea nevoie de ele(adică atunci când sunteți la pasul loopului `for` care le solicită), iar doar 1.000 de texte vor fi încărcate la un moment dat. Acest mod vă permite să nu epuizați memoria RAM chiar dacă prelucrați un set de date uriaș. + +Problema cu un obiect generator este că poate fi utilizat doar o dată, așadar, în loc să ne ofere lista primelor 10 cifre de două ori: + +```py +gen = (i for i in range(10)) +print(list(gen)) +print(list(gen)) +``` + +noi le primim o dată și apoi o listă goală: + +```python out +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[] +``` + +Din acest motive noi definim o funcție ce returnează în schimb un generator: + +```py +def get_training_corpus(): + return ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) + ) + + +training_corpus = get_training_corpus() +``` + +În același timp poți defini un generator înăuntrul unui `for` loop folosing statementul `yield`: + +```py +def get_training_corpus(): + dataset = raw_datasets["train"] + for start_idx in range(0, len(dataset), 1000): + samples = dataset[start_idx : start_idx + 1000] + yield samples["whole_func_string"] +``` + +ceea ce va produce același generator ca înainte, dar îți va permite să folosești o logică mai complexă decât cea pe care ai putea să o folosești într-un list comprehension. + +## Antrenarea unui nou tokenizer[[training-a-new-tokenizer]] + +Acum că avem corpusul nostru sub forma unui iterator de batch-uri de texte, suntem gata să antrenăm un nou tokenizer. Pentru a face acest lucru, trebuie mai întâi să încărcăm tokenizerul pe care dorim să-l asociem cu modelul nostru (aici, GPT-2): + +```py +from transformers import AutoTokenizer + +old_tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Chiar dacă urmează să antrenăm un nou tokenizer, este o idee bună să facem acest lucru pentru a evita să începem să facem tot de la zero. Astfel, nu vom fi nevoiți să specificăm nimic despre algoritmul de tokenizare sau despre special tokens pe care îi vom utiliza; noul nostru tokenizer va fi exact la fel ca GPT-2, iar singur lucrul care se va schimba este vocabularul, care va fi determinat de antrenarea pe corpusul nostru. + +Mai întâi, hai să vedem cum ar interpreta acest tokenizer un exemplu de funcție: + +```py +example = '''def add_numbers(a, b): + """Add the two numbers `a` and `b`.""" + return a + b''' + +tokens = old_tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'n', 'umbers', '(', 'a', ',', 'Ġb', '):', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', + 'Ġnumbers', 'Ġ`', 'a', '`', 'Ġand', 'Ġ`', 'b', '`', '."', '""', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +Acest tokenizer are câteva simboluri speciale, cum ar fi `Ġ` și `Ċ`, care denotă spații și noi linii, respectiv. Așa cum se poate vedea, acest lucru nu este prea eficient: tokenizerul returnează tokenuri individuale pentru fiecare spațiu, când ar putea grupa împreună nivelurile de indentare (deoarece având seturi de patru sau opt spații va fi foarte comun în cod). De asemenea, a divizat numele funcției într-un mod ciudat, nefiind obișnuit să vadă cuvinte care conțin caracterele `_`. + +Acum hai să antrenăm un nou tokenizer și să vedem dacă rezolvă aceste probleme. Pentru aceasta, vom utiliza metoda `train_new_from_iterator()`: + +```py +tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) +``` + +Această comandă ar putea dura puțin timp dacă corpusul este foarte mare, dar pentru acest dataset de 1,6 GB de texte este extrem de rapid (1 minut și 16 secunde pe un procesor AMD Ryzen 9 3900X cu 12 nuclee). + +Reține că `AutoTokenizer.train_new_from_iterator()` funcționează doar dacă tokenizerul pe care îl utilizați este un tokenizer "rapid". Așa cum veți vedea în următoarea secțiune, biblioteca 🤗 Transformers conține două tipuri de tokenizeri: unii sunt scriși în pur Python, iar alții (cei rapizi) sunt susținuți de biblioteca 🤗 Tokenizers, care este scrisă în limbajul de programare Rust. Python este limbajul cel mai frecvent utilizat pentru aplicații de data science și deep learning, dar atunci când orice trebuie să fie paralelizat pentru a fi rapid, trebuie să fie scris într-un alt limbaj de programare. De exemplu, multiplicările matricelor care sunt la baza calculelor modelului sunt scrise în CUDA, o bibliotecă C optimizată pentru GPU-uri. + +Antrenarea unui tokenizer nou în pur Python ar fi extrem de lent, de aceea am dezvoltat biblioteca 🤗 Tokenizers. Reține că, la fel cum nu a trebuit să învățați limbajul CUDA pentru a putea executa modelul pe un batch de inputuri pe un GPU, nu veți avea nevoie să învățați Rust pentru a utiliza un tokenizer rapid. Biblioteca 🤗 Tokenizers oferă legături Python pentru multe metode care apelează intern unele bucăți de cod în Rust; de exemplu, pentru a paraleliza antrenarea noului tokenizer sau, așa cum am văzut în [Capitolul 3](/course/chapter3), tokenizarea unui batch de inputuri. + +Majoritatea modelelor Transformer au un tokenizer rapid disponibil (există unele excepții pe care le puteți verifica [aici](https://huggingface.co/transformers/#supported-frameworks)), iar API-ul `AutoTokenizer` selectează întotdeauna tokenizerul rapid pentru tine dacă este disponibil. În următoarea secțiune, vom examina unele dintre celelalte caracteristici speciale ale tokenizerilor rapizi, care vor fi foarte utile pentru sarcini precum clasificarea tokenilor și răspunderea la întrebări. Înainte de a face acest lucru, totuși, să încercăm noul nostru tokenizer pe exemplul anterior: + +```py +tokens = tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`', + 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +Aici din nou vedem simboluri speciale ca `Ġ` sau `Ċ` care denotă spații sau linii noi, dar în același timp putem vedea că tokenizerul nostru a învățat câțiva tokens care sunt foarte specifici la corpusul de funcții Python: de exemplu, tokenul `ĊĠĠĠ` care reprezintă indentarea, sau tokenul `Ġ"""` care reprezintă cele trei ghilimele cu care se începe un docstring. Tokenizerul, de asemenea face split corect numelui funției pe `_`. Aceasta chiar este o reprezentare compactă: comparativ, utilizând limba tokenizerul Englez pe același exemplu ne va da o propoziție mai lungă: + +```py +print(len(tokens)) +print(len(old_tokenizer.tokenize(example))) +``` + +```python out +27 +36 +``` + +Hai să ne uităm la un alt exemplu: + +```python +example = """class LinearLayer(): + def __init__(self, input_size, output_size): + self.weight = torch.randn(input_size, output_size) + self.bias = torch.zeros(output_size) + + def __call__(self, x): + return x @ self.weights + self.bias + """ +tokenizer.tokenize(example) +``` + +```python out +['class', 'ĠLinear', 'Layer', '():', 'ĊĠĠĠ', 'Ġdef', 'Ġ__', 'init', '__(', 'self', ',', 'Ġinput', '_', 'size', ',', + 'Ġoutput', '_', 'size', '):', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'weight', 'Ġ=', 'Ġtorch', '.', 'randn', '(', 'input', '_', + 'size', ',', 'Ġoutput', '_', 'size', ')', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'bias', 'Ġ=', 'Ġtorch', '.', 'zeros', '(', + 'output', '_', 'size', ')', 'ĊĊĠĠĠ', 'Ġdef', 'Ġ__', 'call', '__(', 'self', ',', 'Ġx', '):', 'ĊĠĠĠĠĠĠĠ', + 'Ġreturn', 'Ġx', 'Ġ@', 'Ġself', '.', 'weights', 'Ġ+', 'Ġself', '.', 'bias', 'ĊĠĠĠĠ'] +``` + +În plus față de tokenul corespunzător unei indentări, aici putem vedea și un token pentru o indentare dublă: `ĊĠĠĠĠĠĠĠ`. Cuvintele speciale din Python, cum ar fi `class`, `init`, `call`, `self` și `return`, sunt tokenizate fiecare ca un singur token, și putem vedea că, pe lângă divizarea la `_` și `.`, tokenizerul divizează corect chiar și numele scrise în stil camel-case: `LinearLayer` este tokenizeat ca `["ĠLinear", "Layer"]`. + +## Salvarea tokenizerului[[saving-the-tokenizer]] + +Pentru a ne asigura că îl putem utiliza mai târziu, trebuie să salvăm noul nostru tokenizer. Asemănător salvării unui model, acest lucru se realizează cu metoda `save_pretrained()`: + +```py +tokenizer.save_pretrained("code-search-net-tokenizer") +``` + +Acest lucru va crea un nou folder numit *code-search-net-tokenizer*, care va conține toate fișierele necesare pentru a reîncărca tokenizerul. Dacă doriți să partajați acest tokenizer cu colegii și prietenii tăi, îl puteți încărca pe Hub prin conectarea la contul tău. Dacă lucrați într-un notebook, există un convenience function pentru a vă ajuta cu acest lucru: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Acest lucru va afișa un widget în care puteți introduce credențialele de conectare Hugging Face. Dacă nu lucrați într-un notebook, introduceți simplu următoarea linie în terminal: + +```bash +huggingface-cli login +``` + +După ce v-ați conectat, puteți face push tokenizerului prin executarea următoarei comenzi: + +```py +tokenizer.push_to_hub("code-search-net-tokenizer") +``` + +Acest lucru va crea un nou repositoriu în namespacel tău cu numele `code-search-net-tokenizer`, care va conține fișierul tokenizerului. După aceea, puteți încărca tokenizerul de oriunde cu metoda `from_pretrained()`: + +```py +# Înlocuiți "huggingface-course" mai jos cu namespaceul tău pentru a utiliza propriul tokenizer +tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") +``` + +Acum sunteți gata să antrenați un model de limbaj de la zero și să îl ajustați pentru sarcina voastră! Vom face acest lucru în [Capitolul 7](/course/chapter7), dar mai întâi, în continuarea acestui capitol, vom arunca o privire mai atentă asupra tokenizerilor rapizi și vom explora în detaliu ce se întâmplă atunci când apelați metoda `train_new_from_iterator()`. \ No newline at end of file diff --git a/chapters/ro/chapter 6/3.mdx b/chapters/ro/chapter 6/3.mdx new file mode 100644 index 000000000..54fa7863f --- /dev/null +++ b/chapters/ro/chapter 6/3.mdx @@ -0,0 +1,473 @@ + + +# Tokenizeri rapizi' puteri speciale[[fast-tokenizers-special-powers]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +În această secțiune, vom analiza mai atent capacitățile tokenizerilor din 🤗 Transformers. Până acum, i-am folosit doar pentru tokenizarea inputurilor sau decodificarea ID-urilor înapoi în text, dar tokenizerii - în special cei susținuți de biblioteca 🤗 Tokenizers - pot face mult mai multe. Pentru a ilustra aceste funcții suplimentare, vom explora modul de reproducere a rezultatelor pipeline-urilor `token-classification` (pe care le-am numit `ner`) și `question-answering` pe care le-am întâlnit pentru prima dată în [Capitolul 1](/course/chapter1). + + + +În discuția următoare, vom face adesea distincția între tokenizatori "lenți" și "rapizi". Tokenizerii lenți sunt cei scriși în Python în interiorul bibliotecii 🤗 Transformers, în timp ce versiunile rapide sunt cele furnizate de 🤗 Tokenizers, care sunt scrise în Rust. Dacă vă amintiți de tabelul din [Capitolul 5](/course/chapter5/3) care a raportat cât timp a durat un tokenizer rapid și unul lent pentru a tokeniza datasetul Drug Review, ar trebui să aveți o idee despre de ce îi numim lenți și rapizi: + +| | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10,8s | 4min41s +`batched=False` | 59,2s | 5min3s + + + +⚠️ Atunci când tokenizați o singură propoziție, nu veți vedea întotdeauna o diferență de viteză între versiunea lentă și rapidă ale aceluiași tokenizer. De fapt, versiunea rapidă poate fi chiar mai lentă! Abia atunci când tokenizați multe texte în paralel, în același timp, veți putea observa clar diferența. + + + +## Batch encoding[[batch-encoding]] + + + +Rezultatul unui tokenizer nu este un simplu dicționar Python; ceea ce obținem este de fapt un obiect special `BatchEncoding`. Este o subclasă a unui dicționar (de aceea am putut indexa acel rezultat fără nici o problemă mai devreme), dar cu metode suplimentare care sunt utilizate în principal de către tokenizerii rapizi. + +Pe lângă capacitățile lor de paralelizare, funcționalitatea principală a tokenizerilor rapizi este aceea că aceștia țin întotdeauna evidența intervalului original de texte din care provin tokenii finali - o funcționalitate pe care o numim *offset mapping*. Acest lucru deblochează funcții precum mappingul fiecărui cuvânt la tokens pe care îi generează sau mappingul fiecărui caracter al textului original la tokenul în care se află și invers. + +Acum hai să aruncăm o privire la un exemplu: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +encoding = tokenizer(example) +print(type(encoding)) +``` + +Așa cum s-a menționat anterior, în outputul tokenizerului obținem un obiect `BatchEncoding`: + +```python out + +``` + +Deoarece clasa `AutoTokenizer` selectează un tokenizer rapid în mod implicit, putem utiliza metodele suplimentare pe care acest obiect `BatchEncoding` le oferă. Avem două metode de verificare dacă tokenizerul nostru este unul lent sau rapid. Putem verifica fie atributul `is_fast` al tokenizer-ului: + +```python +tokenizer.is_fast +``` + +```python out +True +``` + +sau același atribut al `encoding`: + +```python +encoding.is_fast +``` + +```python out +True +``` + +Hai să vedem ce ne permite un tokenizer rapid să facem. În primul rând, putem accesa tokenul fără a fi nevoie să facem convert ID-urile înapoi în tokens: + +```py +encoding.tokens() +``` + +```python out +['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in', + 'Brooklyn', '.', '[SEP]'] +``` + +În acest caz tokenul la indexul 5 este `##yl`, ceea ce este o parte a cuvântului "Sylvain" în propoziția originală. În același timp putem folosi metoda `word_ids()` pentru a obține indexul cuvântului din care provine fiecare token: + +```py +encoding.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 3, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, None] + +Putem vedea că tokenizerul are tokeni speciali `[CLS]` și `[SEP]` care sunt mapped la `None`, iar apoi fiecare token este mapped la cuvântul din care provine. Acest lucru este deosebit de util pentru a determina dacă un token este la începutul unui cuvânt sau dacă două tokenuri sunt în același cuvânt. Ne-am putea baza pe prefixul `##` pentru aceasta, dar funcționează doar pentru tokenizeri de tip BERT; această metodă funcționează pentru orice tip de tokenizator, atâta timp cât este unul rapid. În capitolul următor, vom vedea cum putem utiliza această capabilitate pentru a aplica labeluri pe care le avem pentru fiecare cuvânt în mod corespunzător tokenurilor în sarcini precum named entity recognition (NER) și part-of-speech (POS). De asemenea, îl putem utiliza pentru a face mask tuturor tokenurilor care provin din același cuvânt în masked language modeling(o tehnică numită _whole word masking_). + + + +Noțiunea de ceea ce este un cuvânt este complicată. De exemplu, "I'll" (o prescurtare a "I will") contează ca unul sau două cuvinte? Acest lucru depinde de tokenizer și de operațiunea de pre-tokenizare pe care o aplică. Unii tokenizeri se divid doar pe spații, așa că vor considera acest lucru ca un singur cuvânt. Alții folosesc punctuația pe lângă spații, deci vor considera două cuvinte. + +✏️ **Încercați!** Creați un tokenizer din checkpointurile `bert-base-cased` și `roberta-base` și tokenizați "81s" cu ele. Ce observați? Care sunt ID-urile cuvintelor? + + + +În mod similar, există o metodă `sentence_ids()` pe care o putem utiliza pentru a face map unui token la propoziția din care provine (deși, în acest caz, `token_type_ids` returnate de tokenizer ne pot oferi aceeași informație). + +În cele din urmă, putem face map oricărui cuvânt sau token la caracterele din textul original și invers, prin intermediul metodelor `word_to_chars()` sau `token_to_chars()` și `char_to_word()` sau `char_to_token()`. De exemplu, metoda `word_ids()` ne-a spus că `##yl` face parte din cuvântul cu indicele 3, dar ce cuvânt este în propoziție? Putem afla astfel: + +```py +start, end = encoding.word_to_chars(3) +example[start:end] +``` + +```python out +Sylvain +``` + +Așa cum am menționat anterior, toate acestea sunt posibile datorită faptului că tokenizerul rapid ține evidența spanului de text de la care provine fiecare token într-o listă de *offseturi*. Pentru a ilustra modul în care se utilizează acestea, în continuare vă vom arăta cum să replicați rezultatele pipelineului `token-classification` manual. + + + +✏️ **Încercați!** Creați propriul exemplu de text și încercați să înțelegeți care tokenuri sunt asociate cu ID-ul cuvântului și, de asemenea, cum să extrageți spanurile pentru singur cuvânt. Pentru puncte bonus, încercați să utilizați două propoziții ca inputuri și să vedeți dacă ID-urile propozițiilor au sens pentru voi. + + + +## În interiorul pipelineului `token-classification`[[inside-the-token-classification-pipeline]] + +În [Capitolul 1](/course/chapter1) am avut primul nostru contact aplicând NER - unde sarcina constă în identificarea părților textului care corespund entităților precum persoane, locații sau organizații - cu funcția `pipeline()` din 🤗 Transformers. Apoi, în [Capitolul 2](/course/chapter2), am văzut cum un pipeline grupează cele trei etape necesare pentru a obține predicțiile de la un text "raw": tokenizare, trecerea inputurilor prin model și post-procesare. Primele două etape din pipelineul `token-classification` sunt la fel ca în orice alt pipeline, dar post-procesarea este puțin mai complexă - hai să vedem cum! + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +### Obținerea rezultatelor de bază cu pipelineul[[getting-the-base-results-with-the-pipeline]] + +În primul rând, trebuie să luăm un token classification pipeline pentru a obține câteva rezultate ca să le putem compara manual. Modelul utilizat în mod implicit este [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english); acesta aplică NER pe propoziții: + +```py +from transformers import pipeline + +token_classifier = pipeline("token-classification") +token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Modelul a identificat fiecare token generdat de "Sylvain" ca o persoană, fiecare token generat de "Hugging Face" ca o organizație, și fiecare token "Brooklin" ca o locație". În același timp putem întreba pipelineul să grupeze împreună tokenurile care corespund cu aceeași entitate. + +```py +from transformers import pipeline + +token_classifier = pipeline("token-classification", aggregation_strategy="simple") +token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +`Aggregation_strategy` aleasă va modifica scorurile calculate pentru fiecare entitate grupată. Cu `"simple"`, scorul este doar media scorurilor pentru fiecare token din entitatea dată: de exemplu, scorul pentru "Sylvain" este media scorurilor pe care le-am văzut în exemplele anterioare pentru token-urile `S`, `##yl`, `##va` și `##in`. Alte strategii disponibile sunt: + +- `"first"`, unde scorul fiecărei entități este scorul primului token al acelei entități (astfel, pentru "Sylvain" ar fi 0.993828, scorul token-ului `S`) +- `"max"`, unde scorul fiecărei entități este scorul maxim al tokenilor din acea entitate (astfel, pentru "Hugging Face" ar fi 0.98879766, scorul "Face") +- `"average"`, unde scorul fiecărei entități este media scorurilor cuvintelor care compun acea entitate (astfel, pentru "Sylvain" nu ar exista nicio diferență față de strategia `"simple"`, dar "Hugging Face" ar avea un scor de 0.9819, media scorurilor pentru "Hugging", 0.975 și "Face", 0.98879) + +Acum să vedem cum putem obține aceste rezultate fără a folosi funcția `pipeline()`! + +### De la inputuri la predicții[[from-inputs-to-predictions]] + +{#if fw === 'pt'} + +În primul rând trebuie să tokenizăm inputurile și sp le trecem prin model. Acest lucru este făcut excat ca în [Capitolul 2](/course/chapter2); noi am inițializat tokenizerul și modelul folosind clasa `AutoXxx` și apoi am folosit-o pe exemplul nostru: + +```py +from transformers import AutoTokenizer, AutoModelForTokenClassification + +model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModelForTokenClassification.from_pretrained(model_checkpoint) + +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +inputs = tokenizer(example, return_tensors="pt") +outputs = model(**inputs) +``` + +Deoarece aici folosim `AutoModelForTokenClassification` , noi primim un set de logits pentru fiecare token în input sequence: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +torch.Size([1, 19]) +torch.Size([1, 19, 9]) +``` + +{:else} + +În primul rând trebuie să tokenizăm inputurile și să le trecem prin model. Acest lucru este făcut excat ca în [Capitolul 2](/course/chapter2); noi am inițializat tokenizerul și modelul folosind clasa `TFAutoXxx` și apoi am folosit-o pe exemplul nostru: + +```py +from transformers import AutoTokenizer, TFAutoModelForTokenClassification + +model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = TFAutoModelForTokenClassification.from_pretrained(model_checkpoint) + +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +inputs = tokenizer(example, return_tensors="tf") +outputs = model(**inputs) +``` + +Deoarece aici folosim `TFAutoModelForTokenClassification`, noi primim un set de logits pentru fiecare token în input sequence: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +(1, 19) +(1, 19, 9) +``` + +{/if} + +Noi avem un batch cu 1 secvență din 19 tokenuri și modelul are 9 labeluri diferite, deci outputul modelului are un shape de 1 x 19 x 9. Ca și pentru text classification pipeline, noi folosim o funcție softmax pentru a face convert logiturilor în probabilități și luăm argmax pentru a obține predicții(atrage atenția asupra fatpului că putem lua argmax pe logituri deoarece softmax nu schimbă ordinea): + +{#if fw === 'pt'} + +```py +import torch + +probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)[0].tolist() +predictions = outputs.logits.argmax(dim=-1)[0].tolist() +print(predictions) +``` + +{:else} + +```py +import tensorflow as tf + +probabilities = tf.math.softmax(outputs.logits, axis=-1)[0] +probabilities = probabilities.numpy().tolist() +predictions = tf.math.argmax(outputs.logits, axis=-1)[0] +predictions = predictions.numpy().tolist() +print(predictions) +``` + +{/if} + +```python out +[0, 0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 6, 6, 6, 0, 8, 0, 0] +``` + +Atributul `model.config.id2label` con;ine mappingul indexilor la labeluri pe care le putem folosi pentru a înțelege predicțiile: + +```py +model.config.id2label +``` + +```python out +{0: 'O', + 1: 'B-MISC', + 2: 'I-MISC', + 3: 'B-PER', + 4: 'I-PER', + 5: 'B-ORG', + 6: 'I-ORG', + 7: 'B-LOC', + 8: 'I-LOC'} +``` + +Cum am văzut mai devreme, există 9 labeluri: `O` este labelul pentru tokenurile care nu se află în nicio entitate numită(aceasta reprezintă "exteriorul"), și avem apoi două labeluri pentru fiecare tip de entitate (divers, persoană, organizație și locație). Eticheta `B-XXX` indică faptul că tokenul se află la începutul entității `XXX` și eticheta `I-XXX` indică faptul că tokenul se află în interiorul entității `XXX`. De exemplu, în exemplul curent ne-am aștepta ca modelul nostru să clasifice tokenul `S` ca `B-PER` (începutul unei entități de-tip persoană) și tokenurile `##yl`, `##va` și `##in` ca `I-PER` (în interiorul unei entități de tip persoană). + +S-ar putea să credeți că modelul a greșit în acest caz, deoarece a atribuit eticheta `I-PER` tuturor acestor patru tokeni, dar acesta nu este în întregime adevărat. Există, de fapt, două formate pentru labelurile `B-` și `I-`: *IOB1* și *IOB2*. Formatul IOB2 (în roz mai jos), este cel pe care l-am introdus, în timp ce formatul IOB1 (în albastru), utilizează labelurile care încep cu `B-` doar pentru a separa două entități adiacente de același tip. Modelul pe care îl utilizăm a fost fine-tuned pe un dataset care utilizează acel format, ceea ce explică de ce atribuie labelul `I-PER` tokenului `S`. + +
+IOB1 vs IOB2 format + +
+ +Cu maparea aceasta, suntem gat a să reproducem(aproape în total) rezultat primului pipeline -- noi putem lua scorul și labelul fiecărui token care nu a fost clasificat ca `O`: + +```py +results = [] +tokens = inputs.tokens() + +for idx, pred in enumerate(predictions): + label = model.config.id2label[pred] + if label != "O": + results.append( + {"entity": label, "score": probabilities[idx][pred], "word": tokens[idx]} + ) + +print(results) +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S'}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl'}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va'}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in'}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu'}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging'}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face'}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn'}] +``` + +Acest lucru este foarte similar cu ce am avut mai devreme, cu o excepție: pipelineul de asemenea ne-a oferit informație despre `start` și `end` al fiecărei entități în propoziția originală. Acum e momentul când offset mappingul nostru ne va ajuta. Pentru a obține offseturile, noi trebuie să setăm `return_offsets_mapping=True` când aplicăm tokenizerul pe inputurile noastre: + +```py +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +inputs_with_offsets["offset_mapping"] +``` + +```python out +[(0, 0), (0, 2), (3, 7), (8, 10), (11, 12), (12, 14), (14, 16), (16, 18), (19, 22), (23, 24), (25, 29), (30, 32), + (33, 35), (35, 40), (41, 45), (46, 48), (49, 57), (57, 58), (0, 0)] +``` + +Fiecare tuple este spanul de text care corespunde fiecărui token, unde `(0, 0)` este rezervat pentru tokenii speciali. Noi am văzut înainte că tokenul la indexul 5 este `##yl`, care are aici `(12, 14)` ca offsets. Dacă luăm sliceul corespunzător în exemplul nostru: + +```py +example[12:14] +``` + +noi obținem spanul propriu de text fără `##`: + +```python out +yl +``` + +Folosind aceasta, putem acum completa rezultatele anterioare: + + +```py +results = [] +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +tokens = inputs_with_offsets.tokens() +offsets = inputs_with_offsets["offset_mapping"] + +for idx, pred in enumerate(predictions): + label = model.config.id2label[pred] + if label != "O": + start, end = offsets[idx] + results.append( + { + "entity": label, + "score": probabilities[idx][pred], + "word": tokens[idx], + "start": start, + "end": end, + } + ) + +print(results) +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Acest răspuns e același răspuns pe care l-am primit de la primul pipeline: + +### Gruparea entităților[[grouping-entities]] + +Utilizarea offseturilor pentru a determina cheile de start și de sfârșit pentru fiecare entitate este util, dar această informație nu este strict necesară. Când dorim să grupăm entitățile împreună, totuși, offseturile ne vor salva o mulțime de messy code. De exemplu, dacă am dori să grupăm împreună tokenii `Hu`, `##gging` și `Face`, am putea crea reguli speciale care să spună că primele două ar trebui să fie atașate și să înlăturăm `##`, iar `Face` ar trebui adăugat cu un spațiu, deoarece nu începe cu `##` -- dar acest lucru ar funcționa doar pentru acest tip particular de tokenizer. Ar trebui să scriem un alt set de reguli pentru un tokenizer SentencePiece sau unul Byte-Pair-Encoding (discutat mai târziu în acest capitol). + +Cu offseturile, tot acel cod custom dispare: pur și simplu putem lua spanul din textul original care începe cu primul token și se termină cu ultimul token. Deci, în cazul tokenurilor `Hu`, `##gging` și `Face`, ar trebui să începem la caracterul 33 (începutul lui `Hu`) și să ne oprim înainte de caracterul 45 (sfârșitul lui `Face`): + +```py +example[33:45] +``` + +```python out +Hugging Face +``` + +Pentru a scrie codul care post-procesează predicțiile în timp ce grupăm entitățile, vom grupa entitățile care sunt consecutive și labeled cu `I-XXX`, cu excepția primeia, care poate fi labeled ca `B-XXX` sau `I-XXX` (decidem să oprim gruparea unei entități atunci când întâlnim un `O`, un nou tip de entitate, sau un `B-XXX` care ne spune că o entitate de același tip începe): + +```py +import numpy as np + +results = [] +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +tokens = inputs_with_offsets.tokens() +offsets = inputs_with_offsets["offset_mapping"] + +idx = 0 +while idx < len(predictions): + pred = predictions[idx] + label = model.config.id2label[pred] + if label != "O": + # Remove the B- or I- + label = label[2:] + start, _ = offsets[idx] + + # Grab all the tokens labeled with I-label + all_scores = [] + while ( + idx < len(predictions) + and model.config.id2label[predictions[idx]] == f"I-{label}" + ): + all_scores.append(probabilities[idx][pred]) + _, end = offsets[idx] + idx += 1 + + # The score is the mean of all the scores of the tokens in that grouped entity + score = np.mean(all_scores).item() + word = example[start:end] + results.append( + { + "entity_group": label, + "score": score, + "word": word, + "start": start, + "end": end, + } + ) + idx += 1 + +print(results) +``` + +Și obținem aceleași răspuns ca de la pipelineul secundar! + +```python out +[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Alt exemplu de sarcină unde offseturile sunt extrem de useful pentru răspunderea la întrebări. Scufundându-ne în pipelineuri, un lucru pe care îl vom face în următoarea secțiune, ne vom premite să ne uităm peste o caracteristică a tokenizerului în librăria 🤗 Transformers: vom avea de-a face cu overflowing tokens când truncăm un input de o anumită lungime. + diff --git a/chapters/ro/chapter 6/3b.mdx b/chapters/ro/chapter 6/3b.mdx new file mode 100644 index 000000000..08acea387 --- /dev/null +++ b/chapters/ro/chapter 6/3b.mdx @@ -0,0 +1,643 @@ + + +# Tokenizeri rapizi în pipelineul QA[[fast-tokenizers-in-the-qa-pipeline]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Acum ne vom aprofunda în pipelineul `question-answering` și să vedem cum putem valorifica offesturile pentru a primi răspunzuri la întrebări la îndemână din context, asemănător cum am făcut cu entitățile grupate în secțiunea precedentă. Pe urmă vom vedea cum vom face față contextelor foarte lungi care ajung truncate. Puteți trece peste această secțiune dacă nu sunteți interesat în întrebarea care răspunde la sarcina aceasta. + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +## Folosind `question-answering` pipeline[[using-the-question-answering-pipeline]] + +Cum am văzut în [Capitolul 1](/course/chapter1), noi putem folosi pipelineul `question-answering` ca acesta pentru a răspunde la o întrebare: + +```py +from transformers import pipeline + +question_answerer = pipeline("question-answering") +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) +``` + +```python out +{'score': 0.97773, + 'start': 78, + 'end': 105, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +Spre deosebire de alte pipelineuri, care nu put trunca și face split la text care este mai lung decât lungimea maxim acceptată de model(și, prin urmare, pot pierde informații la sfârșitul unui document), accest pipeline poate face față contextelor foarte lungi și va returna răspunsul la întrebare chiar dacă aceasta se află la sfârșit: + +```py +long_context = """ +🤗 Transformers: State of the Art NLP + +🤗 Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction, +question answering, summarization, translation, text generation and more in over 100 languages. +Its aim is to make cutting-edge NLP easier to use for everyone. + +🤗 Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and +then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and +can be modified to enable quick research experiments. + +Why should I use transformers? + +1. Easy-to-use state-of-the-art models: + - High performance on NLU and NLG tasks. + - Low barrier to entry for educators and practitioners. + - Few user-facing abstractions with just three classes to learn. + - A unified API for using all our pretrained models. + - Lower compute costs, smaller carbon footprint: + +2. Researchers can share trained models instead of always retraining. + - Practitioners can reduce compute time and production costs. + - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages. + +3. Choose the right framework for every part of a model's lifetime: + - Train state-of-the-art models in 3 lines of code. + - Move a single model between TF2.0/PyTorch frameworks at will. + - Seamlessly pick the right framework for training, evaluation and production. + +4. Easily customize a model or an example to your needs: + - We provide examples for each architecture to reproduce the results published by its original authors. + - Model internals are exposed as consistently as possible. + - Model files can be used independently of the library for quick experiments. + +🤗 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_answerer(question=question, context=long_context) +``` + +```python out +{'score': 0.97149, + 'start': 1892, + 'end': 1919, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +Hai să vedem cum el face toate astea! + +## Folosind un model pentru răspunderea la întrebări[[using-a-model-for-question-answering]] + +Ca în cazul oricărui altui pipeline, începem prin tokenizarea datelor de intrare și apoi le trimitem prin model. Checkpointul utilizat în mod implicit pentru pipelineul `question-answering` este [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) ("squad" din nume provine de la datasetul pe care modelul a fost ajustat; vom vorbi mai multe despre datasetul SQuAD în [Capitolul 7](/course/chapter7/7)): + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModelForQuestionAnswering + +model_checkpoint = "distilbert-base-cased-distilled-squad" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) + +inputs = tokenizer(question, context, return_tensors="pt") +outputs = model(**inputs) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModelForQuestionAnswering + +model_checkpoint = "distilbert-base-cased-distilled-squad" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) + +inputs = tokenizer(question, context, return_tensors="tf") +outputs = model(**inputs) +``` + +{/if} + +Observați că noi tokenizăm întrebrea și contextul ca o perecehe, cu întrebarea prima. + + +
+An example of tokenization of question and context + +
+ +Modelele create pentru răspunderea la întrebări funcționează puțin diferit de modelele pe care le-am văzut până acum. Folosind imaginea de mai sus ca exemplu, modelul a fost antrenat pentru a prezice indicele tokenului cu care începe răspunsului (aici 21) și indicele simbolului la care se termină răspunsul (aici 24). Acesta este motivul pentru care modelele respective nu returnează un singur tensor de logits, ci două: unul pentru logits-ul corespunzători tokenului cu care începe răspunsului și unul pentru logits-ul corespunzător tokenului de sfârșit al răspunsului. Deoarece în acest caz avem un singur input care conține 66 de token-uri, obținem: + +```py +start_logits = outputs.start_logits +end_logits = outputs.end_logits +print(start_logits.shape, end_logits.shape) +``` + +{#if fw === 'pt'} + +```python out +torch.Size([1, 66]) torch.Size([1, 66]) +``` + +{:else} + +```python out +(1, 66) (1, 66) +``` + +{/if} + +Pentru a converti acești logits în probabilități, vom aplica o funcție softmax - dar înainte de aceasta, trebuie să ne asigurăm că mascăm indicii care nu fac parte din context. Inputul nostru este `[CLS] întrebare [SEP] context [SEP]`, deci trebuie să mascăm token-urile întrebării, precum și tokenul `[SEP]`. Cu toate acestea, vom păstra simbolul `[CLS]`, deoarece unele modele îl folosesc pentru a indica faptul că răspunsul nu se află în context. + +Deoarece vom aplica ulterior un softmax, trebuie doar să înlocuim logiturile pe care dorim să le mascăm cu un număr negativ mare. Aici, folosim `-10000`: + +{#if fw === 'pt'} + +```py +import torch + +sequence_ids = inputs.sequence_ids() +# Mask everything apart from the tokens of the context +mask = [i != 1 for i in sequence_ids] +# Unmask the [CLS] token +mask[0] = False +mask = torch.tensor(mask)[None] + +start_logits[mask] = -10000 +end_logits[mask] = -10000 +``` + +{:else} + +```py +import tensorflow as tf + +sequence_ids = inputs.sequence_ids() +# Mask everything apart from the tokens of the context +mask = [i != 1 for i in sequence_ids] +# Unmask the [CLS] token +mask[0] = False +mask = tf.constant(mask)[None] + +start_logits = tf.where(mask, -10000, start_logits) +end_logits = tf.where(mask, -10000, end_logits) +``` + +{/if} + +Acum că am mascat în mod corespunzător logiturile corespunzătoare pozițiilor pe care nu dorim să le prezicem, putem aplica softmax: + +{#if fw === 'pt'} + +```py +start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1)[0] +end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1)[0] +``` + +{:else} + +```py +start_probabilities = tf.math.softmax(start_logits, axis=-1)[0].numpy() +end_probabilities = tf.math.softmax(end_logits, axis=-1)[0].numpy() +``` + +{/if} + +La acest stadiu, am putea lua argmax al probabilităților de început și de sfârșit - dar am putea ajunge la un indice de început care este mai mare decât indicele de sfârșit, deci trebuie să luăm câteva precauții suplimentare. Vom calcula probabilitățile fiecărui `start_index` și `end_index` posibil în cazul în care `start_index <= end_index`, apoi vom lua un tuple `(start_index, end_index)` cu cea mai mare probabilitate. + +Presupunând că evenimentele "The answer starts at `start_index`" și "The answer ends at `end_index`" sunt independente, probabilitatea ca răspunsul să înceapă la `start_index` și să se termine la `end_index` este: + +$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ + +Deci, pentru a calcula toate scorurile, trebuie doar să calculăm toate produsele \\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\) unde `start_index <= end_index`. + +Mai întâi hai să calculăm toate produsele posibile: + +```py +scores = start_probabilities[:, None] * end_probabilities[None, :] +``` + +{#if fw === 'pt'} + +Apoi vom masca valorile în care `start_index > end_index` prin stabilirea lor la `0` (celelalte probabilități sunt toate numere pozitive). Funcția `torch.triu()` returnează partea triunghiulară superioară a tensorului 2D trecut ca argument, deci va face această mascare pentru noi: + +```py +scores = torch.triu(score) +``` + +{:else} + +Apoi vom masca valorile în care `start_index > end_index` prin stabilirea lor la `0` (celelalte probabilități sunt toate numere pozitive). Funcția `np.triu()` returnează partea triunghiulară superioară a tensorului 2D trecut ca argument, deci va face această mascare pentru noi: + +```py +import numpy as np + +scores = np.triu(scores) +``` + +{/if} + +Acum trebuie doar să obținem indicele maximului. Deoarece PyTorch va returna indicele în tensorul aplatizat, trebuie să folosim operațiile floor division `//` și modulusul `%` pentru a obține `start_index` și `end_index`: + +```py +max_index = scores.argmax().item() +start_index = max_index // scores.shape[1] +end_index = max_index % scores.shape[1] +print(scores[start_index, end_index]) +``` + +Nu am terminat încă, dar cel puțin avem deja scorul corect pentru răspuns (puteți verifica acest lucru comparându-l cu primul rezultat din secțiunea anterioară): + +```python out +0.97773 +``` + + + +✏️ **Încercați!** Calculați indicii de început și de sfârșit pentru cele mai probabile cinci răspunsuri. + + + +Avem `start_index` și `end_index` ale răspunsului în termeni de tokens, deci acum trebuie doar să convertim în character indices în context. Acesta este momentul în care offseturile vor fi foarte utile. Putem să le luăm și să le folosim așa cum am făcut în sarcina de clasificare a tokenurilor: + +```py +inputs_with_offsets = tokenizer(question, context, return_offsets_mapping=True) +offsets = inputs_with_offsets["offset_mapping"] + +start_char, _ = offsets[start_index] +_, end_char = offsets[end_index] +answer = context[start_char:end_char] +``` + +Acum trebuie doar să formatăm totul pentru a obține rezultatul nostru: + +```py +result = { + "answer": answer, + "start": start_char, + "end": end_char, + "score": scores[start_index, end_index], +} +print(result) +``` + +```python out +{'answer': 'Jax, PyTorch and TensorFlow', + 'start': 78, + 'end': 105, + 'score': 0.97773} +``` + +Grozav! Este la fel ca în primul nostru exemplu! + + + +✏️ **Încercați!** Utilizați cele mai bune scoruri pe care le-ați calculat anterior pentru a afișa cele mai probabile cinci răspunsuri. Pentru a vă verifica rezultatele, întoarceți-vă la primul pipeline și introduceți `top_k=5` atunci când îl apelați. + + + +## Gestionarea contextelor lungi[[handling-long-contexts]] + +Dacă încercăm să tokenizăm întrebarea și contextul lung pe care le-am folosit ca un exemplu anterior, vom obține un număr de tokenuri mai mare decât lungimea maximă utilizată în pipelineul `question-answering` (care este 384): + +```py +inputs = tokenizer(question, long_context) +print(len(inputs["input_ids"])) +``` + +```python out +461 +``` + +Prin urmare, va trebui să trunchiem inputurile la lungimea maximă. Există mai multe modalități prin care putem face acest lucru, dar nu dorim să trunchiem întrebarea, ci doar contextul. Deoarece contextul este a doua propoziție, vom utiliza strategia de trunchiere `"only_second"`. Problema care apare atunci este că răspunsul la întrebare poate să nu fie în contextul trunchiat. Aici, de exemplu, am ales o întrebare la care răspunsul se află spre sfârșitul contextului, iar atunci când îl trunchiem, răspunsul nu este prezent: + +```py +inputs = tokenizer(question, long_context, max_length=384, truncation="only_second") +print(tokenizer.decode(inputs["input_ids"])) +``` + +```python out +""" +[CLS] Which deep learning libraries back [UNK] Transformers? [SEP] [UNK] Transformers : State of the Art NLP + +[UNK] Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction, +question answering, summarization, translation, text generation and more in over 100 languages. +Its aim is to make cutting-edge NLP easier to use for everyone. + +[UNK] Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and +then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and +can be modified to enable quick research experiments. + +Why should I use transformers? + +1. Easy-to-use state-of-the-art models: + - High performance on NLU and NLG tasks. + - Low barrier to entry for educators and practitioners. + - Few user-facing abstractions with just three classes to learn. + - A unified API for using all our pretrained models. + - Lower compute costs, smaller carbon footprint: + +2. Researchers can share trained models instead of always retraining. + - Practitioners can reduce compute time and production costs. + - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages. + +3. Choose the right framework for every part of a model's lifetime: + - Train state-of-the-art models in 3 lines of code. + - Move a single model between TF2.0/PyTorch frameworks at will. + - Seamlessly pick the right framework for training, evaluation and production. + +4. Easily customize a model or an example to your needs: + - We provide examples for each architecture to reproduce the results published by its original authors. + - Model internal [SEP] +""" +``` + +Aceasta înseamnă că modelul va avea dificultăți în a alege răspunsul corect. Pentru a rezolva acest lucru, pipelineul `question-answering` ne permite să împărțim contextul în bucăți mai mici, specificând lungimea maximă. Pentru a ne asigura că nu împărțim contextul exact în locul nepotrivit pentru a face posibilă găsirea răspunsului, aceasta include și o anumită suprapunere între bucăți. + +Putem cere tokenizerului (rapid sau lent) să facă acest lucru pentru noi adăugând `return_overflowing_tokens=True`, și putem specifica suprapunerea dorită cu argumentul `stride`. Iată un exemplu, folosind o propoziție mai mică: + +```py +sentence = "This sentence is not too long but we are going to split it anyway." +inputs = tokenizer( + sentence, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 +) + +for ids in inputs["input_ids"]: + print(tokenizer.decode(ids)) +``` + +```python out +'[CLS] This sentence is not [SEP]' +'[CLS] is not too long [SEP]' +'[CLS] too long but we [SEP]' +'[CLS] but we are going [SEP]' +'[CLS] are going to split [SEP]' +'[CLS] to split it anyway [SEP]' +'[CLS] it anyway. [SEP]' +``` + +După cum putem vedea, propoziția a fost împărțită în bucăți astfel încât fiecare intrare din `inputs["input_ids"]` să aibă cel mult 6 token-uri (aici ar trebui să adăugăm padding pentru ca ultima intrare să aibă aceeași dimensiune ca celelalte) și există o suprapunere de 2 tokenuri între fiecare intrare. + +Să aruncăm o privire mai atentă la rezultatul tokenizării: + +```py +print(inputs.keys()) +``` + +```python out +dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) +``` + +Așa cum era de așteptat, obținem ID-uri de intrare și un attention mask. Ultima cheie, `overflow_to_sample_mapping`, este o hartă care ne spune cărei propoziții îi corespunde fiecare dintre rezultate - aici avem 7 rezultate care provin toate din (singura) propoziție pe care am transmis-o tokenizerului: + +```py +print(inputs["overflow_to_sample_mapping"]) +``` + +```python out +[0, 0, 0, 0, 0, 0, 0] +``` + +Acest lucru este mai util atunci când tokenizăm mai multe propoziții împreună. De exemplu, aceasta: + +```py +sentences = [ + "This sentence is not too long but we are going to split it anyway.", + "This sentence is shorter but will still get split.", +] +inputs = tokenizer( + sentences, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 +) + +print(inputs["overflow_to_sample_mapping"]) +``` + +gets us: + +```python out +[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] +``` + +ceea ce înseamnă că prima propoziție este împărțită în 7 fragmente ca înainte, iar următoarele 4 fragmente provin din a doua propoziție. + +Acum să ne întoarcem la contextul nostru lung. În mod implicit, pipelineul `question-answering` utilizează o lungime maximă de 384, așa cum am menționat mai devreme, și un stride de 128, care corespund modului în care modelul a fost fine-tuned (puteți ajusta acești parametri prin trecerea argumentelor `max_seq_len` și `stride` atunci când apelați pipelineul). Astfel, vom utiliza acești parametri la tokenizare. Vom adăuga, de asemenea, padding (pentru a avea sampleuri de aceeași lungime, astfel încât să putem construi tensori), precum și pentru a solicita offsets: + +```py +inputs = tokenizer( + question, + long_context, + stride=128, + max_length=384, + padding="longest", + truncation="only_second", + return_overflowing_tokens=True, + return_offsets_mapping=True, +) +``` + +Aceste "inputuri" vor conține ID-urile de input și attention maskurile așteptate de model, precum și offseturile și "overflow_to_sample_mapping" despre care tocmai am vorbit. Deoarece cei doi nu sunt parametri utilizați de model, îi vom scoate din `inputs` (și nu vom stoca harta, deoarece nu este utilă aici) înainte de a-l converti într-un tensor: + +{#if fw === 'pt'} + +```py +_ = inputs.pop("overflow_to_sample_mapping") +offsets = inputs.pop("offset_mapping") + +inputs = inputs.convert_to_tensors("pt") +print(inputs["input_ids"].shape) +``` + +```python out +torch.Size([2, 384]) +``` + +{:else} + +```py +_ = inputs.pop("overflow_to_sample_mapping") +offsets = inputs.pop("offset_mapping") + +inputs = inputs.convert_to_tensors("tf") +print(inputs["input_ids"].shape) +``` + +```python out +(2, 384) +``` + +{/if} + +Contextul nostru lung a fost împărțit în două, ceea ce înseamnă că, după ce trece prin modelul nostru, vom avea două seturi de logits de început și de sfârșit: + +```py +outputs = model(**inputs) + +start_logits = outputs.start_logits +end_logits = outputs.end_logits +print(start_logits.shape, end_logits.shape) +``` + +{#if fw === 'pt'} + +```python out +torch.Size([2, 384]) torch.Size([2, 384]) +``` + +{:else} + +```python out +(2, 384) (2, 384) +``` + +{/if} + +Ca și înainte, mai întâi mascăm tokenii care nu fac parte din context înainte de a lua softmax. De asemenea, mascăm toți padding tokens(marcate de attention mask): + +{#if fw === 'pt'} + +```py +sequence_ids = inputs.sequence_ids() +# Mask everything apart from the tokens of the context +mask = [i != 1 for i in sequence_ids] +# Unmask the [CLS] token +mask[0] = False +# Mask all the [PAD] tokens +mask = torch.logical_or(torch.tensor(mask)[None], (inputs["attention_mask"] == 0)) + +start_logits[mask] = -10000 +end_logits[mask] = -10000 +``` + +{:else} + +```py +sequence_ids = inputs.sequence_ids() +# Mask everything apart from the tokens of the context +mask = [i != 1 for i in sequence_ids] +# Unmask the [CLS] token +mask[0] = False +# Mask all the [PAD] tokens +mask = tf.math.logical_or(tf.constant(mask)[None], inputs["attention_mask"] == 0) + +start_logits = tf.where(mask, -10000, start_logits) +end_logits = tf.where(mask, -10000, end_logits) +``` + +{/if} + +Then we can use the softmax to convert our logits to probabilities: + +{#if fw === 'pt'} + +```py +start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1) +end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1) +``` + +{:else} + +```py +start_probabilities = tf.math.softmax(start_logits, axis=-1).numpy() +end_probabilities = tf.math.softmax(end_logits, axis=-1).numpy() +``` + +{/if} + +Următorul pas este similar cu ceea ce am făcut pentru contextul mic, dar îl repetăm pentru fiecare dintre cele două chunkuri. Atribuim un scor tuturor intervalelor posibile de răspuns, apoi luăm intervalul cu cel mai bun scor: + +{#if fw === 'pt'} + +```py +candidates = [] +for start_probs, end_probs in zip(start_probabilities, end_probabilities): + scores = start_probs[:, None] * end_probs[None, :] + idx = torch.triu(scores).argmax().item() + + start_idx = idx // scores.shape[1] + end_idx = idx % scores.shape[1] + score = scores[start_idx, end_idx].item() + candidates.append((start_idx, end_idx, score)) + +print(candidates) +``` + +{:else} + +```py +candidates = [] +for start_probs, end_probs in zip(start_probabilities, end_probabilities): + scores = start_probs[:, None] * end_probs[None, :] + idx = np.triu(scores).argmax().item() + + start_idx = idx // scores.shape[1] + end_idx = idx % scores.shape[1] + score = scores[start_idx, end_idx].item() + candidates.append((start_idx, end_idx, score)) + +print(candidates) +``` + +{/if} + +```python out +[(0, 18, 0.33867), (173, 184, 0.97149)] +``` + +Cei doi candidați corespund celor mai bune răspunsuri pe care modelul le-a putut găsi în fiecare parte. Modelul este mult mai încrezător că răspunsul corect se află în a doua parte (ceea ce este un semn bun!). Acum trebuie doar să facem map celor două intervale de tokenuri cu intervalele de caractere din context (trebuie să o punem în corespondență doar pe a doua pentru a avea răspunsul nostru, dar este interesant să vedem ce a ales modelul în prima parte). + + + +✏️ **Încercați!** Adaptați codul de mai sus pentru a returna scorurile și spanurile intervalele pentru cele mai probabile cinci răspunsuri (în total, nu pe chunk). + + + +`offsets`-urile pe care le-am luat mai devreme este de fapt o listă de offsets, cu o listă pentru fiecare chunk de text: + +```py +for candidate, offset in zip(candidates, offsets): + start_token, end_token, score = candidate + start_char, _ = offset[start_token] + _, end_char = offset[end_token] + answer = long_context[start_char:end_char] + result = {"answer": answer, "start": start_char, "end": end_char, "score": score} + print(result) +``` + +```python out +{'answer': '\n🤗 Transformers: State of the Art NLP', 'start': 0, 'end': 37, 'score': 0.33867} +{'answer': 'Jax, PyTorch and TensorFlow', 'start': 1892, 'end': 1919, 'score': 0.97149} +``` + +Dacă ignorăm primul rezultat, obținem același rezultat ca și pipelineul noastru pentru acest context lung - yay! + + + +✏️ **Încercați!** Utilizați cele mai bune scoruri pe care le-ați calculat înainte pentru a afișa cele mai probabile cinci răspunsuri (pentru întregul context, nu pentru fiecare chunk). Pentru a vă verifica rezultatele, întoarceți-vă la primul pipeline și introduceți `top_k=5` atunci când îl apelați. + + + +Aici se încheie scufundarea noastră în capacitățile tokenizerului. Vom pune toate acestea din nou în practică în capitolul următor, când vă vom arăta cum să ajustați un model pentru o serie de sarcini NLP comune. diff --git a/chapters/ro/chapter 6/4.mdx b/chapters/ro/chapter 6/4.mdx new file mode 100644 index 000000000..e87d27c57 --- /dev/null +++ b/chapters/ro/chapter 6/4.mdx @@ -0,0 +1,122 @@ +# Normalization and pre-tokenization[[normalization-and-pre-tokenization]] + + + +Înainte de a analiza în profunzime cei mai comuni trei algoritmi de subword tokenization utilizați cu modelele Transformer (Byte-Pair Encoding [BPE], WordPiece și Unigram), vom arunca mai întâi o privire la preprocesarea pe care fiecare tokenizer o aplică textului. Iată o prezentare generală a etapelor din pipelineul de tokenizare: + +
+The tokenization pipeline. + +
+ +Înainte de a împărți un text în subtokens (în conformitate cu modelul său), tokenizerul efectuează doi pași: _normalization__ și _pre-tokenization_. + +## Normalization[[normalization]] + + + +Etapa de normalizare implică o curățare generală, cum ar fi eliminarea spațiilor inutile, a le face minuscule, și/sau ștergerea accentelor. Dacă sunteți familiarizat cu [Unicode normalization](http://www.unicode.org/reports/tr15/) (cum ar fi NFC sau NFKC), acest lucru poate fi aplicat și de tokenizer. + +`Tokenizer`-ul 🤗 Transformers are un atribut numit `backend_tokenizer` care oferă acces la tokenizatorul de bază din biblioteca 🤗 Tokenizers: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") +print(type(tokenizer.backend_tokenizer)) +``` + +```python out + +``` + +Atributul `normalizer` al obiectului `tokenizer` are o metodă `normalize_str()` pe care o putem folosi pentru a vedea cum se realizează normalizarea: + +```py +print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +'hello how are u?' +``` + +În acest exemplu, din moment ce am ales checkpointul `bert-base-uncased`, normalizarea a aplicat scrierea cu minusculă și a eliminat accentele. + + + +✏️ **Încercați!** Încărcați un tokenizer din checkpointul `bert-base-cased` și treceți-i același exemplu. Care sunt principalele diferențe pe care le puteți observa între versiunile cased și uncased ale tokenizerului? + + + +## Pre-tokenization[[pre-tokenization]] + + + +După cum vom vedea în secțiunile următoare, un tokenizer nu poate fi antrenat doar pe text raw. În schimb, trebuie mai întâi să împărțim textele în entități mici, cum ar fi cuvintele. Aici intervine etapa de pre-tokenizare. După cum am văzut în [Capitolul 2](/course/chapter2), un tokenizer bazat pe cuvinte poate împărți pur și simplu un text raw în cuvinte pe baza spațiului și a punctuației. Aceste cuvinte vor fi limitele subtokenurilor pe care tokenizerul le poate învăța în timpul instruirii sale. + +Pentru a vedea cum un tokenizator rapid efectuează pre-tokenizarea, putem utiliza metoda `pre_tokenize_str()` a atributului `pre_tokenizer` al obiectului `tokenizer`: + +```py +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))] +``` + +Observați cum tokenizatorul ține deja evidența offseturilor, acesta fiind modul în care ne poate oferi mappingul offseturilor pe care l-am folosit în secțiunea anterioară. Aici, tokenizatorul ignoră cele două spații și le înlocuiește cu unul singur, dar offsetul sare între `are` și `you` pentru a ține cont de acest lucru. + +Deoarece utilizăm un tokenizer BERT, pre-tokenizarea implică separarea spațiilor și a punctuației. Alți tokenizatori pot avea reguli diferite pentru acest pas. De exemplu, dacă folosim tokenizatorul GPT-2: + +```py +tokenizer = AutoTokenizer.from_pretrained("gpt2") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +va despărți în spații și punctuație, dar va păstra spațiile și le va înlocui cu un simbol `Ġ`, permițându-i să recupereze spațiile originale dacă facem decode tokenilor: + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), + ('?', (19, 20))] +``` + +De asemenea, rețineți că, spre deosebire de tokenizatorul BERT, acest tokenizator nu ignoră spațiul dublu. + +Pentru un ultim exemplu, să aruncăm o privire la tokenizerul T5, care se bazează pe algoritmul SentencePiece: + +```py +tokenizer = AutoTokenizer.from_pretrained("t5-small") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +```python out +[('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))] +``` + +Ca și tokenizatorul GPT-2, acesta păstrează spațiile și le înlocuiește cu un token specific (`_`), dar tokenizatorul T5 separă doar spațiile, nu și punctuația. De asemenea, observați că a adăugat un spațiu implicit la începutul propoziției (înainte de `Hello`) și a ignorat spațiul dublu dintre `are` și `you`. + +Acum că am văzut puțin din modul în care diferite tokenizere procesează textul, putem începe să explorăm algoritmii care stau la baza acestora. Vom începe cu o privire rapidă asupra SentencePiece, care se aplică pe scară largă; apoi, în următoarele trei secțiuni, vom examina modul în care funcționează cei trei algoritmi principali utilizați pentru tokenizarea subcuvintelor. + +## SentencePiece[[sentencepiece]] + +[SentencePiece](https://github.com/google/sentencepiece) este un algoritm de tokenizare pentru preprocesarea textului pe care îl puteți utiliza cu oricare dintre modelele pe care le vom vedea în următoarele trei secțiuni. Acesta consideră textul ca o secvență de caractere Unicode și înlocuiește spațiile cu un caracter special, `▁`. Folosit împreună cu algoritmul Unigram (a se vedea [secțiunea 7](/course/chapter7/7)), nu necesită nici măcar o etapă de pre-tokenizare, ceea ce este foarte util pentru limbile în care nu se folosește caracterul spațiu (cum ar fi chineza sau japoneza). + +Cealaltă caracteristică principală a SentencePiece este *reversible tokenization*: deoarece nu există un tratament special al spațiilor, decodarea tokenurilor se face pur și simplu prin concatenarea lor și înlocuirea `_` cu spații - acest lucru rezultă în textul normalizat. După cum am văzut mai devreme, tokenizatorul BERT elimină spațiile care se repetă, deci tokenizarea sa nu este reversibilă. + +## Prezentare generală a algoritmului[[algorithm-overview]] + +În următoarele secțiuni, vom analiza cei trei algoritmi principali de tokenizare a subcuvintelor: BPE (utilizat de GPT-2 și alții), WordPiece (utilizat de exemplu de BERT) și Unigram (utilizat de T5 și alții). Înainte de a începe, iată o scurtă prezentare generală a modului în care funcționează fiecare dintre acestea. Nu ezitați să reveniți la acest tabel după ce citiți fiecare dintre secțiunile următoare, dacă încă nu are sens pentru dumneavoastră. + +Model | BPE | WordPiece | Unigram +:----:|:---:|:---------:|:------: +Training | Pornește de la un vocabular restrâns și învață reguli de îmbinare a tokenilor | Pornește de la un vocabular restrâns și învață reguli de îmbinare a tokenilor | Pornește de la un vocabular mare și învață regulile de eliminare a tokenilor +Training step | Combină tokenii corespunzători celei mai comune perechi | Combină tokenii corespunzători perechii cu cel mai bun scor pe baza frecvenței perechii, privilegiind perechile în care fiecare token individual este mai puțin frecvent| Elimină toți tokenii din vocabular care vor minimiza pierderea calculată pe întregul corpus +Learns | Reguli de combinare și un vocabular| Doar un vocabular| Un vocabular cu un anumit scor pentru fiecare token +Encoding | Împarte un cuvânt în caractere și aplică îmbinările învățate în timpul antrenării | Găsește cel mai lung subcuvânt începând de la început care se află în vocabular, apoi face același lucru pentru restul cuvântului | Găsește cea mai probabilă împărțire în tokens, folosind scorurile învățate în timpul antrenării + +Acum hai să trecem la BPE! \ No newline at end of file diff --git a/chapters/ro/chapter 6/5.mdx b/chapters/ro/chapter 6/5.mdx new file mode 100644 index 000000000..91c26b82f --- /dev/null +++ b/chapters/ro/chapter 6/5.mdx @@ -0,0 +1,360 @@ +# Tokenizarea Byte-Pair Encoding[[byte-pair-encoding-tokenization]] + + + +Byte-Pair Encoding (BPE) a fost inițial dezvoltat ca un algoritm de comprimare a textelor și apoi utilizat de OpenAI pentru tokenizare la preantrenarea modelului GPT. Acesta este utilizat de o mulțime de modele Transformers, inclusiv GPT, GPT-2, RoBERTa, BART și DeBERTa. + + + + + +💡 Această secțiune acoperă BPE în profunzime, mergând până la prezentarea unei implementări complete. Puteți sări la sfârșit dacă doriți doar o prezentare generală a algoritmului de tokenizare. + + + +## Algoritmul de antrenare[[training-algorithm]] + +Antrenarea BPE începe prin calcularea setului unic de cuvinte utilizate în corpus (după finalizarea etapelor de normalizare și pre-tokenizare), apoi construirea vocabularului prin preluarea tuturor simbolurilor utilizate pentru scrierea acestor cuvinte. Ca un exemplu foarte simplu, să spunem că corpusul nostru utilizează aceste cinci cuvinte: + +``` +"hug", "pug", "pun", "bun", "hugs" +``` + +Vocabularul de bază va fi atunci `["b", "g", "h", "n", "p", "s", "u"]`. Pentru cazurile din lumea reală, vocabularul de bază va conține cel puțin toate caracterele ASCII și, probabil, și unele caractere Unicode. Dacă un exemplu pe care îl tokenizați utilizează un caracter care nu se află în corpusul de antrenare, acel caracter va fi convertit într-un token necunoscut. Acesta este unul dintre motivele pentru care o mulțime de modele NLP sunt foarte proaste la analizarea conținutului cu emoji, de exemplu. + + + +Tokenizerele GPT-2 și RoBERTa (care sunt destul de asemănătoare) au o modalitate inteligentă de a rezolva acest lucru: ele nu privesc cuvintele ca fiind scrise cu caractere Unicode, ci cu bytes. În acest fel, vocabularul de bază are o dimensiune mică (256), dar fiecare caracter la care vă puteți gândi va fi inclus și nu va ajunge să fie convertit într-un token necunoscut. Acest truc se numește *byte-level BPE*. + + + +După obținerea acestui vocabular de bază, adăugăm noi tokeni până când se atinge dimensiunea dorită a vocabularului prin învățarea prin *merges*, care sunt reguli de merge a două elemente ale vocabularului existent într-unul nou. Astfel, la început, aceste fuziuni vor crea tokenuri cu două caractere, iar apoi, pe măsură ce antrenamentul progresează, subwords mai lungi. + +În orice etapă din timpul antrenării tokenizerului, algoritmul BPE va căuta cea mai frecventă pereche de tokenuri existente (prin "pereche" înțelegem aici doi tokeni consecutivi într-un cuvânt). Acea pereche cea mai frecventă este cea care va fi mergea, iar noi ștergem și repetăm pentru pasul următor. + +Revenind la exemplul nostru anterior, să presupunem că cuvintele au următoarele frecvențe: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +înțelegând că `"hug"` a fost prezent de 10 ori în corpus, `"pug"` de 5 ori, `"pun"` de 12 ori, `"bun"` de 4 ori, iar `"hugs"` de 5 ori. Începem antrenamentul împărțind fiecare cuvânt în caractere (cele care formează vocabularul nostru inițial), astfel încât să putem vedea fiecare cuvânt ca pe o listă de tokeni: + +``` +("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5) +``` + +Apoi ne uităm la perechi. Perechea `("h", "u")` este prezentă în cuvintele `"hug"` și `"hugs"`, deci de 15 ori în total în corpus. Totuși, nu este cea mai frecventă pereche: această onoare revine perechii `("u", "g")`, care este prezentă în cuvintele `"hug"`, `"pug"` și `"hugs"`, pentru un total de 20 de ori în vocabular. + +Astfel, prima regulă de merge învățată de tokenizer este `("u", "g") -> "ug"`, ceea ce înseamnă că `"ug"` va fi adăugat la vocabular, iar perechea ar trebui să fie merged în toate cuvintele din corpus. La sfârșitul acestei etape, vocabularul și corpus-ul arată astfel: + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug"] +Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5) +``` + +Acum avem câteva perechi care rezultă într-un token mai lung de două caractere: perechea `("h", "ug")`, de exemplu (prezentă de 15 ori în corpus). Cea mai frecventă pereche în această etapă este `("u", "n")`, prezentă însă de 16 ori în corpus, astfel încât a doua regulă de îmbinare învățată este `("u", "n") -> "un"`. Adăugarea acestei reguli la vocabular și mergingul tuturor aparițiilor existente ne conduce la: + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"] +Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5) +``` + +Acum, cea mai frecventă pereche este `("h", "ug")`, așa că învățăm regula de erge `("h", "ug") -> "hug"`, care ne oferă primul nostru simbol din trei litere. După fuzionare, corpusul arată astfel: + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"] +Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5) +``` + +Și continuăm astfel până când ajungem la dimensiunea dorită a vocabularului. + + + +✏️ **Acum e rândul tău!** Care crezi că va fi următoarea regulă de fuziune? + + + +## Algoritmul de tokenizare[[tokenization-algorithm]] + +Tokenizarea urmează îndeaproape procesul de antrenare, în sensul că noile inputuri sunt tokenizate prin aplicarea următoarelor etape: + +1. Normalizare +2. Pre-tokenizare +3. Divizarea cuvintelor în caractere individuale +4. Aplicarea regulilor de merge învățate în ordine asupra acestor împărțiri + +Să luăm exemplul pe care l-am folosit în timpul antrenamentului, cu cele trei reguli de merge învățate: + +``` +("u", "g") -> "ug" +("u", "n") -> "un" +("h", "ug") -> "hug" +``` + +Cuvântul `"bug"` va fi tokenizat ca `["b", "ug"]`. Cu toate acestea, cuvântul `"mug"` va fi tokenizat ca `["[UNK]", "ug"]` deoarece litera `"m"` nu a fost în vocabularul de bază. De asemenea, cuvântul `"thug"` va fi tokenizat ca `["[UNK]", "hug"]`: litera `"t"` nu se află în vocabularul de bază, iar aplicarea regulilor de merge duce mai întâi la fuzionarea lui `"u"` și `"g"` și apoi la fuzionarea lui `"h"` și `"ug"`. + + + +✏️ **Acum e rândul tău!** Cum crezi că va fi tokenizat cuvântul `"unhug"`? + + + +## Implementarea BPE[[implementing-bpe]] + +Acum să aruncăm o privire la implementarea algoritmului BPE. Aceasta nu va fi o versiune optimizată pe care o puteți utiliza pe un corpus mare; dorim doar să vă arătăm codul pentru a putea înțelege algoritmul puțin mai bine. + +În primul rând avem nevoie de un corpus, așa că haideți să creăm unul simplu cu câteva propoziții: + +```python +corpus = [ + "This is the Hugging Face Course.", + "This chapter is about tokenization.", + "This section shows several tokenizer algorithms.", + "Hopefully, you will be able to understand how they are trained and generate tokens.", +] +``` + +Apoi, trebuie să pre-tokenizăm acest corpus în cuvinte. Deoarece replicăm un tokenizator BPE (precum GPT-2), vom utiliza tokenizatorul `gpt2` pentru pre-tokenizare: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Apoi, calculăm frecvențele fiecărui cuvânt din corpus la fel ca în cazul pre-tokenizării: + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) + +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +print(word_freqs) +``` + +```python out +defaultdict(int, {'This': 3, 'Ġis': 2, 'Ġthe': 1, 'ĠHugging': 1, 'ĠFace': 1, 'ĠCourse': 1, '.': 4, 'Ġchapter': 1, + 'Ġabout': 1, 'Ġtokenization': 1, 'Ġsection': 1, 'Ġshows': 1, 'Ġseveral': 1, 'Ġtokenizer': 1, 'Ġalgorithms': 1, + 'Hopefully': 1, ',': 1, 'Ġyou': 1, 'Ġwill': 1, 'Ġbe': 1, 'Ġable': 1, 'Ġto': 1, 'Ġunderstand': 1, 'Ġhow': 1, + 'Ġthey': 1, 'Ġare': 1, 'Ġtrained': 1, 'Ġand': 1, 'Ġgenerate': 1, 'Ġtokens': 1}) +``` + +Următorul pas este calcularea vocabularului de bază, format din toate caracterele utilizate în corpus: + +```python +alphabet = [] + +for word in word_freqs.keys(): + for letter in word: + if letter not in alphabet: + alphabet.append(letter) +alphabet.sort() + +print(alphabet) +``` + +```python out +[ ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', + 't', 'u', 'v', 'w', 'y', 'z', 'Ġ'] +``` + +De asemenea, adăugăm tokenurile speciale utilizate de model la începutul vocabularului respectiv. În cazul GPT-2, singurul simbol special este `"<|endoftext|>"`: + +```python +vocab = ["<|endoftext|>"] + alphabet.copy() +``` + +Acum trebuie să împărțim fiecare cuvânt în caractere individuale, pentru a putea începe antrenarea: + +```python +splits = {word: [c for c in word] for word in word_freqs.keys()} +``` + +Acum că suntem pregătiți pentru antrenare, să scriem o funcție care calculează frecvența fiecărei perechi. Va trebui să folosim această funcție la fiecare etapă a antrenare: + +```python +def compute_pair_freqs(splits): + pair_freqs = defaultdict(int) + for word, freq in word_freqs.items(): + split = splits[word] + if len(split) == 1: + continue + for i in range(len(split) - 1): + pair = (split[i], split[i + 1]) + pair_freqs[pair] += freq + return pair_freqs +``` + +Să aruncăm o privire la o parte din acest dicționar după separările inițiale: + +```python +pair_freqs = compute_pair_freqs(splits) + +for i, key in enumerate(pair_freqs.keys()): + print(f"{key}: {pair_freqs[key]}") + if i >= 5: + break +``` + +```python out +('T', 'h'): 3 +('h', 'i'): 3 +('i', 's'): 5 +('Ġ', 'i'): 2 +('Ġ', 't'): 7 +('t', 'h'): 3 +``` + +Acum, pentru a găsi cea mai frecventă pereche este nevoie doar de o loop rapid: + +```python +best_pair = "" +max_freq = None + +for pair, freq in pair_freqs.items(): + if max_freq is None or max_freq < freq: + best_pair = pair + max_freq = freq + +print(best_pair, max_freq) +``` + +```python out +('Ġ', 't') 7 +``` + +Așadar, prima îmbinare care trebuie învățată este `('Ġ', 't') -> 'Ġt'`, și adăugăm `'Ġt'` la vocabular: + +```python +merges = {("Ġ", "t"): "Ġt"} +vocab.append("Ġt") +``` + +Pentru a continua, trebuie să aplicăm aceast merge în dicționarul nostru `splits`. Să scriem o altă funcție pentru acest lucru: + +```python +def merge_pair(a, b, splits): + for word in word_freqs: + split = splits[word] + if len(split) == 1: + continue + + i = 0 + while i < len(split) - 1: + if split[i] == a and split[i + 1] == b: + split = split[:i] + [a + b] + split[i + 2 :] + else: + i += 1 + splits[word] = split + return splits +``` + +Și putem arunca o privire la rezultatul primului merge: + +```py +splits = merge_pair("Ġ", "t", splits) +print(splits["Ġtrained"]) +``` + +```python out +['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] +``` + +Acum avem tot ce ne trebuie pentru a face bucle până când vom învăța toate mergeu-rile dorite. Ne focusăm pe o mărime a vocabularui de 50: + +```python +vocab_size = 50 + +while len(vocab) < vocab_size: + pair_freqs = compute_pair_freqs(splits) + best_pair = "" + max_freq = None + for pair, freq in pair_freqs.items(): + if max_freq is None or max_freq < freq: + best_pair = pair + max_freq = freq + splits = merge_pair(*best_pair, splits) + merges[best_pair] = best_pair[0] + best_pair[1] + vocab.append(best_pair[0] + best_pair[1]) +``` + +Ca rezultat, am învățat 19 reguli de merge(vocabularul inițial avea o dimensiune de 31 -- 30 de caractere din alfabet, plus simbolul special): + +```py +print(merges) +``` + +```python out +{('Ġ', 't'): 'Ġt', ('i', 's'): 'is', ('e', 'r'): 'er', ('Ġ', 'a'): 'Ġa', ('Ġt', 'o'): 'Ġto', ('e', 'n'): 'en', + ('T', 'h'): 'Th', ('Th', 'is'): 'This', ('o', 'u'): 'ou', ('s', 'e'): 'se', ('Ġto', 'k'): 'Ġtok', + ('Ġtok', 'en'): 'Ġtoken', ('n', 'd'): 'nd', ('Ġ', 'is'): 'Ġis', ('Ġt', 'h'): 'Ġth', ('Ġth', 'e'): 'Ġthe', + ('i', 'n'): 'in', ('Ġa', 'b'): 'Ġab', ('Ġtoken', 'i'): 'Ġtokeni'} +``` + +Iar vocabularul este compus din simbolul special, alfabetul inițial și toate rezultatele merge-urilor: + +```py +print(vocab) +``` + +```python out +['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', + 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'Ġ', 'Ġt', 'is', 'er', 'Ġa', 'Ġto', 'en', 'Th', 'This', 'ou', 'se', + 'Ġtok', 'Ġtoken', 'nd', 'Ġis', 'Ġth', 'Ġthe', 'in', 'Ġab', 'Ġtokeni'] +``` + + + +💡 Folosind `train_new_from_iterator()` pe același corpus nu va rezulta exact același vocabular. Acest lucru se datorează faptului că atunci când există o alegere a celei mai frecvente perechi, am selectat-o pe prima întâlnită, în timp ce biblioteca 🤗 Tokenizers o selectează pe prima pe baza ID-urilor sale interne. + + + +Pentru a tokeniza un text nou, îl pre-tokenizăm, îl împărțim, apoi aplicăm toate regulile de merge învățate: + +```python +def tokenize(text): + pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in pre_tokenize_result] + splits = [[l for l in word] for word in pre_tokenized_text] + for pair, merge in merges.items(): + for idx, split in enumerate(splits): + i = 0 + while i < len(split) - 1: + if split[i] == pair[0] and split[i + 1] == pair[1]: + split = split[:i] + [merge] + split[i + 2 :] + else: + i += 1 + splits[idx] = split + + return sum(splits, []) +``` + +Putem încerca acest lucru pe orice text compus din caractere din alfabet: + +```py +tokenize("This is not a token.") +``` + +```python out +['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.'] +``` + + + +⚠️ Implementarea noastră va arunca o eroare dacă există un caracter necunoscut, deoarece nu am făcut nimic pentru a le gestiona. GPT-2 nu are de fapt un token necunoscut (este imposibil să obțineți un caracter necunoscut atunci când utilizați BPE la nivel de bytes), dar acest lucru s-ar putea întâmpla aici deoarece nu am inclus toate byte-urile posibile în vocabularul inițial. Acest aspect al BPE depășește domeniul de aplicare al acestei secțiuni, așa că am omis detaliile. + + + +Asta e tot pentru algoritmul BPE! În continuare, ne vom uita la WordPiece. \ No newline at end of file diff --git a/chapters/ro/chapter 6/6.mdx b/chapters/ro/chapter 6/6.mdx new file mode 100644 index 000000000..c2b0e6450 --- /dev/null +++ b/chapters/ro/chapter 6/6.mdx @@ -0,0 +1,375 @@ +# Tokenizarea WordPiece[[wordpiece-tokenization]] + + + +WordPiece este algoritmul de tokenizare dezvoltat de Google pentru preantrenarea BERT. De atunci, acesta a fost reutilizat în numeroase modele Transformers bazate pe BERT, cum ar fi DistilBERT, MobileBERT, Funnel Transformers și MPNET. Este foarte similar cu BPE în ceea ce privește antrenarea, dar tokenizarea efectivă se face diferit. + + + + + +💡 Această secțiune acoperă WordPiece în profunzime, mergând până la prezentarea unei implementări complete. Puteți sări la sfârșit dacă doriți doar o prezentare generală a algoritmului de tokenizare. + + + +## Algoritmul de antrenare[[training-algorithm]] + + + +⚠️ Google nu a publicat niciodată implementarea sa a algoritmului de formare a WordPiece, astfel încât ceea ce urmează este cea mai bună presupunere a noastră bazată pe literatura publicată. Este posibil să nu fie 100% exactă. + + + +La fel ca BPE, WordPiece pornește de la un vocabular restrâns care include simbolurile speciale utilizate de model și alfabetul inițial. Deoarece identifică subcuvinte prin adăugarea unui prefix (cum ar fi `##` pentru BERT), fiecare cuvânt este inițial împărțit prin adăugarea prefixului respectiv la toate caracterele din cuvânt. Astfel, de exemplu, `"word"` este împărțit astfel: + +``` +w ##o ##r ##d +``` + +Astfel, alfabetul inițial conține toate caracterele prezente la începutul unui cuvânt și caracterele prezente în interiorul unui cuvânt cu prefixul WordPiece. + +Apoi, la fel ca BPE, WordPiece învață reguli de merge. Principala diferență este modul în care este selectată perechea care urmează să fie merged. În loc să selecteze cea mai frecventă pereche, WordPiece calculează un scor pentru fiecare pereche, utilizând următoarea formulă: + +$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ + +Împărțind frecvența perechii la produsul frecvențelor fiecărei părți a acesteia, algoritmul prioritizează fuzionarea perechilor în care părțile individuale sunt mai puțin frecvente în vocabular. De exemplu, nu va fuziona neapărat `("un", "##able")` chiar dacă această pereche apare foarte frecvent în vocabular, deoarece cele două perechi `"un"` și `"##able"` vor apărea probabil fiecare într-o mulțime de alte cuvinte și vor avea o frecvență ridicată. În schimb, o pereche precum `("hu", "##gging")` va fi probabil fuzionată mai repede (presupunând că cuvântul "hugging" apare frecvent în vocabular), deoarece `"hu"` și `"##gging"` sunt probabil mai puțin frecvente individual. + +Să ne uităm la același vocabular pe care l-am folosit în exemplul de antrenare BPE: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +Spliturile aici vor fi: + +``` +("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) +``` + +deci vocabularul inițial va fi `["b", "h", "p", "##g", "##n", "##s", "##u"]` (dacă uităm deocamdată de tokenurile speciale). Cea mai frecventă pereche este `("##u", "##g")` (prezentă de 20 de ori), dar frecvența individuală a lui `"##u"` este foarte mare, astfel încât scorul său nu este cel mai mare (este 1 / 36). Toate perechile cu un `"##u"` au de fapt același scor (1 / 36), astfel încât cel mai bun scor revine perechii `("##g", "##s")` - singura fără un `"##u"` - cu 1 / 20, iar prima îmbinare învățată este `("##g", "##s") -> ("##gs")`. + +Rețineți că atunci când facem merge, eliminăm `##` dintre cele două tokenuri, deci adăugăm `"##gs"` la vocabular și aplicăm merge în cuvintele din corpus: + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"] +Corpus: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5) +``` + +În acest moment, `"##u"` se află în toate perechile posibile, deci toate au același scor. Să spunem că în acest caz, prima pereche este merged, deci `("h", "##u") -> "hu"`. Acest lucru ne duce la: + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu"] +Corpus: ("hu" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) +``` + +Apoi, următorul scor cu cel mai bun rezultat este împărțit de `("hu", "##g")` și `("hu", "##gs")` (cu 1/15, comparativ cu 1/21 pentru toate celelalte perechi), astfel încât prima pereche cu cel mai mare scor este merged + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"] +Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) +``` + +și continuăm astfel până când ajungem la dimensiunea dorită a vocabularului. + + + +✏️ **Acum e rândul tău!** Care va fi următoarea regulă de merge? + + + +## Algoritm de tokenizare[[tokenization-algorithm]] + +Tokenizarea diferă în WordPiece și BPE prin faptul că WordPiece salvează doar vocabularul final, nu și regulile de merge învățate. Pornind de la cuvântul de tokenizat, WordPiece găsește cel mai lung subcuvânt care se află în vocabular, apoi îl împarte. De exemplu, dacă folosim vocabularul învățat în exemplul de mai sus, pentru cuvântul `"hugs"` cel mai lung subcuvânt de la început care se află în vocabular este `"hug"`, așa că împărțim acolo și obținem `["hug", "##s"]`. Apoi continuăm cu `"##s"`, care se află în vocabular, deci tokenizarea lui `"hugs"` este `["hug", "##s"]`. + +Cu BPE, am fi aplicat mergeurile învățate în ordine și am fi tokenizat acest lucru ca `["hu", "##gs"]`, deci encodingul este diferit. + +Ca un alt exemplu, să vedem cum ar fi tokenizat cuvântul `"bugs"`. `"b"` este cel mai lung subcuvânt care începe de la începutul cuvântului care se află în vocabular, așa că îl împărțim acolo și obținem `["b", "##ugs"]`. Apoi, `"##u"` este cel mai lung subcuvânt care începe de la începutul cuvântului `"##ugs"` care se află în vocabular, deci îl separăm și obținem `["b", "##u, "##gs"]`. În cele din urmă, `"##gs"` se află în vocabular, deci această ultimă listă este tokenizarea lui `"bugs"`. + +Atunci când tokenizarea ajunge într-un stadiu în care nu este posibilă găsirea unui subcuvânt în vocabular, întregul cuvânt este tokenizat ca necunoscut - astfel, de exemplu, `"mug"` ar fi tokenizat ca `["[UNK]"]`, la fel ca `"bum"` (chiar dacă putem începe cu `"b"` și `"##u"`, `"##m"` nu face parte din vocabular, iar tokenizarea rezultată va fi `["[UNK]"]`, nu `["b", "##u", "[UNK]"]`). Aceasta este o altă diferență față de BPE, care ar clasifica doar caracterele individuale care nu se află în vocabular ca necunoscute. + + + +✏️ **Acum e rândul tău!** Cum va fi tokenizat cuvântul `"pugs"`? + + + +## Implementând WordPiece[[implementing-wordpiece]] + +Acum să aruncăm o privire la o implementare a algoritmului WordPiece. La fel ca în cazul BPE, acest lucru este doar pedagogic și nu veți putea să îl utilizați pe un corpus mare. + +Vom utiliza același corpus ca în exemplul BPE: + +```python +corpus = [ + "This is the Hugging Face Course.", + "This chapter is about tokenization.", + "This section shows several tokenizer algorithms.", + "Hopefully, you will be able to understand how they are trained and generate tokens.", +] +``` + +În primul rând, trebuie să pre-tokenizăm corpusul în cuvinte. Deoarece replicăm un tokenizator WordPiece (precum BERT), vom utiliza tokenizatorul `bert-base-cased` pentru pre-tokenizare: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Apoi, calculăm frecvențele fiecărui cuvânt din corpus la fel ca în cazul pre-tokenizării: + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +word_freqs +``` + +```python out +defaultdict( + int, {'This': 3, 'is': 2, 'the': 1, 'Hugging': 1, 'Face': 1, 'Course': 1, '.': 4, 'chapter': 1, 'about': 1, + 'tokenization': 1, 'section': 1, 'shows': 1, 'several': 1, 'tokenizer': 1, 'algorithms': 1, 'Hopefully': 1, + ',': 1, 'you': 1, 'will': 1, 'be': 1, 'able': 1, 'to': 1, 'understand': 1, 'how': 1, 'they': 1, 'are': 1, + 'trained': 1, 'and': 1, 'generate': 1, 'tokens': 1}) +``` + +După cum am văzut mai devreme, alfabetul este setul unic compus din toate primele litere ale cuvintelor și toate celelalte litere care apar în cuvintele cu prefixul `##`: + +```python +alphabet = [] +for word in word_freqs.keys(): + if word[0] not in alphabet: + alphabet.append(word[0]) + for letter in word[1:]: + if f"##{letter}" not in alphabet: + alphabet.append(f"##{letter}") + +alphabet.sort() +alphabet + +print(alphabet) +``` + +```python out +['##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', '##l', '##m', '##n', '##o', '##p', '##r', '##s', + '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', + 'w', 'y'] +``` + +De asemenea, adăugăm simbolurile speciale utilizate de model la începutul vocabularului respectiv. În cazul BERT, este vorba de lista `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]``: + +```python +vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() +``` + +Apoi trebuie să împărțim fiecare cuvânt, cu toate literele care nu sunt primele cu prefixul `##`: + +```python +splits = { + word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)] + for word in word_freqs.keys() +} +``` + +Acum că suntem pregătiți pentru antrenare, să scriem o funcție care calculează scorul fiecărei perechi. Va trebui să folosim această funcție la fiecare etapă a antrenării: + +```python +def compute_pair_scores(splits): + letter_freqs = defaultdict(int) + pair_freqs = defaultdict(int) + for word, freq in word_freqs.items(): + split = splits[word] + if len(split) == 1: + letter_freqs[split[0]] += freq + continue + for i in range(len(split) - 1): + pair = (split[i], split[i + 1]) + letter_freqs[split[i]] += freq + pair_freqs[pair] += freq + letter_freqs[split[-1]] += freq + + scores = { + pair: freq / (letter_freqs[pair[0]] * letter_freqs[pair[1]]) + for pair, freq in pair_freqs.items() + } + return scores +``` + +Să aruncăm o privire la o parte din acest dicționar după separările inițiale: + +```python +pair_scores = compute_pair_scores(splits) +for i, key in enumerate(pair_scores.keys()): + print(f"{key}: {pair_scores[key]}") + if i >= 5: + break +``` + +```python out +('T', '##h'): 0.125 +('##h', '##i'): 0.03409090909090909 +('##i', '##s'): 0.02727272727272727 +('i', '##s'): 0.1 +('t', '##h'): 0.03571428571428571 +('##h', '##e'): 0.011904761904761904 +``` + +Acum, pentru a găsi perechea cu cel mai bun scor este nevoie doar de un loop rapid: + +```python +best_pair = "" +max_score = None +for pair, score in pair_scores.items(): + if max_score is None or max_score < score: + best_pair = pair + max_score = score + +print(best_pair, max_score) +``` + +```python out +('a', '##b') 0.2 +``` + +Așadar, primul merge de învățat este `('a', '##b') -> 'ab'`, iar noi adăugăm `'ab'` la vocabular: + +```python +vocab.append("ab") +``` + +Pentru a continua, trebuie să aplicăm aceast merge în dicționarul nostru `splits`. Să scriem o altă funcție pentru acest lucru: + +```python +def merge_pair(a, b, splits): + for word in word_freqs: + split = splits[word] + if len(split) == 1: + continue + i = 0 + while i < len(split) - 1: + if split[i] == a and split[i + 1] == b: + merge = a + b[2:] if b.startswith("##") else a + b + split = split[:i] + [merge] + split[i + 2 :] + else: + i += 1 + splits[word] = split + return splits +``` + +Și putem arunca o privire la rezultatul primului merge: + +```py +splits = merge_pair("a", "##b", splits) +splits["about"] +``` + +```python out +['ab', '##o', '##u', '##t'] +``` + +Acum avem tot ce ne trebuie pentru a face bucle până când vom învăța toate merge-urile dorite. Să ne propunem un vocabular de mărimea 70: + +```python +vocab_size = 70 +while len(vocab) < vocab_size: + scores = compute_pair_scores(splits) + best_pair, max_score = "", None + for pair, score in scores.items(): + if max_score is None or max_score < score: + best_pair = pair + max_score = score + splits = merge_pair(*best_pair, splits) + new_token = ( + best_pair[0] + best_pair[1][2:] + if best_pair[1].startswith("##") + else best_pair[0] + best_pair[1] + ) + vocab.append(new_token) +``` + +Ne putem uita apoi la vocabularul generat: + +```py +print(vocab) +``` + +```python out +['[PAD]', '[UNK]', '[CLS]', '[SEP]', '[MASK]', '##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', + '##l', '##m', '##n', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', + 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', 'w', 'y', 'ab', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully', + 'Th', 'ch', '##hm', 'cha', 'chap', 'chapt', '##thm', 'Hu', 'Hug', 'Hugg', 'sh', 'th', 'is', '##thms', '##za', '##zat', + '##ut'] +``` + +După cum putem vedea, în comparație cu BPE, acest tokenizator învață părțile din cuvinte ca tokenuri puțin mai repede. + + + +💡 Folosind `train_new_from_iterator()` pe același corpus nu va rezulta exact același vocabular. Acest lucru se datorează faptului că biblioteca 🤗 Tokenizers nu implementează WordPiece pentru antrenare (deoarece nu suntem complet siguri cum funcționează intern), ci utilizează BPE în schimb. + + + +Pentru a tokeniza un text nou, îl pre-tokenizăm, îl împărțim, apoi aplicăm algoritmul de tokenizare pe fiecare cuvânt. Adică, căutăm cel mai mare subcuvânt începând de la începutul primului cuvânt și îl împărțim, apoi repetăm procesul pentru a doua parte și așa mai departe pentru restul acelui cuvânt și pentru următoarele cuvinte din text: + +```python +def encode_word(word): + tokens = [] + while len(word) > 0: + i = len(word) + while i > 0 and word[:i] not in vocab: + i -= 1 + if i == 0: + return ["[UNK]"] + tokens.append(word[:i]) + word = word[i:] + if len(word) > 0: + word = f"##{word}" + return tokens +``` + +Haideți să-l testăm pe un cuvânt care se află în vocabular și pe altul care nu se află: + + +```python +print(encode_word("Hugging")) +print(encode_word("HOgging")) +``` + +```python out +['Hugg', '##i', '##n', '##g'] +['[UNK]'] +``` + +Acum, scriem o funcție care tokenizează un text: + +```python +def tokenize(text): + pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in pre_tokenize_result] + encoded_words = [encode_word(word) for word in pre_tokenized_text] + return sum(encoded_words, []) +``` + +Îl putem încerca pe orice text: + +```python +tokenize("This is the Hugging Face course!") +``` + +```python out +['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s', + '##e', '[UNK]'] +``` + +Asta e tot pentru algoritmul WordPiece! Acum să aruncăm o privire la Unigram. diff --git a/chapters/ro/chapter 6/7.mdx b/chapters/ro/chapter 6/7.mdx new file mode 100644 index 000000000..14744a854 --- /dev/null +++ b/chapters/ro/chapter 6/7.mdx @@ -0,0 +1,381 @@ +# Tokenizarea Unigram[[unigram-tokenization]] + + + +Algoritmul Unigram este adesea utilizat în SentencePiece, care este algoritmul de tokenizare utilizat de modele precum AlBERT, T5, mBART, Big Bird și XLNet. + + + + + +💡 Această secțiune acoperă Unigram în profunzime, mergând până la prezentarea unei implementări complete. Puteți sări la sfârșit dacă doriți doar o prezentare generală a algoritmului de tokenizare. + + + +## Algoritm de antrenare[[training-algorithm]] + +În comparație cu BPE și WordPiece, Unigram lucrează în cealaltă direcție: pornește de la un vocabular mare și elimină tokeni din acesta până când ajunge la dimensiunea dorită. Există mai multe opțiuni pentru a construi acel vocabular de bază: putem lua, de exemplu, cele mai comune substrings din cuvintele pre-tokenizate sau putem aplica BPE pe corpusul inițial cu o dimensiune mare a vocabularului. + +La fiecare etapă a antrenării, algoritmul Unigram calculează o pierdere pe corpus oferit, având în vedere vocabularul curent. Apoi, pentru fiecare simbol din vocabular, algoritmul calculează cu cât ar crește pierderea globală dacă simbolul ar fi eliminat și caută simbolurile care ar crește cel mai puțin pierderea. Aceste simboluri au cel mai redus efect asupra pierderii globale din corpus, deci, într-un fel, sunt "mai puțin necesare" și sunt cei mai buni candidați pentru eliminare. + +Aceasta este o operațiune foarte costisitoare, așa că nu eliminăm doar simbolul asociat cu cea mai mică creștere a pierderii, ci procentul \\(p\\) (\\(p\\) fiind un hyperparameter pe care îl poți controla, de obicei 10 sau 20) din simbolurile asociate cu cea mai mică creștere a pierderilor. Acest proces este se repetă până când vocabularul atinge dimensiunea dorită. + +Rețineți că nu eliminăm niciodată caracterele de bază, pentru a ne asigura că orice cuvânt poate fi tokenizat. + +Acum, acest lucru este încă puțin vag: partea principală a algoritmului este de a calcula o pierdere asupra corpusului și de a vedea cum se schimbă atunci când eliminăm unele tokenuri din vocabular, dar nu am explicat încă cum să facem acest lucru. Acest pas se bazează pe algoritmul de tokenizare al unui model Unigram, așa că îl vom analiza în continuare. + +Vom reutiliza corpusul din exemplele anterioare: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +iar pentru acest exemplu, vom lua toate substringurile stricte pentru vocabularul inițial: + +``` +["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] +``` + +## Algoritm de tokenizare[[tokenization-algorithm]] + +Un model Unigram este un tip de model lingvistic care consideră că fiecare token este independent de tokenii anteriori. Este cel mai simplu model lingvistic, în sensul că probabilitatea simbolului X având în vedere contextul anterior este doar probabilitatea simbolului X. Astfel, dacă am utiliza un model lingvistic Unigram pentru a genera text, am prezice întotdeauna simbolul cel mai frecvent. + +Probabilitatea unui token dat este frecvența sa (numărul de ori în care îl găsim) în corpusul original, împărțită la suma tuturor aparițiilor tuturor tokenilor din vocabular (pentru a ne asigura că probabilitățile sunt egale cu 1). De exemplu, `"ug"` este prezent în `"hug"`, `"pug"`, și `"hugs"`, deci are o frecvență de 20 în corpus-ul nostru. + +Iată frecvențele tuturor subcuvintelor posibile din vocabular: + +``` +("h", 15) ("u", 36) ("g", 20) ("hu", 15) ("ug", 20) ("p", 17) ("pu", 17) ("n", 16) +("un", 16) ("b", 4) ("bu", 4) ("s", 5) ("hug", 15) ("gs", 5) ("ugs", 5) +``` + +Astfel, suma tuturor frecvențelor este 210, iar probabilitatea subcuvântului `"ug"` este 20/210. + + + +✏️ **Acum este rândul tău!** Scrie codul pentru a calcula frecvențele de mai sus și verifică de două ori dacă rezultatele afișate sunt corecte, precum și suma totală. + + + +Acum, pentru a tokeniza un cuvânt dat, ne uităm la toate segmentările posibile în tokeni și calculăm probabilitatea fiecăruia în conformitate cu modelul Unigram. Deoarece toate token-urile sunt considerate independente, această probabilitate este doar produsul probabilității fiecărui token. De exemplu, tokenizarea `["p", "u", "g"]` a lui `"pug"` are probabilitatea: + +$$P([``p", ``u", ``g"]) = P(``p") \times P(``u") \times P(``g") = \frac{5}{210} \times \frac{36}{210} \times \frac{20}{210} = 0.000389$$ + +Comparativ, tokenizarea `["pu", "g"]` are probabilitatea: + +$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$ + +astfel încât una este mult mai probabilă decât alta. În general, tokenizările cu cei mai puțini tokeni posibili vor avea cea mai mare probabilitate (din cauza acelei împărțiri la 210 repetată pentru fiecare token), ceea ce corespunde cu ceea ce dorim intuitiv: să împărțim un cuvânt în cel mai mic număr de tokenuri posibil. + +Tokenizarea unui cuvânt cu modelul Unigram este atunci tokenizarea cu cea mai mare probabilitate. În exemplul `"pug"`, iată probabilitățile pe care le-am obține pentru fiecare segmentare posibilă: + +``` +["p", "u", "g"] : 0.000389 +["p", "ug"] : 0.0022676 +["pu", "g"] : 0.0022676 +``` + +Astfel, `"pug"` ar fi tokenizat ca `["p", "ug"]` sau `["pu", "g"]`, în funcție de care dintre aceste segmentări este întâlnită prima (rețineți că într-un corpus mai mare, cazurile de egalitate ca acesta vor fi rare). + +În acest caz, a fost ușor să găsim toate segmentările posibile și să le calculăm probabilitățile, dar în general va fi puțin mai greu. Există un algoritm clasic utilizat pentru acest lucru, numit *algoritmul Viterbi*. În esență, putem construi un grafic pentru a detecta segmentările posibile ale unui cuvânt dat, spunând că există o ramură de la caracterul _a_ la caracterul _b_ dacă subcuvântul de la _a_ la _b_ se află în vocabular, și atribuind ramurii respective probabilitatea subcuvântului. + +Pentru a găsi calea din acest grafic care va avea cel mai bun scor, algoritmul Viterbi determină, pentru fiecare poziție din cuvânt, segmentarea cu cel mai bun scor care se termină la poziția respectivă. Deoarece mergem de la început la sfârșit, cel mai bun scor poate fi găsit prin parcurgerea în buclă a tuturor subcuvintelor care se termină la poziția curentă și apoi folosind cel mai bun scor de tokenizare de la poziția la care începe acest subcuvânt. Apoi, trebuie doar să derulăm calea parcursă pentru a ajunge la sfârșit. + +Să aruncăm o privire la un exemplu folosind vocabularul nostru și cuvântul `"unhug"`. Pentru fiecare poziție, subcuvintele cu cele mai bune scoruri care se termină acolo sunt următoarele: + +``` +Character 0 (u): "u" (score 0.171429) +Character 1 (n): "un" (score 0.076191) +Character 2 (h): "un" "h" (score 0.005442) +Character 3 (u): "un" "hu" (score 0.005442) +Character 4 (g): "un" "hug" (score 0.005442) +``` + +Astfel, `"unhug"` ar fi tokenizat ca `["un", "hug"]`. + + + +✏️ **Acum e rândul tău!** Determinați tokenizarea cuvântului `"huggun"` și scorul acestuia. + + + +## Înapoi la antrenare[[back-to-training]] + +Acum că am văzut cum funcționează tokenizarea, putem analiza mai în profunzime pierderea utilizată în timpul antrenării. În orice etapă dată, această pierdere este calculată prin tokenizarea fiecărui cuvânt din corpus, utilizând vocabularul curent și modelul Unigram determinat de frecvențele fiecărui token din corpus (după cum am văzut mai devreme). + +Fiecare cuvânt din corpus are un scor, iar pierderea este negative log likelihood a acestor scoruri - adică suma pentru toate cuvintele din corpus a tuturor `-log(P(word))`. + +Să ne întoarcem la exemplul nostru cu următorul corpus: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +Tokenizarea fiecărui cuvânt cu scorurile lor respective este: + +``` +"hug": ["hug"] (score 0.071428) +"pug": ["pu", "g"] (score 0.007710) +"pun": ["pu", "n"] (score 0.006168) +"bun": ["bu", "n"] (score 0.001451) +"hugs": ["hug", "s"] (score 0.001701) +``` + +Deci, pierderea este: + +``` +10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 +``` + +Acum trebuie să calculăm modul în care eliminarea fiecărui token afectează pierderea. Acest lucru este destul de plictisitor, așa că îl vom face doar pentru doi tokeni aici și vom păstra întregul proces pentru atunci când vom avea cod care să ne ajute. În acest caz (foarte) special, aveam două tokenizări echivalente ale tuturor cuvintelor: după cum am văzut mai devreme, de exemplu, `"pug"` ar putea fi tokenizat `["p", "ug"]` cu același scor. Astfel, eliminarea simbolului `"pu"` din vocabular va produce exact aceeași pierdere. + +Pe de altă parte, eliminarea lui `"hug"` va agrava pierderea, deoarece tokenizarea lui `"hug"` și `"hugs"` va deveni: + +``` +"hug": ["hu", "g"] (score 0.006802) +"hugs": ["hu", "gs"] (score 0.001701) +``` + +Aceste modificări vor determina creșterea pierderii cu: + +``` +- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 +``` + +Prin urmare, tokenul `"pu"` va fi probabil eliminat din vocabular, dar nu și `"hug"`. + +## Implementarea Unigram[[implementarea-unigram]] + +Acum să implementăm în cod tot ceea ce am văzut până acum. Ca și în cazul BPE și WordPiece, aceasta nu este o implementare eficientă a algoritmului Unigram (dimpotrivă), dar ar trebui să vă ajute să-l înțelegeți puțin mai bine. + +Vom folosi ca exemplu același corpus ca și până acum: + +```python +corpus = [ + "This is the Hugging Face Course.", + "This chapter is about tokenization.", + "This section shows several tokenizer algorithms.", + "Hopefully, you will be able to understand how they are trained and generate tokens.", +] +``` + +De data aceasta, vom folosi `xlnet-base-cased` ca modelul nostru: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") +``` + +Ca și pentru BPE și WordPiece, începem prin a număra numărul de apariții ale fiecărui cuvânt în corpus: + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +word_freqs +``` + +Apoi, trebuie să inițializăm vocabularul nostru la ceva mai mare decât dimensiunea vocabularului pe care o vom dori la final. Trebuie să includem toate caracterele de bază (altfel nu vom putea tokeniza fiecare cuvânt), dar pentru substringurile mai mari le vom păstra doar pe cele mai comune, așa că le vom sorta după frecvență: + +```python +char_freqs = defaultdict(int) +subwords_freqs = defaultdict(int) +for word, freq in word_freqs.items(): + for i in range(len(word)): + char_freqs[word[i]] += freq + # Loop through the subwords of length at least 2 + for j in range(i + 2, len(word) + 1): + subwords_freqs[word[i:j]] += freq + +# Sortarea subcuvintelor după frecvență +sorted_subwords = sorted(subwords_freqs.items(), key=lambda x: x[1], reverse=True) +sorted_subwords[:10] +``` + +```python out +[('▁t', 7), ('is', 5), ('er', 5), ('▁a', 5), ('▁to', 4), ('to', 4), ('en', 4), ('▁T', 3), ('▁Th', 3), ('▁Thi', 3)] +``` + +Grupăm caracterele cu cele mai bune subcuvinte pentru a ajunge la un vocabular inițial de dimensiunea 300: + +```python +token_freqs = list(char_freqs.items()) + sorted_subwords[: 300 - len(char_freqs)] +token_freqs = {token: freq for token, freq in token_freqs} +``` + + + +💡 SentencePiece utilizează un algoritm mai eficient numit Enhanced Suffix Array (ESA) pentru a crea vocabularul inițial. + + + +În continuare, calculăm suma tuturor frecvențelor, pentru a converti frecvențele în probabilități. Pentru modelul nostru, vom stoca logaritmii probabilităților, deoarece este mai stabil din punct de vedere numeric să adăugăm logaritmi decât să multiplicăm numere mici, iar acest lucru va simplifica calcularea pierderii modelului: + +```python +from math import log + +total_sum = sum([freq for token, freq in token_freqs.items()]) +model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} +``` + +Acum funcția principală este cea care tokenizează cuvintele folosind algoritmul Viterbi. După cum am văzut mai devreme, acest algoritm calculează cea mai bună segmentare a fiecărui substringur din cuvânt, pe care o vom stoca într-o variabilă numită `best_segmentations`. Vom stoca un dicționar pentru fiecare poziție din cuvânt (de la 0 la lungimea totală a acestuia), cu două chei: indicele de început al ultimului token din cea mai bună segmentare și scorul celei mai bune segmentări. Cu ajutorul indicelui de început al ultimului token, vom putea extrage segmentarea completă odată ce lista este complet populată. + +Popularea listei se face cu doar două bucle: bucla principală trece peste fiecare poziție de început, iar a doua bucla încearcă toate subcuvintele care încep la acea poziție de început. Dacă substringul se află în vocabular, avem o nouă segmentare a cuvântului până la acea poziție finală, pe care o comparăm cu cea din `best_segmentations`. + +Odată ce bucla principală este terminată, pornim de la sfârșit și sărim de la o poziție de început la alta, înregistrând tokenii pe parcurs, până când ajungem la începutul cuvântului: + +```python +def encode_word(word, model): + best_segmentations = [{"start": 0, "score": 1}] + [ + {"start": None, "score": None} for _ in range(len(word)) + ] + for start_idx in range(len(word)): + # This should be properly filled by the previous steps of the loop + best_score_at_start = best_segmentations[start_idx]["score"] + for end_idx in range(start_idx + 1, len(word) + 1): + token = word[start_idx:end_idx] + if token in model and best_score_at_start is not None: + score = model[token] + best_score_at_start + # If we have found a better segmentation ending at end_idx, we update + if ( + best_segmentations[end_idx]["score"] is None + or best_segmentations[end_idx]["score"] > score + ): + best_segmentations[end_idx] = {"start": start_idx, "score": score} + + segmentation = best_segmentations[-1] + if segmentation["score"] is None: + # We did not find a tokenization of the word -> unknown + return [""], None + + score = segmentation["score"] + start = segmentation["start"] + end = len(word) + tokens = [] + while start != 0: + tokens.insert(0, word[start:end]) + next_start = best_segmentations[start]["start"] + end = start + start = next_start + tokens.insert(0, word[start:end]) + return tokens, score +``` + +Putem încerca deja modelul nostru inițial pe câteva cuvinte: + +```python +print(encode_word("Hopefully", model)) +print(encode_word("This", model)) +``` + +```python out +(['H', 'o', 'p', 'e', 'f', 'u', 'll', 'y'], 41.5157494601402) +(['This'], 6.288267030694535) +``` + +Acum este ușor de calculat pierderea modelului pe corpus! + +```python +def compute_loss(model): + loss = 0 + for word, freq in word_freqs.items(): + _, word_loss = encode_word(word, model) + loss += freq * word_loss + return loss +``` + +Putem verifica dacă funcționează pe modelul pe care îl avem: + +```python +compute_loss(model) +``` + +```python out +413.10377642940875 +``` + +Nici calcularea scorurilor pentru fiecare token nu este foarte dificilă; trebuie doar să calculăm pierderea pentru modelele obținute prin ștergerea fiecărui tokeb: + +```python +import copy + + +def compute_scores(model): + scores = {} + model_loss = compute_loss(model) + for token, score in model.items(): + # We always keep tokens of length 1 + if len(token) == 1: + continue + model_without_token = copy.deepcopy(model) + _ = model_without_token.pop(token) + scores[token] = compute_loss(model_without_token) - model_loss + return scores +``` + +Îl putem încerca pe un token dat: + +```python +scores = compute_scores(model) +print(scores["ll"]) +print(scores["his"]) +``` + +Deoarece `"ll"` este folosit în tokenizarea lui `"Hopefully"`, iar eliminarea lui ne va face, probabil, să folosim tokenul `"l"` de două ori în schimb, ne așteptăm să aibă o pierdere pozitivă. `"his"` este folosit doar în interiorul cuvântului `"This"`, care este tokenizat ca el însuși, deci ne așteptăm să aibă o pierdere zero. Iată rezultatele: + +```python out +6.376412403623874 +0.0 +``` + + + +💡 Această abordare este foarte ineficientă, astfel încât SentencePiece utilizează o aproximare a pierderii modelului fără simbolul X: în loc să înceapă de la zero, înlocuiește simbolul X cu segmentarea sa în vocabularul rămas. În acest fel, toate scorurile pot fi calculate odată, în același timp cu pierderea modelului. + + + +Cu toate acestea la locul lor, ultimul lucru pe care trebuie să îl facem este să adăugăm la vocabular tokeni speciali utilizate de model, apoi să facem o buclă până când am eliminat suficienți tokeni din vocabular pentru a ajunge la dimensiunea dorită: + +```python +percent_to_remove = 0.1 +while len(model) > 100: + scores = compute_scores(model) + sorted_scores = sorted(scores.items(), key=lambda x: x[1]) + # Remove percent_to_remove tokens with the lowest scores. + for i in range(int(len(model) * percent_to_remove)): + _ = token_freqs.pop(sorted_scores[i][0]) + + total_sum = sum([freq for token, freq in token_freqs.items()]) + model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} +``` + +Apoi, pentru a tokeniza un text, trebuie doar să aplicăm pre-tokenizarea și apoi să folosim funcția `encode_word()`: + +```python +def tokenize(text, model): + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in words_with_offsets] + encoded_words = [encode_word(word, model)[0] for word in pre_tokenized_text] + return sum(encoded_words, []) + + +tokenize("This is the Hugging Face course.", model) +``` + +```python out +['▁This', '▁is', '▁the', '▁Hugging', '▁Face', '▁', 'c', 'ou', 'r', 's', 'e', '.'] +``` + +Asta e tot pentru Unigram! Sperăm că până acum vă simțiți ca un expert în toate lucrurile legate de tokenizer. În secțiunea următoare, vom aprofunda elementele de bază ale bibliotecii 🤗 Tokenizers și vă vom arăta cum le puteți utiliza pentru a vă construi propriul tokenizer. diff --git a/chapters/ro/chapter 6/8.mdx b/chapters/ro/chapter 6/8.mdx new file mode 100644 index 000000000..4e41c84cb --- /dev/null +++ b/chapters/ro/chapter 6/8.mdx @@ -0,0 +1,566 @@ +# Construirea unui tokenizer, bloc cu bloc[[building-a-tokenizer-block-by-block]] + + + +După cum am văzut în secțiunile anterioare, tokenizarea cuprinde mai multe etape: + +- Normalizare (orice curățare a textului care este considerată necesară, cum ar fi eliminarea spațiilor sau a accentelor, normalizarea Unicode etc.) +- Pre-tokenizarea (împărțirea inputului în cuvinte) +- Rularea inputului prin model (utilizarea cuvintelor pre-tokenizate pentru a produce o secvență de tokeni) +- Post-procesare (adăugarea tokenilor speciali ale tokenizerului, generarea attention maskului și a ID-urilor de tip token) + +Ca un reminder, iată o altă perspectivă asupra procesului general: + +
+The tokenization pipeline. + +
+ +Biblioteca 🤗 Tokenizers a fost construită pentru a oferi mai multe opțiuni pentru fiecare dintre acești pași, pe care le puteți amesteca și combina împreună. În această secțiune vom vedea cum putem construi un tokenizer de la zero, spre deosebire de antrenarea unui tokenizer nou dintr-unul vechi, așa cum am făcut în [secțiunea 2](/course/chapter6/2). Veți putea apoi să construiți orice fel de tokenizer la care vă puteți gândi! + + + +Mai exact, biblioteca este construită în jurul unei clase centrale `Tokenizer` cu building grupate în submodule: + +- `normalizers` conține toate tipurile posibile de `Normalizer` pe care le puteți folosi (lista completă [aici](https://huggingface.co/docs/tokenizers/api/normalizers)). +- `pre_tokenizers` coține toate tipurile de `PreTokenizer` pe care le poți folosi(lista completă [aici](https://huggingface.co/docs/tokenizers/api/pre-tokenizers)). +- `models` conține diferitele tipuri de `Model` pe care le puteți folosi, precum `BPE`, `WordPiece` și `Unigram` (lista completă [aici](https://huggingface.co/docs/tokenizers/api/models)). +- `trainers` conține toate tipurile diferite de `Trainer` pe care le puteți folosi pentru a vă antrena modelul pe un corpus (unul pentru fiecare tip de model; lista completă [aici](https://huggingface.co/docs/tokenizers/api/trainers)). +- `post_processors` conține diferitele tipuri de `PostProcessor` pe care le puteți utiliza (lista completă [aici](https://huggingface.co/docs/tokenizers/api/post-processors)). +- `decoders` conține diferitele tipuri de `Decoder` pe care le puteți utiliza pentru a decoda rezultatele tokenizării (lista completă [aici](https://huggingface.co/docs/tokenizers/components#decoders)). + +Puteți găsi întreaga listă de blocuri [aici](https://huggingface.co/docs/tokenizers/components). + +## Obținerea unui corpus[[acquiring-a-corpus]] + +Pentru a antrena noul nostru tokenizer, vom utiliza un corpus mic de text (astfel încât exemplele să ruleze rapid). Pașii pentru obținerea corpusului sunt similari cu cei pe care i-am urmat la [începutul acestui capitol](/course/chapter6/2), dar de data aceasta vom utiliza datasetul [WikiText-2](https://huggingface.co/datasets/wikitext): + +```python +from datasets import load_dataset + +dataset = load_dataset("wikitext", name="wikitext-2-raw-v1", split="train") + + +def get_training_corpus(): + for i in range(0, len(dataset), 1000): + yield dataset[i : i + 1000]["text"] +``` + +Funcția `get_training_corpus()` este un generator care va produce batch-uri de 1 000 de texte, pe care le vom utiliza pentru a antrena tokenizerul. + +🤗 Tokenizers pot fi, de asemenea, antrenate direct pe fișiere text. Iată cum putem genera un fișier text care să conțină toate textele/inputurile din WikiText-2 pe care le putem utiliza local: + +```python +with open("wikitext-2.txt", "w", encoding="utf-8") as f: + for i in range(len(dataset)): + f.write(dataset[i]["text"] + "\n") +``` + +În continuare vă vom arăta cum să vă construiți propriile tokenizere BERT, GPT-2 și XLNet, bloc cu bloc. Acest lucru ne va oferi un exemplu pentru fiecare dintre cei trei algoritmi principali de tokenizare: WordPiece, BPE și Unigram. Să începem cu BERT! + +## Construirea unui tokenizator WordPiece de la zero[[building-a-wordpiece-tokenizer-from-scratch]] + +Pentru a construi un tokenizer cu biblioteca 🤗 Tokenizers, începem prin a inițializa un obiect `Tokenizer` cu un `model`, apoi îi setăm atributele `normalizer`, `pre_tokenizer`, `post_processor` și `decoder` la valorile dorite. + +Pentru acest exemplu, vom crea un `Tokenizer` cu un model WordPiece: + +```python +from tokenizers import ( + decoders, + models, + normalizers, + pre_tokenizers, + processors, + trainers, + Tokenizer, +) + +tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) +``` + +Trebuie să specificăm `unk_token` astfel încât modelul să știe ce să returneze atunci când întâlnește caractere necunoscute. Alte argumente pe care le putem seta aici includ `vocab` al modelului nostru (vom antrena modelul, deci nu este nevoie să setăm acest lucru) și `max_input_chars_per_word`, care specifică o lungime maximă pentru fiecare cuvânt (cuvintele mai lungi decât valoarea trecută vor fi divizate). + +Primul pas al tokenizării este normalizarea, așa că să începem cu aceasta. Deoarece BERT este utilizat pe scară largă, există un `BertNormalizer` cu opțiunile clasice pe care le putem seta pentru BERT: `lowercase` și `strip_accents`, care se explică de la sine; `clean_text` pentru a elimina toate caracterele de control și a înlocui spațiile repetate cu unul singur; și `handle_chinese_chars`, care plasează spații în jurul caracterelor chinezești. Pentru a replica tokenizerul `bert-base-uncased`, putem seta doar acest normalizator: + +```python +tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) +``` + +În general, atunci când construiți un nou tokenizer, nu veți avea acces la un normalizator atât de util, deja implementat în biblioteca 🤗 Tokenizers - așa că să vedem cum să creăm manual normalizatorul BERT. Biblioteca oferă un normalizator `Lowercase` și un normalizator `StripAccents` și puteți compune mai multe normalizatoare folosind un `Sequence`: + +```python +tokenizer.normalizer = normalizers.Sequence( + [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] +) +``` + +De asemenea, folosim un normalizator Unicode `NFD`, deoarece în caz contrar normalizatorul `StripAccents` nu va recunoaște corect caracterele accentuate și astfel nu le va elimina. + +După cum am mai văzut, putem folosi metoda `normalize_str()` a `normalizer` pentru a verifica efectele pe care le are asupra unui text dat: + +```python +print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +hello how are u? +``` + + + +**Pentru a merge mai departe** Dacă testați cele două versiuni ale normalizatorilor anteriori pe un șir care conține caracterul Unicode `u"\u0085"` veți observa cu siguranță că acești doi normalizatori nu sunt exact echivalenți. +Pentru a nu complica prea mult versiunea cu `normalizers.Sequence` , nu am inclus înlocuirile Regex pe care `BertNormalizer` le cere atunci când argumentul `clean_text` este setat la `True` - care este comportamentul implicit. Dar nu vă faceți griji: este posibil să obțineți exact aceeași normalizare fără a utiliza utilul `BertNormalizer` prin adăugarea a două `normalizers.Replace` la secvența normalizers. + + + + +Urmează etapa de pre-tokenizare. Din nou, există un `BertPreTokenizer` pre-construit pe care îl putem utiliza: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() +``` + +Sau îl putem construi de la zero: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() +``` + +Rețineți că pre-tokenizatorul `Whitespace` separă spațiul și toate caracterele care nu sunt litere, cifre sau caracterul underscore, deci tehnic separă spațiul și punctuația: + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), + ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] +``` + +Dacă doriți să separați doar spațiile, ar trebui să utilizați în schimb pre-tokenizerul `WhitespaceSplit`: + +```python +pre_tokenizer = pre_tokenizers.WhitespaceSplit() +pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[("Let's", (0, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre-tokenizer.', (14, 28))] +``` + +Ca și în cazul normalizatorilor, puteți utiliza un `Sequence` pentru a compune mai mulți pre-tokenizeri: + +```python +pre_tokenizer = pre_tokenizers.Sequence( + [pre_tokenizers.WhitespaceSplit(), pre_tokenizers.Punctuation()] +) +pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), + ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] +``` + +Următorul pas în pipelineul de tokenizare este rularea inputurilor prin model. Am specificat deja modelul nostru în inițializare, dar mai trebuie să îl antrenăm, ceea ce va necesita un `WordPieceTrainer`. Principalul lucru de reținut atunci când inițializați un trainer în 🤗 Tokenizers este că trebuie să îi transmiteți toți tokenii speciali pe care intenționați să îi utilizați - în caz contrar, acesta nu le va adăuga la vocabular, deoarece acestea nu se află în corpusul de antrenare: + +```python +special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] +trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) +``` + +Pe lângă specificarea `vocab_size` și `special_tokens`, putem seta `min_frequency` (numărul de ori în care un simbol trebuie să apară pentru a fi inclus în vocabular) sau putem schimba `continuing_subword_prefix` (dacă dorim să folosim ceva diferit de `##`). + +Pentru a antrena modelul nostru folosind iteratorul pe care l-am definit anterior, trebuie doar să executăm această comandă: + +```python +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +De asemenea, putem utiliza fișiere text pentru a ne antrena tokenizerul, care ar arăta astfel (în prealabil, reinițializăm modelul cu un `WordPiece` gol): + +```python +tokenizer.model = models.WordPiece(unk_token="[UNK]") +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +În ambele cazuri, putem apoi testa tokenizerul pe un text prin apelarea metodei `encode()`: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] +``` + +`encoding`-ul obținut este un `Encoding`, care conține toate rezultatele necesare ale tokenizerului în diferitele sale atribute: `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask`, și `overflowing`. + +Ultimul pas în pipelineul de tokenizare este postprocesarea. Trebuie să adăugăm tokenul `[CLS]` la început și tokenul `[SEP]` la sfârșit (sau după fiecare propoziție, dacă avem o pereche de propoziții). Vom folosi un `TemplateProcessor` pentru aceasta, dar mai întâi trebuie să cunoaștem ID-urile tokenilor `[CLS]` și `[SEP]` din vocabular: + +```python +cls_token_id = tokenizer.token_to_id("[CLS]") +sep_token_id = tokenizer.token_to_id("[SEP]") +print(cls_token_id, sep_token_id) +``` + +```python out +(2, 3) +``` + +Pentru a scrie templateul pentru `TemplateProcessor`, trebuie să specificăm cum să tratăm o singură propoziție și o pereche de propoziții. Pentru ambele, scriem tokeni speciali pe care dorim să îi folosim; prima (sau singura) propoziție este reprezentată de `$A`, în timp ce a doua propoziție (dacă facem encoding unei perechi) este reprezentată de `$B`. Pentru fiecare dintre acestea (tokeni speciali și propoziții), specificăm și ID-ul tipului de token corespunzător după două puncte. + +Modelul clasic BERT este astfel definit după cum urmează: + +```python +tokenizer.post_processor = processors.TemplateProcessing( + single=f"[CLS]:0 $A:0 [SEP]:0", + pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1", + special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)], +) +``` + +Rețineți că trebuie să transmitem ID-urile tokenilor speciali, astfel încât tokenizerul să le poată converti corect în ID-urile lor. + +Odată ce acest lucru este adăugat, revenind la exemplul nostru anterior vom obține: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] +``` + +Și pe o pereche de propoziții, obținem rezultatul corect: + +```python +encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences.") +print(encoding.tokens) +print(encoding.type_ids) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '...', '[SEP]', 'on', 'a', 'pair', 'of', 'sentences', '.', '[SEP]'] +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +Aproape am terminat de construit acest tokenizer de la zero - ultimul pas este să includem un decodor: + +```python +tokenizer.decoder = decoders.WordPiece(prefix="##") +``` + +Haideți să-l testăm pe `encoding`-ul nostru anterior: + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"let's test this tokenizer... on a pair of sentences." +``` + +Grozav! Putem salva tokenizatorul nostru într-un singur fișier JSON, astfel: + +```python +tokenizer.save("tokenizer.json") +``` + +Apoi putem reîncărca acel fișier într-un obiect `Tokenizer` cu metoda `from_file()`: + +```python +new_tokenizer = Tokenizer.from_file("tokenizer.json") +``` + +Pentru a utiliza acest tokenizer în 🤗 Transformers, trebuie să îl încorporăm în `PreTrainedTokenizerFast`. Putem fie să folosim clasa generică, fie, dacă tokenizerul nostru corespunde unui model existent, să folosim clasa respectivă (aici, `BertTokenizerFast`). Dacă aplicați această lecție pentru a construi un tokenizer nou, va trebui să utilizați prima opțiune. + +Pentru a include tokenizatorul într-un `PreTrainedTokenizerFast`, putem fie să transmitem tokenizerul construit ca `tokenizer_object`, fie să transmitem fișierul tokenizerului salvat ca `tokenizer_file`. Cel mai important lucru de reținut este că trebuie să setăm manual toți tokenii speciali, deoarece această clasă nu poate deduce din obiectul `tokenizer` care tokne este tokenul mască, tokenul `[CLS]`, etc.: + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + # tokenizer_file="tokenizer.json", # You can load from the tokenizer file, alternatively + unk_token="[UNK]", + pad_token="[PAD]", + cls_token="[CLS]", + sep_token="[SEP]", + mask_token="[MASK]", +) +``` + +Dacă utilizați o clasă specifică de tokenizer (cum ar fi `BertTokenizerFast`), va trebui să specificați doar tokenii speciali care sunt diferiți de cei impliciți (aici, niciunul): + +```python +from transformers import BertTokenizerFast + +wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) +``` + +Apoi puteți utiliza acest tokenizer ca orice alt tokenizer 🤗 Transformers. Îl puteți salva cu metoda `save_pretrained()` sau îl puteți încărca în Hub cu metoda `push_to_hub()`. + +Acum că am văzut cum să construim un tokenizer WordPiece, hai să facem același lucru pentru un tokenizer BPE. Vom merge un pic mai repede, deoarece cunoașteți toți pașii, și vom evidenția doar diferențele. + +## Construirea unui tokenizer BPE de la zero[[building-a-bpe-tokenizer-from-scratch]] + +Să construim acum un tokenizer GPT-2. Ca și pentru tokenizer BERT, începem prin inițializarea unui `Tokenizer` cu un model BPE: + +```python +tokenizer = Tokenizer(models.BPE()) +``` + +De asemenea, la fel ca în cazul BERT, am putea inițializa acest model cu un vocabular, dacă am avea unul (în acest caz, ar trebui să oferim `vocab` și `merges`), dar din moment ce vom antrena de la zero, nu avem nevoie să facem acest lucru. De asemenea, nu trebuie să specificăm un `unk_token` deoarece GPT-2 utilizează BPE la nivel de bytes, care nu necesită acest lucru. + +GPT-2 nu utilizează un normalizator, deci sărim peste acest pas și trecem direct la pre-tokenizare: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) +``` + +Opțiunea pe care am adăugat-o aici la `ByteLevel` nu este pentru a adăuga un spațiu unei propoziții (care este implicit în caz contrar). Putem arunca o privire la pre-tokenizarea unui text exemplu ca înainte: + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!") +``` + +```python out +[('Let', (0, 3)), ("'s", (3, 5)), ('Ġtest', (5, 10)), ('Ġpre', (10, 14)), ('-', (14, 15)), + ('tokenization', (15, 27)), ('!', (27, 28))] +``` + +Urmează modelul, care are nevoie de antrenare. Pentru GPT-2, singurul token special este tokenul de sfârșit de text: + +```python +trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Ca și în cazul `WordPieceTrainer`, precum și `vocab_size` și `special_tokens`, putem specifica `min_frequency` dacă dorim, sau dacă avem un sufix de sfârșit de cuvânt (cum ar fi ``), îl putem seta cu `end_of_word_suffix`. + +Acest tokenizer poate fi antrenat și pe fișiere text: + +```python +tokenizer.model = models.BPE() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Să aruncăm o privire la tokenizarea unui exemplu de text: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] +``` + +Aplicăm postprocesarea la nivel de bytes pentru tokenizerul GPT-2 după cum urmează: + +```python +tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) +``` + +Opțiunea `trim_offsets = False` indică post-procesorului că ar trebui să lăsăm offseturile tokenilor care încep cu "Ġ" așa cum sunt: în acest fel, începutul offseturilor va indica spațiul dinaintea cuvântului, nu primul caracter al cuvântului (deoarece spațiul face parte din punct de vedere tehnic din token). Să aruncăm o privire asupra rezultatului cu textul pe căruia tocmai i-am făcut encoding, unde `'Ġtest'` este tokenul de la indexul 4: + +```python +sentence = "Let's test this tokenizer." +encoding = tokenizer.encode(sentence) +start, end = encoding.offsets[4] +sentence[start:end] +``` + +```python out +' test' +``` + +În cele din urmă, adăugăm un decoder la nivel de bytes: + +```python +tokenizer.decoder = decoders.ByteLevel() +``` + +și putem verifica de două ori dacă funcționează corect: + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"Let's test this tokenizer." +``` + +Grozav! Acum că am terminat, putem salva tokenizatorul ca înainte și îl putem încorpora într-un `PreTrainedTokenizerFast` sau `GPT2TokenizerFast` dacă dorim să îl folosim în 🤗 Transformers: + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", +) +``` + +or: + +```python +from transformers import GPT2TokenizerFast + +wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) +``` + +Ca un ultim exemplu, vă vom arăta cum să construiți un tokenizer Unigram de la zero. + +## Construirea unui tokenizer Unigram de la zero[[building-a-unigram-tokenizer-from-scratch]] + +Să construim acum un tokenizer XLNet. Ca și în cazul tokenizerelor anterioare, începem prin inițializarea unui `Tokenizer` cu un model Unigram: + +```python +tokenizer = Tokenizer(models.Unigram()) +``` + +Din nou, am putea inițializa acest model cu un vocabular, dacă am avea unul. + +Pentru normalizare, XLNet utilizează câteva înlocuiri (care provin din SentencePiece): + +```python +from tokenizers import Regex + +tokenizer.normalizer = normalizers.Sequence( + [ + normalizers.Replace("``", '"'), + normalizers.Replace("''", '"'), + normalizers.NFKD(), + normalizers.StripAccents(), + normalizers.Replace(Regex(" {2,}"), " "), + ] +) +``` + +Acest lucru înlocuitește `` și '' cu " și orice secvență de două sau mai multe spații cu un singur spațiu, precum și ștergerea accentelor în textele ce trebuie tokenizate. + +Pre-tokenizerul care trebuie utilizat pentru orice tokenizer SentencePiece este `Metaspace`: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() +``` + +Putem arunca o privire la pre-tokenizarea unui exemplu de text ca mai înainte: + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!") +``` + +```python out +[("▁Let's", (0, 5)), ('▁test', (5, 10)), ('▁the', (10, 14)), ('▁pre-tokenizer!', (14, 29))] +``` + +Urmează modelul, care are nevoie de antrenare. XLNet are destul de mulți tokeni speciali: + +```python +special_tokens = ["", "", "", "", "", "", ""] +trainer = trainers.UnigramTrainer( + vocab_size=25000, special_tokens=special_tokens, unk_token="" +) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Un argument foarte important care nu trebuie uitat pentru `UnigramTrainer` este `unk_token`. Putem trece și alte argumente specifice algoritmului Unigram, cum ar fi `shrinking_factor` pentru fiecare pas în care eliminăm tokeni (valoarea implicită este 0,75) sau `max_piece_length` pentru a specifica lungimea maximă a unui token dat (valoarea implicită este 16). + +Acest tokenizer poate fi antrenat și pe fișiere text: + +```python +tokenizer.model = models.Unigram() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Să aruncăm o privire la tokenizarea unui exemplu de text: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] +``` + +O particularitate a XLNet este că pune tokenul `` la sfârșitul propoziției, cu un ID de tip 2 (pentru a-l distinge de ceilalți tokeni). Ca urmare, este făcut padding la stânga. Putem trata toți tokenii speciali și ID-urile de tip ale tokenilor cu un template, ca în cazul BERT, dar mai întâi trebuie să obținem ID-urile tokenilor `` și ``: + +```python +cls_token_id = tokenizer.token_to_id("") +sep_token_id = tokenizer.token_to_id("") +print(cls_token_id, sep_token_id) +``` + +```python out +0 1 +``` + +Templateul arată astfel: + +```python +tokenizer.post_processor = processors.TemplateProcessing( + single="$A:0 :0 :2", + pair="$A:0 :0 $B:1 :1 :2", + special_tokens=[("", sep_token_id), ("", cls_token_id)], +) +``` + +Și putem testa că funcționează prin codificarea unei perechi de propoziții: + +```python +encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!") +print(encoding.tokens) +print(encoding.type_ids) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.', '.', '.', '', '▁', 'on', '▁', 'a', '▁pair', + '▁of', '▁sentence', 's', '!', '', ''] +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2] +``` + +În cele din urmă, adăugăm un decoder `Metaspace`: + +```python +tokenizer.decoder = decoders.Metaspace() +``` + +și am terminat cu acest tokenizer! Putem salva tokenizerul ca înainte și îl putem încorpora într-un `PreTrainedTokenizerFast` sau `XLNetTokenizerFast` dacă dorim să îl folosim în 🤗 Transformers. Un lucru de reținut atunci când se utilizează `PreTrainedTokenizerFast` este că, pe lângă tokenii speciali, trebuie să spunem bibliotecii 🤗 Transformers să facă padding la stânga: + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="", + eos_token="", + unk_token="", + pad_token="", + cls_token="", + sep_token="", + mask_token="", + padding_side="left", +) +``` + +Sau alternativ: + +```python +from transformers import XLNetTokenizerFast + +wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) +``` + +Acum că ați văzut cum sunt utilizate diferitele blocuri de construcție pentru a construi tokenizeri existenți, ar trebui să puteți scrie orice tokenizer doriți cu biblioteca 🤗 Tokenizers și să îl puteți utiliza în 🤗 Transformers. diff --git a/chapters/ro/chapter 6/9.mdx b/chapters/ro/chapter 6/9.mdx new file mode 100644 index 000000000..8fc71f8ea --- /dev/null +++ b/chapters/ro/chapter 6/9.mdx @@ -0,0 +1,16 @@ +# Tokenizers, verificați![[tokenizers-check]] + + + +Bună treabă la finalizarea acestui capitol! + +După această scufundare adâncă în tokenizers, ar trebui să: + +- Să fii capabil să antrenezi un nou tokenizer folosind unul vechi ca model +- Să înțelegi modului de utilizare a offseturilor pentru a face map tokenilor în intervalul lor original de text +- Cunoști diferențele dintre BPE, WordPiece și Unigram +- Fii capabil să combini blocurile furnizate de biblioteca 🤗 Tokenizers pentru a vă construi propriul tokenizer +- Să poți folosi acest tokenizer în cadrul bibliotecii 🤗 Transformers From abd846ce9a2facc1b409cdbd31b273a2957d11d4 Mon Sep 17 00:00:00 2001 From: "ludmila.friptu" Date: Wed, 8 Jan 2025 13:10:14 +0200 Subject: [PATCH 07/37] Add chapter 3 --- chapters/rum/chapter3/1.mdx | 26 +++ chapters/rum/chapter3/2.mdx | 385 +++++++++++++++++++++++++++++++++ chapters/rum/chapter3/3.mdx | 171 +++++++++++++++ chapters/rum/chapter3/3_tf.mdx | 198 +++++++++++++++++ chapters/rum/chapter3/4.mdx | 359 ++++++++++++++++++++++++++++++ chapters/rum/chapter3/5.mdx | 25 +++ chapters/rum/chapter3/6.mdx | 301 ++++++++++++++++++++++++++ 7 files changed, 1465 insertions(+) create mode 100644 chapters/rum/chapter3/1.mdx create mode 100644 chapters/rum/chapter3/2.mdx create mode 100644 chapters/rum/chapter3/3.mdx create mode 100644 chapters/rum/chapter3/3_tf.mdx create mode 100644 chapters/rum/chapter3/4.mdx create mode 100644 chapters/rum/chapter3/5.mdx create mode 100644 chapters/rum/chapter3/6.mdx diff --git a/chapters/rum/chapter3/1.mdx b/chapters/rum/chapter3/1.mdx new file mode 100644 index 000000000..564d72960 --- /dev/null +++ b/chapters/rum/chapter3/1.mdx @@ -0,0 +1,26 @@ + + +# Introducere[[introducere]] + + + +În [Capitolul 2](/course/chapter2) am explorat modul de utilizare a tokenizerelor și a modelelor preantrenate pentru a efectua predicții. Dar ce se întâmplă dacă doriți să ajustați un model preantrenat pentru propriul dvs. set de date? Iată subiectul acestui capitol! Veți învăța: + +{#if fw === 'pt'} +* Cum să configurați un set mare de date din Hub +* Cum să utilizați API-ul `Trainer` pentru a ajusta un model +* Cum să utilizați o buclă de instruire personalizată +* Cum să profitați de biblioteca Accelerate 🤗 pentru a rula cu ușurință bucla de instruire personalizată pe orice configurație distribuită + +{:else} +* Cum să configurați un set mare de date din Hub +* Cum să utilizați Keras pentru a ajusta un model +* Cum să utilizați Keras pentru a obține predicții +* Cum să utilizați o măsură personalizată + +{/if} + +Pentru a încărca checkpoint-urile antrenate în Hugging Face Hub, veți avea nevoie de un cont huggingface.co: [creați un cont](https://huggingface.co/join) diff --git a/chapters/rum/chapter3/2.mdx b/chapters/rum/chapter3/2.mdx new file mode 100644 index 000000000..9292cd061 --- /dev/null +++ b/chapters/rum/chapter3/2.mdx @@ -0,0 +1,385 @@ + + +# Prelucrarea datelor[[prelucrarea-datelor]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +Continuând cu exemplul din [capitolul anterior](/course/chapter2), iată cum am antrena un clasificator de secvențe pe un lot în PyTorch: + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# La fel ca înainte +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# Ceva nou +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} +Continuând cu exemplul din [capitolul anterior](/course/chapter2), iată cum am antrena un clasificator de secvențe pe un lot în TensorFlow: + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# La fel ca înainte +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# Ceva nou +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` +{/if} + +Desigur, doar antrenarea modelului pe două propoziții nu va da rezultate foarte bune. Pentru a obține rezultate mai bune, va trebui să pregătiți un set de date mai mare. + +În această secțiune vom folosi ca exemplu setul de date MRPC (Microsoft Research Paraphrase Corpus), introdus într-o [lucrare](https://www.aclweb.org/anthology/I05-5002.pdf) de William B. Dolan și Chris Brockett. Setul de date este format din 5 801 perechi de propoziții, cu o etichetă care indică dacă acestea sunt parafrazări sau nu (adică, dacă ambele propoziții înseamnă același lucru). L-am selectat pentru acest capitol deoarece este un set de date mic, astfel încât este ușor de experimentat cu formarea pe acesta. + +### Încărcarea unui set de date din Hub[[încărcarea-unui-set-de-date-din-Hub]] + +{#if fw === 'pt'} + +{:else} + +{/if} + +Hub-ul nu conține doar modele, ci și multe seturi de date în limbi diferite. Puteți naviga printre seturile de date [aici](https://huggingface.co/datasets) și vă recomandăm să încercați să încărcați și să procesați un nou set de date după ce ați parcurs această secțiune (consultați documentația generală [aici](https://huggingface.co/docs/datasets/loading)). Dar, pentru moment, să ne concentrăm asupra setului de date MRPC! Acesta este unul dintre cele 10 seturi de date care compun [GLUE benchmark](https://gluebenchmark.com/), care este un benchmark academic utilizat pentru a măsura performanța modelelor ML în 10 sarcini diferite de clasificare a textului. + +Biblioteca 🤗 Datasets oferă o comandă foarte simplă pentru a descărca și stoca în cache un set de date pe Hub. Putem descărca setul de date MRPC astfel: + + +⚠️ **Atenție** Asigurați-vă că `datasets` este instalat prin rularea `pip install datasets`. Apoi, încărcați setul de date MRPC și tipăriți-l pentru a vedea ce conține. + + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +După cum puteți vedea, obținem un obiect `DatasetDict` care conține setul de instruire, setul de validare și setul de testare. Fiecare dintre acestea conține mai multe coloane (`sentence1`, `sentence2`, `label` și `idx`) și un număr de rânduri variabil, care reprezintă numărul de elemente din fiecare set (astfel, există 3.668 de perechi de propoziții în setul de instruire, 408 în setul de validare și 1.725 în setul de testare). + +Această comandă descarcă și pune în cache setul de date, implicit în *~/.cache/huggingface/datasets*. Reamintim din capitolul 2 că puteți personaliza folderul cache prin setarea variabilei de mediu `HF_HOME`. + +Putem accesa fiecare pereche de propoziții din obiectul nostru `raw_datasets` prin indexare, ca într-un dicționar: + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +``` + +Putem vedea că etichetele sunt deja numere întregi, deci nu va trebui să efectuăm nicio prelucrare prealabilă. Pentru a ști ce număr întreg corespunde fiecărei etichete, putem inspecta `features` din `raw_train_dataset`. Acest lucru ne va indica tipul fiecărei coloane: + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +În culise, `label` este de tipul `ClassLabel`, iar maparea numerelor întregi și numele etichetei este stocată în folderul *names*. `0` corespunde la `not_equivalent`, iar `1` corespunde la `equivalent`. + + + +✏️ **Încercați!** Uitați-vă la elementul 15 din setul de antrenament și la elementul 87 din setul de validare. Care sunt etichetele lor? + + + +### Preprocesarea unui set de date[[preprocesarea-unui-set-de-date]] + +{#if fw === 'pt'} + +{:else} + +{/if} + +Pentru a preprocesa setul de date, trebuie să convertim textul în numere pe care modelul le înțelege. După cum ați văzut în [capitolul anterior](/course/chapter2), acest lucru se face cu ajutorul unui tokenizer. Putem furniza tokenizatorului o propoziție sau o listă de propoziții, astfel încât putem tokeniza direct primele propoziții și toate propozițiile secundare din fiecare pereche, astfel: + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +Cu toate acestea, nu putem pur și simplu să transmitem două secvențe modelului și să obținem o predicție care să indice dacă cele două propoziții sunt parafraze sau nu. Trebuie să tratăm cele două secvențe ca pe o pereche și să aplicăm preprocesarea corespunzătoare. Din fericire, tokenizatorul poate, de asemenea, să ia o pereche de secvențe și să le pregătească în modul în care se așteaptă modelul nostru BERT: + +```py +inputs = tokenizer("This is the first sentence.", "This is the second one.") +inputs +``` + +```python out +{ + 'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102], + 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], + 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] +} +``` + +Am discutat despre cheile `input_ids` și `attention_mask` în [Capitolul 2](/course/chapter2), dar am amânat discuția despre `token_type_ids`. În acest exemplu, aceasta este ceea ce îi spune modelului care parte a intrării este prima propoziție și care este a doua propoziție. + + + +✏️ **Încercați!** Luați elementul 15 din setul de antrenament și tokenizați cele două propoziții separat apoi ca pe o pereche. Care este diferența dintre cele două rezultate? + + + +Dacă decodificăm ID-urile din `input_ids` înapoi în cuvinte: + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +Vom obține: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +Astfel, modelul se așteaptă ca intrările să fie de forma `[CLS] sentence1 [SEP] sentence2 [SEP]` atunci când există două propoziții. Alinierea acestui lucru cu `token_type_ids` ne dă: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +După cum puteți vedea, părțile de intrare corespunzătoare `[CLS] sentence1 [SEP]` au toate un ID de tip token de `0`, în timp ce celelalte părți, corespunzătoare `sentence2 [SEP]`, au toate un ID de tip token de `1`. + +Rețineți că, dacă selectați un checkpoint diferit, nu veți avea neapărat `token_type_ids` în intrările dvs. tokenizate (de exemplu, acestea nu sunt returnate dacă utilizați un model DistilBERT). Acestea sunt returnate numai atunci când modelul va ști ce să facă cu ele, deoarece le-a văzut în timpul preinstruirii sale. + +Aici, BERT este preinstruit cu ID-uri de tip token și, pe lângă obiectivul de modelare a limbajului mascat despre care am vorbit în [[Chapter 1]](/course/chapter1), are un obiectiv suplimentar numit _next sentence prediction_. Obiectivul acestei sarcini este de a modela relația dintre perechile de propoziții. + +În cazul predicției propoziției următoare, modelul primește perechi de propoziții (cu token-uri mascate aleatoriu) și i se cere să prezică dacă a doua propoziție o urmează pe prima. Pentru ca sarcina să nu fie complicată, jumătate din timp propozițiile se succed reciproc în documentul original din care au fost extrase, iar cealaltă jumătate din timp cele două propoziții provin din două documente diferite. + +În general, nu trebuie să vă faceți griji dacă există sau nu `token_type_ids` în intrările dvs. tokenizate: atâta timp cât utilizați același checkpoint pentru tokenizator și model, totul va fi bine, deoarece tokenizatorul știe ce să furnizeze modelului său. + +Acum că am văzut cum tokenizatorul nostru poate trata o pereche de propoziții, îl putem folosi pentru a tokeniza întregul nostru set de date: la fel ca în [previous chapter](/course/chapter2), putem furniza tokenizatorului o listă de perechi de propoziții oferindu-i lista primelor propoziții, apoi lista celor de-a doua propoziții. Acest lucru este, de asemenea, compatibil cu opțiunile de padding și trunchiere pe care le-am văzut în [Chapter 2](/course/chapter2). Așadar, o modalitate de preprocesare a setului de date de instruire este: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +Această metodă funcționează corespunzător, dar are dezavantajul de a returna un dicționar (cu cheile noastre, `input_ids`, `attention_mask` și `token_type_ids`, și valori care sunt liste ale listelor). De asemenea, va funcționa numai dacă aveți suficientă memorie RAM pentru a stoca întregul set de date în timpul tokenizării (în timp ce seturile de date din biblioteca 🤗 Datasets sunt fișiere [Apache Arrow](https://arrow.apache.org/) stocate pe disc, deci păstrați încărcate în memorie numai eșantioanele pe care le solicitați). + +Pentru a păstra informațiile sub forma unui set de date, vom utiliza metoda [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map). Acest lucru ne permite, de asemenea, o flexibilitate sporită, în cazul în care avem nevoie de mai multe preprocesări decât simpla tokenizare. Metoda `map()` funcționează prin aplicarea unei funcții pe fiecare element al setului de date, deci să definim o funcție care să tokenizeze intrările noastre: + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +Această funcție acceptă un dicționar (precum elementele din setul nostru de date) și returnează un nou dicționar cu cheile `input_ids`, `attention_mask` și `token_type_ids`. Rețineți că funcționează și în cazul în care dicționarul `example` conține mai multe eșantioane (fiecare cheie fiind o listă de propoziții), deoarece `tokenizer` funcționează pe liste de perechi de propoziții, așa cum am văzut anterior. Acest lucru ne va permite să folosim opțiunea `batched=True` în apelul nostru la `map()`, ceea ce va accelera foarte mult tokenizarea. `tokenizer` este susținut de un tokenizer scris în Rust din biblioteca [🤗 Tokenizers](https://github.com/huggingface/tokenizers). Acest tokenizator poate fi foarte rapid, dar numai dacă îi oferim o mulțime de intrări deodată. + +Rețineți că am omis deocamdată argumentul `padding` în funcția noastră de tokenizare. Acest lucru se datorează faptului că umplerea tuturor eșantioanelor la lungimea maximă nu este eficientă: este mai bine să umplem eșantioanele atunci când construim un lot, deoarece atunci trebuie să umplem doar la lungimea maximă din acel lot, și nu la lungimea maximă din întregul set de date. Acest lucru poate economisi mult timp și putere de procesare atunci când intrările au lungimi variate! + +Iată cum aplicăm funcția de tokenizare la toate seturile noastre de date simultan. Utilizăm `batched=True` în apelul către `map`, astfel încât funcția să fie aplicată la mai multe elemente ale setului nostru de date simultan, și nu la fiecare element în parte. Acest lucru permite o preprocesare mai rapidă. + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +Modul în care biblioteca 🤗 Datasets aplică această procesare este prin adăugarea de noi câmpuri la seturile de date, câte unul pentru fiecare cheie din dicționarul returnat de funcția de preprocesare: + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +Puteți utiliza chiar și multiprocesarea atunci când aplicați funcția de preprocesare cu `map()` prin transmiterea unui argument `num_proc`. Nu am făcut acest lucru aici deoarece biblioteca 🤗 Tokenizers utilizează deja mai multe fire pentru a tokeniza mai rapid eșantioanele noastre, dar dacă nu utilizați un tokenizator rapid susținut de această bibliotecă, acest lucru v-ar putea accelera preprocesarea. + +Funcția noastră `tokenize_function` returnează un dicționar cu cheile `input_ids`, `attention_mask` și `token_type_ids`, astfel încât aceste trei câmpuri sunt adăugate la toate diviziunile setului nostru de date. Rețineți că am fi putut, de asemenea, să modificăm câmpurile existente dacă funcția noastră de preprocesare a returnat o nouă valoare pentru o cheie existentă în setul de date căruia i-am aplicat funcția `map()`. + +Ultimul lucru pe care va trebui să îl facem este să umplem toate exemplele la lungimea celui mai lung element atunci când grupăm elementele împreună - o tehnică la care ne referim ca *umplere dinamică*. + +### Umplere dinamică[[umplere-dinamică]] + + + +{#if fw === 'pt'} +Funcția care este responsabilă de combinarea eșantioanelor în cadrul unui lot se numește *funcție de concatenare*. Este un argument pe care îl puteți trece atunci când construiți un `DataLoader`, implicit fiind o funcție care va converti eșantioanele în tensori PyTorch și le va concatena (recursiv dacă elementele sunt liste, tupluri sau dicționare). Acest lucru nu va fi posibil în cazul nostru, deoarece intrările pe care le avem nu vor fi toate de aceeași dimensiune. Am amânat în mod deliberat umplerea, pentru a o aplica doar în funcție de necesități la fiecare lot și pentru a evita să avem intrări prea lungi cu o mulțime de umpluturi. Acest lucru va accelera antrenamentul destul de mult, dar rețineți că, dacă vă antrenați pe o TPU, acest lucru poate cauza probleme - TPU preferă formele fixe, chiar și atunci când acest lucru necesită o umplutură suplimentară. + +{:else} + +Funcția care este responsabilă de reunirea eșantioanelor în cadrul unui lot se numește *funcție de colaționare*. Funcția de colaționare implicită este o funcție care va converti eșantioanele în tf.Tensor și le va concatena (recursiv dacă elementele dvs. sunt liste, tuples sau dicționare). Acest lucru nu va fi posibil în cazul nostru, deoarece intrările pe care le avem nu vor fi toate de aceeași dimensiune. Am amânat în mod deliberat umplerea, pentru a o aplica doar în funcție de necesități la fiecare lot și pentru a evita să avem intrări prea lungi cu o mulțime de umpluturi. Acest lucru va accelera antrenamentul destul de mult, dar rețineți că, dacă vă antrenați pe o TPU, acest lucru poate cauza probleme - TPU preferă formele fixe, chiar și atunci când acest lucru necesită o umplutură suplimentară. + +{/if} + +Pentru a face acest lucru în practică, trebuie să definim o funcție de colaționare care va aplica cantitatea corectă de umplutură elementelor din setul de date pe care dorim să le grupăm. Din fericire, biblioteca 🤗 Transformers ne oferă o astfel de funcție prin `DataCollatorWithPadding`. Aceasta preia un tokenizer atunci când o instanțiați (pentru a ști ce token de umplutură să utilizați și dacă modelul se așteaptă ca umplutura să fie la stânga sau la dreapta intrărilor) și va face tot ceea ce aveți nevoie: + +{#if fw === 'pt'} +```py +from transformers import DataCollatorWithPadding + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) +``` +{:else} +```py +from transformers import DataCollatorWithPadding + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") +``` +{/if} + +Pentru a testa această nouă opțiune, să luăm câteva eșantioane din setul nostru de formare pe care dorim să le grupăm. Aici, eliminăm coloanele `idx`, `sentence1` și `sentence2` deoarece nu vor fi necesare și conțin șiruri de caractere (și nu putem crea tensori cu șiruri de caractere) și aruncăm o privire la lungimile fiecărei intrări din lot: + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +Nici o surpriză, obținem eșantioane de diferite lungimi, de la 32 la 67. Umplerea dinamică înseamnă că toate eșantioanele din acest lot ar trebui să fie umplute la o lungime de 67, lungimea maximă din cadrul lotului. Fără umplutură dinamică, toate eșantioanele ar trebui să fie umplute la lungimea maximă din întregul set de date sau la lungimea maximă pe care modelul o poate accepta. Să verificăm de două ori dacă `data_collator` completează dinamic lotul în mod corespunzător: + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +Perfect! Acum că am trecut de la text brut la loturi cu care modelul nostru se poate descurca, suntem gata să îl ajustăm! + +{/if} + + + +✏️ **Încearcați!** Replicați preprocesarea pe setul de date GLUE SST-2. Acesta este puțin diferit, deoarece este compus din propoziții simple în loc de perechi, dar restul lucrurilor pe care le-am făcut ar trebui să fie la fel. Pentru o provocare mai dificilă, încercați să scrieți o funcție de preprocesare care să funcționeze pe oricare dintre sarcinile GLUE. + + + +{#if fw === 'tf'} + +Acum că avem setul nostru de date și un compilator de date, trebuie să le punem împreună. Am putea încărca manual loturi și să le asamblăm, dar este mult de lucru și probabil nici nu este foarte eficient. În schimb, există o metodă simplă care oferă o soluție performantă la această problemă: `to_tf_dataset()`. Aceasta va înfășura un `tf.data.Dataset` în jurul setului dvs. de date, cu o funcție de colaționare opțională. `tf.data.Dataset` este un format TensorFlow nativ pe care Keras îl poate utiliza pentru `model.fit()`, astfel încât această metodă convertește imediat un set de date 🤗 într-un format care este pregătit pentru antrenament. Să o vedem în acțiune cu setul nostru de date! + +```py +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) + +tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=False, + collate_fn=data_collator, + batch_size=8, +) +``` + +Și asta este tot! Putem folosi aceste seturi de date în cursul următor, unde instruirea va fi extrem de simplă după toată munca grea de preprocesare a datelor. + +{/if} diff --git a/chapters/rum/chapter3/3.mdx b/chapters/rum/chapter3/3.mdx new file mode 100644 index 000000000..eb50bf596 --- /dev/null +++ b/chapters/rum/chapter3/3.mdx @@ -0,0 +1,171 @@ + + +# Ajustarea unui model folosind Trainer API[[ajustarea-unui-model-folosind-trainer-api]] + + + + + +🤗 Transformers oferă o clasă `Trainer` pentru a vă ajuta să reglați mai bine oricare dintre modelele preinstruite pe care le oferă pe setul dvs. de date. După ce ați făcut toată munca de preprocesare a datelor din ultima secțiune, vă mai rămân doar câțiva pași pentru a defini clasa `Trainer`. Cea mai dificilă parte va fi probabil pregătirea mediului pentru a rula `Trainer.train()`, deoarece acesta va rula foarte lent pe un CPU. Dacă nu aveți un GPU configurat, puteți obține acces gratuit la GPU-uri sau TPU-uri pe [Google Colab] (https://colab.research.google.com/). + +Exemplele de cod de mai jos presupun că ați executat deja exemplele din secțiunea anterioară. Iată un scurt rezumat care recapitulează ceea ce aveți nevoie: + +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) +``` + +### Antrenarea[[antrenarea]] + +Primul pas înainte de a ne defini modelul `Trainer` este să definim o clasă `TrainingArguments` care va conține toți hiperparametrii pe care `Trainer` îi va utiliza pentru formare și evaluare. Singurul argument pe care trebuie să îl furnizați este un folder în care va fi salvat modelul antrenat, precum și punctele de control de pe parcurs. Pentru tot restul, puteți lăsa valorile implicite, care ar trebui să funcționeze destul de bine pentru o ajustare de bază. + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 Dacă doriți să încărcați automat modelul în Hub în timpul instruirii, treceți `push_to_hub=True` în `TrainingArguments`. Vom afla mai multe despre acest lucru în [Capitolul 4](/course/chapter4/3) + + + +Al doilea pas este să ne definim modelul. Ca și în [capitolul anterior](/course/chapter2), vom folosi clasa `AutoModelForSequenceClassification`, cu două etichete: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Veți observa că, spre deosebire de [Capitolul 2](/course/chapter2), veți primi un avertisment după instanțierea acestui model preinstruit. Acest lucru se datorează faptului că BERT nu a fost instruit în prealabil cu privire la clasificarea perechilor de propoziții, astfel încât head-ul modelului instruit în prealabil a fost eliminat și a fost adăugat în schimb un nou head adecvat pentru clasificarea secvențelor. Avertizările indică faptul că unele ponderi nu au fost utilizate (cele corespunzătoare head-ului de preformare eliminat) și că altele au fost inițializate aleatoriu (cele pentru noul head). În încheiere, vă încurajează să antrenați modelul, ceea ce vom face acum. + +Odată ce avem modelul nostru, putem defini un `Trainer` transmițându-i toate obiectele construite până acum - `modelul`, `training_args`, seturile de date de formare și validare, `data_collator` și `tokenizer`: + +```py +from transformers import Trainer + +trainer = Trainer( + model, + training_args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, +) +``` + +Rețineți că atunci când treceți `tokenizer` așa cum am făcut aici, `data_collator` implicit folosit de `Trainer` va fi un `DataCollatorWithPadding` așa cum a fost definit anterior, deci puteți sări peste linia `data_collator=data_collator` în acest apel. Era totuși important să vă arăt această parte a procesării în secțiunea 2! + +Pentru a regla cu precizie modelul pe setul nostru de date, trebuie doar să apelăm metoda `train()` a `Trainer`: + +```py +trainer.train() +``` + +Acest lucru va începe reglarea fină (care ar trebui să dureze câteva minute pe un GPU) și va raporta valoarea pierderii de formare la fiecare 500 de pași. Cu toate acestea, nu vă va spune cât de bine (sau rău) funcționează modelul dumneavoastră. Acest lucru se datorează faptului că: + +1. Nu am precizat că `Trainer` trebuie să evalueze în timpul antrenamentului prin setarea `evaluation_strategy` la `„steps”` (evaluare la fiecare `eval_steps`) sau `„epoch”` (evaluare la sfârșitul fiecărei epoch). +2. Nu am furnizat pentru `Trainer` o funcție `compute_metrics()` pentru a calcula o valoare metrică în timpul evaluării (altfel evaluarea ar fi afișat doar pierderea, care nu este un număr foarte intuitiv). + +### Evaluarea[[evaluarea]] + +Să vedem cum putem construi o funcție utilă `compute_metrics()` și să o folosim data viitoare când ne antrenăm. Funcția trebuie să preia un obiect `EvalPrediction` (care este un tuple numit cu un câmp `predictions` și un câmp `label_ids`) și va returna un dicționar care mapează șiruri de caractere în valori float. Pentru a obține unele predicții de la modelul nostru, putem utiliza comanda `Trainer.predict()`: + +```py +predictions = trainer.predict(tokenized_datasets["validation"]) +print(predictions.predictions.shape, predictions.label_ids.shape) +``` + +```python out +(408, 2) (408,) +``` + +Rezultatul metodei `predict()` este un alt tuple numit cu trei câmpuri: `predictions`, `label_ids` și `metrics`. Câmpul `metrics` va conține doar pierderea pe setul de date transmis, precum și unele valori metrice de timp (cât timp a durat predicția, în total și în medie). După ce vom finaliza funcția `compute_metrics()` și o vom transmite către `Trainer`, acest câmp va conține și metrica returnată de `compute_metrics()`. + +După cum puteți vedea, `predictions` este un array bidimensional cu forma 408 x 2 (408 fiind numărul de elemente din setul de date pe care l-am folosit). Acestea sunt logits pentru fiecare element al setului de date pe care l-am transmis la `predict()` (după cum ați văzut în [capitolul anterior](/course/chapter2), toate modelele Transformer returnează logits). Pentru a le transforma în predicții pe care le putem compara cu etichetele noastre, trebuie să luăm indicele cu valoarea maximă pe a doua axă: + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +Acum putem compara `preds` cu etichetele. Pentru a construi funcția noastră `compute_metric()`, ne vom baza pe valorile metrice din biblioteca 🤗 [Evaluate](https://github.com/huggingface/evaluate/). Putem încărca valorile metrice asociate cu setul de date MRPC la fel de ușor cum am încărcat setul de date, de data aceasta cu funcția `evaluate.load()`. Obiectul returnat are o metodă `compute()` pe care o putem utiliza pentru a efectua calculul metric: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +metric.compute(predictions=preds, references=predictions.label_ids) +``` + +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +Rezultatele exacte pe care le obțineți pot varia, deoarece inițializarea aleatorie a head-ului modelului poate schimba parametrii obținuți. Aici, putem vedea că modelul nostru are o precizie de 85,78% pe setul de validare și un scor F1 de 89,97. Acestea sunt cele două valori metrice utilizate pentru a evalua rezultatele pe setul de date MRPC pentru criteriul de referință GLUE. Tabelul din [lucrarea BERT] (https://arxiv.org/pdf/1810.04805.pdf) a raportat un scor F1 de 88,9 pentru modelul de bază. Acesta a fost modelul „fără casare”, în timp ce noi folosim în prezent modelul „cu casare”, ceea ce explică rezultatul mai bun. + +Adunând totul, obținem funcția noastră `compute_metrics()`: + +```py +def compute_metrics(eval_preds): + metric = evaluate.load("glue", "mrpc") + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + return metric.compute(predictions=predictions, references=labels) +``` + +Și pentru a vedea cum se utilizează în practică pentru a raporta datele metrice la sfârșitul fiecărei perioade, iată cum definim un nou `Trainer` cu această funcție `compute_metrics()`: + +```py +training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch") +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + +trainer = Trainer( + model, + training_args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, + compute_metrics=compute_metrics, +) +``` + +Rețineți că creăm un nou `TrainingArguments` cu `evaluation_strategy` setat la `„epoch”` și un nou model - în caz contrar, am continua doar instruirea modelului pe care l-am instruit deja. Pentru a lansa o nouă rulare de formare, executăm: + +```py +trainer.train() +``` + +De data aceasta, acesta va raporta pierderea de validare și metrica la sfârșitul fiecărei perioade, pe lângă valoarea pierderii de formare. Din nou, scorul exact de acuratețe/F1 pe care îl veți obține ar putea fi puțin diferit de ceea ce am găsit noi, din cauza inițializării aleatorii a modelului, dar ar trebui să fie în aceeași zonă. + +Modelul `Trainer` va funcționa din start pe mai multe GPU sau TPU și oferă o mulțime de opțiuni, cum ar fi formarea cu precizie mixtă (utilizați `fp16 = True` în argumentele de formare). Vom trece în revistă toate funcțiile pe care le suportă în capitolul 10. + +Aceasta încheie introducerea la reglarea fină cu ajutorul API-ului `Trainer`. În [Capitolul 7](/course/chapter7) va fi prezentat un exemplu de efectuare a acestei operații pentru cele mai comune sarcini NLP, dar pentru moment să analizăm cum se poate face același lucru în PyTorch pur. + + + +✏️ **Încercați!** Ajustați un model pe setul de date GLUE SST-2, folosind procesarea datelor efectuată în secțiunea 2. + + + diff --git a/chapters/rum/chapter3/3_tf.mdx b/chapters/rum/chapter3/3_tf.mdx new file mode 100644 index 000000000..ede281693 --- /dev/null +++ b/chapters/rum/chapter3/3_tf.mdx @@ -0,0 +1,198 @@ + + +# Ajustarea unui model cu Keras[[ajustarea-unui-model-cu-keras]] + + + +După ce ați efectuat toate lucrările de preprocesare a datelor din ultima secțiune, mai aveți doar câțiva pași de făcut pentru a antrena modelul. Rețineți, totuși, că comanda `model.fit()` va rula foarte lent pe un CPU. Dacă nu aveți un GPU configurat, puteți obține acces gratuit la GPU sau TPU pe [Google Colab] (https://colab.research.google.com/). + +Exemplele de cod de mai jos presupun că ați executat deja exemplele din secțiunea anterioară. Iată un scurt rezumat care recapitulează ceea ce aveți nevoie: + +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding +import numpy as np + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") + +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) + +tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=False, + collate_fn=data_collator, + batch_size=8, +) +``` + +### Antrenarea[[antrenarea]] + +Modelele TensorFlow importate din 🤗 Transformers sunt deja modele Keras. În cele ce urmează găsiți o scurtă introducere în Keras. + + + +Aceasta înseamnă că, odată ce avem datele noastre, este nevoie de foarte puțină muncă pentru a începe antrenarea pe baza acestora. + + + +Ca și în [capitolul anterior](/course/chapter2), vom folosi clasa `TFAutoModelForSequenceClassification`, cu două etichete: + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Veți observa că, spre deosebire de [Capitolul 2](/course/chapter2), veți primi un avertisment după instanțierea acestui model preinstruit. Acest lucru se datorează faptului că BERT nu a fost preinstruit pentru clasificarea perechilor de propoziții, astfel încât head-ul modelului preinstruit a fost eliminat și a fost introdus în schimb un nou head adecvat pentru clasificarea secvențelor. Avertizările indică faptul că unele ponderi nu au fost utilizate (cele corespunzătoare head-ului de preformare eliminat) și că altele au fost inițializate aleatoriu (cele pentru noul head). În încheiere, vă încurajează să antrenați modelul, ceea ce vom face acum. + +Pentru reglarea fină a modelului pe setul nostru de date, trebuie doar să `compilăm()` modelul nostru și apoi să transmitem datele noastre metodei `fit()`. Aceasta va începe procesul de reglare fină (care ar trebui să dureze câteva minute pe un GPU) și va raporta pierderea de formare pe parcurs, plus pierderea de validare la sfârșitul fiecărei epoci. + + + +Rețineți că modelele 🤗 Transformers au o abilitate specială pe care majoritatea modelelor Keras nu o au - ele pot utiliza automat o valoare adecvată a pierderii pe care o calculează intern. Ele vor utiliza această valoare în mod implicit dacă nu setați un argument de pierdere în `compile()`. Rețineți că pentru a utiliza valoarea internă a pierderii va trebui să transmiteți etichetele ca parte a datelor de intrare, nu ca etichetă separată, care este modul normal de utilizare a etichetelor cu modelele Keras. Veți vedea exemple în acest sens în partea 2 a cursului, unde definirea funcției de pierdere corecte poate fi complicată. Cu toate acestea, pentru clasificarea secvențelor, o funcție de pierdere Keras standard funcționează bine, așa că aceasta este cea pe care o vom utiliza aici. + + + +```py +from tensorflow.keras.losses import SparseCategoricalCrossentropy + +model.compile( + optimizer="adam", + loss=SparseCategoricalCrossentropy(from_logits=True), + metrics=["accuracy"], +) +model.fit( + tf_train_dataset, + validation_data=tf_validation_dataset, +) +``` + + + +Remarcați o problemă foarte des întâlnită aici - *puteți* doar să transmiteți numele pierderii ca șir de caractere către Keras, dar în mod implicit Keras va presupune că ați aplicat deja un softmax rezultatelor dvs. Cu toate acestea, multe modele produc valorile chiar înainte de aplicarea softmax, care sunt cunoscute și sub numele de *logits*. Trebuie să spunem funcției de pierdere că asta face modelul nostru, iar singura modalitate de a face acest lucru este să o apelăm direct, mai degrabă decât prin nume cu un șir. + + + + +### Îmbunătățirea performanțelor de instruire[[Îmbunătățirea-performanțelor-de-instruire]] + + + +Dacă încercați codul de mai sus, acesta rulează cu siguranță, dar veți constata că valoarea pierderii scade doar lent sau sporadic. Cauza principală +este *rata de învățare*. Ca și în cazul pierderii, atunci când îi transmitem lui Keras numele unui optimizator ca șir de caractere, Keras inițializează +optimizatorul respectiv cu valori implicite pentru toți parametrii, inclusiv rata de învățare. Totuși, din experiența îndelungată, știm +că modelele transformatoare beneficiază de o rată de învățare mult mai mică decât cea implicită pentru Adam, care este 1e-3, scrisă și +ca 10 la puterea -3, sau 0,001. 5e-5 (0,00005), care este de aproximativ douăzeci de ori mai mică, este un punct de plecare mult mai bun. + +În plus față de scăderea ratei de învățare, avem un al doilea as în mânecă: putem reduce încet rata de învățare +pe parcursul instruirii. În literatura de specialitate, veți vedea uneori că acest lucru este denumit *decădere* sau *analizare* +a ratei de învățare. În Keras, cel mai bun mod de a face acest lucru este de a utiliza un *programator al ratei de învățare*. Un program bun de utilizat este +`PolynomialDecay` - în ciuda numelui, cu setările implicite, acesta pur și simplu scade liniar rata de învățare de la valoarea inițială +până la valoarea finală pe parcursul instruirii, ceea ce este exact ceea ce ne dorim. Pentru a utiliza corect un programator, +totuși, trebuie să îi spunem cât va dura antrenamentul. Calculăm acest lucru ca `num_train_steps` mai jos. + +```py +from tensorflow.keras.optimizers.schedules import PolynomialDecay + +batch_size = 8 +num_epochs = 3 +# Numărul etapelor de formare este numărul de eșantioane din setul de date, împărțit la dimensiunea lotului, apoi înmulțit +# cu numărul total de epoci. Rețineți că setul de date tf_train_dataset de aici este un set de date tf.data.Dataset în loturi, +# nu setul de date original Hugging Face, deci len() este deja num_samples // batch_size. +num_train_steps = len(tf_train_dataset) * num_epochs +lr_scheduler = PolynomialDecay( + initial_learning_rate=5e-5, end_learning_rate=0.0, decay_steps=num_train_steps +) +from tensorflow.keras.optimizers import Adam + +opt = Adam(learning_rate=lr_scheduler) +``` + + + +Biblioteca Transformers 🤗 are, de asemenea, o funcție `create_optimizer()` care va crea un optimizator `AdamW` cu rata de învățare în scădere. Aceasta este o scurtătură convenabilă pe care o veți vedea în detaliu în secțiunile viitoare ale cursului. + + + +Acum avem optimizatorul nostru complet nou și putem încerca să ne antrenăm cu el. În primul rând, să reîncărcăm modelul, pentru a reseta modificările aduse ponderilor în urma instruirii pe care tocmai am efectuat-o, iar apoi îl putem compila cu noul optimizator: + +```py +import tensorflow as tf + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) +model.compile(optimizer=opt, loss=loss, metrics=["accuracy"]) +``` + +Acum, ne potrivim din nou: + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 Dacă doriți să încărcați automat modelul dvs. în Hub în timpul instruirii, puteți trece un `PushToHubCallback` în metoda `model.fit()`. Vom afla mai multe despre acest lucru în [Capitolul 4](/course/chapter4/3) + + + +### Predicțiile modelului[[predicțiile-modelului]] + + + + +Este foarte interesant să ne antrenăm și să vedem cum scade valoarea pierderii, dar ce se întâmplă dacă dorim să obținem rezultate de la modelul antrenat, fie pentru a calcula anumiți parametri, fie pentru a utiliza modelul în producție? Pentru a face acest lucru, putem folosi metoda `predict()`. Aceasta va returna *logit-urile* din rezultatele modelului, unul pentru fiecare clasă. + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +Putem converti aceste logiți în predicții de clasă ale modelului folosind `argmax` pentru a găsi cel mai mare logit, care corespunde celei mai probabile clase: + +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +```python out +(408, 2) (408,) +``` +Acum, să folosim `preds` pentru a calcula niște indicatori! Putem încărca indicatorii de măsurare asociați cu setul de date MRPC la fel de ușor cum am încărcat setul de date, de data aceasta cu funcția `evaluate.load()`. Obiectul returnat are o metodă `compute()` pe care o putem folosi pentru a face calculul metric: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"]) +``` + +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +Rezultatele exacte pe care le obțineți pot varia, deoarece inițializarea aleatorie a head-ului modelului poate schimba parametrii obținuți. Aici, putem vedea că modelul nostru are o precizie de 85,78% pe setul de validare și un scor F1 de 89,97. Acestea sunt cele două metrici utilizate pentru a evalua rezultatele pe setul de date MRPC pentru criteriul de referință GLUE. Tabelul din [lucrarea BERT] (https://arxiv.org/pdf/1810.04805.pdf) a raportat un scor F1 de 88,9 pentru modelul de bază. Acesta a fost modelul `uncased`, în timp ce noi folosim în prezent modelul `cased`, ceea ce explică rezultatul mai bun. + +Aceasta încheie introducerea la ajustarea fină utilizând API-ul Keras. În [Capitolul 7](/course/chapter7) va fi prezentat un exemplu de efectuare a acestui lucru pentru cele mai comune sarcini NLP. Dacă doriți să vă perfecționați abilitățile cu API-ul Keras, încercați să reglați un model pe setul de date GLUE SST-2, utilizând prelucrarea datelor pe care ați făcut-o în secțiunea 2. diff --git a/chapters/rum/chapter3/4.mdx b/chapters/rum/chapter3/4.mdx new file mode 100644 index 000000000..9477601a0 --- /dev/null +++ b/chapters/rum/chapter3/4.mdx @@ -0,0 +1,359 @@ +# O instruire completă[[o-instruire-completă]] + + + + + +Acum vom vedea cum să obținem aceleași rezultate ca în secțiunea anterioară, dar fără să folosim clasa `Trainer`. Din nou, presupunem că ați parcurs deja procesarea datelor din secțiunea 2. Iată un scurt rezumat al tot ceea ce veți avea nevoie: + +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) +``` + +### Pregătirea pentru antrenament[[pregătirea-pentru-antrenament]] + +Înainte de a scrie efectiv bucla de antrenament, va trebui să definim câteva obiecte. Primele sunt încărcătoarele de date (dataloaders) pe care le vom folosi pentru a itera pe loturi (batches). Dar, înainte de a putea defini acele încărcătoare de date, trebuie să aplicăm un pic de postprocesare dataset-urilor noastre `tokenized_datasets`, pentru a ne ocupa de câteva lucruri pe care `Trainer` le făcea automat pentru noi. Mai exact, trebuie să: + +- Eliminăm coloanele care corespund valorilor pe care modelul nu le așteaptă (cum ar fi coloanele `sentence1` și `sentence2`). +- Redenumim coloana `label` în `labels` (pentru că modelul se așteaptă ca argumentul să se numească `labels`). +- Setăm formatul dataset-urilor astfel încât să returneze tensori PyTorch în loc de liste. + +`tokenized_datasets` are câte o metodă pentru fiecare dintre acești pași: + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +Putem apoi să verificăm că rezultatul are doar coloanele pe care modelul le va accepta: + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +Acum, după ce am terminat acest pas, putem defini foarte ușor încărcătoarele de date: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +Pentru a verifica rapid că nu există nicio eroare în procesarea datelor, putem inspecta un batch astfel: + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +Observați că formele reale ar putea fi ușor diferite pentru voi, pentru că am setat `shuffle=True` în încărcătorul de date de antrenament și pentru că împachetăm (padding) la lungimea maximă în interiorul lotului. + +Acum că am terminat complet procesarea datelor (un obiectiv satisfăcător, dar uneori greu de atins pentru orice practician ML), să trecem la model. Îl instanțiem exact ca în secțiunea anterioară: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Pentru a ne asigura că totul va decurge fără probleme în timpul antrenamentului, trecem lotul nostru prin model: + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +Toate modelele 🤗 Transformers vor returna pierderea (loss) când `labels` sunt furnizate, și, de asemenea, obținem logits (două pentru fiecare intrare în lot, deci un tensor de mărimea 8 x 2). + +Suntem aproape gata să scriem bucla de antrenament! Ne mai lipsesc două lucruri: un optimizer și un scheduler pentru rata de învățare. Pentru că încercăm să reproducem ceea ce făcea `Trainer`, vom folosi aceleași valori implicite. Optimizer-ul folosit de `Trainer` este `AdamW`, care este același cu Adam, dar cu o abordare particulară pentru regularizarea weight decay (vedeți lucrarea ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) de Ilya Loshchilov și Frank Hutter): + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +În final, scheduler-ul pentru rata de învățare folosit implicit este doar o descreștere liniară de la valoarea maximă (5e-5) la 0. Pentru a-l defini corect, trebuie să știm numărul de pași de antrenament pe care îi vom face, care este numărul de epoci dorit înmulțit cu numărul de loturi de antrenament (care este lungimea dataloader-ului nostru de antrenament). `Trainer` folosește trei epoci implicit, așa că vom urma acest exemplu: + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### Bucla de antrenament[[bucla-de-antrenament]] + +Încă un lucru: vom dori să folosim GPU-ul dacă avem acces la unul (pe un CPU, antrenamentul poate dura câteva ore în loc de câteva minute). Pentru asta, definim un `device` pe care vom pune modelul și loturile noastre: + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +Acum suntem gata de antrenament! Pentru a ne face o idee despre momentul în care se va termina antrenamentul, adăugăm o bară de progres peste numărul de pași de antrenament, folosind biblioteca `tqdm`: + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Observați că partea principală a buclei de antrenament arată foarte asemănător cu cea din introducere. Nu am cerut niciun raport, așa că această buclă de antrenament nu ne va spune nimic despre performanța modelului. Pentru a avea feedback, trebuie să adăugăm o buclă de evaluare. + + +### Bucla de evaluare[[ bucla-de-evaluare]] + +Ca și înainte, vom folosi o metrică oferită de biblioteca 🤗 Evaluate. Am văzut deja metoda `metric.compute()`, dar metricle pot de fapt să acumuleze loturi pentru noi în timp ce parcurgem bucla de predicție, cu metoda `add_batch()`. Odată ce am acumulat toate loturile, putem obține rezultatul final cu `metric.compute()`. Iată cum să implementăm toate acestea într-o buclă de evaluare: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +Din nou, rezultatele voastre vor fi ușor diferite din cauza aleatorietății în inițializarea layer-ului final (model head) și a amestecării datelor, dar ar trebui să fie în aceeași zonă valorică. + + + +✏️ **Încercați!** Modificați bucla de antrenament anterioară pentru a vă rafina modelul pe dataset-ul SST-2. + + + +### Îmbunătățiți circuitul de antrenament cu 🤗 Accelerate[[îmbunătățiți-circuitul-de-antrenament-cu-accelerate]] + + + +Bucla de antrenament pe care am definit-o anterior funcționează bine pe un singur CPU sau GPU. Dar, folosind biblioteca [🤗 Accelerate](https://github.com/huggingface/accelerate), cu doar câteva ajustări putem activa antrenarea distribuită pe mai multe GPU-uri sau TPU-uri. Pornind de la crearea încărcătoarelor de date de antrenament și validare, iată cum arată bucla noastră manuală de antrenament: + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Iar aici sunt modificările: + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Prima linie de adăugat este linia de import. A doua linie instanțiază un obiect `Accelerator` care va examina mediul și va inițializa setarea distribuită corespunzătoare. 🤗 Accelerate se ocupă de plasarea pe device pentru voi, așa că puteți elimina liniile care pun modelul pe device (sau, dacă preferați, le puteți schimba să folosească `accelerator.device` în loc de `device`). + +Apoi, partea principală a muncii este făcută în linia care trimite dataloaders, modelul și optimizer-ul la `accelerator.prepare()`. Aceasta va împacheta acele obiecte în containerul potrivit pentru a vă asigura că antrenarea distribuită funcționează corespunzător. Restul modificărilor constau în eliminarea liniei care mută lotul pe `device` (din nou, dacă doriți să o păstrați puteți doar să o schimbați să folosească `accelerator.device`) și înlocuirea `loss.backward()` cu `accelerator.backward(loss)`. + + +⚠️ Pentru a beneficia de creșterea vitezei oferită de Cloud TPU-uri, vă recomandăm să împachetați mostrele la o lungime fixă folosind argumentele `padding="max_length"` și `max_length` ale tokenizer-ului. + + +Dacă vreți să copiați și să lipiți pentru a vă juca, iată cum arată bucla completă de antrenament cu 🤗 Accelerate: + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Plasând acest cod într-un fișier `train.py` îl face rulabil pe orice tip de configurare distribuită. Pentru a-l încerca în configurarea voastră distribuită, rulați comanda: + +```bash +accelerate config +``` + +care vă va cere să răspundeți la câteva întrebări și vă va crea un fișier de configurare folosit de comanda: + +``` +accelerate launch train.py +``` + +care va porni antrenarea distribuită. + +Dacă vreți să încercați asta într-un Notebook (de exemplu, pentru a-l testa cu TPU-uri pe Colab), doar lipiți codul într-o funcție `training_function()` și rulați într-o celulă finală: + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +Puteți găsi mai multe exemple în [repo-ul 🤗 Accelerate](https://github.com/huggingface/accelerate/tree/main/examples). \ No newline at end of file diff --git a/chapters/rum/chapter3/5.mdx b/chapters/rum/chapter3/5.mdx new file mode 100644 index 000000000..acd5cfe51 --- /dev/null +++ b/chapters/rum/chapter3/5.mdx @@ -0,0 +1,25 @@ + + +# Fine-tuning, Check![[fine-tuning-check]] + + + +A fost distractiv! În primele două capitole ați învățat despre modele și tokenizere, iar acum știți cum să le ajustați fin (fine-tune) pentru propriile date. Pentru a recapitula, în acest capitol ați: + +{#if fw === 'pt'} +* Ați învățat despre dataset-urile din [Hub](https://huggingface.co/datasets) +* Ați învățat cum să încărcați și să preprocessați dataset-uri, inclusiv folosind împachetare dinamică (dynamic padding) și colatori +* Ați implementat propria ajustare fină (fine-tuning) și evaluare a unui model +* Ați implementat o buclă de antrenament la un nivel mai scăzut (lower-level) +* Ați folosit 🤗 Accelerate pentru a adapta cu ușurință bucla voastră de antrenament, astfel încât să funcționeze pentru mai multe GPU-uri sau TPU-uri + +{:else} +* Ați învățat despre dataset-urile din [Hub](https://huggingface.co/datasets) +* Ați învățat cum să încărcați și să preprocessați dataset-uri +* Ați învățat cum să ajustați fin și să evaluați un model cu Keras +* Ați implementat o metrică personalizată + +{/if} diff --git a/chapters/rum/chapter3/6.mdx b/chapters/rum/chapter3/6.mdx new file mode 100644 index 000000000..225615087 --- /dev/null +++ b/chapters/rum/chapter3/6.mdx @@ -0,0 +1,301 @@ + + + + +# End-of-chapter quiz[[end-of-chapter-quiz]] + + + +Testați-vă cunoștințele din acest capitol! + +### 1. Dataset-ul `emotion` conține mesaje de pe Twitter etichetate cu emoții. Căutați-l în [Hub](https://huggingface.co/datasets) și citiți descrierea dataset-ului. Care dintre acestea nu este una dintre emoțiile sale de bază? + + + +### 2. Căutați dataset-ul `ar_sarcasm` în [Hub](https://huggingface.co/datasets). Ce tip de sarcină suportă? + +descrierea dataset-ului!" + }, + { + text: "Named entity recognition", + explain: "Nu este corect — aruncați din nou o privire la descrierea dataset-ului!" + }, + { + text: "Răspuns la întrebări (Question answering)", + explain: "Din păcate, nu ați răspuns corect. Încercați din nou!" + } + ]} +/> + +### 3. Cum se așteaptă modelul BERT ca o pereche de propoziții să fie procesată? + +[SEP] pentru a separa cele două propoziții, dar nu este singurul lucru necesar!" + }, + { + text: "[CLS] Tokens_of_sentence_1 Tokens_of_sentence_2", + explain: "Este nevoie de un token special [CLS] la început, dar nu este singurul lucru necesar!" + }, + { + text: "[CLS] Tokens_of_sentence_1 [SEP] Tokens_of_sentence_2 [SEP]", + explain: "Corect!", + correct: true + }, + { + text: "[CLS] Tokens_of_sentence_1 [SEP] Tokens_of_sentence_2", + explain: "Este nevoie atât de un token special [CLS] la început, cât și de un token special [SEP] pentru a separa cele două propoziții, dar mai lipsește ceva!" + } + ]} +/> + +{#if fw === 'pt'} +### 4. Care sunt avantajele metodei `Dataset.map()`? + + + +### 5. Ce înseamnă umplerea dinamică (dynamic padding)? + + + +### 6. Care este scopul unei funcții de „collate”? + +DataCollatorWithPadding în mod specific." + }, + { + text: "Reunește toate probele într-un lot.", + explain: "Corect! Puteți transmite funcția de collate ca argument al unui DataLoader. Noi am folosit funcția DataCollatorWithPadding, care împachetează toate elementele dintr-un lot astfel încât să aibă aceeași lungime.", + correct: true + }, + { + text: "Preprocesează întregul set de date.", + explain: "Aceasta ar fi o funcție de preprocessing, nu o funcție de collate." + }, + { + text: "Trunchiază secvențele din setul de date.", + explain: "O funcție de collate se ocupă de manipularea loturilor individuale, nu a întregului set de date. Dacă sunteți interesați de trunchiere, puteți folosi argumentul truncate al tokenizer." + } + ]} +/> + +### 7. Ce se întâmplă când instanțiați una dintre clasele `AutoModelForXxx` cu un model de limbaj preantrenat (cum ar fi `bert-base-uncased`), care corespunde unei alte sarcini decât cea pentru care a fost antrenat? + +AutoModelForSequenceClassification cu bert-base-uncased, am primit avertismente la instanțierea modelului. Head-ul preantrenat nu este folosit pentru sarcina de clasificare secvențială, așa că este eliminat și un nou head este instanțiat cu greutăți inițializate aleator.", + correct: true + }, + { + text: "Head-ul modelului preantrenat este eliminat.", + explain: "Mai trebuie să se întâmple și altceva. Încercați din nou!" + }, + { + text: "Nimic, pentru că modelul poate fi ajustat fin (fine-tuned) chiar și pentru o altă sarcină.", + explain: "Head-ul preantrenat al modelului nu a fost antrenat pentru această sarcină, deci trebuie eliminat!" + } + ]} +/> + +### 8. Care este scopul folosirii `TrainingArguments`? + +Trainer.", + explain: "Corect!", + correct: true + }, + { + text: "Specifică dimensiunea modelului.", + explain: "Dimensiunea modelului este definită de configurația modelului, nu de clasa TrainingArguments." + }, + { + text: "Conține doar hiperparametrii folosiți pentru evaluare.", + explain: "În exemplu, am specificat și unde va fi salvat modelul și checkpoint-urile. Încercați din nou!" + }, + { + text: "Conține doar hiperparametrii folosiți pentru antrenare.", + explain: "În exemplu, am folosit și un evaluation_strategy, așadar acest lucru afectează evaluarea. Încercați din nou!" + } + ]} +/> + +### 9. De ce ar trebui să folosiți biblioteca 🤗 Accelerate? + +Trainer, nu cu biblioteca 🤗 Accelerate. Încercați din nou!" + }, + { + text: "Face ca buclele noastre de antrenament să funcționeze pe strategii distribuite.", + explain: "Corect! Cu 🤗 Accelerate, buclele voastre de antrenament vor funcționa pentru mai multe GPU-uri și TPU-uri.", + correct: true + }, + { + text: "Oferă mai multe funcții de optimizare.", + explain: "Nu, biblioteca 🤗 Accelerate nu oferă nicio funcție de optimizare." + } + ]} +/> + +{:else} +### 4. Ce se întâmplă când instanțiați una dintre clasele `TFAutoModelForXxx` cu un model de limbaj preantrenat (cum ar fi `bert-base-uncased`), care corespunde unei alte sarcini decât cea pentru care a fost antrenat? + +TFAutoModelForSequenceClassification cu bert-base-uncased, am primit avertismente la instanțierea modelului. Head-ul preantrenat nu este folosit pentru sarcina de clasificare secvențială, așa că este eliminat și un nou head este instanțiat cu greutăți inițializate aleator.", + correct: true + }, + { + text: "Head-ul modelului preantrenat este eliminat.", + explain: "Mai trebuie să se întâmple și altceva. Încercați din nou!" + }, + { + text: "Nimic, pentru că modelul poate fi ajustat fin (fine-tuned) chiar și pentru o altă sarcină.", + explain: "Head-ul preantrenat al modelului nu a fost antrenat pentru această sarcină, deci trebuie eliminat!" + } + ]} +/> + +### 5. Modelele TensorFlow din `transformers` sunt deja modele Keras. Ce avantaj oferă acest lucru? + +TPUStrategy, inclusiv inițializarea modelului." + }, + { + text: "Puteți valorifica metodele existente precum compile(), fit() și predict().", + explain: "Corect! Odată ce aveți datele, antrenarea necesită foarte puțin efort.", + correct: true + }, + { + text: "Învățați atât Keras, cât și transformers.", + explain: "Corect, dar căutăm altceva :)", + correct: true + }, + { + text: "Puteți calcula cu ușurință metrici legate de dataset.", + explain: "Keras ne ajută la antrenarea și evaluarea modelului, nu la calcularea metricilor legate de dataset." + } + ]} +/> + +### 6. Cum vă puteți defini propria metrică personalizată? + +tf.keras.metrics.Metric.", + explain: "Perfect!", + correct: true + }, + { + text: "Folosind API-ul funcțional Keras.", + explain: "Mai încearcă!" + }, + { + text: "Folosind o funcție apelabilă cu semnătura metric_fn(y_true, y_pred).", + explain: "Corect!", + correct: true + }, + { + text: "Căutând pe Google.", + explain: "Nu este răspunsul pe care îl căutăm, dar probabil v-ar putea ajuta să-l găsiți.", + correct: true + } + ]} +/> + +{/if} \ No newline at end of file From 3b79756af2d004b5885b3fad7df31d440c827c68 Mon Sep 17 00:00:00 2001 From: Angroys <120798951+Angroys@users.noreply.github.com> Date: Thu, 9 Jan 2025 00:24:58 +0200 Subject: [PATCH 08/37] Done the first three sections of the 7th chapter --- chapters/ro/{chapter 6 => chapter6}/1.mdx | 0 chapters/ro/{chapter 6 => chapter6}/10.mdx | 0 chapters/ro/{chapter 6 => chapter6}/2.mdx | 0 chapters/ro/{chapter 6 => chapter6}/3.mdx | 0 chapters/ro/{chapter 6 => chapter6}/3b.mdx | 0 chapters/ro/{chapter 6 => chapter6}/4.mdx | 0 chapters/ro/{chapter 6 => chapter6}/5.mdx | 0 chapters/ro/{chapter 6 => chapter6}/6.mdx | 0 chapters/ro/{chapter 6 => chapter6}/7.mdx | 0 chapters/ro/{chapter 6 => chapter6}/8.mdx | 0 chapters/ro/{chapter 6 => chapter6}/9.mdx | 0 chapters/ro/chapter7/1.mdx | 38 + chapters/ro/chapter7/2.mdx | 983 ++++++++++++++++ chapters/ro/chapter7/3.mdx | 1044 +++++++++++++++++ chapters/ro/chapter7/4.mdx | 1002 ++++++++++++++++ chapters/ro/chapter7/5.mdx | 1072 +++++++++++++++++ chapters/ro/chapter7/6.mdx | 914 +++++++++++++++ chapters/ro/chapter7/7.mdx | 1203 ++++++++++++++++++++ chapters/ro/chapter7/8.mdx | 22 + chapters/ro/chapter7/9.mdx | 329 ++++++ 20 files changed, 6607 insertions(+) rename chapters/ro/{chapter 6 => chapter6}/1.mdx (100%) rename chapters/ro/{chapter 6 => chapter6}/10.mdx (100%) rename chapters/ro/{chapter 6 => chapter6}/2.mdx (100%) rename chapters/ro/{chapter 6 => chapter6}/3.mdx (100%) rename chapters/ro/{chapter 6 => chapter6}/3b.mdx (100%) rename chapters/ro/{chapter 6 => chapter6}/4.mdx (100%) rename chapters/ro/{chapter 6 => chapter6}/5.mdx (100%) rename chapters/ro/{chapter 6 => chapter6}/6.mdx (100%) rename chapters/ro/{chapter 6 => chapter6}/7.mdx (100%) rename chapters/ro/{chapter 6 => chapter6}/8.mdx (100%) rename chapters/ro/{chapter 6 => chapter6}/9.mdx (100%) create mode 100644 chapters/ro/chapter7/1.mdx create mode 100644 chapters/ro/chapter7/2.mdx create mode 100644 chapters/ro/chapter7/3.mdx create mode 100644 chapters/ro/chapter7/4.mdx create mode 100644 chapters/ro/chapter7/5.mdx create mode 100644 chapters/ro/chapter7/6.mdx create mode 100644 chapters/ro/chapter7/7.mdx create mode 100644 chapters/ro/chapter7/8.mdx create mode 100644 chapters/ro/chapter7/9.mdx diff --git a/chapters/ro/chapter 6/1.mdx b/chapters/ro/chapter6/1.mdx similarity index 100% rename from chapters/ro/chapter 6/1.mdx rename to chapters/ro/chapter6/1.mdx diff --git a/chapters/ro/chapter 6/10.mdx b/chapters/ro/chapter6/10.mdx similarity index 100% rename from chapters/ro/chapter 6/10.mdx rename to chapters/ro/chapter6/10.mdx diff --git a/chapters/ro/chapter 6/2.mdx b/chapters/ro/chapter6/2.mdx similarity index 100% rename from chapters/ro/chapter 6/2.mdx rename to chapters/ro/chapter6/2.mdx diff --git a/chapters/ro/chapter 6/3.mdx b/chapters/ro/chapter6/3.mdx similarity index 100% rename from chapters/ro/chapter 6/3.mdx rename to chapters/ro/chapter6/3.mdx diff --git a/chapters/ro/chapter 6/3b.mdx b/chapters/ro/chapter6/3b.mdx similarity index 100% rename from chapters/ro/chapter 6/3b.mdx rename to chapters/ro/chapter6/3b.mdx diff --git a/chapters/ro/chapter 6/4.mdx b/chapters/ro/chapter6/4.mdx similarity index 100% rename from chapters/ro/chapter 6/4.mdx rename to chapters/ro/chapter6/4.mdx diff --git a/chapters/ro/chapter 6/5.mdx b/chapters/ro/chapter6/5.mdx similarity index 100% rename from chapters/ro/chapter 6/5.mdx rename to chapters/ro/chapter6/5.mdx diff --git a/chapters/ro/chapter 6/6.mdx b/chapters/ro/chapter6/6.mdx similarity index 100% rename from chapters/ro/chapter 6/6.mdx rename to chapters/ro/chapter6/6.mdx diff --git a/chapters/ro/chapter 6/7.mdx b/chapters/ro/chapter6/7.mdx similarity index 100% rename from chapters/ro/chapter 6/7.mdx rename to chapters/ro/chapter6/7.mdx diff --git a/chapters/ro/chapter 6/8.mdx b/chapters/ro/chapter6/8.mdx similarity index 100% rename from chapters/ro/chapter 6/8.mdx rename to chapters/ro/chapter6/8.mdx diff --git a/chapters/ro/chapter 6/9.mdx b/chapters/ro/chapter6/9.mdx similarity index 100% rename from chapters/ro/chapter 6/9.mdx rename to chapters/ro/chapter6/9.mdx diff --git a/chapters/ro/chapter7/1.mdx b/chapters/ro/chapter7/1.mdx new file mode 100644 index 000000000..82f8c9611 --- /dev/null +++ b/chapters/ro/chapter7/1.mdx @@ -0,0 +1,38 @@ + + +# Introduction[[introduction]] + + + +În [Capitolul 3](/course/chapter3), ați văzut cum să faceți fine-tune unui model pentru clasificarea textului. În acest capitol, vom aborda următoarele sarcini NLP: + +- Clasificarea tokenilor +- Masked language modeling (precum BERT) +- Sumarizare +- Traducere +- Preantrenare pentru causal language modeling (precum GPT-2) +- Răspunderea la întrebări + +{#if fw === 'pt'} + +Pentru a face acest lucru, va trebui să valorificați tot ceea ce ați învățat despre API-ul `Trainer` și biblioteca 🤗 Accelerate în [Capitolul 3](/course/chapter3), biblioteca 🤗 Datasets în [Capitolul 5](/course/chapter5) și biblioteca 🤗 Tokenizers în [Capitolul 6](/course/chapter6). De asemenea, vom încărca rezultatele noastre în Model Hub, așa cum am făcut în [Capitolul 4](/course/chapter4), astfel încât acesta este cu adevărat capitolul în care totul se unește! + +Fiecare secțiune poate fi citită independent și vă va arăta cum să antrenați un model cu API-ul `Trainer` sau cu propria buclă de antrenament, utilizând 🤗 Accelerate. Nu ezitați să săriți peste oricare dintre cele două părți și să vă concentrați pe cea care vă interesează cel mai mult: API-ul `Trainer` este excelent pentru fine-tuning sau antrenarea modelului vostru fără a vă face griji cu privire la ceea ce se întâmplă în spatele scenei, în timp ce bucla de antrenament cu `Accelerate` vă va permite să personalizați mai ușor orice parte doriți. + +{:else} + +Pentru a face acest lucru, va trebui să valorificați tot ceea ce ați învățat despre API-ul `Trainer` și biblioteca 🤗 Accelerate în [Capitolul 3](/course/chapter3), biblioteca 🤗 Datasets în [Capitolul 5](/course/chapter5) și biblioteca 🤗 Tokenizers în [Capitolul 6](/course/chapter6). De asemenea, vom încărca rezultatele noastre în Model Hub, așa cum am făcut în [Capitolul 4](/course/chapter4), astfel încât acesta este cu adevărat capitolul în care totul se unește! + +Fiecare secțiune poate fi citită independent. + +{/if} + + + + +Dacă citiți secțiunile în succesiune, veți observa că acestea au destul de mult cod și proză în comun. Repetarea este intenționată, pentru a vă permite să intrați (sau să reveniți mai târziu) la orice sarcină care vă interesează și să găsiți un exemplu. + + diff --git a/chapters/ro/chapter7/2.mdx b/chapters/ro/chapter7/2.mdx new file mode 100644 index 000000000..108806a81 --- /dev/null +++ b/chapters/ro/chapter7/2.mdx @@ -0,0 +1,983 @@ + + +# Clasificarea tokenilor[[token-classification]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Prima aplicație pe care o vom explora este clasificarea tokenilor. Această sarcină generică cuprinde orice problemă care poate fi formulată ca "atribuirea unui label fiecărui token dintr-o propoziție", cum ar fi: + +- **Named entity recognition(NER)**: Găsirea entităților (cum ar fi persoane, locații sau organizații) într-o propoziție. Acest lucru poate fi formulat ca atribuirea unui label fiecărui token, având o clasă pentru fiecare entitate și o clasă pentru "nicio entitate". +- **Part-of-speech tagging (POS)**: Marchează fiecare cuvânt dintr-o propoziție ca corespunzând unei anumite părți de vorbire (cum ar fi substantiv, verb, adjectiv etc.). +- **Chunking**: Găsirea tokenilor care aparțin aceleiași entități. Această sarcină (care poate fi combinată cu POS sau NER) poate fi formulată ca atribuirea unui label (de obicei `B-`) tuturor tokenilor care se află la începutul unui chunk, a unui alt label (de obicei `I-`) la tokeni care se află în interiorul unui chunk și a unui al treilea label (de obicei `O`) la tokeni care nu aparțin niciunui chunk. + + + +Desigur, există multe alte tipuri de probleme de clasificare a tokenilor; acestea sunt doar câteva exemple reprezentative. În această secțiune, vom pune la punct un model (BERT) pe o sarcină NER, care va fi apoi capabil să calculeze predicții precum aceasta: + + + + +One-hot encoding labels pentru răspunderea la întrebări. + + + +Puteți găsi modelul pe care îl vom antrena și încărca în Hub și puteți verifica de două ori predicțiile sale [aici](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+name+is+Sylvain+and+I+work+at+Hugging+Face+in+Brooklyn). + +## Pregătirea datelor[[preparing-the-data]] + +În primul rând, avem nevoie de un dataset adecvat pentru clasificarea simbolurilor. În această secțiune vom utiliza [datasetul CoNLL-2003] (https://huggingface.co/datasets/conll2003), care conține știri de la Reuters. + + + +💡 Atât timp cât datasetul vostru constă în texte împărțite în cuvinte cu labelurile corespunzătoare, veți putea adapta procedurile de preprocesare a datelor descrise aici la propriul dataset. Consultați [Capitolul 5](/course/chapter5) dacă aveți nevoie de o recapitulare a modului de încărcare a propriilor date personalizate într-un `Dataset`. + + + +### Datasetul CoNLL-2003 [[the-conll-2003-dataset]] + +Pentru a încărca dataetul CoNLL-2003, folosim metoda `load_dataset()` din biblioteca 🤗 Datasets: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("conll2003") +``` + +Acest lucru va descărca și va stoca în cache datasetul, așa cum am văzut în [Capitolul 3](/course/chapter3) pentru setul de date GLUE MRPC. Inspectarea acestui obiect ne arată coloanele prezente și împărțirea între seturile de antrenare, validare și testare: + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], + num_rows: 14041 + }) + validation: Dataset({ + features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], + num_rows: 3250 + }) + test: Dataset({ + features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], + num_rows: 3453 + }) +}) +``` + +În special, putem vedea că datasetul conține labeluri pentru cele trei sarcini pe care le-am menționat mai devreme: NER, POS și Chunking. O mare diferență față de alte datseturi este că textele de intrare nu sunt prezentate ca propoziții sau documente, ci ca liste de cuvinte (ultima coloană se numește `tokens`, dar conține cuvinte în sensul că acestea sunt inputuri pre-tokenizate care mai trebuie să treacă prin tokenizer pentru tokenizarea subcuvintelor). + +Să aruncăm o privire la primul element al setului de formare: + +```py +raw_datasets["train"][0]["tokens"] +``` + +```python out +['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'] +``` + +Deoarece dorim să efectuăm named entity recognition, ne vom uita la etichetele NER: + +```py +raw_datasets["train"][0]["ner_tags"] +``` + +```python out +[3, 0, 7, 0, 0, 0, 7, 0, 0] +``` + +Acestea sunt labelurile ca numere întregi pregătite pentru antrenare, dar nu sunt neapărat utile atunci când dorim să inspectăm datele. La fel ca în cazul clasificării textului, putem accesa corespondența dintre aceste numere întregi și numele labelurilor consultând atributul `features` al datasetului nostru: + +```py +ner_feature = raw_datasets["train"].features["ner_tags"] +ner_feature +``` + +```python out +Sequence(feature=ClassLabel(num_classes=9, names=['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'], names_file=None, id=None), length=-1, id=None) +``` + +Deci, această coloană conține elemente care sunt secvențe a `ClassLabel`. Tipul elementelor din secvență se află în atributul `feature` al acestei `ner_feature`, iar noi putem accesa lista de nume consultând atributul `names` al acelei `feature`: + +```py +label_names = ner_feature.feature.names +label_names +``` + +```python out +['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'] +``` + +Am văzut deja aceste labeluri atunci când am cercetat pipelineul `token-classification` în [Capitolul 6](/course/chapter6/3), dar pentru o reîmprospătare rapidă: + +- `O` înseamnă că cuvântul nu corespunde niciunei entități. +- `B-PER`/`I-PER` înseamnă că cuvântul corespunde începutului/este în interiorul unei entități de tip *persoană*. +- `B-ORG`/`I-ORG` înseamnă că cuvântul corespunde începutului/este în interiorul unei entități de tip *organizaționale*. +- `B-LOC`/`I-LOC` înseamnă că cuvântul corespunde începutului/este în interiorul unei entități de tip *locație*. +- `B-MISC`/`I-MISC` înseamnă că cuvântul corespunde începutului/este în interiorul unei entități de tip *miscellaneous. + +Acum, decodificarea labelurilor pe care le-am văzut mai devreme ne dă următorul rezultat: + +```python +words = raw_datasets["train"][0]["tokens"] +labels = raw_datasets["train"][0]["ner_tags"] +line1 = "" +line2 = "" +for word, label in zip(words, labels): + full_label = label_names[label] + max_length = max(len(word), len(full_label)) + line1 += word + " " * (max_length - len(word) + 1) + line2 += full_label + " " * (max_length - len(full_label) + 1) + +print(line1) +print(line2) +``` + +```python out +'EU rejects German call to boycott British lamb .' +'B-ORG O B-MISC O O O B-MISC O O' +``` + +Și pentru un exemplu de amestecare a labelurilor `B-` și `I-`, iată ce ne oferă același cod pentru elementul din setul de antrenare de la indexul 4: + +```python out +'Germany \'s representative to the European Union \'s veterinary committee Werner Zwingmann said on Wednesday consumers should buy sheepmeat from countries other than Britain until the scientific advice was clearer .' +'B-LOC O O O O B-ORG I-ORG O O O B-PER I-PER O O O O O O O O O O O B-LOC O O O O O O O' +``` + +După cum putem vedea, entităților care cuprind două cuvinte, precum "Uniunea Europeană" și "Werner Zwingmann", li se atribuie un label `B-` pentru primul cuvânt și un label `I-` pentru al doilea. + + + +✏️ **E rândul tău!** Afișați aceleași două propoziții cu labelurile POS sau chunking. + + + +### Procesarea datelor[[processing-the-data]] + + + +Ca de obicei, textele noastre trebuie să fie convertite în ID-uri token înainte ca modelul să le poată înțelege. După cum am văzut în [Capitolul 6](/course/chapter6/), o mare diferență în cazul sarcinilor de clasificare a tokenilor este că avem inputuri pre-tokenizate. Din fericire, API-ul tokenizerului poate face față acestei situații destul de ușor; trebuie doar să avertizăm `tokenizerul` cu un indicator special. + +Pentru început, hai să creăm obiectul `tokenizer`. După cum am mai spus, vom utiliza un model BERT preantrenat, deci vom începe prin a descărca și a stoca în cache tokenizerul asociat: + +```python +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Puteți înlocui `model_checkpoint` cu orice alt model preferat din [Hub](https://huggingface.co/models) sau cu un folder local în care ați salvat un model preantreant și un tokenizer. Singura constrângere este că tokenizerul trebuie să fie susținut de biblioteca 🤗 Tokenizers, pentru ca să existe o versiune "rapidă" disponibilă. 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`: + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +Pentru a tokeniza un input pre-tokenizat, putem utiliza `tokenizer` ca de obicei și să adăugăm `is_split_into_words=True`: + +```py +inputs = tokenizer(raw_datasets["train"][0]["tokens"], is_split_into_words=True) +inputs.tokens() +``` + +```python out +['[CLS]', 'EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'la', '##mb', '.', '[SEP]'] +``` + +După cum putem vedea, tokenizerul a adăugat tokenii speciali utilizați de model (`[CLS]` la început și `[SEP]` la sfârșit) și a lăsat majoritatea cuvintelor neatinse. Cu toate acestea, cuvântul `lamb` a fost tokenizat în două subcuvinte, `la` și `##mb`. Acest lucru introduce o neconcordanță între inputurile noastre și labeluri: lista de labeluri are doar 9 elemente, în timp ce inputurile noastre au acum 12 tokeni. Să luăm în considerare tokenii speciali este ușor (știm că sunt la început și la sfârșit), dar trebuie să ne asigurăm că aliniem toate labelurile cu cuvintele corespunzătoare. + +Din fericire, pentru că folosim un tokenizer rapid, avem acces la superputerile 🤗 Tokenizers, ceea ce înseamnă că putem mapa cu ușurință fiecare token la cuvântul corespunzător (după cum se vede în [Capitolul 6](/course/chapter6/3)): + +```py +inputs.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] +``` + +Cu puțină muncă, putem apoi extinde lista de labeluri pentru a se potrivi cu tokenii. Prima regulă pe care o vom aplica este că tokenii special primesc un label `-100`. Acest lucru se datorează faptului că în mod implicit `-100` este un indice care este ignorat în funcția de pierdere pe care o vom utiliza (cross entropy). Apoi, fiecare token primește același label ca și tokenul care a inițiat cuvântul în care se află, deoarece fac parte din aceeași entitate. Pentru tokenii din interiorul unui cuvânt, dar care nu se află la început, înlocuim `B-` cu `I-` (deoarece tokenul nu începe entitatea): + + +```python +def align_labels_with_tokens(labels, word_ids): + new_labels = [] + current_word = None + for word_id in word_ids: + if word_id != current_word: + # Start of a new word! + current_word = word_id + label = -100 if word_id is None else labels[word_id] + new_labels.append(label) + elif word_id is None: + # Special token + new_labels.append(-100) + else: + # Same word as previous token + label = labels[word_id] + # If the label is B-XXX we change it to I-XXX + if label % 2 == 1: + label += 1 + new_labels.append(label) + + return new_labels +``` + +Hai să îl încercăm cu prima noastră propoziție: + +```py +labels = raw_datasets["train"][0]["ner_tags"] +word_ids = inputs.word_ids() +print(labels) +print(align_labels_with_tokens(labels, word_ids)) +``` + +```python out +[3, 0, 7, 0, 0, 0, 7, 0, 0] +[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100] +``` + +După cum putem vedea, funcția noastră a adăugat `-100` pentru cei doi tokeni speciali de la început și de la sfârșit, și un nou `0` pentru cuvântul nostru care a fost împărțit în doi tokeni. + + + +✏️ ** Rândul tău!** Unii cercetători preferă să atribuie un singur label pe cuvânt și să atribuie `-100` celorlalți subtokeni dintr-un cuvânt dat. Aceasta are loc pentru a evita ca cuvintele lungi care se împart în mai mulți subtokeni să contribuie puternic la pierdere. Modificați funcția anterioară pentru a alinia labelurile cu ID-urile de input urmând această regulă. + + + +Pentru a preprocesa întregul nostru dataset, trebuie să tokenizăm toate inputurile și să aplicăm `align_labels_with_tokens()` pe toate labelurile. Pentru a profita de viteza tokenizerului nostru rapid, este mai bine să tokenizăm multe texte în același timp, așa că vom scrie o funcție care procesează o listă de exemple și vom folosi metoda `Dataset.map()` cu opțiunea `batched=True`. Singurul lucru diferit față de exemplul nostru anterior este că funcția `word_ids()` trebuie să obțină indexul exemplului din care dorim ID-urile cuvintelor atunci când inputurile către tokenizer sunt liste de texte (sau, în cazul nostru, liste de liste de cuvinte), așa că adăugăm și acest lucru: + +```py +def tokenize_and_align_labels(examples): + tokenized_inputs = tokenizer( + examples["tokens"], truncation=True, is_split_into_words=True + ) + all_labels = examples["ner_tags"] + new_labels = [] + for i, labels in enumerate(all_labels): + word_ids = tokenized_inputs.word_ids(i) + new_labels.append(align_labels_with_tokens(labels, word_ids)) + + tokenized_inputs["labels"] = new_labels + return tokenized_inputs +``` + +Rețineți că nu am completat încă inputurile; vom face acest lucru mai târziu, atunci când vom crea batchurile cu un data collator. + +Acum putem aplica toate aceste preprocesări dintr-o dată celorlalte fracțiuni ale datasetului nostru: + +```py +tokenized_datasets = raw_datasets.map( + tokenize_and_align_labels, + batched=True, + remove_columns=raw_datasets["train"].column_names, +) +``` + +Am făcut partea cea mai grea! Acum, că datele au fost preprocesate, antrenarea efectivă va semăna mult cu ceea ce am făcut în [Capitolul 3](/course/chapter3). + +{#if fw === 'pt'} + +## Fine-tuningul modelului cu `Trainer` API[[fine-tuning-the-model-with-the-trainer-api]] + +Codul real care utilizează `Trainer` va fi același ca înainte; singurele modificări sunt modul în care datele sunt collated într-un batch și metric computation function. + +{:else} + +## Fine-tuningul modelului cu Keras[[fine-tuning-the-model-with-keras]] + +Codul real care utilizează `Keras` va fi același ca înainte; singurele modificări sunt modul în care datele sunt collated într-un batch și metric computation function. + +{/if} + + +### Data collation[[data-collation]] + +Nu putem folosi doar un `DataCollatorWithPadding` ca în [Capitolul 3](/course/chapter3) deoarece acesta doar adaugă padding inputurilor(input IDs, attention mask, and token type IDs). Aici labelurile noastre ar trebui să fie padded exact în același mod ca și inputurile, astfel încât să rămână de aceeași dimensiune, folosind `-100` ca valoare, astfel încât predicțiile corespunzătoare să fie ignorate în calculul pierderilor. + +Toate acestea sunt realizate de un [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification). La fel ca `DataCollatorWithPadding`, acesta ia `tokenizer`-ul folosit pentru preprocesarea inputurilor: + +{#if fw === 'pt'} + +```py +from transformers import DataCollatorForTokenClassification + +data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer) +``` + +{:else} + +```py +from transformers import DataCollatorForTokenClassification + +data_collator = DataCollatorForTokenClassification( + tokenizer=tokenizer, return_tensors="tf" +) +``` + +{/if} + +Pentru a testa acest lucru pe câteva sampleuri, îl putem apela doar pe o listă de exemple din setul nostru de antrenat tokenizat: + +```py +batch = data_collator([tokenized_datasets["train"][i] for i in range(2)]) +batch["labels"] +``` + +```python out +tensor([[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100], + [-100, 1, 2, -100, -100, -100, -100, -100, -100, -100, -100, -100]]) +``` + +Hai să comparăm acest lucru cu labelurile pentru primul și al doilea element din datasetul nostru: + +```py +for i in range(2): + print(tokenized_datasets["train"][i]["labels"]) +``` + +```python out +[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100] +[-100, 1, 2, -100] +``` + +{#if fw === 'pt'} + +După cum se poate observa, al doilea set de labeluri a fost padded la lungimea primului folosind `-100`. + +{:else} + +Data collatorul nostru de date este gata de utilizare! Acum să îl folosim pentru a crea un `tf.data.Dataset` cu metoda `to_tf_dataset()`. De asemenea, puteți utiliza `model.prepare_tf_dataset()` pentru a face acest lucru cu un pic mai puțin cod repetitiv - veți vedea acest lucru în unele dintre celelalte secțiuni ale acestui capitol. + +```py +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["attention_mask", "input_ids", "labels", "token_type_ids"], + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) + +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["attention_mask", "input_ids", "labels", "token_type_ids"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + + +Următoarea oprire: modelul în sine. + +{/if} + +{#if fw === 'tf'} + +### Definirea modelului[[defining-the-model]] + +Deoarece lucrăm la o problemă de clasificare a tokenilor, vom utiliza clasa `TFAutoModelForTokenClassification`. Principalul lucru de care trebuie să ne amintim atunci când definim acest model este să transmitem informații privind numărul de labeluri pe care le avem. Cel mai simplu mod de a face acest lucru este să transmiteți acest număr cu argumentul `num_labels`, dar dacă dorim un inference widget frumos care să funcționeze ca cel pe care l-am văzut la începutul acestei secțiuni, este mai bine să setați în schimb corespondențele corecte ale labelurilor. + +Acestea ar trebui să fie stabilite de două dicționare, `id2label` și `label2id`, care conțin corespondența de la ID la label și viceversa: + +```py +id2label = {i: label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +Acum putem să le transmitem metodei `TFAutoModelForTokenClassification.from_pretrained()`, iar acestea vor fi setate în configurația modelului, apoi salvate corespunzător și încărcate în Hub: + +```py +from transformers import TFAutoModelForTokenClassification + +model = TFAutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +La fel ca atunci când am definit `TFAutoModelForSequenceClassification` în [Capitolul 3](/course/chapter3), crearea modelului emite un avertisment că unele weights nu au fost utilizate (cele din headul de preantrenare) și că alte weights sunt inițializate aleatoriu (cele din headul de clasificare a tokenilor noi) și că acest model ar trebui să fie antrenat. Vom face acest lucru într-un minut, dar mai întâi să verificăm de două ori că modelul nostru are numărul corect de labeluri: + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ Dacă aveți un model cu un număr greșit de labeluri, veți primi o eroare obscură atunci când apelați `model.fit()` mai târziu. Acest lucru poate fi enervant pentru debbuging, așa că asigurați-vă că faceți această verificare pentru a confirma că aveți numărul așteptat de labeluri. + + + +### Fine-tuningul modelului[[fine-tuning-the-model]] + +Acum suntem gata să ne antrenăm modelul! Totuși, mai avem doar câteva lucruri de făcut mai întâi: ar trebui să ne conectăm la Hugging Face și să definim hiperparametrii noștri de antrenare. Dacă lucrați într-un notebook, există o funcție convenabilă pentru a vă ajuta cu acest lucru: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Aceasta va afișa un widget în care puteți introduce datele tale de autentificare Hugging Face. + +Dacă nu lucrați într-un notebook, tastați următoarea linie în terminal: + +```bash +huggingface-cli login +``` + +După autentificare, putem pregăti tot ce avem nevoie pentru a compila modelul nostru. 🤗 Transformers oferă o funcție convenabilă `create_optimizer()` care vă va oferi un optimizator `AdamW` cu setări adecvate pentru weight decay și learning rate decay, ambele îmbunătățind performanța modelului vsotru în comparație cu optimizatorul `Adam` încorporat: + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Antrenarea în mixed-precision float16 +# Comentați această linie dacă utilizați un GPU care nu va beneficia de acest lucru +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +# Numărul de etape de antrenare este numărul de sampleuri din dataset, împărțit la dimensiunea batch-ului, apoi multiplicat +# cu numărul total de epoci. Rețineți că tf_train_dataset de aici este un batched tf.data.Dataset, +# nu este originalul Hugging Face Dataset, deci len() este deja num_samples // batch_size. +num_epochs = 3 +num_train_steps = len(tf_train_dataset) * num_epochs + +optimizer, schedule = create_optimizer( + init_lr=2e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) +``` + +Rețineți, de asemenea, că nu furnizăm un argument `loss` la `compile()`. Acest lucru se datorează faptului că modelele pot calcula de fapt pierderea intern - dacă compilați fără o pierdere și furnizați labelurile în dicționarul de intrare (așa cum facem în dataseturile noastre), atunci modelul se va antrena folosind acea pierdere internă, care va fi adecvată pentru sarcina și tipul de model pe care le-ați ales. + +În continuare, definim un `PushToHubCallback` pentru a încărca modelul nostru în Hub în timpul antrenării și pentru a potrivi modelul cu acel callback: + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback(output_dir="bert-finetuned-ner", tokenizer=tokenizer) + +model.fit( + tf_train_dataset, + validation_data=tf_eval_dataset, + callbacks=[callback], + epochs=num_epochs, +) +``` + +Puteți specifica numele complet al repositoriului către care doriți să efectuați push cu argumentul `hub_model_id` (în special, va trebui să utilizați acest argument pentru a efectua push către o organizație). De exemplu, atunci când am trimis modelul către organizația [`huggingface-course`](https://huggingface.co/huggingface-course), am adăugat `hub_model_id="huggingface-course/bert-finetuned-ner"`. În mod implicit, repositoriul utilizat va fi în namespace-ul denumit după directory output pe care l-ați stabilit, de exemplu `"cool_huggingface_user/bert-finetuned-ner"`. + + + +💡 Dacă directory output pe care îl utilizați există deja, acesta trebuie să fie o clonă locală a repositoriului către care doriți să faceți push. Dacă nu este, veți primi o eroare atunci când apelați `model.fit()` și va trebui să setați un nume nou. + + + +Rețineți că, în timpul antrenamentului, de fiecare dată când modelul este salvat (aici, la fiecare epocă), acesta este încărcat pe Hub în fundal. În acest fel, veți putea să reluați formarea pe o altă mașină, dacă este necesar. + +În acest stadiu, puteți utiliza inference widget de pe Model Hub pentru a testa modelul vostru și pentru a-l partaja cu prietenii. Ați făcut fine-tune cu succes unui model pentru o sarcină de clasificare a tokenilor - felicitări! Dar cât de bun este modelul nostru, de fapt? Ar trebui să evaluăm anumiți parametri pentru a afla. + +{/if} + + +### Metrici[[metrics]] + +{#if fw === 'pt'} + +Pentru ca `Trainer` să calculeze o metrică în fiecare epocă, va trebui să definim o funcție `compute_metrics()` care primește matricele de predicții și labelurile și returnează un dicționar cu numele și valorile metricilor. + +Frameworkul tradițional utilizat pentru a evalua predicția clasificării a tokenilor este [*seqeval*](https://github.com/chakki-works/seqeval). Pentru a utiliza această metrică, trebuie mai întâi să instalăm biblioteca *seqeval*: + +```py +!pip install seqeval +``` + +Apoi îl putem încărca prin intermediul funcției `evaluate.load()` așa cum am făcut în [Capitolul 3](/course/chapter3): + +{:else} + +Frameworkul tradițional utilizat pentru a evalua predicția clasificării tokenilor este [*seqeval*](https://github.com/chakki-works/seqeval). Pentru a utiliza această metrică, trebuie mai întâi să instalăm biblioteca *seqeval*: + +```py +!pip install seqeval +``` + +Apoi îl putem încărca prin intermediul funcției `evaluate.load()` așa cum am făcut în [Capitolul 3](/course/chapter3): + +{/if} + +```py +import evaluate + +metric = evaluate.load("seqeval") +``` + +Această metrică nu se comportă ca precizia standard: de fapt, va lua listele de labeluri ca șiruri de caractere, nu ca numere întregi, deci va trebui să decodificăm complet predicțiile și labelurile înainte de a le trece în metrice. Să vedem cum funcționează. În primul rând, vom obține labelurile pentru primul nostru exemplu de antrenare: + +```py +labels = raw_datasets["train"][0]["ner_tags"] +labels = [label_names[i] for i in labels] +labels +``` + +```python out +['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O', 'O'] +``` + +Putem apoi crea predicții false pentru acestea prin simpla schimbare a valorii la indexul 2: + +```py +predictions = labels.copy() +predictions[2] = "O" +metric.compute(predictions=[predictions], references=[labels]) +``` + +Rețineți că metrica ia o listă de predicții (nu doar una) și o listă de labels. Iată rezultatul: + +```python out +{'MISC': {'precision': 1.0, 'recall': 0.5, 'f1': 0.67, 'number': 2}, + 'ORG': {'precision': 1.0, 'recall': 1.0, 'f1': 1.0, 'number': 1}, + 'overall_precision': 1.0, + 'overall_recall': 0.67, + 'overall_f1': 0.8, + 'overall_accuracy': 0.89} +``` + +{#if fw === 'pt'} + +Acesta trimite înapoi o mulțime de informații! Noi obținem precizia, recallul și scorul F1 pentru fiecare entitate în parte, precum și scorul general. Pentru calculul nostru metric, vom păstra doar scorul global, dar nu ezitați să modificați funcția `compute_metrics()` pentru a returna toate metricile pe care doriți să le raportați. + +Această funcție `compute_metrics()` ia mai întâi argmaxul logiturilor pentru a le converti în predicții (ca de obicei, logiturile și probabilitățile sunt în aceeași ordine, deci nu trebuie să aplicăm softmaxul). Apoi trebuie să convertim atât labelurile, cât și predicțiile din numere întregi în șiruri de caractere. Eliminăm toate valorile în care labelul este `-100`, apoi transmitem rezultatele metodei `metric.compute()`: + +```py +import numpy as np + + +def compute_metrics(eval_preds): + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + + # Remove ignored index (special tokens) and convert to labels + true_labels = [[label_names[l] for l in label if l != -100] for label in labels] + true_predictions = [ + [label_names[p] for (p, l) in zip(prediction, label) if l != -100] + for prediction, label in zip(predictions, labels) + ] + all_metrics = metric.compute(predictions=true_predictions, references=true_labels) + return { + "precision": all_metrics["overall_precision"], + "recall": all_metrics["overall_recall"], + "f1": all_metrics["overall_f1"], + "accuracy": all_metrics["overall_accuracy"], + } +``` + +Acum că acest lucru este făcut, suntem aproape gata să definim `Trainer`-ul nostru. Avem nevoie doar de un `model` pentru a face fine-tune! + +{:else} + +Aceasta trimite înapoi o mulțime de informații! Obținem precizia, recallul și scorul F1 pentru fiecare entitate în parte, precum și în ansamblu. Acum să vedem ce se întâmplă dacă încercăm să folosim predicțiile modelului nostru real pentru a calcula niște scoruri reale. + +TensorFlow nu apreciază concatenarea predicțiilor noastre, deoarece acestea au lungimi de secvențe variabile. Aceasta înseamnă că nu putem folosi pur și simplu `model.predict()` - dar asta nu ne va opri. Vom obține unele predicții pe rând și le vom concatena într-o singură listă mare și lungă, eliminând simbolurile `-100` care indică mascarea/paddingul, apoi vom calcula metrici pe lista de la sfârșit: + +```py +import numpy as np + +all_predictions = [] +all_labels = [] +for batch in tf_eval_dataset: + logits = model.predict_on_batch(batch)["logits"] + labels = batch["labels"] + predictions = np.argmax(logits, axis=-1) + for prediction, label in zip(predictions, labels): + for predicted_idx, label_idx in zip(prediction, label): + if label_idx == -100: + continue + all_predictions.append(label_names[predicted_idx]) + all_labels.append(label_names[label_idx]) +metric.compute(predictions=[all_predictions], references=[all_labels]) +``` + + +```python out +{'LOC': {'precision': 0.91, 'recall': 0.92, 'f1': 0.91, 'number': 1668}, + 'MISC': {'precision': 0.70, 'recall': 0.79, 'f1': 0.74, 'number': 702}, + 'ORG': {'precision': 0.85, 'recall': 0.90, 'f1': 0.88, 'number': 1661}, + 'PER': {'precision': 0.95, 'recall': 0.95, 'f1': 0.95, 'number': 1617}, + 'overall_precision': 0.87, + 'overall_recall': 0.91, + 'overall_f1': 0.89, + 'overall_accuracy': 0.97} +``` + +Cum s-a descurcat modelul tău, comparativ cu al nostru? Dacă ați obținut cifre similare, antrenarea a fost un succes! + +{/if} + +{#if fw === 'pt'} + +### Definirea modelului[[defining-the-model]] + +Deoarece lucrăm la o problemă de clasificare a tokenilor, vom utiliza clasa `AutoModelForTokenClassification`. Principalul lucru de care trebuie să ne amintim atunci când definim acest model este să transmitem informații privind numărul de labeluri pe care le avem. Cel mai simplu mod de a face acest lucru este să transmiteți acest număr cu argumentul `num_labels`, dar dacă dorim un inferencea widget frumos care să funcționeze ca cel pe care l-am văzut la începutul acestei secțiuni, este mai bine să setați în schimb corespondențele corecte ale labelurilor. + +Acestea ar trebui să fie stabilite de două dicționare, `id2label` și `label2id`, care conțin corespondențele de la ID la labeluri și viceversa: + +```py +id2label = {i: label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +Acum putem doar să le transmitem metodei `AutoModelForTokenClassification.from_pretrained()`, iar acestea vor fi setate în configurația modelului și apoi salvate și încărcate corespunzător în Hub: + +```py +from transformers import AutoModelForTokenClassification + +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +La fel ca atunci când am definit `AutoModelForSequenceClassification` în [Capitolul 3](/course/chapter3), crearea modelului emite un avertisment că unele weights nu au fost utilizate (cele din headul de antrenare) și alte weights sunt inițializate aleatoriu (cele din headul de clasificare a tokenilor noi) și că acest model ar trebui să fie format. Vom face acest lucru într-un minut, dar mai întâi să verificăm de două ori că modelul nostru are numărul corect de labeluri: + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ Dacă aveți un model cu un număr greșit de labeluri, veți primi o eroare obscură atunci când apelați metoda `Trainer.train()` mai târziu (ceva de genul "CUDA error: device-side assert triggered"). Aceasta este cauza numărul unu a erorilor raportate de utilizatori pentru astfel de erori, așa că asigurați-vă că faceți această verificare pentru a confirma că aveți numărul de labeluri așteptat. + + + +### Fine-tuningul modelului[[fine-tuning-the-model]] + +Acum suntem gata să ne antrenăm modelul! Trebuie doar să facem ultimele două lucruri înainte de a defini modelul nostru `Trainer`: să ne conectăm la Hugging Face și să definim argumentele de antrenare. Dacă lucrați într-un notebook, există o funcție convenabilă pentru a vă ajuta cu acest lucru: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Aceasta va afișa un widget în care puteți introduce datele tale de autentificare Hugging Face. + +Dacă nu lucrați într-un notebook, tastați următoarea linie în terminal: + +```bash +huggingface-cli login +``` + +Odată făcut acest lucru, putem defini`TrainingArguments`: + +```python +from transformers import TrainingArguments + +args = TrainingArguments( + "bert-finetuned-ner", + evaluation_strategy="epoch", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, + push_to_hub=True, +) +``` + +Ați mai văzut cele mai multe dintre acestea: stabilim niște hiperparametri (cum ar fi learning rate, numărul de epoci pentru care să ne antrenăm și weights decay) și specificăm `push_to_hub=True` pentru a indica faptul că dorim să salvăm modelul și să îl evaluăm la sfârșitul fiecărei epoci și că dorim să încărcăm rezultatele noastre în Model Hub. Rețineți că puteți specifica numele repositoriului către care doriți să faceți push cu argumentul `hub_model_id` (în special, va trebui să utilizați acest argument pentru a face push către o organizație). De exemplu, atunci când am trimis modelul către organizația [`huggingface-course`](https://huggingface.co/huggingface-course), am adăugat `hub_model_id="huggingface-course/bert-finetuned-ner"` la `TrainingArguments`. În mod implicit, repositoriul utilizat va fi în namespaceul tău și denumit după output directory-ul pe care l-ați setat, deci în cazul nostru va fi `"sgugger/bert-finetuned-ner"`. + + + +💡 Dacă output directory-ul pe care îl utilizați există deja, acesta trebuie să fie o clonă locală a repositoriul către care doriți să faceți push. Dacă nu este așa, veți primi o eroare la definirea `Trainer` și va trebui să setați un nume nou. + + + +În final, transmitem totul către `Trainer` și lansăm antrenarea: + +```python +from transformers import Trainer + +trainer = Trainer( + model=model, + args=args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + compute_metrics=compute_metrics, + tokenizer=tokenizer, +) +trainer.train() +``` + +Rețineți că, în timpul antrenării, de fiecare dată când modelul este salvat (aici, la fiecare epocă), acesta este încărcat pe Hub în fundal. În acest fel, veți putea să reluați antrenareape o altă mașină, dacă este necesar. + +Odată ce antrenamentul este complet, folosim metoda `push_to_hub()` pentru a ne asigura că încărcăm cea mai recentă versiune a modelului: + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +Această comandă returnează URL-ul comitului pe care tocmai l-ai făcut, dacă doriți să o inspectați: + +```python out +'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' +``` + +De asemenea, `Trainer` redactează un model card cu toate rezultatele evaluării și o încarcă. În acest stadiu, puteți utiliza inference widgetul de pe Model Hub pentru a testa modelul și a-l partaja cu prietenii. Ați făcut fine-tune cu succes un model pentru o sarcină de clasificare a tokenilor - felicitări! + +Dacă doriți să vă scufundați puțin mai profund în bucla de antrenare, vă vom arăta acum cum să faceți același lucru utilizând 🤗 Accelerate. + +## Un training loop personalizat[[a-custom-training-loop]] + +Să aruncăm acum o privire la training loopul complet, astfel încât să puteți personaliza cu ușurință părțile de care aveți nevoie. Va semăna foarte mult cu ceea ce am făcut în [Capitolul 3](/course/chapter3/4), cu câteva modificări pentru evaluare. + +### Pregătiți totul pentru antrenare[[preparing-everything-for-training]] + +Mai întâi trebuie să construim `DataLoader`s din dataseturile noastre. Vom reutiliza `data_collator` ca un `collate_fn` și vom amesteca setul de antrenare, dar nu și setul de validare: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8 +) +``` + +În continuare, reinstanțiem modelul, pentru a ne asigura că nu continuăm fine-tuningul de dinainte, ci pornim din nou de la modelul preantrenat BERT: + +```py +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Atunci vom avea nevoie de un optimizator. Vom folosi clasicul `AdamW`, care este ca `Adam`, dar cu o corecție în modul în care se aplică weight decay-ul: + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Odată ce avem toate aceste obiecte, le putem trimite la metoda `accelerator.prepare()`: + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Dacă vă antrenați pe un TPU, va trebui să mutați tot codul începând de la celula de mai sus într-o funcție de antrenament dedicată. Consultați [Capitolul 3](/course/chapter3) pentru mai multe detalii. + + + +Acum că am trimis `train_dataloader` la `accelerator.prepare()`, putem utiliza lungimea acestuia pentru a calcula numărul de pași de antrenare. Rețineți că ar trebui să facem întotdeauna acest lucru după ce pregătim dataloaderul, deoarece această metodă îi va modifica lungimea. Utilizăm un classic liner schedule de la rata de învățare la 0: + +```py +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, +) +``` + +În cele din urmă, 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, dacă nu sunteți deja conectat. Vom determina numele repositoriului pornind de la ID-ul modelului pe care dorim să îl dăm modelului nostru (nu ezitați să înlocuiți `repo_name` cu propriul nume; trebuie doar să conțină numele vostru de utilizator, ceea ce face funcția `get_full_repo_name()`): + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "bert-finetuned-ner-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/bert-finetuned-ner-accelerate' +``` + +Apoi putem clona acel repositoriu într-un folder local. Dacă există deja, acest folder local ar trebui să fie o clonă existentă a repositoriului cu care lucrăm: + +```py +output_dir = "bert-finetuned-ner-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. + +### Loopul de antrenare[[training-loop]] + +Acum suntem pregătiți să scriem bucla de antrenare completă. Pentru a simplifica partea sa de evaluare, definim funcția `postprocess()` care preia predicțiile și labelurile și le convertește în liste de șiruri de caractere, așa cum se așteaptă obiectul nostru `metric`: + +```py +def postprocess(predictions, labels): + predictions = predictions.detach().cpu().clone().numpy() + labels = labels.detach().cpu().clone().numpy() + + # Remove ignored index (special tokens) and convert to labels + true_labels = [[label_names[l] for l in label if l != -100] for label in labels] + true_predictions = [ + [label_names[p] for (p, l) in zip(prediction, label) if l != -100] + for prediction, label in zip(predictions, labels) + ] + return true_labels, true_predictions +``` + +Apoi putem scrie bucla de antrenare. După definirea unei bare de progres pentru a urmări modul în care decurge antrenarea, bucla are trei părți: + +- Antrenarea în sine, care este iterația clasică peste `train_dataloader`, trecerea înainte prin model, apoi trecerea înapoi și pasul optimizatorului. +- Evaluarea, în care există o noutate după obținerea outputurilor modelului nostru pe un batch: din moment ce două procese pot ar fi putut face padding inputurilor și labelurile la forme diferite, trebuie să folosim `accelerator.pad_across_processes()` pentru a face predicțiile și labelurile să aibă aceeași formă înainte de a apela metoda `gather()`. Dacă nu facem acest lucru, evaluarea va da eroare sau se va bloca pentru totdeauna. Apoi trimitem rezultatele la `metric.add_batch()` și apelăm `metric.compute()` odată ce bucla de evaluare s-a încheiat. +- Salvarea și încărcarea, unde mai întâi salvăm modelul și tokenizerul, apoi apelăm `repo.push_to_hub()`. Observați că folosim argumentul `blocking=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ă în fundal. + +Iată codul complet pentru bucla de antrenare: + +```py +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 batch in 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() + for batch in eval_dataloader: + with torch.no_grad(): + outputs = model(**batch) + + predictions = outputs.logits.argmax(dim=-1) + labels = batch["labels"] + + # Necessary to pad predictions and labels for being gathered + predictions = accelerator.pad_across_processes(predictions, dim=1, pad_index=-100) + labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100) + + predictions_gathered = accelerator.gather(predictions) + labels_gathered = accelerator.gather(labels) + + true_predictions, true_labels = postprocess(predictions_gathered, labels_gathered) + metric.add_batch(predictions=true_predictions, references=true_labels) + + results = metric.compute() + print( + f"epoch {epoch}:", + { + key: results[f"overall_{key}"] + for key in ["precision", "recall", "f1", "accuracy"] + }, + ) + + # 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: + +```py +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 a salva. 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 la [*huggingface-course/bert-finetuned-ner-accelerate*] (https://huggingface.co/huggingface-course/bert-finetuned-ner-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! + + +{/if} + +## Utilizarea model fine-tuned[[using-the-fine-tuned-model]] + +V-am arătat deja cum puteți utiliza modelul pe care l-am ajustat pe Model Hub cu inference widget. Pentru a-l utiliza la nivel local într-un `pipeline`, trebuie doar să specificați identificatorul de model corespunzător: + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +model_checkpoint = "huggingface-course/bert-finetuned-ner" +token_classifier = pipeline( + "token-classification", model=model_checkpoint, aggregation_strategy="simple" +) +token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.9988506, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.9647625, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.9986118, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Grozav! Modelul nostru funcționează la fel de bine ca cel implicit pentru aceast pipeline! \ No newline at end of file diff --git a/chapters/ro/chapter7/3.mdx b/chapters/ro/chapter7/3.mdx new file mode 100644 index 000000000..de3da9a1f --- /dev/null +++ b/chapters/ro/chapter7/3.mdx @@ -0,0 +1,1044 @@ + + +# Fine-tuning a masked language model[[fine-tuning-a-masked-language-model]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +For many NLP applications involving Transformer models, you can simply take a pretrained model from the Hugging Face Hub and fine-tune it directly on your data for the task at hand. Provided that the corpus used for pretraining is not too different from the corpus used for fine-tuning, transfer learning will usually produce good results. + +However, there are a few cases where you'll want to first fine-tune the language models on your data, before training a task-specific head. For example, if your dataset contains legal contracts or scientific articles, a vanilla Transformer model like BERT will typically treat the domain-specific words in your corpus as rare tokens, and the resulting performance may be less than satisfactory. By fine-tuning the language model on in-domain data you can boost the performance of many downstream tasks, which means you usually only have to do this step once! + +This process of fine-tuning a pretrained language model on in-domain data is usually called _domain adaptation_. It was popularized in 2018 by [ULMFiT](https://arxiv.org/abs/1801.06146), which was one of the first neural architectures (based on LSTMs) to make transfer learning really work for NLP. An example of domain adaptation with ULMFiT is shown in the image below; in this section we'll do something similar, but with a Transformer instead of an LSTM! + +
+ULMFiT. + +
+ +By the end of this section you'll have a [masked language model](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) on the Hub that can autocomplete sentences as shown below: + + + +Let's dive in! + + + + + +🙋 If the terms "masked language modeling" and "pretrained model" sound unfamiliar to you, go check out [Chapter 1](/course/chapter1), where we explain all these core concepts, complete with videos! + + + +## Picking a pretrained model for masked language modeling[[picking-a-pretrained-model-for-masked-language-modeling]] + +To get started, let's pick a suitable pretrained model for masked language modeling. As shown in the following screenshot, you can find a list of candidates by applying the "Fill-Mask" filter on the [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads): + +
+Hub models. +
+ +Although the BERT and RoBERTa family of models are the most downloaded, we'll use a model called [DistilBERT](https://huggingface.co/distilbert-base-uncased) +that can be trained much faster with little to no loss in downstream performance. This model was trained using a special technique called [_knowledge distillation_](https://en.wikipedia.org/wiki/Knowledge_distillation), where a large "teacher model" like BERT is used to guide the training of a "student model" that has far fewer parameters. An explanation of the details of knowledge distillation would take us too far afield in this section, but if you're interested you can read all about it in [_Natural Language Processing with Transformers_](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) (colloquially known as the Transformers textbook). + +{#if fw === 'pt'} + +Let's go ahead and download DistilBERT using the `AutoModelForMaskedLM` class: + +```python +from transformers import AutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +We can see how many parameters this model has by calling the `num_parameters()` method: + +```python +distilbert_num_parameters = model.num_parameters() / 1_000_000 +print(f"'>>> DistilBERT number of parameters: {round(distilbert_num_parameters)}M'") +print(f"'>>> BERT number of parameters: 110M'") +``` + +```python out +'>>> DistilBERT number of parameters: 67M' +'>>> BERT number of parameters: 110M' +``` + +{:else} + +Let's go ahead and download DistilBERT using the `AutoModelForMaskedLM` class: + +```python +from transformers import TFAutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +We can see how many parameters this model has by calling the `summary()` method: + +```python +model.summary() +``` + +```python out +Model: "tf_distil_bert_for_masked_lm" +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +distilbert (TFDistilBertMain multiple 66362880 +_________________________________________________________________ +vocab_transform (Dense) multiple 590592 +_________________________________________________________________ +vocab_layer_norm (LayerNorma multiple 1536 +_________________________________________________________________ +vocab_projector (TFDistilBer multiple 23866170 +================================================================= +Total params: 66,985,530 +Trainable params: 66,985,530 +Non-trainable params: 0 +_________________________________________________________________ +``` + +{/if} + +With around 67 million parameters, DistilBERT is approximately two times smaller than the BERT base model, which roughly translates into a two-fold speedup in training -- nice! Let's now see what kinds of tokens this model predicts are the most likely completions of a small sample of text: + +```python +text = "This is a great [MASK]." +``` + +As humans, we can imagine many possibilities for the `[MASK]` token, such as "day", "ride", or "painting". For pretrained models, the predictions depend on the corpus the model was trained on, since it learns to pick up the statistical patterns present in the data. Like BERT, DistilBERT was pretrained on the [English Wikipedia](https://huggingface.co/datasets/wikipedia) and [BookCorpus](https://huggingface.co/datasets/bookcorpus) datasets, so we expect the predictions for `[MASK]` to reflect these domains. To predict the mask we need DistilBERT's tokenizer to produce the inputs for the model, so let's download that from the Hub as well: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +With a tokenizer and a model, we can now pass our text example to the model, extract the logits, and print out the top 5 candidates: + +{#if fw === 'pt'} + +```python +import torch + +inputs = tokenizer(text, return_tensors="pt") +token_logits = model(**inputs).logits +# Find the location of [MASK] and extract its logits +mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Pick the [MASK] candidates with the highest logits +top_5_tokens = torch.topk(mask_token_logits, 5, dim=1).indices[0].tolist() + +for token in top_5_tokens: + print(f"'>>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}'") +``` + +{:else} + +```python +import numpy as np +import tensorflow as tf + +inputs = tokenizer(text, return_tensors="np") +token_logits = model(**inputs).logits +# Find the location of [MASK] and extract its logits +mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Pick the [MASK] candidates with the highest logits +# We negate the array before argsort to get the largest, not the smallest, logits +top_5_tokens = np.argsort(-mask_token_logits)[:5].tolist() + +for token in top_5_tokens: + print(f">>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}") +``` + +{/if} + +```python out +'>>> This is a great deal.' +'>>> This is a great success.' +'>>> This is a great adventure.' +'>>> This is a great idea.' +'>>> This is a great feat.' +``` + +We can see from the outputs that the model's predictions refer to everyday terms, which is perhaps not surprising given the foundation of English Wikipedia. Let's see how we can change this domain to something a bit more niche -- highly polarized movie reviews! + + +## The dataset[[the-dataset]] + +To showcase domain adaptation, we'll use the famous [Large Movie Review Dataset](https://huggingface.co/datasets/imdb) (or IMDb for short), which is a corpus of movie reviews that is often used to benchmark sentiment analysis models. By fine-tuning DistilBERT on this corpus, we expect the language model will adapt its vocabulary from the factual data of Wikipedia that it was pretrained on to the more subjective elements of movie reviews. We can get the data from the Hugging Face Hub with the `load_dataset()` function from 🤗 Datasets: + +```python +from datasets import load_dataset + +imdb_dataset = load_dataset("imdb") +imdb_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['text', 'label'], + num_rows: 25000 + }) + test: Dataset({ + features: ['text', 'label'], + num_rows: 25000 + }) + unsupervised: Dataset({ + features: ['text', 'label'], + num_rows: 50000 + }) +}) +``` + +We can see that the `train` and `test` splits each consist of 25,000 reviews, while there is an unlabeled split called `unsupervised` that contains 50,000 reviews. Let's take a look at a few samples to get an idea of what kind of text we're dealing with. As we've done in previous chapters of the course, we'll chain the `Dataset.shuffle()` and `Dataset.select()` functions to create a random sample: + +```python +sample = imdb_dataset["train"].shuffle(seed=42).select(range(3)) + +for row in sample: + print(f"\n'>>> Review: {row['text']}'") + print(f"'>>> Label: {row['label']}'") +``` + +```python out + +'>>> Review: This is your typical Priyadarshan movie--a bunch of loony characters out on some silly mission. His signature climax has the entire cast of the film coming together and fighting each other in some crazy moshpit over hidden money. Whether it is a winning lottery ticket in Malamaal Weekly, black money in Hera Pheri, "kodokoo" in Phir Hera Pheri, etc., etc., the director is becoming ridiculously predictable. Don\'t get me wrong; as clichéd and preposterous his movies may be, I usually end up enjoying the comedy. However, in most his previous movies there has actually been some good humor, (Hungama and Hera Pheri being noteworthy ones). Now, the hilarity of his films is fading as he is using the same formula over and over again.

Songs are good. Tanushree Datta looks awesome. Rajpal Yadav is irritating, and Tusshar is not a whole lot better. Kunal Khemu is OK, and Sharman Joshi is the best.' +'>>> Label: 0' + +'>>> Review: Okay, the story makes no sense, the characters lack any dimensionally, the best dialogue is ad-libs about the low quality of movie, the cinematography is dismal, and only editing saves a bit of the muddle, but Sam" Peckinpah directed the film. Somehow, his direction is not enough. For those who appreciate Peckinpah and his great work, this movie is a disappointment. Even a great cast cannot redeem the time the viewer wastes with this minimal effort.

The proper response to the movie is the contempt that the director San Peckinpah, James Caan, Robert Duvall, Burt Young, Bo Hopkins, Arthur Hill, and even Gig Young bring to their work. Watch the great Peckinpah films. Skip this mess.' +'>>> Label: 0' + +'>>> Review: I saw this movie at the theaters when I was about 6 or 7 years old. I loved it then, and have recently come to own a VHS version.

My 4 and 6 year old children love this movie and have been asking again and again to watch it.

I have enjoyed watching it again too. Though I have to admit it is not as good on a little TV.

I do not have older children so I do not know what they would think of it.

The songs are very cute. My daughter keeps singing them over and over.

Hope this helps.' +'>>> Label: 1' +``` + +Yep, these are certainly movie reviews, and if you're old enough you may even understand the comment in the last review about owning a VHS version 😜! Although we won't need the labels for language modeling, we can already see that a `0` denotes a negative review, while a `1` corresponds to a positive one. + + + +✏️ **Try it out!** Create a random sample of the `unsupervised` split and verify that the labels are neither `0` nor `1`. While you're at it, you could also check that the labels in the `train` and `test` splits are indeed `0` or `1` -- this is a useful sanity check that every NLP practitioner should perform at the start of a new project! + + + +Now that we've had a quick look at the data, let's dive into preparing it for masked language modeling. As we'll see, there are some additional steps that one needs to take compared to the sequence classification tasks we saw in [Chapter 3](/course/chapter3). Let's go! + +## Preprocessing the data[[preprocessing-the-data]] + + + +For both auto-regressive and masked language modeling, a common preprocessing step is to concatenate all the examples and then split the whole corpus into chunks of equal size. This is quite different from our usual approach, where we simply tokenize individual examples. Why concatenate everything together? The reason is that individual examples might get truncated if they're too long, and that would result in losing information that might be useful for the language modeling task! + +So to get started, we'll first tokenize our corpus as usual, but _without_ setting the `truncation=True` option in our tokenizer. We'll also grab the word IDs if they are available ((which they will be if we're using a fast tokenizer, as described in [Chapter 6](/course/chapter6/3)), as we will need them later on to do whole word masking. We'll wrap this in a simple function, and while we're at it we'll remove the `text` and `label` columns since we don't need them any longer: + +```python +def tokenize_function(examples): + result = tokenizer(examples["text"]) + if tokenizer.is_fast: + result["word_ids"] = [result.word_ids(i) for i in range(len(result["input_ids"]))] + return result + + +# Use batched=True to activate fast multithreading! +tokenized_datasets = imdb_dataset.map( + tokenize_function, batched=True, remove_columns=["text", "label"] +) +tokenized_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'input_ids', 'word_ids'], + num_rows: 25000 + }) + test: Dataset({ + features: ['attention_mask', 'input_ids', 'word_ids'], + num_rows: 25000 + }) + unsupervised: Dataset({ + features: ['attention_mask', 'input_ids', 'word_ids'], + num_rows: 50000 + }) +}) +``` + +Since DistilBERT is a BERT-like model, we can see that the encoded texts consist of the `input_ids` and `attention_mask` that we've seen in other chapters, as well as the `word_ids` we added. + +Now that we've tokenized our movie reviews, the next step is to group them all together and split the result into chunks. But how big should these chunks be? This will ultimately be determined by the amount of GPU memory that you have available, but a good starting point is to see what the model's maximum context size is. This can be inferred by inspecting the `model_max_length` attribute of the tokenizer: + +```python +tokenizer.model_max_length +``` + +```python out +512 +``` + +This value is derived from the *tokenizer_config.json* file associated with a checkpoint; in this case we can see that the context size is 512 tokens, just like with BERT. + + + +✏️ **Try it out!** Some Transformer models, like [BigBird](https://huggingface.co/google/bigbird-roberta-base) and [Longformer](hf.co/allenai/longformer-base-4096), have a much longer context length than BERT and other early Transformer models. Instantiate the tokenizer for one of these checkpoints and verify that the `model_max_length` agrees with what's quoted on its model card. + + + +So, in order to run our experiments on GPUs like those found on Google Colab, we'll pick something a bit smaller that can fit in memory: + +```python +chunk_size = 128 +``` + + + +Note that using a small chunk size can be detrimental in real-world scenarios, so you should use a size that corresponds to the use case you will apply your model to. + + + +Now comes the fun part. To show how the concatenation works, let's take a few reviews from our tokenized training set and print out the number of tokens per review: + +```python +# Slicing produces a list of lists for each feature +tokenized_samples = tokenized_datasets["train"][:3] + +for idx, sample in enumerate(tokenized_samples["input_ids"]): + print(f"'>>> Review {idx} length: {len(sample)}'") +``` + +```python out +'>>> Review 0 length: 200' +'>>> Review 1 length: 559' +'>>> Review 2 length: 192' +``` + +We can then concatenate all these examples with a simple dictionary comprehension, as follows: + +```python +concatenated_examples = { + k: sum(tokenized_samples[k], []) for k in tokenized_samples.keys() +} +total_length = len(concatenated_examples["input_ids"]) +print(f"'>>> Concatenated reviews length: {total_length}'") +``` + +```python out +'>>> Concatenated reviews length: 951' +``` + +Great, the total length checks out -- so now let's split the concatenated reviews into chunks of the size given by `chunk_size`. To do so, we iterate over the features in `concatenated_examples` and use a list comprehension to create slices of each feature. The result is a dictionary of chunks for each feature: + +```python +chunks = { + k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)] + for k, t in concatenated_examples.items() +} + +for chunk in chunks["input_ids"]: + print(f"'>>> Chunk length: {len(chunk)}'") +``` + +```python out +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 55' +``` + +As you can see in this example, the last chunk will generally be smaller than the maximum chunk size. There are two main strategies for dealing with this: + +* Drop the last chunk if it's smaller than `chunk_size`. +* Pad the last chunk until its length equals `chunk_size`. + +We'll take the first approach here, so let's wrap all of the above logic in a single function that we can apply to our tokenized datasets: + +```python +def group_texts(examples): + # Concatenate all texts + concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} + # Compute length of concatenated texts + total_length = len(concatenated_examples[list(examples.keys())[0]]) + # We drop the last chunk if it's smaller than chunk_size + total_length = (total_length // chunk_size) * chunk_size + # Split by chunks of max_len + result = { + k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)] + for k, t in concatenated_examples.items() + } + # Create a new labels column + result["labels"] = result["input_ids"].copy() + return result +``` + +Note that in the last step of `group_texts()` we create a new `labels` column which is a copy of the `input_ids` one. As we'll see shortly, that's because in masked language modeling the objective is to predict randomly masked tokens in the input batch, and by creating a `labels` column we provide the ground truth for our language model to learn from. + +Let's now apply `group_texts()` to our tokenized datasets using our trusty `Dataset.map()` function: + +```python +lm_datasets = tokenized_datasets.map(group_texts, batched=True) +lm_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 61289 + }) + test: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 59905 + }) + unsupervised: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 122963 + }) +}) +``` + +You can see that grouping and then chunking the texts has produced many more examples than our original 25,000 for the `train` and `test` splits. That's because we now have examples involving _contiguous tokens_ that span across multiple examples from the original corpus. You can see this explicitly by looking for the special `[SEP]` and `[CLS]` tokens in one of the chunks: + +```python +tokenizer.decode(lm_datasets["train"][1]["input_ids"]) +``` + +```python out +".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless" +``` + +In this example you can see two overlapping movie reviews, one about a high school movie and the other about homelessness. Let's also check out what the labels look like for masked language modeling: + +```python out +tokenizer.decode(lm_datasets["train"][1]["labels"]) +``` + +```python out +".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless" +``` + +As expected from our `group_texts()` function above, this looks identical to the decoded `input_ids` -- but then how can our model possibly learn anything? We're missing a key step: inserting `[MASK]` tokens at random positions in the inputs! Let's see how we can do this on the fly during fine-tuning using a special data collator. + +## Fine-tuning DistilBERT with the `Trainer` API[[fine-tuning-distilbert-with-the-trainer-api]] + +Fine-tuning a masked language model is almost identical to fine-tuning a sequence classification model, like we did in [Chapter 3](/course/chapter3). The only difference is that we need a special data collator that can randomly mask some of the tokens in each batch of texts. Fortunately, 🤗 Transformers comes prepared with a dedicated `DataCollatorForLanguageModeling` for just this task. We just have to pass it the tokenizer and an `mlm_probability` argument that specifies what fraction of the tokens to mask. We'll pick 15%, which is the amount used for BERT and a common choice in the literature: + +```python +from transformers import DataCollatorForLanguageModeling + +data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) +``` + +To see how the random masking works, let's feed a few examples to the data collator. Since it expects a list of `dict`s, where each `dict` represents a single chunk of contiguous text, we first iterate over the dataset before feeding the batch to the collator. We remove the `"word_ids"` key for this data collator as it does not expect it: + +```python +samples = [lm_datasets["train"][i] for i in range(2)] +for sample in samples: + _ = sample.pop("word_ids") + +for chunk in data_collator(samples)["input_ids"]: + print(f"\n'>>> {tokenizer.decode(chunk)}'") +``` + +```python output +'>>> [CLS] bromwell [MASK] is a cartoon comedy. it ran at the same [MASK] as some other [MASK] about school life, [MASK] as " teachers ". [MASK] [MASK] [MASK] in the teaching [MASK] lead [MASK] to believe that bromwell high\'[MASK] satire is much closer to reality than is " teachers ". the scramble [MASK] [MASK] financially, the [MASK]ful students whogn [MASK] right through [MASK] pathetic teachers\'pomp, the pettiness of the whole situation, distinction remind me of the schools i knew and their students. when i saw [MASK] episode in [MASK] a student repeatedly tried to burn down the school, [MASK] immediately recalled. [MASK]...' + +'>>> .... at.. [MASK]... [MASK]... high. a classic line plucked inspector : i\'[MASK] here to [MASK] one of your [MASK]. student : welcome to bromwell [MASK]. i expect that many adults of my age think that [MASK]mwell [MASK] is [MASK] fetched. what a pity that it isn\'t! [SEP] [CLS] [MASK]ness ( or [MASK]lessness as george 宇in stated )公 been an issue for years but never [MASK] plan to help those on the street that were once considered human [MASK] did everything from going to school, [MASK], [MASK] vote for the matter. most people think [MASK] the homeless' +``` + +Nice, it worked! We can see that the `[MASK]` token has been randomly inserted at various locations in our text. These will be the tokens which our model will have to predict during training -- and the beauty of the data collator is that it will randomize the `[MASK]` insertion with every batch! + + + +✏️ **Try it out!** Run the code snippet above several times to see the random masking happen in front of your very eyes! Also replace the `tokenizer.decode()` method with `tokenizer.convert_ids_to_tokens()` to see that sometimes a single token from a given word is masked, and not the others. + + + +{#if fw === 'pt'} + +One side effect of random masking is that our evaluation metrics will not be deterministic when using the `Trainer`, since we use the same data collator for the training and test sets. We'll see later, when we look at fine-tuning with 🤗 Accelerate, how we can use the flexibility of a custom evaluation loop to freeze the randomness. + +{/if} + +When training models for masked language modeling, one technique that can be used is to mask whole words together, not just individual tokens. This approach is called _whole word masking_. If we want to use whole word masking, we will need to build a data collator ourselves. A data collator is just a function that takes a list of samples and converts them into a batch, so let's do this now! We'll use the word IDs computed earlier to make a map between word indices and the corresponding tokens, then randomly decide which words to mask and apply that mask on the inputs. Note that the labels are all `-100` except for the ones corresponding to mask words. + +{#if fw === 'pt'} + +```py +import collections +import numpy as np + +from transformers import default_data_collator + +wwm_probability = 0.2 + + +def whole_word_masking_data_collator(features): + for feature in features: + word_ids = feature.pop("word_ids") + + # Create a map between words and corresponding token indices + mapping = collections.defaultdict(list) + current_word_index = -1 + current_word = None + for idx, word_id in enumerate(word_ids): + if word_id is not None: + if word_id != current_word: + current_word = word_id + current_word_index += 1 + mapping[current_word_index].append(idx) + + # Randomly mask words + mask = np.random.binomial(1, wwm_probability, (len(mapping),)) + input_ids = feature["input_ids"] + labels = feature["labels"] + new_labels = [-100] * len(labels) + for word_id in np.where(mask)[0]: + word_id = word_id.item() + for idx in mapping[word_id]: + new_labels[idx] = labels[idx] + input_ids[idx] = tokenizer.mask_token_id + feature["labels"] = new_labels + + return default_data_collator(features) +``` + +{:else} + +```py +import collections +import numpy as np + +from transformers.data.data_collator import tf_default_data_collator + +wwm_probability = 0.2 + + +def whole_word_masking_data_collator(features): + for feature in features: + word_ids = feature.pop("word_ids") + + # Create a map between words and corresponding token indices + mapping = collections.defaultdict(list) + current_word_index = -1 + current_word = None + for idx, word_id in enumerate(word_ids): + if word_id is not None: + if word_id != current_word: + current_word = word_id + current_word_index += 1 + mapping[current_word_index].append(idx) + + # Randomly mask words + mask = np.random.binomial(1, wwm_probability, (len(mapping),)) + input_ids = feature["input_ids"] + labels = feature["labels"] + new_labels = [-100] * len(labels) + for word_id in np.where(mask)[0]: + word_id = word_id.item() + for idx in mapping[word_id]: + new_labels[idx] = labels[idx] + input_ids[idx] = tokenizer.mask_token_id + feature["labels"] = new_labels + + return tf_default_data_collator(features) +``` + +{/if} + +Next, we can try it on the same samples as before: + +```py +samples = [lm_datasets["train"][i] for i in range(2)] +batch = whole_word_masking_data_collator(samples) + +for chunk in batch["input_ids"]: + print(f"\n'>>> {tokenizer.decode(chunk)}'") +``` + +```python out +'>>> [CLS] bromwell high is a cartoon comedy [MASK] it ran at the same time as some other programs about school life, such as " teachers ". my 35 years in the teaching profession lead me to believe that bromwell high\'s satire is much closer to reality than is " teachers ". the scramble to survive financially, the insightful students who can see right through their pathetic teachers\'pomp, the pettiness of the whole situation, all remind me of the schools i knew and their students. when i saw the episode in which a student repeatedly tried to burn down the school, i immediately recalled.....' + +'>>> .... [MASK] [MASK] [MASK] [MASK]....... high. a classic line : inspector : i\'m here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn\'t! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless' +``` + + + +✏️ **Try it out!** Run the code snippet above several times to see the random masking happen in front of your very eyes! Also replace the `tokenizer.decode()` method with `tokenizer.convert_ids_to_tokens()` to see that the tokens from a given word are always masked together. + + + +Now that we have two data collators, the rest of the fine-tuning steps are standard. Training can take a while on Google Colab if you're not lucky enough to score a mythical P100 GPU 😭, so we'll first downsample the size of the training set to a few thousand examples. Don't worry, we'll still get a pretty decent language model! A quick way to downsample a dataset in 🤗 Datasets is via the `Dataset.train_test_split()` function that we saw in [Chapter 5](/course/chapter5): + +```python +train_size = 10_000 +test_size = int(0.1 * train_size) + +downsampled_dataset = lm_datasets["train"].train_test_split( + train_size=train_size, test_size=test_size, seed=42 +) +downsampled_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 10000 + }) + test: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 1000 + }) +}) +``` + +This has automatically created new `train` and `test` splits, with the training set size set to 10,000 examples and the validation set to 10% of that -- feel free to increase this if you have a beefy GPU! The next thing we need to do is log in to the Hugging Face Hub. If you're running this code in a notebook, you can do so with the following utility function: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +which will display a widget where you can enter your credentials. Alternatively, you can run: + +``` +huggingface-cli login +``` + +in your favorite terminal and log in there. + +{#if fw === 'tf'} + +Once we're logged in, we can create our `tf.data` datasets. To do so, we'll use the `prepare_tf_dataset()` method, which uses our model to automatically infer which columns should go into the dataset. If you want to control exactly which columns to use, you can use the `Dataset.to_tf_dataset()` method instead. To keep things simple, we'll just use the standard data collator here, but you can also try the whole word masking collator and compare the results as an exercise: + +```python +tf_train_dataset = model.prepare_tf_dataset( + downsampled_dataset["train"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) + +tf_eval_dataset = model.prepare_tf_dataset( + downsampled_dataset["test"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +Next, we set up our training hyperparameters and compile our model. We use the `create_optimizer()` function from the 🤗 Transformers library, which gives us an `AdamW` optimizer with linear learning rate decay. We also use the model's built-in loss, which is the default when no loss is specified as an argument to `compile()`, and we set the training precision to `"mixed_float16"`. Note that if you're using a Colab GPU or other GPU that does not have accelerated float16 support, you should probably comment out that line. + +In addition, we set up a `PushToHubCallback` that will save the model to the Hub after each epoch. You can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, to push the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`. By default, the repository used will be in your namespace and named after the output directory you set, so in our case it will be `"lewtun/distilbert-finetuned-imdb"`. + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +num_train_steps = len(tf_train_dataset) +optimizer, schedule = create_optimizer( + init_lr=2e-5, + num_warmup_steps=1_000, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +model_name = model_checkpoint.split("/")[-1] +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer +) +``` + +We're now ready to run `model.fit()` -- but before doing so let's briefly look at _perplexity_, which is a common metric to evaluate the performance of language models. + +{:else} + +Once we're logged in, we can specify the arguments for the `Trainer`: + +```python +from transformers import TrainingArguments + +batch_size = 64 +# Show the training loss with every epoch +logging_steps = len(downsampled_dataset["train"]) // batch_size +model_name = model_checkpoint.split("/")[-1] + +training_args = TrainingArguments( + output_dir=f"{model_name}-finetuned-imdb", + overwrite_output_dir=True, + evaluation_strategy="epoch", + learning_rate=2e-5, + weight_decay=0.01, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + push_to_hub=True, + fp16=True, + logging_steps=logging_steps, +) +``` + +Here we tweaked a few of the default options, including `logging_steps` to ensure we track the training loss with each epoch. We've also used `fp16=True` to enable mixed-precision training, which gives us another boost in speed. By default, the `Trainer` will remove any columns that are not part of the model's `forward()` method. This means that if you're using the whole word masking collator, you'll also need to set `remove_unused_columns=False` to ensure we don't lose the `word_ids` column during training. + +Note that you can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` to `TrainingArguments`. By default, the repository used will be in your namespace and named after the output directory you set, so in our case it will be `"lewtun/distilbert-finetuned-imdb"`. + +We now have all the ingredients to instantiate the `Trainer`. Here we just use the standard `data_collator`, but you can try the whole word masking collator and compare the results as an exercise: + +```python +from transformers import Trainer + +trainer = Trainer( + model=model, + args=training_args, + train_dataset=downsampled_dataset["train"], + eval_dataset=downsampled_dataset["test"], + data_collator=data_collator, + tokenizer=tokenizer, +) +``` + +We're now ready to run `trainer.train()` -- but before doing so let's briefly look at _perplexity_, which is a common metric to evaluate the performance of language models. + +{/if} + +### Perplexity for language models[[perplexity-for-language-models]] + + + +Unlike other tasks like text classification or question answering where we're given a labeled corpus to train on, with language modeling we don't have any explicit labels. So how do we determine what makes a good language model? Like with the autocorrect feature in your phone, a good language model is one that assigns high probabilities to sentences that are grammatically correct, and low probabilities to nonsense sentences. To give you a better idea of what this looks like, you can find whole sets of "autocorrect fails" online, where the model in a person's phone has produced some rather funny (and often inappropriate) completions! + +{#if fw === 'pt'} + +Assuming our test set consists mostly of sentences that are grammatically correct, then one way to measure the quality of our language model is to calculate the probabilities it assigns to the next word in all the sentences of the test set. High probabilities indicates that the model is not "surprised" or "perplexed" by the unseen examples, and suggests it has learned the basic patterns of grammar in the language. There are various mathematical definitions of perplexity, but the one we'll use defines it as the exponential of the cross-entropy loss. Thus, we can calculate the perplexity of our pretrained model by using the `Trainer.evaluate()` function to compute the cross-entropy loss on the test set and then taking the exponential of the result: + +```python +import math + +eval_results = trainer.evaluate() +print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") +``` + +{:else} + +Assuming our test set consists mostly of sentences that are grammatically correct, then one way to measure the quality of our language model is to calculate the probabilities it assigns to the next word in all the sentences of the test set. High probabilities indicates that the model indicates that the model is not "surprised" or "perplexed" by the unseen examples, and suggests it has learned the basic patterns of grammar in the language. There are various mathematical definitions of perplexity, but the one we'll use defines it as the exponential of the cross-entropy loss. Thus, we can calculate the perplexity of our pretrained model by using the `model.evaluate()` method to compute the cross-entropy loss on the test set and then taking the exponential of the result: + +```python +import math + +eval_loss = model.evaluate(tf_eval_dataset) +print(f"Perplexity: {math.exp(eval_loss):.2f}") +``` + +{/if} + +```python out +>>> Perplexity: 21.75 +``` + +A lower perplexity score means a better language model, and we can see here that our starting model has a somewhat large value. Let's see if we can lower it by fine-tuning! To do that, we first run the training loop: + +{#if fw === 'pt'} + +```python +trainer.train() +``` + +{:else} + +```python +model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) +``` + +{/if} + +and then compute the resulting perplexity on the test set as before: + +{#if fw === 'pt'} + +```python +eval_results = trainer.evaluate() +print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") +``` + +{:else} + +```python +eval_loss = model.evaluate(tf_eval_dataset) +print(f"Perplexity: {math.exp(eval_loss):.2f}") +``` + +{/if} + +```python out +>>> Perplexity: 11.32 +``` + +Nice -- this is quite a reduction in perplexity, which tells us the model has learned something about the domain of movie reviews! + +{#if fw === 'pt'} + +Once training is finished, we can push the model card with the training information to the Hub (the checkpoints are saved during training itself): + +```python +trainer.push_to_hub() +``` + +{/if} + + + +✏️ **Your turn!** Run the training above after changing the data collator to the whole word masking collator. Do you get better results? + + + +{#if fw === 'pt'} + +In our use case we didn't need to do anything special with the training loop, but in some cases you might need to implement some custom logic. For these applications, you can use 🤗 Accelerate -- let's take a look! + +## Fine-tuning DistilBERT with 🤗 Accelerate[[fine-tuning-distilbert-with-accelerate]] + +As we saw with the `Trainer`, fine-tuning a masked language model is very similar to the text classification example from [Chapter 3](/course/chapter3). In fact, the only subtlety is the use of a special data collator, and we've already covered that earlier in this section! + +However, we saw that `DataCollatorForLanguageModeling` also applies random masking with each evaluation, so we'll see some fluctuations in our perplexity scores with each training run. One way to eliminate this source of randomness is to apply the masking _once_ on the whole test set, and then use the default data collator in 🤗 Transformers to collect the batches during evaluation. To see how this works, let's implement a simple function that applies the masking on a batch, similar to our first encounter with `DataCollatorForLanguageModeling`: + +```python +def insert_random_mask(batch): + features = [dict(zip(batch, t)) for t in zip(*batch.values())] + masked_inputs = data_collator(features) + # Create a new "masked" column for each column in the dataset + return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()} +``` + +Next, we'll apply this function to our test set and drop the unmasked columns so we can replace them with the masked ones. You can use whole word masking by replacing the `data_collator` above with the appropriate one, in which case you should remove the first line here: + +```py +downsampled_dataset = downsampled_dataset.remove_columns(["word_ids"]) +eval_dataset = downsampled_dataset["test"].map( + insert_random_mask, + batched=True, + remove_columns=downsampled_dataset["test"].column_names, +) +eval_dataset = eval_dataset.rename_columns( + { + "masked_input_ids": "input_ids", + "masked_attention_mask": "attention_mask", + "masked_labels": "labels", + } +) +``` + +We can then set up the dataloaders as usual, but we'll use the `default_data_collator` from 🤗 Transformers for the evaluation set: + +```python +from torch.utils.data import DataLoader +from transformers import default_data_collator + +batch_size = 64 +train_dataloader = DataLoader( + downsampled_dataset["train"], + shuffle=True, + batch_size=batch_size, + collate_fn=data_collator, +) +eval_dataloader = DataLoader( + eval_dataset, batch_size=batch_size, collate_fn=default_data_collator +) +``` + +Form here, we follow the standard steps with 🤗 Accelerate. The first order of business is to load a fresh version of the pretrained model: + +``` +model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Then we need to specify the optimizer; we'll use the standard `AdamW`: + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +With these objects, we can now prepare everything for training with the `Accelerator` object: + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Now that our model, optimizer, and dataloaders are configured, we can specify the learning rate scheduler as follows: + +```python +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, +) +``` + +There is just one last thing to do before training: create a model repository on the Hugging Face Hub! We can use the 🤗 Hub library to first generate the full name of our repo: + +```python +from huggingface_hub import get_full_repo_name + +model_name = "distilbert-base-uncased-finetuned-imdb-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'lewtun/distilbert-base-uncased-finetuned-imdb-accelerate' +``` + +then create and clone the repository using the `Repository` class from 🤗 Hub: + +```python +from huggingface_hub import Repository + +output_dir = model_name +repo = Repository(output_dir, clone_from=repo_name) +``` + +With that done, it's just a simple matter of writing out the full training and evaluation loop: + +```python +from tqdm.auto import tqdm +import torch +import math + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + model.train() + for batch in 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() + losses = [] + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + outputs = model(**batch) + + loss = outputs.loss + losses.append(accelerator.gather(loss.repeat(batch_size))) + + losses = torch.cat(losses) + losses = losses[: len(eval_dataset)] + try: + perplexity = math.exp(torch.mean(losses)) + except OverflowError: + perplexity = float("inf") + + print(f">>> Epoch {epoch}: Perplexity: {perplexity}") + + # 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 + ) +``` + +```python out +>>> Epoch 0: Perplexity: 11.397545307900472 +>>> Epoch 1: Perplexity: 10.904909330983092 +>>> Epoch 2: Perplexity: 10.729503505340409 +``` + +Cool, we've been able to evaluate perplexity with each epoch and ensure that multiple training runs are reproducible! + +{/if} + +## Using our fine-tuned model[[using-our-fine-tuned-model]] + +You can interact with your fine-tuned model either by using its widget on the Hub or locally with the `pipeline` from 🤗 Transformers. Let's use the latter to download our model using the `fill-mask` pipeline: + +```python +from transformers import pipeline + +mask_filler = pipeline( + "fill-mask", model="huggingface-course/distilbert-base-uncased-finetuned-imdb" +) +``` + +We can then feed the pipeline our sample text of "This is a great [MASK]" and see what the top 5 predictions are: + +```python +preds = mask_filler(text) + +for pred in preds: + print(f">>> {pred['sequence']}") +``` + +```python out +'>>> this is a great movie.' +'>>> this is a great film.' +'>>> this is a great story.' +'>>> this is a great movies.' +'>>> this is a great character.' +``` + +Neat -- our model has clearly adapted its weights to predict words that are more strongly associated with movies! + + + +This wraps up our first experiment with training a language model. In [section 6](/course/en/chapter7/6) you'll learn how to train an auto-regressive model like GPT-2 from scratch; head over there if you'd like to see how you can pretrain your very own Transformer model! + + + +✏️ **Try it out!** To quantify the benefits of domain adaptation, fine-tune a classifier on the IMDb labels for both the pretrained and fine-tuned DistilBERT checkpoints. If you need a refresher on text classification, check out [Chapter 3](/course/chapter3). + + diff --git a/chapters/ro/chapter7/4.mdx b/chapters/ro/chapter7/4.mdx new file mode 100644 index 000000000..cdae18d5b --- /dev/null +++ b/chapters/ro/chapter7/4.mdx @@ -0,0 +1,1002 @@ + + +# Translation[[translation]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Let's now dive into translation. This is another [sequence-to-sequence task](/course/chapter1/7), which means it's a problem that can be formulated as going from one sequence to another. In that sense the problem is pretty close to [summarization](/course/chapter7/6), and you could adapt what we will see here to other sequence-to-sequence problems such as: + +- **Style transfer**: Creating a model that *translates* texts written in a certain style to another (e.g., formal to casual or Shakespearean English to modern English) +- **Generative question answering**: Creating a model that generates answers to questions, given a context + + + +If you have a big enough corpus of texts in two (or more) languages, you can train a new translation model from scratch like we will in the section on [causal language modeling](/course/chapter7/6). It will be faster, however, to fine-tune an existing translation model, be it a multilingual one like mT5 or mBART that you want to fine-tune to a specific language pair, or even a model specialized for translation from one language to another that you want to fine-tune to your specific corpus. + +In this section, we will fine-tune a Marian model pretrained to translate from English to French (since a lot of Hugging Face employees speak both those languages) on the [KDE4 dataset](https://huggingface.co/datasets/kde4), which is a dataset of localized files for the [KDE apps](https://apps.kde.org/). The model we will use has been pretrained on a large corpus of French and English texts taken from the [Opus dataset](https://opus.nlpl.eu/), which actually contains the KDE4 dataset. But even if the pretrained model we use has seen that data during its pretraining, we will see that we can get a better version of it after fine-tuning. + +Once we're finished, we will have a model able to make predictions like this one: + + + + +One-hot encoded labels for question answering. + + + +As in the previous sections, you can find the actual model that we'll train and upload to the Hub using the code below and double-check its predictions [here](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.). + +## Preparing the data[[preparing-the-data]] + +To fine-tune or train a translation model from scratch, we will need a dataset suitable for the task. As mentioned previously, we'll use the [KDE4 dataset](https://huggingface.co/datasets/kde4) in this section, but you can adapt the code to use your own data quite easily, as long as you have pairs of sentences in the two languages you want to translate from and into. Refer back to [Chapter 5](/course/chapter5) if you need a reminder of how to load your custom data in a `Dataset`. + +### The KDE4 dataset[[the-kde4-dataset]] + +As usual, we download our dataset using the `load_dataset()` function: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") +``` + +If you want to work with a different pair of languages, you can specify them by their codes. A total of 92 languages are available for this dataset; you can see them all by expanding the language tags on its [dataset card](https://huggingface.co/datasets/kde4). + +Language available for the KDE4 dataset. + +Let's have a look at the dataset: + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'translation'], + num_rows: 210173 + }) +}) +``` + +We have 210,173 pairs of sentences, but in one single split, so we will need to create our own validation set. As we saw in [Chapter 5](/course/chapter5), a `Dataset` has a `train_test_split()` method that can help us. We'll provide a seed for reproducibility: + +```py +split_datasets = raw_datasets["train"].train_test_split(train_size=0.9, seed=20) +split_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'translation'], + num_rows: 189155 + }) + test: Dataset({ + features: ['id', 'translation'], + num_rows: 21018 + }) +}) +``` + +We can rename the `"test"` key to `"validation"` like this: + +```py +split_datasets["validation"] = split_datasets.pop("test") +``` + +Now let's take a look at one element of the dataset: + +```py +split_datasets["train"][1]["translation"] +``` + +```python out +{'en': 'Default to expanded threads', + 'fr': 'Par défaut, développer les fils de discussion'} +``` + +We get a dictionary with two sentences in the pair of languages we requested. One particularity of this dataset full of technical computer science terms is that they are all fully translated in French. However, French engineers leave most computer science-specific words in English when they talk. Here, for instance, the word "threads" might well appear in a French sentence, especially in a technical conversation; but in this dataset it has been translated into the more correct "fils de discussion." The pretrained model we use, which has been pretrained on a larger corpus of French and English sentences, takes the easier option of leaving the word as is: + +```py +from transformers import pipeline + +model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" +translator = pipeline("translation", model=model_checkpoint) +translator("Default to expanded threads") +``` + +```python out +[{'translation_text': 'Par défaut pour les threads élargis'}] +``` + +Another example of this behavior can be seen with the word "plugin," which isn't officially a French word but which most native speakers will understand and not bother to translate. +In the KDE4 dataset this word has been translated in French into the more official "module d'extension": + +```py +split_datasets["train"][172]["translation"] +``` + +```python out +{'en': 'Unable to import %1 using the OFX importer plugin. This file is not the correct format.', + 'fr': "Impossible d'importer %1 en utilisant le module d'extension d'importation OFX. Ce fichier n'a pas un format correct."} +``` + +Our pretrained model, however, sticks with the compact and familiar English word: + +```py +translator( + "Unable to import %1 using the OFX importer plugin. This file is not the correct format." +) +``` + +```python out +[{'translation_text': "Impossible d'importer %1 en utilisant le plugin d'importateur OFX. Ce fichier n'est pas le bon format."}] +``` + +It will be interesting to see if our fine-tuned model picks up on those particularities of the dataset (spoiler alert: it will). + + + + + +✏️ **Your turn!** Another English word that is often used in French is "email." Find the first sample in the training dataset that uses this word. How is it translated? How does the pretrained model translate the same English sentence? + + + +### Processing the data[[processing-the-data]] + + + +You should know the drill by now: the texts all need to be converted into sets of token IDs so the model can make sense of them. For this task, we'll need to tokenize both the inputs and the targets. Our first task is to create our `tokenizer` object. As noted earlier, we'll be using a Marian English to French pretrained model. If you are trying this code with another pair of languages, make sure to adapt the model checkpoint. The [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) organization provides more than a thousand models in multiple languages. + +```python +from transformers import AutoTokenizer + +model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="pt") +``` + +You can also replace the `model_checkpoint` with any other model you prefer from the [Hub](https://huggingface.co/models), or a local folder where you've saved a pretrained model and a tokenizer. + + + +💡 If you are using a multilingual tokenizer such as mBART, mBART-50, or M2M100, you will need to set the language codes of your inputs and targets in the tokenizer by setting `tokenizer.src_lang` and `tokenizer.tgt_lang` to the right values. + + + +The preparation of our data is pretty straightforward. There's just one thing to remember; you need to ensure that the tokenizer processes the targets in the output language (here, French). You can do this by passing the targets to the `text_targets` argument of the tokenizer's `__call__` method. + +To see how this works, let's process one sample of each language in the training set: + +```python +en_sentence = split_datasets["train"][1]["translation"]["en"] +fr_sentence = split_datasets["train"][1]["translation"]["fr"] + +inputs = tokenizer(en_sentence, text_target=fr_sentence) +inputs +``` + +```python out +{'input_ids': [47591, 12, 9842, 19634, 9, 0], 'attention_mask': [1, 1, 1, 1, 1, 1], 'labels': [577, 5891, 2, 3184, 16, 2542, 5, 1710, 0]} +``` + +As we can see, the output contains the input IDs associated with the English sentence, while the IDs associated with the French one are stored in the `labels` field. If you forget to indicate that you are tokenizing labels, they will be tokenized by the input tokenizer, which in the case of a Marian model is not going to go well at all: + +```python +wrong_targets = tokenizer(fr_sentence) +print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) +print(tokenizer.convert_ids_to_tokens(inputs["labels"])) +``` + +```python out +['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', ''] +['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', ''] +``` + +As we can see, using the English tokenizer to preprocess a French sentence results in a lot more tokens, since the tokenizer doesn't know any French words (except those that also appear in the English language, like "discussion"). + +Since `inputs` is a dictionary with our usual keys (input IDs, attention mask, etc.), the last step is to define the preprocessing function we will apply on the datasets: + +```python +max_length = 128 + + +def preprocess_function(examples): + inputs = [ex["en"] for ex in examples["translation"]] + targets = [ex["fr"] for ex in examples["translation"]] + model_inputs = tokenizer( + inputs, text_target=targets, max_length=max_length, truncation=True + ) + return model_inputs +``` + +Note that we set the same maximum length for our inputs and outputs. Since the texts we're dealing with seem pretty short, we use 128. + + + +💡 If you are using a T5 model (more specifically, one of the `t5-xxx` checkpoints), the model will expect the text inputs to have a prefix indicating the task at hand, such as `translate: English to French:`. + + + + + +⚠️ We don't pay attention to the attention mask of the targets, as the model won't expect it. Instead, the labels corresponding to a padding token should be set to `-100` so they are ignored in the loss computation. This will be done by our data collator later on since we are applying dynamic padding, but if you use padding here, you should adapt the preprocessing function to set all labels that correspond to the padding token to `-100`. + + + +We can now apply that preprocessing in one go on all the splits of our dataset: + +```py +tokenized_datasets = split_datasets.map( + preprocess_function, + batched=True, + remove_columns=split_datasets["train"].column_names, +) +``` + +Now that the data has been preprocessed, we are ready to fine-tune our pretrained model! + +{#if fw === 'pt'} + +## Fine-tuning the model with the `Trainer` API[[fine-tuning-the-model-with-the-trainer-api]] + +The actual code using the `Trainer` will be the same as before, with just one little change: we use a [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer) here, which is a subclass of `Trainer` that will allow us to properly deal with the evaluation, using the `generate()` method to predict outputs from the inputs. We'll dive into that in more detail when we talk about the metric computation. + +First things first, we need an actual model to fine-tune. We'll use the usual `AutoModel` API: + +```py +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## Fine-tuning the model with Keras[[fine-tuning-the-model-with-keras]] + +First things first, we need an actual model to fine-tune. We'll use the usual `AutoModel` API: + +```py +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) +``` + + + +💡 The `Helsinki-NLP/opus-mt-en-fr` checkpoint only has PyTorch weights, so +you'll get an error if you try to load the model without using the +`from_pt=True` argument in the `from_pretrained()` method. When you specify +`from_pt=True`, the library will automatically download and convert the +PyTorch weights for you. As you can see, it is very simple to switch between +frameworks in 🤗 Transformers! + + + +{/if} + +Note that this time we are using a model that was trained on a translation task and can actually be used already, so there is no warning about missing weights or newly initialized ones. + +### Data collation[[data-collation]] + +We'll need a data collator to deal with the padding for dynamic batching. We can't just use a `DataCollatorWithPadding` like in [Chapter 3](/course/chapter3) in this case, because that only pads the inputs (input IDs, attention mask, and token type IDs). Our labels should also be padded to the maximum length encountered in the labels. And, as mentioned previously, the padding value used to pad the labels should be `-100` and not the padding token of the tokenizer, to make sure those padded values are ignored in the loss computation. + +This is all done by a [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Like the `DataCollatorWithPadding`, it takes the `tokenizer` used to preprocess the inputs, but it also takes the `model`. This is because this data collator will also be responsible for preparing the decoder input IDs, which are shifted versions of the labels with a special token at the beginning. Since this shift is done slightly differently for different architectures, the `DataCollatorForSeq2Seq` needs to know the `model` object: + +{#if fw === 'pt'} + +```py +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) +``` + +{:else} + +```py +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf") +``` + +{/if} + +To test this on a few samples, we just call it on a list of examples from our tokenized training set: + +```py +batch = data_collator([tokenized_datasets["train"][i] for i in range(1, 3)]) +batch.keys() +``` + +```python out +dict_keys(['attention_mask', 'input_ids', 'labels', 'decoder_input_ids']) +``` + +We can check our labels have been padded to the maximum length of the batch, using `-100`: + +```py +batch["labels"] +``` + +```python out +tensor([[ 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, -100, + -100, -100, -100, -100, -100, -100], + [ 1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, + 550, 7032, 5821, 7907, 12649, 0]]) +``` + +And we can also have a look at the decoder input IDs, to see that they are shifted versions of the labels: + +```py +batch["decoder_input_ids"] +``` + +```python out +tensor([[59513, 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, + 59513, 59513, 59513, 59513, 59513, 59513], + [59513, 1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, + 817, 550, 7032, 5821, 7907, 12649]]) +``` + +Here are the labels for the first and second elements in our dataset: + +```py +for i in range(1, 3): + print(tokenized_datasets["train"][i]["labels"]) +``` + +```python out +[577, 5891, 2, 3184, 16, 2542, 5, 1710, 0] +[1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, 550, 7032, 5821, 7907, 12649, 0] +``` + +{#if fw === 'pt'} + +We will pass this `data_collator` along to the `Seq2SeqTrainer`. Next, let's have a look at the metric. + +{:else} + +We can now use this `data_collator` to convert each of our datasets to a `tf.data.Dataset`, ready for training: + +```python +tf_train_dataset = model.prepare_tf_dataset( + tokenized_datasets["train"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +{/if} + + +### Metrics[[metrics]] + + + +{#if fw === 'pt'} + +The feature that `Seq2SeqTrainer` adds to its superclass `Trainer` is the ability to use the `generate()` method during evaluation or prediction. During training, the model will use the `decoder_input_ids` with an attention mask ensuring it does not use the tokens after the token it's trying to predict, to speed up training. During inference we won't be able to use those since we won't have labels, so it's a good idea to evaluate our model with the same setup. + +As we saw in [Chapter 1](/course/chapter1/6), the decoder performs inference by predicting tokens one by one -- something that's implemented behind the scenes in 🤗 Transformers by the `generate()` method. The `Seq2SeqTrainer` will let us use that method for evaluation if we set `predict_with_generate=True`. + +{/if} + +The traditional metric used for translation is the [BLEU score](https://en.wikipedia.org/wiki/BLEU), introduced in [a 2002 article](https://aclanthology.org/P02-1040.pdf) by Kishore Papineni et al. The BLEU score evaluates how close the translations are to their labels. It does not measure the intelligibility or grammatical correctness of the model's generated outputs, but uses statistical rules to ensure that all the words in the generated outputs also appear in the targets. In addition, there are rules that penalize repetitions of the same words if they are not also repeated in the targets (to avoid the model outputting sentences like `"the the the the the"`) and output sentences that are shorter than those in the targets (to avoid the model outputting sentences like `"the"`). + +One weakness with BLEU is that it expects the text to already be tokenized, which makes it difficult to compare scores between models that use different tokenizers. So instead, the most commonly used metric for benchmarking translation models today is [SacreBLEU](https://github.com/mjpost/sacrebleu), which addresses this weakness (and others) by standardizing the tokenization step. To use this metric, we first need to install the SacreBLEU library: + +```py +!pip install sacrebleu +``` + +We can then load it via `evaluate.load()` like we did in [Chapter 3](/course/chapter3): + +```py +import evaluate + +metric = evaluate.load("sacrebleu") +``` + +This metric will take texts as inputs and targets. It is designed to accept several acceptable targets, as there are often multiple acceptable translations of the same sentence -- the dataset we're using only provides one, but it's not uncommon in NLP to find datasets that give several sentences as labels. So, the predictions should be a list of sentences, but the references should be a list of lists of sentences. + +Let's try an example: + +```py +predictions = [ + "This plugin lets you translate web pages between several languages automatically." +] +references = [ + [ + "This plugin allows you to automatically translate web pages between several languages." + ] +] +metric.compute(predictions=predictions, references=references) +``` + +```python out +{'score': 46.750469682990165, + 'counts': [11, 6, 4, 3], + 'totals': [12, 11, 10, 9], + 'precisions': [91.67, 54.54, 40.0, 33.33], + 'bp': 0.9200444146293233, + 'sys_len': 12, + 'ref_len': 13} +``` + +This gets a BLEU score of 46.75, which is rather good -- for reference, the original Transformer model in the ["Attention Is All You Need" paper](https://arxiv.org/pdf/1706.03762.pdf) achieved a BLEU score of 41.8 on a similar translation task between English and French! (For more information about the individual metrics, like `counts` and `bp`, see the [SacreBLEU repository](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74).) On the other hand, if we try with the two bad types of predictions (lots of repetitions or too short) that often come out of translation models, we will get rather bad BLEU scores: + +```py +predictions = ["This This This This"] +references = [ + [ + "This plugin allows you to automatically translate web pages between several languages." + ] +] +metric.compute(predictions=predictions, references=references) +``` + +```python out +{'score': 1.683602693167689, + 'counts': [1, 0, 0, 0], + 'totals': [4, 3, 2, 1], + 'precisions': [25.0, 16.67, 12.5, 12.5], + 'bp': 0.10539922456186433, + 'sys_len': 4, + 'ref_len': 13} +``` + +```py +predictions = ["This plugin"] +references = [ + [ + "This plugin allows you to automatically translate web pages between several languages." + ] +] +metric.compute(predictions=predictions, references=references) +``` + +```python out +{'score': 0.0, + 'counts': [2, 1, 0, 0], + 'totals': [2, 1, 0, 0], + 'precisions': [100.0, 100.0, 0.0, 0.0], + 'bp': 0.004086771438464067, + 'sys_len': 2, + 'ref_len': 13} +``` + +The score can go from 0 to 100, and higher is better. + +{#if fw === 'tf'} + +To get from the model outputs to texts the metric can use, we will use the `tokenizer.batch_decode()` method. We just have to clean up all the `-100`s in the labels; the tokenizer will automatically do the same for the padding token. Let's define a function that takes our model and a dataset and computes metrics on it. We're also going to use a trick that dramatically increases performance - compiling our generation code with [XLA](https://www.tensorflow.org/xla), TensorFlow's accelerated linear algebra compiler. XLA applies various optimizations to the model's computation graph, and results in significant improvements to speed and memory usage. As described in the Hugging Face [blog](https://huggingface.co/blog/tf-xla-generate), XLA works best when our input shapes don't vary too much. To handle this, we'll pad our inputs to multiples of 128, and make a new dataset with the padding collator, and then we'll apply the `@tf.function(jit_compile=True)` decorator to our generation function, which marks the whole function for compilation with XLA. + +```py +import numpy as np +import tensorflow as tf +from tqdm import tqdm + +generation_data_collator = DataCollatorForSeq2Seq( + tokenizer, model=model, return_tensors="tf", pad_to_multiple_of=128 +) + +tf_generate_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], + collate_fn=generation_data_collator, + shuffle=False, + batch_size=8, +) + + +@tf.function(jit_compile=True) +def generate_with_xla(batch): + return model.generate( + input_ids=batch["input_ids"], + attention_mask=batch["attention_mask"], + max_new_tokens=128, + ) + + +def compute_metrics(): + all_preds = [] + all_labels = [] + + for batch, labels in tqdm(tf_generate_dataset): + predictions = generate_with_xla(batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = labels.numpy() + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + all_preds.extend(decoded_preds) + all_labels.extend(decoded_labels) + + result = metric.compute(predictions=all_preds, references=all_labels) + return {"bleu": result["score"]} +``` + +{:else} + +To get from the model outputs to texts the metric can use, we will use the `tokenizer.batch_decode()` method. We just have to clean up all the `-100`s in the labels (the tokenizer will automatically do the same for the padding token): + +```py +import numpy as np + + +def compute_metrics(eval_preds): + preds, labels = eval_preds + # In case the model returns more than the prediction logits + if isinstance(preds, tuple): + preds = preds[0] + + decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) + + # Replace -100s in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Some simple post-processing + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + + result = metric.compute(predictions=decoded_preds, references=decoded_labels) + return {"bleu": result["score"]} +``` + +{/if} + +Now that this is done, we are ready to fine-tune our model! + + +### Fine-tuning the model[[fine-tuning-the-model]] + +The first step is to log in to Hugging Face, so you're able to upload your results to the Model Hub. There's a convenience function to help you with this in a notebook: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +This will display a widget where you can enter your Hugging Face login credentials. + +If you aren't working in a notebook, just type the following line in your terminal: + +```bash +huggingface-cli login +``` + +{#if fw === 'tf'} + +Before we start, let's see what kind of results we get from our model without any training: + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 33.26983701454733} +``` + +Once this is done, we can prepare everything we need to compile and train our model. Note the use of `tf.keras.mixed_precision.set_global_policy("mixed_float16")` -- this will tell Keras to train using float16, which can give a significant speedup on GPUs that support it (Nvidia 20xx/V100 or newer). + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied +# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset, +# not the original Hugging Face Dataset, so its len() is already num_samples // batch_size. +num_epochs = 3 +num_train_steps = len(tf_train_dataset) * num_epochs + +optimizer, schedule = create_optimizer( + init_lr=5e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Next, we define a `PushToHubCallback` to upload our model to the Hub during training, as we saw in [section 2]((/course/chapter7/2)), and then we simply fit the model with that callback: + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback( + output_dir="marian-finetuned-kde4-en-to-fr", tokenizer=tokenizer +) + +model.fit( + tf_train_dataset, + validation_data=tf_eval_dataset, + callbacks=[callback], + epochs=num_epochs, +) +``` + +Note that you can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` to `Seq2SeqTrainingArguments`. By default, the repository used will be in your namespace and named after the output directory you set, so here it will be `"sgugger/marian-finetuned-kde4-en-to-fr"` (which is the model we linked to at the beginning of this section). + + + +💡 If the output directory you are using already exists, it needs to be a local clone of the repository you want to push to. If it isn't, you'll get an error when calling `model.fit()` and will need to set a new name. + + + +Finally, let's see what our metrics look like now that training has finished: + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 57.334066271545865} +``` + +At this stage, you can use the inference widget on the Model Hub to test your model and share it with your friends. You have successfully fine-tuned a model on a translation task -- congratulations! + +{:else} + +Once this is done, we can define our `Seq2SeqTrainingArguments`. Like for the `Trainer`, we use a subclass of `TrainingArguments` that contains a few more fields: + +```python +from transformers import Seq2SeqTrainingArguments + +args = Seq2SeqTrainingArguments( + f"marian-finetuned-kde4-en-to-fr", + evaluation_strategy="no", + save_strategy="epoch", + learning_rate=2e-5, + per_device_train_batch_size=32, + per_device_eval_batch_size=64, + weight_decay=0.01, + save_total_limit=3, + num_train_epochs=3, + predict_with_generate=True, + fp16=True, + push_to_hub=True, +) +``` + +Apart from the usual hyperparameters (like learning rate, number of epochs, batch size, and some weight decay), here are a few changes compared to what we saw in the previous sections: + +- We don't set any regular evaluation, as evaluation takes a while; we will just evaluate our model once before training and after. +- We set `fp16=True`, which speeds up training on modern GPUs. +- We set `predict_with_generate=True`, as discussed above. +- We use `push_to_hub=True` to upload the model to the Hub at the end of each epoch. + +Note that you can specify the full name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` to `Seq2SeqTrainingArguments`. By default, the repository used will be in your namespace and named after the output directory you set, so in our case it will be `"sgugger/marian-finetuned-kde4-en-to-fr"` (which is the model we linked to at the beginning of this section). + + + +💡 If the output directory you are using already exists, it needs to be a local clone of the repository you want to push to. If it isn't, you'll get an error when defining your `Seq2SeqTrainer` and will need to set a new name. + + + + +Finally, we just pass everything to the `Seq2SeqTrainer`: + +```python +from transformers import Seq2SeqTrainer + +trainer = Seq2SeqTrainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, + compute_metrics=compute_metrics, +) +``` + +Before training, we'll first look at the score our model gets, to double-check that we're not making things worse with our fine-tuning. This command will take a bit of time, so you can grab a coffee while it executes: + +```python +trainer.evaluate(max_length=max_length) +``` + +```python out +{'eval_loss': 1.6964408159255981, + 'eval_bleu': 39.26865061007616, + 'eval_runtime': 965.8884, + 'eval_samples_per_second': 21.76, + 'eval_steps_per_second': 0.341} +``` + +A BLEU score of 39 is not too bad, which reflects the fact that our model is already good at translating English sentences to French ones. + +Next is the training, which will also take a bit of time: + +```python +trainer.train() +``` + +Note that while the training happens, each time the model is saved (here, every epoch) it is uploaded to the Hub in the background. This way, you will be able to to resume your training on another machine if necessary. + +Once training is done, we evaluate our model again -- hopefully we will see some amelioration in the BLEU score! + +```py +trainer.evaluate(max_length=max_length) +``` + +```python out +{'eval_loss': 0.8558505773544312, + 'eval_bleu': 52.94161337775576, + 'eval_runtime': 714.2576, + 'eval_samples_per_second': 29.426, + 'eval_steps_per_second': 0.461, + 'epoch': 3.0} +``` + +That's a nearly 14-point improvement, which is great. + +Finally, we use the `push_to_hub()` method to make sure we upload the latest version of the model. The `Trainer` also drafts a model card with all the evaluation results and uploads it. This model card contains metadata that helps the Model Hub pick the widget for the inference demo. Usually, there is no need to say anything as it can infer the right widget from the model class, but in this case, the same model class can be used for all kinds of sequence-to-sequence problems, so we specify it's a translation model: + +```py +trainer.push_to_hub(tags="translation", commit_message="Training complete") +``` + +This command returns the URL of the commit it just did, if you want to inspect it: + +```python out +'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' +``` + +At this stage, you can use the inference widget on the Model Hub to test your model and share it with your friends. You have successfully fine-tuned a model on a translation task -- congratulations! + +If you want to dive a bit more deeply into the training loop, we will now show you how to do the same thing using 🤗 Accelerate. + +{/if} + +{#if fw === 'pt'} + +## A custom training loop[[a-custom-training-loop]] + +Let's now take a look at the full training loop, so you can easily customize the parts you need. It will look a lot like what we did in [section 2](/course/chapter7/2) and [Chapter 3](/course/chapter3/4). + +### Preparing everything for training[[preparing-everything-for-training]] + +You've seen all of this a few times now, so we'll go through the code quite quickly. First we'll build the `DataLoader`s from our datasets, after setting the datasets to the `"torch"` format so we get PyTorch tensors: + +```py +from torch.utils.data import DataLoader + +tokenized_datasets.set_format("torch") +train_dataloader = DataLoader( + tokenized_datasets["train"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8 +) +``` + +Next we reinstantiate our model, to make sure we're not continuing the fine-tuning from before but starting from the pretrained model again: + +```py +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +Then we will need an optimizer: + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Once we have all those objects, we can send them to the `accelerator.prepare()` method. Remember that if you want to train on TPUs in a Colab notebook, you will need to move all of this code into a training function, and that shouldn't execute any cell that instantiates an `Accelerator`. + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Now that we have sent our `train_dataloader` to `accelerator.prepare()`, we can use its length to compute the number of training steps. Remember we should always do this after preparing the dataloader, as that method will change the length of the `DataLoader`. We use a classic linear schedule from the learning rate to 0: + +```py +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, +) +``` + +Lastly, to push our model to the Hub, we will need to create a `Repository` object in a working folder. First log in to the Hugging Face Hub, if you're not logged in already. We'll determine the repository name from the model ID we want to give our model (feel free to replace the `repo_name` with your own choice; it just needs to contain your username, which is what the function `get_full_repo_name()` does): + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "marian-finetuned-kde4-en-to-fr-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/marian-finetuned-kde4-en-to-fr-accelerate' +``` + +Then we can clone that repository in a local folder. If it already exists, this local folder should be a clone of the repository we are working with: + +```py +output_dir = "marian-finetuned-kde4-en-to-fr-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +We can now upload anything we save in `output_dir` by calling the `repo.push_to_hub()` method. This will help us upload the intermediate models at the end of each epoch. + +### Training loop[[training-loop]] + +We are now ready to write the full training loop. To simplify its evaluation part, we define this `postprocess()` function that takes predictions and labels and converts them to the lists of strings our `metric` object will expect: + +```py +def postprocess(predictions, labels): + predictions = predictions.cpu().numpy() + labels = labels.cpu().numpy() + + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + + # Replace -100 in the labels as we can't decode them. + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Some simple post-processing + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + return decoded_preds, decoded_labels +``` + +The training loop looks a lot like the ones in [section 2](/course/chapter7/2) and [Chapter 3](/course/chapter3), with a few differences in the evaluation part -- so let's focus on that! + +The first thing to note is that we use the `generate()` method to compute predictions, but this is a method on our base model, not the wrapped model 🤗 Accelerate created in the `prepare()` method. That's why we unwrap the model first, then call this method. + +The second thing is that, like with [token classification](/course/chapter7/2), two processes may have padded the inputs and labels to different shapes, so we use `accelerator.pad_across_processes()` to make the predictions and labels the same shape before calling the `gather()` method. If we don't do this, the evaluation will either error out or hang forever. + +```py +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 batch in 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() + for batch in tqdm(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + max_length=128, + ) + labels = batch["labels"] + + # Necessary to pad predictions and labels for being gathered + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100) + + predictions_gathered = accelerator.gather(generated_tokens) + labels_gathered = accelerator.gather(labels) + + decoded_preds, decoded_labels = postprocess(predictions_gathered, labels_gathered) + metric.add_batch(predictions=decoded_preds, references=decoded_labels) + + results = metric.compute() + print(f"epoch {epoch}, BLEU score: {results['score']:.2f}") + + # 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 + ) +``` + +```python out +epoch 0, BLEU score: 53.47 +epoch 1, BLEU score: 54.24 +epoch 2, BLEU score: 54.44 +``` + +Once this is done, you should have a model that has results pretty similar to the one trained with the `Seq2SeqTrainer`. You can check the one we trained using this code at [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). And if you want to test out any tweaks to the training loop, you can directly implement them by editing the code shown above! + +{/if} + +## Using the fine-tuned model[[using-the-fine-tuned-model]] + +We've already shown you how you can use the model we fine-tuned on the Model Hub with the inference widget. To use it locally in a `pipeline`, we just have to specify the proper model identifier: + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +model_checkpoint = "huggingface-course/marian-finetuned-kde4-en-to-fr" +translator = pipeline("translation", model=model_checkpoint) +translator("Default to expanded threads") +``` + +```python out +[{'translation_text': 'Par défaut, développer les fils de discussion'}] +``` + +As expected, our pretrained model adapted its knowledge to the corpus we fine-tuned it on, and instead of leaving the English word "threads" alone, it now translates it to the French official version. It's the same for "plugin": + +```py +translator( + "Unable to import %1 using the OFX importer plugin. This file is not the correct format." +) +``` + +```python out +[{'translation_text': "Impossible d'importer %1 en utilisant le module externe d'importation OFX. Ce fichier n'est pas le bon format."}] +``` + +Another great example of domain adaptation! + + + +✏️ **Your turn!** What does the model return on the sample with the word "email" you identified earlier? + + diff --git a/chapters/ro/chapter7/5.mdx b/chapters/ro/chapter7/5.mdx new file mode 100644 index 000000000..b8afcfaa0 --- /dev/null +++ b/chapters/ro/chapter7/5.mdx @@ -0,0 +1,1072 @@ + + +# Summarization[[summarization]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +In this section we'll take a look at how Transformer models can be used to condense long documents into summaries, a task known as _text summarization_. This is one of the most challenging NLP tasks as it requires a range of abilities, such as understanding long passages and generating coherent text that captures the main topics in a document. However, when done well, text summarization is a powerful tool that can speed up various business processes by relieving the burden of domain experts to read long documents in detail. + + + +Although there already exist various fine-tuned models for summarization on the [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), almost all of these are only suitable for English documents. So, to add a twist in this section, we'll train a bilingual model for English and Spanish. By the end of this section, you'll have a [model](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) that can summarize customer reviews like the one shown here: + + + +As we'll see, these summaries are concise because they're learned from the titles that customers provide in their product reviews. Let's start by putting together a suitable bilingual corpus for this task. + +## Preparing a multilingual corpus[[preparing-a-multilingual-corpus]] + +We'll use the [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) to create our bilingual summarizer. This corpus consists of Amazon product reviews in six languages and is typically used to benchmark multilingual classifiers. However, since each review is accompanied by a short title, we can use the titles as the target summaries for our model to learn from! To get started, let's download the English and Spanish subsets from the Hugging Face Hub: + +```python +from datasets import load_dataset + +spanish_dataset = load_dataset("amazon_reviews_multi", "es") +english_dataset = load_dataset("amazon_reviews_multi", "en") +english_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], + num_rows: 200000 + }) + validation: Dataset({ + features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], + num_rows: 5000 + }) + test: Dataset({ + features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], + num_rows: 5000 + }) +}) +``` + +As you can see, for each language there are 200,000 reviews for the `train` split, and 5,000 reviews for each of the `validation` and `test` splits. The review information we are interested in is contained in the `review_body` and `review_title` columns. Let's take a look at a few examples by creating a simple function that takes a random sample from the training set with the techniques we learned in [Chapter 5](/course/chapter5): + +```python +def show_samples(dataset, num_samples=3, seed=42): + sample = dataset["train"].shuffle(seed=seed).select(range(num_samples)) + for example in sample: + print(f"\n'>> Title: {example['review_title']}'") + print(f"'>> Review: {example['review_body']}'") + + +show_samples(english_dataset) +``` + +```python out +'>> Title: Worked in front position, not rear' +'>> Review: 3 stars because these are not rear brakes as stated in the item description. At least the mount adapter only worked on the front fork of the bike that I got it for.' + +'>> Title: meh' +'>> Review: Does it’s job and it’s gorgeous but mine is falling apart, I had to basically put it together again with hot glue' + +'>> Title: Can\'t beat these for the money' +'>> Review: Bought this for handling miscellaneous aircraft parts and hanger "stuff" that I needed to organize; it really fit the bill. The unit arrived quickly, was well packaged and arrived intact (always a good sign). There are five wall mounts-- three on the top and two on the bottom. I wanted to mount it on the wall, so all I had to do was to remove the top two layers of plastic drawers, as well as the bottom corner drawers, place it when I wanted and mark it; I then used some of the new plastic screw in wall anchors (the 50 pound variety) and it easily mounted to the wall. Some have remarked that they wanted dividers for the drawers, and that they made those. Good idea. My application was that I needed something that I can see the contents at about eye level, so I wanted the fuller-sized drawers. I also like that these are the new plastic that doesn\'t get brittle and split like my older plastic drawers did. I like the all-plastic construction. It\'s heavy duty enough to hold metal parts, but being made of plastic it\'s not as heavy as a metal frame, so you can easily mount it to the wall and still load it up with heavy stuff, or light stuff. No problem there. For the money, you can\'t beat it. Best one of these I\'ve bought to date-- and I\'ve been using some version of these for over forty years.' +``` + + + +✏️ **Try it out!** Change the random seed in the `Dataset.shuffle()` command to explore other reviews in the corpus. If you're a Spanish speaker, take a look at some of the reviews in `spanish_dataset` to see if the titles also seem like reasonable summaries. + + + +This sample shows the diversity of reviews one typically finds online, ranging from positive to negative (and everything in between!). Although the example with the "meh" title is not very informative, the other titles look like decent summaries of the reviews themselves. Training a summarization model on all 400,000 reviews would take far too long on a single GPU, so instead we'll focus on generating summaries for a single domain of products. To get a feel for what domains we can choose from, let's convert `english_dataset` to a `pandas.DataFrame` and compute the number of reviews per product category: + +```python +english_dataset.set_format("pandas") +english_df = english_dataset["train"][:] +# Show counts for top 20 products +english_df["product_category"].value_counts()[:20] +``` + +```python out +home 17679 +apparel 15951 +wireless 15717 +other 13418 +beauty 12091 +drugstore 11730 +kitchen 10382 +toy 8745 +sports 8277 +automotive 7506 +lawn_and_garden 7327 +home_improvement 7136 +pet_products 7082 +digital_ebook_purchase 6749 +pc 6401 +electronics 6186 +office_product 5521 +shoes 5197 +grocery 4730 +book 3756 +Name: product_category, dtype: int64 +``` + +The most popular products in the English dataset are about household items, clothing, and wireless electronics. To stick with the Amazon theme, though, let's focus on summarizing book reviews -- after all, this is what the company was founded on! We can see two product categories that fit the bill (`book` and `digital_ebook_purchase`), so let's filter the datasets in both languages for just these products. As we saw in [Chapter 5](/course/chapter5), the `Dataset.filter()` function allows us to slice a dataset very efficiently, so we can define a simple function to do this: + +```python +def filter_books(example): + return ( + example["product_category"] == "book" + or example["product_category"] == "digital_ebook_purchase" + ) +``` + +Now when we apply this function to `english_dataset` and `spanish_dataset`, the result will contain just those rows involving the book categories. Before applying the filter, let's switch the format of `english_dataset` from `"pandas"` back to `"arrow"`: + +```python +english_dataset.reset_format() +``` + +We can then apply the filter function, and as a sanity check let's inspect a sample of reviews to see if they are indeed about books: + +```python +spanish_books = spanish_dataset.filter(filter_books) +english_books = english_dataset.filter(filter_books) +show_samples(english_books) +``` + +```python out +'>> Title: I\'m dissapointed.' +'>> Review: I guess I had higher expectations for this book from the reviews. I really thought I\'d at least like it. The plot idea was great. I loved Ash but, it just didnt go anywhere. Most of the book was about their radio show and talking to callers. I wanted the author to dig deeper so we could really get to know the characters. All we know about Grace is that she is attractive looking, Latino and is kind of a brat. I\'m dissapointed.' + +'>> Title: Good art, good price, poor design' +'>> Review: I had gotten the DC Vintage calendar the past two years, but it was on backorder forever this year and I saw they had shrunk the dimensions for no good reason. This one has good art choices but the design has the fold going through the picture, so it\'s less aesthetically pleasing, especially if you want to keep a picture to hang. For the price, a good calendar' + +'>> Title: Helpful' +'>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.' +``` + +Okay, we can see that the reviews are not strictly about books and might refer to things like calendars and electronic applications such as OneNote. Nevertheless, the domain seems about right to train a summarization model on. Before we look at various models that are suitable for this task, we have one last bit of data preparation to do: combining the English and Spanish reviews as a single `DatasetDict` object. 🤗 Datasets provides a handy `concatenate_datasets()` function that (as the name suggests) will stack two `Dataset` objects on top of each other. So, to create our bilingual dataset, we'll loop over each split, concatenate the datasets for that split, and shuffle the result to ensure our model doesn't overfit to a single language: + +```python +from datasets import concatenate_datasets, DatasetDict + +books_dataset = DatasetDict() + +for split in english_books.keys(): + books_dataset[split] = concatenate_datasets( + [english_books[split], spanish_books[split]] + ) + books_dataset[split] = books_dataset[split].shuffle(seed=42) + +# Peek at a few examples +show_samples(books_dataset) +``` + +```python out +'>> Title: Easy to follow!!!!' +'>> Review: I loved The dash diet weight loss Solution. Never hungry. I would recommend this diet. Also the menus are well rounded. Try it. Has lots of the information need thanks.' + +'>> Title: PARCIALMENTE DAÑADO' +'>> Review: Me llegó el día que tocaba, junto a otros libros que pedí, pero la caja llegó en mal estado lo cual dañó las esquinas de los libros porque venían sin protección (forro).' + +'>> Title: no lo he podido descargar' +'>> Review: igual que el anterior' +``` + +This certainly looks like a mix of English and Spanish reviews! Now that we have a training corpus, one final thing to check is the distribution of words in the reviews and their titles. This is especially important for summarization tasks, where short reference summaries in the data can bias the model to only output one or two words in the generated summaries. The plots below show the word distributions, and we can see that the titles are heavily skewed toward just 1-2 words: + +
+Word count distributions for the review titles and texts. + +
+ +To deal with this, we'll filter out the examples with very short titles so that our model can produce more interesting summaries. Since we're dealing with English and Spanish texts, we can use a rough heuristic to split the titles on whitespace and then use our trusty `Dataset.filter()` method as follows: + +```python +books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) +``` + +Now that we've prepared our corpus, let's take a look at a few possible Transformer models that one might fine-tune on it! + +## Models for text summarization[[models-for-text-summarization]] + +If you think about it, text summarization is a similar sort of task to machine translation: we have a body of text like a review that we'd like to "translate" into a shorter version that captures the salient features of the input. Accordingly, most Transformer models for summarization adopt the encoder-decoder architecture that we first encountered in [Chapter 1](/course/chapter1), although there are some exceptions like the GPT family of models which can also be used for summarization in few-shot settings. The following table lists some popular pretrained models that can be fine-tuned for summarization. + +| Transformer model | Description | Multilingual? | +| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | +| [GPT-2](https://huggingface.co/gpt2-xl) | Although trained as an auto-regressive language model, you can make GPT-2 generate summaries by appending "TL;DR" at the end of the input text. | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | Uses a pretraining objective to predict masked sentences in multi-sentence texts. This pretraining objective is closer to summarization than vanilla language modeling and scores highly on popular benchmarks. | ❌ | +| [T5](https://huggingface.co/t5-base) | A universal Transformer architecture that formulates all tasks in a text-to-text framework; e.g., the input format for the model to summarize a document is `summarize: ARTICLE`. | ❌ | +| [mT5](https://huggingface.co/google/mt5-base) | A multilingual version of T5, pretrained on the multilingual Common Crawl corpus (mC4), covering 101 languages. | ✅ | +| [BART](https://huggingface.co/facebook/bart-base) | A novel Transformer architecture with both an encoder and a decoder stack trained to reconstruct corrupted input that combines the pretraining schemes of BERT and GPT-2. | ❌ | +| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | A multilingual version of BART, pretrained on 50 languages. | ✅ | + +As you can see from this table, the majority of Transformer models for summarization (and indeed most NLP tasks) are monolingual. This is great if your task is in a "high-resource" language like English or German, but less so for the thousands of other languages in use across the world. Fortunately, there is a class of multilingual Transformer models, like mT5 and mBART, that come to the rescue. These models are pretrained using language modeling, but with a twist: instead of training on a corpus of one language, they are trained jointly on texts in over 50 languages at once! + +We'll focus on mT5, an interesting architecture based on T5 that was pretrained in a text-to-text framework. In T5, every NLP task is formulated in terms of a prompt prefix like `summarize:` which conditions the model to adapt the generated text to the prompt. As shown in the figure below, this makes T5 extremely versatile, as you can solve many tasks with a single model! + +
+Different tasks performed by the T5 architecture. + +
+ +mT5 doesn't use prefixes, but shares much of the versatility of T5 and has the advantage of being multilingual. Now that we've picked a model, let's take a look at preparing our data for training. + + + + +✏️ **Try it out!** Once you've worked through this section, see how well mT5 compares to mBART by fine-tuning the latter with the same techniques. For bonus points, you can also try fine-tuning T5 on just the English reviews. Since T5 has a special prefix prompt, you'll need to prepend `summarize:` to the input examples in the preprocessing steps below. + + + +## Preprocessing the data[[preprocessing-the-data]] + + + +Our next task is to tokenize and encode our reviews and their titles. As usual, we begin by loading the tokenizer associated with the pretrained model checkpoint. We'll use `mt5-small` as our checkpoint so we can fine-tune the model in a reasonable amount of time: + +```python +from transformers import AutoTokenizer + +model_checkpoint = "google/mt5-small" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + + + +💡 In the early stages of your NLP projects, a good practice is to train a class of "small" models on a small sample of data. This allows you to debug and iterate faster toward an end-to-end workflow. Once you are confident in the results, you can always scale up the model by simply changing the model checkpoint! + + + +Let's test out the mT5 tokenizer on a small example: + +```python +inputs = tokenizer("I loved reading the Hunger Games!") +inputs +``` + +```python out +{'input_ids': [336, 259, 28387, 11807, 287, 62893, 295, 12507, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} +``` + +Here we can see the familiar `input_ids` and `attention_mask` that we encountered in our first fine-tuning experiments back in [Chapter 3](/course/chapter3). Let's decode these input IDs with the tokenizer's `convert_ids_to_tokens()` function to see what kind of tokenizer we're dealing with: + +```python +tokenizer.convert_ids_to_tokens(inputs.input_ids) +``` + +```python out +['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] +``` + +The special Unicode character `▁` and end-of-sequence token `` indicate that we're dealing with the SentencePiece tokenizer, which is based on the Unigram segmentation algorithm discussed in [Chapter 6](/course/chapter6). Unigram is especially useful for multilingual corpora since it allows SentencePiece to be agnostic about accents, punctuation, and the fact that many languages, like Japanese, do not have whitespace characters. + +To tokenize our corpus, we have to deal with a subtlety associated with summarization: because our labels are also text, it is possible that they exceed the model's maximum context size. This means we need to apply truncation to both the reviews and their titles to ensure we don't pass excessively long inputs to our model. The tokenizers in 🤗 Transformers provide a nifty `text_target` argument that allows you to tokenize the labels in parallel to the inputs. Here is an example of how the inputs and targets are processed for mT5: + +```python +max_input_length = 512 +max_target_length = 30 + + +def preprocess_function(examples): + model_inputs = tokenizer( + examples["review_body"], + max_length=max_input_length, + truncation=True, + ) + labels = tokenizer( + examples["review_title"], max_length=max_target_length, truncation=True + ) + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +Let's walk through this code to understand what's happening. The first thing we've done is define values for `max_input_length` and `max_target_length`, which set the upper limits for how long our reviews and titles can be. Since the review body is typically much larger than the title, we've scaled these values accordingly. + +With `preprocess_function()`, it is then a simple matter to tokenize the whole corpus using the handy `Dataset.map()` function we've used extensively throughout this course: + +```python +tokenized_datasets = books_dataset.map(preprocess_function, batched=True) +``` + +Now that the corpus has been preprocessed, let's take a look at some metrics that are commonly used for summarization. As we'll see, there is no silver bullet when it comes to measuring the quality of machine-generated text. + + + +💡 You may have noticed that we used `batched=True` in our `Dataset.map()` function above. This encodes the examples in batches of 1,000 (the default) and allows you to make use of the multithreading capabilities of the fast tokenizers in 🤗 Transformers. Where possible, try using `batched=True` to get the most out of your preprocessing! + + + + +## Metrics for text summarization[[metrics-for-text-summarization]] + + + +In comparison to most of the other tasks we've covered in this course, measuring the performance of text generation tasks like summarization or translation is not as straightforward. For example, given a review like "I loved reading the Hunger Games", there are multiple valid summaries, like "I loved the Hunger Games" or "Hunger Games is a great read". Clearly, applying some sort of exact match between the generated summary and the label is not a good solution -- even humans would fare poorly under such a metric, because we all have our own writing style. + +For summarization, one of the most commonly used metrics is the [ROUGE score](https://en.wikipedia.org/wiki/ROUGE_(metric)) (short for Recall-Oriented Understudy for Gisting Evaluation). The basic idea behind this metric is to compare a generated summary against a set of reference summaries that are typically created by humans. To make this more precise, suppose we want to compare the following two summaries: + +```python +generated_summary = "I absolutely loved reading the Hunger Games" +reference_summary = "I loved reading the Hunger Games" +``` + +One way to compare them could be to count the number of overlapping words, which in this case would be 6. However, this is a bit crude, so instead ROUGE is based on computing the _precision_ and _recall_ scores for the overlap. + + + +🙋 Don't worry if this is the first time you've heard of precision and recall -- we'll go through some explicit examples together to make it all clear. These metrics are usually encountered in classification tasks, so if you want to understand how precision and recall are defined in that context, we recommend checking out the `scikit-learn` [guides](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). + + + +For ROUGE, recall measures how much of the reference summary is captured by the generated one. If we are just comparing words, recall can be calculated according to the following formula: + +$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ + +For our simple example above, this formula gives a perfect recall of 6/6 = 1; i.e., all the words in the reference summary have been produced by the model. This may sound great, but imagine if our generated summary had been "I really really loved reading the Hunger Games all night". This would also have perfect recall, but is arguably a worse summary since it is verbose. To deal with these scenarios we also compute the precision, which in the ROUGE context measures how much of the generated summary was relevant: + +$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ + +Applying this to our verbose summary gives a precision of 6/10 = 0.6, which is considerably worse than the precision of 6/7 = 0.86 obtained by our shorter one. In practice, both precision and recall are usually computed, and then the F1-score (the harmonic mean of precision and recall) is reported. We can do this easily in 🤗 Datasets by first installing the `rouge_score` package: + +```py +!pip install rouge_score +``` + +and then loading the ROUGE metric as follows: + +```python +import evaluate + +rouge_score = evaluate.load("rouge") +``` + +Then we can use the `rouge_score.compute()` function to calculate all the metrics at once: + +```python +scores = rouge_score.compute( + predictions=[generated_summary], references=[reference_summary] +) +scores +``` + +```python out +{'rouge1': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)), + 'rouge2': AggregateScore(low=Score(precision=0.67, recall=0.8, fmeasure=0.73), mid=Score(precision=0.67, recall=0.8, fmeasure=0.73), high=Score(precision=0.67, recall=0.8, fmeasure=0.73)), + 'rougeL': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)), + 'rougeLsum': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92))} +``` + +Whoa, there's a lot of information in that output -- what does it all mean? First, 🤗 Datasets actually computes confidence intervals for precision, recall, and F1-score; these are the `low`, `mid`, and `high` attributes you can see here. Moreover, 🤗 Datasets computes a variety of ROUGE scores which are based on different types of text granularity when comparing the generated and reference summaries. The `rouge1` variant is the overlap of unigrams -- this is just a fancy way of saying the overlap of words and is exactly the metric we've discussed above. To verify this, let's pull out the `mid` value of our scores: + +```python +scores["rouge1"].mid +``` + +```python out +Score(precision=0.86, recall=1.0, fmeasure=0.92) +``` + +Great, the precision and recall numbers match up! Now what about those other ROUGE scores? `rouge2` measures the overlap between bigrams (think the overlap of pairs of words), while `rougeL` and `rougeLsum` measure the longest matching sequences of words by looking for the longest common substrings in the generated and reference summaries. The "sum" in `rougeLsum` refers to the fact that this metric is computed over a whole summary, while `rougeL` is computed as the average over individual sentences. + + + +✏️ **Try it out!** Create your own example of a generated and reference summary and see if the resulting ROUGE scores agree with a manual calculation based on the formulas for precision and recall. For bonus points, split the text into bigrams and compare the precision and recall for the `rouge2` metric. + + + +We'll use these ROUGE scores to track the performance of our model, but before doing that let's do something every good NLP practitioner should do: create a strong, yet simple baseline! + +### Creating a strong baseline[[creating-a-strong-baseline]] + +A common baseline for text summarization is to simply take the first three sentences of an article, often called the _lead-3_ baseline. We could use full stops to track the sentence boundaries, but this will fail on acronyms like "U.S." or "U.N." -- so instead we'll use the `nltk` library, which includes a better algorithm to handle these cases. You can install the package using `pip` as follows: + +```python +!pip install nltk +``` + +and then download the punctuation rules: + +```python +import nltk + +nltk.download("punkt") +``` + +Next, we import the sentence tokenizer from `nltk` and create a simple function to extract the first three sentences in a review. The convention in text summarization is to separate each summary with a newline, so let's also include this and test it on a training example: + +```python +from nltk.tokenize import sent_tokenize + + +def three_sentence_summary(text): + return "\n".join(sent_tokenize(text)[:3]) + + +print(three_sentence_summary(books_dataset["train"][1]["review_body"])) +``` + +```python out +'I grew up reading Koontz, and years ago, I stopped,convinced i had "outgrown" him.' +'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.' +'She found Strangers.' +``` + +This seems to work, so let's now implement a function that extracts these "summaries" from a dataset and computes the ROUGE scores for the baseline: + +```python +def evaluate_baseline(dataset, metric): + summaries = [three_sentence_summary(text) for text in dataset["review_body"]] + return metric.compute(predictions=summaries, references=dataset["review_title"]) +``` + +We can then use this function to compute the ROUGE scores over the validation set and prettify them a bit using Pandas: + +```python +import pandas as pd + +score = evaluate_baseline(books_dataset["validation"], rouge_score) +rouge_names = ["rouge1", "rouge2", "rougeL", "rougeLsum"] +rouge_dict = dict((rn, round(score[rn].mid.fmeasure * 100, 2)) for rn in rouge_names) +rouge_dict +``` + +```python out +{'rouge1': 16.74, 'rouge2': 8.83, 'rougeL': 15.6, 'rougeLsum': 15.96} +``` + +We can see that the `rouge2` score is significantly lower than the rest; this likely reflects the fact that review titles are typically concise and so the lead-3 baseline is too verbose. Now that we have a good baseline to work from, let's turn our attention toward fine-tuning mT5! + +{#if fw === 'pt'} + +## Fine-tuning mT5 with the `Trainer` API[[fine-tuning-mt5-with-the-trainer-api]] + +Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `AutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: + +```python +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## Fine-tuning mT5 with Keras[[fine-tuning-mt5-with-keras]] + +Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `TFAutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: + +```python +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{/if} + + + +💡 If you're wondering why you don't see any warnings about fine-tuning the model on a downstream task, that's because for sequence-to-sequence tasks we keep all the weights of the network. Compare this to our text classification model in [Chapter 3](/course/chapter3), where the head of the pretrained model was replaced with a randomly initialized network. + + + +The next thing we need to do is log in to the Hugging Face Hub. If you're running this code in a notebook, you can do so with the following utility function: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +which will display a widget where you can enter your credentials. Alternatively, you can run this command in your terminal and log in there: + +``` +huggingface-cli login +``` + +{#if fw === 'pt'} + +We'll need to generate summaries in order to compute ROUGE scores during training. Fortunately, 🤗 Transformers provides dedicated `Seq2SeqTrainingArguments` and `Seq2SeqTrainer` classes that can do this for us automatically! To see how this works, let's first define the hyperparameters and other arguments for our experiments: + +```python +from transformers import Seq2SeqTrainingArguments + +batch_size = 8 +num_train_epochs = 8 +# Show the training loss with every epoch +logging_steps = len(tokenized_datasets["train"]) // batch_size +model_name = model_checkpoint.split("/")[-1] + +args = Seq2SeqTrainingArguments( + output_dir=f"{model_name}-finetuned-amazon-en-es", + evaluation_strategy="epoch", + learning_rate=5.6e-5, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + weight_decay=0.01, + save_total_limit=3, + num_train_epochs=num_train_epochs, + predict_with_generate=True, + logging_steps=logging_steps, + push_to_hub=True, +) +``` + +Here, the `predict_with_generate` argument has been set to indicate that we should generate summaries during evaluation so that we can compute ROUGE scores for each epoch. As discussed in [Chapter 1](/course/chapter1), the decoder performs inference by predicting tokens one by one, and this is implemented by the model's `generate()` method. Setting `predict_with_generate=True` tells the `Seq2SeqTrainer` to use that method for evaluation. We've also adjusted some of the default hyperparameters, like the learning rate, number of epochs, and weight decay, and we've set the `save_total_limit` option to only save up to 3 checkpoints during training -- this is because even the "small" version of mT5 uses around a GB of hard drive space, and we can save a bit of room by limiting the number of copies we save. + +The `push_to_hub=True` argument will allow us to push the model to the Hub after training; you'll find the repository under your user profile in the location defined by `output_dir`. Note that you can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` to `Seq2SeqTrainingArguments`. + +The next thing we need to do is provide the trainer with a `compute_metrics()` function so that we can evaluate our model during training. For summarization this is a bit more involved than simply calling `rouge_score.compute()` on the model's predictions, since we need to _decode_ the outputs and labels into text before we can compute the ROUGE scores. The following function does exactly that, and also makes use of the `sent_tokenize()` function from `nltk` to separate the summary sentences with newlines: + +```python +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + # Decode generated summaries into text + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + # Decode reference summaries into text + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + # ROUGE expects a newline after each sentence + decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] + decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] + # Compute ROUGE scores + result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True + ) + # Extract the median scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + return {k: round(v, 4) for k, v in result.items()} +``` + +{/if} + +Next, we need to define a data collator for our sequence-to-sequence task. Since mT5 is an encoder-decoder Transformer model, one subtlety with preparing our batches is that during decoding we need to shift the labels to the right by one. This is required to ensure that the decoder only sees the previous ground truth labels and not the current or future ones, which would be easy for the model to memorize. This is similar to how masked self-attention is applied to the inputs in a task like [causal language modeling](/course/chapter7/6). + +Luckily, 🤗 Transformers provides a `DataCollatorForSeq2Seq` collator that will dynamically pad the inputs and the labels for us. To instantiate this collator, we simply need to provide the `tokenizer` and `model`: + +{#if fw === 'pt'} + +```python +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) +``` + +{:else} + +```python +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf") +``` + +{/if} + +Let's see what this collator produces when fed a small batch of examples. First, we need to remove the columns with strings because the collator won't know how to pad these elements: + +```python +tokenized_datasets = tokenized_datasets.remove_columns( + books_dataset["train"].column_names +) +``` + +Since the collator expects a list of `dict`s, where each `dict` represents a single example in the dataset, we also need to wrangle the data into the expected format before passing it to the data collator: + +```python +features = [tokenized_datasets["train"][i] for i in range(2)] +data_collator(features) +``` + +```python out +{'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]), 'input_ids': tensor([[ 1494, 259, 8622, 390, 259, 262, 2316, 3435, 955, + 772, 281, 772, 1617, 263, 305, 14701, 260, 1385, + 3031, 259, 24146, 332, 1037, 259, 43906, 305, 336, + 260, 1, 0, 0, 0, 0, 0, 0], + [ 259, 27531, 13483, 259, 7505, 260, 112240, 15192, 305, + 53198, 276, 259, 74060, 263, 260, 459, 25640, 776, + 2119, 336, 259, 2220, 259, 18896, 288, 4906, 288, + 1037, 3931, 260, 7083, 101476, 1143, 260, 1]]), 'labels': tensor([[ 7483, 259, 2364, 15695, 1, -100], + [ 259, 27531, 13483, 259, 7505, 1]]), 'decoder_input_ids': tensor([[ 0, 7483, 259, 2364, 15695, 1], + [ 0, 259, 27531, 13483, 259, 7505]])} +``` + +The main thing to notice here is that the first example is longer than the second one, so the `input_ids` and `attention_mask` of the second example have been padded on the right with a `[PAD]` token (whose ID is `0`). Similarly, we can see that the `labels` have been padded with `-100`s, to make sure the padding tokens are ignored by the loss function. And finally, we can see a new `decoder_input_ids` which has shifted the labels to the right by inserting a `[PAD]` token in the first entry. + +{#if fw === 'pt'} + +We finally have all the ingredients we need to train with! We now simply need to instantiate the trainer with the standard arguments: + +```python +from transformers import Seq2SeqTrainer + +trainer = Seq2SeqTrainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, + compute_metrics=compute_metrics, +) +``` + +and launch our training run: + +```python +trainer.train() +``` + +During training, you should see the training loss decrease and the ROUGE scores increase with each epoch. Once the training is complete, you can see the final ROUGE scores by running `Trainer.evaluate()`: + +```python +trainer.evaluate() +``` + +```python out +{'eval_loss': 3.028524398803711, + 'eval_rouge1': 16.9728, + 'eval_rouge2': 8.2969, + 'eval_rougeL': 16.8366, + 'eval_rougeLsum': 16.851, + 'eval_gen_len': 10.1597, + 'eval_runtime': 6.1054, + 'eval_samples_per_second': 38.982, + 'eval_steps_per_second': 4.914} +``` + +From the scores we can see that our model has handily outperformed our lead-3 baseline -- nice! The final thing to do is push the model weights to the Hub, as follows: + +``` +trainer.push_to_hub(commit_message="Training complete", tags="summarization") +``` + +```python out +'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' +``` + +This will save the checkpoint and configuration files to `output_dir`, before uploading all the files to the Hub. By specifying the `tags` argument, we also ensure that the widget on the Hub will be one for a summarization pipeline instead of the default text generation one associated with the mT5 architecture (for more information about model tags, see the [🤗 Hub documentation](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). The output from `trainer.push_to_hub()` is a URL to the Git commit hash, so you can easily see the changes that were made to the model repository! + +To wrap up this section, let's take a look at how we can also fine-tune mT5 using the low-level features provided by 🤗 Accelerate. + +{:else} + +We're almost ready to train! We just need to convert our datasets to `tf.data.Dataset`s using the data collator we defined above, and then `compile()` and `fit()` the model. First, the datasets: + +```python +tf_train_dataset = model.prepare_tf_dataset( + tokenized_datasets["train"], + collate_fn=data_collator, + shuffle=True, + batch_size=8, +) +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], + collate_fn=data_collator, + shuffle=False, + batch_size=8, +) +``` + +Now, we define our training hyperparameters and compile: + +```python +from transformers import create_optimizer +import tensorflow as tf + +# The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied +# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset, +# not the original Hugging Face Dataset, so its len() is already num_samples // batch_size. +num_train_epochs = 8 +num_train_steps = len(tf_train_dataset) * num_train_epochs +model_name = model_checkpoint.split("/")[-1] + +optimizer, schedule = create_optimizer( + init_lr=5.6e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) + +model.compile(optimizer=optimizer) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +And finally, we fit the model. We use a `PushToHubCallback` to save the model to the Hub after each epoch, which will allow us to use it for inference later: + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-amazon-en-es", tokenizer=tokenizer +) + +model.fit( + tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback], epochs=8 +) +``` + +We got some loss values during training, but really we'd like to see the ROUGE metrics we computed earlier. To get those metrics, we'll need to generate outputs from the model and convert them to strings. Let's build some lists of labels and predictions for the ROUGE metric to compare (note that if you get import errors for this section, you may need to`!pip install tqdm`). We're also going to use a trick that dramatically increases performance - compiling our generation code with [XLA](https://www.tensorflow.org/xla), TensorFlow's accelerated linear algebra compiler. XLA applies various optimizations to the model's computation graph, and results in significant improvements to speed and memory usage. As described in the Hugging Face [blog](https://huggingface.co/blog/tf-xla-generate), XLA works best when our input shapes don't vary too much. To handle this, we'll pad our inputs to multiples of 128, and make a new dataset with the padding collator, and then we'll apply the `@tf.function(jit_compile=True)` decorator to our generation function, which marks the whole function for compilation with XLA. + +```python +from tqdm import tqdm +import numpy as np + +generation_data_collator = DataCollatorForSeq2Seq( + tokenizer, model=model, return_tensors="tf", pad_to_multiple_of=320 +) + +tf_generate_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], + collate_fn=generation_data_collator, + shuffle=False, + batch_size=8, + drop_remainder=True, +) + + +@tf.function(jit_compile=True) +def generate_with_xla(batch): + return model.generate( + input_ids=batch["input_ids"], + attention_mask=batch["attention_mask"], + max_new_tokens=32, + ) + + +all_preds = [] +all_labels = [] +for batch, labels in tqdm(tf_generate_dataset): + predictions = generate_with_xla(batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = labels.numpy() + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] + decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] + all_preds.extend(decoded_preds) + all_labels.extend(decoded_labels) +``` + +Once we have our lists of label and prediction strings, computing the ROUGE score is easy: + +```python +result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True +) +result = {key: value.mid.fmeasure * 100 for key, value in result.items()} +{k: round(v, 4) for k, v in result.items()} +``` + +``` +{'rouge1': 31.4815, 'rouge2': 25.4386, 'rougeL': 31.4815, 'rougeLsum': 31.4815} +``` + + +{/if} + +{#if fw === 'pt'} + +## Fine-tuning mT5 with 🤗 Accelerate[[fine-tuning-mt5-with-accelerate]] + +Fine-tuning our model with 🤗 Accelerate is very similar to the text classification example we encountered in [Chapter 3](/course/chapter3). The main differences will be the need to explicitly generate our summaries during training and define how we compute the ROUGE scores (recall that the `Seq2SeqTrainer` took care of the generation for us). Let's take a look how we can implement these two requirements within 🤗 Accelerate! + +### Preparing everything for training[[preparing-everything-for-training]] + +The first thing we need to do is create a `DataLoader` for each of our splits. Since the PyTorch dataloaders expect batches of tensors, we need to set the format to `"torch"` in our datasets: + +```python +tokenized_datasets.set_format("torch") +``` + +Now that we've got datasets consisting of just tensors, the next thing to do is instantiate the `DataCollatorForSeq2Seq` again. For this we need to provide a fresh version of the model, so let's load it again from our cache: + +```python +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +We can then instantiate the data collator and use this to define our dataloaders: + +```python +from torch.utils.data import DataLoader + +batch_size = 8 +train_dataloader = DataLoader( + tokenized_datasets["train"], + shuffle=True, + collate_fn=data_collator, + batch_size=batch_size, +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], collate_fn=data_collator, batch_size=batch_size +) +``` + +The next thing to do is define the optimizer we want to use. As in our other examples, we'll use `AdamW`, which works well for most problems: + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Finally, we feed our model, optimizer, and dataloaders to the `accelerator.prepare()` method: + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 If you're training on a TPU, you'll need to move all the code above into a dedicated training function. See [Chapter 3](/course/chapter3) for more details. + + + +Now that we've prepared our objects, there are three remaining things to do: + +* Define the learning rate schedule. +* Implement a function to post-process the summaries for evaluation. +* Create a repository on the Hub that we can push our model to. + +For the learning rate schedule, we'll use the standard linear one from previous sections: + +```python +from transformers import get_scheduler + +num_train_epochs = 10 +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, +) +``` + +For post-processing, we need a function that splits the generated summaries into sentences that are separated by newlines. This is the format the ROUGE metric expects, and we can achieve this with the following snippet of code: + +```python +def postprocess_text(preds, labels): + preds = [pred.strip() for pred in preds] + labels = [label.strip() for label in labels] + + # ROUGE expects a newline after each sentence + preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] + labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] + + return preds, labels +``` + +This should look familiar to you if you recall how we defined the `compute_metrics()` function of the `Seq2SeqTrainer`. + +Finally, we need to create a model repository on the Hugging Face Hub. For this, we can use the appropriately titled 🤗 Hub library. We just need to define a name for our repository, and the library has a utility function to combine the repository ID with the user profile: + +```python +from huggingface_hub import get_full_repo_name + +model_name = "test-bert-finetuned-squad-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'lewtun/mt5-finetuned-amazon-en-es-accelerate' +``` + +Now we can use this repository name to clone a local version to our results directory that will store the training artifacts: + +```python +from huggingface_hub import Repository + +output_dir = "results-mt5-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +This will allow us to push the artifacts back to the Hub by calling the `repo.push_to_hub()` method during training! Let's now wrap up our analysis by writing out the training loop. + +### Training loop[[training-loop]] + +The training loop for summarization is quite similar to the other 🤗 Accelerate examples that we've encountered and is roughly split into four main steps: + +1. Train the model by iterating over all the examples in `train_dataloader` for each epoch. +2. Generate model summaries at the end of each epoch, by first generating the tokens and then decoding them (and the reference summaries) into text. +3. Compute the ROUGE scores using the same techniques we saw earlier. +4. Save the checkpoints and push everything to the Hub. Here we rely on the nifty `blocking=False` argument of the `Repository` object so that we can push the checkpoints per epoch _asynchronously_. This allows us to continue training without having to wait for the somewhat slow upload associated with a GB-sized model! + +These steps can be seen in the following block of code: + +```python +from tqdm.auto import tqdm +import torch +import numpy as np + +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() + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + ) + + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = batch["labels"] + + # If we did not pad to max length, we need to pad the labels too + labels = accelerator.pad_across_processes( + batch["labels"], dim=1, pad_index=tokenizer.pad_token_id + ) + + generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() + labels = accelerator.gather(labels).cpu().numpy() + + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + if isinstance(generated_tokens, tuple): + generated_tokens = generated_tokens[0] + decoded_preds = tokenizer.batch_decode( + generated_tokens, skip_special_tokens=True + ) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + decoded_preds, decoded_labels = postprocess_text( + decoded_preds, decoded_labels + ) + + rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels) + + # Compute metrics + result = rouge_score.compute() + # Extract the median ROUGE scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + result = {k: round(v, 4) for k, v in result.items()} + print(f"Epoch {epoch}:", result) + + # 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 + ) +``` + +```python out +Epoch 0: {'rouge1': 5.6351, 'rouge2': 1.1625, 'rougeL': 5.4866, 'rougeLsum': 5.5005} +Epoch 1: {'rouge1': 9.8646, 'rouge2': 3.4106, 'rougeL': 9.9439, 'rougeLsum': 9.9306} +Epoch 2: {'rouge1': 11.0872, 'rouge2': 3.3273, 'rougeL': 11.0508, 'rougeLsum': 10.9468} +Epoch 3: {'rouge1': 11.8587, 'rouge2': 4.8167, 'rougeL': 11.7986, 'rougeLsum': 11.7518} +Epoch 4: {'rouge1': 12.9842, 'rouge2': 5.5887, 'rougeL': 12.7546, 'rougeLsum': 12.7029} +Epoch 5: {'rouge1': 13.4628, 'rouge2': 6.4598, 'rougeL': 13.312, 'rougeLsum': 13.2913} +Epoch 6: {'rouge1': 12.9131, 'rouge2': 5.8914, 'rougeL': 12.6896, 'rougeLsum': 12.5701} +Epoch 7: {'rouge1': 13.3079, 'rouge2': 6.2994, 'rougeL': 13.1536, 'rougeLsum': 13.1194} +Epoch 8: {'rouge1': 13.96, 'rouge2': 6.5998, 'rougeL': 13.9123, 'rougeLsum': 13.7744} +Epoch 9: {'rouge1': 14.1192, 'rouge2': 7.0059, 'rougeL': 14.1172, 'rougeLsum': 13.9509} +``` + +And that's it! Once you run this, you'll have a model and results that are pretty similar to the ones we obtained with the `Trainer`. + +{/if} + +## Using your fine-tuned model[[using-your-fine-tuned-model]] + +Once you've pushed the model to the Hub, you can play with it either via the inference widget or with a `pipeline` object, as follows: + +```python +from transformers import pipeline + +hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" +summarizer = pipeline("summarization", model=hub_model_id) +``` + +We can feed some examples from the test set (which the model has not seen) to our pipeline to get a feel for the quality of the summaries. First let's implement a simple function to show the review, title, and generated summary together: + +```python +def print_summary(idx): + review = books_dataset["test"][idx]["review_body"] + title = books_dataset["test"][idx]["review_title"] + summary = summarizer(books_dataset["test"][idx]["review_body"])[0]["summary_text"] + print(f"'>>> Review: {review}'") + print(f"\n'>>> Title: {title}'") + print(f"\n'>>> Summary: {summary}'") +``` + +Let's take a look at one of the English examples we get: + +```python +print_summary(100) +``` + +```python out +'>>> Review: Nothing special at all about this product... the book is too small and stiff and hard to write in. The huge sticker on the back doesn’t come off and looks super tacky. I would not purchase this again. I could have just bought a journal from the dollar store and it would be basically the same thing. It’s also really expensive for what it is.' + +'>>> Title: Not impressed at all... buy something else' + +'>>> Summary: Nothing special at all about this product' +``` + +This is not too bad! We can see that our model has actually been able to perform _abstractive_ summarization by augmenting parts of the review with new words. And perhaps the coolest aspect of our model is that it is bilingual, so we can also generate summaries of Spanish reviews: + +```python +print_summary(0) +``` + +```python out +'>>> Review: Es una trilogia que se hace muy facil de leer. Me ha gustado, no me esperaba el final para nada' + +'>>> Title: Buena literatura para adolescentes' + +'>>> Summary: Muy facil de leer' +``` + +The summary translates into "Very easy to read" in English, which we can see in this case was extracted directly from the review. Nevertheless, this shows the versatility of the mT5 model and has given you a taste of what it's like to deal with a multilingual corpus! + +Next, we'll turn our attention to a slightly more complex task: training a language model from scratch. diff --git a/chapters/ro/chapter7/6.mdx b/chapters/ro/chapter7/6.mdx new file mode 100644 index 000000000..44551f15d --- /dev/null +++ b/chapters/ro/chapter7/6.mdx @@ -0,0 +1,914 @@ + + +# Training a causal language model from scratch[[training-a-causal-language-model-from-scratch]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Up until now, we've mostly been using pretrained models and fine-tuning them for new use cases by reusing the weights from pretraining. As we saw in [Chapter 1](/course/chapter1), this is commonly referred to as _transfer learning_, and it's a very successful strategy for applying Transformer models to most real-world use cases where labeled data is sparse. In this chapter, we'll take a different approach and train a completely new model from scratch. This is a good approach to take if you have a lot of data and it is very different from the pretraining data used for the available models. However, it also requires considerably more compute resources to pretrain a language model than just to fine-tune an existing one. Examples where it can make sense to train a new model include for datasets consisting of musical notes, molecular sequences such as DNA, or programming languages. The latter have recently gained traction thanks to tools such as TabNine and GitHub's Copilot, powered by OpenAI's Codex model, that can generate long sequences of code. This task of text generation is best addressed with auto-regressive or causal language models such as GPT-2. + +In this section we will build a scaled-down version of a code generation model: we'll focus on one-line completions instead of full functions or classes, using a subset of Python code. When working with data in Python you are in frequent contact with the Python data science stack, consisting of the `matplotlib`, `seaborn`, `pandas`, and `scikit-learn` libraries. When using those frameworks it's common to need to look up specific commands, so it would be nice if we could use a model to complete these calls for us. + + + +In [Chapter 6](/course/chapter6) we created an efficient tokenizer to process Python source code, but what we still need is a large-scale dataset to pretrain a model on. Here, we'll apply our tokenizer to a corpus of Python code derived from GitHub repositories. We will then use the `Trainer` API and 🤗 Accelerate to train the model. Let's get to it! + + + +This is actually showcasing the model that was trained and uploaded to the Hub using the code shown in this section. You can find it [here](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Note that since there is some randomization happening in the text generation, you will probably get a slightly different result. + +## Gathering the data[[gathering-the-data]] + +Python code is abundantly available from code repositories such as GitHub, which we can use to create a dataset by scraping for every Python repository. This was the approach taken in the [Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098136789/) to pretrain a large GPT-2 model. Using a GitHub dump of about 180 GB containing roughly 20 million Python files called `codeparrot`, the authors built a dataset that they then shared on the [Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot). + +However, training on the full corpus is time- and compute-consuming, and we only need the subset of the dataset concerned with the Python data science stack. So, let's start by filtering the `codeparrot` dataset for all files that include any of the libraries in this stack. Because of the dataset's size, we want to avoid downloading it; instead, we'll use the streaming feature to filter it on the fly. To help us filter the code samples using the libraries we mentioned earlier, we'll use the following function: + +```py +def any_keyword_in_string(string, keywords): + for keyword in keywords: + if keyword in string: + return True + return False +``` + +Let's test it on two examples: + +```py +filters = ["pandas", "sklearn", "matplotlib", "seaborn"] +example_1 = "import numpy as np" +example_2 = "import pandas as pd" + +print( + any_keyword_in_string(example_1, filters), any_keyword_in_string(example_2, filters) +) +``` + +```python out +False True +``` + +We can use this to create a function that will stream the dataset and filter the elements we want: + +```py +from collections import defaultdict +from tqdm import tqdm +from datasets import Dataset + + +def filter_streaming_dataset(dataset, filters): + filtered_dict = defaultdict(list) + total = 0 + for sample in tqdm(iter(dataset)): + total += 1 + if any_keyword_in_string(sample["content"], filters): + for k, v in sample.items(): + filtered_dict[k].append(v) + print(f"{len(filtered_dict['content'])/total:.2%} of data after filtering.") + return Dataset.from_dict(filtered_dict) +``` + +Then we can simply apply this function to the streaming dataset: + +```py +# This cell will take a very long time to execute, so you should skip it and go to +# the next one! +from datasets import load_dataset + +split = "train" # "valid" +filters = ["pandas", "sklearn", "matplotlib", "seaborn"] + +data = load_dataset(f"transformersbook/codeparrot-{split}", split=split, streaming=True) +filtered_data = filter_streaming_dataset(data, filters) +``` + +```python out +3.26% of data after filtering. +``` + +This leaves us with about 3% of the original dataset, which is still quite sizable -- the resulting dataset is 6 GB and consists of 600,000 Python scripts! + +Filtering the full dataset can take 2-3h depending on your machine and bandwidth. If you don't want to go through this lengthy process yourself, we provide the filtered dataset on the Hub for you to download: + +```py +from datasets import load_dataset, DatasetDict + +ds_train = load_dataset("huggingface-course/codeparrot-ds-train", split="train") +ds_valid = load_dataset("huggingface-course/codeparrot-ds-valid", split="validation") + +raw_datasets = DatasetDict( + { + "train": ds_train, # .shuffle().select(range(50000)), + "valid": ds_valid, # .shuffle().select(range(500)) + } +) + +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['repo_name', 'path', 'copies', 'size', 'content', 'license'], + num_rows: 606720 + }) + valid: Dataset({ + features: ['repo_name', 'path', 'copies', 'size', 'content', 'license'], + num_rows: 3322 + }) +}) +``` + + + +Pretraining the language model will take a while. We suggest that you first run the training loop on a sample of the data by uncommenting the two partial lines above, and make sure that the training successfully completes and the models are stored. Nothing is more frustrating than a training run failing at the last step because you forgot to create a folder or because there's a typo at the end of the training loop! + + + +Let's look at an example from the dataset. We'll just show the first 200 characters of each field: + +```py +for key in raw_datasets["train"][0]: + print(f"{key.upper()}: {raw_datasets['train'][0][key][:200]}") +``` + +```python out +'REPO_NAME: kmike/scikit-learn' +'PATH: sklearn/utils/__init__.py' +'COPIES: 3' +'SIZE: 10094' +'''CONTENT: """ +The :mod:`sklearn.utils` module includes various utilites. +""" + +from collections import Sequence + +import numpy as np +from scipy.sparse import issparse +import warnings + +from .murmurhash import murm +LICENSE: bsd-3-clause''' +``` + +We can see that the `content` field contains the code that we want our model to train on. Now that we have a dataset, we need to prepare the texts so they're in a format suitable for pretraining. + +## Preparing the dataset[[preparing-the-dataset]] + + + +The first step will be to tokenize the data, so we can use it for training. Since our goal is to mainly autocomplete short function calls, we can keep the context size relatively small. This has the benefit that we can train the model much faster and it requires significantly less memory. If it is important for your application to have more context (for example, if you want the model to write unit tests based on a file with the function definition), make sure you increase that number, but also keep in mind that this comes with a greater GPU memory footprint. For now, let's fix the context size at 128 tokens, as opposed to the 1,024 or 2,048 used in GPT-2 or GPT-3, respectively. + +Most documents contain many more than 128 tokens, so simply truncating the inputs to the maximum length would eliminate a large fraction of our dataset. Instead, we'll use the `return_overflowing_tokens` option to tokenize the whole input and split it into several chunks, as we did in [Chapter 6](/course/chapter6/4). We'll also use the `return_length` option to return the length of each created chunk automatically. Often the last chunk will be smaller than the context size, and we'll get rid of these pieces to avoid padding issues; we don't really need them as we have plenty of data anyway. + +
+Chunking a large texts in several pieces. + +
+ +Let's see exactly how this works by looking at the first two examples: + +```py +from transformers import AutoTokenizer + +context_length = 128 +tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") + +outputs = tokenizer( + raw_datasets["train"][:2]["content"], + truncation=True, + max_length=context_length, + return_overflowing_tokens=True, + return_length=True, +) + +print(f"Input IDs length: {len(outputs['input_ids'])}") +print(f"Input chunk lengths: {(outputs['length'])}") +print(f"Chunk mapping: {outputs['overflow_to_sample_mapping']}") +``` + +```python out +Input IDs length: 34 +Input chunk lengths: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 117, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 41] +Chunk mapping: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] +``` + +We can see that we get 34 segments in total from those two examples. Looking at the chunk lengths, we can see that the chunks at the ends of both documents have less than 128 tokens (117 and 41, respectively). These represent just a small fraction of the total chunks that we have, so we can safely throw them away. With the `overflow_to_sample_mapping` field, we can also reconstruct which chunks belonged to which input samples. + +With this operation we're using a handy feature of the `Dataset.map()` function in 🤗 Datasets, which is that it does not require one-to-one maps; as we saw in [section 3](/course/chapter7/3), we can create batches with more or fewer elements than the input batch. This is useful when doing operations like data augmentation or data filtering that change the number of elements. In our case, when tokenizing each element into chunks of the specified context size, we create many samples from each document. We just need to make sure to delete the existing columns, since they have a conflicting size. If we wanted to keep them, we could repeat them appropriately and return them within the `Dataset.map()` call: + +```py +def tokenize(element): + outputs = tokenizer( + element["content"], + truncation=True, + max_length=context_length, + return_overflowing_tokens=True, + return_length=True, + ) + input_batch = [] + for length, input_ids in zip(outputs["length"], outputs["input_ids"]): + if length == context_length: + input_batch.append(input_ids) + return {"input_ids": input_batch} + + +tokenized_datasets = raw_datasets.map( + tokenize, batched=True, remove_columns=raw_datasets["train"].column_names +) +tokenized_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['input_ids'], + num_rows: 16702061 + }) + valid: Dataset({ + features: ['input_ids'], + num_rows: 93164 + }) +}) +``` + +We now have 16.7 million examples with 128 tokens each, which corresponds to about 2.1 billion tokens in total. For reference, OpenAI's GPT-3 and Codex models are trained on 300 and 100 billion tokens, respectively, where the Codex models are initialized from the GPT-3 checkpoints. Our goal in this section is not to compete with these models, which can generate long, coherent texts, but to create a scaled-down version providing a quick autocomplete function for data scientists. + +Now that we have the dataset ready, let's set up the model! + + + +✏️ **Try it out!** Getting rid of all the chunks that are smaller than the context size wasn't a big issue here because we're using small context windows. As you increase the context size (or if you have a corpus of short documents), the fraction of chunks that are thrown away will also grow. A more efficient way to prepare the data is to join all the tokenized samples in a batch with an `eos_token_id` token in between, and then perform the chunking on the concatenated sequences. As an exercise, modify the `tokenize()` function to make use of that approach. Note that you'll want to set `truncation=False` and remove the other arguments from the tokenizer to get the full sequence of token IDs. + + + + +## Initializing a new model[[initializing-a-new-model]] + +Our first step is to freshly initialize a GPT-2 model. We'll use the same configuration for our model as for the small GPT-2 model, so we load the pretrained configuration, make sure that the tokenizer size matches the model vocabulary size and pass the `bos` and `eos` (beginning and end of sequence) token IDs: + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, GPT2LMHeadModel, AutoConfig + +config = AutoConfig.from_pretrained( + "gpt2", + vocab_size=len(tokenizer), + n_ctx=context_length, + bos_token_id=tokenizer.bos_token_id, + eos_token_id=tokenizer.eos_token_id, +) +``` + +With that configuration, we can load a new model. Note that this is the first time we don't use the `from_pretrained()` function, since we're actually initializing a model ourself: + +```py +model = GPT2LMHeadModel(config) +model_size = sum(t.numel() for t in model.parameters()) +print(f"GPT-2 size: {model_size/1000**2:.1f}M parameters") +``` + +```python out +GPT-2 size: 124.2M parameters +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFGPT2LMHeadModel, AutoConfig + +config = AutoConfig.from_pretrained( + "gpt2", + vocab_size=len(tokenizer), + n_ctx=context_length, + bos_token_id=tokenizer.bos_token_id, + eos_token_id=tokenizer.eos_token_id, +) +``` + +With that configuration, we can load a new model. Note that this is the first time we don't use the `from_pretrained()` function, since we're actually initializing a model ourself: + +```py +model = TFGPT2LMHeadModel(config) +model(model.dummy_inputs) # Builds the model +model.summary() +``` + +```python out +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +transformer (TFGPT2MainLayer multiple 124242432 +================================================================= +Total params: 124,242,432 +Trainable params: 124,242,432 +Non-trainable params: 0 +_________________________________________________________________ +``` + +{/if} + +Our model has 124M parameters that we'll have to tune. Before we can start training, we need to set up a data collator that will take care of creating the batches. We can use the `DataCollatorForLanguageModeling` collator, which is designed specifically for language modeling (as the name subtly suggests). Besides stacking and padding batches, it also takes care of creating the language model labels -- in causal language modeling the inputs serve as labels too (just shifted by one element), and this data collator creates them on the fly during training so we don't need to duplicate the `input_ids`. + +Note that `DataCollatorForLanguageModeling` supports both masked language modeling (MLM) and causal language modeling (CLM). By default it prepares data for MLM, but we can switch to CLM by setting the argument `mlm=False`: + +{#if fw === 'pt'} + +```py +from transformers import DataCollatorForLanguageModeling + +tokenizer.pad_token = tokenizer.eos_token +data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False) +``` + +{:else} + +```py +from transformers import DataCollatorForLanguageModeling + +tokenizer.pad_token = tokenizer.eos_token +data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False, return_tensors="tf") +``` + +{/if} + +Let's have a look at an example: + +```py +out = data_collator([tokenized_datasets["train"][i] for i in range(5)]) +for key in out: + print(f"{key} shape: {out[key].shape}") +``` + +{#if fw === 'pt'} + +```python out +input_ids shape: torch.Size([5, 128]) +attention_mask shape: torch.Size([5, 128]) +labels shape: torch.Size([5, 128]) +``` + +{:else} + +```python out +input_ids shape: (5, 128) +attention_mask shape: (5, 128) +labels shape: (5, 128) +``` + +{/if} + +We can see that the examples have been stacked and all the tensors have the same shape. + +{#if fw === 'tf'} + +Now we can use the `prepare_tf_dataset()` method to convert our datasets to TensorFlow datasets with the data collator we created above: + +```python +tf_train_dataset = model.prepare_tf_dataset( + tokenized_datasets["train"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_datasets["valid"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +{/if} + + + +⚠️ Shifting the inputs and labels to align them happens inside the model, so the data collator just copies the inputs to create the labels. + + + + +Now we have everything in place to actually train our model -- that wasn't so much work after all! Before we start training we should log in to Hugging Face. If you're working in a notebook, you can do so with the following utility function: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +This will display a widget where you can enter your Hugging Face login credentials. + +If you aren't working in a notebook, just type the following line in your terminal: + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +All that's left to do is configure the training arguments and fire up the `Trainer`. We'll use a cosine learning rate schedule with some warmup and an effective batch size of 256 (`per_device_train_batch_size` * `gradient_accumulation_steps`). Gradient accumulation is used when a single batch does not fit into memory, and incrementally builds up the gradient through several forward/backward passes. We'll see this in action when we create the training loop with 🤗 Accelerate. + +```py +from transformers import Trainer, TrainingArguments + +args = TrainingArguments( + output_dir="codeparrot-ds", + per_device_train_batch_size=32, + per_device_eval_batch_size=32, + evaluation_strategy="steps", + eval_steps=5_000, + logging_steps=5_000, + gradient_accumulation_steps=8, + num_train_epochs=1, + weight_decay=0.1, + warmup_steps=1_000, + lr_scheduler_type="cosine", + learning_rate=5e-4, + save_steps=5_000, + fp16=True, + push_to_hub=True, +) + +trainer = Trainer( + model=model, + tokenizer=tokenizer, + args=args, + data_collator=data_collator, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["valid"], +) +``` + +Now we can just start the `Trainer` and wait for training to finish. Depending on whether you run it on the full or a subset of the training set this will take 20 or 2 hours, respectively, so grab a few coffees and a good book to read! + +```py +trainer.train() +``` + +After training completes, we can push the model and tokenizer to the Hub: + +```py +trainer.push_to_hub() +``` + +{:else} + +All that's left to do is configure the training hyperparameters and call `compile()` and `fit()`. We'll use a learning rate schedule with some warmup to improve the stability of training: + +```py +from transformers import create_optimizer +import tensorflow as tf + +num_train_steps = len(tf_train_dataset) +optimizer, schedule = create_optimizer( + init_lr=5e-5, + num_warmup_steps=1_000, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Now we can just call `model.fit()` and wait for training to finish. Depending on whether you run it on the full or a subset of the training set this will take 20 or 2 hours, respectively, so grab a few coffees and a good book to read! After training completes we can push the model and tokenizer to the Hub: + +```py +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback(output_dir="codeparrot-ds", tokenizer=tokenizer) + +model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) +``` + +{/if} + + + +✏️ **Try it out!** It only took us about 30 lines of code in addition to the `TrainingArguments` to get from raw texts to training GPT-2. Try it out with your own dataset and see if you can get good results! + + + + + +{#if fw === 'pt'} + +💡 If you have access to a machine with multiple GPUs, try to run the code there. The `Trainer` automatically manages multiple machines, and this can speed up training tremendously. + +{:else} + +💡 If you have access to a machine with multiple GPUs, you can try using a `MirroredStrategy` context to substantially speed up training. You'll need to create a `tf.distribute.MirroredStrategy` object, and make sure that any `to_tf_dataset()` or `prepare_tf_dataset()` methods as well as model creation and the call to `fit()` are all run in its `scope()` context. You can see documentation on this [here](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). + +{/if} + + + +## Code generation with a pipeline[[code-generation-with-a-pipeline]] + +Now is the moment of truth: let's see how well the trained model actually works! We can see in the logs that the loss went down steadily, but to put the model to the test let's take a look at how well it works on some prompts. To do that we'll wrap the model in a text generation `pipeline`, and we'll put it on the GPU for fast generations if there is one available: + +{#if fw === 'pt'} + +```py +import torch +from transformers import pipeline + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +pipe = pipeline( + "text-generation", model="huggingface-course/codeparrot-ds", device=device +) +``` + +{:else} + +```py +from transformers import pipeline + +course_model = TFGPT2LMHeadModel.from_pretrained("huggingface-course/codeparrot-ds") +course_tokenizer = AutoTokenizer.from_pretrained("huggingface-course/codeparrot-ds") +pipe = pipeline( + "text-generation", model=course_model, tokenizer=course_tokenizer, device=0 +) +``` + +{/if} + +Let's start with the simple task of creating a scatter plot: + +```py +txt = """\ +# create some data +x = np.random.randn(100) +y = np.random.randn(100) + +# create scatter plot with x, y +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# create some data +x = np.random.randn(100) +y = np.random.randn(100) + +# create scatter plot with x, y +plt.scatter(x, y) + +# create scatter +``` + +The result looks correct. Does it also work for a `pandas` operation? Let's see if we can create a `DataFrame` from two arrays: + +```py +txt = """\ +# create some data +x = np.random.randn(100) +y = np.random.randn(100) + +# create dataframe from x and y +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# create some data +x = np.random.randn(100) +y = np.random.randn(100) + +# create dataframe from x and y +df = pd.DataFrame({'x': x, 'y': y}) +df.insert(0,'x', x) +for +``` + +Nice, that's the correct answer -- although it then inserts the column `x` again. Since the number of generated tokens is limited, the following `for` loop is cut off. Let's see if we can do something a bit more complex and have the model help us use the `groupby` operation: + +```py +txt = """\ +# dataframe with profession, income and name +df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) + +# calculate the mean income per profession +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# dataframe with profession, income and name +df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) + +# calculate the mean income per profession +profession = df.groupby(['profession']).mean() + +# compute the +``` + +Not bad; that's the right way to do it. Finally, let's see if we can also use it for `scikit-learn` and set up a Random Forest model: + +```py +txt = """ +# import random forest regressor from scikit-learn +from sklearn.ensemble import RandomForestRegressor + +# fit random forest model with 300 estimators on X, y: +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# import random forest regressor from scikit-learn +from sklearn.ensemble import RandomForestRegressor + +# fit random forest model with 300 estimators on X, y: +rf = RandomForestRegressor(n_estimators=300, random_state=random_state, max_depth=3) +rf.fit(X, y) +rf +``` + +{#if fw === 'tf'} + +Looking at these few examples, it seems that the model has learned some of the syntax of the Python data science stack. Of course, we would need to evaluate the model more thoroughly before deploying it in the real world, but this is still an impressive prototype. + +{:else} + +Looking at these few examples, it seems that the model has learned some of the syntax of the Python data science stack (of course, we would need to evaluate it more thoroughly before deploying the model in the real world). Sometimes it requires more customization of the model training to achieve the necessary performance for a given use case, however. For example, what if we would like to dynamically update the batch size or have a conditional training loop that skips bad examples on the fly? One option would be to subclass the `Trainer` and add the necessary changes, but sometimes it's simpler to write the training loop from scratch. That's where 🤗 Accelerate comes in. + +{/if} + +{#if fw === 'pt'} + +## Training with 🤗 Accelerate[[training-with-accelerate]] + +We've seen how to train a model with the `Trainer`, which can allow for some customization. However, sometimes we want full control over the training loop, or we want to make some exotic changes. In this case 🤗 Accelerate is a great choice, and in this section we'll go through the steps to use it to train our model. To make things more interesting, we'll also add a twist to the training loop. + + + +Since we are mainly interested in sensible autocompletion for the the data science libraries, it makes sense to give more weight to training samples that make more use of these libraries. We can easily identify these examples through the use of keywords such as `plt`, `pd`, `sk`, `fit`, and `predict`, which are the most frequent import names for `matplotlib.pyplot`, `pandas`, and `sklearn` as well as the fit/predict pattern of the latter. If these are each represented as a single token, we can easily check if they occur in the input sequence. Tokens can have a whitespace prefix, so we'll also check for those versions in the tokenizer vocabulary. To verify that it works, we'll add one test token which should be split into multiple tokens: + +```py +keytoken_ids = [] +for keyword in [ + "plt", + "pd", + "sk", + "fit", + "predict", + " plt", + " pd", + " sk", + " fit", + " predict", + "testtest", +]: + ids = tokenizer([keyword]).input_ids[0] + if len(ids) == 1: + keytoken_ids.append(ids[0]) + else: + print(f"Keyword has not single token: {keyword}") +``` + +```python out +'Keyword has not single token: testtest' +``` + +Great, that seems to work nicely! We can now write a custom loss function that takes the input sequence, the logits, and the key tokens we just selected as inputs. First we need to align the logits and inputs: the input sequence shifted by one to the right forms the labels, since the next token is the label for the current token. We can achieve this by starting the labels from the second token of the input sequence, since the model does not make a prediction for the first token anyway. Then we cut off the last logit, as we don't have a label for the token that follows the full input sequence. With that we can compute the loss per sample and count the occurrences of all keywords in each sample. Finally, we calculate the weighted average over all samples using the occurrences as weights. Since we don't want to throw away all the samples that have no keywords, we add 1 to the weights: + +```py +from torch.nn import CrossEntropyLoss +import torch + + +def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): + # Shift so that tokens < n predict n + shift_labels = inputs[..., 1:].contiguous() + shift_logits = logits[..., :-1, :].contiguous() + # Calculate per-token loss + loss_fct = CrossEntropyLoss(reduce=False) + loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) + # Resize and average loss per sample + loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1) + # Calculate and scale weighting + weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum( + axis=[0, 2] + ) + weights = alpha * (1.0 + weights) + # Calculate weighted average + weighted_loss = (loss_per_sample * weights).mean() + return weighted_loss +``` + +Before we can start training with this awesome new loss function, we need to prepare a few things: + +- We need dataloaders to load the data in batches. +- We need to set up weight decay parameters. +- From time to time we want to evaluate, so it makes sense to wrap the evaluation code in a function. + +Let's start with the dataloaders. We only need to set the dataset's format to `"torch"`, and then we can pass it to a PyTorch `DataLoader` with the appropriate batch size: + +```py +from torch.utils.data.dataloader import DataLoader + +tokenized_datasets.set_format("torch") +train_dataloader = DataLoader(tokenized_datasets["train"], batch_size=32, shuffle=True) +eval_dataloader = DataLoader(tokenized_datasets["valid"], batch_size=32) +``` + +Next, we group the parameters so that the optimizer knows which ones will get an additional weight decay. Usually, all bias and LayerNorm weights terms are exempt from this; here's how we can do this: + +```py +weight_decay = 0.1 + + +def get_grouped_params(model, no_decay=["bias", "LayerNorm.weight"]): + params_with_wd, params_without_wd = [], [] + for n, p in model.named_parameters(): + if any(nd in n for nd in no_decay): + params_without_wd.append(p) + else: + params_with_wd.append(p) + return [ + {"params": params_with_wd, "weight_decay": weight_decay}, + {"params": params_without_wd, "weight_decay": 0.0}, + ] +``` + +Since we want to evaluate the model regularly on the validation set during training, let's write a function for that as well. It just runs through the evaluation dataloader and gathers all the losses across processes: + +```py +def evaluate(): + model.eval() + losses = [] + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + outputs = model(batch["input_ids"], labels=batch["input_ids"]) + + losses.append(accelerator.gather(outputs.loss)) + loss = torch.mean(torch.cat(losses)) + try: + perplexity = torch.exp(loss) + except OverflowError: + perplexity = float("inf") + return loss.item(), perplexity.item() +``` + +With the `evaluate()` function we can report loss and [perplexity](/course/chapter7/3) at regular intervals. Next, we redefine our model to make sure we train from scratch again: + +```py +model = GPT2LMHeadModel(config) +``` + +We can then define our optimizer, using the function from before to split the parameters for weight decay: + +```py +from torch.optim import AdamW + +optimizer = AdamW(get_grouped_params(model), lr=5e-4) +``` + +Now let's prepare the model, optimizer, and dataloaders so we can start training: + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) + +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 If you're training on a TPU, you'll need to move all the code starting at the cell above into a dedicated training function. See [Chapter 3](/course/chapter3) for more details. + + + +Now that we have sent our `train_dataloader` to `accelerator.prepare()`, we can use its length to compute the number of training steps. Remember that we should always do this after preparing the dataloader, as that method will change its length. We use a classic linear schedule from the learning rate to 0: + +```py +from transformers import get_scheduler + +num_train_epochs = 1 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + name="linear", + optimizer=optimizer, + num_warmup_steps=1_000, + num_training_steps=num_training_steps, +) +``` + +Lastly, to push our model to the Hub, we will need to create a `Repository` object in a working folder. First log in to the Hugging Face Hub, if you aren't logged in already. We'll determine the repository name from the model ID we want to give our model (feel free to replace the `repo_name` with your own choice; it just needs to contain your username, which is what the function `get_full_repo_name()` does): + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "codeparrot-ds-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/codeparrot-ds-accelerate' +``` + +Then we can clone that repository in a local folder. If it already exists, this local folder should be an existing clone of the repository we are working with: + +```py +output_dir = "codeparrot-ds-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +We can now upload anything we save in `output_dir` by calling the `repo.push_to_hub()` method. This will help us upload the intermediate models at the end of each epoch. + +Before we train, let's run a quick test to see if the evaluation function works properly: + +```py +evaluate() +``` + +```python out +(10.934126853942871, 56057.14453125) +``` + +Those are very high values for loss and perplexity, but that's not surprising as we haven't trained the model yet. With that, we have everything prepared to write the core part of the training script: the training loop. In the training loop we iterate over the dataloader and pass the batches to the model. With the logits, we can then evaluate our custom loss function. We scale the loss by the number of gradient accumulation steps so as not to create larger losses when aggregating more steps. Before we optimize, we also clip the gradients for better convergence. Finally, every few steps we evaluate the model on the evaluation set with our new `evaluate()` function: + +```py +from tqdm.notebook import tqdm + +gradient_accumulation_steps = 8 +eval_steps = 5_000 + +model.train() +completed_steps = 0 +for epoch in range(num_train_epochs): + for step, batch in tqdm( + enumerate(train_dataloader, start=1), total=num_training_steps + ): + logits = model(batch["input_ids"]).logits + loss = keytoken_weighted_loss(batch["input_ids"], logits, keytoken_ids) + if step % 100 == 0: + accelerator.print( + { + "samples": step * samples_per_step, + "steps": completed_steps, + "loss/train": loss.item() * gradient_accumulation_steps, + } + ) + loss = loss / gradient_accumulation_steps + accelerator.backward(loss) + if step % gradient_accumulation_steps == 0: + accelerator.clip_grad_norm_(model.parameters(), 1.0) + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + completed_steps += 1 + if (step % (eval_steps * gradient_accumulation_steps)) == 0: + eval_loss, perplexity = evaluate() + accelerator.print({"loss/eval": eval_loss, "perplexity": perplexity}) + model.train() + 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 step {step}", blocking=False + ) +``` + +And that's it -- you now have your own custom training loop for causal language models such as GPT-2 that you can further customize to your needs. + + + +✏️ **Try it out!** Either create your own custom loss function tailored to your use case, or add another custom step into the training loop. + + + + + +✏️ **Try it out!** When running long training experiments it's a good idea to log important metrics using tools such as TensorBoard or Weights & Biases. Add proper logging to the training loop so you can always check how the training is going. + + + +{/if} diff --git a/chapters/ro/chapter7/7.mdx b/chapters/ro/chapter7/7.mdx new file mode 100644 index 000000000..34556be21 --- /dev/null +++ b/chapters/ro/chapter7/7.mdx @@ -0,0 +1,1203 @@ + + +# Question answering[[question-answering]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Time to look at question answering! This task comes in many flavors, but the one we'll focus on in this section is called *extractive* question answering. This involves posing questions about a document and identifying the answers as _spans of text_ in the document itself. + + + +We will fine-tune a BERT model on the [SQuAD dataset](https://rajpurkar.github.io/SQuAD-explorer/), which consists of questions posed by crowdworkers on a set of Wikipedia articles. This will give us a model able to compute predictions like this one: + + + +This is actually showcasing the model that was trained and uploaded to the Hub using the code shown in this section. You can find it and double-check the predictions [here](https://huggingface.co/huggingface-course/bert-finetuned-squad?context=%F0%9F%A4%97+Transformers+is+backed+by+the+three+most+popular+deep+learning+libraries+%E2%80%94+Jax%2C+PyTorch+and+TensorFlow+%E2%80%94+with+a+seamless+integration+between+them.+It%27s+straightforward+to+train+your+models+with+one+before+loading+them+for+inference+with+the+other.&question=Which+deep+learning+libraries+back+%F0%9F%A4%97+Transformers%3F). + + + +💡 Encoder-only models like BERT tend to be great at extracting answers to factoid questions like "Who invented the Transformer architecture?" but fare poorly when given open-ended questions like "Why is the sky blue?" In these more challenging cases, encoder-decoder models like T5 and BART are typically used to synthesize the information in a way that's quite similar to [text summarization](/course/chapter7/5). If you're interested in this type of *generative* question answering, we recommend checking out our [demo](https://yjernite.github.io/lfqa.html) based on the [ELI5 dataset](https://huggingface.co/datasets/eli5). + + + +## Preparing the data[[preparing-the-data]] + +The dataset that is used the most as an academic benchmark for extractive question answering is [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), so that's the one we'll use here. There is also a harder [SQuAD v2](https://huggingface.co/datasets/squad_v2) benchmark, which includes questions that don't have an answer. As long as your own dataset contains a column for contexts, a column for questions, and a column for answers, you should be able to adapt the steps below. + +### The SQuAD dataset[[the-squad-dataset]] + +As usual, we can download and cache the dataset in just one step thanks to `load_dataset()`: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("squad") +``` + +We can then have a look at this object to learn more about the SQuAD dataset: + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'title', 'context', 'question', 'answers'], + num_rows: 87599 + }) + validation: Dataset({ + features: ['id', 'title', 'context', 'question', 'answers'], + num_rows: 10570 + }) +}) +``` + +It looks like we have everything we need with the `context`, `question`, and `answers` fields, so let's print those for the first element of our training set: + +```py +print("Context: ", raw_datasets["train"][0]["context"]) +print("Question: ", raw_datasets["train"][0]["question"]) +print("Answer: ", raw_datasets["train"][0]["answers"]) +``` + +```python out +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]} +``` + +The `context` and `question` fields are very straightforward to use. The `answers` field is a bit trickier as it comports a dictionary with two fields that are both lists. This is the format that will be expected by the `squad` metric during evaluation; if you are using your own data, you don't necessarily need to worry about putting the answers in the same format. The `text` field is rather obvious, and the `answer_start` field contains the starting character index of each answer in the context. + +During training, there is only one possible answer. We can double-check this by using the `Dataset.filter()` method: + +```py +raw_datasets["train"].filter(lambda x: len(x["answers"]["text"]) != 1) +``` + +```python out +Dataset({ + features: ['id', 'title', 'context', 'question', 'answers'], + num_rows: 0 +}) +``` + +For evaluation, however, there are several possible answers for each sample, which may be the same or different: + +```py +print(raw_datasets["validation"][0]["answers"]) +print(raw_datasets["validation"][2]["answers"]) +``` + +```python out +{'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]} +``` + +We won't dive into the evaluation script as it will all be wrapped up by a 🤗 Datasets metric for us, but the short version is that some of the questions have several possible answers, and this script will compare a predicted answer to all the acceptable answers and take the best score. If we take a look at the sample at index 2, for instance: + +```py +print(raw_datasets["validation"][2]["context"]) +print(raw_datasets["validation"][2]["question"]) +``` + +```python out +'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?' +``` + +we can see that the answer can indeed be one of the three possibilities we saw before. + +### Processing the training data[[processing-the-training-data]] + + + +Let's start with preprocessing the training data. The hard part will be to generate labels for the question's answer, which will be the start and end positions of the tokens corresponding to the answer inside the context. + +But let's not get ahead of ourselves. First, we need to convert the text in the input into IDs the model can make sense of, using a tokenizer: + +```py +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +As mentioned previously, we'll be fine-tuning a BERT model, but you can use any other model type as long as it has a fast tokenizer implemented. You can see all the architectures that come with a fast version in [this big table](https://huggingface.co/transformers/#supported-frameworks), and to check that the `tokenizer` object you're using is indeed backed by 🤗 Tokenizers you can look at its `is_fast` attribute: + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +We can pass to our tokenizer the question and the context together, and it will properly insert the special tokens to form a sentence like this: + +``` +[CLS] question [SEP] context [SEP] +``` + +Let's double-check: + +```py +context = raw_datasets["train"][0]["context"] +question = raw_datasets["train"][0]["question"] + +inputs = tokenizer(question, context) +tokenizer.decode(inputs["input_ids"]) +``` + +```python out +'[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]' +``` + +The labels will then be the index of the tokens starting and ending the answer, and the model will be tasked to predicted one start and end logit per token in the input, with the theoretical labels being as follow: + +
+One-hot encoded labels for question answering. + +
+ +In this case the context is not too long, but some of the examples in the dataset have very long contexts that will exceed the maximum length we set (which is 384 in this case). As we saw in [Chapter 6](/course/chapter6/4) when we explored the internals of the `question-answering` pipeline, we will deal with long contexts by creating several training features from one sample of our dataset, with a sliding window between them. + +To see how this works using the current example, we can limit the length to 100 and use a sliding window of 50 tokens. As a reminder, we use: + +- `max_length` to set the maximum length (here 100) +- `truncation="only_second"` to truncate the context (which is in the second position) when the question with its context is too long +- `stride` to set the number of overlapping tokens between two successive chunks (here 50) +- `return_overflowing_tokens=True` to let the tokenizer know we want the overflowing tokens + +```py +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)) +``` + +```python out +'[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]' +``` + +As we can see, our example has been in split into four inputs, each of them containing the question and some part of the context. Note that the answer to the question ("Bernadette Soubirous") only appears in the third and last inputs, so by dealing with long contexts in this way we will create some training examples where the answer is not included in the context. For those examples, the labels will be `start_position = end_position = 0` (so we predict the `[CLS]` token). We will also set those labels in the unfortunate case where the answer has been truncated so that we only have the start (or end) of it. For the examples where the answer is fully in the context, the labels will be the index of the token where the answer starts and the index of the token where the answer ends. + +The dataset provides us with the start character of the answer in the context, and by adding the length of the answer, we can find the end character in the context. To map those to token indices, we will need to use the offset mappings we studied in [Chapter 6](/course/chapter6/4). We can have our tokenizer return these by passing along `return_offsets_mapping=True`: + +```py +inputs = tokenizer( + question, + context, + max_length=100, + truncation="only_second", + stride=50, + return_overflowing_tokens=True, + return_offsets_mapping=True, +) +inputs.keys() +``` + +```python out +dict_keys(['input_ids', 'token_type_ids', 'attention_mask', 'offset_mapping', 'overflow_to_sample_mapping']) +``` + +As we can see, we get back the usual input IDs, token type IDs, and attention mask, as well as the offset mapping we required and an extra key, `overflow_to_sample_mapping`. The corresponding value will be of use to us when we tokenize several texts at the same time (which we should do to benefit from the fact that our tokenizer is backed by Rust). Since one sample can give several features, it maps each feature to the example it originated from. Because here we only tokenized one example, we get a list of `0`s: + +```py +inputs["overflow_to_sample_mapping"] +``` + +```python out +[0, 0, 0, 0] +``` + +But if we tokenize more examples, this will become more useful: + +```py +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']}.") +``` + +```python out +'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].' +``` + +As we can see, the first three examples (at indices 2, 3, and 4 in the training set) each gave four features and the last example (at index 5 in the training set) gave 7 features. + +This information will be useful to map each feature we get to its corresponding label. As mentioned earlier, those labels are: + +- `(0, 0)` if the answer is not in the corresponding span of the context +- `(start_position, end_position)` if the answer is in the corresponding span of the context, with `start_position` being the index of the token (in the input IDs) at the start of the answer and `end_position` being the index of the token (in the input IDs) where the answer ends + +To determine which of these is the case and, if relevant, the positions of the tokens, we first find the indices that start and end the context in the input IDs. We could use the token type IDs to do this, but since those do not necessarily exist for all models (DistilBERT does not require them, for instance), we'll instead use the `sequence_ids()` method of the `BatchEncoding` our tokenizer returns. + +Once we have those token indices, we look at the corresponding offsets, which are tuples of two integers representing the span of characters inside the original context. We can thus detect if the chunk of the context in this feature starts after the answer or ends before the answer begins (in which case the label is `(0, 0)`). If that's not the case, we loop to find the first and last token of the answer: + +```py +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 +``` + +```python out +([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]) +``` + +Let's take a look at a few results to verify that our approach is correct. For the first feature we find `(83, 85)` as labels, so let's compare the theoretical answer with the decoded span of tokens from 83 to 85 (inclusive): + +```py +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}") +``` + +```python out +'Theoretical answer: the Main Building, labels give: the Main Building' +``` + +So that's a match! Now let's check index 4, where we set the labels to `(0, 0)`, which means the answer is not in the context chunk of that feature: + +```py +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}") +``` + +```python out +'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]' +``` + +Indeed, we don't see the answer inside the context. + + + +✏️ **Your turn!** When using the XLNet architecture, padding is applied on the left and the question and context are switched. Adapt all the code we just saw to the XLNet architecture (and add `padding=True`). Be aware that the `[CLS]` token may not be at the 0 position with padding applied. + + + +Now that we have seen step by step how to preprocess our training data, we can group it in a function we will apply on the whole training dataset. We'll pad every feature to the maximum length we set, as most of the contexts will be long (and the corresponding samples will be split into several features), so there is no real benefit to applying dynamic padding here: + +```py +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 +``` + +Note that we defined two constants to determine the maximum length used as well as the length of the sliding window, and that we added a tiny bit of cleanup before tokenizing: some of the questions in the SQuAD dataset have extra spaces at the beginning and the end that don't add anything (and take up space when being tokenized if you use a model like RoBERTa), so we removed those extra spaces. + +To apply this function to the whole training set, we use the `Dataset.map()` method with the `batched=True` flag. It's necessary here as we are changing the length of the dataset (since one example can give several training features): + +```py +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) +``` + +```python out +(87599, 88729) +``` + +As we can see, the preprocessing added roughly 1,000 features. Our training set is now ready to be used -- let's dig into the preprocessing of the validation set! + +### Processing the validation data[[processing-the-validation-data]] + +Preprocessing the validation data will be slightly easier as we don't need to generate labels (unless we want to compute a validation loss, but that number won't really help us understand how good the model is). The real joy will be to interpret the predictions of the model into spans of the original context. For this, we will just need to store both the offset mappings and some way to match each created feature to the original example it comes from. Since there is an ID column in the original dataset, we'll use that ID. + +The only thing we'll add here is a tiny bit of cleanup of the offset mappings. They will contain offsets for the question and the context, but once we're in the post-processing stage we won't have any way to know which part of the input IDs corresponded to the context and which part was the question (the `sequence_ids()` method we used is available for the output of the tokenizer only). So, we'll set the offsets corresponding to the question to `None`: + +```py +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 +``` + +We can apply this function on the whole validation dataset like before: + +```py +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) +``` + +```python out +(10570, 10822) +``` + +In this case we've only added a couple of hundred samples, so it appears the contexts in the validation dataset are a bit shorter. + +Now that we have preprocessed all the data, we can get to the training. + +{#if fw === 'pt'} + +## Fine-tuning the model with the `Trainer` API[[fine-tuning-the-model-with-the-trainer-api]] + +The training code for this example will look a lot like the code in the previous sections -- the hardest thing will be to write the `compute_metrics()` function. Since we padded all the samples to the maximum length we set, there is no data collator to define, so this metric computation is really the only thing we have to worry about. The difficult part will be to post-process the model predictions into spans of text in the original examples; once we have done that, the metric from the 🤗 Datasets library will do most of the work for us. + +{:else} + +## Fine-tuning the model with Keras[[fine-tuning-the-model-with-keras]] + +The training code for this example will look a lot like the code in the previous sections, but computing the metrics will be uniquely challenging. Since we padded all the samples to the maximum length we set, there is no data collator to define, so this metric computation is really the only thing we have to worry about. The hard part will be to post-process the model predictions into spans of text in the original examples; once we have done that, the metric from the 🤗 Datasets library will do most of the work for us. + +{/if} + +### Post-processing[[post-processing]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +The model will output logits for the start and end positions of the answer in the input IDs, as we saw during our exploration of the [`question-answering` pipeline](/course/chapter6/3b). The post-processing step will be similar to what we did there, so here's a quick reminder of the actions we took: + +- We masked the start and end logits corresponding to tokens outside of the context. +- We then converted the start and end logits into probabilities using a softmax. +- We attributed a score to each `(start_token, end_token)` pair by taking the product of the corresponding two probabilities. +- We looked for the pair with the maximum score that yielded a valid answer (e.g., a `start_token` lower than `end_token`). + +Here we will change this process slightly because we don't need to compute actual scores (just the predicted answer). This means we can skip the softmax step. To go faster, we also won't score all the possible `(start_token, end_token)` pairs, but only the ones corresponding to the highest `n_best` logits (with `n_best=20`). Since we will skip the softmax, those scores will be logit scores, and will be obtained by taking the sum of the start and end logits (instead of the product, because of the rule \\(\log(ab) = \log(a) + \log(b)\\)). + +To demonstrate all of this, we will need some kind of predictions. Since we have not trained our model yet, we are going to use the default model for the QA pipeline to generate some predictions on a small part of the validation set. We can use the same processing function as before; because it relies on the global constant `tokenizer`, we just have to change that object to the tokenizer of the model we want to use temporarily: + +```python +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, +) +``` + +Now that the preprocessing is done, we change the tokenizer back to the one we originally picked: + +```python +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +We then remove the columns of our `eval_set` that are not expected by the model, build a batch with all of that small validation set, and pass it through the model. If a GPU is available, we use it to go faster: + +{#if fw === 'pt'} + +```python +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) +``` + +Since the `Trainer` will give us predictions as NumPy arrays, we grab the start and end logits and convert them to that format: + +```python +start_logits = outputs.start_logits.cpu().numpy() +end_logits = outputs.end_logits.cpu().numpy() +``` + +{:else} + +```python +import tensorflow as tf +from transformers import TFAutoModelForQuestionAnswering + +eval_set_for_model = eval_set.remove_columns(["example_id", "offset_mapping"]) +eval_set_for_model.set_format("numpy") + +batch = {k: eval_set_for_model[k] for k in eval_set_for_model.column_names} +trained_model = TFAutoModelForQuestionAnswering.from_pretrained(trained_checkpoint) + +outputs = trained_model(**batch) +``` + +For ease of experimentation, let's convert these outputs to NumPy arrays: + +```python +start_logits = outputs.start_logits.numpy() +end_logits = outputs.end_logits.numpy() +``` + +{/if} + +Now, we need to find the predicted answer for each example in our `small_eval_set`. One example may have been split into several features in `eval_set`, so the first step is to map each example in `small_eval_set` to the corresponding features in `eval_set`: + +```python +import collections + +example_to_features = collections.defaultdict(list) +for idx, feature in enumerate(eval_set): + example_to_features[feature["example_id"]].append(idx) +``` + +With this in hand, we can really get to work by looping through all the examples and, for each example, through all the associated features. As we said before, we'll look at the logit scores for the `n_best` start logits and end logits, excluding positions that give: + +- An answer that wouldn't be inside the context +- An answer with negative length +- An answer that is too long (we limit the possibilities at `max_answer_length=30`) + +Once we have all the scored possible answers for one example, we just pick the one with the best logit score: + +```python +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"]}) +``` + +The final format of the predicted answers is the one that will be expected by the metric we will use. As usual, we can load it with the help of the 🤗 Evaluate library: + +```python +import evaluate + +metric = evaluate.load("squad") +``` + +This metric expects the predicted answers in the format we saw above (a list of dictionaries with one key for the ID of the example and one key for the predicted text) and the theoretical answers in the format below (a list of dictionaries with one key for the ID of the example and one key for the possible answers): + +```python +theoretical_answers = [ + {"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set +] +``` + +We can now check that we get sensible results by looking at the first element of both lists: + +```python +print(predicted_answers[0]) +print(theoretical_answers[0]) +``` + +```python out +{'id': '56be4db0acb8001400a502ec', 'prediction_text': 'Denver Broncos'} +{'id': '56be4db0acb8001400a502ec', 'answers': {'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]}} +``` + +Not too bad! Now let's have a look at the score the metric gives us: + +```python +metric.compute(predictions=predicted_answers, references=theoretical_answers) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +Again, that's rather good considering that according to [its paper](https://arxiv.org/abs/1910.01108v2) DistilBERT fine-tuned on SQuAD obtains 79.1 and 86.9 for those scores on the whole dataset. + +{#if fw === 'pt'} + +Now let's put everything we just did in a `compute_metrics()` function that we will use in the `Trainer`. Normally, that `compute_metrics()` function only receives a tuple `eval_preds` with logits and labels. Here we will need a bit more, as we have to look in the dataset of features for the offset and in the dataset of examples for the original contexts, so we won't be able to use this function to get regular evaluation results during training. We will only use it at the end of training to check the results. + +The `compute_metrics()` function groups the same steps as before; we just add a small check in case we don't come up with any valid answers (in which case we predict an empty string). + +{:else} + +Now let's put everything we just did in a `compute_metrics()` function that we will use after training our model. We will need to pass a bit more than just the output logits, as we have to look in the dataset of features for the offset and in the dataset of examples for the original contexts: + +{/if} + +```python +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) +``` + +We can check it works on our predictions: + +```python +compute_metrics(start_logits, end_logits, eval_set, small_eval_set) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +Looking good! Now let's use this to fine-tune our model. + +### Fine-tuning the model[[fine-tuning-the-model]] + +{#if fw === 'pt'} + +We are now ready to train our model. Let's create it first, using the `AutoModelForQuestionAnswering` class like before: + +```python +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{:else} + +We are now ready to train our model. Let's create it first, using the `TFAutoModelForQuestionAnswering` class like before: + +```python +model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{/if} + +As usual, we get a warning that some weights are not used (the ones from the pretraining head) and some others are initialized randomly (the ones for the question answering head). You should be used to this by now, but that means this model is not ready to be used just yet and needs fine-tuning -- good thing we're about to do that! + +To be able to push our model to the Hub, we'll need to log in to Hugging Face. If you're running this code in a notebook, you can do so with the following utility function, which displays a widget where you can enter your login credentials: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +If you aren't working in a notebook, just type the following line in your terminal: + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +Once this is done, we can define our `TrainingArguments`. As we said when we defined our function to compute the metric, we won't be able to have a regular evaluation loop because of the signature of the `compute_metrics()` function. We could write our own subclass of `Trainer` to do this (an approach you can find in the [question answering example script](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), but that's a bit too long for this section. Instead, we will only evaluate the model at the end of training here and show you how to do a regular evaluation in "A custom training loop" below. + +This is really where the `Trainer` API shows its limits and the 🤗 Accelerate library shines: customizing the class to a specific use case can be painful, but tweaking a fully exposed training loop is easy. + +Let's take a look at our `TrainingArguments`: + +```python +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, +) +``` + +We've seen most of these before: we set some hyperparameters (like the learning rate, the number of epochs we train for, and some weight decay) and indicate that we want to save the model at the end of every epoch, skip evaluation, and upload our results to the Model Hub. We also enable mixed-precision training with `fp16=True`, as it can speed up the training nicely on a recent GPU. + +{:else} + +Now that's done, we can create our TF Datasets. We can use the simple default data collator this time: + +```python +from transformers import DefaultDataCollator + +data_collator = DefaultDataCollator(return_tensors="tf") +``` + +And now we create the datasets as usual. + +```python +tf_train_dataset = model.prepare_tf_dataset( + train_dataset, + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) +tf_eval_dataset = model.prepare_tf_dataset( + validation_dataset, + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +Next, we set up our training hyperparameters and compile our model: + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied +# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset, +# not the original Hugging Face Dataset, so its len() is already num_samples // batch_size. +num_train_epochs = 3 +num_train_steps = len(tf_train_dataset) * num_train_epochs +optimizer, schedule = create_optimizer( + init_lr=2e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Finally, we're ready to train with `model.fit()`. We use a `PushToHubCallback` to upload the model to the Hub after each epoch. + +{/if} + +By default, the repository used will be in your namespace and named after the output directory you set, so in our case it will be in `"sgugger/bert-finetuned-squad"`. We can override this by passing a `hub_model_id`; for instance, to push the model to the `huggingface_course` organization we used `hub_model_id="huggingface_course/bert-finetuned-squad"` (which is the model we linked to at the beginning of this section). + +{#if fw === 'pt'} + + + +💡 If the output directory you are using exists, it needs to be a local clone of the repository you want to push to (so set a new name if you get an error when defining your `Trainer`). + + + +Finally, we just pass everything to the `Trainer` class and launch the training: + +```python +from transformers import Trainer + +trainer = Trainer( + model=model, + args=args, + train_dataset=train_dataset, + eval_dataset=validation_dataset, + tokenizer=tokenizer, +) +trainer.train() +``` + +{:else} + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback(output_dir="bert-finetuned-squad", tokenizer=tokenizer) + +# We're going to do validation afterwards, so no validation mid-training +model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) +``` + +{/if} + +Note that while the training happens, each time the model is saved (here, every epoch) it is uploaded to the Hub in the background. This way, you will be able to to resume your training on another machine if necessary. The whole training takes a while (a little over an hour on a Titan RTX), so you can grab a coffee or reread some of the parts of the course that you've found more challenging while it proceeds. Also note that as soon as the first epoch is finished, you will see some weights uploaded to the Hub and you can start playing with your model on its page. + +{#if fw === 'pt'} + +Once the training is complete, we can finally evaluate our model (and pray we didn't spend all that compute time on nothing). The `predict()` method of the `Trainer` will return a tuple where the first elements will be the predictions of the model (here a pair with the start and end logits). We send this to our `compute_metrics()` function: + +```python +predictions, _, _ = trainer.predict(validation_dataset) +start_logits, end_logits = predictions +compute_metrics(start_logits, end_logits, validation_dataset, raw_datasets["validation"]) +``` + +{:else} + +Once the training is complete, we can finally evaluate our model (and pray we didn't spend all that compute time on nothing). The `predict()` method of our `model` will take care of getting predictions, and since we did all the hard work of defining a `compute_metrics()` function earlier, we can get our results in a single line: + +```python +predictions = model.predict(tf_eval_dataset) +compute_metrics( + predictions["start_logits"], + predictions["end_logits"], + validation_dataset, + raw_datasets["validation"], +) +``` + +{/if} + +```python out +{'exact_match': 81.18259224219489, 'f1': 88.67381321905516} +``` + +Great! As a comparison, the baseline scores reported in the BERT article for this model are 80.8 and 88.5, so we're right where we should be. + +{#if fw === 'pt'} + +Finally, we use the `push_to_hub()` method to make sure we upload the latest version of the model: + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +This returns the URL of the commit it just did, if you want to inspect it: + +```python out +'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' +``` + +The `Trainer` also drafts a model card with all the evaluation results and uploads it. + +{/if} + +At this stage, you can use the inference widget on the Model Hub to test the model and share it with your friends, family, and favorite pets. You have successfully fine-tuned a model on a question answering task -- congratulations! + + + +✏️ **Your turn!** Try another model architecture to see if it performs better on this task! + + + +{#if fw === 'pt'} + +If you want to dive a bit more deeply into the training loop, we will now show you how to do the same thing using 🤗 Accelerate. + +## A custom training loop[[a-custom-training-loop]] + +Let's now have a look at the full training loop, so you can easily customize the parts you need. It will look a lot like the training loop in [Chapter 3](/course/chapter3/4), with the exception of the evaluation loop. We will be able to evaluate the model regularly since we're not constrained by the `Trainer` class anymore. + +### Preparing everything for training[[preparing-everything-for-training]] + +First we need to build the `DataLoader`s from our datasets. We set the format of those datasets to `"torch"`, and remove the columns in the validation set that are not used by the model. Then, we can use the `default_data_collator` provided by Transformers as a `collate_fn` and shuffle the training set, but not the validation set: + +```py +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 +) +``` + +Next we reinstantiate our model, to make sure we're not continuing the fine-tuning from before but starting from the BERT pretrained model again: + +```py +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +Then we will need an optimizer. As usual we use the classic `AdamW`, which is like Adam, but with a fix in the way weight decay is applied: + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Once we have all those objects, we can send them to the `accelerator.prepare()` method. Remember that if you want to train on TPUs in a Colab notebook, you will need to move all of this code into a training function, and that shouldn't execute any cell that instantiates an `Accelerator`. We can force mixed-precision training by passing `fp16=True` to the `Accelerator` (or, if you are executing the code as a script, just make sure to fill in the 🤗 Accelerate `config` appropriately). + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +As you should know from the previous sections, we can only use the `train_dataloader` length to compute the number of training steps after it has gone through the `accelerator.prepare()` method. We use the same linear schedule as in the previous sections: + +```py +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, +) +``` + +To push our model to the Hub, we will need to create a `Repository` object in a working folder. First log in to the Hugging Face Hub, if you're not logged in already. We'll determine the repository name from the model ID we want to give our model (feel free to replace the `repo_name` with your own choice; it just needs to contain your username, which is what the function `get_full_repo_name()` does): + +```py +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 +``` + +```python out +'sgugger/bert-finetuned-squad-accelerate' +``` + +Then we can clone that repository in a local folder. If it already exists, this local folder should be a clone of the repository we are working with: + +```py +output_dir = "bert-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +We can now upload anything we save in `output_dir` by calling the `repo.push_to_hub()` method. This will help us upload the intermediate models at the end of each epoch. + +## Training loop[[training-loop]] + +We are now ready to write the full training loop. After defining a progress bar to follow how training goes, the loop has three parts: + +- The training in itself, which is the classic iteration over the `train_dataloader`, forward pass through the model, then backward pass and optimizer step. +- The evaluation, in which we gather all the values for `start_logits` and `end_logits` before converting them to NumPy arrays. Once the evaluation loop is finished, we concatenate all the results. Note that we need to truncate because the `Accelerator` may have added a few samples at the end to ensure we have the same number of examples in each process. +- Saving and uploading, where we first save the model and the tokenizer, then call `repo.push_to_hub()`. As we did before, we use the argument `blocking=False` to tell the 🤗 Hub library to push in an asynchronous process. This way, training continues normally and this (long) instruction is executed in the background. + +Here's the complete code for the training loop: + +```py +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 + ) +``` + +In case this is the first time you're seeing a model saved with 🤗 Accelerate, let's take a moment to inspect the three lines of code that go with it: + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +The first line is self-explanatory: it tells all the processes to wait until everyone is at that stage before continuing. This is to make sure we have the same model in every process before saving. Then we grab the `unwrapped_model`, which is the base model we defined. The `accelerator.prepare()` method changes the model to work in distributed training, so it won't have the `save_pretrained()` method anymore; the `accelerator.unwrap_model()` method undoes that step. Lastly, we call `save_pretrained()` but tell that method to use `accelerator.save()` instead of `torch.save()`. + +Once this is done, you should have a model that produces results pretty similar to the one trained with the `Trainer`. You can check the model we trained using this code at [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). And if you want to test out any tweaks to the training loop, you can directly implement them by editing the code shown above! + +{/if} + +## Using the fine-tuned model[[using-the-fine-tuned-model]] + +We've already shown you how you can use the model we fine-tuned on the Model Hub with the inference widget. To use it locally in a `pipeline`, you just have to specify the model identifier: + +```py +from transformers import pipeline + +# Replace this with your own 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) +``` + +```python out +{'score': 0.9979003071784973, + 'start': 78, + 'end': 105, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +Great! Our model is working as well as the default one for this pipeline! diff --git a/chapters/ro/chapter7/8.mdx b/chapters/ro/chapter7/8.mdx new file mode 100644 index 000000000..78693b25b --- /dev/null +++ b/chapters/ro/chapter7/8.mdx @@ -0,0 +1,22 @@ +# Mastering NLP[[mastering-nlp]] + + + +If you've made it this far in the course, congratulations -- you now have all the knowledge and tools you need to tackle (almost) any NLP task with 🤗 Transformers and the Hugging Face ecosystem! + +We have seen a lot of different data collators, so we made this little video to help you find which one to use for each task: + + + +After completing this lightning tour through the core NLP tasks, you should: + +* Know which architectures (encoder, decoder, or encoder-decoder) are best suited for each task +* Understand the difference between pretraining and fine-tuning a language model +* Know how to train Transformer models using either the `Trainer` API and distributed training features of 🤗 Accelerate or TensorFlow and Keras, depending on which track you've been following +* Understand the meaning and limitations of metrics like ROUGE and BLEU for text generation tasks +* Know how to interact with your fine-tuned models, both on the Hub and using the `pipeline` from 🤗 Transformers + +Despite all this knowledge, there will come a time when you'll either encounter a difficult bug in your code or have a question about how to solve a particular NLP problem. Fortunately, the Hugging Face community is here to help you! In the final chapter of this part of the course, we'll explore how you can debug your Transformer models and ask for help effectively. \ No newline at end of file diff --git a/chapters/ro/chapter7/9.mdx b/chapters/ro/chapter7/9.mdx new file mode 100644 index 000000000..cb517efbf --- /dev/null +++ b/chapters/ro/chapter7/9.mdx @@ -0,0 +1,329 @@ + + + + +# End-of-chapter quiz[[end-of-chapter-quiz]] + + + +Let's test what you learned in this chapter! + +### 1. Which of the following tasks can be framed as a token classification problem? + + + +### 2. What part of the preprocessing for token classification differs from the other preprocessing pipelines? + +-100 to label the special tokens.", + explain: "That's not specific to token classification -- we always use -100 as the label for tokens we want to ignore in the loss." + }, + { + text: "We need to make sure to truncate or pad the labels to the same size as the inputs, when applying truncation/padding.", + explain: "Indeed! That's not the only difference, though.", + correct: true + } + ]} +/> + +### 3. What problem arises when we tokenize the words in a token classification problem and want to label the tokens? + +-100 so they are ignored in the loss." + }, + { + text: "Each word can produce several tokens, so we end up with more tokens than we have labels.", + explain: "That is the main problem, and we need to align the original labels with the tokens.", + correct: true + }, + { + text: "The added tokens have no labels, so there is no problem.", + explain: "That's incorrect; we need as many labels as we have tokens or our models will error out." + } + ]} +/> + +### 4. What does "domain adaptation" mean? + + + +### 5. What are the labels in a masked language modeling problem? + + + +### 6. Which of these tasks can be seen as a sequence-to-sequence problem? + + + +### 7. What is the proper way to preprocess the data for a sequence-to-sequence problem? + +inputs=... and targets=....", + explain: "This might be an API we add in the future, but that's not possible right now." + }, + { + text: "The inputs and the targets both have to be preprocessed, in two separate calls to the tokenizer.", + explain: "That is true, but incomplete. There is something you need to do to make sure the tokenizer processes both properly." + }, + { + text: "As usual, we just have to tokenize the inputs.", + explain: "Not in a sequence classification problem; the targets are also texts we need to convert into numbers!" + }, + { + text: "The inputs have to be sent to the tokenizer, and the targets too, but under a special context manager.", + explain: "That's correct, the tokenizer needs to be put into target mode by that context manager.", + correct: true + } + ]} +/> + +{#if fw === 'pt'} + +### 8. Why is there a specific subclass of `Trainer` for sequence-to-sequence problems? + +-100", + explain: "That's not a custom loss at all, but the way the loss is always computed." + }, + { + text: "Because sequence-to-sequence problems require a special evaluation loop", + explain: "That's correct. Sequence-to-sequence models' predictions are often run using the generate() method.", + correct: true + }, + { + text: "Because the targets are texts in sequence-to-sequence problems", + explain: "The Trainer doesn't really care about that since they have been preprocessed before." + }, + { + text: "Because we use two models in sequence-to-sequence problems", + explain: "We do use two models in a way, an encoder and a decoder, but they are grouped together in one model." + } + ]} +/> + +{:else} + +### 9. Why is it often unnecessary to specify a loss when calling `compile()` on a Transformer model? + + + +{/if} + +### 10. When should you pretrain a new model? + + + +### 11. Why is it easy to pretrain a language model on lots and lots of texts? + + + +### 12. What are the main challenges when preprocessing data for a question answering task? + + + +### 13. How is post-processing usually done in question answering? + + From 5681ee4c9d309c24437582c84ecf8aa20381e477 Mon Sep 17 00:00:00 2001 From: Angroys <120798951+Angroys@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:12:41 +0200 Subject: [PATCH 09/37] done until section 6 --- chapters/ro/chapter7/3.mdx | 207 +++++++++++++++-------------- chapters/ro/chapter7/4.mdx | 259 ++++++++++++++++++------------------- chapters/ro/chapter7/5.mdx | 246 ++++++++++++++++++----------------- chapters/ro/chapter7/6.mdx | 136 +++++++++---------- chapters/ro/chapter7/7.mdx | 2 +- 5 files changed, 425 insertions(+), 425 deletions(-) diff --git a/chapters/ro/chapter7/3.mdx b/chapters/ro/chapter7/3.mdx index de3da9a1f..9c33524f7 100644 --- a/chapters/ro/chapter7/3.mdx +++ b/chapters/ro/chapter7/3.mdx @@ -1,6 +1,6 @@ -# Fine-tuning a masked language model[[fine-tuning-a-masked-language-model]] +# Fine-tuningul unui masked language model[[fine-tuning-a-masked-language-model]] {#if fw === 'pt'} @@ -22,45 +22,45 @@ {/if} -For many NLP applications involving Transformer models, you can simply take a pretrained model from the Hugging Face Hub and fine-tune it directly on your data for the task at hand. Provided that the corpus used for pretraining is not too different from the corpus used for fine-tuning, transfer learning will usually produce good results. +Pentru multe aplicații NLP care implică modele Transformer, puteți lua pur și simplu un model preantrenat de pe Hugging Face Hub și să îl faceți fine-tune direct pe datele voastre pentru sarcina dată. Cu condiția că corpusul utilizat pentru preantrenare să nu fie prea diferit de corpusul utilizat pentru fine-tuning, învățarea prin transfer va produce de obicei rezultate bune. -However, there are a few cases where you'll want to first fine-tune the language models on your data, before training a task-specific head. For example, if your dataset contains legal contracts or scientific articles, a vanilla Transformer model like BERT will typically treat the domain-specific words in your corpus as rare tokens, and the resulting performance may be less than satisfactory. By fine-tuning the language model on in-domain data you can boost the performance of many downstream tasks, which means you usually only have to do this step once! +Cu toate acestea, există câteva cazuri în care veți dori să faceți fine-tune mai întâi modelelor lingvistice pe datele voastre, înainte de a antrena un head specific sarcinii. De exemplu, dacă datasetul vostru conține contracte juridice sau articole științifice, un model Transformer obișnuit, precum BERT, va trata de obicei cuvintele specifice domeniului din corpus ca pe niște tokeni rari, iar performanța rezultată poate fi mai puțin satisfăcătoare. Prin fine-tuningul modelului lingvistic pe baza datelor din domeniu, puteți crește performanța multor sarcini, ceea ce înseamnă că, de obicei, trebuie să efectuați acest pas o singură dată! -This process of fine-tuning a pretrained language model on in-domain data is usually called _domain adaptation_. It was popularized in 2018 by [ULMFiT](https://arxiv.org/abs/1801.06146), which was one of the first neural architectures (based on LSTMs) to make transfer learning really work for NLP. An example of domain adaptation with ULMFiT is shown in the image below; in this section we'll do something similar, but with a Transformer instead of an LSTM! +Acest proces de fine-tuning a unui model lingvistic preantrenat pe date din domeniu se numește de obicei _adaptare la domeniu_. Acesta a fost popularizat în 2018 de [ULMFiT](https://arxiv.org/abs/1801.06146), care a fost una dintre primele arhitecturi neuronale (bazate pe LSTM-uri) care a făcut ca învățarea prin transfer să funcționeze cu adevărat pentru NLP. Un exemplu de adaptare la domeniu cu ULMFiT este prezentat în imaginea de mai jos; în această secțiune vom face ceva similar, dar cu un Transformer în loc de un LSTM!
ULMFiT.
-By the end of this section you'll have a [masked language model](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) on the Hub that can autocomplete sentences as shown below: +Până la sfârșitul acestei secțiuni, veți avea un [model de limbaj mascat](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) pe Hub, care poate completa automat propoziții, după cum se poate vedea mai jos: -Let's dive in! +Hai să începem! -🙋 If the terms "masked language modeling" and "pretrained model" sound unfamiliar to you, go check out [Chapter 1](/course/chapter1), where we explain all these core concepts, complete with videos! +🙋 Dacă termenii "masked language modeling" și "pretrained model" nu vă sună familiar, mergeți să verificați [Capitolul 1](/course/chapter1), unde vă explicăm toate aceste concepte de bază, cu videoclipuri! -## Picking a pretrained model for masked language modeling[[picking-a-pretrained-model-for-masked-language-modeling]] +## Alegerea unui model preantrenat pentru masked language modeling[[picking-a-pretrained-model-for-masked-language-modeling]] -To get started, let's pick a suitable pretrained model for masked language modeling. As shown in the following screenshot, you can find a list of candidates by applying the "Fill-Mask" filter on the [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads): +Pentru a începe, să alegem un model preantrenat adecvat pentru modelarea limbajului mascat. După cum se vede în următoarea captură de ecran, puteți găsi o listă de candidați prin aplicarea filtrului "Fill-Mask" pe [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads):
Hub models.
-Although the BERT and RoBERTa family of models are the most downloaded, we'll use a model called [DistilBERT](https://huggingface.co/distilbert-base-uncased) -that can be trained much faster with little to no loss in downstream performance. This model was trained using a special technique called [_knowledge distillation_](https://en.wikipedia.org/wiki/Knowledge_distillation), where a large "teacher model" like BERT is used to guide the training of a "student model" that has far fewer parameters. An explanation of the details of knowledge distillation would take us too far afield in this section, but if you're interested you can read all about it in [_Natural Language Processing with Transformers_](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) (colloquially known as the Transformers textbook). +Deși modelele din familia BERT și RoBERTa sunt cele mai descărcate, vom utiliza un model numit [DistilBERT](https://huggingface.co/distilbert-base-uncased) +care poate fi antrenat mult mai rapid, cu o pierdere mică sau nulă a performanței în aval. Acest model a fost antrenat folosind o tehnică specială numită [_knowledge distillation_](https://en.wikipedia.org/wiki/Knowledge_distillation), în care un "model profesor" mare, precum BERT, este folosit pentru a ghida antrenarea unui "model elev" care are mult mai puțini parametrii. O explicație a detaliilor privind distilarea cunoștințelor ne-ar duce prea departe în această secțiune, dar dacă ești interesat, poți citi totul despre aceasta în [_Natural Language Processing with Transformers_](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) (cunoscut sub numele colocvial de Transformers textbooks). {#if fw === 'pt'} -Let's go ahead and download DistilBERT using the `AutoModelForMaskedLM` class: +Să continuăm și să descărcăm modelul DistilBERT folosind clasa `AutoModelForMaskedLM`: ```python from transformers import AutoModelForMaskedLM @@ -69,7 +69,7 @@ model_checkpoint = "distilbert-base-uncased" model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) ``` -We can see how many parameters this model has by calling the `num_parameters()` method: +Putem vedea câți parametri are acest model prin apelarea metodei `num_parameters()`: ```python distilbert_num_parameters = model.num_parameters() / 1_000_000 @@ -84,7 +84,7 @@ print(f"'>>> BERT number of parameters: 110M'") {:else} -Let's go ahead and download DistilBERT using the `AutoModelForMaskedLM` class: +Să continuăm și să descărcăm modelul DistilBERT folosind clasa `TFAutoModelForMaskedLM`: ```python from transformers import TFAutoModelForMaskedLM @@ -93,7 +93,7 @@ model_checkpoint = "distilbert-base-uncased" model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) ``` -We can see how many parameters this model has by calling the `summary()` method: +Putem vedea câți parametri are acest model prin apelarea metodei `summary()`: ```python model.summary() @@ -120,13 +120,13 @@ _________________________________________________________________ {/if} -With around 67 million parameters, DistilBERT is approximately two times smaller than the BERT base model, which roughly translates into a two-fold speedup in training -- nice! Let's now see what kinds of tokens this model predicts are the most likely completions of a small sample of text: +Cu aproximativ 67 de milioane de parametri, DistilBERT este de aproximativ două ori mai mic decât modelul de bază BERT, ceea ce se traduce aproximativ printr-o creștere de două ori a vitezei de antrenare - super! Să vedem acum ce tipuri de tokeni prezice acest model ca fiind cele mai probabile completări ale unui mic sample de text: ```python text = "This is a great [MASK]." ``` -As humans, we can imagine many possibilities for the `[MASK]` token, such as "day", "ride", or "painting". For pretrained models, the predictions depend on the corpus the model was trained on, since it learns to pick up the statistical patterns present in the data. Like BERT, DistilBERT was pretrained on the [English Wikipedia](https://huggingface.co/datasets/wikipedia) and [BookCorpus](https://huggingface.co/datasets/bookcorpus) datasets, so we expect the predictions for `[MASK]` to reflect these domains. To predict the mask we need DistilBERT's tokenizer to produce the inputs for the model, so let's download that from the Hub as well: +Ca oameni, ne putem imagina multe posibilități pentru tokenul `[MASK]`, cum ar fi "day", "ride" sau "painting". Pentru modelele preantrenate, predicțiile depind de corpusul pe care modelul a fost antrenat, deoarece acesta învață să detecteze tiparele statistice prezente în date. La fel ca BERT, DistilBERT a fost preantrenat pe dataseturile [English Wikipedia](https://huggingface.co/datasets/wikipedia) și [BookCorpus](https://huggingface.co/datasets/bookcorpus), astfel încât ne așteptăm ca predicțiile pentru `[MASK]` să reflecte aceste domenii. Pentru a prezice masca, avem nevoie de tokenizerul DistilBERT pentru a produce inputurile pentru model, deci hai să-l descărcăm și pe acesta din Hub: ```python from transformers import AutoTokenizer @@ -134,7 +134,7 @@ from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) ``` -With a tokenizer and a model, we can now pass our text example to the model, extract the logits, and print out the top 5 candidates: +Cu un tokenizer și un model, putem acum să transmitem exemplul nostru de text modelului, să extragem logiturile și să tipărim primii 5 candidați: {#if fw === 'pt'} @@ -181,13 +181,12 @@ for token in top_5_tokens: '>>> This is a great idea.' '>>> This is a great feat.' ``` +Putem vedea din rezultate că predicțiile modelului se referă la termeni din viața de zi cu zi, ceea ce poate că nu este surprinzător având în vedere fundamentul Wikipedia în limba engleză. Să vedem cum putem schimba acest domeniu în ceva puțin mai nișat - recenzii de filme foarte polarizate! -We can see from the outputs that the model's predictions refer to everyday terms, which is perhaps not surprising given the foundation of English Wikipedia. Let's see how we can change this domain to something a bit more niche -- highly polarized movie reviews! +## Datasetul[[the-dataset]] -## The dataset[[the-dataset]] - -To showcase domain adaptation, we'll use the famous [Large Movie Review Dataset](https://huggingface.co/datasets/imdb) (or IMDb for short), which is a corpus of movie reviews that is often used to benchmark sentiment analysis models. By fine-tuning DistilBERT on this corpus, we expect the language model will adapt its vocabulary from the factual data of Wikipedia that it was pretrained on to the more subjective elements of movie reviews. We can get the data from the Hugging Face Hub with the `load_dataset()` function from 🤗 Datasets: +Pentru a prezenta adaptarea la domeniu, vom utiliza faimosul [Large Movie Review Dataset] (https://huggingface.co/datasets/imdb) (sau IMDb pe scurt), care este un corpus de recenzii de filme care este adesea utilizat pentru a evalua modelele de analiză a sentimentelor. Prin fine-tuningul aplicat asupra DistilBERT pe acest corpus, ne așteptăm ca modelul de limbaj să își adapteze vocabularul de la datele factuale din Wikipedia pe care a fost antrenat în prealabil la elementele mai subiective ale recenziilor de film. Putem obține datele din Hugging Face Hub cu funcția `load_dataset()` din 🤗 Datasets: ```python from datasets import load_dataset @@ -213,7 +212,7 @@ DatasetDict({ }) ``` -We can see that the `train` and `test` splits each consist of 25,000 reviews, while there is an unlabeled split called `unsupervised` that contains 50,000 reviews. Let's take a look at a few samples to get an idea of what kind of text we're dealing with. As we've done in previous chapters of the course, we'll chain the `Dataset.shuffle()` and `Dataset.select()` functions to create a random sample: +Putem vedea că segmentele `train` și `test` conțin fiecare 25.000 de recenzii, în timp ce există un segment fără label numită `unsupervised` care conține 50.000 de recenzii. Să aruncăm o privire la câteva sampleuri pentru a ne face o idee despre tipul de text cu care avem de-a face. Așa cum am făcut în capitolele anterioare ale cursului, vom combina funcțiile `Dataset.shuffle()` și `Dataset.select()` pentru a crea un sample aleatoriu: ```python sample = imdb_dataset["train"].shuffle(seed=42).select(range(3)) @@ -235,23 +234,23 @@ for row in sample: '>>> Label: 1' ``` -Yep, these are certainly movie reviews, and if you're old enough you may even understand the comment in the last review about owning a VHS version 😜! Although we won't need the labels for language modeling, we can already see that a `0` denotes a negative review, while a `1` corresponds to a positive one. +Da, acestea sunt cu siguranță recenzii de film și, dacă sunteți suficient de bătrâni, ați putea chiar înțelege comentariul din ultima recenzie despre deținerea unei versiuni VHS 😜! Deși nu vom avea nevoie de labeluri pentru modelarea limbajului, putem vedea deja că un `0` denotă o recenzie negativă, în timp ce un `1` corespunde uneia pozitive. -✏️ **Try it out!** Create a random sample of the `unsupervised` split and verify that the labels are neither `0` nor `1`. While you're at it, you could also check that the labels in the `train` and `test` splits are indeed `0` or `1` -- this is a useful sanity check that every NLP practitioner should perform at the start of a new project! +✏️ **Încearcă!** Creați un sample aleatoriu din segmentul `unsupervised` și verificați că labelurile nu sunt nici `0`, nici `1`. În același timp, ați putea verifica și dacă labelurile din segmentele `train` și `test` sunt într-adevăr `0` sau `1` - aceasta este o verificare utilă pe care orice practicant NLP ar trebui să o efectueze la începutul unui nou proiect! -Now that we've had a quick look at the data, let's dive into preparing it for masked language modeling. As we'll see, there are some additional steps that one needs to take compared to the sequence classification tasks we saw in [Chapter 3](/course/chapter3). Let's go! +Acum că am aruncat o privire rapidă asupra datelor, să ne apucăm să le pregătim pentru modelarea limbajului mascat. După cum vom vedea, există câteva etape suplimentare pe care trebuie să le parcurgem în comparație cu sarcinile de clasificare a secvențelor pe care le-am văzut în [Capitolul 3](/course/chapter3). Să începem! -## Preprocessing the data[[preprocessing-the-data]] +## Preprocesarea datelor[[preprocessing-the-data]] -For both auto-regressive and masked language modeling, a common preprocessing step is to concatenate all the examples and then split the whole corpus into chunks of equal size. This is quite different from our usual approach, where we simply tokenize individual examples. Why concatenate everything together? The reason is that individual examples might get truncated if they're too long, and that would result in losing information that might be useful for the language modeling task! +Atât pentru auto-regressive cât și pentru masked language modeling, un pas comun de preprocesare este concatenarea tuturor exemplelor și apoi împărțirea întregului corpus în bucăți de dimensiuni egale. Acest lucru este destul de diferit de abordarea noastră obișnuită, în care pur și simplu tokenizăm exemplele individuale. De ce să concatenăm totul împreună? Motivul este că exemplele individuale ar putea fi trunchiate dacă sunt prea lungi, ceea ce ar duce la pierderea de informații care ar putea fi utile pentru sarcina de modelare a limbajului! -So to get started, we'll first tokenize our corpus as usual, but _without_ setting the `truncation=True` option in our tokenizer. We'll also grab the word IDs if they are available ((which they will be if we're using a fast tokenizer, as described in [Chapter 6](/course/chapter6/3)), as we will need them later on to do whole word masking. We'll wrap this in a simple function, and while we're at it we'll remove the `text` and `label` columns since we don't need them any longer: +Deci, pentru a începe, vom tokeniza mai întâi corpusul nostru ca de obicei, dar _fără_ a seta opțiunea `truncation=True` în tokenizerul nostru. De asemenea, vom prelua ID-urile cuvintelor dacă acestea sunt disponibile (ceea ce va fi cazul dacă folosim un tokenizer rapid, așa cum este descris în [Capitolul 6](/course/chapter6/3)), deoarece vom avea nevoie de ele mai târziu pentru a face mascarea întregului cuvânt. Vom include acest lucru într-o funcție simplă și, în același timp, vom elimina coloanele `text` și `label`, deoarece nu mai avem nevoie de ele: ```python def tokenize_function(examples): @@ -261,7 +260,7 @@ def tokenize_function(examples): return result -# Use batched=True to activate fast multithreading! +# Utilizați batched=True pentru a activa multithreadingul! tokenized_datasets = imdb_dataset.map( tokenize_function, batched=True, remove_columns=["text", "label"] ) @@ -285,9 +284,9 @@ DatasetDict({ }) ``` -Since DistilBERT is a BERT-like model, we can see that the encoded texts consist of the `input_ids` and `attention_mask` that we've seen in other chapters, as well as the `word_ids` we added. +Deoarece DistilBERT este un model de tip BERT, putem vedea că textele codate constau din `input_ids` și `attention_mask` pe care le-am văzut în alte capitole, precum și din `word_ids` pe care le-am adăugat. -Now that we've tokenized our movie reviews, the next step is to group them all together and split the result into chunks. But how big should these chunks be? This will ultimately be determined by the amount of GPU memory that you have available, but a good starting point is to see what the model's maximum context size is. This can be inferred by inspecting the `model_max_length` attribute of the tokenizer: +Acum că am tokenizat recenziile de filme, următorul pas este să le grupăm pe toate și să împărțim rezultatul în chunkuri. Dar cât de mari ar trebui să fie aceste chunkuri? Acest lucru va fi determinat în cele din urmă de cantitatea de memorie GPU pe care o aveți disponibilă, dar un bun punct de plecare este să vedeți care este dimensiunea maximă a contextului modelului. Aceasta poate fi dedusă prin inspectarea atributului `model_max_length` al tokenizerului: ```python tokenizer.model_max_length @@ -297,15 +296,15 @@ tokenizer.model_max_length 512 ``` -This value is derived from the *tokenizer_config.json* file associated with a checkpoint; in this case we can see that the context size is 512 tokens, just like with BERT. +Această valoare este derivată din fișierul *tokenizer_config.json* asociat cu un checkpoint; în acest caz putem vedea că dimensiunea contextului este de 512 tokeni, la fel ca în cazul BERT. -✏️ **Try it out!** Some Transformer models, like [BigBird](https://huggingface.co/google/bigbird-roberta-base) and [Longformer](hf.co/allenai/longformer-base-4096), have a much longer context length than BERT and other early Transformer models. Instantiate the tokenizer for one of these checkpoints and verify that the `model_max_length` agrees with what's quoted on its model card. +✏️ **Încearcă!** Unele modele Transformer, precum [BigBird](https://huggingface.co/google/bigbird-roberta-base) și [Longformer](hf.co/allenai/longformer-base-4096), au o lungime de context mult mai mare decât BERT și alte modele Transformer mai vechi. Inițializați tokenizerul pentru unul dintre aceste checkpointuri și verificați dacă `model_max_length` este în concordanță cu ceea ce este menționat pe model card. -So, in order to run our experiments on GPUs like those found on Google Colab, we'll pick something a bit smaller that can fit in memory: +Prin urmare, pentru a derula experimentele pe GPU-uri precum cele de pe Google Colab, vom alege ceva mai mic care să încapă în memorie: ```python chunk_size = 128 @@ -313,14 +312,14 @@ chunk_size = 128 -Note that using a small chunk size can be detrimental in real-world scenarios, so you should use a size that corresponds to the use case you will apply your model to. +Rețineți că utilizarea unei dimensiuni mici a chunkurilor poate fi dăunător în scenariile din lumea reală, astfel încât ar trebui să utilizați o dimensiune care corespunde cazului de utilizare la care veți aplica modelul. -Now comes the fun part. To show how the concatenation works, let's take a few reviews from our tokenized training set and print out the number of tokens per review: +Acum vine partea distractivă. Pentru a arăta cum funcționează concatenarea, să luăm câteva recenzii din setul nostru de antrenare tokenizat și să imprimăm numărul de tokeni per recenzie: ```python -# Slicing produces a list of lists for each feature +# Slicingul produce o listă de liste pentru fiecare caracteristică tokenized_samples = tokenized_datasets["train"][:3] for idx, sample in enumerate(tokenized_samples["input_ids"]): @@ -333,7 +332,7 @@ for idx, sample in enumerate(tokenized_samples["input_ids"]): '>>> Review 2 length: 192' ``` -We can then concatenate all these examples with a simple dictionary comprehension, as follows: +Putem apoi concatena toate aceste exemple cu un dictionary comprehension, după cum urmează: ```python concatenated_examples = { @@ -347,7 +346,7 @@ print(f"'>>> Concatenated reviews length: {total_length}'") '>>> Concatenated reviews length: 951' ``` -Great, the total length checks out -- so now let's split the concatenated reviews into chunks of the size given by `chunk_size`. To do so, we iterate over the features in `concatenated_examples` and use a list comprehension to create slices of each feature. The result is a dictionary of chunks for each feature: +Minunat, lungimea totală se verifică - așa că acum să împărțim recenziile concatenate în chunkuri de dimensiunea dată de `chunk_size`. Pentru a face acest lucru, iterăm peste caracteristicile din `concatenated_examples` și folosim un list comprehension pentru a crea slice-uri ale fiecărei caracteristici. Rezultatul este un dicționar de chunkuri pentru fiecare caracteristică: ```python chunks = { @@ -370,34 +369,34 @@ for chunk in chunks["input_ids"]: '>>> Chunk length: 55' ``` -As you can see in this example, the last chunk will generally be smaller than the maximum chunk size. There are two main strategies for dealing with this: +După cum puteți vedea în acest exemplu, ultimul fragment va fi în general mai mic decât dimensiunea maximă a fragmentului. Există două strategii principale pentru a face față acestei situații: -* Drop the last chunk if it's smaller than `chunk_size`. -* Pad the last chunk until its length equals `chunk_size`. +* Aruncați ultimul chunk dacă este mai mic decât `chunk_size`. +* Faceți padding ultimului chunk până când lungimea sa este egală cu `chunk_size`. -We'll take the first approach here, so let's wrap all of the above logic in a single function that we can apply to our tokenized datasets: +Vom adopta prima abordare aici, așa că hai să încorporăm toată logica de mai sus într-o singură funcție pe care o putem aplica dataseturilor tokenizate: ```python def group_texts(examples): - # Concatenate all texts + # Concatenarea tuturor textelor concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} - # Compute length of concatenated texts + # Calcularea lungimii textelor concatenate total_length = len(concatenated_examples[list(examples.keys())[0]]) - # We drop the last chunk if it's smaller than chunk_size + # Renunțăm la ultimul chunk dacă este mai mic decât chunk_size total_length = (total_length // chunk_size) * chunk_size - # Split by chunks of max_len + # Împărțiți pe bucăți de max_len result = { k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)] for k, t in concatenated_examples.items() } - # Create a new labels column + # Creați o nouă coloană de labeluri result["labels"] = result["input_ids"].copy() return result ``` -Note that in the last step of `group_texts()` we create a new `labels` column which is a copy of the `input_ids` one. As we'll see shortly, that's because in masked language modeling the objective is to predict randomly masked tokens in the input batch, and by creating a `labels` column we provide the ground truth for our language model to learn from. +Observați că în ultimul pas al `group_texts()` creăm o nouă coloană `labels` care este o copie a coloanei `input_ids`. După cum vom vedea în curând, acest lucru se datorează faptului că în modelarea limbajului mascat obiectivul este de a prezice tokeni mascați aleatoriu în input batch, iar prin crearea unei coloane `labels` furnizăm adevărul de bază din care modelul nostru de limbaj poate să învețe. -Let's now apply `group_texts()` to our tokenized datasets using our trusty `Dataset.map()` function: +Să aplicăm acum funcția `group_texts()` dataseturilor tokenizate folosind funcția noastră de încredere `Dataset.map()`: ```python lm_datasets = tokenized_datasets.map(group_texts, batched=True) @@ -421,7 +420,7 @@ DatasetDict({ }) ``` -You can see that grouping and then chunking the texts has produced many more examples than our original 25,000 for the `train` and `test` splits. That's because we now have examples involving _contiguous tokens_ that span across multiple examples from the original corpus. You can see this explicitly by looking for the special `[SEP]` and `[CLS]` tokens in one of the chunks: +Puteți vedea că gruparea și apoi fragmentarea textelor a produs mult mai multe exemple decât cele 25.000 inițiale pentru spliturile `train` și `test`. Acest lucru se datorează faptului că acum avem exemple care implică _contigous tokens_ care se întind pe mai multe exemple din corpusul original. Puteți vedea acest lucru în mod explicit căutând tokenii speciali `[SEP]` și `[CLS]` într-unul dintre chunkuri: ```python tokenizer.decode(lm_datasets["train"][1]["input_ids"]) @@ -431,7 +430,7 @@ tokenizer.decode(lm_datasets["train"][1]["input_ids"]) ".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless" ``` -In this example you can see two overlapping movie reviews, one about a high school movie and the other about homelessness. Let's also check out what the labels look like for masked language modeling: +În acest exemplu puteți vedea două recenzii de film care se suprapun, una despre un film de liceu și cealaltă despre persoanele fără adăpost. Să verificăm, de asemenea, cum arată labelurile pentru modelarea limbajului mascat: ```python out tokenizer.decode(lm_datasets["train"][1]["labels"]) @@ -441,11 +440,11 @@ tokenizer.decode(lm_datasets["train"][1]["labels"]) ".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless" ``` -As expected from our `group_texts()` function above, this looks identical to the decoded `input_ids` -- but then how can our model possibly learn anything? We're missing a key step: inserting `[MASK]` tokens at random positions in the inputs! Let's see how we can do this on the fly during fine-tuning using a special data collator. +Așa cum era de așteptat de la funcția noastră `group_texts()` de mai sus, acest lucru pare identic cu `input_ids` decodificat - dar atunci cum poate modelul nostru să învețe ceva? Ne lipsește un pas cheie: inserarea tokenilor `[MASK]` în poziții aleatorii în inputuri! Să vedem cum putem face acest lucru din mers, în timpul fine-tuningului, folosind un data collator special. -## Fine-tuning DistilBERT with the `Trainer` API[[fine-tuning-distilbert-with-the-trainer-api]] +## Fine-tuningul asupra DistilBERT cu API-ul `Trainer`[[fine-tuning-distilbert-with-the-trainer-api]] -Fine-tuning a masked language model is almost identical to fine-tuning a sequence classification model, like we did in [Chapter 3](/course/chapter3). The only difference is that we need a special data collator that can randomly mask some of the tokens in each batch of texts. Fortunately, 🤗 Transformers comes prepared with a dedicated `DataCollatorForLanguageModeling` for just this task. We just have to pass it the tokenizer and an `mlm_probability` argument that specifies what fraction of the tokens to mask. We'll pick 15%, which is the amount used for BERT and a common choice in the literature: +Fine-tuningul unui model lingvistic mascat este aproape identic cu fine-tuningul a unui model de clasificare a secvențelor, așa cum am făcut în [Capitolul 3](/course/chapter3). Singura diferență este că avem nevoie de un data collator care poate masca aleatoriu o parte dintre tokeni din fiecare batch de texte. Din fericire, 🤗 Transformers vine pregătit cu un `DataCollatorForLanguageModeling` dedicat tocmai pentru această sarcină. Trebuie doar să îi transmitem tokenizerul și un argument `mlm_probability` care specifică ce fracțiune din tokeni trebuie mascată. Vom alege 15%, care este cantitatea utilizată pentru BERT și o alegere comună în literatura de specialitate: ```python from transformers import DataCollatorForLanguageModeling @@ -453,7 +452,7 @@ from transformers import DataCollatorForLanguageModeling data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) ``` -To see how the random masking works, let's feed a few examples to the data collator. Since it expects a list of `dict`s, where each `dict` represents a single chunk of contiguous text, we first iterate over the dataset before feeding the batch to the collator. We remove the `"word_ids"` key for this data collator as it does not expect it: +Pentru a vedea cum funcționează mascarea aleatorie, să introducem câteva exemple în data collator. Deoarece se așteaptă la o listă de "dict"-uri, în care fiecare "dict" reprezintă un singur chunk de text continuu, mai întâi iterăm peste dataset înainte de a trimite batchul către data collator. Eliminăm cheia `"word_ids"` pentru acest data collator, deoarece acesta nu o așteaptă: ```python samples = [lm_datasets["train"][i] for i in range(2)] @@ -470,21 +469,21 @@ for chunk in data_collator(samples)["input_ids"]: '>>> .... at.. [MASK]... [MASK]... high. a classic line plucked inspector : i\'[MASK] here to [MASK] one of your [MASK]. student : welcome to bromwell [MASK]. i expect that many adults of my age think that [MASK]mwell [MASK] is [MASK] fetched. what a pity that it isn\'t! [SEP] [CLS] [MASK]ness ( or [MASK]lessness as george 宇in stated )公 been an issue for years but never [MASK] plan to help those on the street that were once considered human [MASK] did everything from going to school, [MASK], [MASK] vote for the matter. most people think [MASK] the homeless' ``` -Nice, it worked! We can see that the `[MASK]` token has been randomly inserted at various locations in our text. These will be the tokens which our model will have to predict during training -- and the beauty of the data collator is that it will randomize the `[MASK]` insertion with every batch! +Frumos, a funcționat! Putem vedea că tokenul `[MASK]` a fost inserat aleatoriu în diferite locuri din textul nostru. Acestea vor fi tokenii pe care modelul nostru va trebui să le prezică în timpul antrenamentului - iar frumusețea data collatorului este că va introduce aleatoriu tokenul `[MASK]` cu fiecare batch! -✏️ **Try it out!** Run the code snippet above several times to see the random masking happen in front of your very eyes! Also replace the `tokenizer.decode()` method with `tokenizer.convert_ids_to_tokens()` to see that sometimes a single token from a given word is masked, and not the others. +✏️ **Încercați!** Rulați fragmentul de cod de mai sus de mai multe ori pentru a vedea cum se întâmplă mascarea aleatorie în fața ochilor voștri! De asemenea, înlocuiți metoda `tokenizer.decode()` cu `tokenizer.convert_ids_to_tokens()` pentru a vedea că uneori un singur token dintr-un cuvânt dat este mascat, și nu celelalte. {#if fw === 'pt'} -One side effect of random masking is that our evaluation metrics will not be deterministic when using the `Trainer`, since we use the same data collator for the training and test sets. We'll see later, when we look at fine-tuning with 🤗 Accelerate, how we can use the flexibility of a custom evaluation loop to freeze the randomness. +Un efect secundar al mascării aleatorii este faptul că metricile noastre de evaluare nu vor fi deterministe atunci când folosim `Trainer`, deoarece folosim același data collator pentru seturile de antrenare și testare. Vom vedea mai târziu, când ne vom uita la aplicarea fine-tuningului cu 🤗 Accelerate, cum putem folosi flexibilitatea unei bucle de evaluare personalizate pentru a îngheța caracterul aleatoriu. {/if} -When training models for masked language modeling, one technique that can be used is to mask whole words together, not just individual tokens. This approach is called _whole word masking_. If we want to use whole word masking, we will need to build a data collator ourselves. A data collator is just a function that takes a list of samples and converts them into a batch, so let's do this now! We'll use the word IDs computed earlier to make a map between word indices and the corresponding tokens, then randomly decide which words to mask and apply that mask on the inputs. Note that the labels are all `-100` except for the ones corresponding to mask words. +La antrenarea modelelor pentru modelarea limbajului mascat, o tehnică care poate fi utilizată este mascarea cuvintelor întregi împreună, nu doar a tokenilor individuali. Această abordare se numește _whole word masking_. Dacă dorim să utilizăm mascarea întregului cuvânt, va trebui să construim noi înșine un data collator. Un data collator este doar o funcție care preia o listă de sampleuri și le convertește într-un batch, așa că hai să facem asta acum! Vom utiliza ID-urile cuvintelor calculate mai devreme pentru a realiza o hartă între indicii cuvintelor și tokenii corespunzători, apoi vom decide aleatoriu ce cuvinte să mascăm și vom aplica masca respectivă asupra inputurilor. Rețineți că labelurile sunt toate `-100`, cu excepția celor care corespund cuvintelor mascate. {#if fw === 'pt'} @@ -570,7 +569,7 @@ def whole_word_masking_data_collator(features): {/if} -Next, we can try it on the same samples as before: +În continuare, îl putem încerca pe aceleași sampleuri ca înainte: ```py samples = [lm_datasets["train"][i] for i in range(2)] @@ -588,11 +587,11 @@ for chunk in batch["input_ids"]: -✏️ **Try it out!** Run the code snippet above several times to see the random masking happen in front of your very eyes! Also replace the `tokenizer.decode()` method with `tokenizer.convert_ids_to_tokens()` to see that the tokens from a given word are always masked together. +✏️ **Încercați!** Rulați fragmentul de cod de mai sus de mai multe ori pentru a vedea cum se întâmplă mascarea aleatorie în fața ochilor voștri! De asemenea, înlocuiți metoda `tokenizer.decode()` cu `tokenizer.convert_ids_to_tokens()` pentru a vedea că tokenii dintr-un cuvânt dat sunt întotdeauna mascați împreună. -Now that we have two data collators, the rest of the fine-tuning steps are standard. Training can take a while on Google Colab if you're not lucky enough to score a mythical P100 GPU 😭, so we'll first downsample the size of the training set to a few thousand examples. Don't worry, we'll still get a pretty decent language model! A quick way to downsample a dataset in 🤗 Datasets is via the `Dataset.train_test_split()` function that we saw in [Chapter 5](/course/chapter5): +Acum, că avem două data collators, restul pașilor fine-tuning sunt standard. Pregătirea poate dura ceva timp pe Google Colab dacă nu sunteți suficient de norocos să obțineți un GPU P100 mitic 😭, așa că vom reduce mai întâi dimensiunea setului de antrenare la câteva mii de exemple. Nu vă faceți griji, vom obține în continuare un model lingvistic destul de decent! O modalitate rapidă de a reduce sampleurile unui dataset în 🤗 Datasets este prin intermediul funcției `Dataset.train_test_split()` pe care am văzut-o în [Capitolul 5](/course/chapter5): ```python train_size = 10_000 @@ -617,7 +616,7 @@ DatasetDict({ }) ``` -This has automatically created new `train` and `test` splits, with the training set size set to 10,000 examples and the validation set to 10% of that -- feel free to increase this if you have a beefy GPU! The next thing we need to do is log in to the Hugging Face Hub. If you're running this code in a notebook, you can do so with the following utility function: +Acest lucru a creat în mod automat noi splituri de `train` și `test`, cu dimensiunea setului de antrenare setată la 10.000 de exemple și a setului de validare la 10% din aceasta - nu ezitați să măriți această valoare dacă aveți un GPU puternic! Următorul lucru pe care trebuie să îl facem este să ne conectăm la Hugging Face Hub. Dacă executați acest cod într-un notebook, puteți face acest lucru cu următoarea funcție utilitară: ```python from huggingface_hub import notebook_login @@ -625,7 +624,7 @@ from huggingface_hub import notebook_login notebook_login() ``` -which will display a widget where you can enter your credentials. Alternatively, you can run: +care va afișa un widget în care vă puteți introduce credențialele. Alternativ, puteți rula: ``` huggingface-cli login @@ -635,7 +634,7 @@ in your favorite terminal and log in there. {#if fw === 'tf'} -Once we're logged in, we can create our `tf.data` datasets. To do so, we'll use the `prepare_tf_dataset()` method, which uses our model to automatically infer which columns should go into the dataset. If you want to control exactly which columns to use, you can use the `Dataset.to_tf_dataset()` method instead. To keep things simple, we'll just use the standard data collator here, but you can also try the whole word masking collator and compare the results as an exercise: +Odată ce ne-am conectat, putem crea dataseturile `tf.data`. Pentru a face acest lucru, vom utiliza metoda `prepare_tf_dataset()`, care utilizează modelul nostru pentru a deduce automat ce coloane ar trebui să intre în dataset. Dacă doriți să controlați exact ce coloane să utilizați, puteți folosi în schimb metoda `Dataset.to_tf_dataset()`. Pentru a simplifica lucrurile, vom utiliza aici doar data collatorul standard, dar puteți încerca și whole word masking collator și puteți compara rezultatele ca un exercițiu: ```python tf_train_dataset = model.prepare_tf_dataset( @@ -653,9 +652,9 @@ tf_eval_dataset = model.prepare_tf_dataset( ) ``` -Next, we set up our training hyperparameters and compile our model. We use the `create_optimizer()` function from the 🤗 Transformers library, which gives us an `AdamW` optimizer with linear learning rate decay. We also use the model's built-in loss, which is the default when no loss is specified as an argument to `compile()`, and we set the training precision to `"mixed_float16"`. Note that if you're using a Colab GPU or other GPU that does not have accelerated float16 support, you should probably comment out that line. +În continuare, setăm hiperparametrii de antrenare și compilăm modelul nostru. Utilizăm funcția `create_optimizer()` din biblioteca 🤗 Transformers, care ne oferă un optimizator `AdamW` cu o scădere liniară a ratei de învățare. Utilizăm, de asemenea, pierderea încorporată în model, care este cea implicită atunci când nu este specificată nicio pierdere ca argument pentru `compile()`, și setăm precizia de antrenare la `"mixed_float16"`. Rețineți că, dacă utilizați un GPU Colab sau alt GPU care nu are suport accelerat pentru float16, ar trebui probabil să comentați această linie. -In addition, we set up a `PushToHubCallback` that will save the model to the Hub after each epoch. You can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, to push the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`. By default, the repository used will be in your namespace and named after the output directory you set, so in our case it will be `"lewtun/distilbert-finetuned-imdb"`. +În plus, am configurat un `PushToHubCallback` care va salva modelul în Hub după fiecare epocă. Puteți specifica numele repositoriului către care doriți să faceți push cu argumentul `hub_model_id` (în special, va trebui să utilizați acest argument pentru a face push către o organizație). De exemplu, pentru a trimite modelul către organizația [`huggingface-course`](https://huggingface.co/huggingface-course), am adăugat `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`. În mod implicit, repositoriul utilizat va fi în namespaceul vostru și numit după output directory-ul pe care l-ați stabilit, deci în cazul nostru va fi `"lewtun/distilbert-finetuned-imdb"`. ```python from transformers import create_optimizer @@ -680,11 +679,11 @@ callback = PushToHubCallback( ) ``` -We're now ready to run `model.fit()` -- but before doing so let's briefly look at _perplexity_, which is a common metric to evaluate the performance of language models. +Acum suntem gata să executăm `model.fit()` - dar înainte de a face acest lucru, să ne uităm pe scurt la _perplexitate_, care este o metrică comună pentru a evalua performanța modelelor de limbaj. {:else} -Once we're logged in, we can specify the arguments for the `Trainer`: +Odată ce suntem conectați, putem specifica argumentele pentru `Trainer`: ```python from transformers import TrainingArguments @@ -708,11 +707,11 @@ training_args = TrainingArguments( ) ``` -Here we tweaked a few of the default options, including `logging_steps` to ensure we track the training loss with each epoch. We've also used `fp16=True` to enable mixed-precision training, which gives us another boost in speed. By default, the `Trainer` will remove any columns that are not part of the model's `forward()` method. This means that if you're using the whole word masking collator, you'll also need to set `remove_unused_columns=False` to ensure we don't lose the `word_ids` column during training. +Aici am modificat câteva dintre opțiunile implicite, inclusiv `logging_steps` pentru a ne asigura că urmărim pierderea de antrenare cu fiecare epocă. De asemenea, am folosit `fp16=True` pentru a activa antrenarea cu precizie mixtă, ceea ce ne oferă un alt impuls vitezei. În mod implicit, `Trainer` va elimina toate coloanele care nu fac parte din metoda `forward()` a modelului. Aceasta înseamnă că, dacă utilizați whole word masking collator, va trebui să setați și `remove_unused_columns=False` pentru a vă asigura că nu pierdem coloana `word_ids` în timpul antrenamentului. -Note that you can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` to `TrainingArguments`. By default, the repository used will be in your namespace and named after the output directory you set, so in our case it will be `"lewtun/distilbert-finetuned-imdb"`. +Rețineți că puteți specifica numele repositoriului către care doriți să faceți push cu argumentul `hub_model_id` (în special, va trebui să utilizați acest argument pentru a face push către o organizație). De exemplu, atunci când am făcut push modelului către organizația [`huggingface-course`](https://huggingface.co/huggingface-course), am adăugat `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` la `TrainingArguments`. În mod implicit, repositoriul utilizat va fi în namespaceul vostru și denumit după output directory-ul pe care l-ați stabilit, deci în cazul nostru va fi `"lewtun/distilbert-finetuned-imdb"`. -We now have all the ingredients to instantiate the `Trainer`. Here we just use the standard `data_collator`, but you can try the whole word masking collator and compare the results as an exercise: +Acum avem toate ingredientele pentru inițializarea `Trainer`. Aici folosim doar `data_collator` standard, dar puteți încerca whole word masking collator și să comparați rezultatele ca un exercițiu: ```python from transformers import Trainer @@ -727,19 +726,19 @@ trainer = Trainer( ) ``` -We're now ready to run `trainer.train()` -- but before doing so let's briefly look at _perplexity_, which is a common metric to evaluate the performance of language models. +Acum suntem gata să rulăm `trainer.train()` - dar înainte de a face acest lucru, să analizăm pe scurt _perplexitatea_, care este o metrică comună de evaluare a performanței modelelor de limbaj. {/if} -### Perplexity for language models[[perplexity-for-language-models]] +### Perplexity pentru language models[[perplexity-for-language-models]] -Unlike other tasks like text classification or question answering where we're given a labeled corpus to train on, with language modeling we don't have any explicit labels. So how do we determine what makes a good language model? Like with the autocorrect feature in your phone, a good language model is one that assigns high probabilities to sentences that are grammatically correct, and low probabilities to nonsense sentences. To give you a better idea of what this looks like, you can find whole sets of "autocorrect fails" online, where the model in a person's phone has produced some rather funny (and often inappropriate) completions! +Spre deosebire de alte sarcini, cum ar fi clasificarea textului sau răspunderea la întrebări, unde ni se oferă un corpus labeled pe care să antrenăm, cu modelarea limbajului nu avem labeluri explicite. Așadar, cum determinăm ce face un model lingvistic bun? La fel ca în cazul funcției de autocorectare din telefon, un model lingvistic bun este unul care atribuie probabilități ridicate propozițiilor corecte din punct de vedere gramatical și probabilități scăzute propozițiilor fără sens. Pentru a vă face o idee mai bună despre cum arată acest lucru, puteți găsi online seturi întregi de "autocorrect fails", în care modelul din telefonul unei persoane a produs niște completări destul de amuzante (și adesea nepotrivite)! {#if fw === 'pt'} -Assuming our test set consists mostly of sentences that are grammatically correct, then one way to measure the quality of our language model is to calculate the probabilities it assigns to the next word in all the sentences of the test set. High probabilities indicates that the model is not "surprised" or "perplexed" by the unseen examples, and suggests it has learned the basic patterns of grammar in the language. There are various mathematical definitions of perplexity, but the one we'll use defines it as the exponential of the cross-entropy loss. Thus, we can calculate the perplexity of our pretrained model by using the `Trainer.evaluate()` function to compute the cross-entropy loss on the test set and then taking the exponential of the result: +Presupunând că setul nostru de testare constă în cea mai mare parte din propoziții corecte din punct de vedere gramatical, atunci o modalitate de a măsura calitatea modelului nostru lingvistic este de a calcula probabilitățile pe care le atribuie următorului cuvânt în toate propozițiile din setul de testare. Probabilitatea ridicată indică faptul că modelul nu este "surprins" sau "perplex" de exemplele nevăzute și sugerează că a învățat tiparele gramaticale de bază ale limbii. Există diverse definiții matematice ale perplexității, dar cea pe care o vom utiliza o definește ca the exponential of the cross-entropy loss. Astfel, putem calcula perplexitatea modelului nostru preantrenat utilizând funcția `Trainer.evaluate()` pentru a calcula pierderea de cross-entropy pe setul de testare și apoi luând exponențiala rezultatului: ```python import math @@ -750,7 +749,7 @@ print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") {:else} -Assuming our test set consists mostly of sentences that are grammatically correct, then one way to measure the quality of our language model is to calculate the probabilities it assigns to the next word in all the sentences of the test set. High probabilities indicates that the model indicates that the model is not "surprised" or "perplexed" by the unseen examples, and suggests it has learned the basic patterns of grammar in the language. There are various mathematical definitions of perplexity, but the one we'll use defines it as the exponential of the cross-entropy loss. Thus, we can calculate the perplexity of our pretrained model by using the `model.evaluate()` method to compute the cross-entropy loss on the test set and then taking the exponential of the result: +Presupunând că setul nostru de testare constă în cea mai mare parte din propoziții corecte din punct de vedere gramatical, atunci o modalitate de a măsura calitatea modelului nostru lingvistic este de a calcula probabilitățile pe care le atribuie următorului cuvânt în toate propozițiile din setul de testare. Probabilitățile ridicate indică faptul că modelul indică faptul că modelul nu este "surprins" sau "perplex" de exemplele nevăzute și sugerează că a învățat modelele de bază ale gramaticii limbii. Există diverse definiții matematice ale perplexității, dar cea pe care o vom folosi o definește ca the exponential of the cross-entropy loss. Astfel, putem calcula perplexitatea modelului nostru preantrenat folosind metoda `model.evaluate()` pentru a calcula pierderea de entropie încrucișată pe setul de testare și apoi luând exponențiala rezultatului: ```python import math @@ -765,7 +764,7 @@ print(f"Perplexity: {math.exp(eval_loss):.2f}") >>> Perplexity: 21.75 ``` -A lower perplexity score means a better language model, and we can see here that our starting model has a somewhat large value. Let's see if we can lower it by fine-tuning! To do that, we first run the training loop: +Un scor de perplexitate mai mic înseamnă un model lingvistic mai bun, iar aici putem vedea că modelul nostru inițial are o valoare oarecum mare. Să vedem dacă o putem reduce prin fine-tuning! Pentru a face acest lucru, vom rula mai întâi bucla de antrenare: {#if fw === 'pt'} @@ -781,7 +780,7 @@ model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback {/if} -and then compute the resulting perplexity on the test set as before: +și apoi calculați perplexitatea rezultată pe setul de testare ca înainte: {#if fw === 'pt'} @@ -803,11 +802,11 @@ print(f"Perplexity: {math.exp(eval_loss):.2f}") >>> Perplexity: 11.32 ``` -Nice -- this is quite a reduction in perplexity, which tells us the model has learned something about the domain of movie reviews! +Grozav - aceasta este o reducere destul de mare a perplexității, ceea ce ne spune că modelul a învățat ceva despre domeniul recenziilor de filme! {#if fw === 'pt'} -Once training is finished, we can push the model card with the training information to the Hub (the checkpoints are saved during training itself): +Odată ce antrenarea este finalizată, putem trimite cardul modelului cu informațiile de antrenare către Hub (checkpointurile sunt salvate în timpul antrenare): ```python trainer.push_to_hub() @@ -817,19 +816,19 @@ trainer.push_to_hub() -✏️ **Your turn!** Run the training above after changing the data collator to the whole word masking collator. Do you get better results? +✏️ **Rândul tău!** Rulați antrenamentul de mai sus după schimbarea data collatorului cu whole word masking collator. Obțineți rezultate mai bune? {#if fw === 'pt'} -In our use case we didn't need to do anything special with the training loop, but in some cases you might need to implement some custom logic. For these applications, you can use 🤗 Accelerate -- let's take a look! +În cazul nostru de utilizare, nu a fost nevoie să facem nimic special cu bucla de antrenare, dar în unele cazuri s-ar putea să fie nevoie să implementați o logică personalizată. Pentru aceste aplicații, puteți utiliza 🤗 Accelerate -- să aruncăm o privire! -## Fine-tuning DistilBERT with 🤗 Accelerate[[fine-tuning-distilbert-with-accelerate]] +## Fine-tuningul DistilBERT cu 🤗 Accelerate[[fine-tuning-distilbert-with-accelerate]] -As we saw with the `Trainer`, fine-tuning a masked language model is very similar to the text classification example from [Chapter 3](/course/chapter3). In fact, the only subtlety is the use of a special data collator, and we've already covered that earlier in this section! +Așa cum am văzut cu `Trainer`, fine-tuningul unui model de limbaj mascat este foarte asemănător cu exemplul de clasificare a textului din [Capitolul 3](/course/chapter3). De fapt, singura subtilitate este utilizarea unui data collator special, pe care l-am abordat mai devreme în această secțiune! -However, we saw that `DataCollatorForLanguageModeling` also applies random masking with each evaluation, so we'll see some fluctuations in our perplexity scores with each training run. One way to eliminate this source of randomness is to apply the masking _once_ on the whole test set, and then use the default data collator in 🤗 Transformers to collect the batches during evaluation. To see how this works, let's implement a simple function that applies the masking on a batch, similar to our first encounter with `DataCollatorForLanguageModeling`: +Cu toate acestea, am văzut că `DataCollatorForLanguageModeling` aplică, de asemenea, o mascare aleatorie cu fiecare evaluare, astfel încât vom vedea unele fluctuații în scorurile noastre de perplexitate cu fiecare rulare de antrenament. O modalitate de a elimina această sursă de dezordine este de a aplica mascarea _o singură dată_ pe întregul set de teste și apoi de a utiliza data collatorul implicit din 🤗 Transformers pentru a colecta batch-urile în timpul evaluării. Pentru a vedea cum funcționează acest lucru, să implementăm o funcție simplă care aplică mascarea pe un batch, similară cu prima noastră întâlnire cu `DataCollatorForLanguageModeling`: ```python def insert_random_mask(batch): @@ -839,7 +838,7 @@ def insert_random_mask(batch): return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()} ``` -Next, we'll apply this function to our test set and drop the unmasked columns so we can replace them with the masked ones. You can use whole word masking by replacing the `data_collator` above with the appropriate one, in which case you should remove the first line here: +În continuare, vom aplica această funcție setului nostru de testare și vom elimina coloanele nemascate pentru a le putea înlocui cu cele mascate. Puteți utiliza whole word masking prin înlocuirea `data_collator` de mai sus cu cel corespunzător, caz în care trebuie să eliminați prima linie de aici: ```py downsampled_dataset = downsampled_dataset.remove_columns(["word_ids"]) @@ -857,7 +856,7 @@ eval_dataset = eval_dataset.rename_columns( ) ``` -We can then set up the dataloaders as usual, but we'll use the `default_data_collator` from 🤗 Transformers for the evaluation set: +Putem configura apoi dataloaderele ca de obicei, dar vom folosi `default_data_collator` de la 🤗 Transformers pentru setul de evaluare: ```python from torch.utils.data import DataLoader @@ -875,13 +874,13 @@ eval_dataloader = DataLoader( ) ``` -Form here, we follow the standard steps with 🤗 Accelerate. The first order of business is to load a fresh version of the pretrained model: +De aici, vom urma pașii standard cu 🤗 Accelerate. În primul rând, se încarcă o versiune nouă a modelului antrenat: ``` model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) ``` -Then we need to specify the optimizer; we'll use the standard `AdamW`: +Apoi trebuie să specificăm optimizatorul; vom folosi standardul `AdamW`: ```python from torch.optim import AdamW @@ -889,7 +888,7 @@ from torch.optim import AdamW optimizer = AdamW(model.parameters(), lr=5e-5) ``` -With these objects, we can now prepare everything for training with the `Accelerator` object: +Cu aceste obiecte, acum putem pregăti totul pentru antrenare cu obiectul `Accelerator`: ```python from accelerate import Accelerator @@ -900,7 +899,7 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( ) ``` -Now that our model, optimizer, and dataloaders are configured, we can specify the learning rate scheduler as follows: +Acum că modelul, optimizatorul și dataloaderul sunt configurate, putem specifica learning rate schedulerul cum urmează: ```python from transformers import get_scheduler @@ -917,7 +916,7 @@ lr_scheduler = get_scheduler( ) ``` -There is just one last thing to do before training: create a model repository on the Hugging Face Hub! We can use the 🤗 Hub library to first generate the full name of our repo: +Mai este un singur lucru de făcut înainte de antrenare: creați un repositoriu de modele pe Hugging Face Hub! Putem utiliza biblioteca 🤗 Hub pentru a genera mai întâi numele complet al repositoriul nostru: ```python from huggingface_hub import get_full_repo_name @@ -931,7 +930,7 @@ repo_name 'lewtun/distilbert-base-uncased-finetuned-imdb-accelerate' ``` -then create and clone the repository using the `Repository` class from 🤗 Hub: +apoi creați și clonați repositoriul folosind clasa `Repository` din 🤗 Hub: ```python from huggingface_hub import Repository @@ -940,7 +939,7 @@ output_dir = model_name repo = Repository(output_dir, clone_from=repo_name) ``` -With that done, it's just a simple matter of writing out the full training and evaluation loop: +Odată făcut acest lucru, este doar o chestiune simplă de scriere a ciclului complet de antrenare și evaluare: ```python from tqdm.auto import tqdm @@ -998,13 +997,13 @@ for epoch in range(num_train_epochs): >>> Epoch 2: Perplexity: 10.729503505340409 ``` -Cool, we've been able to evaluate perplexity with each epoch and ensure that multiple training runs are reproducible! +Mișto, am reușit să evaluăm perplexitatea cu fiecare epocă și să ne asigurăm că mai multe runde de antrenament sunt reproductibile! {/if} -## Using our fine-tuned model[[using-our-fine-tuned-model]] +## Utilizarea modelului fine-tuned[[using-our-fine-tuned-model]] -You can interact with your fine-tuned model either by using its widget on the Hub or locally with the `pipeline` from 🤗 Transformers. Let's use the latter to download our model using the `fill-mask` pipeline: +Puteți interacționa cu modelul vostru fine-tuned utilizând widgetul său de pe Hub, fie local cu `pipeline` din 🤗 Transformers. Să folosim aceasta din urmă pentru a descărca modelul nostru folosind pipelineuul `fill-mask`: ```python from transformers import pipeline @@ -1014,7 +1013,7 @@ mask_filler = pipeline( ) ``` -We can then feed the pipeline our sample text of "This is a great [MASK]" and see what the top 5 predictions are: +Putem apoi să alimentăm pipelineul cu exemplul nostru de text "This is a hrea [MASK]" și să vedem care sunt primele 5 predicții: ```python preds = mask_filler(text) @@ -1031,14 +1030,14 @@ for pred in preds: '>>> this is a great character.' ``` -Neat -- our model has clearly adapted its weights to predict words that are more strongly associated with movies! +Frumos - modelul nostru și-a adaptat în mod clar weighturile pentru a prezice cuvintele care sunt asociate mai puternic cu filmele! -This wraps up our first experiment with training a language model. In [section 6](/course/en/chapter7/6) you'll learn how to train an auto-regressive model like GPT-2 from scratch; head over there if you'd like to see how you can pretrain your very own Transformer model! +Acest lucru încheie primul nostru experiment de antrenare a unui model lingvistic. În [secțiunea 6](/course/ro/chapter7/6) veți învăța cum să antrenați de la zero un model auto-regressive precum GPT-2; mergeți acolo dacă doriți să vedeți cum vă puteți preantrena propriul model Transformer! -✏️ **Try it out!** To quantify the benefits of domain adaptation, fine-tune a classifier on the IMDb labels for both the pretrained and fine-tuned DistilBERT checkpoints. If you need a refresher on text classification, check out [Chapter 3](/course/chapter3). +✏️ **Încercați!** Pentru a cuantifica beneficiile adaptării domeniului, faceți fine-tune unui clasificator pe labelurile IMDb atât pentru checkpointurile DistilBERT preantrenate, cât și pentru cele fine-tuned. Dacă aveți nevoie de o recapitulare a clasificării textului, consultați [Capitolul 3](/course/chapter3). diff --git a/chapters/ro/chapter7/4.mdx b/chapters/ro/chapter7/4.mdx index cdae18d5b..ae1098e58 100644 --- a/chapters/ro/chapter7/4.mdx +++ b/chapters/ro/chapter7/4.mdx @@ -1,6 +1,6 @@ -# Translation[[translation]] +# Traducere[[translation]] {#if fw === 'pt'} @@ -22,35 +22,35 @@ {/if} -Let's now dive into translation. This is another [sequence-to-sequence task](/course/chapter1/7), which means it's a problem that can be formulated as going from one sequence to another. In that sense the problem is pretty close to [summarization](/course/chapter7/6), and you could adapt what we will see here to other sequence-to-sequence problems such as: +Să trecem acum la traducere. Aceasta este o altă [sarcină de sequence-to-sequence](/course/chapter1/7), ceea ce înseamnă că este o problemă care poate fi formulată ca trecerea de la o secvență la alta. În acest sens, problema este destul de apropiată de [sumarizare](/course/chapter7/6) și ați putea adapta ceea ce vom vedea aici la alte probleme de la sequence-to-sequence, cum ar fi: -- **Style transfer**: Creating a model that *translates* texts written in a certain style to another (e.g., formal to casual or Shakespearean English to modern English) -- **Generative question answering**: Creating a model that generates answers to questions, given a context +- **Style transfer**: Crearea unui model care *traduce* texte scrise într-un anumit stil în altul (de exemplu, din formal în casual sau din engleza Shakespeariană în engleza modernă) +- **Generative question answering**: Crearea unui model care generează răspunsuri la întrebări, fiind dat un context -If you have a big enough corpus of texts in two (or more) languages, you can train a new translation model from scratch like we will in the section on [causal language modeling](/course/chapter7/6). It will be faster, however, to fine-tune an existing translation model, be it a multilingual one like mT5 or mBART that you want to fine-tune to a specific language pair, or even a model specialized for translation from one language to another that you want to fine-tune to your specific corpus. +Dacă aveți un corpus suficient de mare de texte în două (sau mai multe) limbi, puteți antrena un nou model de traducere de la zero, așa cum vom face în secțiunea despre [causal language modeling] (/course/chapter7/6). Cu toate acestea, va fi mai rapid să faceți fine-tune unui model de traducere existent, fie că este vorba de un model multilingv precum mT5 sau mBART, pe care doriți să îi faceți fine-tune pentru o anumită pereche de limbi, sau chiar un model specializat pentru traducerea dintr-o limbă în alta, pe care doriți să îl perfecționați pentru corpusul vostru specific. -In this section, we will fine-tune a Marian model pretrained to translate from English to French (since a lot of Hugging Face employees speak both those languages) on the [KDE4 dataset](https://huggingface.co/datasets/kde4), which is a dataset of localized files for the [KDE apps](https://apps.kde.org/). The model we will use has been pretrained on a large corpus of French and English texts taken from the [Opus dataset](https://opus.nlpl.eu/), which actually contains the KDE4 dataset. But even if the pretrained model we use has seen that data during its pretraining, we will see that we can get a better version of it after fine-tuning. +În această secțiune, vom pune face fine-tune unui model Marian preantrenat pentru a traduce din engleză în franceză (deoarece mulți dintre angajații Hugging Face vorbesc ambele limbi) pe [datasetul KDE4](https://huggingface.co/datasets/kde4), care este un set de fișiere localizate pentru [KDE apps](https://apps.kde.org/). Modelul pe care îl vom utiliza a fost preantrenat pe un corpus mare de texte în franceză și engleză preluate din [datasetul Opus](https://opus.nlpl.eu/), care conține de fapt datasetul KDE4. Dar chiar dacă modelul preantrenat pe care îl folosim a văzut aceste date în timpul preantrenării sale, vom vedea că putem obține o versiune mai bună a acestuia după fine-tuning. -Once we're finished, we will have a model able to make predictions like this one: +Odată ce am terminat, vom avea un model capabil să facă predicții ca acesta: -One-hot encoded labels for question answering. - +One-hot encoded labels pentru răspunderea la întrebări. + -As in the previous sections, you can find the actual model that we'll train and upload to the Hub using the code below and double-check its predictions [here](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.). +Ca și în secțiunile anterioare, puteți găsi modelul pe care îl vom antrena și încărca în Hub utilizând codul de mai jos și puteți verifica predicțiile acestuia [aici](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.). -## Preparing the data[[preparing-the-data]] +## Pregătirea datelor[[preparing-the-data]] -To fine-tune or train a translation model from scratch, we will need a dataset suitable for the task. As mentioned previously, we'll use the [KDE4 dataset](https://huggingface.co/datasets/kde4) in this section, but you can adapt the code to use your own data quite easily, as long as you have pairs of sentences in the two languages you want to translate from and into. Refer back to [Chapter 5](/course/chapter5) if you need a reminder of how to load your custom data in a `Dataset`. +Pentru a face fine-tune sau a antrena un model de traducere de la zero, vom avea nevoie de un dataset adecvat pentru această sarcină. După cum am menționat anterior, vom utiliza [datasetul KDE4](https://huggingface.co/datasets/kde4) în această secțiune, dar puteți adapta codul pentru a utiliza propriile date destul de ușor, atâta timp cât aveți perechi de propoziții în cele două limbi din și în care doriți să traduceți. Consultați [Capitolul 5](/course/chapter5) dacă aveți nevoie de o reamintire a modului de încărcare a datelor personalizate într-un `Dataset`. -### The KDE4 dataset[[the-kde4-dataset]] +### Datasetul KDE4[[the-kde4-dataset]] -As usual, we download our dataset using the `load_dataset()` function: +Ca de obicei, descărcăm datasetul nostru folosind funcția `load_dataset()`: ```py from datasets import load_dataset @@ -58,11 +58,11 @@ from datasets import load_dataset raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") ``` -If you want to work with a different pair of languages, you can specify them by their codes. A total of 92 languages are available for this dataset; you can see them all by expanding the language tags on its [dataset card](https://huggingface.co/datasets/kde4). +Dacă doriți să lucrați cu o pereche de limbi diferită, le puteți specifica prin codurile lor. Un total de 92 de limbi sunt disponibile pentru acest dataset; le puteți vedea pe toate prin extinderea labelurilor de limbă pe [dataset cardul acesteia](https://huggingface.co/datasets/kde4). -Language available for the KDE4 dataset. +Limba disponibilă pentru datasetul KDE4. -Let's have a look at the dataset: +Să aruncăm o privire la dataset: ```py raw_datasets @@ -77,7 +77,7 @@ DatasetDict({ }) ``` -We have 210,173 pairs of sentences, but in one single split, so we will need to create our own validation set. As we saw in [Chapter 5](/course/chapter5), a `Dataset` has a `train_test_split()` method that can help us. We'll provide a seed for reproducibility: +Avem 210 173 de perechi de propoziții, dar într-o singură împărțire, deci va trebui să ne creăm propriul set de validare. Așa cum am văzut în [Capitolul 5](/course/chapter5), un `Dataset` are o metodă `train_test_split()` care ne poate ajuta. Vom furniza un seed pentru reproductibilitate: ```py split_datasets = raw_datasets["train"].train_test_split(train_size=0.9, seed=20) @@ -97,13 +97,13 @@ DatasetDict({ }) ``` -We can rename the `"test"` key to `"validation"` like this: +Putem redenumi cheia `"test"` în `"validation"` astfel: ```py split_datasets["validation"] = split_datasets.pop("test") ``` -Now let's take a look at one element of the dataset: +Acum să aruncăm o privire la un element al datasetului: ```py split_datasets["train"][1]["translation"] @@ -114,7 +114,7 @@ split_datasets["train"][1]["translation"] 'fr': 'Par défaut, développer les fils de discussion'} ``` -We get a dictionary with two sentences in the pair of languages we requested. One particularity of this dataset full of technical computer science terms is that they are all fully translated in French. However, French engineers leave most computer science-specific words in English when they talk. Here, for instance, the word "threads" might well appear in a French sentence, especially in a technical conversation; but in this dataset it has been translated into the more correct "fils de discussion." The pretrained model we use, which has been pretrained on a larger corpus of French and English sentences, takes the easier option of leaving the word as is: +Obținem un dicționar cu două propoziții în perechea de limbi solicitate. O particularitate a acestui dataset plin de termeni tehnici din domeniul informaticii este că toate sunt traduse integral în franceză. Cu toate acestea, inginerii francezi lasă majoritatea cuvintelor specifice informaticii în engleză atunci când vorbesc. Aici, de exemplu, cuvântul "threads" ar putea foarte bine să apară într-o propoziție în limba franceză, în special într-o conversație tehnică; dar în acest dataset a fost tradus în "fils de discussion". Modelul preantrenat pe care îl folosim, care a fost preantrenat pe un corpus mai mare de propoziții în franceză și engleză, alege opțiunea mai ușoară de a lăsa cuvântul așa cum este: ```py from transformers import pipeline @@ -128,8 +128,8 @@ translator("Default to expanded threads") [{'translation_text': 'Par défaut pour les threads élargis'}] ``` -Another example of this behavior can be seen with the word "plugin," which isn't officially a French word but which most native speakers will understand and not bother to translate. -In the KDE4 dataset this word has been translated in French into the more official "module d'extension": +Un alt exemplu al acestui comportament poate fi văzut cu cuvântul "plugin", care nu este oficial un cuvânt francez, dar pe care majoritatea vorbitorilor nativi îl vor înțelege și nu se vor obosi să îl traducă. +În datasetul KDE4, acest cuvânt a fost tradus în franceză în termenul puțin mai oficial "module d'extension": ```py split_datasets["train"][172]["translation"] @@ -140,7 +140,7 @@ split_datasets["train"][172]["translation"] 'fr': "Impossible d'importer %1 en utilisant le module d'extension d'importation OFX. Ce fichier n'a pas un format correct."} ``` -Our pretrained model, however, sticks with the compact and familiar English word: +Modelul nostru preantrenat, cu toate acestea, rămâne la cuvântul englez compact și familiar: ```py translator( @@ -152,21 +152,21 @@ translator( [{'translation_text': "Impossible d'importer %1 en utilisant le plugin d'importateur OFX. Ce fichier n'est pas le bon format."}] ``` -It will be interesting to see if our fine-tuned model picks up on those particularities of the dataset (spoiler alert: it will). +Va fi interesant de văzut dacă modelul nostru fine-tuned reține aceste particularități ale datasetului(spoiler alert: va reține). -✏️ **Your turn!** Another English word that is often used in French is "email." Find the first sample in the training dataset that uses this word. How is it translated? How does the pretrained model translate the same English sentence? +✏️ **Rândul tău!** Un alt cuvânt englezesc care este adesea folosit în franceză este "email". Găsiți primul sample din datasetul de antrenare care utilizează acest cuvânt. Cum este tradus? Cum traduce modelul preantrenat aceeași propoziție în limba engleză? -### Processing the data[[processing-the-data]] +### Procesarea datelor[[processing-the-data]] -You should know the drill by now: the texts all need to be converted into sets of token IDs so the model can make sense of them. For this task, we'll need to tokenize both the inputs and the targets. Our first task is to create our `tokenizer` object. As noted earlier, we'll be using a Marian English to French pretrained model. If you are trying this code with another pair of languages, make sure to adapt the model checkpoint. The [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) organization provides more than a thousand models in multiple languages. +Ar trebui să știți deja cum stă treaba: toate textele trebuie convertite în seturi de ID-uri token, astfel încât modelul să le poată înțelege. Pentru această sarcină, va trebui să tokenizăm atât inputurile, cât și targeturile. Prima noastră sarcină este să creăm obiectul `tokenizer`. După cum am menționat mai devreme, vom utiliza un model Marian preantrenat din engleză în franceză. Dacă încercați acest cod cu o altă pereche de limbi, asigurați-vă că adaptați checkpointul modelului. Organizația [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) oferă mai mult de o mie de modele în mai multe limbi. ```python from transformers import AutoTokenizer @@ -175,17 +175,17 @@ model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="pt") ``` -You can also replace the `model_checkpoint` with any other model you prefer from the [Hub](https://huggingface.co/models), or a local folder where you've saved a pretrained model and a tokenizer. +De asemenea, puteți înlocui `model_checkpoint` cu orice alt model preferat din [Hub](https://huggingface.co/models) sau cu un folder local în care ați salvat un model preantrenat și un tokenizer. -💡 If you are using a multilingual tokenizer such as mBART, mBART-50, or M2M100, you will need to set the language codes of your inputs and targets in the tokenizer by setting `tokenizer.src_lang` and `tokenizer.tgt_lang` to the right values. +💡 Dacă utilizați un tokenizer multilingv, cum ar fi mBART, mBART-50 sau M2M100, va trebui să setați codurile de limbă ale inputurilor și targeturilor în tokenizer prin setarea `tokenizer.src_lang` și `tokenizer.tgt_lang` la valorile corecte. -The preparation of our data is pretty straightforward. There's just one thing to remember; you need to ensure that the tokenizer processes the targets in the output language (here, French). You can do this by passing the targets to the `text_targets` argument of the tokenizer's `__call__` method. +Pregătirea datelor noastre este destul de simplă. Trebuie să rețineți un singur lucru; trebuie să vă asigurați că tokenizerul procesează targeturile în limba de ieșire (aici, franceză). Puteți face acest lucru trecând targeturile la argumentul `text_targets` al metodei `__call__` a tokenizerului. -To see how this works, let's process one sample of each language in the training set: +Pentru a vedea cum funcționează acest lucru, să procesăm un sample din fiecare limbă din setul de antrenare: ```python en_sentence = split_datasets["train"][1]["translation"]["en"] @@ -199,7 +199,7 @@ inputs {'input_ids': [47591, 12, 9842, 19634, 9, 0], 'attention_mask': [1, 1, 1, 1, 1, 1], 'labels': [577, 5891, 2, 3184, 16, 2542, 5, 1710, 0]} ``` -As we can see, the output contains the input IDs associated with the English sentence, while the IDs associated with the French one are stored in the `labels` field. If you forget to indicate that you are tokenizing labels, they will be tokenized by the input tokenizer, which in the case of a Marian model is not going to go well at all: +După cum putem vedea, rezultatul conține ID-urile de input asociate cu propoziția în limba engleză, în timp ce ID-urile asociate cu cea în limba franceză sunt stocate în câmpul `labels`. Dacă uitați să indicați că tokenizați labelurile, acestea vor fi tokenizate de către tokenizerul de input, ceea ce în cazul unui model Marian nu va merge deloc bine: ```python wrong_targets = tokenizer(fr_sentence) @@ -212,9 +212,9 @@ print(tokenizer.convert_ids_to_tokens(inputs["labels"])) ['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', ''] ``` -As we can see, using the English tokenizer to preprocess a French sentence results in a lot more tokens, since the tokenizer doesn't know any French words (except those that also appear in the English language, like "discussion"). +După cum se poate observa, utilizarea tokenizerului englez pentru preprocesarea unei propoziții franceze are ca rezultat mult mai mulți tokeni, deoarece tokenizerul nu cunoaște niciun cuvânt francez (cu excepția celor care apar și în limba engleză, precum "discussion"). -Since `inputs` is a dictionary with our usual keys (input IDs, attention mask, etc.), the last step is to define the preprocessing function we will apply on the datasets: +Deoarece `inputs` este un dicționar cu cheile noastre obișnuite (ID-uri de input, attention mask etc.), ultimul pas este să definim funcția de preprocesare pe care o vom aplica dataseturilor: ```python max_length = 128 @@ -229,21 +229,21 @@ def preprocess_function(examples): return model_inputs ``` -Note that we set the same maximum length for our inputs and outputs. Since the texts we're dealing with seem pretty short, we use 128. +Rețineți că am stabilit aceeași lungime maximă pentru inputurile și outputurile noastre. Deoarece textele cu care avem de-a face par destul de scurte, vom folosi 128. -💡 If you are using a T5 model (more specifically, one of the `t5-xxx` checkpoints), the model will expect the text inputs to have a prefix indicating the task at hand, such as `translate: English to French:`. +💡 Dacă utilizați un model T5 (mai precis, unul dintre checkpointurile `t5-xxx`), modelul se va aștepta ca inputurile text să aibă un prefix care să indice sarcina în cauză, cum ar fi `translate: din engleză în franceză:`. -⚠️ We don't pay attention to the attention mask of the targets, as the model won't expect it. Instead, the labels corresponding to a padding token should be set to `-100` so they are ignored in the loss computation. This will be done by our data collator later on since we are applying dynamic padding, but if you use padding here, you should adapt the preprocessing function to set all labels that correspond to the padding token to `-100`. +⚠️ Nu acordăm atenție attention maskului a targeturilor, deoarece modelul nu se va aștepta la aceasta. În schimb, labelurile corespunzătoare unui padding token trebuie setate la `-100`, astfel încât acestea să fie ignorate în calculul pierderilor. Acest lucru va fi făcut mai târziu de data collatorul nostru, deoarece aplicăm padding dinamic, dar dacă utilizați padding aici, ar trebui să adaptați funcția de preprocesare pentru a seta toate labelurile care corespund simbolului de padding la `-100`. -We can now apply that preprocessing in one go on all the splits of our dataset: +Acum putem aplica această preprocesare dintr-o singură dată pe toate diviziunile datasetului nostru: ```py tokenized_datasets = split_datasets.map( @@ -253,15 +253,15 @@ tokenized_datasets = split_datasets.map( ) ``` -Now that the data has been preprocessed, we are ready to fine-tune our pretrained model! +Acum că datele au fost preprocesate, suntem pregătiți să facem fine-tune modelul nostru preantrenat! {#if fw === 'pt'} -## Fine-tuning the model with the `Trainer` API[[fine-tuning-the-model-with-the-trainer-api]] +## Fine-tuningul modelului cu API-ul `Trainer`[[fine-tuning-the-model-with-the-trainer-api]] -The actual code using the `Trainer` will be the same as before, with just one little change: we use a [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer) here, which is a subclass of `Trainer` that will allow us to properly deal with the evaluation, using the `generate()` method to predict outputs from the inputs. We'll dive into that in more detail when we talk about the metric computation. +Codul real care utilizează `Trainer` va fi același ca înainte, cu o singură mică schimbare: folosim aici un [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer), care este o subclasă a `Trainer` care ne va permite să ne ocupăm în mod corespunzător de evaluare, folosind metoda `generate()` pentru a prezice rezultatele din inputuri. Vom analiza acest aspect mai în detaliu atunci când vom vorbi despre calculul metricelor. -First things first, we need an actual model to fine-tune. We'll use the usual `AutoModel` API: +În primul rând, avem nevoie de un model pe care să îl aplicăm fine-tuningul. Vom utiliza API-ul obișnuit `AutoModel`: ```py from transformers import AutoModelForSeq2SeqLM @@ -271,9 +271,9 @@ model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) {:else} -## Fine-tuning the model with Keras[[fine-tuning-the-model-with-keras]] +## Fine-tuningul modelului cu Keras[[fine-tuning-the-model-with-keras]] -First things first, we need an actual model to fine-tune. We'll use the usual `AutoModel` API: +În primul rând, avem nevoie de un model pe care să îl aplicăm fine-tuningul. Vom folosi API-ul obișnuit `AutoModel`: ```py from transformers import TFAutoModelForSeq2SeqLM @@ -283,24 +283,24 @@ model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) -💡 The `Helsinki-NLP/opus-mt-en-fr` checkpoint only has PyTorch weights, so -you'll get an error if you try to load the model without using the -`from_pt=True` argument in the `from_pretrained()` method. When you specify -`from_pt=True`, the library will automatically download and convert the -PyTorch weights for you. As you can see, it is very simple to switch between -frameworks in 🤗 Transformers! +💡 Checkpointul `Helsinki-NLP/opus-mt-en-fr` are doar weighturi PyTorch, deci +veți primi o eroare dacă încercați să încărcați modelul fără a utiliza argumentul +`from_pt=True` în metoda `from_pretrained()`. Atunci când specificați +`from_pt=True`, biblioteca va descărca și va converti automat +weighturile PyTorch pentru voi. După cum puteți vedea, este foarte simplu să vă schimbați între +frameworkuri în 🤗 Transformers! {/if} -Note that this time we are using a model that was trained on a translation task and can actually be used already, so there is no warning about missing weights or newly initialized ones. +Rețineți că de data aceasta folosim un model care a fost antrenat pe o sarcină de traducere și care poate fi utilizat deja, astfel încât nu există niciun avertisment cu privire la weighturile lipsă sau la cele nou inițializate. ### Data collation[[data-collation]] -We'll need a data collator to deal with the padding for dynamic batching. We can't just use a `DataCollatorWithPadding` like in [Chapter 3](/course/chapter3) in this case, because that only pads the inputs (input IDs, attention mask, and token type IDs). Our labels should also be padded to the maximum length encountered in the labels. And, as mentioned previously, the padding value used to pad the labels should be `-100` and not the padding token of the tokenizer, to make sure those padded values are ignored in the loss computation. +Vom avea nevoie de un data collator care să se ocupe de paddingul pentru batching-ul dinamic. Nu putem folosi un `DataCollatorWithPadding` ca în [Capitolul 3](/course/chapter3) în acest caz, pentru că acesta doar face padding inputurilor (input Is, attention mask și token type IDs). Labelurile noastre ar trebui, de asemenea, să fie padded la lungimea maximă întâlnită în labeluri. Și, așa cum am menționat anterior, valoarea de padding utilizată pentru a face padding labelurilor ar trebui să fie `-100` și nu padding tokenul tokenizerului, pentru a ne asigura că aceste valori de padding sunt ignorate în calculul pierderilor. -This is all done by a [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Like the `DataCollatorWithPadding`, it takes the `tokenizer` used to preprocess the inputs, but it also takes the `model`. This is because this data collator will also be responsible for preparing the decoder input IDs, which are shifted versions of the labels with a special token at the beginning. Since this shift is done slightly differently for different architectures, the `DataCollatorForSeq2Seq` needs to know the `model` object: +Toate acestea sunt realizate de un [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Ca și `DataCollatorWithPadding`, acesta preia `tokenizer` utilizat pentru preprocesarea inputurilor, dar preia și `modelul`. Acest lucru se datorează faptului că acest data collator va fi, de asemenea, responsabil de pregătirea ID-urilor de input ale decoderului, care sunt versiuni schimbate ale labelurilor cu un token special la început. Deoarece această schimbare se face ușor diferit pentru diferite arhitecturi, `DataCollatorForSeq2Seq` trebuie să cunoască obiectul `model`: {#if fw === 'pt'} @@ -320,7 +320,7 @@ data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="t {/if} -To test this on a few samples, we just call it on a list of examples from our tokenized training set: +Pentru a testa acest lucru pe câteva sampleuri, îl apelăm doar pe o listă de sampleuri din setul nostru de antrenare tokenizat: ```py batch = data_collator([tokenized_datasets["train"][i] for i in range(1, 3)]) @@ -331,7 +331,7 @@ batch.keys() dict_keys(['attention_mask', 'input_ids', 'labels', 'decoder_input_ids']) ``` -We can check our labels have been padded to the maximum length of the batch, using `-100`: +Putem verifica dacă labelurile noastre au fost padded la lungimea maximă a batchului, folosind `-100`: ```py batch["labels"] @@ -344,7 +344,7 @@ tensor([[ 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, -100, 550, 7032, 5821, 7907, 12649, 0]]) ``` -And we can also have a look at the decoder input IDs, to see that they are shifted versions of the labels: +De asemenea, putem arunca o privire la ID-urile de input ale decoderului, pentru a vedea că acestea sunt versiuni schimbate ale labelurilor: ```py batch["decoder_input_ids"] @@ -357,7 +357,7 @@ tensor([[59513, 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, 817, 550, 7032, 5821, 7907, 12649]]) ``` -Here are the labels for the first and second elements in our dataset: +Iată labelurile pentru primul și al doilea element din datasetul nostru: ```py for i in range(1, 3): @@ -371,11 +371,11 @@ for i in range(1, 3): {#if fw === 'pt'} -We will pass this `data_collator` along to the `Seq2SeqTrainer`. Next, let's have a look at the metric. +Vom transmite acest `data_collator` către `Seq2SeqTrainer`. În continuare, să aruncăm o privire la metrice. {:else} -We can now use this `data_collator` to convert each of our datasets to a `tf.data.Dataset`, ready for training: +Acum putem folosi acest `data_collator` pentru a converti fiecare dintre dataseturile într-un `tf.data.Dataset`, gata pentru antrenare: ```python tf_train_dataset = model.prepare_tf_dataset( @@ -395,27 +395,27 @@ tf_eval_dataset = model.prepare_tf_dataset( {/if} -### Metrics[[metrics]] +### Metrice[[metrics]] {#if fw === 'pt'} -The feature that `Seq2SeqTrainer` adds to its superclass `Trainer` is the ability to use the `generate()` method during evaluation or prediction. During training, the model will use the `decoder_input_ids` with an attention mask ensuring it does not use the tokens after the token it's trying to predict, to speed up training. During inference we won't be able to use those since we won't have labels, so it's a good idea to evaluate our model with the same setup. +Caracteristica pe care `Seq2SeqTrainer` o adaugă superclasei sale `Trainer` este capacitatea de a utiliza metoda `generate()` în timpul evaluării sau predicției. În timpul antrenării, modelul va utiliza `decoder_input_ids` cu un attention mask care asigură că nu utilizează tokenii de după tokenul pe care încearcă să îl prezică, pentru a accelera antrenarea. În timpul inferenței, nu le vom putea utiliza deoarece nu vom avea labeluri, deci este o idee bună să ne evaluăm modelul cu aceeași configurație. -As we saw in [Chapter 1](/course/chapter1/6), the decoder performs inference by predicting tokens one by one -- something that's implemented behind the scenes in 🤗 Transformers by the `generate()` method. The `Seq2SeqTrainer` will let us use that method for evaluation if we set `predict_with_generate=True`. +După cum am văzut în [Capitolul 1](/course/chapter1/6), decoderul realizează inferența prin prezicerea tokenilor unul câte unul - lucru care este implementat behind the scenes în 🤗 Transformers prin metoda `generate()`. `Seq2SeqTrainer` ne va permite să folosim această metodă pentru evaluare dacă setăm `predict_with_generate=True`. {/if} -The traditional metric used for translation is the [BLEU score](https://en.wikipedia.org/wiki/BLEU), introduced in [a 2002 article](https://aclanthology.org/P02-1040.pdf) by Kishore Papineni et al. The BLEU score evaluates how close the translations are to their labels. It does not measure the intelligibility or grammatical correctness of the model's generated outputs, but uses statistical rules to ensure that all the words in the generated outputs also appear in the targets. In addition, there are rules that penalize repetitions of the same words if they are not also repeated in the targets (to avoid the model outputting sentences like `"the the the the the"`) and output sentences that are shorter than those in the targets (to avoid the model outputting sentences like `"the"`). +Metricele tradiționale utilizate pentru traducere sunt [scorul BLEU](https://en.wikipedia.org/wiki/BLEU), introdus în [un articol din 2002](https://aclanthology.org/P02-1040.pdf) de Kishore Papineni et al. Scorul BLEU evaluează cât de apropiate sunt traducerile de labelurile lor. Acesta nu măsoară inteligibilitatea sau corectitudinea gramaticală a rezultatelor generate de model, ci utilizează reguli statistice pentru a se asigura că toate cuvintele din rezultatele generate apar și în targets. În plus, există reguli care penalizează repetițiile acelorași cuvinte dacă acestea nu sunt repetate și în targets(pentru a evita ca modelul să producă propoziții de tipul `"the the the the the the"`) și să producă propoziții care sunt mai scurte decât cele din targets(pentru a evita ca modelul să producă propoziții de tipul `"the"`). -One weakness with BLEU is that it expects the text to already be tokenized, which makes it difficult to compare scores between models that use different tokenizers. So instead, the most commonly used metric for benchmarking translation models today is [SacreBLEU](https://github.com/mjpost/sacrebleu), which addresses this weakness (and others) by standardizing the tokenization step. To use this metric, we first need to install the SacreBLEU library: +Un punct slab al BLEU este că se așteaptă ca textul să fie deja tokenizat, ceea ce face dificilă compararea scorurilor între modele care utilizează tokenizere diferite. În schimb, cea mai frecvent utilizată măsură pentru evaluarea comparativă a modelelor de traducere este [SacreBLEU](https://github.com/mjpost/sacrebleu), care abordează acest punct slab (și altele) prin standardizarea etapei de tokenizare. Pentru a utiliza această metrică, trebuie mai întâi să instalăm biblioteca SacreBLEU: ```py !pip install sacrebleu ``` -We can then load it via `evaluate.load()` like we did in [Chapter 3](/course/chapter3): +Îl putem încărca apoi prin `evaluate.load()` așa cum am făcut în [Capitolul 3](/course/chapter3): ```py import evaluate @@ -423,9 +423,9 @@ import evaluate metric = evaluate.load("sacrebleu") ``` -This metric will take texts as inputs and targets. It is designed to accept several acceptable targets, as there are often multiple acceptable translations of the same sentence -- the dataset we're using only provides one, but it's not uncommon in NLP to find datasets that give several sentences as labels. So, the predictions should be a list of sentences, but the references should be a list of lists of sentences. +Această metrică va lua texte ca inputuri și targeturi. Este conceput pentru a lua mai multe obiective acceptabile, deoarece există adesea mai multe traduceri acceptabile ale aceleiași propoziții - datasetul pe care îl folosim oferă doar una, dar nu este neobișnuit în NLP să găsim dataseturi care oferă mai multe propoziții ca labeluri. Deci, predicțiile ar trebui să fie o listă de propoziții, dar referințele ar trebui să fie o listă de liste de propoziții. -Let's try an example: +Hai să încercăm acest exemplu: ```py predictions = [ @@ -449,7 +449,7 @@ metric.compute(predictions=predictions, references=references) 'ref_len': 13} ``` -This gets a BLEU score of 46.75, which is rather good -- for reference, the original Transformer model in the ["Attention Is All You Need" paper](https://arxiv.org/pdf/1706.03762.pdf) achieved a BLEU score of 41.8 on a similar translation task between English and French! (For more information about the individual metrics, like `counts` and `bp`, see the [SacreBLEU repository](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74).) On the other hand, if we try with the two bad types of predictions (lots of repetitions or too short) that often come out of translation models, we will get rather bad BLEU scores: +Se obține un scor BLEU de 46,75, ceea ce este destul de bine - ca referință, modelul original Transformer din lucrarea ["Attention Is All You Need"](https://arxiv.org/pdf/1706.03762.pdf) a obținut un scor BLEU de 41,8 la o sarcină similară de traducere între engleză și franceză! (Pentru mai multe informații despre parametrii individuali, precum `counts` și `bp`, consultați [repositoriul SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74).) Pe de altă parte, dacă încercăm cu cele două tipuri de predicții proaste (multe repetări sau prea scurte) care rezultă adesea din modelele de traducere, vom obține scoruri BLEU destul de proaste: ```py predictions = ["This This This This"] @@ -491,11 +491,11 @@ metric.compute(predictions=predictions, references=references) 'ref_len': 13} ``` -The score can go from 0 to 100, and higher is better. +Scorul poate varia de la 0 la 100, iar mai mare înseamnă un scor mai bun. {#if fw === 'tf'} -To get from the model outputs to texts the metric can use, we will use the `tokenizer.batch_decode()` method. We just have to clean up all the `-100`s in the labels; the tokenizer will automatically do the same for the padding token. Let's define a function that takes our model and a dataset and computes metrics on it. We're also going to use a trick that dramatically increases performance - compiling our generation code with [XLA](https://www.tensorflow.org/xla), TensorFlow's accelerated linear algebra compiler. XLA applies various optimizations to the model's computation graph, and results in significant improvements to speed and memory usage. As described in the Hugging Face [blog](https://huggingface.co/blog/tf-xla-generate), XLA works best when our input shapes don't vary too much. To handle this, we'll pad our inputs to multiples of 128, and make a new dataset with the padding collator, and then we'll apply the `@tf.function(jit_compile=True)` decorator to our generation function, which marks the whole function for compilation with XLA. +Pentru a trece de la rezultatele modelului la texte pe care metricele le pot utiliza, vom utiliza metoda `tokenizer.batch_decode()`. Trebuie doar să curățăm toate `-100` din labeluri; tokenizerul va face automat același lucru pentru padding token. Să definim o funcție care să preia modelul nostru și un dataset și să calculeze metrici pe acesta. De asemenea, vom utiliza un truc care crește dramatic performanța - compilarea codului nostru de generare cu [XLA](https://www.tensorflow.org/xla), compilatorul accelerat de algebră liniară al TensorFlow. XLA aplică diverse optimizări graficului de calcul al modelului și are ca rezultat îmbunătățiri semnificative ale vitezei și utilizării memoriei. După cum se descrie în [blogul](https://huggingface.co/blog/tf-xla-generate) Hugging Face, XLA funcționează cel mai bine atunci când shaperulire noastre de input nu variază prea mult. Pentru a face față acestui lucru, vom aplica padding inputurilor cu multipli ai 128 și vom crea un nou dataset cu padding collatorul, iar apoi vom aplica decoratorul `@tf.function(jit_compile=True)` funcției noastre de generare, care marchează întreaga funcție pentru compilare cu XLA. ```py import numpy as np @@ -544,7 +544,7 @@ def compute_metrics(): {:else} -To get from the model outputs to texts the metric can use, we will use the `tokenizer.batch_decode()` method. We just have to clean up all the `-100`s in the labels (the tokenizer will automatically do the same for the padding token): +Pentru a trece de la rezultatele modelului la textele pe care metricele le pot utiliza, vom utiliza metoda `tokenizer.batch_decode()`. Trebuie doar să curățăm toate `-100` din labeluri(tokenizerul va face automat același lucru pentru padding token): ```py import numpy as np @@ -572,12 +572,12 @@ def compute_metrics(eval_preds): {/if} -Now that this is done, we are ready to fine-tune our model! +Acum că am făcut acest lucru, suntem gata să facem fine-tune modelului! -### Fine-tuning the model[[fine-tuning-the-model]] +### Fine-tuningul modelului[[fine-tuning-the-model]] -The first step is to log in to Hugging Face, so you're able to upload your results to the Model Hub. There's a convenience function to help you with this in a notebook: +Primul pas pe care trebuie să îl faceți este să vă conectați la Hugging Face, astfel încât să vă puteți încărca rezultatele în Model Hub. Există o funcție convenabilă care vă ajuta cu acest lucru într-un notebook: ```python from huggingface_hub import notebook_login @@ -585,9 +585,9 @@ from huggingface_hub import notebook_login notebook_login() ``` -This will display a widget where you can enter your Hugging Face login credentials. +Aceasta va afișa un widget în care puteți introduce datele voastre de autentificare Hugging Face. -If you aren't working in a notebook, just type the following line in your terminal: +Dacă nu lucrați într-un notebook, tastați următoarea linie în terminal: ```bash huggingface-cli login @@ -595,7 +595,7 @@ huggingface-cli login {#if fw === 'tf'} -Before we start, let's see what kind of results we get from our model without any training: +Înainte de a începe, să vedem ce fel de rezultate obținem de la modelul nostru fără niciun antrenament: ```py print(compute_metrics()) @@ -605,16 +605,16 @@ print(compute_metrics()) {'bleu': 33.26983701454733} ``` -Once this is done, we can prepare everything we need to compile and train our model. Note the use of `tf.keras.mixed_precision.set_global_policy("mixed_float16")` -- this will tell Keras to train using float16, which can give a significant speedup on GPUs that support it (Nvidia 20xx/V100 or newer). +Odată făcut acest lucru, putem pregăti tot ce avem nevoie pentru a compila și antrena modelul nostru. Observați utilizarea `tf.keras.mixed_precision.set_global_policy("mixed_float16")` -- aceasta îi va spune lui Keras să se antreneze folosind float16, ceea ce poate oferi o creștere semnificativă a vitezei pe GPU-urile care o acceptă (Nvidia 20xx/V100 sau mai noi). ```python from transformers import create_optimizer from transformers.keras_callbacks import PushToHubCallback import tensorflow as tf -# The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied -# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset, -# not the original Hugging Face Dataset, so its len() is already num_samples // batch_size. +# Numărul etapelor de antrenare este numărul de sampleuri din dataset, împărțit la dimensiunea batchului, apoi înmulțit +# cu numărul total de epoci. Rețineți că datasetul tf_train_dataset de aici este un dataset tf.data.Dataset în batchuri, +# nu datasetul original Hugging Face, deci len() este deja num_samples // batch_size. num_epochs = 3 num_train_steps = len(tf_train_dataset) * num_epochs @@ -626,11 +626,11 @@ optimizer, schedule = create_optimizer( ) model.compile(optimizer=optimizer) -# Train in mixed-precision float16 +# Antrenarea în float16 cu precizie mixtă tf.keras.mixed_precision.set_global_policy("mixed_float16") ``` -Next, we define a `PushToHubCallback` to upload our model to the Hub during training, as we saw in [section 2]((/course/chapter7/2)), and then we simply fit the model with that callback: +În continuare, definim un `PushToHubCallback` pentru a încărca modelul nostru în Hub în timpul antrenamentului, așa cum am văzut în [secțiunea 2]((/course/chapter7/2)), iar apoi pur și simplu facem fit modelului cu acel callback: ```python from transformers.keras_callbacks import PushToHubCallback @@ -647,15 +647,15 @@ model.fit( ) ``` -Note that you can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` to `Seq2SeqTrainingArguments`. By default, the repository used will be in your namespace and named after the output directory you set, so here it will be `"sgugger/marian-finetuned-kde4-en-to-fr"` (which is the model we linked to at the beginning of this section). +Rețineți că puteți specifica numele repositoriului către care doriți să faceți push cu argumentul `hub_model_id` (în special, va trebui să utilizați acest argument pentru a face push către o organizație). De exemplu, atunci când am trimis modelul către organizația [`huggingface-course`](https://huggingface.co/huggingface-course), am adăugat `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` la `Seq2SeqTrainingArguments`. În mod implicit, repositoriul utilizat va fi în namespaceul vostru și denumit după output directory-ul pe care l-ați stabilit, deci aici va fi `"sgugger/marian-finetuned-kde4-en-to-fr"` (care este modelul la care am făcut legătura la începutul acestei secțiuni). -💡 If the output directory you are using already exists, it needs to be a local clone of the repository you want to push to. If it isn't, you'll get an error when calling `model.fit()` and will need to set a new name. +💡 Dacă output directory-ul pe care îl utilizați există deja, acesta trebuie să fie o clonă locală a repositoriului către care doriți să faceți push. Dacă nu este, veți primi o eroare atunci când apelați `model.fit()` și va trebui să setați un nume nou. -Finally, let's see what our metrics look like now that training has finished: +În cele din urmă, hai să vedem cum arată metricele noastre acum că antrenarea s-a încheiat: ```py print(compute_metrics()) @@ -665,11 +665,11 @@ print(compute_metrics()) {'bleu': 57.334066271545865} ``` -At this stage, you can use the inference widget on the Model Hub to test your model and share it with your friends. You have successfully fine-tuned a model on a translation task -- congratulations! +În acest moment, puteți utiliza widgetul de inferență de pe Model Hub pentru a testa modelul și pentru a-l partaja cu prietenii voștrii. Ați făcut fine-tune cu succes unui model pentru o sarcină de traducere - felicitări! {:else} -Once this is done, we can define our `Seq2SeqTrainingArguments`. Like for the `Trainer`, we use a subclass of `TrainingArguments` that contains a few more fields: +Odată făcut acest lucru, putem defini `Seq2SeqTrainingArguments`. Ca și pentru `Trainer`, folosim o subclasă a `TrainingArguments` care conține câteva câmpuri suplimentare: ```python from transformers import Seq2SeqTrainingArguments @@ -690,23 +690,22 @@ args = Seq2SeqTrainingArguments( ) ``` -Apart from the usual hyperparameters (like learning rate, number of epochs, batch size, and some weight decay), here are a few changes compared to what we saw in the previous sections: +În afară de hiperparametrii obișnuiți (cum ar fi rata de învățare, numărul de epoci, dimensiunea batch-ului și o anumită scădere a weighturilor), aici sunt câteva schimbări în comparație cu ceea ce am văzut în secțiunile anterioare: -- We don't set any regular evaluation, as evaluation takes a while; we will just evaluate our model once before training and after. -- We set `fp16=True`, which speeds up training on modern GPUs. -- We set `predict_with_generate=True`, as discussed above. -- We use `push_to_hub=True` to upload the model to the Hub at the end of each epoch. +- Nu setăm nicio evaluare periodică, deoarece evaluarea durează; ne vom evalua modelul doar o dată înainte și după antrenare. +- Setăm `fp16=True`, care accelerează antrenarea pe GPU-urile moderne. +- Setăm `predict_with_generate=True`, așa cum am discutat mai sus. +- Utilizăm `push_to_hub=True` pentru a încărca modelul în Hub la sfârșitul fiecărei epoci. -Note that you can specify the full name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` to `Seq2SeqTrainingArguments`. By default, the repository used will be in your namespace and named after the output directory you set, so in our case it will be `"sgugger/marian-finetuned-kde4-en-to-fr"` (which is the model we linked to at the beginning of this section). +Rețineți că puteți specifica numele complet al repositoriului către care doriți să faceți push cu argumentul `hub_model_id` (în special, va trebui să utilizați acest argument pentru a face push către o organizație). De exemplu, atunci când am trimis modelul către organizația [`huggingface-course`](https://huggingface.co/huggingface-course), am adăugat `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` la `Seq2SeqTrainingArguments`. În mod implicit, repositoriul utilizat va fi în namespaceul vostru și denumit după output directory-ul pe care l-ați stabilit, deci în cazul nostru va fi `"sgugger/marian-finetuned-kde4-en-to-fr"` (care este modelul la care am făcut legătura la începutul acestei secțiuni). -💡 If the output directory you are using already exists, it needs to be a local clone of the repository you want to push to. If it isn't, you'll get an error when defining your `Seq2SeqTrainer` and will need to set a new name. +💡 Dacă output directory-ul pe care îl utilizați există deja, acesta trebuie să fie o clonă locală a repositoriului către care doriți să faceți push. În caz contrar, veți primi o eroare atunci când vă definiți `Seq2SeqTrainer` și va trebui să stabiliți un nume nou. - -Finally, we just pass everything to the `Seq2SeqTrainer`: +În final, transmitem totul către `Seq2SeqTrainer`: ```python from transformers import Seq2SeqTrainer @@ -722,7 +721,7 @@ trainer = Seq2SeqTrainer( ) ``` -Before training, we'll first look at the score our model gets, to double-check that we're not making things worse with our fine-tuning. This command will take a bit of time, so you can grab a coffee while it executes: +Înainte de antrenare, ne vom uita mai întâi la scorul obținut de modelul nostru, pentru a verifica dacă nu înrăutățim lucrurile prin fine-tuning. Această comandă va dura ceva timp, așa că puteți lua o cafea sau două în timp ce se execută: ```python trainer.evaluate(max_length=max_length) @@ -736,17 +735,17 @@ trainer.evaluate(max_length=max_length) 'eval_steps_per_second': 0.341} ``` -A BLEU score of 39 is not too bad, which reflects the fact that our model is already good at translating English sentences to French ones. +Un scor BLEU de 39 nu este prea rău, ceea ce reflectă faptul că modelul nostru este deja bun la traducerea propozițiilor din engleză în franceză. -Next is the training, which will also take a bit of time: +Urmează antrenarea, care va necesita, de asemenea, puțin timp: ```python trainer.train() ``` -Note that while the training happens, each time the model is saved (here, every epoch) it is uploaded to the Hub in the background. This way, you will be able to to resume your training on another machine if necessary. +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 în fundal. În acest fel, veți putea să reluați antrenarea pe o altă mașină, dacă este necesar. -Once training is done, we evaluate our model again -- hopefully we will see some amelioration in the BLEU score! +Odată ce formarea este terminată, evaluăm din nou modelul nostru - sperăm că vom vedea o îmbunătățire a scorului BLEU! ```py trainer.evaluate(max_length=max_length) @@ -761,35 +760,35 @@ trainer.evaluate(max_length=max_length) 'epoch': 3.0} ``` -That's a nearly 14-point improvement, which is great. +Aceasta este o îmbunătățire de aproape 14 puncte, ceea ce este minunat. -Finally, we use the `push_to_hub()` method to make sure we upload the latest version of the model. The `Trainer` also drafts a model card with all the evaluation results and uploads it. This model card contains metadata that helps the Model Hub pick the widget for the inference demo. Usually, there is no need to say anything as it can infer the right widget from the model class, but in this case, the same model class can be used for all kinds of sequence-to-sequence problems, so we specify it's a translation model: +În final, folosim metoda `push_to_hub()` pentru a ne asigura că încărcăm cea mai recentă versiune a modelului. De asemenea, `Trainer` redactează un model card cu toate rezultatele evaluării și o încarcă. Acest model card conține metadate care ajută Model Hub să aleagă widgetul pentru demonstrația de inferență. De obicei, nu este nevoie să se menționeze nimic, deoarece poate deduce widgetul potrivit din clasa modelului, dar în acest caz, aceeași clasă de model poate fi utilizată pentru toate tipurile de probleme de tip sequence-to-sequence, așa că specificăm că este un model de traducere: ```py trainer.push_to_hub(tags="translation", commit_message="Training complete") ``` -This command returns the URL of the commit it just did, if you want to inspect it: +Această comandă returnează URL-ul comitului pe care tocmai l-am făcut, dacă doriți să îl inspectați: ```python out 'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' ``` -At this stage, you can use the inference widget on the Model Hub to test your model and share it with your friends. You have successfully fine-tuned a model on a translation task -- congratulations! +În această etapă, puteți utiliza widget-ul de inferență de pe Model Hub pentru a vă testa modelul și pentru a-l partaja cu prietenii. Ați făcut fine-tune cu succes un model pentru o sarcină de traducere - felicitări! -If you want to dive a bit more deeply into the training loop, we will now show you how to do the same thing using 🤗 Accelerate. +Dacă doriți să pătrundeți puțin mai adânc în bucla de antrenare, vă vom arăta acum să faceți același lucru folosind 🤗 Accelerate. {/if} {#if fw === 'pt'} -## A custom training loop[[a-custom-training-loop]] +## O buclă de antrenare personalizată[[a-custom-training-loop]] -Let's now take a look at the full training loop, so you can easily customize the parts you need. It will look a lot like what we did in [section 2](/course/chapter7/2) and [Chapter 3](/course/chapter3/4). +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. Va arăta foarte asemănător cu ceea ce am făcut în [secțiunea 2](/course/chapter7/2) și [capitolul 3](/course/chapter7/4). -### Preparing everything for training[[preparing-everything-for-training]] +### Pregătirea tuturor lucrulilor pentru antrenare[[preparing-everything-for-training]] -You've seen all of this a few times now, so we'll go through the code quite quickly. First we'll build the `DataLoader`s from our datasets, after setting the datasets to the `"torch"` format so we get PyTorch tensors: +Ați văzut toate acestea de câteva ori până acum, așa că vom trece prin cod destul de repede. Mai întâi vom construi `DataLoader`s din dataseturile noastre, după ce vom seta dataseturile în formatul `"torch"` astfel încât să obținem tensori PyTorch: ```py from torch.utils.data import DataLoader @@ -806,13 +805,13 @@ eval_dataloader = DataLoader( ) ``` -Next we reinstantiate our model, to make sure we're not continuing the fine-tuning from before but starting from the pretrained model again: +Î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: ```py model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) ``` -Then we will need an optimizer: +Pe urmă vom avea nevoie de un optimizator: ```py from transformers import AdamW @@ -820,7 +819,7 @@ from transformers import AdamW optimizer = AdamW(model.parameters(), lr=2e-5) ``` -Once we have all those objects, we can send them to the `accelerator.prepare()` method. Remember that if you want to train on TPUs in a Colab notebook, you will need to move all of this code into a training function, and that shouldn't execute any cell that instantiates an `Accelerator`. +Odată ce avem toate aceste obiecte, le putem trimite la metoda `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 antrenare, care nu ar trebui să execute nicio celulă care inițializează un `Accelerator`. ```py from accelerate import Accelerator @@ -831,7 +830,7 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( ) ``` -Now that we have sent our `train_dataloader` to `accelerator.prepare()`, we can use its length to compute the number of training steps. Remember we should always do this after preparing the dataloader, as that method will change the length of the `DataLoader`. We use a classic linear schedule from the learning rate to 0: +Acum că am trimis `train_dataloader` la `accelerator.prepare()`, putem utiliza lungimea acestuia pentru a calcula numărul de pași de antrenare. Amintiți-vă că trebuie să facem acest lucru întotdeauna după pregătirea data loaderului, deoarece metoda respectivă va modifica lungimea `DataLoader`. Utilizăm un program liniar clasic de la rata de învățare la 0: ```py from transformers import get_scheduler @@ -848,7 +847,7 @@ lr_scheduler = get_scheduler( ) ``` -Lastly, to push our model to the Hub, we will need to create a `Repository` object in a working folder. First log in to the Hugging Face Hub, if you're not logged in already. We'll determine the repository name from the model ID we want to give our model (feel free to replace the `repo_name` with your own choice; it just needs to contain your username, which is what the function `get_full_repo_name()` does): +În cele din urmă, 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 repositoriului 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()`): ```py from huggingface_hub import Repository, get_full_repo_name @@ -862,18 +861,18 @@ repo_name 'sgugger/marian-finetuned-kde4-en-to-fr-accelerate' ``` -Then we can clone that repository in a local folder. If it already exists, this local folder should be a clone of the repository we are working with: +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: ```py output_dir = "marian-finetuned-kde4-en-to-fr-accelerate" repo = Repository(output_dir, clone_from=repo_name) ``` -We can now upload anything we save in `output_dir` by calling the `repo.push_to_hub()` method. This will help us upload the intermediate models at the end of each epoch. +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. -### Training loop[[training-loop]] +### Bucla de antrenare[[training-loop]] -We are now ready to write the full training loop. To simplify its evaluation part, we define this `postprocess()` function that takes predictions and labels and converts them to the lists of strings our `metric` object will expect: +Acum suntem pregătiți să scriem bucla de antrenare completă. Pentru a simplifica partea de evaluare, definim această funcție `postprocess()` care preia predicțiile și labelurile și le convertește în liste de stringuri pe care obiectul nostru `metric` le va aștepta: ```py def postprocess(predictions, labels): @@ -892,11 +891,11 @@ def postprocess(predictions, labels): return decoded_preds, decoded_labels ``` -The training loop looks a lot like the ones in [section 2](/course/chapter7/2) and [Chapter 3](/course/chapter3), with a few differences in the evaluation part -- so let's focus on that! +Bucla de antrenare seamănă foarte mult cu cele din [secțiunea 2](/course/chapter7/2) și [capitolul 3](/course/chapter3), cu câteva diferențe în partea de evaluare - așa că hai să ne concentrăm pe asta! -The first thing to note is that we use the `generate()` method to compute predictions, but this is a method on our base model, not the wrapped model 🤗 Accelerate created in the `prepare()` method. That's why we unwrap the model first, then call this method. +Primul lucru pe care îl putem remarca este că folosim metoda `generate()` pentru a calcula predicțiile, dar aceasta este o metodă a modelul nostru de bază, nu pe modelul wrapped 🤗 Accelerate creat în metoda `prepare()`. De aceea, mai întâi facem unwrap modelului, apoi apelăm această metodă. -The second thing is that, like with [token classification](/course/chapter7/2), two processes may have padded the inputs and labels to different shapes, so we use `accelerator.pad_across_processes()` to make the predictions and labels the same shape before calling the `gather()` method. If we don't do this, the evaluation will either error out or hang forever. +Al doilea lucru este că, la fel ca în cazul [token classification](/course/chapter7/2), este posibil ca două procese să fi făcut padding inputurilor și labelurilor cu forme diferite, așa că folosim `accelerator.pad_across_processes()` pentru a face predicțiile și labelurile de aceeași formă înainte de a apela metoda `gather()`. Dacă nu facem acest lucru, evaluarea va da o eroare sau se va bloca pentru totdeauna. ```py from tqdm.auto import tqdm @@ -960,13 +959,13 @@ epoch 1, BLEU score: 54.24 epoch 2, BLEU score: 54.44 ``` -Once this is done, you should have a model that has results pretty similar to the one trained with the `Seq2SeqTrainer`. You can check the one we trained using this code at [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). And if you want to test out any tweaks to the training loop, you can directly implement them by editing the code shown above! +Odată făcut acest lucru, ar trebui să aveți un model care are rezultate destul de asemănătoare cu cel antrenat cu `Seq2SeqTrainer`. Îl puteți verifica pe cel pe care l-am antrenat folosind acest cod la [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*] (https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-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! {/if} -## Using the fine-tuned model[[using-the-fine-tuned-model]] +## Utilizarea modelului fine-tuned[[using-the-fine-tuned-model]] -We've already shown you how you can use the model we fine-tuned on the Model Hub with the inference widget. To use it locally in a `pipeline`, we just have to specify the proper model identifier: +V-am arătat deja cum puteți utiliza modelul pe căruia i-am aplicat fine-tune pe Model Hub cu widgetul de inferență. Pentru a-l utiliza la nivel local într-un `pipeline`, trebuie doar să specificăm identificatorul de model corespunzător: ```py from transformers import pipeline @@ -981,7 +980,7 @@ translator("Default to expanded threads") [{'translation_text': 'Par défaut, développer les fils de discussion'}] ``` -As expected, our pretrained model adapted its knowledge to the corpus we fine-tuned it on, and instead of leaving the English word "threads" alone, it now translates it to the French official version. It's the same for "plugin": +Așa cum era de așteptat, modelul nostru preantrenat și-a adaptat cunoștințele la corpusul pe care i-am făcut fine-tune și, în loc să lase cuvântul englezesc "threads", acum îl traduce în versiunea oficială franceză. Același lucru este valabil și pentru "plugin": ```py translator( @@ -993,10 +992,10 @@ translator( [{'translation_text': "Impossible d'importer %1 en utilisant le module externe d'importation OFX. Ce fichier n'est pas le bon format."}] ``` -Another great example of domain adaptation! +Un alt exemplu excelent de adaptare a domeniului! -✏️ **Your turn!** What does the model return on the sample with the word "email" you identified earlier? +✏️ **E rândul tău!** Care este rezultatul modelului pentru sampleul cuvântul "email" pe care l-ai identificat mai devreme? diff --git a/chapters/ro/chapter7/5.mdx b/chapters/ro/chapter7/5.mdx index b8afcfaa0..84157cc3f 100644 --- a/chapters/ro/chapter7/5.mdx +++ b/chapters/ro/chapter7/5.mdx @@ -1,6 +1,6 @@ -# Summarization[[summarization]] +# Sumarizare[[summarization]] {#if fw === 'pt'} @@ -23,19 +23,20 @@ {/if} -In this section we'll take a look at how Transformer models can be used to condense long documents into summaries, a task known as _text summarization_. This is one of the most challenging NLP tasks as it requires a range of abilities, such as understanding long passages and generating coherent text that captures the main topics in a document. However, when done well, text summarization is a powerful tool that can speed up various business processes by relieving the burden of domain experts to read long documents in detail. +În această secțiune vom analiza modul în care modelele Transformer pot fi utilizate pentru a condensa documente lungi în rezumate, o sarcină cunoscută sub numele de _text summarization_. Aceasta este una dintre cele mai dificile sarcini NLP, deoarece necesită o gamă largă de abilități, cum ar fi înțelegerea pasajelor lungi și generarea unui text coerent care integrează principalele subiecte dintr-un document. Cu toate acestea, atunci când este bine realizată, rezumarea textului este un instrument puternic care poate accelera diverse procese de business prin scutirea experților într-u anumit domeniu de a citi documente lungi în detaliu. -Although there already exist various fine-tuned models for summarization on the [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), almost all of these are only suitable for English documents. So, to add a twist in this section, we'll train a bilingual model for English and Spanish. By the end of this section, you'll have a [model](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) that can summarize customer reviews like the one shown here: +Deși există deja diverse modele bine puse la punct pentru sumarizare pe [Hugging Face Hub] (https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), aproape toate acestea sunt potrivite numai pentru documentele în limba engleză. Prin urmare, pentru a adăuga o întorsătură în această secțiune, vom antrena un model bilingv pentru engleză și spaniolă. Până la sfârșitul acestei secțiuni, veți avea un [model](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) care poate rezuma recenziile clienților precum cel prezentat aici: + -As we'll see, these summaries are concise because they're learned from the titles that customers provide in their product reviews. Let's start by putting together a suitable bilingual corpus for this task. +După cum vom vedea, aceste rezumate sunt concise deoarece sunt învățate din titlurile pe care clienții le furnizează în recenziile lor despre produse. Să începem prin alcătuirea unui corpus bilingv adecvat pentru această sarcină. -## Preparing a multilingual corpus[[preparing-a-multilingual-corpus]] +## Pregătirea unui corpus multilingv[[preparing-a-multilingual-corpus]] -We'll use the [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) to create our bilingual summarizer. This corpus consists of Amazon product reviews in six languages and is typically used to benchmark multilingual classifiers. However, since each review is accompanied by a short title, we can use the titles as the target summaries for our model to learn from! To get started, let's download the English and Spanish subsets from the Hugging Face Hub: +Vom utiliza [Multilingual Amazon Reviews Corpus] (https://huggingface.co/datasets/amazon_reviews_multi) pentru a crea bilingv summarizerul nostru. Acest corpus este format din recenzii ale produselor Amazon în șase limbi și este utilizat de obicei pentru a evalua clasificatoarele multilingve. Cu toate acestea, deoarece fiecare recenzie este însoțită de un titlu scurt, putem folosi titlurile ca rezumate țintă din care modelul nostru să învețe! Pentru a începe, să descărcăm subseturile în engleză și spaniolă de la Hugging Face Hub: ```python from datasets import load_dataset @@ -62,7 +63,7 @@ DatasetDict({ }) ``` -As you can see, for each language there are 200,000 reviews for the `train` split, and 5,000 reviews for each of the `validation` and `test` splits. The review information we are interested in is contained in the `review_body` and `review_title` columns. Let's take a look at a few examples by creating a simple function that takes a random sample from the training set with the techniques we learned in [Chapter 5](/course/chapter5): +După cum puteți vedea, pentru fiecare limbă există 200.000 de recenzii pentru splitul `train` și 5.000 de recenzii pentru fiecare dintre spliturile `validation` și `test`. Informațiile despre recenzii care ne interesează sunt conținute în coloanele `review_body` și `review_title`. Să analizăm câteva exemple prin crearea unei funcții simple care preia un sample aleatoriu din setul de antrenare cu ajutorul tehnicilor învățate în [Capitolul 5](/course/chapter5): ```python def show_samples(dataset, num_samples=3, seed=42): @@ -88,11 +89,11 @@ show_samples(english_dataset) -✏️ **Try it out!** Change the random seed in the `Dataset.shuffle()` command to explore other reviews in the corpus. If you're a Spanish speaker, take a look at some of the reviews in `spanish_dataset` to see if the titles also seem like reasonable summaries. +✏️ **Încercați!** Schimbați seedul aleatoriu în comanda `Dataset.shuffle()` pentru a explora alte recenzii din corpus. Dacă sunteți vorbitor de spaniolă, aruncați o privire la unele dintre recenziile din `spanish_dataset` pentru a vedea dacă și titlurile par a fi rezumate rezonabil. -This sample shows the diversity of reviews one typically finds online, ranging from positive to negative (and everything in between!). Although the example with the "meh" title is not very informative, the other titles look like decent summaries of the reviews themselves. Training a summarization model on all 400,000 reviews would take far too long on a single GPU, so instead we'll focus on generating summaries for a single domain of products. To get a feel for what domains we can choose from, let's convert `english_dataset` to a `pandas.DataFrame` and compute the number of reviews per product category: +Acest sample arată diversitatea recenziilor pe care le găsim de obicei online, variind de la pozitive la negative (și totul între ele!). Deși exemplul cu titlul "meh" nu este foarte informativ, celelalte titluri par a fi rezumate decente ale recenziilor în sine. Antrenarea unui model de rezumare pe toate cele 400 000 de recenzii ar dura mult prea mult pe un singur GPU, așa că ne vom concentra pe generarea de rezumate pentru un singur domeniu de produse. Pentru a avea o idee despre domeniile din care putem alege, să convertim `english_dataset` într-un `pandas.DataFrame` și să calculăm numărul de recenzii per categorie de produse: ```python english_dataset.set_format("pandas") @@ -125,7 +126,7 @@ book 3756 Name: product_category, dtype: int64 ``` -The most popular products in the English dataset are about household items, clothing, and wireless electronics. To stick with the Amazon theme, though, let's focus on summarizing book reviews -- after all, this is what the company was founded on! We can see two product categories that fit the bill (`book` and `digital_ebook_purchase`), so let's filter the datasets in both languages for just these products. As we saw in [Chapter 5](/course/chapter5), the `Dataset.filter()` function allows us to slice a dataset very efficiently, so we can define a simple function to do this: +Cele mai populare produse din datasetul în limba engleză sunt despre articole de uz casnic, îmbrăcăminte și electronice fără fir. Cu toate acestea, pentru a rămâne la Amazontheme, să ne concentrăm pe rezumatul recenziilor de cărți - la urma urmei, acesta este motivul pentru care compania a fost fondată! Putem vedea două categorii de produse care se potrivesc (`book` și `digital_ebook_purchase`), deci să filtrăm dataseturile în ambele limbi doar pentru aceste produse. După cum am văzut în [Capitolul 5](/course/chapter5), funcția `Dataset.filter()` ne permite să tăiem un datasetfoarte eficient, deci putem defini o funcție simplă pentru a face acest lucru: ```python def filter_books(example): @@ -135,13 +136,13 @@ def filter_books(example): ) ``` -Now when we apply this function to `english_dataset` and `spanish_dataset`, the result will contain just those rows involving the book categories. Before applying the filter, let's switch the format of `english_dataset` from `"pandas"` back to `"arrow"`: +Acum, când aplicăm această funcție la `english_dataset` și `spanish_dataset`, rezultatul va conține doar acele rânduri care implică categoriile de cărți. Înainte de a aplica filtrul, să schimbăm formatul din `english_dataset` din `"pandas"` înapoi în `"arrow"`: ```python english_dataset.reset_format() ``` -We can then apply the filter function, and as a sanity check let's inspect a sample of reviews to see if they are indeed about books: +Putem aplica apoi funcția de filtrare și, ca o verificare a corectitudinii, să inspectăm un sample de recenzii pentru a vedea dacă acestea sunt într-adevăr despre cărți: ```python spanish_books = spanish_dataset.filter(filter_books) @@ -160,7 +161,7 @@ show_samples(english_books) '>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.' ``` -Okay, we can see that the reviews are not strictly about books and might refer to things like calendars and electronic applications such as OneNote. Nevertheless, the domain seems about right to train a summarization model on. Before we look at various models that are suitable for this task, we have one last bit of data preparation to do: combining the English and Spanish reviews as a single `DatasetDict` object. 🤗 Datasets provides a handy `concatenate_datasets()` function that (as the name suggests) will stack two `Dataset` objects on top of each other. So, to create our bilingual dataset, we'll loop over each split, concatenate the datasets for that split, and shuffle the result to ensure our model doesn't overfit to a single language: +Bine, putem vedea că recenziile nu sunt strict despre cărți și se pot referi la lucruri precum calendare și aplicații electronice precum OneNote. Cu toate acestea, domeniul pare potrivit pentru a antrena un model de sumarizare. Înainte de a analiza diferitele modele care sunt potrivite pentru această sarcină, trebuie să mai pregătim puțin datele: să combinăm recenziile în engleză și spaniolă ca un singur obiect `DatasetDict`. 🤗 Datasets oferă o funcție utilă `concatenate_datasets()` care (după cum sugerează și numele) va concatena două obiecte `Dataset` unul peste celălalt. Așadar, pentru a crea datasetul nostru bilingv, vom parcurge în buclă fiecare împărțire, vom concatena dataseturile pentru acel split și vom amesteca rezultatul pentru a ne asigura că modelul nostru nu se adaptează excesiv la o singură limbă: ```python from datasets import concatenate_datasets, DatasetDict @@ -188,14 +189,14 @@ show_samples(books_dataset) '>> Review: igual que el anterior' ``` -This certainly looks like a mix of English and Spanish reviews! Now that we have a training corpus, one final thing to check is the distribution of words in the reviews and their titles. This is especially important for summarization tasks, where short reference summaries in the data can bias the model to only output one or two words in the generated summaries. The plots below show the word distributions, and we can see that the titles are heavily skewed toward just 1-2 words: +Acest lucru arată cu siguranță ca un amestec de recenzii în engleză și spaniolă! Acum că avem un corpus de antrenament, un ultim lucru de verificat este distribuția cuvintelor în recenzii și în titlurile acestora. Acest lucru este deosebit de important pentru sarcinile de sumarizare, în cazul în care rezumatele scurte de referință din date pot influența modelul să producă doar unul sau două cuvinte în rezumatele generate. Graficele de mai jos arată distribuția cuvintelor și putem observa că titlurile sunt puternic înclinate spre 1-2 cuvinte:
-Word count distributions for the review titles and texts. - +Distribuția numărului de cuvinte pentru titlurile și textele recenziei. +
-To deal with this, we'll filter out the examples with very short titles so that our model can produce more interesting summaries. Since we're dealing with English and Spanish texts, we can use a rough heuristic to split the titles on whitespace and then use our trusty `Dataset.filter()` method as follows: +Pentru a rezolva acest lucru, vom filtra exemplele cu titluri foarte scurte, astfel încât modelul nostru să poată produce rezumate mai interesante. Deoarece avem de-a face cu texte în engleză și spaniolă, putem folosi rough heuristic pentru a face split titlurilor pe baza spațiului alb și apoi să folosim metoda noastră de încredere `Dataset.filter()` după cum urmează: ```python books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) @@ -203,34 +204,34 @@ books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > Now that we've prepared our corpus, let's take a look at a few possible Transformer models that one might fine-tune on it! -## Models for text summarization[[models-for-text-summarization]] +## Modele pentru sumarizarea textului[[models-for-text-summarization]] -If you think about it, text summarization is a similar sort of task to machine translation: we have a body of text like a review that we'd like to "translate" into a shorter version that captures the salient features of the input. Accordingly, most Transformer models for summarization adopt the encoder-decoder architecture that we first encountered in [Chapter 1](/course/chapter1), although there are some exceptions like the GPT family of models which can also be used for summarization in few-shot settings. The following table lists some popular pretrained models that can be fine-tuned for summarization. +Dacă vă gândiți bine, rezumarea textului este o sarcină similară cu machine translation: avem un corp de text, cum ar fi o recenzie, pe care am dori să o "traducem" într-o versiune mai scurtă care să capteze caracteristicile principale ale datelor de intrare. În consecință, majoritatea modelelor Transformer pentru rezumare adoptă arhitectura codificator-decodificator pe care am întâlnit-o pentru prima dată în [Capitolul 1](/course/chapter1), deși există unele excepții, cum ar fi familia de modele GPT, care poate fi, de asemenea, utilizată pentru rezumare în few-shot settings. Tabelul de mai jos enumeră câteva modele preantrenate populare care pot fi ajustate pentru sumarizare. | Transformer model | Description | Multilingual? | | :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | -| [GPT-2](https://huggingface.co/gpt2-xl) | Although trained as an auto-regressive language model, you can make GPT-2 generate summaries by appending "TL;DR" at the end of the input text. | ❌ | -| [PEGASUS](https://huggingface.co/google/pegasus-large) | Uses a pretraining objective to predict masked sentences in multi-sentence texts. This pretraining objective is closer to summarization than vanilla language modeling and scores highly on popular benchmarks. | ❌ | -| [T5](https://huggingface.co/t5-base) | A universal Transformer architecture that formulates all tasks in a text-to-text framework; e.g., the input format for the model to summarize a document is `summarize: ARTICLE`. | ❌ | -| [mT5](https://huggingface.co/google/mt5-base) | A multilingual version of T5, pretrained on the multilingual Common Crawl corpus (mC4), covering 101 languages. | ✅ | -| [BART](https://huggingface.co/facebook/bart-base) | A novel Transformer architecture with both an encoder and a decoder stack trained to reconstruct corrupted input that combines the pretraining schemes of BERT and GPT-2. | ❌ | -| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | A multilingual version of BART, pretrained on 50 languages. | ✅ | +| [GPT-2](https://huggingface.co/gpt2-xl) | Deși este antrenat ca un model lingvistic autoregresiv, puteți face GPT-2 să genereze rezumate prin adăugarea "TL;DR" la sfârșitul textului de intrare. | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | Utilizează un obiectiv de preantrenare pentru a prezice propoziții mascate în texte cu mai multe propoziții. Acest obiectiv de preantrenare este mai apropiat de rezumare decât de vanilla language modeling și obține scoruri ridicate la standardele populare. | ❌ | +| [T5](https://huggingface.co/t5-base) | O arhitectură Transformer universală care formulează toate sarcinile într-un framework text-text; de exemplu, formatul de intrare pentru modelul de rezumare a unui document este `summarize: ARTICOL`. | ❌ | +| [mT5](https://huggingface.co/google/mt5-base) | O versiune multilingvă a T5, preantrenată pe corpusul multilingv Common Crawl (mC4), care acoperă 101 limbi. | ✅ | +| [BART](https://huggingface.co/facebook/bart-base) | O nouă arhitectură Transformer cu un encoder și un stack de decodere antrenate pentru a reconstrui intrarea coruptă care combină schemele de preantrenare ale BERT și GPT-2. | ❌ | +| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | O versiune multilingvă a BART, preantrenată pe 50 de limbi. | ✅ | -As you can see from this table, the majority of Transformer models for summarization (and indeed most NLP tasks) are monolingual. This is great if your task is in a "high-resource" language like English or German, but less so for the thousands of other languages in use across the world. Fortunately, there is a class of multilingual Transformer models, like mT5 and mBART, that come to the rescue. These models are pretrained using language modeling, but with a twist: instead of training on a corpus of one language, they are trained jointly on texts in over 50 languages at once! +După cum puteți vedea din acest tabel, majoritatea modelelor Transformer pentru rezumare (și, într-adevăr, majoritatea sarcinilor NLP) sunt monolingve. Acest lucru este grozav dacă sarcina voastrăeste într-o limbă cu multe resurse precum engleza sau germana, dar mai puțin pentru miile de alte limbi utilizate în întreaga lume. Din fericire, există o clasă de modele Transformer multilingve, precum mT5 și mBART, care vin în ajutor. Aceste modele sunt preantrenate folosind modelarea limbajului, dar cu o întorsătură: în loc să fie antrenate pe un corpus dintr-o singură limbă, ele sunt antrenate împreună pe texte în peste 50 de limbi deodată! -We'll focus on mT5, an interesting architecture based on T5 that was pretrained in a text-to-text framework. In T5, every NLP task is formulated in terms of a prompt prefix like `summarize:` which conditions the model to adapt the generated text to the prompt. As shown in the figure below, this makes T5 extremely versatile, as you can solve many tasks with a single model! +Ne vom concentra asupra mT5, o arhitectură interesantă bazată pe T5, care a fost preantrenată într-un cadru text-to-text. În T5, fiecare sarcină NLP este formulată în termenii unui prompt prefix precum `summarize:` care condiționează modelul să adapteze textul generat la prompt. După cum se arată în figura de mai jos, acest lucru face T5 extrem de versatil, deoarece puteți rezolva multe sarcini cu un singur model!
-Different tasks performed by the T5 architecture. - +Diferite sarcini îndeplinite de arhitectura T5. +
-mT5 doesn't use prefixes, but shares much of the versatility of T5 and has the advantage of being multilingual. Now that we've picked a model, let's take a look at preparing our data for training. +mT5 nu utilizează prefixe, dar împărtășește o mare parte din versatilitatea T5 și are avantajul de a fi multilingv. Acum că am ales un model, să aruncăm o privire la pregătirea datelor noastre pentru antrenare. -✏️ **Try it out!** Once you've worked through this section, see how well mT5 compares to mBART by fine-tuning the latter with the same techniques. For bonus points, you can also try fine-tuning T5 on just the English reviews. Since T5 has a special prefix prompt, you'll need to prepend `summarize:` to the input examples in the preprocessing steps below. +✏️ **Încercați!** După ce ați parcurs această secțiune, vedeți cât de bine se compară mT5 cu mBART prin aplicarea fine-tuningului acestuia din urmă cu aceleași tehnici. Pentru puncte bonus, puteți încerca, de asemenea, fine-tuningul a T5 doar pe recenziile în limba engleză. Deoarece T5 are un prefix prompt special, va trebui să adăugați `summarize:` la exemplele de intrare în pașii de preprocesare de mai jos. @@ -238,7 +239,7 @@ mT5 doesn't use prefixes, but shares much of the versatility of T5 and has the a -Our next task is to tokenize and encode our reviews and their titles. As usual, we begin by loading the tokenizer associated with the pretrained model checkpoint. We'll use `mt5-small` as our checkpoint so we can fine-tune the model in a reasonable amount of time: +Următoarea noastră sarcină este să tokenizăm și să codificăm recenziile și titlurile acestora. Ca de obicei, începem prin încărcarea tokenizelui asociat cu checkpointul modelului preantrenat. Vom folosi `mt5-small` ca checkpoint, astfel încât să putem face fine-tune modelului într-un timp rezonabil: ```python from transformers import AutoTokenizer @@ -249,11 +250,11 @@ tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -💡 In the early stages of your NLP projects, a good practice is to train a class of "small" models on a small sample of data. This allows you to debug and iterate faster toward an end-to-end workflow. Once you are confident in the results, you can always scale up the model by simply changing the model checkpoint! +💡 În stadiile inițiale ale proiectelor NLP, o bună practică este de a antrena o clasă de modele "mici" pe un sample mic de date. Acest lucru vă permite să faceți debug și să iterați mai rapid către un flux de lucru end-to-end. Odată ce sunteți încrezător în rezultate, puteți oricând să măriți modelul prin simpla schimbare a checkpointului modelului! -Let's test out the mT5 tokenizer on a small example: +Să testăm tokenizerul mT5 pe un mic exemplu: ```python inputs = tokenizer("I loved reading the Hunger Games!") @@ -264,7 +265,7 @@ inputs {'input_ids': [336, 259, 28387, 11807, 287, 62893, 295, 12507, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} ``` -Here we can see the familiar `input_ids` and `attention_mask` that we encountered in our first fine-tuning experiments back in [Chapter 3](/course/chapter3). Let's decode these input IDs with the tokenizer's `convert_ids_to_tokens()` function to see what kind of tokenizer we're dealing with: +Aici putem vedea binecunoscutele `input_ids` și `attention_mask` pe care le-am întâlnit în primele noastre experimente de fine-tuning în [Capitolul 3](/course/chapter3). Să decodificăm aceste ID-uri de intrare cu funcția `convert_ids_to_tokens()` a tokenizerului pentru a vedea cu ce fel de tokenizer avem de-a face: ```python tokenizer.convert_ids_to_tokens(inputs.input_ids) @@ -274,9 +275,9 @@ tokenizer.convert_ids_to_tokens(inputs.input_ids) ['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] ``` -The special Unicode character `▁` and end-of-sequence token `` indicate that we're dealing with the SentencePiece tokenizer, which is based on the Unigram segmentation algorithm discussed in [Chapter 6](/course/chapter6). Unigram is especially useful for multilingual corpora since it allows SentencePiece to be agnostic about accents, punctuation, and the fact that many languages, like Japanese, do not have whitespace characters. +Caracterul Unicode special `▁` și tokenul de sfârșit de secvență `` indică faptul că avem de-a face cu tokenizeerul SentencePiece, care se bazează pe algoritmul de segmentare Unigram discutat în [Capitolul 6](/course/chapter6). Unigram este deosebit de util pentru corpusurile multilingve, deoarece permite SentencePiece să fie agnostic în ceea ce privește accentele, punctuația și faptul că multe limbi, precum japoneza, nu au caractere de spațiu alb. -To tokenize our corpus, we have to deal with a subtlety associated with summarization: because our labels are also text, it is possible that they exceed the model's maximum context size. This means we need to apply truncation to both the reviews and their titles to ensure we don't pass excessively long inputs to our model. The tokenizers in 🤗 Transformers provide a nifty `text_target` argument that allows you to tokenize the labels in parallel to the inputs. Here is an example of how the inputs and targets are processed for mT5: +Pentru a tokeniza corpusul nostru, trebuie să ne ocupăm de o subtilitate asociată cu rezumarea: deoarece labelurile noastre sunt, de asemenea, text, este posibil ca acestea să depășească dimensiunea maximă a contextului modelului. Acest lucru înseamnă că trebuie să aplicăm trunchierea atât a recenziilor, cât și a titlurilor acestora, pentru a ne asigura că nu trecem inputuri excesiv de lungi modelului nostru. Tokenizerele din 🤗 Transformers oferă un argument ingenios, `text_target`, care vă permite să tokenizați labelurile în paralel cu inputurile. Iată un exemplu al modului în care inputurile și targeturile sunt procesate pentru mT5: ```python max_input_length = 512 @@ -296,59 +297,60 @@ def preprocess_function(examples): return model_inputs ``` -Let's walk through this code to understand what's happening. The first thing we've done is define values for `max_input_length` and `max_target_length`, which set the upper limits for how long our reviews and titles can be. Since the review body is typically much larger than the title, we've scaled these values accordingly. +Să parcurgem acest cod pentru a înțelege ce se întâmplă. Primul lucru pe care l-am făcut a fost să definim valorile pentru `max_input_length` și `max_target_length`, care stabilesc limitele superioare pentru cât de lungi pot fi recenziile și titlurile noastre. Deoarece corpul recenziei este de obicei mult mai mare decât titlul, am mărit aceste valori în consecință. -With `preprocess_function()`, it is then a simple matter to tokenize the whole corpus using the handy `Dataset.map()` function we've used extensively throughout this course: +Cu ajutorul funcției `preprocess_function()`, este simplu să tokenizăm întregul corpus cu ajutorul funcției practice `Dataset.map()` pe care am folosit-o la greu pe parcursul acestui curs: ```python tokenized_datasets = books_dataset.map(preprocess_function, batched=True) ``` -Now that the corpus has been preprocessed, let's take a look at some metrics that are commonly used for summarization. As we'll see, there is no silver bullet when it comes to measuring the quality of machine-generated text. +Acum că corpusul a fost preprocesat, să aruncăm o privire asupra unor metrici care sunt utilizate în mod obișnuit pentru sumarizare. După cum vom vedea, nu există un glonț de argint atunci când vine vorba de măsurarea calității textului generat de calculator. -💡 You may have noticed that we used `batched=True` in our `Dataset.map()` function above. This encodes the examples in batches of 1,000 (the default) and allows you to make use of the multithreading capabilities of the fast tokenizers in 🤗 Transformers. Where possible, try using `batched=True` to get the most out of your preprocessing! +💡 Poate ați observat că am folosit `batched=True` în funcția noastră `Dataset.map()` de mai sus. Aceasta codifică exemplele în batchuri de 1.000 (implicit) și vă permite să utilizați capacitățile multithreading ale tokenizerilor rapizi din 🤗 Transformers. Atunci când este posibil, încercați să utilizați `batched=True` pentru a profita la maximum de preprocesare! -## Metrics for text summarization[[metrics-for-text-summarization]] +## Metrice pentru sumarizare[[metrics-for-text-summarization]] -In comparison to most of the other tasks we've covered in this course, measuring the performance of text generation tasks like summarization or translation is not as straightforward. For example, given a review like "I loved reading the Hunger Games", there are multiple valid summaries, like "I loved the Hunger Games" or "Hunger Games is a great read". Clearly, applying some sort of exact match between the generated summary and the label is not a good solution -- even humans would fare poorly under such a metric, because we all have our own writing style. +În comparație cu majoritatea celorlalte sarcini pe care le-am abordat în acest curs, măsurarea performanței sarcinilor de generare a textului, precum sumarizare sau traducerea, nu este la fel de simplă. De exemplu, având în vedere o recenzie precum "Mi-a plăcut să citesc Hunger Games", există mai multe rezumate valide, precum "Mi-a plăcut Hunger Games" sau "Hunger Games este o lectură excelentă". În mod clar, aplicarea unui exact match între rezumatul generat și label nu este o soluție bună - chiar și oamenii s-ar descurca prost cu un astfel de metric, deoarece toți avem propriul nostru stil de scriere. -For summarization, one of the most commonly used metrics is the [ROUGE score](https://en.wikipedia.org/wiki/ROUGE_(metric)) (short for Recall-Oriented Understudy for Gisting Evaluation). The basic idea behind this metric is to compare a generated summary against a set of reference summaries that are typically created by humans. To make this more precise, suppose we want to compare the following two summaries: +Pentru rezumare, una dintre cele mai frecvent utilizate metrici este [ROUGE score](https://en.wikipedia.org/wiki/ROUGE_(metric)) (prescurtarea de la Recall-Oriented Understudy for Gisting Evaluation). Ideea de bază din spatele acestei metrici este de a compara un rezumat generat cu un set de rezumate de referință care sunt de obicei create de oameni. Pentru a face acest lucru mai precis, să presupunem că dorim să comparăm următoarele două rezumate: ```python generated_summary = "I absolutely loved reading the Hunger Games" reference_summary = "I loved reading the Hunger Games" ``` -One way to compare them could be to count the number of overlapping words, which in this case would be 6. However, this is a bit crude, so instead ROUGE is based on computing the _precision_ and _recall_ scores for the overlap. +O modalitate de a le compara ar fi să numărați numărul de cuvinte care se suprapun, care în acest caz ar fi 6. Cu toate acestea, acest lucru este un pic crud, astfel încât, în schimb, ROUGE se bazează pe calcularea scorurilor _preicision_ și _recall_ pentru suprapunere. -🙋 Don't worry if this is the first time you've heard of precision and recall -- we'll go through some explicit examples together to make it all clear. These metrics are usually encountered in classification tasks, so if you want to understand how precision and recall are defined in that context, we recommend checking out the `scikit-learn` [guides](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). +🙋 Nu vă faceți griji dacă aceasta este prima dată când auziți de precision și recall - vom trece împreună prin câteva exemple explicite pentru a clarifica totul. Aceste metrici sunt de obicei întâlnite în sarcinile de clasificare, deci dacă doriți să înțelegeți cum sunt definite precizia și recallul în acest context, vă recomandăm să consultați [ghidurile `scikit-learn`] (https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). -For ROUGE, recall measures how much of the reference summary is captured by the generated one. If we are just comparing words, recall can be calculated according to the following formula: +Pentru ROUGE, recall măsoară cât de mult din rezumatul de referință este capturat de cel generat. Dacă comparăm doar cuvinte, recall poate fi calculată conform următoarei formule: $$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ -For our simple example above, this formula gives a perfect recall of 6/6 = 1; i.e., all the words in the reference summary have been produced by the model. This may sound great, but imagine if our generated summary had been "I really really loved reading the Hunger Games all night". This would also have perfect recall, but is arguably a worse summary since it is verbose. To deal with these scenarios we also compute the precision, which in the ROUGE context measures how much of the generated summary was relevant: +Pentru exemplul nostru simplu de mai sus, această formulă oferă un recall perfect de 6/6 = 1; adică, toate cuvintele din rezumatul de referință au fost produse de model. Acest lucru poate părea grozav, dar imaginați-vă dacă rezumatul nostru generat ar fi fost "Mi-a plăcut foarte mult să citesc Jocurile Foamei toată noaptea". Aceasta ar avea, de asemenea, o reamintire perfectă, dar este, fără îndoială, un rezumat mai prost, deoarece are mai multe cuvinte. Pentru a face față acestor scenarii, calculăm și precizia, care, în contextul ROUGE, măsoară cât de mult din rezumatul generat a fost relevant: + $$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ -Applying this to our verbose summary gives a precision of 6/10 = 0.6, which is considerably worse than the precision of 6/7 = 0.86 obtained by our shorter one. In practice, both precision and recall are usually computed, and then the F1-score (the harmonic mean of precision and recall) is reported. We can do this easily in 🤗 Datasets by first installing the `rouge_score` package: +Aplicând acest lucru la rezumatul nostru cu mai multe cuvinte, se obține o precizie de 6/10 = 0,6, ceea ce este considerabil mai rău decât precizia de 6/7 = 0,86 obținută de rezumatul nostru mai scurt. În practică, se calculează de obicei atât precision, cât și recallul, iar apoi se raportează scorul F1 (media armonică a precision și recall). Putem face acest lucru cu ușurință în 🤗 Datasets instalând mai întâi biblioteca `rouge_score`: ```py !pip install rouge_score ``` -and then loading the ROUGE metric as follows: +și apoi încărcând metrica ROUGE după cum urmează: ```python import evaluate @@ -356,7 +358,7 @@ import evaluate rouge_score = evaluate.load("rouge") ``` -Then we can use the `rouge_score.compute()` function to calculate all the metrics at once: +Apoi, putem utiliza funcția `rouge_score.compute()` pentru a calcula toate metricile odată: ```python scores = rouge_score.compute( @@ -372,7 +374,7 @@ scores 'rougeLsum': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92))} ``` -Whoa, there's a lot of information in that output -- what does it all mean? First, 🤗 Datasets actually computes confidence intervals for precision, recall, and F1-score; these are the `low`, `mid`, and `high` attributes you can see here. Moreover, 🤗 Datasets computes a variety of ROUGE scores which are based on different types of text granularity when comparing the generated and reference summaries. The `rouge1` variant is the overlap of unigrams -- this is just a fancy way of saying the overlap of words and is exactly the metric we've discussed above. To verify this, let's pull out the `mid` value of our scores: +Whoa, există o mulțime de informații în aceast output - ce înseamnă toate acestea? În primul rând, 🤗 Datasets calculează de fapt confidence intervalurile pentru precision, recall și scorul F1; acestea sunt atributele `low`, `mid` și `high` pe care le puteți vedea aici. În plus, 🤗 Datasets calculează o varietate de scoruri ROUGE care se bazează pe diferite tipuri de granularitate a textului atunci când compară rezumatele generate și de referință. Varianta `rouge1` este suprapunerea unigramelor - acesta este doar un mod elegant de a spune suprapunerea cuvintelor și este exact metrica pe care am discutat-o mai sus. Pentru a verifica acest lucru, să extragem valoarea `mid` a scorurilor noastre: ```python scores["rouge1"].mid @@ -382,25 +384,25 @@ scores["rouge1"].mid Score(precision=0.86, recall=1.0, fmeasure=0.92) ``` -Great, the precision and recall numbers match up! Now what about those other ROUGE scores? `rouge2` measures the overlap between bigrams (think the overlap of pairs of words), while `rougeL` and `rougeLsum` measure the longest matching sequences of words by looking for the longest common substrings in the generated and reference summaries. The "sum" in `rougeLsum` refers to the fact that this metric is computed over a whole summary, while `rougeL` is computed as the average over individual sentences. +Grozav, numerele de precision și de recall se potrivesc! Acum ce se întâmplă cu celelalte scoruri ROUGE? `rouge2` măsoară suprapunerea dintre bigrame (suprapunerea perechilor de cuvinte), în timp ce `rougeL` și `rougeLsum` măsoară cele mai lungi secvențe de cuvinte care se potrivesc, căutând cele mai lungi substraturi comune în rezumatele generate și de referință. Termenul "sum" din `rougeLsum` se referă la faptul că această metrică este calculată pentru un rezumat întreg, în timp ce `rougeL` este calculată ca medie a propozițiilor individuale. -✏️ **Try it out!** Create your own example of a generated and reference summary and see if the resulting ROUGE scores agree with a manual calculation based on the formulas for precision and recall. For bonus points, split the text into bigrams and compare the precision and recall for the `rouge2` metric. +✏️ **Încercați!** Creați propriul exemplu de rezumat generat și de referință și vedeți dacă scorurile ROUGE rezultate sunt în concordanță cu un calcul manual bazat pe formulele de precision și recall. Pentru puncte bonus, împărțiți textul în bigrame și comparați precizia și recallul pentru metrica `rouge2`. -We'll use these ROUGE scores to track the performance of our model, but before doing that let's do something every good NLP practitioner should do: create a strong, yet simple baseline! +Vom folosi aceste scoruri ROUGE pentru a urmări performanța modelului nostru, dar înainte de a face acest lucru, să facem ceva ce orice bun practician NLP ar trebui să facă: să creăm un baseline puternic, dar simplu! -### Creating a strong baseline[[creating-a-strong-baseline]] +### Crearea unui baseline bun[[creating-a-strong-baseline]] -A common baseline for text summarization is to simply take the first three sentences of an article, often called the _lead-3_ baseline. We could use full stops to track the sentence boundaries, but this will fail on acronyms like "U.S." or "U.N." -- so instead we'll use the `nltk` library, which includes a better algorithm to handle these cases. You can install the package using `pip` as follows: +un baseline obișnuit pentru rezumarea textului este de a lua pur și simplu primele trei propoziții ale unui articol, adesea numit _lead-3_ baseline. Am putea folosi puncte de oprire pentru a urmări limitele propoziției, dar acest lucru va eșua în cazul acronimelor precum "U.S." sau "U.N." - așa că vom folosi în schimb biblioteca `nltk`, care include un algoritm mai bun pentru a gestiona aceste cazuri. Puteți instala pachetul folosind `pip` după cum urmează: ```python !pip install nltk ``` -and then download the punctuation rules: +și apoi descărcați regulile de punctuație: ```python import nltk @@ -408,7 +410,7 @@ import nltk nltk.download("punkt") ``` -Next, we import the sentence tokenizer from `nltk` and create a simple function to extract the first three sentences in a review. The convention in text summarization is to separate each summary with a newline, so let's also include this and test it on a training example: +În continuare, importăm tokenizerul de propoziții din `nltk` și creăm o funcție simplă pentru a extrage primele trei propoziții dintr-o recenzie. Convenția în rezumarea textului este de a separa fiecare rezumat cu o linie nouă, deci să includem și aceasta și să o testăm pe un exemplu de antrenare: ```python from nltk.tokenize import sent_tokenize @@ -427,7 +429,7 @@ print(three_sentence_summary(books_dataset["train"][1]["review_body"])) 'She found Strangers.' ``` -This seems to work, so let's now implement a function that extracts these "summaries" from a dataset and computes the ROUGE scores for the baseline: +Acest lucru pare să funcționeze, deci să implementăm acum o funcție care extrage aceste "rezumate" dintr-un dataset și calculează scorurile ROUGE pentru baseline: ```python def evaluate_baseline(dataset, metric): @@ -435,7 +437,7 @@ def evaluate_baseline(dataset, metric): return metric.compute(predictions=summaries, references=dataset["review_title"]) ``` -We can then use this function to compute the ROUGE scores over the validation set and prettify them a bit using Pandas: +Putem folosi apoi această funcție pentru a calcula scorurile ROUGE pe setul de validare și pentru a le înfrumuseța puțin folosind Pandas: ```python import pandas as pd @@ -450,13 +452,13 @@ rouge_dict {'rouge1': 16.74, 'rouge2': 8.83, 'rougeL': 15.6, 'rougeLsum': 15.96} ``` -We can see that the `rouge2` score is significantly lower than the rest; this likely reflects the fact that review titles are typically concise and so the lead-3 baseline is too verbose. Now that we have a good baseline to work from, let's turn our attention toward fine-tuning mT5! +Putem vedea că scorul `rouge2` este semnificativ mai mic decât restul; acest lucru reflectă probabil faptul că titlurile recenziilor sunt de obicei concise și, prin urmare, baselineul lead-3 are prea multe cuvinte. Acum, că avem un baseline bun de lucru, să ne îndreptăm atenția către fine-tuningul mT5! {#if fw === 'pt'} -## Fine-tuning mT5 with the `Trainer` API[[fine-tuning-mt5-with-the-trainer-api]] +## Fine-tuningul mT5 cu API-ul `Trainer`[[fine-tuning-mt5-with-the-trainer-api]] -Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `AutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: +Fine-tuningul unui model pentru rezumare este foarte asemănător cu celelalte sarcini pe care le-am acoperit în acest capitol. Primul lucru pe care trebuie să îl facem este să încărcăm modelul preantrenat din checkpointul `mt5-small`. Deoarece sumarizarea este o sarcină de la secvență la secvență, putem încărca modelul cu clasa `AutoModelForSeq2SeqLM`, care va descărca automat și va stoca în cache weighturile: ```python from transformers import AutoModelForSeq2SeqLM @@ -466,9 +468,9 @@ model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) {:else} -## Fine-tuning mT5 with Keras[[fine-tuning-mt5-with-keras]] +## Fine-tuningul mT5 cu Keras[[fine-tuning-mt5-with-keras]] -Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `TFAutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: +Fine-tuningul unui model pentru sumarizare este foarte asemănătoare cu celelalte sarcini pe care le-am acoperit în acest capitol. Primul lucru pe care trebuie să îl facem este să încărcăm modelul preantrenat din punctul de control `mt5-small`. Deoarece rezumarea este o sarcină de la secvență la secvență, putem încărca modelul cu clasa `TFAutoModelForSeq2SeqLM`, care va descărca și va stoca în cache weighturile: ```python from transformers import TFAutoModelForSeq2SeqLM @@ -480,11 +482,11 @@ model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -💡 If you're wondering why you don't see any warnings about fine-tuning the model on a downstream task, that's because for sequence-to-sequence tasks we keep all the weights of the network. Compare this to our text classification model in [Chapter 3](/course/chapter3), where the head of the pretrained model was replaced with a randomly initialized network. +💡 Dacă vă întrebați de ce nu vedeți niciun avertisment cu privire la fine-tuningul modelului pe un downstream task, acest lucru se datorează faptului că pentru sarcinile secvență-la-secvență păstrăm toate weighturile rețelei. Comparați acest lucru cu modelul nostru de clasificare a textului din [Capitolul 3](/course/chapter3), unde headul modelului preantrenat a fost înlocuit cu o rețea inițializată aleatoriu. -The next thing we need to do is log in to the Hugging Face Hub. If you're running this code in a notebook, you can do so with the following utility function: +Următorul lucru pe care trebuie să îl facem este să ne conectăm la Hugging Face Hub. Dacă executați acest cod într-un notebook, puteți face acest lucru cu următoarea funcție: ```python from huggingface_hub import notebook_login @@ -492,7 +494,7 @@ from huggingface_hub import notebook_login notebook_login() ``` -which will display a widget where you can enter your credentials. Alternatively, you can run this command in your terminal and log in there: +care va afișa un widget în care puteți introduce credențialele. Alternativ, puteți rula această comandă în terminal și să vă conectați acolo: ``` huggingface-cli login @@ -500,7 +502,7 @@ huggingface-cli login {#if fw === 'pt'} -We'll need to generate summaries in order to compute ROUGE scores during training. Fortunately, 🤗 Transformers provides dedicated `Seq2SeqTrainingArguments` and `Seq2SeqTrainer` classes that can do this for us automatically! To see how this works, let's first define the hyperparameters and other arguments for our experiments: +Va trebui să generăm rezumate pentru a calcula scorurile ROUGE în timpul antrenării. Din fericire, 🤗 Transformers oferă clase dedicate `Seq2SeqTrainingArguments` și `Seq2SeqTrainer` care pot face acest lucru pentru noi în mod automat! Pentru a vedea cum funcționează acest lucru, să definim mai întâi hiperparametrii și alte argumente pentru experimentele noastre: ```python from transformers import Seq2SeqTrainingArguments @@ -526,11 +528,11 @@ args = Seq2SeqTrainingArguments( ) ``` -Here, the `predict_with_generate` argument has been set to indicate that we should generate summaries during evaluation so that we can compute ROUGE scores for each epoch. As discussed in [Chapter 1](/course/chapter1), the decoder performs inference by predicting tokens one by one, and this is implemented by the model's `generate()` method. Setting `predict_with_generate=True` tells the `Seq2SeqTrainer` to use that method for evaluation. We've also adjusted some of the default hyperparameters, like the learning rate, number of epochs, and weight decay, and we've set the `save_total_limit` option to only save up to 3 checkpoints during training -- this is because even the "small" version of mT5 uses around a GB of hard drive space, and we can save a bit of room by limiting the number of copies we save. +Aici, argumentul `predict_with_generate` a fost setat pentru a indica faptul că ar trebui să generăm rezumate în timpul evaluării, astfel încât să putem calcula scorurile ROUGE pentru fiecare epocă. După cum s-a discutat în [Capitolul 1](/course/chapter1), decodificatorul realizează inference-ul prin prezicerea tokenilor unul câte unul, iar acest lucru este implementat de metoda `generate()` a modelului. Setarea `predict_with_generate=True` îi spune lui `Seq2SeqTrainer` să utilizeze această metodă pentru evaluare. Am ajustat, de asemenea, unii dintre hiperparametrii impliciți, cum ar fi rata de învățare, numărul de epoci și scăderea weighturilor și am setat opțiunea `save_total_limit` pentru a salva numai până la 3 checkpointuri în timpul antrenamentului - acest lucru se datorează faptului că chiar și versiunea "mică" a mT5 utilizează aproximativ 1 GB de spațiu pe hard disk și putem economisi puțin spațiu prin limitarea numărului de copii salvate. -The `push_to_hub=True` argument will allow us to push the model to the Hub after training; you'll find the repository under your user profile in the location defined by `output_dir`. Note that you can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` to `Seq2SeqTrainingArguments`. +Argumentul `push_to_hub=True` ne va permite să trimitem modelul în Hub după antrenare; veți găsi repositoriul în profilul vostru de utilizator, în locația definită de `output_dir`. Rețineți că puteți specifica numele repositoriului către care doriți să trimiteți modelul cu argumentul `hub_model_id` (în special, va trebui să utilizați acest argument pentru a trimite modelul către o organizație). De exemplu, atunci când am trimis modelul către organizația [`huggingface-course`](https://huggingface.co/huggingface-course), am adăugat `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` la `Seq2SeqTrainingArguments`. -The next thing we need to do is provide the trainer with a `compute_metrics()` function so that we can evaluate our model during training. For summarization this is a bit more involved than simply calling `rouge_score.compute()` on the model's predictions, since we need to _decode_ the outputs and labels into text before we can compute the ROUGE scores. The following function does exactly that, and also makes use of the `sent_tokenize()` function from `nltk` to separate the summary sentences with newlines: +Următorul lucru pe care trebuie să îl facem este să oferim trainerului o funcție `compute_metrics()`, astfel încât să ne putem evalua modelul în timpul antrenării. Pentru sumarizare, acest lucru este un pic mai complicat decât simpla apelare a funcției `rouge_score.compute()` pentru predicțiile modelului, deoarece trebuie să _decodăm_ rezultatele și labelurile din text înainte de a putea calcula scorurile ROUGE. Următoarea funcție face exact acest lucru și, de asemenea, utilizează funcția `sent_tokenize()` din `nltk` pentru a separa propozițiile rezumate cu linii noi: ```python import numpy as np @@ -558,9 +560,9 @@ def compute_metrics(eval_pred): {/if} -Next, we need to define a data collator for our sequence-to-sequence task. Since mT5 is an encoder-decoder Transformer model, one subtlety with preparing our batches is that during decoding we need to shift the labels to the right by one. This is required to ensure that the decoder only sees the previous ground truth labels and not the current or future ones, which would be easy for the model to memorize. This is similar to how masked self-attention is applied to the inputs in a task like [causal language modeling](/course/chapter7/6). +În continuare, trebuie să definim un data collator pentru sarcina noastră secvență-la-secvență. Deoarece mT5 este un transformer model encoder-decoder, o subtilitate a pregătirii batch-urilor noastre este că, în timpul decodificării, trebuie să deplasăm labelurile la dreapta cu una. Acest lucru este necesar pentru a ne asigura că decodificatorul vede doar labelurile anterioare ale adevărului de bază și nu pe cele actuale sau viitoare, care ar fi ușor de memorat de către model. Acest lucru este similar cu modul în care masked self-attention este aplicată inputurilor într-o sarcină precum [causal language modeling](/course/chapter7/6). -Luckily, 🤗 Transformers provides a `DataCollatorForSeq2Seq` collator that will dynamically pad the inputs and the labels for us. To instantiate this collator, we simply need to provide the `tokenizer` and `model`: +Din fericire, 🤗 Transformers oferă un collator `DataCollatorForSeq2Seq` care va face padding dinamic inputurilor și labelurilor pentru noi. Pentru a inițializa acest collator, trebuie doar să furnizăm `tokenizer` și `model`: {#if fw === 'pt'} @@ -580,7 +582,7 @@ data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="t {/if} -Let's see what this collator produces when fed a small batch of examples. First, we need to remove the columns with strings because the collator won't know how to pad these elements: +Să vedem ce produce acest collator atunci când este alimentat cu un mic batch de exemple. În primul rând, trebuie să eliminăm coloanele cu șiruri de caractere, deoarece collatorul nu va ști cum să completeze aceste elemente: ```python tokenized_datasets = tokenized_datasets.remove_columns( @@ -588,7 +590,7 @@ tokenized_datasets = tokenized_datasets.remove_columns( ) ``` -Since the collator expects a list of `dict`s, where each `dict` represents a single example in the dataset, we also need to wrangle the data into the expected format before passing it to the data collator: +Deoarece collatorul așteaptă o listă de `dict`, unde fiecare `dict` reprezintă un singur exemplu din dataset, trebuie să transformăm datele în formatul așteptat înainte de a le transmite data collatorului: ```python features = [tokenized_datasets["train"][i] for i in range(2)] @@ -611,11 +613,11 @@ data_collator(features) [ 0, 259, 27531, 13483, 259, 7505]])} ``` -The main thing to notice here is that the first example is longer than the second one, so the `input_ids` and `attention_mask` of the second example have been padded on the right with a `[PAD]` token (whose ID is `0`). Similarly, we can see that the `labels` have been padded with `-100`s, to make sure the padding tokens are ignored by the loss function. And finally, we can see a new `decoder_input_ids` which has shifted the labels to the right by inserting a `[PAD]` token in the first entry. +Principalul lucru care trebuie observat aici este că primul exemplu este mai lung decât al doilea, astfel încât `input_ids` și `attention_mask` din al doilea exemplu au primit padding în dreapta cu un simbol `[PAD]` (al cărui ID este `0`). În mod similar, putem vedea că `labels` au primit padding cu `-100`, pentru a ne asigura că tokenii de padding sunt ignorați de funcția de pierdere. În sfârșit, putem vedea un nou `decoder_input_ids` care a deplasat labelurile spre dreapta prin inserarea unui simbol `[PAD]` în prima intrare. {#if fw === 'pt'} -We finally have all the ingredients we need to train with! We now simply need to instantiate the trainer with the standard arguments: +Avem în sfârșit toate ingredientele de care avem nevoie pentru a antrenare! Acum trebuie doar să inițializăm trainerul cu argumentele standard: ```python from transformers import Seq2SeqTrainer @@ -631,13 +633,13 @@ trainer = Seq2SeqTrainer( ) ``` -and launch our training run: +și să lansăm cursa noastră de antrenare: ```python trainer.train() ``` -During training, you should see the training loss decrease and the ROUGE scores increase with each epoch. Once the training is complete, you can see the final ROUGE scores by running `Trainer.evaluate()`: +În timpul antrenamentului, ar trebui să vedeți cum training loss scade și scorurile ROUGE cresc cu fiecare epocă. După ce antrenamentul este complet, puteți vedea scorurile ROUGE finale executând `Trainer.evaluate()`: ```python trainer.evaluate() @@ -655,7 +657,7 @@ trainer.evaluate() 'eval_steps_per_second': 4.914} ``` -From the scores we can see that our model has handily outperformed our lead-3 baseline -- nice! The final thing to do is push the model weights to the Hub, as follows: +Din scorurile obținute, putem vedea că modelul nostru a depășit cu mult modelul nostru de bază lead-3 - frumos! Ultimul lucru de făcut este să introducem weighturile modelului în Hub, după cum urmează: ``` trainer.push_to_hub(commit_message="Training complete", tags="summarization") @@ -665,13 +667,13 @@ trainer.push_to_hub(commit_message="Training complete", tags="summarization") 'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' ``` -This will save the checkpoint and configuration files to `output_dir`, before uploading all the files to the Hub. By specifying the `tags` argument, we also ensure that the widget on the Hub will be one for a summarization pipeline instead of the default text generation one associated with the mT5 architecture (for more information about model tags, see the [🤗 Hub documentation](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). The output from `trainer.push_to_hub()` is a URL to the Git commit hash, so you can easily see the changes that were made to the model repository! +Acest lucru va salva checkpointul și fișierele de configurare în `output_dir`, înainte de a încărca toate fișierele în Hub. Specificând argumentul `tags`, ne asigurăm, de asemenea, că widgetul de pe Hub va fi unul pentru un pipeline de rezumare în locul celui implicit de generare de text asociat arhitecturii mT5 (pentru mai multe informații despre labelurile modelului, consultați [🤗Documentația Hub](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). Rezultatul din `trainer.push_to_hub()` este o adresă URL către hash-ul Git commit, astfel încât să puteți vedea cu ușurință modificările care au fost făcute în repositoriul modelului! -To wrap up this section, let's take a look at how we can also fine-tune mT5 using the low-level features provided by 🤗 Accelerate. +Pentru a încheia această secțiune, să aruncăm o privire la modul în care putem, de asemenea, să facem fine-tune la mT5 folosind featururile de nivel scăzut oferite de 🤗 Accelerate. {:else} -We're almost ready to train! We just need to convert our datasets to `tf.data.Dataset`s using the data collator we defined above, and then `compile()` and `fit()` the model. First, the datasets: +Suntem aproape gata de antrenament! Trebuie doar să convertim dataseturile în `tf.data.Dataset`s folosind data collatorul pe care l-am definit mai sus, iar apoi aplicarea `compil()` și `fit()` modelul. Mai întâi, dataseturile: ```python tf_train_dataset = model.prepare_tf_dataset( @@ -688,7 +690,7 @@ tf_eval_dataset = model.prepare_tf_dataset( ) ``` -Now, we define our training hyperparameters and compile: +Acum, ne definim hiperparametrii de antrenare și compilare: ```python from transformers import create_optimizer @@ -714,7 +716,7 @@ model.compile(optimizer=optimizer) tf.keras.mixed_precision.set_global_policy("mixed_float16") ``` -And finally, we fit the model. We use a `PushToHubCallback` to save the model to the Hub after each epoch, which will allow us to use it for inference later: +În cele din urmă, facem fit modelului modelului. Folosim un `PushToHubCallback` pentru a salva modelul în Hub după fiecare epocă, ceea ce ne va permite să îl folosim ulterior pentru inference: ```python from transformers.keras_callbacks import PushToHubCallback @@ -728,7 +730,7 @@ model.fit( ) ``` -We got some loss values during training, but really we'd like to see the ROUGE metrics we computed earlier. To get those metrics, we'll need to generate outputs from the model and convert them to strings. Let's build some lists of labels and predictions for the ROUGE metric to compare (note that if you get import errors for this section, you may need to`!pip install tqdm`). We're also going to use a trick that dramatically increases performance - compiling our generation code with [XLA](https://www.tensorflow.org/xla), TensorFlow's accelerated linear algebra compiler. XLA applies various optimizations to the model's computation graph, and results in significant improvements to speed and memory usage. As described in the Hugging Face [blog](https://huggingface.co/blog/tf-xla-generate), XLA works best when our input shapes don't vary too much. To handle this, we'll pad our inputs to multiples of 128, and make a new dataset with the padding collator, and then we'll apply the `@tf.function(jit_compile=True)` decorator to our generation function, which marks the whole function for compilation with XLA. +Am obținut unele valori ale pierderilor în timpul antrenării, dar am dori să vedem metricile ROUGE pe care le-am calculat mai devreme. Pentru a obține aceste metrici, va trebui să generăm outputuri din model și să le convertim în șiruri de caractere. Să construim câteva liste de labels și predicții pentru a compara metrica ROUGE (rețineți că, dacă obțineți erori de import pentru această secțiune, este posibil să fie necesar să faceți `!pip install tqdm`). De asemenea, vom utiliza un truc care crește dramatic performanța - compilarea codului nostru de generare cu [XLA](https://www.tensorflow.org/xla), compilatorul accelerat de algebră liniară al TensorFlow. XLA aplică diverse optimizări graficului de calcul al modelului și are ca rezultat îmbunătățiri semnificative ale vitezei și utilizării memoriei. După cum se descrie în [blogul Hugging Face](https://huggingface.co/blog/tf-xla-generate), XLA funcționează cel mai bine atunci când formele noastre de input nu variază prea mult. Pentru a face față acestui lucru, vom face padding inputurilor la multipli a 128 și vom crea un nou dataset cu padding collatorul, iar apoi vom aplica decoratorul `@tf.function(jit_compile=True)` funcției noastre de generare, care marchează întreaga funcție pentru compilare cu XLA. ```python from tqdm import tqdm @@ -770,7 +772,7 @@ for batch, labels in tqdm(tf_generate_dataset): all_labels.extend(decoded_labels) ``` -Once we have our lists of label and prediction strings, computing the ROUGE score is easy: +Odată ce avem listele noastre de labeluri și de șiruri de predicție, calcularea scorului ROUGE este ușoară: ```python result = rouge_score.compute( @@ -789,25 +791,25 @@ result = {key: value.mid.fmeasure * 100 for key, value in result.items()} {#if fw === 'pt'} -## Fine-tuning mT5 with 🤗 Accelerate[[fine-tuning-mt5-with-accelerate]] +## Fine-tuningul mT5 cu 🤗 Accelerate[[fine-tuning-mt5-with-accelerate]] -Fine-tuning our model with 🤗 Accelerate is very similar to the text classification example we encountered in [Chapter 3](/course/chapter3). The main differences will be the need to explicitly generate our summaries during training and define how we compute the ROUGE scores (recall that the `Seq2SeqTrainer` took care of the generation for us). Let's take a look how we can implement these two requirements within 🤗 Accelerate! +Fine-tuningul nostru cu 🤗 Accelerate este foarte asemănător cu exemplul de clasificare a textului pe care l-am întâlnit în [Capitolul 3](/course/chapter3). Principalele diferențe vor fi necesitatea de a genera în mod explicit rezumatele noastre în timpul antrenării și de a defini modul în care calculăm scorurile ROUGE (reamintim că `Seq2SeqTrainer` a avut grijă de generare pentru noi). Să aruncăm o privire la modul în care putem implementa aceste două cerințe în cadrul 🤗 Accelerate! -### Preparing everything for training[[preparing-everything-for-training]] +### Pregătirea pentru antrenare[[preparing-everything-for-training]] -The first thing we need to do is create a `DataLoader` for each of our splits. Since the PyTorch dataloaders expect batches of tensors, we need to set the format to `"torch"` in our datasets: +Primul lucru pe care trebuie să-l facem este să creăm un `DataLoader` pentru fiecare dintre spliturile noastre. Deoarece dataloaders PyTorch așteaptă batchuri de tensori, trebuie să setăm formatul la `"torch"` în dataseturile noastre: ```python tokenized_datasets.set_format("torch") ``` -Now that we've got datasets consisting of just tensors, the next thing to do is instantiate the `DataCollatorForSeq2Seq` again. For this we need to provide a fresh version of the model, so let's load it again from our cache: +Acum că avem dataseturi formate doar din tensori, următorul lucru este inițializarea `DataCollatorForSeq2Seq`. Pentru aceasta trebuie să furnizăm o versiune nouă a modelului, așa că hai să îl încărcăm din nou din cache: ```python model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) ``` -We can then instantiate the data collator and use this to define our dataloaders: +Putem apoi să instanțiem data collatorul și să îl folosim pentru a ne defini dataloaders: ```python from torch.utils.data import DataLoader @@ -824,7 +826,7 @@ eval_dataloader = DataLoader( ) ``` -The next thing to do is define the optimizer we want to use. As in our other examples, we'll use `AdamW`, which works well for most problems: +Următorul lucru este definirea optimizatorului pe care dorim să îl utilizăm. Ca și în celelalte exemple, vom folosi `AdamW`, care funcționează bine pentru majoritatea problemelor: ```python from torch.optim import AdamW @@ -832,7 +834,7 @@ from torch.optim import AdamW optimizer = AdamW(model.parameters(), lr=2e-5) ``` -Finally, we feed our model, optimizer, and dataloaders to the `accelerator.prepare()` method: +În cele din urmă, introducem modelul, optimizatorul și dataloaders în metoda `accelerator.prepare()`: ```python from accelerate import Accelerator @@ -845,17 +847,17 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( -🚨 If you're training on a TPU, you'll need to move all the code above into a dedicated training function. See [Chapter 3](/course/chapter3) for more details. +🚨 Dacă faceți antrenarea pe un TPU, va trebui să mutați tot codul de mai sus într-o funcție de antrenare aparte. Consultați [Capitolul 3](/course/chapter3) pentru mai multe detalii. -Now that we've prepared our objects, there are three remaining things to do: +Acum că ne-am pregătit obiectele, mai avem trei lucruri de făcut: -* Define the learning rate schedule. -* Implement a function to post-process the summaries for evaluation. -* Create a repository on the Hub that we can push our model to. +* Definirea learning rate schedule. +* Implementarea unei funcții de post-procesare a rezumatelor pentru evaluare. +* Crearea unui repositoriu pe Hub în care să putem trimite modelul nostru. -For the learning rate schedule, we'll use the standard linear one from previous sections: +Pentru learning rate schedule, îl vom utiliza pe cel liniar standard din secțiunile anterioare: ```python from transformers import get_scheduler @@ -872,7 +874,7 @@ lr_scheduler = get_scheduler( ) ``` -For post-processing, we need a function that splits the generated summaries into sentences that are separated by newlines. This is the format the ROUGE metric expects, and we can achieve this with the following snippet of code: +Pentru post-procesare, avem nevoie de o funcție care să împartă rezumatele generate în propoziții separate prin linii noi. Acesta este formatul pe care îl așteaptă metrica ROUGE, iar noi putem realiza acest lucru cu următorul fragment de cod: ```python def postprocess_text(preds, labels): @@ -886,9 +888,9 @@ def postprocess_text(preds, labels): return preds, labels ``` -This should look familiar to you if you recall how we defined the `compute_metrics()` function of the `Seq2SeqTrainer`. +Acest lucru ar trebui să vă pară familiar dacă vă amintiți cum am definit funcția `compute_metrics()` a `Seq2SeqTrainer`. -Finally, we need to create a model repository on the Hugging Face Hub. For this, we can use the appropriately titled 🤗 Hub library. We just need to define a name for our repository, and the library has a utility function to combine the repository ID with the user profile: +În cele din urmă, trebuie să creăm un repositoriu de modele pe Hugging Face Hub. Pentru aceasta, putem utiliza biblioteca 🤗 Hub intitulată corespunzător . Trebuie doar să definim un nume pentru repositoriul nostru, iar biblioteca are o funcție utilitară pentru a combina ID-ul repositoriul cu profilul utilizatorului: ```python from huggingface_hub import get_full_repo_name @@ -902,7 +904,7 @@ repo_name 'lewtun/mt5-finetuned-amazon-en-es-accelerate' ``` -Now we can use this repository name to clone a local version to our results directory that will store the training artifacts: +Acum putem folosi numele repositoriului pentru a clona o versiune locală în folderul nostru cu rezultate care va stoca artefactele de antrenare: ```python from huggingface_hub import Repository @@ -911,18 +913,18 @@ output_dir = "results-mt5-finetuned-squad-accelerate" repo = Repository(output_dir, clone_from=repo_name) ``` -This will allow us to push the artifacts back to the Hub by calling the `repo.push_to_hub()` method during training! Let's now wrap up our analysis by writing out the training loop. +Acest lucru ne va permite să trimitem artefactele înapoi la Hub prin apelarea metodei `repo.push_to_hub()` în timpul antrenării! Să încheiem acum analiza noastră prin scrierea buclei de antrenare. -### Training loop[[training-loop]] +### Bucla de antrenare[[training-loop]] -The training loop for summarization is quite similar to the other 🤗 Accelerate examples that we've encountered and is roughly split into four main steps: +Bucla de formare pentru sumarizare este destul de asemănătoare cu celelalte exemple 🤗 Accelerate pe care le-am întâlnit și este împărțită aproximativ în patru etape principale: -1. Train the model by iterating over all the examples in `train_dataloader` for each epoch. -2. Generate model summaries at the end of each epoch, by first generating the tokens and then decoding them (and the reference summaries) into text. -3. Compute the ROUGE scores using the same techniques we saw earlier. -4. Save the checkpoints and push everything to the Hub. Here we rely on the nifty `blocking=False` argument of the `Repository` object so that we can push the checkpoints per epoch _asynchronously_. This allows us to continue training without having to wait for the somewhat slow upload associated with a GB-sized model! +1. Antrenarea modelului prin iterarea peste toate exemplele din `train_dataloader` pentru fiecare epocă. +2. Generarea rezumatelor modelului la sfârșitul fiecărei epoci, mai întâi prin generarea token-urilor și apoi prin decodarea lor (și a rezumatelor de referință) în text. +3. Calcularea scorurilor ROUGE folosind aceleași tehnici pe care le-am văzut mai devreme. +4. Salvați checkpointurile și încărcați totul pe Hub. Aici ne bazăm pe argumentul `blocking=False` al obiectului `Repository` astfel încât să putem împinge checkointurile pentru fiecare epocă _asincron_. Acest lucru ne permite să continuăm antrenamentul fără a fi nevoiți să așteptăm încărcarea oarecum lentă asociată cu un model de dimensiunea unui GB! -These steps can be seen in the following block of code: +Acești pași pot fi observați în următorul bloc de cod: ```python from tqdm.auto import tqdm @@ -1012,13 +1014,13 @@ Epoch 8: {'rouge1': 13.96, 'rouge2': 6.5998, 'rougeL': 13.9123, 'rougeLsum': 13. Epoch 9: {'rouge1': 14.1192, 'rouge2': 7.0059, 'rougeL': 14.1172, 'rougeLsum': 13.9509} ``` -And that's it! Once you run this, you'll have a model and results that are pretty similar to the ones we obtained with the `Trainer`. +Și asta e tot! După ce executați acest lucru, veți avea un model și rezultate care sunt destul de asemănătoare cu cele obținute cu `Trainer`. {/if} -## Using your fine-tuned model[[using-your-fine-tuned-model]] +## Utilizarea modelului fine-tuned[[using-your-fine-tuned-model]] -Once you've pushed the model to the Hub, you can play with it either via the inference widget or with a `pipeline` object, as follows: +Odată ce ați încărcat modelul în Hub, vă puteți juca cu el prin widgetul de inference, fie cu un obiect `pipeline`, după cum urmează: ```python from transformers import pipeline @@ -1027,7 +1029,7 @@ hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" summarizer = pipeline("summarization", model=hub_model_id) ``` -We can feed some examples from the test set (which the model has not seen) to our pipeline to get a feel for the quality of the summaries. First let's implement a simple function to show the review, title, and generated summary together: +Putem introduce câteva exemple din setul de testare (pe care modelul nu le-a văzut) în pipelineul noastru pentru a avea o idee despre calitatea rezumatelor. Mai întâi, să implementăm o funcție simplă pentru a afișa împreună recenzia, titlul și rezumatul generat: ```python def print_summary(idx): @@ -1039,7 +1041,7 @@ def print_summary(idx): print(f"\n'>>> Summary: {summary}'") ``` -Let's take a look at one of the English examples we get: +Să aruncăm o privire la unul dintre exemplele englezești pe care le primim: ```python print_summary(100) @@ -1067,6 +1069,6 @@ print_summary(0) '>>> Summary: Muy facil de leer' ``` -The summary translates into "Very easy to read" in English, which we can see in this case was extracted directly from the review. Nevertheless, this shows the versatility of the mT5 model and has given you a taste of what it's like to deal with a multilingual corpus! +Rezumatul se traduce prin "Very easy to read" în limba engleză, ceea ce putem vedea că în acest caz a fost extras direct din recenzie. Cu toate acestea, acest lucru arată versatilitatea modelului mT5 și v-a dat o idee despre cum este să aveți de-a face cu un corpus multilingv! -Next, we'll turn our attention to a slightly more complex task: training a language model from scratch. +În continuare, ne vom îndrepta atenția către o sarcină puțin mai complexă: antrenarea unui model lingvistic de la zero. \ No newline at end of file diff --git a/chapters/ro/chapter7/6.mdx b/chapters/ro/chapter7/6.mdx index 44551f15d..c47f46030 100644 --- a/chapters/ro/chapter7/6.mdx +++ b/chapters/ro/chapter7/6.mdx @@ -1,6 +1,6 @@ -# Training a causal language model from scratch[[training-a-causal-language-model-from-scratch]] +# Antrenarea unui model de limbaj cauzal de la zero[[training-a-causal-language-model-from-scratch]] {#if fw === 'pt'} @@ -22,23 +22,23 @@ {/if} -Up until now, we've mostly been using pretrained models and fine-tuning them for new use cases by reusing the weights from pretraining. As we saw in [Chapter 1](/course/chapter1), this is commonly referred to as _transfer learning_, and it's a very successful strategy for applying Transformer models to most real-world use cases where labeled data is sparse. In this chapter, we'll take a different approach and train a completely new model from scratch. This is a good approach to take if you have a lot of data and it is very different from the pretraining data used for the available models. However, it also requires considerably more compute resources to pretrain a language model than just to fine-tune an existing one. Examples where it can make sense to train a new model include for datasets consisting of musical notes, molecular sequences such as DNA, or programming languages. The latter have recently gained traction thanks to tools such as TabNine and GitHub's Copilot, powered by OpenAI's Codex model, that can generate long sequences of code. This task of text generation is best addressed with auto-regressive or causal language models such as GPT-2. +Până acum, am folosit în principal modele preantrenate și le-am făcut fine-tuning pentru noi cazuri de utilizare prin reutilizarea weighturilor din preantrenare. După cum am văzut în [Capitolul 1](/course/chapter1), acest lucru este denumit în mod obișnuit _învățare prin transfer_ și este o strategie foarte reușită pentru aplicarea modelelor Transformer la majoritatea cazurilor de utilizare din lumea reală în care datele etichetate sunt puține. În acest capitol, vom adopta o abordare diferită și vom antrena un model complet nou de la zero. Aceasta este o abordare bună dacă aveți multe date și este foarte diferită de datele de preantrenare utilizate pentru modelele disponibile. Cu toate acestea, preantrenarea unui model lingvistic necesită, de asemenea, mult mai multe resurse de calcul decât fine-tuningul unui model existent. Printre exemplele în care poate fi utilă antrenarea unui nou model se numără dataseturile formate din note muzicale, secvențe moleculare precum ADN sau limbaje de programare. Acestea din urmă au câștigat recent teren datorită unor instrumente precum TabNine și Copilot de la GitHub, alimentate de modelul Codex al OpenAI, care pot genera secvențe lungi de cod. Această sarcină de generare a textului este cel mai bine abordată cu modele de limbaj autoregresive sau cauzale, cum ar fi GPT-2. -In this section we will build a scaled-down version of a code generation model: we'll focus on one-line completions instead of full functions or classes, using a subset of Python code. When working with data in Python you are in frequent contact with the Python data science stack, consisting of the `matplotlib`, `seaborn`, `pandas`, and `scikit-learn` libraries. When using those frameworks it's common to need to look up specific commands, so it would be nice if we could use a model to complete these calls for us. +În această secțiune vom construi o versiune la scară redusă a unui model de generare a codului: ne vom concentra pe completări de o linie în loc de funcții sau clase complete, folosind un subset de cod Python. Atunci când lucrați cu date în Python, sunteți în contact frecvent Python data science stack, formată din bibliotecile `matplotlib`, `seaborn`, `pandas` și `scikit-learn`. Atunci când se utilizează aceste cadre, este frecvent să fie nevoie să se caute comenzi specifice, astfel încât ar fi bine dacă am putea utiliza un model care să efectueze aceste apeluri pentru noi. -In [Chapter 6](/course/chapter6) we created an efficient tokenizer to process Python source code, but what we still need is a large-scale dataset to pretrain a model on. Here, we'll apply our tokenizer to a corpus of Python code derived from GitHub repositories. We will then use the `Trainer` API and 🤗 Accelerate to train the model. Let's get to it! +În [Capitolul 6](/course/chapter6) am creat un tokenizer eficient pentru a procesa codul sursă Python, dar avem nevoie de un dataset la scară largă pe care să preantrenăm un model. Aici, vom aplica tokenizerul nostru la un corpus de cod Python derivat din repositoriile GitHub. Vom utiliza apoi API-ul `Trainer` și 🤗 Accelerate pentru a antrena modelul. Să trecem la treabă! -This is actually showcasing the model that was trained and uploaded to the Hub using the code shown in this section. You can find it [here](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Note that since there is some randomization happening in the text generation, you will probably get a slightly different result. +Aceasta este de fapt o prezentare a modelului care a fost antrenat și încărcat în Hub folosind codul prezentat în această secțiune. Îl puteți găsi [aici](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Rețineți că, deoarece are loc o anumită randomizare în generarea textului, veți obține probabil un rezultat ușor diferit. -## Gathering the data[[gathering-the-data]] +## Colectarea datelor[[gathering-the-data]] -Python code is abundantly available from code repositories such as GitHub, which we can use to create a dataset by scraping for every Python repository. This was the approach taken in the [Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098136789/) to pretrain a large GPT-2 model. Using a GitHub dump of about 180 GB containing roughly 20 million Python files called `codeparrot`, the authors built a dataset that they then shared on the [Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot). +Codul Python este disponibil din abundență în repositorii de cod, cum ar fi GitHub, pe care le putem utiliza pentru a crea un dataset prin scraping pentru fiecare repositoriu Python. Aceasta a fost abordarea adoptată în [Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098136789/) pentru a preantrena un model GPT-2. Folosind o descărcare GitHub de aproximativ 180 GB care conține aproximativ 20 de milioane de fișiere Python numită `codeparrot`, autorii au construit un dataset pe care l-au oferit apoi pe [Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot). -However, training on the full corpus is time- and compute-consuming, and we only need the subset of the dataset concerned with the Python data science stack. So, let's start by filtering the `codeparrot` dataset for all files that include any of the libraries in this stack. Because of the dataset's size, we want to avoid downloading it; instead, we'll use the streaming feature to filter it on the fly. To help us filter the code samples using the libraries we mentioned earlier, we'll use the following function: +Cu toate acestea, antrenarea pe întregul corpus consumă timp și puterea calculatorului, iar noi avem nevoie doar de subsetul datasetului referitor la Python data science stack. Așadar, să începem prin filtrarea datasetului `codeparrot` pentru toate fișierele care includ oricare dintre bibliotecile din aceast stack. Din cauza dimensiunii datasetului, dorim să evităm descărcarea acestuia; în schimb, vom utiliza funcția de streaming pentru a-l filtra din mers. Pentru a ne ajuta să filtrăm exemplele de cod care utilizează bibliotecile pe care le-am menționat mai devreme, vom utiliza următoarea funcție: ```py def any_keyword_in_string(string, keywords): @@ -48,7 +48,7 @@ def any_keyword_in_string(string, keywords): return False ``` -Let's test it on two examples: +Să-l testăm pe două exemple: ```py filters = ["pandas", "sklearn", "matplotlib", "seaborn"] @@ -64,7 +64,7 @@ print( False True ``` -We can use this to create a function that will stream the dataset and filter the elements we want: +Putem folosi acest lucru pentru a crea o funcție care va transmite în flux datasetul și va filtra elementele dorite: ```py from collections import defaultdict @@ -84,7 +84,7 @@ def filter_streaming_dataset(dataset, filters): return Dataset.from_dict(filtered_dict) ``` -Then we can simply apply this function to the streaming dataset: +Apoi, putem aplica pur și simplu această funcție pe datasetului din flux: ```py # This cell will take a very long time to execute, so you should skip it and go to @@ -102,9 +102,9 @@ filtered_data = filter_streaming_dataset(data, filters) 3.26% of data after filtering. ``` -This leaves us with about 3% of the original dataset, which is still quite sizable -- the resulting dataset is 6 GB and consists of 600,000 Python scripts! +Acest lucru ne lasă cu aproximativ 3% din setul de date original, care este încă destul de mare - datasetul rezultat este de 6 GB și constă din 600.000 de scripturi Python! -Filtering the full dataset can take 2-3h depending on your machine and bandwidth. If you don't want to go through this lengthy process yourself, we provide the filtered dataset on the Hub for you to download: +Filtrarea datasetului complet poate dura 2-3 ore, în funcție de calculator și de bandwidth. Dacă nu doriți să parcurgeți singur acest proces îndelungat, vă punem la dispoziție datasetul filtrat pe Hub pentru a-l descărca: ```py from datasets import load_dataset, DatasetDict @@ -137,11 +137,11 @@ DatasetDict({ -Pretraining the language model will take a while. We suggest that you first run the training loop on a sample of the data by uncommenting the two partial lines above, and make sure that the training successfully completes and the models are stored. Nothing is more frustrating than a training run failing at the last step because you forgot to create a folder or because there's a typo at the end of the training loop! +Preantrenarea modelului de limbaj va dura ceva timp. Vă sugerăm să rulați mai întâi bucla de antrenare pe un sample de date prin decomentarea celor două linii parțiale de mai sus și să vă asigurați că antrenarea se finalizează cu succes și că modelele sunt stocate. Nimic nu este mai frustrant decât o rulare de antrenare care eșuează la ultimul pas pentru că ați uitat să creați un folder sau pentru că există o greșeală de tipar la sfârșitul buclei de antrenare! -Let's look at an example from the dataset. We'll just show the first 200 characters of each field: +Să ne uităm la un exemplu din dataset. Vom arăta doar primele 200 de caractere din fiecare câmp: ```py for key in raw_datasets["train"][0]: @@ -167,22 +167,22 @@ from .murmurhash import murm LICENSE: bsd-3-clause''' ``` -We can see that the `content` field contains the code that we want our model to train on. Now that we have a dataset, we need to prepare the texts so they're in a format suitable for pretraining. +Putem vedea căci câmpul `content` conține codul pe care dorim ca modelul nostru să se antreneze. Acum că avem un dataset, trebuie să pregătim textele astfel încât acestea să fie într-un format adecvat pentru preantrenare. -## Preparing the dataset[[preparing-the-dataset]] +## Pregătirea datasetului[[preparing-the-dataset]] -The first step will be to tokenize the data, so we can use it for training. Since our goal is to mainly autocomplete short function calls, we can keep the context size relatively small. This has the benefit that we can train the model much faster and it requires significantly less memory. If it is important for your application to have more context (for example, if you want the model to write unit tests based on a file with the function definition), make sure you increase that number, but also keep in mind that this comes with a greater GPU memory footprint. For now, let's fix the context size at 128 tokens, as opposed to the 1,024 or 2,048 used in GPT-2 or GPT-3, respectively. +Primul pas va fi tokenizarea datelor, astfel încât să le putem utiliza pentru antrenare. Deoarece obiectivul nostru este de a autocompleta în principal apeluri scurte de funcții, putem păstra dimensiunea contextului relativ mică. Acest lucru are avantajul că putem antrena modelul mult mai rapid și că necesită semnificativ mai puțină memorie. Dacă este important pentru aplicația voastră să aveți mai mult context (de exemplu, dacă doriți ca modelul să scrie teste unitare pe baza unui fișier cu definiția funcției), asigurați-vă că măriți acest număr, dar rețineți, de asemenea, că acest lucru vine cu utilizare mai mare de memorie GPU. Pentru moment, să fixăm dimensiunea contextului la 128 de tokeni, spre deosebire de 1 024 sau 2 048 utilizate în GPT-2 sau respectiv GPT-3. -Most documents contain many more than 128 tokens, so simply truncating the inputs to the maximum length would eliminate a large fraction of our dataset. Instead, we'll use the `return_overflowing_tokens` option to tokenize the whole input and split it into several chunks, as we did in [Chapter 6](/course/chapter6/4). We'll also use the `return_length` option to return the length of each created chunk automatically. Often the last chunk will be smaller than the context size, and we'll get rid of these pieces to avoid padding issues; we don't really need them as we have plenty of data anyway. +Majoritatea documentelor conțin mult mai mult de 128 de cuvinte, astfel încât trunchierea simplă a inputurilor la lungimea maximă ar elimina o mare parte din datasetul nostru. În schimb, vom utiliza opțiunea `return_overflowing_tokens` pentru a tokeniza întreagul input și a o împărți în mai multe bucăți, așa cum am făcut în [Capitolul 6](/course/chapter6/4). De asemenea, vom utiliza opțiunea `return_length` pentru a returna automat lungimea fiecărui fragment creat. Adesea, ultimul fragment va fi mai mic decât dimensiunea contextului, iar noi vom scăpa de aceste bucăți pentru a evita problemele de padding; nu avem nevoie de ele, deoarece oricum avem o mulțime de date.
-Chunking a large texts in several pieces. - +Fragmentarea unui text mare în mai multe bucăți. +
-Let's see exactly how this works by looking at the first two examples: +Să vedem exact cum funcționează acest lucru analizând primele două exemple: ```py from transformers import AutoTokenizer @@ -209,9 +209,9 @@ Input chunk lengths: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 Chunk mapping: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] ``` -We can see that we get 34 segments in total from those two examples. Looking at the chunk lengths, we can see that the chunks at the ends of both documents have less than 128 tokens (117 and 41, respectively). These represent just a small fraction of the total chunks that we have, so we can safely throw them away. With the `overflow_to_sample_mapping` field, we can also reconstruct which chunks belonged to which input samples. +Putem vedea că obținem 34 de segmente în total din aceste două exemple. Uitându-ne la lungimea segmentelor, putem vedea că segmentele de la sfârșitul ambelor documente au mai puțin de 128 de token-uri (117 și, respectiv, 41). Acestea reprezintă doar o mică parte din totalul segmentelor pe care le avem, așa că le putem șterge în siguranță. Cu ajutorul câmpului `overflow_to_sample_mapping`, putem, de asemenea, să reconstituim care segmente au aparținut căror probe de intrare. -With this operation we're using a handy feature of the `Dataset.map()` function in 🤗 Datasets, which is that it does not require one-to-one maps; as we saw in [section 3](/course/chapter7/3), we can create batches with more or fewer elements than the input batch. This is useful when doing operations like data augmentation or data filtering that change the number of elements. In our case, when tokenizing each element into chunks of the specified context size, we create many samples from each document. We just need to make sure to delete the existing columns, since they have a conflicting size. If we wanted to keep them, we could repeat them appropriately and return them within the `Dataset.map()` call: +Cu această operațiune folosim o caracteristică utilă a funcției `Dataset.map()` din 🤗 Datasets, și anume că nu necesită one-to-one maps; așa cum am văzut în [secțiunea 3](/course/chaptero7/3), putem crea batch-uri cu mai multe sau mai puține elemente decât batch-ul de intrare. Acest lucru este util atunci când efectuăm operațiuni precum augmentarea sau filtrarea datelor care modifică numărul de elemente. În cazul nostru, atunci când tokenizăm fiecare element în segmente de dimensiunea contextului specificat, creăm multe probe din fiecare document. Trebuie doar să ne asigurăm că ștergem coloanele existente, deoarece acestea au o dimensiune conflictuală. Dacă am dori să le păstrăm, am putea să le repetăm în mod corespunzător și să le returnăm în cadrul apelului `Dataset.map()`: ```py def tokenize(element): @@ -248,20 +248,20 @@ DatasetDict({ }) ``` -We now have 16.7 million examples with 128 tokens each, which corresponds to about 2.1 billion tokens in total. For reference, OpenAI's GPT-3 and Codex models are trained on 300 and 100 billion tokens, respectively, where the Codex models are initialized from the GPT-3 checkpoints. Our goal in this section is not to compete with these models, which can generate long, coherent texts, but to create a scaled-down version providing a quick autocomplete function for data scientists. +Avem acum 16,7 milioane de exemple cu 128 de tokenii fiecare, ceea ce corespunde unui total de aproximativ 2,1 miliarde de tokeni. Ca referință, modelele GPT-3 și Codex ale OpenAI sunt antrenate pe 300 și, respectiv, 100 de miliarde de tokeni, unde modelele Codex sunt inițializate din checkpointurile GPT-3. Scopul nostru în această secțiune nu este de a concura cu aceste modele, care pot genera texte lungi și coerente, ci de a crea o versiune la scară redusă care să ofere o funcție rapidă de autocompletare pentru data scientists. -Now that we have the dataset ready, let's set up the model! +Acum că avem datasetul gata, hai să configurăm modelul! -✏️ **Try it out!** Getting rid of all the chunks that are smaller than the context size wasn't a big issue here because we're using small context windows. As you increase the context size (or if you have a corpus of short documents), the fraction of chunks that are thrown away will also grow. A more efficient way to prepare the data is to join all the tokenized samples in a batch with an `eos_token_id` token in between, and then perform the chunking on the concatenated sequences. As an exercise, modify the `tokenize()` function to make use of that approach. Note that you'll want to set `truncation=False` and remove the other arguments from the tokenizer to get the full sequence of token IDs. +✏️ **Încercați!** Eliminarea tuturor bucăților care sunt mai mici decât dimensiunea contextului nu a fost o problemă majoră aici, deoarece folosim ferestre de context mici. Pe măsură ce creșteți dimensiunea contextului (sau dacă aveți un corpus de documente scurte), fracțiunea de segmente care sunt aruncate va crește și ea. O modalitate mai eficientă de a pregăti datele este de a uni toate sampleurile tokenizate într-un batch cu un token `eos_token_id` între ele, iar apoi de a efectua chunkingul pe secvențele concatenate. Ca exercițiu, modificați funcția `tokenize()` pentru a utiliza această abordare. Rețineți că veți dori să setați `truncation=False` și să eliminați celelalte argumente din tokenizer pentru a obține secvența completă de token IDs. -## Initializing a new model[[initializing-a-new-model]] +## Inițializarea unui nou model[[initializing-a-new-model]] -Our first step is to freshly initialize a GPT-2 model. We'll use the same configuration for our model as for the small GPT-2 model, so we load the pretrained configuration, make sure that the tokenizer size matches the model vocabulary size and pass the `bos` and `eos` (beginning and end of sequence) token IDs: +Primul nostru pas este să inițializăm un model GPT-2. Vom utiliza aceeași configurație pentru modelul nostru ca și pentru modelul GPT-2 mic, deci încărcăm configurația preantrenată, ne asigurăm că dimensiunea tokenizerlui corespunde cu dimensiunea vocabularului modelului și transmitem ID-urile tokenilor `bos` și `eos` (începutul și sfârșitul secvenței): {#if fw === 'pt'} @@ -277,7 +277,7 @@ config = AutoConfig.from_pretrained( ) ``` -With that configuration, we can load a new model. Note that this is the first time we don't use the `from_pretrained()` function, since we're actually initializing a model ourself: +Cu această configurație, putem încărca un nou model. Rețineți că aceasta este prima dată când nu folosim funcția `from_pretrained()`, deoarece inițializăm noi înșine un model: ```py model = GPT2LMHeadModel(config) @@ -303,7 +303,7 @@ config = AutoConfig.from_pretrained( ) ``` -With that configuration, we can load a new model. Note that this is the first time we don't use the `from_pretrained()` function, since we're actually initializing a model ourself: +Cu această configurație, putem încărca un nou model. Rețineți că aceasta este prima dată când nu folosim funcția `from_pretrained()`, deoarece inițializăm noi înșine un model: ```py model = TFGPT2LMHeadModel(config) @@ -325,9 +325,9 @@ _________________________________________________________________ {/if} -Our model has 124M parameters that we'll have to tune. Before we can start training, we need to set up a data collator that will take care of creating the batches. We can use the `DataCollatorForLanguageModeling` collator, which is designed specifically for language modeling (as the name subtly suggests). Besides stacking and padding batches, it also takes care of creating the language model labels -- in causal language modeling the inputs serve as labels too (just shifted by one element), and this data collator creates them on the fly during training so we don't need to duplicate the `input_ids`. +Modelul nostru are 124 milioane de parametri pe care va trebui să le facem tune. Înainte de a începe antrenarea, trebuie să configurăm un data collator care se va ocupa de crearea batch-urilor. Putem utiliza colatorul `DataCollatorForLanguageModeling`, care este conceput special pentru modelarea limbajului (după cum sugerează subtil numele). Pe lângă stacking și paddingul batchurilor, acesta se ocupă și de crearea labelurilor modelului lingvistic - în modelarea cauzală a limbajului, inputurile servesc și ca labels (doar că sunt decalate cu un element), iar acest data collator le creează din mers în timpul antrenării, astfel încât să nu fie nevoie să duplicăm `input_ids`. -Note that `DataCollatorForLanguageModeling` supports both masked language modeling (MLM) and causal language modeling (CLM). By default it prepares data for MLM, but we can switch to CLM by setting the argument `mlm=False`: +Rețineți că `DataCollatorForLanguageModeling` acceptă atât masked language masking (MLM), cât și causal language modeling(CLM). În mod implicit, acesta pregătește datele pentru MLM, dar putem trece la CLM prin setarea argumentului `mlm=False`: {#if fw === 'pt'} @@ -349,7 +349,7 @@ data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False, return_ten {/if} -Let's have a look at an example: +Să aruncăm o privire la un exemplu: ```py out = data_collator([tokenized_datasets["train"][i] for i in range(5)]) @@ -375,11 +375,11 @@ labels shape: (5, 128) {/if} -We can see that the examples have been stacked and all the tensors have the same shape. +Putem vedea că exemplele sunt stacked și că toți tensorii au aceeași formă. {#if fw === 'tf'} -Now we can use the `prepare_tf_dataset()` method to convert our datasets to TensorFlow datasets with the data collator we created above: +Acum putem utiliza metoda `prepare_tf_dataset()` pentru a converti dataseturile noastre în dataseturi TensorFlow cu ajutorul data collatorului pe care l-am creat mai sus: ```python tf_train_dataset = model.prepare_tf_dataset( @@ -400,12 +400,12 @@ tf_eval_dataset = model.prepare_tf_dataset( -⚠️ Shifting the inputs and labels to align them happens inside the model, so the data collator just copies the inputs to create the labels. +⚠️ Schimbarea inputurilor și a labelurilor pentru a le alinia are loc în interiorul modelului, astfel încât data collatorului doar copiază inputurile pentru a crea labeluri. -Now we have everything in place to actually train our model -- that wasn't so much work after all! Before we start training we should log in to Hugging Face. If you're working in a notebook, you can do so with the following utility function: +Acum avem totul pregătit pentru a ne antrena modelul - până la urmă nu a fost atât de greu! Înainte de a începe antrenamentul, trebuie să ne conectăm la Hugging Face. Dacă lucrați într-un notebook, puteți face acest lucru cu următoarea funcție de utilitate: ```python from huggingface_hub import notebook_login @@ -413,9 +413,9 @@ from huggingface_hub import notebook_login notebook_login() ``` -This will display a widget where you can enter your Hugging Face login credentials. +Aceasta va afișa un widget în care puteți introduce datele voastre de autentificare Hugging Face. -If you aren't working in a notebook, just type the following line in your terminal: +Dacă nu lucrați într-un notebook, tastați următoarea linie în terminal: ```bash huggingface-cli login @@ -423,7 +423,7 @@ huggingface-cli login {#if fw === 'pt'} -All that's left to do is configure the training arguments and fire up the `Trainer`. We'll use a cosine learning rate schedule with some warmup and an effective batch size of 256 (`per_device_train_batch_size` * `gradient_accumulation_steps`). Gradient accumulation is used when a single batch does not fit into memory, and incrementally builds up the gradient through several forward/backward passes. We'll see this in action when we create the training loop with 🤗 Accelerate. +Tot ce a mai rămas de făcut este să configurăm argumentele de antrenare și să pornim `Trainer`-ul. Vom utiliza un cosine learning rate schedule cu un warmup și o dimensiune efectivă a batch-ului de 256 (`per_device_train_batch_size` * `gradient_accumulation_steps`). Acumularea gradientului este utilizată atunci când un singur batch nu încape în memorie și construiește treptat gradientul prin mai multe treceri înainte/înapoi. Vom vedea acest lucru în acțiune atunci când vom crea bucla de antrenare cu 🤗 Accelerate. ```py from transformers import Trainer, TrainingArguments @@ -456,13 +456,13 @@ trainer = Trainer( ) ``` -Now we can just start the `Trainer` and wait for training to finish. Depending on whether you run it on the full or a subset of the training set this will take 20 or 2 hours, respectively, so grab a few coffees and a good book to read! +Acum putem doar să pornim `Trainer`-ul și să așteptăm ca antrenamentul să se termine. În funcție de executarea antrenării pe întregul set de antrenarea sau pe un subset al acestuia, va dura 20 minute, sau respectiv 2 ore, așa că luați câteva cafeluțe și o carte bună de citit! ```py trainer.train() ``` -After training completes, we can push the model and tokenizer to the Hub: +După finalizarea antrenării, putem trimite modelul și tokenizerul către Hub: ```py trainer.push_to_hub() @@ -470,7 +470,7 @@ trainer.push_to_hub() {:else} -All that's left to do is configure the training hyperparameters and call `compile()` and `fit()`. We'll use a learning rate schedule with some warmup to improve the stability of training: +Tot ce rămâne de făcut este să configurați hiperparametrii de antrenament și să apelați `compile()` și `fit()`. Vom utiliza un program al learning rate cu un anumit warmup pentru a îmbunătăți stabilitatea antrenării: ```py from transformers import create_optimizer @@ -489,7 +489,7 @@ model.compile(optimizer=optimizer) tf.keras.mixed_precision.set_global_policy("mixed_float16") ``` -Now we can just call `model.fit()` and wait for training to finish. Depending on whether you run it on the full or a subset of the training set this will take 20 or 2 hours, respectively, so grab a few coffees and a good book to read! After training completes we can push the model and tokenizer to the Hub: +Acum putem apela `model.fit()` și să așteptăm ca antrenarea să se încheie. În funcție de executarea antrenării pe întregul set de antrenarea sau pe un subset al acestuia, va dura 20 minute, sau respectiv 2 ore, așa că luați câteva cafeluțe și o carte bună de citit! ```py from transformers.keras_callbacks import PushToHubCallback @@ -503,7 +503,7 @@ model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback -✏️ **Try it out!** It only took us about 30 lines of code in addition to the `TrainingArguments` to get from raw texts to training GPT-2. Try it out with your own dataset and see if you can get good results! +✏️ **Try it out!** Ne-a luat doar aproximativ 30 de linii de cod în plus față de `TrainingArguments` pentru a ajunge de la texte brute la antrenarea GPT-2. Încercați antrenarea cu propriul dataset și vedeți dacă puteți obține rezultate bune! @@ -511,19 +511,19 @@ model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback {#if fw === 'pt'} -💡 If you have access to a machine with multiple GPUs, try to run the code there. The `Trainer` automatically manages multiple machines, and this can speed up training tremendously. +💡 Dacă aveți acces la un calculator cu mai multe GPU-uri, încercați să rulați codul acolo. `Trainer` gestionează automat mai multe calculatoare, iar acest lucru poate accelera foarte mult antrenamentul. {:else} -💡 If you have access to a machine with multiple GPUs, you can try using a `MirroredStrategy` context to substantially speed up training. You'll need to create a `tf.distribute.MirroredStrategy` object, and make sure that any `to_tf_dataset()` or `prepare_tf_dataset()` methods as well as model creation and the call to `fit()` are all run in its `scope()` context. You can see documentation on this [here](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). +💡 Dacă aveți acces la un calculator cu mai multe GPU-uri, puteți încerca să utilizați un context `MirroredStrategy` pentru a accelera substanțial antrenarea. Va trebui să creați un obiect `tf.distribute.MirroredStrategy` și să vă asigurați că toate metodele `to_tf_dataset()` sau `prepare_tf_dataset()`, precum și crearea modelului și apelul la `fit()` sunt rulate în contextul său `scope()`. Puteți vedea documentația despre acest lucru [aici] (https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). {/if} -## Code generation with a pipeline[[code-generation-with-a-pipeline]] +## Generarea codului cu un pipeline[[code-generation-with-a-pipeline]] -Now is the moment of truth: let's see how well the trained model actually works! We can see in the logs that the loss went down steadily, but to put the model to the test let's take a look at how well it works on some prompts. To do that we'll wrap the model in a text generation `pipeline`, and we'll put it on the GPU for fast generations if there is one available: +Acum este momentul adevărului: să vedem cât de bine funcționează de fapt modelul antrenat! Putem vedea în loguri că pierderea a scăzut în mod constant, dar pentru a testa modelul, hai să vedem cât de bine funcționează la câteva încerări. Pentru a face acest lucru, vom încorpora modelul într-un `pipeline` de generare a textului și îl vom pune pe un GPU pentru generații rapide, dacă există unul disponibil: {#if fw === 'pt'} @@ -551,7 +551,7 @@ pipe = pipeline( {/if} -Let's start with the simple task of creating a scatter plot: +Să începem cu sarcina simplă de a crea un scatter plot: ```py txt = """\ @@ -559,23 +559,23 @@ txt = """\ x = np.random.randn(100) y = np.random.randn(100) -# create scatter plot with x, y +# crearea unui scatter plot cu x, y """ print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) ``` ```python out -# create some data +# crearea unor date x = np.random.randn(100) y = np.random.randn(100) -# create scatter plot with x, y +# crearea scatter plot cu x, y plt.scatter(x, y) -# create scatter +# crearea scatter ``` -The result looks correct. Does it also work for a `pandas` operation? Let's see if we can create a `DataFrame` from two arrays: +Rezultatul pare corect. Funcționează și pentru o operație `pandas`? Să vedem dacă putem crea un `DataFrame` din două array-uri: ```py txt = """\ @@ -583,7 +583,7 @@ txt = """\ x = np.random.randn(100) y = np.random.randn(100) -# create dataframe from x and y +# creați dataframeul din x și y """ print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) ``` @@ -593,20 +593,20 @@ print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) x = np.random.randn(100) y = np.random.randn(100) -# create dataframe from x and y +# crează un dataframe din x și y df = pd.DataFrame({'x': x, 'y': y}) df.insert(0,'x', x) for ``` -Nice, that's the correct answer -- although it then inserts the column `x` again. Since the number of generated tokens is limited, the following `for` loop is cut off. Let's see if we can do something a bit more complex and have the model help us use the `groupby` operation: +Excelent, acesta este răspunsul corect - deși apoi introduce din nou coloana `x`. Deoarece numărul de tokeni generate este limitat, următoarea buclă `for` este întreruptă. Să vedem dacă putem face ceva un pic mai complex și dacă modelul ne ajută să folosim operația `groupby`: ```py txt = """\ # dataframe with profession, income and name df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) -# calculate the mean income per profession +# calculați venitul mediu pe profesie """ print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) ``` @@ -615,20 +615,20 @@ print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) # dataframe with profession, income and name df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) -# calculate the mean income per profession +# calculeză venitul mediu pe profesie profession = df.groupby(['profession']).mean() -# compute the +# calculează ``` -Not bad; that's the right way to do it. Finally, let's see if we can also use it for `scikit-learn` and set up a Random Forest model: +Nu este rău; acesta este modul corect de a face acest lucru. În cele din urmă, să vedem dacă îl putem folosi și pentru `scikit-learn` și să configurăm un model Random Forest: ```py txt = """ # import random forest regressor from scikit-learn from sklearn.ensemble import RandomForestRegressor -# fit random forest model with 300 estimators on X, y: +# ajustați modelul Random Forest cu 300 de estimatori pe X, y: """ print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) ``` @@ -637,7 +637,7 @@ print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) # import random forest regressor from scikit-learn from sklearn.ensemble import RandomForestRegressor -# fit random forest model with 300 estimators on X, y: +# ajustați modelul Random Forest cu 300 de estimatori pe X, y: rf = RandomForestRegressor(n_estimators=300, random_state=random_state, max_depth=3) rf.fit(X, y) rf @@ -645,17 +645,17 @@ rf {#if fw === 'tf'} -Looking at these few examples, it seems that the model has learned some of the syntax of the Python data science stack. Of course, we would need to evaluate the model more thoroughly before deploying it in the real world, but this is still an impressive prototype. +Privind la aceste câteva exemple, se pare că modelul a învățat o parte din sintaxa Python data science stack. Desigur, ar trebui să evaluăm modelul mai amănunțit înainte de a-l implementa în lumea reală, totuși acesta este un prototip impresionant. {:else} -Looking at these few examples, it seems that the model has learned some of the syntax of the Python data science stack (of course, we would need to evaluate it more thoroughly before deploying the model in the real world). Sometimes it requires more customization of the model training to achieve the necessary performance for a given use case, however. For example, what if we would like to dynamically update the batch size or have a conditional training loop that skips bad examples on the fly? One option would be to subclass the `Trainer` and add the necessary changes, but sometimes it's simpler to write the training loop from scratch. That's where 🤗 Accelerate comes in. +Privind aceste câteva exemple, se pare că modelul a învățat o parte din sintaxa Python data science stack(desigur, ar trebui să o evaluăm mai bine înainte de a implementa modelul în lumea reală). Cu toate acestea, uneori este nevoie de o mai mare personalizare a antrenării modelului pentru a obține performanța necesară pentru un anumit caz de utilizare. De exemplu, dacă am dori să actualizăm dinamic dimensiunea batch-ului sau să avem o buclă de antrenare condiționată care trece peste exemplele proaste din mers? O opțiune ar fi să facem subclass la `Trainer` și să adăugăm modificările necesare, dar uneori este mai simplu să scriem bucla de antrenare de la zero. Aici intervine 🤗 Accelerate. {/if} {#if fw === 'pt'} -## Training with 🤗 Accelerate[[training-with-accelerate]] +## Antrenarea cu 🤗 Accelerate[[training-with-accelerate]] We've seen how to train a model with the `Trainer`, which can allow for some customization. However, sometimes we want full control over the training loop, or we want to make some exotic changes. In this case 🤗 Accelerate is a great choice, and in this section we'll go through the steps to use it to train our model. To make things more interesting, we'll also add a twist to the training loop. diff --git a/chapters/ro/chapter7/7.mdx b/chapters/ro/chapter7/7.mdx index 34556be21..72358b6cc 100644 --- a/chapters/ro/chapter7/7.mdx +++ b/chapters/ro/chapter7/7.mdx @@ -22,7 +22,7 @@ {/if} -Time to look at question answering! This task comes in many flavors, but the one we'll focus on in this section is called *extractive* question answering. This involves posing questions about a document and identifying the answers as _spans of text_ in the document itself. +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 _spans de text_ în documentul în sine. From a759450ad330ccd6ffe7fa74bc2ba5fca0468a2b Mon Sep 17 00:00:00 2001 From: Angroys <120798951+Angroys@users.noreply.github.com> Date: Thu, 23 Jan 2025 17:14:23 +0200 Subject: [PATCH 10/37] finished chapter 7 --- chapters/ro/chapter7/6.mdx | 46 +++--- chapters/ro/chapter7/7.mdx | 289 +++++++++++++++++++------------------ chapters/ro/chapter7/8.mdx | 20 +-- chapters/ro/chapter7/9.mdx | 230 ++++++++++++++--------------- 4 files changed, 294 insertions(+), 291 deletions(-) diff --git a/chapters/ro/chapter7/6.mdx b/chapters/ro/chapter7/6.mdx index c47f46030..decbcf3eb 100644 --- a/chapters/ro/chapter7/6.mdx +++ b/chapters/ro/chapter7/6.mdx @@ -657,11 +657,11 @@ Privind aceste câteva exemple, se pare că modelul a învățat o parte din sin ## Antrenarea cu 🤗 Accelerate[[training-with-accelerate]] -We've seen how to train a model with the `Trainer`, which can allow for some customization. However, sometimes we want full control over the training loop, or we want to make some exotic changes. In this case 🤗 Accelerate is a great choice, and in this section we'll go through the steps to use it to train our model. To make things more interesting, we'll also add a twist to the training loop. +Am văzut cum să antrenăm un model cu `Trainer`, care poate permite o anumită personalizare. Cu toate acestea, uneori dorim control deplin asupra buclei de antrenare sau dorim să facem unele schimbări exotice. În acest caz, 🤗 Accelerate este o alegere excelentă, iar în această secțiune vom parcurge pașii de utilizare a acestuia pentru a ne antrena modelul. Pentru a face lucrurile mai interesante, vom adăuga și un twist buclei de antrenare. -Since we are mainly interested in sensible autocompletion for the the data science libraries, it makes sense to give more weight to training samples that make more use of these libraries. We can easily identify these examples through the use of keywords such as `plt`, `pd`, `sk`, `fit`, and `predict`, which are the most frequent import names for `matplotlib.pyplot`, `pandas`, and `sklearn` as well as the fit/predict pattern of the latter. If these are each represented as a single token, we can easily check if they occur in the input sequence. Tokens can have a whitespace prefix, so we'll also check for those versions in the tokenizer vocabulary. To verify that it works, we'll add one test token which should be split into multiple tokens: +Deoarece suntem interesați în principal de o autocompletare sensibilă pentru bibliotecile din domeniul data science, este logic să acordăm mai multă importanță exemplelor de antrenare care utilizează mai mult aceste biblioteci. Putem identifica cu ușurință aceste exemple prin utilizarea unor cuvinte-cheie precum `plt`, `pd`, `sk`, `fit` și `predict`, care sunt cele mai frecvente nume de import pentru `matplotlib.pyplot`, `pandas` și `sklearn`, precum și modelul fit/predict al acestora din urmă. Dacă acestea sunt reprezentate fiecare ca un singur simbol, putem verifica cu ușurință dacă apar în secvența de input. Tokenii pot avea un prefix de spațiu, deci vom verifica și aceste versiuni în vocabularul tokenizerului. Pentru a verifica dacă funcționează, vom adăuga un token de test care ar trebui să fie împărțit în mai mulți tokeni: ```py keytoken_ids = [] @@ -689,7 +689,7 @@ for keyword in [ 'Keyword has not single token: testtest' ``` -Great, that seems to work nicely! We can now write a custom loss function that takes the input sequence, the logits, and the key tokens we just selected as inputs. First we need to align the logits and inputs: the input sequence shifted by one to the right forms the labels, since the next token is the label for the current token. We can achieve this by starting the labels from the second token of the input sequence, since the model does not make a prediction for the first token anyway. Then we cut off the last logit, as we don't have a label for the token that follows the full input sequence. With that we can compute the loss per sample and count the occurrences of all keywords in each sample. Finally, we calculate the weighted average over all samples using the occurrences as weights. Since we don't want to throw away all the samples that have no keywords, we add 1 to the weights: +Grozav, se pare că funcționează bine! Acum putem scrie o funcție de pierdere personalizată care ia ca secvența de input, logurile și tokenii cheie pe care tocmai le-am selectat. În primul rând, trebuie să aliniem logurile și inputurile: secvența de intrare deplasată cu o unitate la dreapta formează labeluri, deoarece următorul tokenul este labelul pentru tokenul curent. Putem realiza acest lucru începând cu labelurile de la al doilea token al secvenței de intrare, deoarece modelul nu face o predicție pentru primul token în orice caz. Apoi tăiem ultimul logit, deoarece nu avem un label pentru tokenul care urmează secvenței complete de intrare. Astfel, putem calcula pierderea per sample și putem număra aparițiile tuturor cuvintelor-cheie în fiecare sample. În cele din urmă, calculăm media weighturilor pe fiecare sample folosind aparițiile ca weighturi. Deoarece nu dorim să eliminăm toate sampleurile care nu au cuvinte-cheie, adăugăm 1 la weighturi: ```py from torch.nn import CrossEntropyLoss @@ -715,13 +715,13 @@ def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): return weighted_loss ``` -Before we can start training with this awesome new loss function, we need to prepare a few things: +Înainte de a începe antrenamentul cu această nouă funcție de pierdere minunată, trebuie să pregătim câteva lucruri: -- We need dataloaders to load the data in batches. -- We need to set up weight decay parameters. -- From time to time we want to evaluate, so it makes sense to wrap the evaluation code in a function. +- Avem nevoie de dataloaders pentru a încărca datele în batch-uri. +- Trebuie să configurăm parametrii de scădere a weighturilor. +- Din când în când, dorim să evaluăm, astfel încât este logic să includem codul de evaluare într-o funcție. -Let's start with the dataloaders. We only need to set the dataset's format to `"torch"`, and then we can pass it to a PyTorch `DataLoader` with the appropriate batch size: +Să începem cu dataloaders. Trebuie doar să setăm formatul datasetului la `"torch"`, iar apoi îl putem trece la un `DataLoader` PyTorch cu dimensiunea corespunzătoare a batch-lui: ```py from torch.utils.data.dataloader import DataLoader @@ -731,7 +731,7 @@ train_dataloader = DataLoader(tokenized_datasets["train"], batch_size=32, shuffl eval_dataloader = DataLoader(tokenized_datasets["valid"], batch_size=32) ``` -Next, we group the parameters so that the optimizer knows which ones will get an additional weight decay. Usually, all bias and LayerNorm weights terms are exempt from this; here's how we can do this: +În continuare, grupăm parametrii astfel încât optimizatorul să știe care dintre aceștia vor primi o scădere suplimentară a weighturilor. De obicei, toți termenii weighturilor bias și LayerNorm sunt scutiți de acest lucru; iată cum putem face acest lucru: ```py weight_decay = 0.1 @@ -750,7 +750,7 @@ def get_grouped_params(model, no_decay=["bias", "LayerNorm.weight"]): ] ``` -Since we want to evaluate the model regularly on the validation set during training, let's write a function for that as well. It just runs through the evaluation dataloader and gathers all the losses across processes: +Deoarece dorim să evaluăm modelul în mod regulat pe setul de validare în timpul antrenării, trebuie să scriem o funcție și pentru acest lucru. Aceasta rulează pur și simplu prin dataloaderul de evaluare și adună toate pierderile în cadrul proceselor: ```py def evaluate(): @@ -769,13 +769,13 @@ def evaluate(): return loss.item(), perplexity.item() ``` -With the `evaluate()` function we can report loss and [perplexity](/course/chapter7/3) at regular intervals. Next, we redefine our model to make sure we train from scratch again: +Cu funcția `evaluate()` putem raporta pierderile și [perplexitatea](/course/chapter7/3) la intervale regulate. În continuare, redefinim modelul nostru pentru a ne asigura că antrenăm din nou de la zero: ```py model = GPT2LMHeadModel(config) ``` -We can then define our optimizer, using the function from before to split the parameters for weight decay: +Apoi putem defini optimizatorul nostru, folosind funcția de mai devreme pentru a împărți parametrii pentru scăderea weighturilor: ```py from torch.optim import AdamW @@ -783,7 +783,7 @@ from torch.optim import AdamW optimizer = AdamW(get_grouped_params(model), lr=5e-4) ``` -Now let's prepare the model, optimizer, and dataloaders so we can start training: +Acum să pregătim modelul, optimizatorul și dataloaderurile, astfel încât să putem începe antrenamentul: ```py from accelerate import Accelerator @@ -797,11 +797,11 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( -🚨 If you're training on a TPU, you'll need to move all the code starting at the cell above into a dedicated training function. See [Chapter 3](/course/chapter3) for more details. +🚨 Dacă antrenați pe un TPU, va trebui să mutați tot codul începând cu celula de mai sus într-o funcție de antrenare dedicată. Consultați [Capitolul 3](/course/chapter3) pentru mai multe detalii. -Now that we have sent our `train_dataloader` to `accelerator.prepare()`, we can use its length to compute the number of training steps. Remember that we should always do this after preparing the dataloader, as that method will change its length. We use a classic linear schedule from the learning rate to 0: +Acum că am trimis `train_dataloader` la `accelerator.prepare()`, putem utiliza lungimea acestuia pentru a calcula numărul de pași de antrenare. Rețineți că ar trebui să facem acest lucru întotdeauna după ce pregătim dataloaderurile, deoarece această metodă îi va modifica lungimea. Utilizăm un program liniar clasic de la rata de învățare la 0: ```py from transformers import get_scheduler @@ -818,7 +818,7 @@ lr_scheduler = get_scheduler( ) ``` -Lastly, to push our model to the Hub, we will need to create a `Repository` object in a working folder. First log in to the Hugging Face Hub, if you aren't logged in already. We'll determine the repository name from the model ID we want to give our model (feel free to replace the `repo_name` with your own choice; it just needs to contain your username, which is what the function `get_full_repo_name()` does): +În cele din urmă, 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 repositoriului 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()`): ```py from huggingface_hub import Repository, get_full_repo_name @@ -832,16 +832,16 @@ repo_name 'sgugger/codeparrot-ds-accelerate' ``` -Then we can clone that repository in a local folder. If it already exists, this local folder should be an existing clone of the repository we are working with: +Apoi putem clona acel repositoriu într-un folder local. Dacă există deja, acest folder local ar trebui să fie o clonă existentă a repositoriul cu care lucrăm: ```py output_dir = "codeparrot-ds-accelerate" repo = Repository(output_dir, clone_from=repo_name) ``` -We can now upload anything we save in `output_dir` by calling the `repo.push_to_hub()` method. This will help us upload the intermediate models at the end of each epoch. +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. -Before we train, let's run a quick test to see if the evaluation function works properly: +Înainte de antrenament, să efectuăm un test rapid pentru a vedea dacă funcția de evaluare funcționează corect: ```py evaluate() @@ -851,7 +851,7 @@ evaluate() (10.934126853942871, 56057.14453125) ``` -Those are very high values for loss and perplexity, but that's not surprising as we haven't trained the model yet. With that, we have everything prepared to write the core part of the training script: the training loop. In the training loop we iterate over the dataloader and pass the batches to the model. With the logits, we can then evaluate our custom loss function. We scale the loss by the number of gradient accumulation steps so as not to create larger losses when aggregating more steps. Before we optimize, we also clip the gradients for better convergence. Finally, every few steps we evaluate the model on the evaluation set with our new `evaluate()` function: +Acestea sunt valori foarte ridicate pentru pierdere și perplexitate, dar acest lucru nu este surprinzător, deoarece nu am antrenat încă modelul. Astfel, avem totul pregătit pentru a scrie partea principală a scriptului de antrenare: bucla de antrenare. În bucla de antrenare, iterăm peste dataloader și transmitem batch-urile către model. Cu logurile, putem apoi evalua funcția noastră de pierdere personalizată. Redimensionăm pierderea în funcție de numărul de etape de acumulare a gradientului pentru a nu crea pierderi mai mari atunci când agregăm mai multe etape. Înainte de a optimiza, comprimăm, de asemenea, gradienții pentru o mai bună convergență. În cele din urmă, la fiecare câțiva pași, evaluăm modelul pe setul de evaluare cu noua noastră funcție `evaluate()`: ```py from tqdm.notebook import tqdm @@ -897,17 +897,17 @@ for epoch in range(num_train_epochs): ) ``` -And that's it -- you now have your own custom training loop for causal language models such as GPT-2 that you can further customize to your needs. +Și asta e tot - acum aveți propria buclă de antrenare personalizată pentru modele de limbaj cauzal, cum ar fi GPT-2, pe care o puteți personaliza în continuare în funcție de nevoile voastre. -✏️ **Try it out!** Either create your own custom loss function tailored to your use case, or add another custom step into the training loop. +✏️ **încercați!** Fie vă creați propria funcție de pierdere personalizată, adaptată la cazul vostru de utilizare, fie adăugați un alt pas personalizat în bucla de antrenare. -✏️ **Try it out!** When running long training experiments it's a good idea to log important metrics using tools such as TensorBoard or Weights & Biases. Add proper logging to the training loop so you can always check how the training is going. +✏️ **încercați!** Atunci când efectuați experimente de antrenare de lungă durată, este o idee bună să înregistrați parametrii importanți utilizând instrumente precum TensorBoard sau Weights & Biases. Adăugați o logare adecvată la bucla de antrenare, astfel încât să puteți verifica întotdeauna cum decurge antrenarea. diff --git a/chapters/ro/chapter7/7.mdx b/chapters/ro/chapter7/7.mdx index 72358b6cc..87c0b1924 100644 --- a/chapters/ro/chapter7/7.mdx +++ b/chapters/ro/chapter7/7.mdx @@ -1,6 +1,6 @@ -# Question answering[[question-answering]] +# Răspunderea la întrebări[[question-answering]] {#if fw === 'pt'} @@ -22,29 +22,29 @@ {/if} -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 _spans de text_ în documentul în sine. +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. -We will fine-tune a BERT model on the [SQuAD dataset](https://rajpurkar.github.io/SQuAD-explorer/), which consists of questions posed by crowdworkers on a set of Wikipedia articles. This will give us a model able to compute predictions like this one: +Vom face fine-tuning unuimodel 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: -This is actually showcasing the model that was trained and uploaded to the Hub using the code shown in this section. You can find it and double-check the predictions [here](https://huggingface.co/huggingface-course/bert-finetuned-squad?context=%F0%9F%A4%97+Transformers+is+backed+by+the+three+most+popular+deep+learning+libraries+%E2%80%94+Jax%2C+PyTorch+and+TensorFlow+%E2%80%94+with+a+seamless+integration+between+them.+It%27s+straightforward+to+train+your+models+with+one+before+loading+them+for+inference+with+the+other.&question=Which+deep+learning+libraries+back+%F0%9F%A4%97+Transformers%3F). +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](https://huggingface.co/huggingface-course/bert-finetuned-squad?context=%F0%9F%A4%97+Transformers+is+backed+by+the+three+most+popular+deep+learning+libraries+%E2%80%94+Jax%2C+PyTorch+and+TensorFlow+%E2%80%94+with+a+seamless+integration+between+them.+It%27s+straightforward+to+train+your+models+with+one+before+loading+them+for+inference+with+the+other.&question=Which+deep+learning+libraries+back+%F0%9F%A4%97+Transformers%3F). -💡 Encoder-only models like BERT tend to be great at extracting answers to factoid questions like "Who invented the Transformer architecture?" but fare poorly when given open-ended questions like "Why is the sky blue?" In these more challenging cases, encoder-decoder models like T5 and BART are typically used to synthesize the information in a way that's quite similar to [text summarization](/course/chapter7/5). If you're interested in this type of *generative* question answering, we recommend checking out our [demo](https://yjernite.github.io/lfqa.html) based on the [ELI5 dataset](https://huggingface.co/datasets/eli5). +💡 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](/course/chapter7/5). Dacă sunteți interesat de acest tip de răspuns *generativ* la întrebări, vă recomandăm să consultați [demo-ul](https://yjernite.github.io/lfqa.html) nostru bazat pe [datasetul ELI5](https://huggingface.co/datasets/eli5). -## Preparing the data[[preparing-the-data]] +## Pregătirea datelor[[preparing-the-data]] -The dataset that is used the most as an academic benchmark for extractive question answering is [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), so that's the one we'll use here. There is also a harder [SQuAD v2](https://huggingface.co/datasets/squad_v2) benchmark, which includes questions that don't have an answer. As long as your own dataset contains a column for contexts, a column for questions, and a column for answers, you should be able to adapt the steps below. +Datasetul care este cel mai utilizat ca referință academică pentru răspunderea extractivă la întrebări este [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), deci acesta este cel pe care îl vom utiliza aici. Există, de asemenea, un benchmark mai dificil [SQuAD v2](https://huggingface.co/datasets/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. -### The SQuAD dataset[[the-squad-dataset]] +### Datasetul SQuD[[the-squad-dataset]] -As usual, we can download and cache the dataset in just one step thanks to `load_dataset()`: +Ca de obicei, putem descărca și stoca în cache datasetul într-un singur pas datorită funcției `load_dataset()`: ```py from datasets import load_dataset @@ -52,7 +52,7 @@ from datasets import load_dataset raw_datasets = load_dataset("squad") ``` -We can then have a look at this object to learn more about the SQuAD dataset: +Ne putem uita apoi la acest obiect pentru a afla mai multe despre datasetul SQuAD: ```py raw_datasets @@ -71,7 +71,7 @@ DatasetDict({ }) ``` -It looks like we have everything we need with the `context`, `question`, and `answers` fields, so let's print those for the first element of our training set: +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: ```py print("Context: ", raw_datasets["train"][0]["context"]) @@ -85,9 +85,9 @@ Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes Franc Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]} ``` -The `context` and `question` fields are very straightforward to use. The `answers` field is a bit trickier as it comports a dictionary with two fields that are both lists. This is the format that will be expected by the `squad` metric during evaluation; if you are using your own data, you don't necessarily need to worry about putting the answers in the same format. The `text` field is rather obvious, and the `answer_start` field contains the starting character index of each answer in the context. +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. -During training, there is only one possible answer. We can double-check this by using the `Dataset.filter()` method: +În timpul antrenamentului, există un singur răspuns posibil. Putem verifica acest lucru folosind metoda `Dataset.filter()`: ```py raw_datasets["train"].filter(lambda x: len(x["answers"]["text"]) != 1) @@ -100,7 +100,7 @@ Dataset({ }) ``` -For evaluation, however, there are several possible answers for each sample, which may be the same or different: +Cu toate acestea, pentru evaluare, există mai multe răspunsuri posibile pentru fiecare sample, care pot fi identice sau diferite: ```py print(raw_datasets["validation"][0]["answers"]) @@ -112,7 +112,7 @@ print(raw_datasets["validation"][2]["answers"]) {'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]} ``` -We won't dive into the evaluation script as it will all be wrapped up by a 🤗 Datasets metric for us, but the short version is that some of the questions have several possible answers, and this script will compare a predicted answer to all the acceptable answers and take the best score. If we take a look at the sample at index 2, for instance: +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: ```py print(raw_datasets["validation"][2]["context"]) @@ -124,15 +124,15 @@ print(raw_datasets["validation"][2]["question"]) 'Where did Super Bowl 50 take place?' ``` -we can see that the answer can indeed be one of the three possibilities we saw before. +putem vedea că răspunsul poate fi într-adevăr una dintre cele trei posibilități pe care le-am văzut anterior. -### Processing the training data[[processing-the-training-data]] +### Procesarea datelor de antrenare[[processing-the-training-data]] -Let's start with preprocessing the training data. The hard part will be to generate labels for the question's answer, which will be the start and end positions of the tokens corresponding to the answer inside the context. +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. -But let's not get ahead of ourselves. First, we need to convert the text in the input into IDs the model can make sense of, using a tokenizer: +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: ```py from transformers import AutoTokenizer @@ -141,7 +141,7 @@ model_checkpoint = "bert-base-cased" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) ``` -As mentioned previously, we'll be fine-tuning a BERT model, but you can use any other model type as long as it has a fast tokenizer implemented. You can see all the architectures that come with a fast version in [this big table](https://huggingface.co/transformers/#supported-frameworks), and to check that the `tokenizer` object you're using is indeed backed by 🤗 Tokenizers you can look at its `is_fast` attribute: +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`: ```py tokenizer.is_fast @@ -151,13 +151,13 @@ tokenizer.is_fast True ``` -We can pass to our tokenizer the question and the context together, and it will properly insert the special tokens to form a sentence like this: +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] ``` -Let's double-check: +Hai să verificăm de două ori: ```py context = raw_datasets["train"][0]["context"] @@ -178,21 +178,21 @@ tokenizer.decode(inputs["input_ids"]) 'and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]' ``` -The labels will then be the index of the tokens starting and ending the answer, and the model will be tasked to predicted one start and end logit per token in the input, with the theoretical labels being as follow: +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:
-One-hot encoded labels for question answering. - +One-hot encoded label pentru răspunderea la întrebări. +
-In this case the context is not too long, but some of the examples in the dataset have very long contexts that will exceed the maximum length we set (which is 384 in this case). As we saw in [Chapter 6](/course/chapter6/4) when we explored the internals of the `question-answering` pipeline, we will deal with long contexts by creating several training features from one sample of our dataset, with a sliding window between them. +Î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](/course/chapter6/4) 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. -To see how this works using the current example, we can limit the length to 100 and use a sliding window of 50 tokens. As a reminder, we use: +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` to set the maximum length (here 100) -- `truncation="only_second"` to truncate the context (which is in the second position) when the question with its context is too long -- `stride` to set the number of overlapping tokens between two successive chunks (here 50) -- `return_overflowing_tokens=True` to let the tokenizer know we want the overflowing tokens +- `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 ```py inputs = tokenizer( @@ -215,9 +215,9 @@ for ids in inputs["input_ids"]: '[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]' ``` -As we can see, our example has been in split into four inputs, each of them containing the question and some part of the context. Note that the answer to the question ("Bernadette Soubirous") only appears in the third and last inputs, so by dealing with long contexts in this way we will create some training examples where the answer is not included in the context. For those examples, the labels will be `start_position = end_position = 0` (so we predict the `[CLS]` token). We will also set those labels in the unfortunate case where the answer has been truncated so that we only have the start (or end) of it. For the examples where the answer is fully in the context, the labels will be the index of the token where the answer starts and the index of the token where the answer ends. +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. -The dataset provides us with the start character of the answer in the context, and by adding the length of the answer, we can find the end character in the context. To map those to token indices, we will need to use the offset mappings we studied in [Chapter 6](/course/chapter6/4). We can have our tokenizer return these by passing along `return_offsets_mapping=True`: +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](/course/chapter6/4). Putem face ca tokenizatorul nostru să le returneze trecând `return_offsets_mapping=True`: ```py inputs = tokenizer( @@ -236,7 +236,7 @@ inputs.keys() dict_keys(['input_ids', 'token_type_ids', 'attention_mask', 'offset_mapping', 'overflow_to_sample_mapping']) ``` -As we can see, we get back the usual input IDs, token type IDs, and attention mask, as well as the offset mapping we required and an extra key, `overflow_to_sample_mapping`. The corresponding value will be of use to us when we tokenize several texts at the same time (which we should do to benefit from the fact that our tokenizer is backed by Rust). Since one sample can give several features, it maps each feature to the example it originated from. Because here we only tokenized one example, we get a list of `0`s: +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`: ```py inputs["overflow_to_sample_mapping"] @@ -246,7 +246,7 @@ inputs["overflow_to_sample_mapping"] [0, 0, 0, 0] ``` -But if we tokenize more examples, this will become more useful: +Dar dacă vom tokeniza mai multe exemple, acest lucru va deveni mai util: ```py inputs = tokenizer( @@ -268,16 +268,16 @@ print(f"Here is where each comes from: {inputs['overflow_to_sample_mapping']}.") '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].' ``` -As we can see, the first three examples (at indices 2, 3, and 4 in the training set) each gave four features and the last example (at index 5 in the training set) gave 7 features. +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. -This information will be useful to map each feature we get to its corresponding label. As mentioned earlier, those labels are: +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)` if the answer is not in the corresponding span of the context -- `(start_position, end_position)` if the answer is in the corresponding span of the context, with `start_position` being the index of the token (in the input IDs) at the start of the answer and `end_position` being the index of the token (in the input IDs) where the answer ends +- `(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, cu `start_position` fiind indicele tokenului (în ID-urile de intrare) la începutul răspunsului și `end_position` fiind indicele tokenului (în ID-urile de intrare) unde se termină răspunsul -To determine which of these is the case and, if relevant, the positions of the tokens, we first find the indices that start and end the context in the input IDs. We could use the token type IDs to do this, but since those do not necessarily exist for all models (DistilBERT does not require them, for instance), we'll instead use the `sequence_ids()` method of the `BatchEncoding` our tokenizer returns. +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ă. -Once we have those token indices, we look at the corresponding offsets, which are tuples of two integers representing the span of characters inside the original context. We can thus detect if the chunk of the context in this feature starts after the answer or ends before the answer begins (in which case the label is `(0, 0)`). If that's not the case, we loop to find the first and last token of the answer: +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: ```py answers = raw_datasets["train"][2:6]["answers"] @@ -324,7 +324,7 @@ start_positions, end_positions [85, 53, 21, 0, 0, 70, 33, 0, 40, 0, 0, 0, 68, 35, 0, 0, 0, 0, 0]) ``` -Let's take a look at a few results to verify that our approach is correct. For the first feature we find `(83, 85)` as labels, so let's compare the theoretical answer with the decoded span of tokens from 83 to 85 (inclusive): +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): ```py idx = 0 @@ -342,7 +342,7 @@ print(f"Theoretical answer: {answer}, labels give: {labeled_answer}") 'Theoretical answer: the Main Building, labels give: the Main Building' ``` -So that's a match! Now let's check index 4, where we set the labels to `(0, 0)`, which means the answer is not in the context chunk of that feature: +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: ```py idx = 4 @@ -357,15 +357,15 @@ 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]' ``` -Indeed, we don't see the answer inside the context. +Într-adevăr, nu vedem răspunsul în interiorul contextului. -✏️ **Your turn!** When using the XLNet architecture, padding is applied on the left and the question and context are switched. Adapt all the code we just saw to the XLNet architecture (and add `padding=True`). Be aware that the `[CLS]` token may not be at the 0 position with padding applied. +✏️ **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. -Now that we have seen step by step how to preprocess our training data, we can group it in a function we will apply on the whole training dataset. We'll pad every feature to the maximum length we set, as most of the contexts will be long (and the corresponding samples will be split into several features), so there is no real benefit to applying dynamic padding here: +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: ```py max_length = 384 @@ -428,9 +428,9 @@ def preprocess_training_examples(examples): return inputs ``` -Note that we defined two constants to determine the maximum length used as well as the length of the sliding window, and that we added a tiny bit of cleanup before tokenizing: some of the questions in the SQuAD dataset have extra spaces at the beginning and the end that don't add anything (and take up space when being tokenized if you use a model like RoBERTa), so we removed those extra spaces. +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. -To apply this function to the whole training set, we use the `Dataset.map()` method with the `batched=True` flag. It's necessary here as we are changing the length of the dataset (since one example can give several training features): +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): ```py train_dataset = raw_datasets["train"].map( @@ -445,13 +445,14 @@ len(raw_datasets["train"]), len(train_dataset) (87599, 88729) ``` -As we can see, the preprocessing added roughly 1,000 features. Our training set is now ready to be used -- let's dig into the preprocessing of the validation set! +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! -### Processing the validation data[[processing-the-validation-data]] +### Procesarea datelor de validare[[processing-the-validation-data]] -Preprocessing the validation data will be slightly easier as we don't need to generate labels (unless we want to compute a validation loss, but that number won't really help us understand how good the model is). The real joy will be to interpret the predictions of the model into spans of the original context. For this, we will just need to store both the offset mappings and some way to match each created feature to the original example it comes from. Since there is an ID column in the original dataset, we'll use that ID. +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`: -The only thing we'll add here is a tiny bit of cleanup of the offset mappings. They will contain offsets for the question and the context, but once we're in the post-processing stage we won't have any way to know which part of the input IDs corresponded to the context and which part was the question (the `sequence_ids()` method we used is available for the output of the tokenizer only). So, we'll set the offsets corresponding to the question to `None`: ```py def preprocess_validation_examples(examples): @@ -484,7 +485,7 @@ def preprocess_validation_examples(examples): return inputs ``` -We can apply this function on the whole validation dataset like before: +Putem aplica această funcție pe întregul dataset de validare, ca și înainte: ```py validation_dataset = raw_datasets["validation"].map( @@ -499,25 +500,25 @@ len(raw_datasets["validation"]), len(validation_dataset) (10570, 10822) ``` -In this case we've only added a couple of hundred samples, so it appears the contexts in the validation dataset are a bit shorter. +Î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. -Now that we have preprocessed all the data, we can get to the training. +Acum că am preprocesat toate datele, putem trece la antrenare. {#if fw === 'pt'} -## Fine-tuning the model with the `Trainer` API[[fine-tuning-the-model-with-the-trainer-api]] +## Fine-tuningul modelului cu API-ul `Trainer`[[fine-tuning-the-model-with-the-trainer-api]] -The training code for this example will look a lot like the code in the previous sections -- the hardest thing will be to write the `compute_metrics()` function. Since we padded all the samples to the maximum length we set, there is no data collator to define, so this metric computation is really the only thing we have to worry about. The difficult part will be to post-process the model predictions into spans of text in the original examples; once we have done that, the metric from the 🤗 Datasets library will do most of the work for us. +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. {:else} -## Fine-tuning the model with Keras[[fine-tuning-the-model-with-keras]] +## Fine-tuningul unui model cu Keras[[fine-tuning-the-model-with-keras]] -The training code for this example will look a lot like the code in the previous sections, but computing the metrics will be uniquely challenging. Since we padded all the samples to the maximum length we set, there is no data collator to define, so this metric computation is really the only thing we have to worry about. The hard part will be to post-process the model predictions into spans of text in the original examples; once we have done that, the metric from the 🤗 Datasets library will do most of the work for us. +Codul de antrenare pentru acest exemplu va semăna foarte mult cu codul din secțiunile anterioare, dar calcularea metricilor va fi o provocare unică. Din moment ce am făcut padding tuturor sampleurilor la lungimea maximă pe care am stabilit-o, nu există niciun data collator care să fie definit, 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. {/if} -### Post-processing[[post-processing]] +### Post-procesare[[post-processing]] {#if fw === 'pt'} @@ -529,16 +530,16 @@ The training code for this example will look a lot like the code in the previous {/if} -The model will output logits for the start and end positions of the answer in the input IDs, as we saw during our exploration of the [`question-answering` pipeline](/course/chapter6/3b). The post-processing step will be similar to what we did there, so here's a quick reminder of the actions we took: +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](/course/chapter6/3b). 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: -- We masked the start and end logits corresponding to tokens outside of the context. -- We then converted the start and end logits into probabilities using a softmax. -- We attributed a score to each `(start_token, end_token)` pair by taking the product of the corresponding two probabilities. -- We looked for the pair with the maximum score that yielded a valid answer (e.g., a `start_token` lower than `end_token`). +- 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ât `end_token`). -Here we will change this process slightly because we don't need to compute actual scores (just the predicted answer). This means we can skip the softmax step. To go faster, we also won't score all the possible `(start_token, end_token)` pairs, but only the ones corresponding to the highest `n_best` logits (with `n_best=20`). Since we will skip the softmax, those scores will be logit scores, and will be obtained by taking the sum of the start and end logits (instead of the product, because of the rule \\(\log(ab) = \log(a) + \log(b)\\)). +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 \\(\log(ab) = \log(a) + \log(b)\\\)). -To demonstrate all of this, we will need some kind of predictions. Since we have not trained our model yet, we are going to use the default model for the QA pipeline to generate some predictions on a small part of the validation set. We can use the same processing function as before; because it relies on the global constant `tokenizer`, we just have to change that object to the tokenizer of the model we want to use temporarily: +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: ```python small_eval_set = raw_datasets["validation"].select(range(100)) @@ -552,13 +553,13 @@ eval_set = small_eval_set.map( ) ``` -Now that the preprocessing is done, we change the tokenizer back to the one we originally picked: +Acum că preprocesarea este terminată, schimbăm tokenizerul înapoi la cel pe care l-am ales inițial: ```python tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) ``` -We then remove the columns of our `eval_set` that are not expected by the model, build a batch with all of that small validation set, and pass it through the model. If a GPU is available, we use it to go faster: +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: {#if fw === 'pt'} @@ -579,7 +580,7 @@ with torch.no_grad(): outputs = trained_model(**batch) ``` -Since the `Trainer` will give us predictions as NumPy arrays, we grab the start and end logits and convert them to that format: +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: ```python start_logits = outputs.start_logits.cpu().numpy() @@ -601,7 +602,7 @@ trained_model = TFAutoModelForQuestionAnswering.from_pretrained(trained_checkpoi outputs = trained_model(**batch) ``` -For ease of experimentation, let's convert these outputs to NumPy arrays: +Pentru ușurința experimentelor, să convertim aceste rezultate în array-uri NumPy: ```python start_logits = outputs.start_logits.numpy() @@ -610,7 +611,7 @@ end_logits = outputs.end_logits.numpy() {/if} -Now, we need to find the predicted answer for each example in our `small_eval_set`. One example may have been split into several features in `eval_set`, so the first step is to map each example in `small_eval_set` to the corresponding features in `eval_set`: +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`: ```python import collections @@ -620,13 +621,13 @@ for idx, feature in enumerate(eval_set): example_to_features[feature["example_id"]].append(idx) ``` -With this in hand, we can really get to work by looping through all the examples and, for each example, through all the associated features. As we said before, we'll look at the logit scores for the `n_best` start logits and end logits, excluding positions that give: +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: -- An answer that wouldn't be inside the context -- An answer with negative length -- An answer that is too long (we limit the possibilities at `max_answer_length=30`) +- 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`) -Once we have all the scored possible answers for one example, we just pick the one with the best logit score: +Odată ce avem toate răspunsurile posibile scored pentru un exemplu, îl alegem pe cel cu cel mai bun scor logit: ```python import numpy as np @@ -670,7 +671,7 @@ for example in small_eval_set: predicted_answers.append({"id": example_id, "prediction_text": best_answer["text"]}) ``` -The final format of the predicted answers is the one that will be expected by the metric we will use. As usual, we can load it with the help of the 🤗 Evaluate library: +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: ```python import evaluate @@ -678,7 +679,7 @@ import evaluate metric = evaluate.load("squad") ``` -This metric expects the predicted answers in the format we saw above (a list of dictionaries with one key for the ID of the example and one key for the predicted text) and the theoretical answers in the format below (a list of dictionaries with one key for the ID of the example and one key for the possible answers): +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): ```python theoretical_answers = [ @@ -686,7 +687,7 @@ theoretical_answers = [ ] ``` -We can now check that we get sensible results by looking at the first element of both lists: +Acum putem verifica dacă obținem rezultate rezonabile analizând primul element din ambele liste: ```python print(predicted_answers[0]) @@ -698,7 +699,7 @@ print(theoretical_answers[0]) {'id': '56be4db0acb8001400a502ec', 'answers': {'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]}} ``` -Not too bad! Now let's have a look at the score the metric gives us: +Nu-i deloc rău! Acum putem arunca o privire la scorul pe care ni-l oferă metrica: ```python metric.compute(predictions=predicted_answers, references=theoretical_answers) @@ -708,17 +709,17 @@ metric.compute(predictions=predicted_answers, references=theoretical_answers) {'exact_match': 83.0, 'f1': 88.25} ``` -Again, that's rather good considering that according to [its paper](https://arxiv.org/abs/1910.01108v2) DistilBERT fine-tuned on SQuAD obtains 79.1 and 86.9 for those scores on the whole dataset. +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. {#if fw === 'pt'} -Now let's put everything we just did in a `compute_metrics()` function that we will use in the `Trainer`. Normally, that `compute_metrics()` function only receives a tuple `eval_preds` with logits and labels. Here we will need a bit more, as we have to look in the dataset of features for the offset and in the dataset of examples for the original contexts, so we won't be able to use this function to get regular evaluation results during training. We will only use it at the end of training to check the results. +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. -The `compute_metrics()` function groups the same steps as before; we just add a small check in case we don't come up with any valid answers (in which case we predict an empty string). +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). {:else} -Now let's put everything we just did in a `compute_metrics()` function that we will use after training our model. We will need to pass a bit more than just the output logits, as we have to look in the dataset of features for the offset and in the dataset of examples for the original contexts: +Acum să punem tot ce tocmai am făcut într-o funcție `compute_metrics()` pe care o vom folosi după antrenarea modelului nostru. Va trebui să transmitem puțin mai mult decât output logits, deoarece trebuie să căutăm în datasetul de caracteristici pentru offset și în datasetul de exemple pentru contextele originale: {/if} @@ -776,7 +777,7 @@ def compute_metrics(start_logits, end_logits, features, examples): return metric.compute(predictions=predicted_answers, references=theoretical_answers) ``` -We can check it works on our predictions: +Putem verifica dacă funcționează pe baza predicțiilor noastre: ```python compute_metrics(start_logits, end_logits, eval_set, small_eval_set) @@ -786,13 +787,13 @@ compute_metrics(start_logits, end_logits, eval_set, small_eval_set) {'exact_match': 83.0, 'f1': 88.25} ``` -Looking good! Now let's use this to fine-tune our model. +Arată bine! Acum să folosim acest lucru pentru a face fine-tune modelului nostru. -### Fine-tuning the model[[fine-tuning-the-model]] +### Fine-tuningul modelului[[fine-tuning-the-model]] {#if fw === 'pt'} -We are now ready to train our model. Let's create it first, using the `AutoModelForQuestionAnswering` class like before: +Acum suntem gata să antrenăm modelul. Să-l creăm mai întâi, folosind clasa `AutoModelForQuestionAnswering` ca și înainte: ```python model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) @@ -800,7 +801,7 @@ model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) {:else} -We are now ready to train our model. Let's create it first, using the `TFAutoModelForQuestionAnswering` class like before: +Acum suntem gata să antrenăm modelul. Să-l creăm mai întâi, folosind clasa `TFAutoModelForQuestionAnswering` ca mai înainte: ```python model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) @@ -808,9 +809,10 @@ model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) {/if} -As usual, we get a warning that some weights are not used (the ones from the pretraining head) and some others are initialized randomly (the ones for the question answering head). You should be used to this by now, but that means this model is not ready to be used just yet and needs fine-tuning -- good thing we're about to do that! +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: -To be able to push our model to the Hub, we'll need to log in to Hugging Face. If you're running this code in a notebook, you can do so with the following utility function, which displays a widget where you can enter your login credentials: ```python from huggingface_hub import notebook_login @@ -818,7 +820,7 @@ from huggingface_hub import notebook_login notebook_login() ``` -If you aren't working in a notebook, just type the following line in your terminal: +Dacă nu lucrați într-un notebook, tastați următoarea linie în terminal: ```bash huggingface-cli login @@ -826,11 +828,11 @@ huggingface-cli login {#if fw === 'pt'} -Once this is done, we can define our `TrainingArguments`. As we said when we defined our function to compute the metric, we won't be able to have a regular evaluation loop because of the signature of the `compute_metrics()` function. We could write our own subclass of `Trainer` to do this (an approach you can find in the [question answering example script](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), but that's a bit too long for this section. Instead, we will only evaluate the model at the end of training here and show you how to do a regular evaluation in "A custom training loop" below. +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](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), 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. -This is really where the `Trainer` API shows its limits and the 🤗 Accelerate library shines: customizing the class to a specific use case can be painful, but tweaking a fully exposed training loop is easy. +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ă. -Let's take a look at our `TrainingArguments`: +Să aruncăm o privire la `TrainingArguments`: ```python from transformers import TrainingArguments @@ -847,11 +849,11 @@ args = TrainingArguments( ) ``` -We've seen most of these before: we set some hyperparameters (like the learning rate, the number of epochs we train for, and some weight decay) and indicate that we want to save the model at the end of every epoch, skip evaluation, and upload our results to the Model Hub. We also enable mixed-precision training with `fp16=True`, as it can speed up the training nicely on a recent GPU. +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. {:else} -Now that's done, we can create our TF Datasets. We can use the simple default data collator this time: +Acum, putem crea dataseturile TF. De data aceasta, putem utiliza data collatorul de bază: ```python from transformers import DefaultDataCollator @@ -859,7 +861,7 @@ from transformers import DefaultDataCollator data_collator = DefaultDataCollator(return_tensors="tf") ``` -And now we create the datasets as usual. +Și acum creăm dataseturile ca de obicei. ```python tf_train_dataset = model.prepare_tf_dataset( @@ -876,16 +878,16 @@ tf_eval_dataset = model.prepare_tf_dataset( ) ``` -Next, we set up our training hyperparameters and compile our model: +În continuare, stabilim hiperparametrii de antrenament și compilăm modelul nostru: ```python from transformers import create_optimizer from transformers.keras_callbacks import PushToHubCallback import tensorflow as tf -# The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied -# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset, -# not the original Hugging Face Dataset, so its len() is already num_samples // batch_size. +# Numărul etapelor de antrenare este numărul de sampleuri din dataset, împărțit la dimensiunea batch-ului, apoi înmulțit +# cu numărul total de epoci. Rețineți că datasetul tf_train_dataset de aici este un batched tf.data.Dataset, +# nu datasetul original Hugging Face, deci len() este deja num_samples // batch_size. num_train_epochs = 3 num_train_steps = len(tf_train_dataset) * num_train_epochs optimizer, schedule = create_optimizer( @@ -896,25 +898,26 @@ optimizer, schedule = create_optimizer( ) model.compile(optimizer=optimizer) -# Train in mixed-precision float16 +# Antrenarea în mixed-precision float16 tf.keras.mixed_precision.set_global_policy("mixed_float16") ``` -Finally, we're ready to train with `model.fit()`. We use a `PushToHubCallback` to upload the model to the Hub after each epoch. +În cele din urmă, suntem gata să ne antrenăm cu `model.fit()`. Folosim un `PushToHubCallback` pentru a încărca modelul în Hub după fiecare epocă. {/if} -By default, the repository used will be in your namespace and named after the output directory you set, so in our case it will be in `"sgugger/bert-finetuned-squad"`. We can override this by passing a `hub_model_id`; for instance, to push the model to the `huggingface_course` organization we used `hub_model_id="huggingface_course/bert-finetuned-squad"` (which is the model we linked to at the beginning of this section). +Î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). + {#if fw === 'pt'} -💡 If the output directory you are using exists, it needs to be a local clone of the repository you want to push to (so set a new name if you get an error when defining your `Trainer`). +💡 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`). -Finally, we just pass everything to the `Trainer` class and launch the training: +În cele din urmă, trecem totul în clasa `Trainer` și lansăm antrenarea: ```python from transformers import Trainer @@ -936,17 +939,17 @@ from transformers.keras_callbacks import PushToHubCallback callback = PushToHubCallback(output_dir="bert-finetuned-squad", tokenizer=tokenizer) -# We're going to do validation afterwards, so no validation mid-training +# Vom face validarea după aceea, deci nu vom face o validare în timpul antrenării model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) ``` {/if} -Note that while the training happens, each time the model is saved (here, every epoch) it is uploaded to the Hub in the background. This way, you will be able to to resume your training on another machine if necessary. The whole training takes a while (a little over an hour on a Titan RTX), so you can grab a coffee or reread some of the parts of the course that you've found more challenging while it proceeds. Also note that as soon as the first epoch is finished, you will see some weights uploaded to the Hub and you can start playing with your model on its page. +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. {#if fw === 'pt'} -Once the training is complete, we can finally evaluate our model (and pray we didn't spend all that compute time on nothing). The `predict()` method of the `Trainer` will return a tuple where the first elements will be the predictions of the model (here a pair with the start and end logits). We send this to our `compute_metrics()` function: +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()`: ```python predictions, _, _ = trainer.predict(validation_dataset) @@ -956,7 +959,7 @@ compute_metrics(start_logits, end_logits, validation_dataset, raw_datasets["vali {:else} -Once the training is complete, we can finally evaluate our model (and pray we didn't spend all that compute time on nothing). The `predict()` method of our `model` will take care of getting predictions, and since we did all the hard work of defining a `compute_metrics()` function earlier, we can get our results in a single line: +Odată ce antrenamentul este complet, putem în sfârșit să ne evaluăm modelul (și să ne rugăm să nu fi cheltuit tot timpul de calcul degeaba). Metoda `predict()` a modelului nostru `model` se va ocupa de obținerea predicțiilor și, deoarece am făcut toată munca grea de definire a unei funcții `compute_metrics()` mai devreme, putem obține rezultatele noastre într-o singură linie: ```python predictions = model.predict(tf_eval_dataset) @@ -974,45 +977,45 @@ compute_metrics( {'exact_match': 81.18259224219489, 'f1': 88.67381321905516} ``` -Great! As a comparison, the baseline scores reported in the BERT article for this model are 80.8 and 88.5, so we're right where we should be. +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. {#if fw === 'pt'} -Finally, we use the `push_to_hub()` method to make sure we upload the latest version of the model: +În final, folosim metoda `push_to_hub()` pentru a ne asigura că încărcăm cea mai recentă versiune a modelului: ```py trainer.push_to_hub(commit_message="Training complete") ``` -This returns the URL of the commit it just did, if you want to inspect it: +Aceasta returnează URL-ul commit-ului pe care tocmai l-a făcut, dacă doriți să îl inspectați: ```python out 'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' ``` -The `Trainer` also drafts a model card with all the evaluation results and uploads it. +De asemenea, `Trainer` redactează un model card cu toate rezultatele evaluării și o încarcă. {/if} -At this stage, you can use the inference widget on the Model Hub to test the model and share it with your friends, family, and favorite pets. You have successfully fine-tuned a model on a question answering task -- congratulations! +Î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! -✏️ **Your turn!** Try another model architecture to see if it performs better on this task! +✏️ **E rândul tău!** Încearcă un alt model de arhitectură pentru a vedea dacă are performanțe mai bune la această sarcină! {#if fw === 'pt'} -If you want to dive a bit more deeply into the training loop, we will now show you how to do the same thing using 🤗 Accelerate. +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. -## A custom training loop[[a-custom-training-loop]] +## O buclă de antrenare personalizată[[a-custom-training-loop]] -Let's now have a look at the full training loop, so you can easily customize the parts you need. It will look a lot like the training loop in [Chapter 3](/course/chapter3/4), with the exception of the evaluation loop. We will be able to evaluate the model regularly since we're not constrained by the `Trainer` class anymore. +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](/course/chapter3/4), cu excepția buclei de evaluare. Vom putea evalua modelul în mod regulat, deoarece nu mai suntem constrânși de clasa `Trainer`. -### Preparing everything for training[[preparing-everything-for-training]] +### Pregătirea pentru antrenament[[preparing-everything-for-training]] -First we need to build the `DataLoader`s from our datasets. We set the format of those datasets to `"torch"`, and remove the columns in the validation set that are not used by the model. Then, we can use the `default_data_collator` provided by Transformers as a `collate_fn` and shuffle the training set, but not the validation set: +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: ```py from torch.utils.data import DataLoader @@ -1033,13 +1036,13 @@ eval_dataloader = DataLoader( ) ``` -Next we reinstantiate our model, to make sure we're not continuing the fine-tuning from before but starting from the BERT pretrained model again: +Î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: ```py model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) ``` -Then we will need an optimizer. As usual we use the classic `AdamW`, which is like Adam, but with a fix in the way weight decay is applied: +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: ```py from torch.optim import AdamW @@ -1047,7 +1050,7 @@ from torch.optim import AdamW optimizer = AdamW(model.parameters(), lr=2e-5) ``` -Once we have all those objects, we can send them to the `accelerator.prepare()` method. Remember that if you want to train on TPUs in a Colab notebook, you will need to move all of this code into a training function, and that shouldn't execute any cell that instantiates an `Accelerator`. We can force mixed-precision training by passing `fp16=True` to the `Accelerator` (or, if you are executing the code as a script, just make sure to fill in the 🤗 Accelerate `config` appropriately). +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). ```py from accelerate import Accelerator @@ -1058,7 +1061,7 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( ) ``` -As you should know from the previous sections, we can only use the `train_dataloader` length to compute the number of training steps after it has gone through the `accelerator.prepare()` method. We use the same linear schedule as in the previous sections: +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: ```py from transformers import get_scheduler @@ -1075,7 +1078,7 @@ lr_scheduler = get_scheduler( ) ``` -To push our model to the Hub, we will need to create a `Repository` object in a working folder. First log in to the Hugging Face Hub, if you're not logged in already. We'll determine the repository name from the model ID we want to give our model (feel free to replace the `repo_name` with your own choice; it just needs to contain your username, which is what the function `get_full_repo_name()` does): +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()`): ```py from huggingface_hub import Repository, get_full_repo_name @@ -1089,24 +1092,24 @@ repo_name 'sgugger/bert-finetuned-squad-accelerate' ``` -Then we can clone that repository in a local folder. If it already exists, this local folder should be a clone of the repository we are working with: +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: ```py output_dir = "bert-finetuned-squad-accelerate" repo = Repository(output_dir, clone_from=repo_name) ``` -We can now upload anything we save in `output_dir` by calling the `repo.push_to_hub()` method. This will help us upload the intermediate models at the end of each epoch. +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. -## Training loop[[training-loop]] +## Bucla de antrenare[[training-loop]] -We are now ready to write the full training loop. After defining a progress bar to follow how training goes, the loop has three parts: +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: -- The training in itself, which is the classic iteration over the `train_dataloader`, forward pass through the model, then backward pass and optimizer step. -- The evaluation, in which we gather all the values for `start_logits` and `end_logits` before converting them to NumPy arrays. Once the evaluation loop is finished, we concatenate all the results. Note that we need to truncate because the `Accelerator` may have added a few samples at the end to ensure we have the same number of examples in each process. -- Saving and uploading, where we first save the model and the tokenizer, then call `repo.push_to_hub()`. As we did before, we use the argument `blocking=False` to tell the 🤗 Hub library to push in an asynchronous process. This way, training continues normally and this (long) instruction is executed in the background. +- 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` și `end_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 deoarece `Accelerator` 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 argumentul `blocking=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. -Here's the complete code for the training loop: +Iată codul complet pentru bucla de antrenare: ```py from tqdm.auto import tqdm @@ -1160,7 +1163,7 @@ for epoch in range(num_train_epochs): ) ``` -In case this is the first time you're seeing a model saved with 🤗 Accelerate, let's take a moment to inspect the three lines of code that go with it: +Î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: ```py accelerator.wait_for_everyone() @@ -1168,20 +1171,20 @@ unwrapped_model = accelerator.unwrap_model(model) unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) ``` -The first line is self-explanatory: it tells all the processes to wait until everyone is at that stage before continuing. This is to make sure we have the same model in every process before saving. Then we grab the `unwrapped_model`, which is the base model we defined. The `accelerator.prepare()` method changes the model to work in distributed training, so it won't have the `save_pretrained()` method anymore; the `accelerator.unwrap_model()` method undoes that step. Lastly, we call `save_pretrained()` but tell that method to use `accelerator.save()` instead of `torch.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()`. -Once this is done, you should have a model that produces results pretty similar to the one trained with the `Trainer`. You can check the model we trained using this code at [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). And if you want to test out any tweaks to the training loop, you can directly implement them by editing the code shown above! +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! {/if} -## Using the fine-tuned model[[using-the-fine-tuned-model]] +## Utilizarea modelului fine-tuned[[using-the-fine-tuned-model]] -We've already shown you how you can use the model we fine-tuned on the Model Hub with the inference widget. To use it locally in a `pipeline`, you just have to specify the model identifier: +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: ```py from transformers import pipeline -# Replace this with your own checkpoint +# Înlocuiți acest checkpoint cu propriul checkpoint model_checkpoint = "huggingface-course/bert-finetuned-squad" question_answerer = pipeline("question-answering", model=model_checkpoint) @@ -1200,4 +1203,4 @@ question_answerer(question=question, context=context) 'answer': 'Jax, PyTorch and TensorFlow'} ``` -Great! Our model is working as well as the default one for this pipeline! +Grozav! Modelul nostru funcționează la fel de bine ca cel implicit pentru aceast pipeline! diff --git a/chapters/ro/chapter7/8.mdx b/chapters/ro/chapter7/8.mdx index 78693b25b..71652433d 100644 --- a/chapters/ro/chapter7/8.mdx +++ b/chapters/ro/chapter7/8.mdx @@ -1,22 +1,22 @@ -# Mastering NLP[[mastering-nlp]] +# Înțelegerea NLP[[mastering-nlp]] -If you've made it this far in the course, congratulations -- you now have all the knowledge and tools you need to tackle (almost) any NLP task with 🤗 Transformers and the Hugging Face ecosystem! +Dacă ați ajuns până aici în curs, felicitări - acum aveți toate cunoștințele și instrumentele necesare pentru a aborda (aproape) orice sarcină NLP cu 🤗 Transformers și ecosistemul Hugging Face! -We have seen a lot of different data collators, so we made this little video to help you find which one to use for each task: +Am văzut o mulțime de data collators, așa că am făcut acest mic videoclip pentru a vă ajuta să găsiți cel pe care să îl utilizați pentru fiecare sarcină: -After completing this lightning tour through the core NLP tasks, you should: +După finalizarea acestui tur fulger prin sarcinile de bază ale NLP, ar trebui să: -* Know which architectures (encoder, decoder, or encoder-decoder) are best suited for each task -* Understand the difference between pretraining and fine-tuning a language model -* Know how to train Transformer models using either the `Trainer` API and distributed training features of 🤗 Accelerate or TensorFlow and Keras, depending on which track you've been following -* Understand the meaning and limitations of metrics like ROUGE and BLEU for text generation tasks -* Know how to interact with your fine-tuned models, both on the Hub and using the `pipeline` from 🤗 Transformers +* Știți care arhitecturi (codificator, decodificator sau codificator-decodificator) sunt cele mai potrivite pentru fiecare sarcină +* Înțelegeți diferența dintre preantrenare și fine-tuningul a unui model lingvistic +* Știți cum să antrenați modele Transformer utilizând API-ul `Trainer` și caracteristicile de antrenare distribuită ale 🤗 Accelerate sau TensorFlow și Keras, în funcție de calea pe care ați urmat-o +* Înțelegeți semnificația și limitele metricilor precum ROUGE și BLEU pentru sarcinile de generare a textului +* Știți cum să interacționați cu modelele dvs. ajustate, atât pe Hub, cât și utilizând `pipeline` din 🤗 Transformers -Despite all this knowledge, there will come a time when you'll either encounter a difficult bug in your code or have a question about how to solve a particular NLP problem. Fortunately, the Hugging Face community is here to help you! In the final chapter of this part of the course, we'll explore how you can debug your Transformer models and ask for help effectively. \ No newline at end of file +În ciuda tuturor acestor cunoștințe, va veni un moment în care fie veți întâlni un bug dificil în codul vostru fie veți avea o întrebare despre cum să rezolvați o anumită problemă NLP. Din fericire, comunitatea Hugging Face este aici pentru a vă ajuta! În ultimul capitol al acestei părți a cursului, vom explora modul în care vă puteți face debugging modelelor Transformer și solicita ajutor în mod eficient. \ No newline at end of file diff --git a/chapters/ro/chapter7/9.mdx b/chapters/ro/chapter7/9.mdx index cb517efbf..38e4ee0a0 100644 --- a/chapters/ro/chapter7/9.mdx +++ b/chapters/ro/chapter7/9.mdx @@ -2,179 +2,179 @@ -# End-of-chapter quiz[[end-of-chapter-quiz]] +# Quiz de sfârșit de capitol[[end-of-chapter-quiz]] -Let's test what you learned in this chapter! +Să testăm ce ați învățat în acest capitol! -### 1. Which of the following tasks can be framed as a token classification problem? +### 1. Care dintre următoarele sarcini pot fi încadrate ca o problemă de clasificare a tokenilor? -### 2. What part of the preprocessing for token classification differs from the other preprocessing pipelines? +### 2. Ce parte a preprocesării pentru clasificarea tokenilor diferă de celelalte pipelineuri de preprocesare? -100 to label the special tokens.", - explain: "That's not specific to token classification -- we always use -100 as the label for tokens we want to ignore in the loss." + text: "Folosim -100 pentru a eticheta tokenii speciali.", + explain: "Acest lucru nu este specific clasificării tokenilor - folosim întotdeauna -100 ca label pentru tokenii pe care dorim să îi ignorăm în pierdere." }, { - text: "We need to make sure to truncate or pad the labels to the same size as the inputs, when applying truncation/padding.", - explain: "Indeed! That's not the only difference, though.", + text: "Trebuie să ne asigurăm că labelurile sunt trunchiate sau padded la aceeași dimensiune ca și inputurile, atunci când aplicăm trunchierea/paddingul.", + explain: "Într-adevăr! Totuși, aceasta nu este singura diferență.", correct: true } ]} /> -### 3. What problem arises when we tokenize the words in a token classification problem and want to label the tokens? +### 3. Ce problemă apare atunci când tokenizăm cuvintele într-o problemă de clasificare a tokenilor și dorim să etichetăm tokenii? -100 so they are ignored in the loss." + text: "Tokenizatorul adaugă tokeni speciali și nu avem labeluri pentru ele.", + explain: "Le etichetăm pe acestea cu -100, astfel încât acestea să fie ignorate în pierdere." }, { - text: "Each word can produce several tokens, so we end up with more tokens than we have labels.", - explain: "That is the main problem, and we need to align the original labels with the tokens.", + text: "Fiecare cuvânt poate produce mai mulți tokeni, astfel încât ajungem să avem mai multe tokeni decât labeluri.", + explain: "Aceasta este problema principală, iar noi trebuie să aliniem labelurile originale cu tokenii.", correct: true }, { - text: "The added tokens have no labels, so there is no problem.", - explain: "That's incorrect; we need as many labels as we have tokens or our models will error out." + text: "Tokenii adăugați nu au etichete, deci nu există nicio problemă.", + explain: "Incorect; avem nevoie de atâtea etichete câțo tokeni avem, altfel modelele noastre vor da erori." } ]} /> -### 4. What does "domain adaptation" mean? +### 4. Ce înseamnă "domain adaptation"?? -### 5. What are the labels in a masked language modeling problem? +### 5. Ce sunt labelurile într-o problemă de modelare a limbajului mascat? -### 6. Which of these tasks can be seen as a sequence-to-sequence problem? +### 6. Care dintre aceste sarcini poate fi văzută ca o problemă de sequence-to-sequence? -### 7. What is the proper way to preprocess the data for a sequence-to-sequence problem? +### 7. Care este modalitatea corectă de preprocesare a datelor pentru o problemă de sequence-to-sequence? inputs=... and targets=....", - explain: "This might be an API we add in the future, but that's not possible right now." + text: "Inputurile și targeturile trebuie trimise împreună la tokenizer cu inputs=... și targets=....", + explain: "Acesta ar putea fi un API pe care îl vom adăuga în viitor, dar nu este disponibil acum." }, { - text: "The inputs and the targets both have to be preprocessed, in two separate calls to the tokenizer.", - explain: "That is true, but incomplete. There is something you need to do to make sure the tokenizer processes both properly." + text: "Inputurile și targturile trebuie preprocesate, în două apeluri separate către tokenizer.", + explain: "Acest lucru este adevărat, dar incomplet. Trebuie să faceți ceva pentru a vă asigura că tokenizerul le procesează pe ambele în mod corespunzător." }, { - text: "As usual, we just have to tokenize the inputs.", - explain: "Not in a sequence classification problem; the targets are also texts we need to convert into numbers!" + text: "Ca de obicei, trebuie doar să tokenizăm inputurile.", + explain: "Nu într-o problemă de clasificare a secvențelor; targeturile sunt de asemenea texte pe care trebuie să le convertim în numere!" }, { - text: "The inputs have to be sent to the tokenizer, and the targets too, but under a special context manager.", - explain: "That's correct, the tokenizer needs to be put into target mode by that context manager.", + text: "Inputurile trebuie să fie trimise către tokenizer, la fel și targeturile, dar în cadrul unui manager de context special.", + explain: "Corect, tokenizerul trebuie să fie pus în target mode de către acel context manager.", correct: true } ]} @@ -182,148 +182,148 @@ Let's test what you learned in this chapter! {#if fw === 'pt'} -### 8. Why is there a specific subclass of `Trainer` for sequence-to-sequence problems? +### 8. De ce există o subclasă specifică a `Trainer` pentru problemele sequence-to-sequence? -100", - explain: "That's not a custom loss at all, but the way the loss is always computed." + text: "Deoarece problemele de sequence-to-sequence utilizează o pierdere personalizată, pentru a ignora labelurile setate la -100", + explain: "Aceasta nu este deloc o pierdere personalizată, ci modul în care pierderea este întotdeauna calculată." }, { - text: "Because sequence-to-sequence problems require a special evaluation loop", - explain: "That's correct. Sequence-to-sequence models' predictions are often run using the generate() method.", + text: "Deoarece problemele de sequence-to-sequence la secvență necesită o buclă de evaluare specială", + explain: "Acest lucru este corect. Predicțiile modelelor sequence-to-sequence sunt de obicei rulate cu metoda generate().", correct: true }, { - text: "Because the targets are texts in sequence-to-sequence problems", - explain: "The Trainer doesn't really care about that since they have been preprocessed before." + text: "Deoarece targeturile sunt texte în probleme sequence-to-sequence", + explain: "Trainer-ului nu prea îi pasă de asta, deoarece acestea au fost preprocesate înainte." }, { - text: "Because we use two models in sequence-to-sequence problems", - explain: "We do use two models in a way, an encoder and a decoder, but they are grouped together in one model." + text: "Deoarece folosim două modele în problemele sequence-to-sequence", + explain: "Într-un fel, folosim două modele, un codificator și un decodificator, dar acestea sunt grupate într-un singur model." } ]} /> {:else} -### 9. Why is it often unnecessary to specify a loss when calling `compile()` on a Transformer model? +### 9. De ce este adesea inutil să se specifice o pierdere atunci când se apelează `compile()` pe un model Transformer? {/if} -### 10. When should you pretrain a new model? +### 10. Când ar trebui să preantrenați un model nou? -### 11. Why is it easy to pretrain a language model on lots and lots of texts? +### 11. De ce este ușor să preantrenăm un model lingvistic pe o mulțime de texte? -### 12. What are the main challenges when preprocessing data for a question answering task? +### 12. Care sunt principalele provocări la preprocesarea datelor pentru o sarcină de răspundere a întrebărilor? -### 13. How is post-processing usually done in question answering? +### 13. Cum se face de obicei post-procesarea în răspunderea la întrebări? From 1bd8547e14cb9436be75265143e47ba7d2cd3157 Mon Sep 17 00:00:00 2001 From: eduard-balamatiuc Date: Mon, 26 May 2025 18:36:14 +0300 Subject: [PATCH 11/37] fix: add toctree content --- chapters/rum/_toctree.yml | 60 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/chapters/rum/_toctree.yml b/chapters/rum/_toctree.yml index bb40acb8b..da431247c 100644 --- a/chapters/rum/_toctree.yml +++ b/chapters/rum/_toctree.yml @@ -2,3 +2,63 @@ sections: - local: chapter0/1 title: Introducere +- title: 1. Modele Transformer + sections: + - local: chapter1/1 + title: Introducere + - local: chapter1/2 + title: Procesarea limbajului natural și modelele de limbaj mari + - local: chapter1/3 + title: Transformers, ce pot face? + - local: chapter1/4 + title: Cum funcționează Transformers? + - local: chapter1/5 + title: Modele Encoder + - local: chapter1/6 + title: Modele Decoder + - local: chapter1/7 + title: Modele secvență-la-secvență + - local: chapter1/8 + title: Prejudecăți și limitări + - local: chapter1/9 + title: Rezumat + - local: chapter1/10 + title: Quiz de final de capitol + quiz: 1 + +- title: 2. Using 🤗 Transformers + sections: + - local: chapter2/1 + title: Introducere + - local: chapter2/2 + title: În spatele pipeline-ului + - local: chapter2/3 + title: Modele + - local: chapter2/4 + title: Tokenizatoare + - local: chapter2/5 + title: Gestionarea secvențelor multiple + - local: chapter2/6 + title: Să punem totul cap la cap + - local: chapter2/7 + title: Utilizarea de bază este completă! + - local: chapter2/8 + title: Quiz la final de capitol + quiz: 2 + +- title: 3. Fine-tuning unui model preantrenat + sections: + - local: chapter3/1 + title: Introducere + - local: chapter3/2 + title: Procesarea datelor + - local: chapter3/3 + title: Fine-tuningul unui model cu Trainer API sau Keras + local_fw: { pt: chapter3/3, tf: chapter3/3_tf } + - local: chapter3/4 + title: Un antrenament complet + - local: chapter3/5 + title: Fine-tuning, verificare! + - local: chapter3/6 + title: Quiz la final de capitol + quiz: 3 \ No newline at end of file From 9026c66948904ea214f0e54b22c082c684bf9785 Mon Sep 17 00:00:00 2001 From: eduard-balamatiuc Date: Tue, 27 May 2025 11:04:20 +0300 Subject: [PATCH 12/37] fix: update toctree to only include the current chapter --- chapters/rum/_toctree.yml | 43 --------------------------------------- 1 file changed, 43 deletions(-) diff --git a/chapters/rum/_toctree.yml b/chapters/rum/_toctree.yml index da431247c..ae923929b 100644 --- a/chapters/rum/_toctree.yml +++ b/chapters/rum/_toctree.yml @@ -2,49 +2,6 @@ sections: - local: chapter0/1 title: Introducere -- title: 1. Modele Transformer - sections: - - local: chapter1/1 - title: Introducere - - local: chapter1/2 - title: Procesarea limbajului natural și modelele de limbaj mari - - local: chapter1/3 - title: Transformers, ce pot face? - - local: chapter1/4 - title: Cum funcționează Transformers? - - local: chapter1/5 - title: Modele Encoder - - local: chapter1/6 - title: Modele Decoder - - local: chapter1/7 - title: Modele secvență-la-secvență - - local: chapter1/8 - title: Prejudecăți și limitări - - local: chapter1/9 - title: Rezumat - - local: chapter1/10 - title: Quiz de final de capitol - quiz: 1 - -- title: 2. Using 🤗 Transformers - sections: - - local: chapter2/1 - title: Introducere - - local: chapter2/2 - title: În spatele pipeline-ului - - local: chapter2/3 - title: Modele - - local: chapter2/4 - title: Tokenizatoare - - local: chapter2/5 - title: Gestionarea secvențelor multiple - - local: chapter2/6 - title: Să punem totul cap la cap - - local: chapter2/7 - title: Utilizarea de bază este completă! - - local: chapter2/8 - title: Quiz la final de capitol - quiz: 2 - title: 3. Fine-tuning unui model preantrenat sections: From e61e11e7b4cd8a4f42e8bbda360ddc021db7614c Mon Sep 17 00:00:00 2001 From: eduard-balamatiuc Date: Tue, 27 May 2025 11:26:26 +0300 Subject: [PATCH 13/37] fix: remove unnecessary files --- .codegpt/head | 1 - .gitignore | 3 --- 2 files changed, 4 deletions(-) delete mode 100644 .codegpt/head diff --git a/.codegpt/head b/.codegpt/head deleted file mode 100644 index 61e0708bd..000000000 --- a/.codegpt/head +++ /dev/null @@ -1 +0,0 @@ -d92002a2-d67d-44e7-9370-14bf913c3669 \ No newline at end of file diff --git a/.gitignore b/.gitignore index fa8e74203..9f8d58807 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,3 @@ nbs/ # Byte-compiled __pycache__/ .cache/ - - -.codegpt \ No newline at end of file From 2550453dd843eee3b99c5a7a48c2017fe8f746be Mon Sep 17 00:00:00 2001 From: eduard-balamatiuc Date: Tue, 27 May 2025 11:27:11 +0300 Subject: [PATCH 14/37] fix: remove unecessary files from wrong folder --- chapters/ro/_toctree.yml | 28 -- chapters/ro/chapter0/1.mdx | 110 ------ chapters/ro/chapter2/1.mdx | 28 -- chapters/ro/chapter2/2.mdx | 364 ------------------- chapters/ro/chapter2/3.mdx | 235 ------------- chapters/ro/chapter2/4.mdx | 246 ------------- chapters/ro/chapter2/5.mdx | 338 ------------------ chapters/ro/chapter2/6.mdx | 164 --------- chapters/ro/chapter2/7.mdx | 19 - chapters/ro/chapter2/8.mdx | 310 ---------------- chapters/ro/chapter3/1.mdx | 25 -- chapters/ro/chapter3/2.mdx | 393 --------------------- chapters/ro/chapter3/3.mdx | 174 --------- chapters/ro/chapter3/3_tf.mdx | 206 ----------- chapters/ro/chapter3/4.mdx | 361 ------------------- chapters/ro/chapter3/5.mdx | 26 -- chapters/ro/chapter3/6.mdx | 301 ---------------- chapters/ro/chapter4/1.mdx | 22 -- chapters/ro/chapter4/2.mdx | 98 ------ chapters/ro/chapter4/3.mdx | 641 ---------------------------------- chapters/ro/chapter4/4.mdx | 88 ----- chapters/ro/chapter4/5.mdx | 12 - chapters/ro/chapter4/6.mdx | 233 ------------ 23 files changed, 4422 deletions(-) delete mode 100644 chapters/ro/_toctree.yml delete mode 100644 chapters/ro/chapter0/1.mdx delete mode 100644 chapters/ro/chapter2/1.mdx delete mode 100644 chapters/ro/chapter2/2.mdx delete mode 100644 chapters/ro/chapter2/3.mdx delete mode 100644 chapters/ro/chapter2/4.mdx delete mode 100644 chapters/ro/chapter2/5.mdx delete mode 100644 chapters/ro/chapter2/6.mdx delete mode 100644 chapters/ro/chapter2/7.mdx delete mode 100644 chapters/ro/chapter2/8.mdx delete mode 100644 chapters/ro/chapter3/1.mdx delete mode 100644 chapters/ro/chapter3/2.mdx delete mode 100644 chapters/ro/chapter3/3.mdx delete mode 100644 chapters/ro/chapter3/3_tf.mdx delete mode 100644 chapters/ro/chapter3/4.mdx delete mode 100644 chapters/ro/chapter3/5.mdx delete mode 100644 chapters/ro/chapter3/6.mdx delete mode 100644 chapters/ro/chapter4/1.mdx delete mode 100644 chapters/ro/chapter4/2.mdx delete mode 100644 chapters/ro/chapter4/3.mdx delete mode 100644 chapters/ro/chapter4/4.mdx delete mode 100644 chapters/ro/chapter4/5.mdx delete mode 100644 chapters/ro/chapter4/6.mdx diff --git a/chapters/ro/_toctree.yml b/chapters/ro/_toctree.yml deleted file mode 100644 index 25730606c..000000000 --- a/chapters/ro/_toctree.yml +++ /dev/null @@ -1,28 +0,0 @@ -- title: 0. Configurare - sections: - - local: chapter0/1 - title: Introducere - -- title: 1. Modele Transformer - sections: - - local: chapter1/1 - title: Introducere - - local: chapter1/2 - title: Procesarea limbajului natural și modelele de limbaj mari - - local: chapter1/3 - title: Transformers, ce pot face? - - local: chapter1/4 - title: Cum funcționează Transformers? - - local: chapter1/5 - title: Modele Encoder - - local: chapter1/6 - title: Modele Decoder - - local: chapter1/7 - title: Modele secvență-la-secvență - - local: chapter1/8 - title: Prejudecăți și limitări - - local: chapter1/9 - title: Rezumat - - local: chapter1/10 - title: Quiz de final de capitol - quiz: 1 diff --git a/chapters/ro/chapter0/1.mdx b/chapters/ro/chapter0/1.mdx deleted file mode 100644 index c7c072707..000000000 --- a/chapters/ro/chapter0/1.mdx +++ /dev/null @@ -1,110 +0,0 @@ -# Introducere[[introducere]] - -Bun venit la cursul Hugging Face! Această introducere te va ghida în configurarea environment-ului de lucru. Dacă abia începi cursul, îți recomandăm să arunci o privire mai întâi asupra [Capitolului 1](/course/chapter1), apoi să te întorci și să îți configurezi environment-ul pentru a putea încerca singur codul. - -Toate bibliotecile pe care le vom folosi în acest curs sunt disponibile ca Python packages, așa că aici îți vom arăta cum să configurezi Python environment-ul și să instalezi librăriile specifice de care ai nevoie. - -Vom acoperi două moduri de a-ți configura environment-ul, folosind un notebook Colab sau un Python environment. Simte-te liber să alegi pe cel care ți se potrivește cel mai bine. Pentru începători, recomandăm să începi folosind un notebook Colab. - -Reține că nu vom acoperi sistemul Windows. Dacă folosești Windows, îți recomandăm să urmezi pașii folosind un notebook Colab. Dacă folosești o distribuție Linux sau macOS, poți folosi oricare dintre abordările descrise aici. - -Majoritatea cursului se bazează pe faptul că ai un cont Hugging Face. Îți recomandăm să creezi unul acum: [crează un cont](https://huggingface.co/join). - -## Folosirea unui notebook Google Colab[[folosind-un-notebook-google-colab]] - -Folosirea unui notebook Colab este cea mai simplă configurare posibilă; deschide un notebook în browserul tău și începe imediat să scrii cod! - -Dacă nu ești familiarizat cu Colab, îți recomandăm să începi urmând [introducerea](https://colab.research.google.com/notebooks/intro.ipynb). Colab îți permite să folosești hardware accelerat, cum ar fi GPU-uri sau TPU-uri, și este gratuit pentru sarcini mai mici. - -Odată ce ești confortabil să te descurci în Colab, creează un nou notebook și începe cu configurarea: - -
-Un notebook Colab gol -
- -Următorul pas este să instalezi librăriile pe care le vom folosi în acest curs. Vom folosi `pip` pentru instalare, care este managerul de packages pentru Python. În notebook-uri, poți rula comenzi de sistem începând comanda cu caracterul `!`, așa că poți instala librăria 🤗 Transformers astfel: - -``` -!pip install transformers -``` - -Te poți asigura că pachetul a fost instalat corect importându-l în cadrul mediului tău Python: - -``` -import transformers -``` - -
-O animație care arată rezultatul celor două comenzi de mai sus: instalare și importarea -
- -Aceasta instalează o versiune foarte ușoară a 🤗 Transformers. În special, nu sunt instalate framework-uri specifice de machine learning (cum ar fi PyTorch sau TensorFlow). Deoarece vom folosi multe caracteristici diferite ale librăriei, îți recomandăm să instalezi versiunea pentru development, care vine cu toate dependențele necesare pentru cam orice caz de utilizare imaginabil: - -``` -!pip install transformers[sentencepiece] -``` - -Aceasta va dura puțin timp, dar apoi vei fi gata de drum pentru restul cursului! - -## Folosirea unui virtual environment Python[[folosirea-unui-virtual-environment-python]] - -Dacă preferi să folosești un mediu virtual Python, primul pas este să instalezi Python pe sistemul tău. Îți recomandăm să urmezi [această ghidare](https://realpython.com/installing-python/) pentru a începe. - -Odată ce ai Python instalat, ar trebui să poți rula comenzi Python în terminalul tău. Poți începe rulând următoarea comandă pentru a te asigura că este instalat corect înainte de a trece la pașii următori: `python --version`. Aceasta ar trebui să afișeze versiunea Python disponibilă acum pe sistemul tău. - -Când rulezi o comandă Python în terminalul tău, cum ar fi `python --version`, ar trebui să te gândești la programul care rulează comanda ta ca la Python-ul "principal" de pe sistemul tău. Îți recomandăm să păstrezi această instalare principală liberă de orice pachete și să o folosești pentru a crea environment-uri separate pentru fiecare aplicație pe care lucrezi — în acest fel, fiecare aplicație poate avea propriile sale dependențe și pachete, și nu va trebui să te preocupi de problemele de compatibilitate potențiale cu alte aplicații. - -În Python, acest lucru se face prin [*virtual environments*](https://docs.python.org/3/tutorial/venv.html), care sunt directory trees autonomi ce conțin fiecare o instalare Python cu o anumită versiune Python împreună cu toate pachetele de care are nevoie aplicația. Crearea unui astfel de mediu virtual poate fi realizată cu mai multe instrumente diferite, dar vom folosi pachetul oficial Python pentru acest scop, denumit [`venv`](https://docs.python.org/3/library/venv.html#module-venv). - -În primul rând, creează folder în care dorești ca aplicația ta să locuiască — de exemplu, ai putea dori să faci un nou folder numit *transformers-course* în rădăcina folderului tău personal: - -``` -mkdir ~/transformers-course -cd ~/transformers-course -``` - -Din interiorul acestui folder, creează un virtual environment folosind modulul Python `venv`: - -``` -python -m venv .env -``` - -Acum ar trebui să ai un folder numit *.env* în folderul tău altfel gol: - -``` -ls -a -``` - -```out -. .. .env -``` - -Poți să intri și să ieși din environment folosind comenzile `activate` și `deactivate`: - -``` -# Activează mediul virtual -source .env/bin/activate - -# Dezactivează virtual environment-ul -deactivate -``` - -Te poți asigura că environment-ul este activat rulând comanda `which python`: dacă aceasta indică către virtual environment, atunci l-ai activat cu succes! - -``` -which python -``` - -```out -/home//transformers-course/.env/bin/python -``` - -### Instalarea dependențelor[[instalarea-dependențelor]] - -La fel ca în secțiunea anterioară despre utilizarea instanțelor Google Colab, acum va trebui să instalezi pachetele necesare pentru a continua. Din nou, poți instala versiunea pentru development a 🤗 Transformers folosind managerul de pachete `pip`: - -``` -pip install "transformers[sentencepiece]" -``` - -Acum ești gata să începi! \ No newline at end of file diff --git a/chapters/ro/chapter2/1.mdx b/chapters/ro/chapter2/1.mdx deleted file mode 100644 index 75093a9f9..000000000 --- a/chapters/ro/chapter2/1.mdx +++ /dev/null @@ -1,28 +0,0 @@ -# Introducere[[introducere]] - - - -După cum ați văzut în [Capitolul 1](/course/chapter1), modelele Transformer sunt de obicei foarte voluminoase. Fiind alcătuite din milioane până la zeci de *miliarde* de parametri, instruirea și implementarea acestor modele este o sarcină complicată. În plus, cu noi modele lansate aproape zilnic și fiecare având propria sa implementare, testarea tuturor acestora nu este o sarcină ușoară. - -Biblioteca 🤗 Transformers a fost creată pentru a rezolva această problemă. Scopul său este de a oferi un singur API prin care orice model Transformer poate fi încărcat, instruit și salvat. Principalele caracteristici ale bibliotecii sunt: - -- ** Simplitate în utilizare**: Descărcarea, încărcarea și utilizarea unui model NLP de ultimă generație pentru inferență pot fi realizate în doar două linii de cod. -- **Flexibilitate**: În esența lor, toate modelele sunt simple clase PyTorch `nn.Module` sau TensorFlow `tf.keras.Model` și pot fi manipulate ca orice alte modele în framework-urile lor respective de învățare automată (ML). -- **Simplitate**: Aproape că nu se fac abstractizări în întreaga bibliotecă. „All in one file” este un concept de bază: trecerea înainte a unui model este definită în întregime într-un singur fișier, astfel încât codul în sine să fie ușor de înțeles și hackable. - -Această ultimă caracteristică face 🤗 Transformers destul de diferit de alte biblioteci ML. Modelele nu sunt construite pe module -care sunt partajate între fișiere; în schimb, fiecare model are propriile sale straturi. În plus, pe lângă faptul că modelele sunt mai ușor de abordat și de înțeles, acest lucru vă permite să experimentați cu ușurință pe un model fără a le afecta pe celelalte. - -Acest capitol va începe cu un exemplu end-to-end în care folosim împreună un model și un tokenizer pentru a replica funcția `pipeline()` introdusă în [Capitolul 1](/course/chapter1). În continuare, vom discuta despre API-ul modelului: vom analiza clasele de model și de configurare și vă vom arăta cum să încărcați un model și cum acesta procesează intrările numerice pentru a genera predicții. - -Apoi vom analiza API-ul tokenizer, care este cealaltă componentă principală a funcției `pipeline()`. Tokenizerii se ocupă de prima și ultima etapă de procesare, gestionând conversia de la text la intrări numerice pentru rețeaua neuronală și conversia înapoi la text atunci când este necesar. În cele din urmă, vă vom arăta cum să vă ocupați de trimiterea mai multor propoziții printr-un model în cadrul unui lot pregătit, apoi vom încheia totul cu o examinare mai atentă a funcției `tokenizer()`. - - - -⚠️ -Pentru a beneficia de toate funcțiile disponibile cu Model Hub și 🤗 Transformers, vă recomandăm să vă creați un cont. - - \ No newline at end of file diff --git a/chapters/ro/chapter2/2.mdx b/chapters/ro/chapter2/2.mdx deleted file mode 100644 index b0f1c4855..000000000 --- a/chapters/ro/chapter2/2.mdx +++ /dev/null @@ -1,364 +0,0 @@ - - -# În spatele pipeline-ului[[în-spatele-pipeline-ului]] - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - - -Aceasta este prima secțiune în care conținutul este ușor diferit în funcție de utilizarea PyTorch sau TensorFlow. Schimbați comutatorul din partea de sus a titlului pentru a selecta platforma pe care o preferați! - - -{#if fw === 'pt'} - -{:else} - -{/if} - -Să începem cu un exemplu complet, aruncând o privire la ceea ce s-a întâmplat în spate atunci când am executat următorul cod în [Capitolul 1](/curs/capitol1): - - -```python -from transformers import pipeline - -classifier = pipeline("sentiment-analysis") -classifier( - [ - "I've been waiting for a HuggingFace course my whole life.", - "I hate this so much!", - ] -) -``` - -și am obținut: - -```python out -[{'label': 'POSITIVE', 'score': 0.9598047137260437}, - {'label': 'NEGATIVE', 'score': 0.9994558095932007}] -``` - -După cum am văzut în [Capitolul 1](/course/chapter1), acest pipeline grupează trei etape: preprocesarea, trecerea intrărilor prin model și postprocesarea: - -
-The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. - -
- -Să trecem rapid prin fiecare dintre acestea. - -## Preprocesarea cu un tokenizator[[preprocesarea-cu-un-tokenizerator]] - -La fel ca alte rețele neuronale, modelele Transformer nu pot procesa direct text brut, astfel încât primul pas al pipeline-ului nostru este de a converti intrările de text în numere pe care modelul le poate înțelege. Pentru a face acest lucru, folosim un *tokenizer*, care va fi responsabil pentru: - -- Împărțirea datelor de intrare în cuvinte, părți de cuvinte sau simboluri (cum ar fi punctuația) care se numesc *tokens* -- Maparea fiecărui token într-un număr întreg -- Adăugarea de intrări suplimentare care pot fi utile pentru model - -Toată această preprocesare trebuie efectuată exact în același mod ca atunci când modelul a fost preinstruit, așa că mai întâi trebuie să descărcăm aceste informații din [Model Hub] (https://huggingface.co/models). Pentru a face acest lucru, folosim clasa `AutoTokenizer` și metoda sa `from_pretrained()`. Folosind numele checkpoint-ului modelului nostru, aceasta va prelua automat datele asociate cu tokenizer-ul modelului și le va stoca în cache (astfel încât acestea să fie descărcate doar prima dată când executați codul de mai jos). - -Deoarece punctul de control implicit al pipeline-ului `sentiment-analysis` este `distilbert-base-uncased-finetuned-sst-2-english` (puteți vedea fișa modelului [aici](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), executăm următoarele: - -```python -from transformers import AutoTokenizer - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -``` - -Odată ce avem tokenizatorul, putem să îi transmitem direct propozițiile noastre și vom primi înapoi un dicționar care este gata să fie introdus în modelul nostru! Singurul lucru rămas de făcut este să convertim lista de ID-uri de intrare în tensori. - -Puteți utiliza 🤗 Transformers fără a trebui să vă faceți griji cu privire la cadrul ML utilizat ca backend; ar putea fi PyTorch sau TensorFlow, sau Flax pentru unele modele. Cu toate acestea, modelele Transformer acceptă numai *tensori * ca intrare. Dacă este prima dată când auziți despre tensori, vă puteți gândi la ei ca la matrici NumPy. Un array NumPy poate fi un scalar (0D), un vector (1D), o matrice (2D) sau poate avea mai multe dimensiuni. Este de fapt un tensor; tensorii altor cadre ML se comportă similar și sunt de obicei la fel de simplu de instanțiat ca și array-urile NumPy. - -Pentru a specifica tipul de tensori pe care dorim să îi primim înapoi (PyTorch, TensorFlow sau NumPy simplu), folosim argumentul `return_tensors`: - - -{#if fw === 'pt'} -```python -raw_inputs = [ - "I've been waiting for a HuggingFace course my whole life.", - "I hate this so much!", -] -inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") -print(inputs) -``` -{:else} -```python -raw_inputs = [ - "I've been waiting for a HuggingFace course my whole life.", - "I hate this so much!", -] -inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") -print(inputs) -``` -{/if} - - -Nu vă faceți încă griji cu privire la padding și trunchiere; le vom explica mai târziu. Principalele lucruri de reținut aici sunt că puteți trece o propoziție sau o listă de propoziții, precum și specificarea tipului de tensori pe care doriți să îi primiți înapoi (dacă nu este trecut niciun tip, veți primi o listă de liste ca rezultat) -{#if fw === 'pt'} - -Iată cum arată rezultatele ca tensori PyTorch: - -```python out -{ - 'input_ids': tensor([ - [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], - [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] - ]), - 'attention_mask': tensor([ - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0] - ]) -} -``` -{:else} - -Iată cum arată rezultatele ca tensori TensorFlow: - -```python out -{ - 'input_ids': , - 'attention_mask': -} -``` -{/if} - - -Rezultatul în sine este un dicționar care conține două chei, `input_ids` și `attention_mask`. `input_ids` conține două rânduri de numere întregi (unul pentru fiecare propoziție) care sunt identificatorii unici ai simbolurilor din fiecare propoziție. Vom explica ce este `attention_mask` mai târziu în acest capitol. - -## Parcurgerea modelului[[parcurgerea-modelului]] - -{#if fw === 'pt'} -Putem descărca modelul nostru preinstruit în același mod în care am făcut-o cu tokenizatorul nostru. 🤗 Transformers oferă o clasă `AutoModel` care are și o metodă `from_pretrained()`: - -```python -from transformers import AutoModel - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -model = AutoModel.from_pretrained(checkpoint) -``` -{:else} - -Putem descărca modelul nostru preinstruit în același mod în care am făcut-o cu tokenizatorul nostru. 🤗 Transformers oferă o clasă `TFAutoModel` care are și o metodă `from_pretrained`: - -```python -from transformers import TFAutoModel - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -model = TFAutoModel.from_pretrained(checkpoint) -``` -{/if} - - -În acest fragment de cod, am descărcat același checkpoint pe care l-am folosit anterior în pipeline-ul nostru (de fapt, ar fi trebuit să fie deja în cache) și am instanțiat un model cu acesta. - -Această arhitectură conține doar modulul Transformer de bază: având în vedere anumite intrări, acesta produce ceea ce vom numi *stări ascunse*, cunoscute și ca *caracteristici*. Pentru fiecare intrare a modelului, vom extrage un vector înalt-dimensional care reprezintă **înțelegerea contextuală a intrării respective de către modelul Transformer**. - -Dacă acest lucru nu are sens, nu vă faceți griji. Vom explica totul mai târziu. - -Deși aceste stări ascunse pot fi utile pe cont propriu, ele sunt de obicei intrări pentru o altă parte a modelului, cunoscută sub numele de *head*. În [Capitolul 1](/curs/capitol1), diferitele sarcini ar fi putut fi efectuate cu aceeași arhitectură, dar fiecăreia dintre aceste sarcini îi va fi asociat un head diferit. - -### Un vector multidimensional?[[un-vector-multidimensional]] - - -Vectorul emis de modulul Transformator este de obicei de dimensiuni mari. Acesta are în general trei dimensiuni: - -- **Dimensiunea lotului**: Numărul de secvențe prelucrate simultan (2 în exemplul nostru). -- **Lungimea secvenței**: Lungimea reprezentării numerice a secvenței (16 în exemplul nostru). -- **Dimensiunea ascunsă**: Dimensiunea vectorială a fiecărei intrări a modelului. - -Se spune că este multidimensional din cauza ultimei valori. Dimensiunea ascunsă poate fi foarte mare (768 este comună pentru modelele mai mici, iar în modelele mai mari aceasta poate ajunge la 3072 sau mai mult). - -Putem vedea acest lucru dacă introducem în modelul nostru intrările pe care le-am preprocesat: - -{#if fw === 'pt'} -```python -outputs = model(**inputs) -print(outputs.last_hidden_state.shape) -``` - -```python out -torch.Size([2, 16, 768]) -``` -{:else} -```py -outputs = model(inputs) -print(outputs.last_hidden_state.shape) -``` - -```python out -(2, 16, 768) -``` -{/if} - - -Rețineți că ieșirile modelelor 🤗 Transformers se comportă ca `namedtuple`s sau dicționare. Puteți accesa elementele prin atribute (așa cum am făcut noi) sau prin cheie (`outputs[„last_hidden_state”]`), sau chiar prin index dacă știți exact unde se află lucrul pe care îl căutați (`outputs[0]`). - -### Modele de head-uri: Înțelegerea numerelor[modele-de-head-uri-înțelegerea-numerelor]] - -Head-urile modelelor iau ca intrare vectorul multidimensional al stărilor ascunse și le proiectează pe o altă dimensiune. Acestea sunt de obicei compuse din unul sau câteva straturi liniare: - -
-A Transformer network alongside its head. - -
- - -Rezultatul modelului Transformer este trimis direct la head-ul modelului pentru a fi prelucrat. - -În această diagramă, modelul este reprezentat de stratul său de încorporare și de straturile următoare. Stratul de încorporare convertește fiecare ID de intrare din intrarea tokenizată într-un vector care reprezintă tokenul asociat. Straturile ulterioare manipulează acești vectori folosind mecanismul de atenție pentru a produce reprezentarea finală a propozițiilor. - -Există multe arhitecturi diferite disponibile în 🤗 Transformers, fiecare fiind concepută în jurul abordării unei sarcini specifice. Iată o listă neexhaustivă: - -- `*Model` (extragerea stărilor ascunse) -- `*ForCausalLM` -- `*ForMaskedLM` -- `*ForMultipleChoice` -- `*ForQuestionAnswering` -- `*ForSequenceClassification` -- `*ForTokenClassification` -- și altele 🤗 - -{#if fw === 'pt'} - Pentru exemplul nostru, vom avea nevoie de un model cu un head de clasificare a secvențelor (pentru a putea clasifica propozițiile ca fiind pozitive sau negative). Așadar, nu vom utiliza clasa `AutoModel`, ci `AutoModelForSequenceClassification`: - -```python -from transformers import AutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) -outputs = model(**inputs) -``` -{:else} - Pentru exemplul nostru, vom avea nevoie de un model cu un head de clasificare a secvențelor (pentru a putea clasifica propozițiile ca fiind pozitive sau negative). Așadar, nu vom utiliza clasa `TFAutoModel`, ci `TFAutoModelForSequenceClassification`: - -```python -from transformers import TFAutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) -outputs = model(inputs) -``` -{/if} - -Acum, dacă ne uităm la forma ieșirilor noastre, dimensiunea va fi mult mai mică: head-ul modelului ia ca intrare vectorii multidimensionali pe care i-am văzut înainte și scoate vectori care conțin două valori (una pentru fiecare etichetă): - -```python -print(outputs.logits.shape) -``` - -{#if fw === 'pt'} -```python out -torch.Size([2, 2]) -``` -{:else} -```python out -(2, 2) -``` -{/if} - - -Deoarece avem doar două propoziții și două etichete, rezultatul pe care îl obținem din modelul nostru este de forma 2 x 2. - -## Postprocesarea rezultatului[[postprocesarea-rezultatului]] - -Valorile pe care le obținem ca rezultat al modelului nostru nu au neapărat sens în sine. Să aruncăm o privire: - -```python -print(outputs.logits) -``` - -{#if fw === 'pt'} -```python out -tensor([[-1.5607, 1.6123], - [ 4.1692, -3.3464]], grad_fn=) -``` -{:else} -```python out - -``` -{/if} - -Modelul nostru a prezis `[-1.5607, 1.6123]` pentru prima propoziție și `[ 4.1692, -3.3464]` pentru cea de-a doua. Acestea nu sunt probabilități, ci *logits*, scorurile brute, nenormalizate, emise de ultimul strat al modelului. Pentru a fi convertite în probabilități, acestea trebuie să treacă printr-un strat [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) (toate modelele 🤗 Transformers produc logits, deoarece funcția de pierdere pentru formare va fuziona în general ultima funcție de activare, cum ar fi SoftMax, cu funcția de pierdere reală, cum ar fi entropia încrucișată): - -{#if fw === 'pt'} -```py -import torch - -predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) -print(predictions) -``` -{:else} -```py -import tensorflow as tf - -predictions = tf.math.softmax(outputs.logits, axis=-1) -print(predictions) -``` -{/if} - -{#if fw === 'pt'} -```python out -tensor([[4.0195e-02, 9.5980e-01], - [9.9946e-01, 5.4418e-04]], grad_fn=) -``` -{:else} -```python out -tf.Tensor( -[[4.01951671e-02 9.59804833e-01] - [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) -``` -{/if} - - -Acum putem vedea că modelul a prezis `[0.0402, 0.9598]` pentru prima propoziție și `[0.9995, 0.0005]` pentru cea de-a doua. Acestea sunt scoruri de probabilitate care pot fi recunoscute. - -Pentru a obține etichetele corespunzătoare fiecărei poziții, putem inspecta atributul `id2label` din configurația modelului (mai multe despre acest lucru în secțiunea următoare): - -```python -model.config.id2label -``` - -```python out -{0: 'NEGATIVE', 1: 'POSITIVE'} -``` -Acum putem concluziona că modelul a prezis următoarele: - -- Prima propoziție: NEGATIV: 0,0402, POZITIV: 0,9598 -- A doua propoziție: NEGATIVĂ: 0,9995, POZITIVĂ: 0,0005 - -Am reprodus cu succes cele trei etape ale pipeline-ului: preprocesarea cu tokenizere, trecerea intrărilor prin model și postprocesarea! Acum haideți să analizăm în profunzime fiecare dintre aceste etape. - - - -✏️ -** Încercați!** Alegeți două (sau mai multe) texte proprii și treceți-le prin conducta `sentiment-analysis`. Apoi repetați pașii pe care i-ați văzut aici și verificați dacă obțineți aceleași rezultate! - - diff --git a/chapters/ro/chapter2/3.mdx b/chapters/ro/chapter2/3.mdx deleted file mode 100644 index e0f0758df..000000000 --- a/chapters/ro/chapter2/3.mdx +++ /dev/null @@ -1,235 +0,0 @@ - - -# Modele[[modele]] - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} - -{:else} - -{/if} - -{#if fw === 'pt'} - -În această secțiune vom analiza mai detaliat crearea și utilizarea unui model. Vom folosi clasa `AutoModel`, care este utilă atunci când doriți să instanțiați orice model dintr-un checkpoint. - -Clasa `AutoModel` și toate celelalte clase înrudite sunt, de fapt, simple wrappere ale gamei largi de modele disponibile în bibliotecă. Este un wrapper inteligent, deoarece poate ghici automat arhitectura modelului corespunzător pentru checkpoint-ul dumneavoastră și apoi instanțiază un model cu această arhitectură. - -{:else} - -În această secțiune vom analiza mai detaliat crearea și utilizarea unui model. Vom utiliza clasa `TFAutoModel`, care este utilă atunci când doriți să instanțați un model dintr-un checkpoint. - -Clasa `TFAutoModel` și toate clasele înrudite cu aceasta sunt de fapt simple wrappere ale gamei largi de modele disponibile în bibliotecă. Este un wrapper inteligent, deoarece poate ghici automat arhitectura modelului corespunzător pentru checkpoint-ul dumneavoastră și apoi instanțiază un model cu această arhitectură. - -{/if} - -Cu toate acestea, dacă știți ce tip de model doriți să utilizați, puteți folosi direct clasa care definește arhitectura acestuia. Să aruncăm o privire la modul în care funcționează acest lucru cu un model BERT. - -## Crearea unui Transformer[[crearea-unui-transformer]] - -Primul lucru pe care va trebui să îl facem pentru a inițializa un model BERT este să încărcăm un obiect de configurare: - -{#if fw === 'pt'} -```py -from transformers import BertConfig, BertModel - -# Construirea configurației -config = BertConfig() - -# Construirea modelului pornind de la configurație -model = BertModel(config) -``` -{:else} -```py -from transformers import BertConfig, TFBertModel - -# Construirea configurației -config = BertConfig() - -# Construirea modelului pornind de la configurație -model = TFBertModel(config) -``` -{/if} - -Configurația conține multe atribute care sunt utilizate pentru a construi modelul: - -```py -print(config) -``` - -```python out -BertConfig { - [...] - "hidden_size": 768, - "intermediate_size": 3072, - "max_position_embeddings": 512, - "num_attention_heads": 12, - "num_hidden_layers": 12, - [...] -} -``` - -Deși nu ați văzut încă ce fac toate aceste atribute, ar trebui să recunoașteți unele dintre ele: atributul `hidden_size` definește dimensiunea vectorului `hidden_states`, iar `num_hidden_layers` definește numărul de straturi pe care le are modelul Transformer. - -### Diferite metode de încărcare[[diferite-metode-de-încărcare]] - -Crearea unui model din configurația implicită îl inițializează cu valori aleatorii: - - -{#if fw === 'pt'} -```py -from transformers import BertConfig, BertModel - -config = BertConfig() -model = BertModel(config) - -# Modelul este inițializat aleatoriu! -``` -{:else} -```py -from transformers import BertConfig, TFBertModel - -config = BertConfig() -model = TFBertModel(config) - -# Modelul este inițializat aleatoriu! -``` -{/if} - -Modelul poate fi utilizat în această stare, dar va produce rezultate neclare; mai întâi trebuie să fie antrenat. Am putea antrena modelul de la zero pentru sarcina în cauză, dar, după cum ați văzut în [Capitolul 1](/course/chapter1), acest lucru ar necesita mult timp și o mulțime de date și ar avea un impact semnificativ asupra mediului. Pentru a evita efortul inutil, este esențial să puteți partaja și reutiliza modelele care au fost deja formate. - -Încărcarea unui model Transformer care este deja instruit este simplă - putem face acest lucru utilizând metoda `from_pr - -{#if fw === 'pt'} -```py -from transformers import BertModel - -model = BertModel.from_pretrained("bert-base-cased") -``` - -După cum ați văzut mai devreme, am putea înlocui `BertModel` cu clasa `AutoModel` echivalentă. Vom face acest lucru în continuare, deoarece acest lucru produce un cod compatibil cu checkpoint-urile; dacă codul dumneavoastră funcționează pentru un checkpoint, ar trebui să funcționeze fără probleme pentru altul. Acest lucru este valabil chiar dacă arhitectura este diferită, atât timp cât checkpoint-ul a fost instruit pentru o sarcină similară (de exemplu, o sarcină de analiză a sentimentelor). - -{:else} -```py -from transformers import TFBertModel - -model = TFBertModel.from_pretrained("bert-base-cased") -``` - -După cum ați văzut mai devreme, am putea înlocui `BertModel` cu clasa `AutoModel` echivalentă. Vom face acest lucru în continuare, deoarece acest lucru produce un cod compatibil cu checkpoint-urile; dacă codul dumneavoastră funcționează pentru un checkpoint, ar trebui să funcționeze fără probleme pentru altul. Acest lucru este valabil chiar dacă arhitectura este diferită, atât timp cât checkpoint-ul a fost instruit pentru o sarcină similară (de exemplu, o sarcină de analiză a sentimentelor). - -{/if} - -În exemplul de cod de mai sus nu am utilizat `BertConfig` și, în loc de acesta, am încărcat un model preinstruit prin intermediul identificatorului `bert-base-cased`. Acesta este un checkpoint al modelului care a fost antrenat chiar de autorii BERT; puteți găsi mai multe detalii despre acesta în [model card](https://huggingface.co/bert-base-cased). - -Acest model este acum inițializat cu toate weight-urile din checkpoint. Acesta poate fi utilizat direct pentru inferență pe sarcinile pe care a fost antrenat și poate fi, de asemenea, ajustat pe o sarcină nouă. Prin antrenarea cu weight-uri instruite în prealabil, putem obține rapid rezultate bune. - -Greutățile au fost descărcate și stocate în cache (astfel încât apelurile viitoare la metoda `from_pretrained()` nu le vor descărca din nou) în folderul cache, care are ca valoare implicită *~/.cache/huggingface/transformers*. Puteți personaliza folderul cache prin setarea variabilei de mediu `HF_HOME`. - -Identificatorul utilizat pentru încărcarea modelului poate fi identificatorul oricărui model de pe Model Hub, atâta timp cât acesta este compatibil cu arhitectura OARET. Lista completă a checkpoint-urilor OARET disponibile poate fi găsită [aici](https://huggingface.co/models?filter=bert). - - -### Metode de păstrare[[metode-de-păstrare]] - -Salvarea unui model este la fel de ușoară ca încărcarea unuia - folosim metoda `save_pretrained()`, care este analogă metodei `from_pretrained()`: - -```py -model.save_pretrained("directory_on_my_computer") -``` - -Se salvează două fișiere pe disc: - -{#if fw === 'pt'} -``` -ls directory_on_my_computer - -config.json pytorch_model.bin -``` -{:else} -``` -ls directory_on_my_computer - -config.json tf_model.h5 -``` -{/if} - -Dacă aruncați o privire la fișierul *config.json*, veți recunoaște atributele necesare pentru construirea arhitecturii modelului. Acest fișier conține, de asemenea, unele metadate, cum ar fi locul de origine al checkpoint-ului și ce versiune 🤗 Transformers ați folosit când ați salvat ultima dată checkpoint-ul. - -{#if fw === 'pt'} - -Fișierul *pytorch_model.bin* este cunoscut sub numele de *state dictionary*; acesta conține toate weight-urile modelului dumneavoastră. Cele două fișiere merg mână în mână; configurația este necesară pentru a cunoaște arhitectura modelului, în timp ce weight-urile modelului sunt parametrii modelului. - -{:else} - -Fișierul *tf_model.h5* este cunoscut sub numele de *state dictionary*; acesta conține toate weight-urile modelului dumneavoastră. Cele două fișiere merg mână în mână; configurația este necesară pentru a cunoaște arhitectura modelului, în timp ce weight-urile modelului sunt parametrii modelului. - -{/if} - -## Utilizarea unui model Transformer pentru inferență[[utilizarea-unui-model-transformer-pentru-inferență]] - - -Acum că știți cum să încărcați și să salvați un model, să încercăm să îl folosim pentru a face câteva predicții. Modelele Transformer pot procesa doar numere - numere pe care le generează tokenizatorul. Dar înainte de a discuta despre tokenizeri, să explorăm ce intrări acceptă modelul. - -Tokenizerii se pot ocupa de transformarea intrărilor în tensori ai framework-ului corespunzător, dar pentru a vă ajuta să înțelegeți ce se întâmplă, vom arunca o privire rapidă la ceea ce trebuie făcut înainte de trimiterea intrărilor către model. - -Să spunem că avem câteva secvențe: - -```py -sequences = ["Hello!", "Cool.", "Nice!"] -``` - -Tokenizatorul le convertește în indici de vocabular care sunt denumiți în mod obișnuit *input IDs*. Fiecare secvență este acum o listă de numere! Rezultatul este: - -```py no-format -encoded_sequences = [ - [101, 7592, 999, 102], - [101, 4658, 1012, 102], - [101, 3835, 999, 102], -] -``` - -Aceasta este o listă de secvențe codificate: o listă de liste. Tensorii acceptă numai forme dreptunghiulare (gândiți-vă la matrici). Această " listă " are deja o formă dreptunghiulară, astfel încât convertirea sa într-un tensor este ușoară: - -{#if fw === 'pt'} -```py -import torch - -model_inputs = torch.tensor(encoded_sequences) -``` -{:else} -```py -import tensorflow as tf - -model_inputs = tf.constant(encoded_sequences) -``` -{/if} - -### Utilizarea tensorilor ca intrări în model[[utilizarea-tensorilor-ca-intrări-în-to-model]] - -Utilizarea tensorilor cu ajutorul modelului este extrem de simplă - trebuie doar să apelăm modelul cu intrările: - -```py -output = model(model_inputs) -``` - -Deși modelul acceptă o mulțime de argumente diferite, doar ID-urile de intrare sunt necesare. Vom explica mai târziu ce fac celelalte argumente și când sunt necesare, -dar mai întâi trebuie să aruncăm o privire mai atentă la tokenizerii care construiesc intrările pe care un model Transformer le poate înțelege. \ No newline at end of file diff --git a/chapters/ro/chapter2/4.mdx b/chapters/ro/chapter2/4.mdx deleted file mode 100644 index c38234e14..000000000 --- a/chapters/ro/chapter2/4.mdx +++ /dev/null @@ -1,246 +0,0 @@ - - -# Tokenizere[[tokenizere]] - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - - - -Tokenizoarele sunt una dintre componentele de bază ale pipeline-ului NLP. Acestea au un singur scop: să transforme textul în date care pot fi prelucrate de model. Modelele pot procesa doar numere, astfel încât tokenizerii trebuie să convertească intrările noastre de text în date numerice. În această secțiune, vom explora exact ce se întâmplă în pipeline-ul de tokenizare. - -În sarcinile NLP, datele care sunt în general prelucrate sunt text brut. Iată un exemplu de astfel de text: - -``` -Jim Henson was a puppeteer - -``` - -Cu toate acestea, modelele pot procesa doar numere, deci trebuie să găsim o modalitate de a converti textul brut în numere. Asta fac tokenizerii și există o mulțime de modalități de a face acest lucru. Scopul este de a găsi cea mai relevantă reprezentare - adică cea care are cel mai mare sens pentru model - și, dacă este posibil, cea mai mică reprezentare. - -Să aruncăm o privire la câteva exemple de algoritmi de tokenizare și să încercăm să răspundem la unele dintre întrebările pe care le puteți avea despre tokenizare. - - - -## Word-based[[word-based]] - - - - -Primul tip de tokenizator care îmi vine în minte este _word-based_. În general, este foarte ușor de configurat și de utilizat, cu doar câteva reguli, și adesea produce rezultate satisfăcătoare. De exemplu, în imaginea de mai jos, obiectivul este de a împărți textul brut în cuvinte și de a găsi o reprezentare numerică pentru fiecare dintre ele: - -
- An example of word-based tokenization. - -
- - -Există diferite moduri de a împărți textul. De exemplu, am putea folosi spațiu pentru a tokeniza textul în cuvinte prin aplicarea funcției `split()` din Python: - - -```py -tokenized_text = "Jim Henson was a puppeteer".split() -print(tokenized_text) -``` - -```python out -['Jim', 'Henson', 'was', 'a', 'puppeteer'] -``` - -Există, de asemenea, variații ale tokenizerelor de cuvinte care au reguli suplimentare pentru punctuație. Cu acest tip de tokenizator, putem ajunge la „vocabulare” destul de mari, unde un vocabular este definit de numărul total de token-uri independente pe care le avem în corpus. - -Fiecărui cuvânt i se atribuie un ID, începând de la 0 și mergând până la dimensiunea vocabularului. Modelul utilizează aceste ID-uri pentru a identifica fiecare cuvânt. - -Dacă dorim să acoperim complet o limbă cu un tokenizer bazat pe cuvinte, va trebui să avem un identificator pentru fiecare cuvânt din limbă, ceea ce va genera o cantitate uriașă de token-uri. De exemplu, există peste 500 000 de cuvinte în limba engleză, astfel încât, pentru a construi o hartă de la fiecare cuvânt la un ID de intrare, ar trebui să ținem evidența unui număr atât de mare de ID-uri. În plus, cuvinte precum „dog” sunt reprezentate diferit de cuvinte precum „dogs”, iar modelul nu va avea inițial nicio modalitate de a ști că „dog” și „dogs” sunt similare: va identifica cele două cuvinte ca neavând legătură. Același lucru este valabil și pentru alte cuvinte similare, precum „run” și „running”, pe care modelul nu le va vedea inițial ca fiind similare. - -În cele din urmă, avem nevoie de un token personalizat pentru a reprezenta cuvintele care nu se află în vocabularul nostru. Acesta este cunoscut sub numele de token "necunoscut", adesea reprezentat ca "[UNK]" sau "<unk>". În general, este un indiciu rău dacă vedeți că tokenizatorul produce o mulțime de astfel de token-uri, deoarece nu a fost capabil să recupereze reprezentarea sensibilă a unui cuvânt și veți pierde informații pe parcurs. Scopul elaborării vocabularului este de a face în așa fel încât tokenizatorul să tokenizeze cât mai puține cuvinte posibil în tokenul necunoscut. - -O modalitate de a reduce numărul de token-uri necunoscute este de a merge un nivel mai adânc, folosind un tokenizer _character-based_. - -## Character-based[[character-based]] - - - - -Tokenizerele character-based împart textul în caractere, și nu în cuvinte. Acest lucru are două beneficii principale: - -- Dimensiunea vocabularului este mult mai mică. -- Există mult mai puține token-uri în afara vocabularului (necunoscute), deoarece fiecare cuvânt poate fi construit din caractere. - -Dar și aici apar unele probleme legate de spații și punctuație: - - -
- An example of character-based tokenization. - -
- - -Nici această abordare nu este perfectă. Deoarece reprezentarea se bazează acum pe caractere și nu pe cuvinte, s-ar putea spune că, din punct de vedere intuitiv, este mai puțin relevantă: fiecare caracter nu înseamnă mult în sine, în timp ce în cazul cuvintelor situația este diferită. Totuși, acest lucru variază din nou în funcție de limbă; în chineză, de exemplu, fiecare caracter conține mai multe informații decât un caracter într-o limbă latină. - -Un alt lucru care trebuie luat în considerare este faptul că ne vom trezi cu o cantitate foarte mare de token-uri care vor fi prelucrate de modelul nostru: în timp ce un cuvânt ar fi un singur token cu un tokenizator bazat pe cuvinte, acesta se poate transforma cu ușurință în 10 sau mai multe token-uri atunci când este convertit în caractere. - -Pentru a obține ce este mai bun din ambele lumi, putem utiliza o a treia tehnică care combină cele două abordări: *subword tokenization*. - -## Subword tokenization[[subword-tokenization]] - - - -Algoritmii "Subword Tokenization " se bazează pe principiul conform căruia cuvintele utilizate frecvent nu ar trebui să fie împărțite în cuvinte secundare mai mici, dar cuvintele rare ar trebui să fie descompuse în cuvinte secundare semnificative. - -De exemplu, " annoyingly " ar putea fi considerat un cuvânt rar și ar putea fi descompus în " annoying " și " ly ". Este probabil ca ambele să apară mai frecvent ca subcuvinte de sine stătătoare, în timp ce, în același timp, sensul cuvântului " annoyingly " este păstrat de sensul compus al cuvintelor " annoying " și " ly ". - -Iată un exemplu care arată modul în care un algoritm de subword tokenization ar tokeniza secvența "Let's do tokenization!": - -
- A subword tokenization algorithm. - -
- -Aceste părți de cuvinte oferă în cele din urmă o mulțime de semnificații semantice: în exemplul de mai sus, "tokenization" a fost împărțit în "token" și "ization", două cuvinte care au o semnificație semantică, fiind în același timp eficiente din punct de vedere al spațiului (sunt necesare doar două cuvinte pentru a reprezenta un cuvânt lung). Acest lucru ne permite să avem o acoperire relativ bună cu vocabulare de dimensiuni mici și aproape fără token-uri necunoscute. - -Această abordare este utilă în special în limbile aglutinative, cum ar fi turca, în care se pot forma cuvinte complexe (aproape) arbitrar de lungi prin combinarea părților de cuvinte. - -### Și nu numai![[și-nu-numai]] - -Nu este surprinzător faptul că există mult mai multe tehnici. Iată câteva: - -- BPE la nivel de byte, utilizată în GPT-2 -- WordPiece, utilizată în BERT -- SentencePiece sau Unigram, utilizate în mai multe modele multilingve - -Acum ar trebui să cunoașteți destul de bine cum funcționează tokenizerele pentru a începe să utilizați API- - -## Încărcarea și salvarea[[încărcarea-și-salvarea]] - -Încărcarea și salvarea tokenizerelor este la fel de simplă ca în cazul modelelor. De fapt, se bazează pe aceleași două metode: `from_pretrained()` și `save_pretrained()`. Aceste metode vor încărca sau salva algoritmul utilizat de tokenizator (un pic ca *arhitectura* modelului), precum și vocabularul său (un pic ca *weight-urile* modelului). - -Încărcarea tokenizatorului BERT antrenat cu același punct de control ca BERT se face în același mod ca și încărcarea modelului, cu excepția faptului că folosim clasa `BertTokenizer`: - -```py -from transformers import BertTokenizer - -tokenizer = BertTokenizer.from_pretrained("bert-base-cased") -``` - -{#if fw === 'pt'} -Similar cu `AutoModel`, clasa `AutoTokenizer` va prelua clasa tokenizer corespunzătoare din bibliotecă pe baza numelui checkpoint-ului și poate fi utilizată direct cu orice checkpoint: - -{:else} -Similar cu `TFAutoModel`, clasa `AutoTokenizer` va prelua clasa tokenizer corespunzătoare din bibliotecă pe baza numelui checkpoint-ului și poate fi utilizată direct cu orice checkpoint: - -{/if} - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -``` - -Acum putem utiliza tokenizatorul așa cum am arătat în secțiunea anterioară: - -```python -tokenizer("Using a Transformer network is simple") -``` - -```python out -{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102], - 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], - 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} -``` - -Salvarea unui tokenizer este identică cu salvarea unui model: - -```py -tokenizer.save_pretrained("directory_on_my_computer") -``` - -Vom vorbi mai mult despre `token_type_ids` în [Capitolul 3](/course/chapter3) și vom explica cheia `attention_mask` puțin mai târziu. Mai întâi, să vedem cum sunt generate `input_ids`. Pentru a face acest lucru, va trebui să ne uităm la metodele intermediare ale tokenizatorului. - -## Codificarea[[codificarea]] - - - -Al doilea pas este să convertim aceste token-uri în numere, astfel încât să putem construi un tensor din ele și să le introducem în model. Pentru a face acest lucru, tokenizatorul are un *vocabular*, care este partea pe care o descărcăm atunci când îl instanțiem cu metoda `from_pretrained()`. Din nou, trebuie să folosim același vocabular utilizat atunci când modelul a fost preinstruit. - -Pentru a înțelege mai bine cele două etape, le vom explora separat. Rețineți că vom utiliza unele metode care efectuează părți ale pipeline-ului de tokenizare separat pentru a vă arăta rezultatele intermediare ale acestor etape, dar, în practică, ar trebui să apelați tokenizatorul direct pe intrările dvs. (așa cum se arată în secțiunea 2). - -### Tokenizarea[[tokenizarea]] - -Procesul de tokenizare este realizat prin metoda `tokenize()`: - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") - -sequence = "Using a Transformer network is simple" -tokens = tokenizer.tokenize(sequence) - -print(tokens) -``` - -Rezultatul acestei metode este o listă de șiruri de caractere, sau token-uri: - -```python out -['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] -``` - -Acest tokenizator este un tokenizator de părți de cuvinte: împarte cuvintele până când obține token-uri care pot fi reprezentate de vocabularul său. Acesta este cazul aici cu `transformer`, care este împărțit în două token-uri: `transform` și `##er` - -### De la token-uri la ID-uri de intrare[[de-la-token-uri-la-id-uri-de-intrare]] - -Conversia în ID-uri de intrare este gestionată de metoda de tokenizare `convert_tokens_to_ids()`: - -```py -ids = tokenizer.convert_tokens_to_ids(tokens) - -print(ids) -``` - -```python out -[7993, 170, 11303, 1200, 2443, 1110, 3014] -``` - -Aceste rezultate, odată convertite în tensorul framework-ului corespunzător, pot fi apoi utilizate ca intrări într-un model, așa cum am văzut mai devreme în acest capitol. - - - -✏️ ** Încercați!** Replicați ultimii doi pași (tokenizarea și conversia în ID-uri de intrare) pe propozițiile de intrare pe care le-am folosit în secțiunea 2 ("I've been waiting for a HuggingFace course my whole life." și "I hate this so much!"). Verificați dacă obțineți aceleași ID-uri de intrare pe care le-am obținut mai devreme! - - - -## Decodificare[[decodificare]] - -*Decodificarea* este inversă: din indicii vocabularului, dorim să obținem un șir de caractere. Acest lucru poate fi realizat cu metoda `decode()` după cum urmează: - -```py -decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) -print(decoded_string) -``` - -```python out -'Using a Transformer network is simple' -``` - -Rețineți că metoda `decode` nu numai că convertește indicii înapoi în token-uri, dar și grupează token-urile care fac parte din aceleași cuvinte pentru a produce o propoziție inteligibilă. Acest comportament va fi extrem de util atunci când vom utiliza modele care prezic text nou (fie text generat de un prompt, fie pentru probleme sequence-to-sequence, precum traducerea sau rezumarea). - -Până acum ar trebui să înțelegeți operațiunile atomice pe care le poate gestiona un tokenizator: tokenizarea, conversia în ID-uri și conversia ID-urilor înapoi într-un șir. Cu toate acestea, am atins doar vârful icebergului. În secțiunea următoare, vom aborda limitele metodei noastre și vom vedea cum să le depășim. diff --git a/chapters/ro/chapter2/5.mdx b/chapters/ro/chapter2/5.mdx deleted file mode 100644 index 1d71f6188..000000000 --- a/chapters/ro/chapter2/5.mdx +++ /dev/null @@ -1,338 +0,0 @@ - - -# Gestionarea secvențelor multiple[[gestionarea-secvențelor-multiple]] - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} - -{:else} - -{/if} - -În secțiunea anterioară, am explorat cel mai simplu caz de utilizare: realizarea de inferențe pe o singură secvență de lungime mică. Cu toate acestea, apar deja unele întrebări: - -- Cum gestionăm secvențe multiple? -- Cum gestionăm secvențe multiple *de lungimi diferite*? -- Indicii vocabularului sunt singurele intrări care permit unui model să funcționeze bine? -- Există o secvență prea lungă? - -Să vedem ce tipuri de probleme prezintă aceste întrebări și cum le putem rezolva folosind API-ul 🤗 Transformers. - - -## Modelele așteaptă un lot de intrări[[modelele-așteaptă-un-lot-de-intrări]] - -În exercițiul anterior ați văzut cum secvențele sunt transformate în liste de numere. Să convertim această listă de numere într-un tensor și să o transmitem modelului: - -{#if fw === 'pt'} -```py -import torch -from transformers import AutoTokenizer, AutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) -input_ids = torch.tensor(ids) -# Această linie va eșua. -model(input_ids) -``` - -```python out -IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1) -``` -{:else} -```py -import tensorflow as tf -from transformers import AutoTokenizer, TFAutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) -input_ids = tf.constant(ids) -# This line will fail. -model(input_ids) -``` - -```py out -InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape] -``` -{/if} - -Oh, nu! De ce a eșuat? Am urmat pașii din pipeline-ul din secțiunea 2. - -Problema este că am trimis o singură secvență către model, în timp ce modelele 🤗 Transformers așteaptă în mod implicit mai multe propoziții. Aici am încercat să facem tot ce a făcut tokenizatorul în fundal atunci când l-am aplicat unei `secvențe`. Dar, dacă vă uitați cu atenție, veți vedea că tokenizatorul nu a convertit doar lista de ID-uri de intrare într-un tensor, ci a adăugat și o dimensiune peste aceasta: - -{#if fw === 'pt'} -```py -tokenized_inputs = tokenizer(sequence, return_tensors="pt") -print(tokenized_inputs["input_ids"]) -``` - -```python out -tensor([[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, - 2607, 2026, 2878, 2166, 1012, 102]]) -``` -{:else} -```py -tokenized_inputs = tokenizer(sequence, return_tensors="tf") -print(tokenized_inputs["input_ids"]) -``` - -```py out - -``` -{/if} - -Să încercăm din nou și să adăugăm o nouă dimensiune: - -{#if fw === 'pt'} -```py -import torch -from transformers import AutoTokenizer, AutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) - -input_ids = torch.tensor([ids]) -print("Input IDs:", input_ids) - -output = model(input_ids) -print("Logits:", output.logits) -``` -{:else} -```py -import tensorflow as tf -from transformers import AutoTokenizer, TFAutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) - -input_ids = tf.constant([ids]) -print("Input IDs:", input_ids) - -output = model(input_ids) -print("Logits:", output.logits) -``` -{/if} - -Imprimăm ID-urile de intrare, precum și logit-urile rezultate - iată rezultatul: - -{#if fw === 'pt'} -```python out -Input IDs: [[ 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012]] -Logits: [[-2.7276, 2.8789]] -``` -{:else} -```py out -Input IDs: tf.Tensor( -[[ 1045 1005 2310 2042 3403 2005 1037 17662 12172 2607 2026 2878 - 2166 1012]], shape=(1, 14), dtype=int32) -Logits: tf.Tensor([[-2.7276208 2.8789377]], shape=(1, 2), dtype=float32) -``` -{/if} - -Gruparea în loturi este acțiunea de a trimite mai multe propoziții prin model, toate odată. Dacă aveți o singură propoziție, puteți crea un lot cu o singură secvență: - -``` -batched_ids = [ids, ids] -``` - -Acesta este un lot de două secvențe identice! - - - -✏️ ** Încercați!** Convertiți această listă `batched_ids` într-un tensor și treceți-o prin modelul dumneavoastră. Verificați dacă obțineți aceleași logits ca înainte (dar de două ori)! - - - -Gruparea în loturi permite modelului să funcționeze atunci când îi furnizați mai multe secvențe. Utilizarea mai multor secvențe este la fel de simplă ca și crearea unui lot cu o singură secvență. Există însă o a doua problemă. Atunci când încercați să combinați două (sau mai multe) propoziții, acestea pot avea lungimi diferite. Dacă ați mai lucrat vreodată cu tensori, știți că aceștia trebuie să aibă o formă dreptunghiulară, deci nu veți putea converti direct lista de ID-uri de intrare într-un tensor. Pentru a rezolva această problemă, de obicei *umplem* datele de intrare. - -## Umplerea datelor de intrare[[umplerea-datelor-de-intrare]] - -Următoarea serie de liste nu poate fi convertită într-un tensor: - -```py no-format -batched_ids = [ - [200, 200, 200], - [200, 200] -] -``` - -Pentru a ocoli acest lucru, vom folosi *padding-ul* pentru ca tensorii noștri să aibă o formă dreptunghiulară. Padding-ul (umplerea datelor) asigură că toate propozițiile noastre au aceeași lungime prin adăugarea unui cuvânt special numit *padding token* la propozițiile cu mai puține valori. De exemplu, dacă aveți 10 propoziții cu 10 cuvinte și 1 propoziție cu 20 de cuvinte, padding-ul va asigura că toate propozițiile au 20 de cuvinte. În exemplul nostru, tensorul rezultat arată astfel: - -```py no-format -padding_id = 100 - -batched_ids = [ - [200, 200, 200], - [200, 200, padding_id], -] -``` - -ID-ul token-ului de umplere poate fi găsit în `tokenizer.pad_token_id`. Să-l folosim și să trimitem cele două propoziții prin model în mod separat și grupate împreună: - -{#if fw === 'pt'} -```py no-format -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence1_ids = [[200, 200, 200]] -sequence2_ids = [[200, 200]] -batched_ids = [ - [200, 200, 200], - [200, 200, tokenizer.pad_token_id], -] - -print(model(torch.tensor(sequence1_ids)).logits) -print(model(torch.tensor(sequence2_ids)).logits) -print(model(torch.tensor(batched_ids)).logits) -``` - -```python out -tensor([[ 1.5694, -1.3895]], grad_fn=) -tensor([[ 0.5803, -0.4125]], grad_fn=) -tensor([[ 1.5694, -1.3895], - [ 1.3373, -1.2163]], grad_fn=) -``` -{:else} -```py no-format -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence1_ids = [[200, 200, 200]] -sequence2_ids = [[200, 200]] -batched_ids = [ - [200, 200, 200], - [200, 200, tokenizer.pad_token_id], -] - -print(model(tf.constant(sequence1_ids)).logits) -print(model(tf.constant(sequence2_ids)).logits) -print(model(tf.constant(batched_ids)).logits) -``` - -```py out -tf.Tensor([[ 1.5693678 -1.3894581]], shape=(1, 2), dtype=float32) -tf.Tensor([[ 0.5803005 -0.41252428]], shape=(1, 2), dtype=float32) -tf.Tensor( -[[ 1.5693681 -1.3894582] - [ 1.3373486 -1.2163193]], shape=(2, 2), dtype=float32) -``` -{/if} - -Este ceva în neregulă cu logit-urile din predicțiile noastre grupate: al doilea rând ar trebui să fie identic cu logit-urile pentru a doua propoziție, dar avem valori complet diferite! - -Acest lucru se datorează faptului că principala caracteristică a modelelor Transformer sunt straturile de atenție care *contextualizează* fiecare token. Acestea vor lua în considerare token-urile de umplutură, deoarece se ocupă de toate token-urile unei secvențe. Pentru a obține același rezultat atunci când trecem propoziții individuale de diferite lungimi prin model sau atunci când trecem un lot cu aceleași propoziții și același umplutură aplicată, trebuie să spunem acelor straturi de atenție să ignore token-urile de umplutură. Acest lucru se realizează prin utilizarea unei măști de atenție (attention mask). - -## Attention masks(măști de atenție)[[attention-masks]] - -*Măștile de atenție* sunt tensori cu exact aceeași formă ca tensorul ID-urilor de intrare, completate cu 0 și 1: valorile 1 indică faptul că ar trebui să se acorde atenție token-urilor corespunzătoare, iar valorile 0 indică faptul că nu ar trebui să se acorde atenție token-urilor corespunzătoare (adică acestea ar trebui ignorate de straturile de atenție ale modelului). - -Să completăm exemplul anterior cu o mască de atenție: - -{#if fw === 'pt'} -```py no-format -batched_ids = [ - [200, 200, 200], - [200, 200, tokenizer.pad_token_id], -] - -attention_mask = [ - [1, 1, 1], - [1, 1, 0], -] - -outputs = model(torch.tensor(batched_ids), attention_mask=torch.tensor(attention_mask)) -print(outputs.logits) -``` - -```python out -tensor([[ 1.5694, -1.3895], - [ 0.5803, -0.4125]], grad_fn=) -``` -{:else} -```py no-format -batched_ids = [ - [200, 200, 200], - [200, 200, tokenizer.pad_token_id], -] - -attention_mask = [ - [1, 1, 1], - [1, 1, 0], -] - -outputs = model(tf.constant(batched_ids), attention_mask=tf.constant(attention_mask)) -print(outputs.logits) -``` - -```py out -tf.Tensor( -[[ 1.5693681 -1.3894582 ] - [ 0.5803021 -0.41252586]], shape=(2, 2), dtype=float32) -``` -{/if} - -Acum obținem aceeași logits pentru a doua propoziție din lot. - -Observăm cum ultima valoare a celei de-a doua secvențe este un ID de umplere, care este o valoare 0 în masca de atenție. - - - -✏️ ** Încercați!** Aplicați manual tokenizarea pe cele două propoziții utilizate în secțiunea 2 ("I've been waiting for a HuggingFace course my whole life." și "I hate this so much!"). Treceți-le prin model și verificați dacă obțineți aceeași logiți ca în secțiunea 2. Acum grupați-le împreună folosind token-ul de umplere, apoi creați masca de atenție corespunzătoare. Verificați dacă obțineți aceleași rezultate atunci când parcurgeți modelul! - - - -## Secvențe mai lungi[[secvențe-mai-lungi]] - -Cu modelele Transformer, există o limită a lungimii secvențelor pe care le putem transmite modelelor. Majoritatea modelelor gestionează secvențe de până la 512 sau 1024 de token-uri și se vor bloca atunci când li se cere să proceseze secvențe mai lungi. Există două soluții la această problemă: - -- Utilizați un model cu o lungime de secvență acceptată mai mare. -- Trunchiați secvențele. - -Modelele au diferite lungimi de secvență acceptate, iar unele sunt specializate în tratarea secvențelor foarte lungi. [Longformer](https://huggingface.co/docs/transformers/model_doc/longformer) este un exemplu, iar altul este [LED](https://huggingface.co/docs/transformers/model_doc/led). Dacă lucrați la o sarcină care necesită secvențe foarte lungi, vă recomandăm să aruncați o privire la aceste modele. - -În caz contrar, vă recomandăm să vă trunchiați secvențele prin specificarea parametrului `max_sequence_length`: -```py -sequence = sequence[:max_sequence_length] -``` diff --git a/chapters/ro/chapter2/6.mdx b/chapters/ro/chapter2/6.mdx deleted file mode 100644 index a95a92d56..000000000 --- a/chapters/ro/chapter2/6.mdx +++ /dev/null @@ -1,164 +0,0 @@ - - -# Să punem totul cap la cap[[să-punem-totul-cap-la-cap]] - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -În ultimele secțiuni, ne-am străduit să facem cea mai mare parte a muncii manual. Am explorat modul în care funcționează tokenizerele și am analizat tokenizarea, conversia în ID-uri de intrare, padding, trunchiere și măști de atenție. - -Cu toate acestea, după cum am văzut în secțiunea 2, API-ul 🤗 Transformers poate gestiona toate acestea pentru noi cu o funcție de nivel înalt în care ne vom adânci aici. Atunci când apelați `tokenizer` direct pe propoziție, primiți înapoi intrări care sunt gata să treacă prin modelul dvs: - -```py -from transformers import AutoTokenizer - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." - -model_inputs = tokenizer(sequence) -``` - -Aici, variabila `model_inputs` conține tot ceea ce este necesar pentru ca un model să funcționeze bine. Pentru DistilBERT, aceasta include ID-urile de intrare, precum și masca de atenție. Alte modele care acceptă intrări suplimentare le vor avea, de asemenea, la ieșire prin obiectul `tokenizer`. - -După cum vom vedea în câteva exemple de mai jos, această metodă este foarte eficientă. În primul rând, poate tokeniza o singură secvență: - -```py -sequence = "I've been waiting for a HuggingFace course my whole life." - -model_inputs = tokenizer(sequence) -``` - -De asemenea, gestionează mai multe secvențe simultan, fără nicio modificare a API-ului: - -```py -sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] - -model_inputs = tokenizer(sequences) -``` - -Acesta poate umple în funcție de mai multe obiective: - -```py -# Va umple secvențele până la lungimea maximă a secvenței -model_inputs = tokenizer(sequences, padding="longest") - -# Va umple secvențele până la lungimea maximă a modelului -# (512 pentru BERT sau DistilBERT) -model_inputs = tokenizer(sequences, padding="max_length") - -# Va umple secvențele până la lungimea maximă specificată -model_inputs = tokenizer(sequences, padding="max_length", max_length=8) -``` - -De asemenea, poate trunchia secvențele: - -```py -sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] - -# Va trunchia secvențele care sunt mai lungi decât lungimea maximă a modelului -# (512 pentru BERT sau DistilBERT) -model_inputs = tokenizer(sequences, truncation=True) - -# Va trunchia secvențele care sunt mai lungi decât lungimea maximă specificată -model_inputs = tokenizer(sequences, max_length=8, truncation=True) -``` - -Obiectul `tokenizer` poate gestiona conversia în tensori specifici framework-ului, care pot fi apoi trimiși direct la model. De exemplu, în următorul exemplu de cod, solicităm tokenizatorului să returneze tensori din diferite cadre - `„pt”` returnează tensori PyTorch, `„tf”` returnează tensori TensorFlow, iar `„np”` returnează matrici NumPy: - -```py -sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] - -# Returnează tensori PyTorch -model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") - -# Returnează tensori TensorFlow -model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") - -# Returnează array-uri NumPy -model_inputs = tokenizer(sequences, padding=True, return_tensors="np") -``` - -## Token-uri speciale[[token-uri-speciale]] - -Dacă aruncăm o privire la ID-urile de intrare returnate de tokenizer, vom vedea că sunt puțin diferite de cele pe care le-am avut mai devreme: - -```py -sequence = "I've been waiting for a HuggingFace course my whole life." - -model_inputs = tokenizer(sequence) -print(model_inputs["input_ids"]) - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) -print(ids) -``` - -```python out -[101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102] -[1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012] -``` - -Un token ID a fost adăugat la început, iar unul la sfârșit. Să decodificăm cele două secvențe de ID-uri de mai sus pentru a vedea despre ce este vorba: - -```py -print(tokenizer.decode(model_inputs["input_ids"])) -print(tokenizer.decode(ids)) -``` - -```python out -"[CLS] i've been waiting for a huggingface course my whole life. [SEP]" -"i've been waiting for a huggingface course my whole life." -``` - -Tokenizatorul a adăugat cuvântul special `[CLS]` la început și cuvântul special `[SEP]` la sfârșit. Acest lucru se datorează faptului că modelul a fost preinstruit cu aceste cuvinte, deci pentru a obține aceleași rezultate pentru inferență trebuie să le adăugăm și pe acestea. Rețineți că unele modele nu adaugă cuvinte speciale sau adaugă cuvinte diferite; de asemenea, modelele pot adăuga aceste cuvinte speciale doar la început sau doar la sfârșit. În orice caz, tokenizatorul știe care sunt cele așteptate și se va ocupa de acest lucru pentru dumneavoastră. - -## Încheiere: De la tokenizer la model[[încheiere-de-la-tokenizator--model]] - -Acum că am văzut toți pașii individuali pe care îi utilizează obiectul `tokenizer` atunci când este aplicat pe texte, să vedem o ultimă dată cum poate gestiona secvențe multiple (padding!), secvențe foarte lungi (trunchiere!) și mai multe tipuri de tensori cu API-ul său principal: - -{#if fw === 'pt'} -```py -import torch -from transformers import AutoTokenizer, AutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) -sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] - -tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") -output = model(**tokens) -``` -{:else} -```py -import tensorflow as tf -from transformers import AutoTokenizer, TFAutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) -sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] - -tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="tf") -output = model(**tokens) -``` -{/if} diff --git a/chapters/ro/chapter2/7.mdx b/chapters/ro/chapter2/7.mdx deleted file mode 100644 index 2e6f1175c..000000000 --- a/chapters/ro/chapter2/7.mdx +++ /dev/null @@ -1,19 +0,0 @@ -# Utilizarea de bază este completă![[utilizarea-de-bază-este-completă]] - - - - -Ați făcut o treabă excelentă dacă ați urmat cursul până aici! Pentru a sintetiza, în acest capitol ați: - -- Ați învățat elementele de bază ale unui model Transformer. -- Ați învățat ce formează un pipeline de tokenizare. -- Ați văzut cum să utilizați un model Transformer în practică. -- Ați învățat cum să folosiți un tokenizer pentru a converti textul în tensori care sunt inteligibili pentru model. -- Am configurat împreună un tokenizer și un model pentru a ajunge de la text la predicții. -- Am învățat limitările ID-urilor de intrare și am învățat despre attention masks(măști de atenție). -- V-ați antrenat cu ajutorul metodelor versatile și configurabile ale tokenizatorului. - -De acum înainte, ar trebui să puteți naviga liber prin documentele 🤗 Transformers: vocabularul va suna familiar și ați văzut deja metodele pe care le veți utiliza în majoritatea timpului. \ No newline at end of file diff --git a/chapters/ro/chapter2/8.mdx b/chapters/ro/chapter2/8.mdx deleted file mode 100644 index c41f27936..000000000 --- a/chapters/ro/chapter2/8.mdx +++ /dev/null @@ -1,310 +0,0 @@ - - - - -# End-of-chapter quiz[[end-of-chapter-quiz]] - - - -### 1. What is the order of the language modeling pipeline? - - - -### 2. How many dimensions does the tensor output by the base Transformer model have, and what are they? - -