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/27] 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/27] 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/27] 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/27] 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 abd846ce9a2facc1b409cdbd31b273a2957d11d4 Mon Sep 17 00:00:00 2001 From: "ludmila.friptu" Date: Wed, 8 Jan 2025 13:10:14 +0200 Subject: [PATCH 06/27] 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 1bd8547e14cb9436be75265143e47ba7d2cd3157 Mon Sep 17 00:00:00 2001 From: eduard-balamatiuc Date: Mon, 26 May 2025 18:36:14 +0300 Subject: [PATCH 07/27] 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 08/27] 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 09/27] 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 10/27] 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? - -