diff --git a/README.md b/README.md index 42f187cea..dbb302dcc 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ This repo contains the content that's used to create the **[Hugging Face course] | [Vietnamese](https://huggingface.co/course/vi/chapter1/1) | [`chapters/vi`](https://github.com/huggingface/course/tree/main/chapters/vi) | [@honghanhh](https://github.com/honghanhh) | | [Chinese (simplified)](https://huggingface.co/course/zh-CN/chapter1/1) | [`chapters/zh-CN`](https://github.com/huggingface/course/tree/main/chapters/zh-CN) | [@zhlhyx](https://github.com/zhlhyx), [petrichor1122](https://github.com/petrichor1122), [@1375626371](https://github.com/1375626371) | | [Chinese (traditional)](https://huggingface.co/course/zh-TW/chapter1/1) (WIP) | [`chapters/zh-TW`](https://github.com/huggingface/course/tree/main/chapters/zh-TW) | [@davidpeng86](https://github.com/davidpeng86) | -| [Romanian](https://huggingface.co/course/rum/chapter1/1) (WIP) | [`chapters/rum`](https://github.com/huggingface/course/tree/main/chapters/rum) | [@Sigmoid](https://github.com/SigmoidAI), [@eduard-balamatiuc](https://github.com/eduard-balamatiuc), [@FriptuLudmila](https://github.com/FriptuLudmila), [@tokyo-s](https://github.com/tokyo-s), [@hbkdesign](https://github.com/hbkdesign), [@grumpycatyo-collab](https://github.com/grumpycatyo-collab) | +| [Romanian](https://huggingface.co/course/rum/chapter1/1) (WIP) | [`chapters/rum`](https://github.com/huggingface/course/tree/main/chapters/rum) | [@Sigmoid](https://github.com/SigmoidAI), [@eduard-balamatiuc](https://github.com/eduard-balamatiuc), [@FriptuLudmila](https://github.com/FriptuLudmila), [@tokyo-s](https://github.com/tokyo-s), [@hbkdesign](https://github.com/hbkdesign), [@grumpycatyo-collab](https://github.com/grumpycatyo-collab), [@Angroys](https://github.com/Angroys) | ### Translating the course into your language diff --git a/chapters/rum/_toctree.yml b/chapters/rum/_toctree.yml index cf1353fe3..ef1f0cf56 100644 --- a/chapters/rum/_toctree.yml +++ b/chapters/rum/_toctree.yml @@ -48,3 +48,83 @@ - local: chapter2/9 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 + +- title: 4. Partajarea modelelor și a tokenizatoarelor + sections: + - local: chapter4/1 + title: Platforma Hugging Face Hub + - local: chapter4/2 + title: Utilizarea modelelor preantrenate + - local: chapter4/3 + title: Partajarea modelelor preantrenate + - local: chapter4/4 + title: Crearea unui card de model + - local: chapter4/5 + title: Partea 1 este completă + - local: chapter4/6 + title: Quiz la final de capitol + quiz: 4 + +- title: 5. Biblioteca 🤗 Datasets + sections: + - local: chapter5/1 + title: Introducere + - local: chapter5/2 + title: Ce fac dacă dataset-ul meu nu este pe Hub? + - local: chapter5/3 + title: E timpul să tăiem și să analizăm datele + - local: chapter5/4 + title: Big data? 🤗 Datasets vine în ajutor! + - local: chapter5/5 + title: Creează propriul tău dataset + - local: chapter5/6 + title: Căutare semantică cu FAISS + - local: chapter5/7 + title: 🤗 Datasets, verificare! + - local: chapter5/8 + title: Quiz de final de capitol + quiz: 5 + +- title: 6. Biblioteca 🤗 Tokenizers + sections: + - local: chapter6/1 + title: Introducere + - local: chapter6/2 + title: Antrenarea unui nou tokenizer dintr-unul vechi + - local: chapter6/3 + title: Superputerile tokenizerilor rapizi + - local: chapter6/3b + title: Tokenizerii rapizi în pipeline-ul de QA + - local: chapter6/4 + title: Normalizare și pre-tokenizare + - local: chapter6/5 + title: Tokenizare Byte-Pair Encoding + - local: chapter6/6 + title: Tokenizare WordPiece + - local: chapter6/7 + title: Tokenizare Unigram + - local: chapter6/8 + title: Construirea unui tokenizer, bloc cu bloc + - local: chapter6/9 + title: Tokenizeri, verificare! + - local: chapter6/10 + title: Quiz de sfârșit de capitol + quiz: 6 + 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..e6f391072 --- /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 batch î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 batch î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 batch, deoarece atunci trebuie să umplem doar la lungimea maximă din acel batch, ș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 batch se numește *funcție de colaționare*. 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 dvs. sunt liste, tupluri sau dicționare). În cazul nostru, va fi de asemenea să aplicați umplutură pentru a avea toate intrările de aceeași lungime. Clasa `DataCollatorWithPadding` face exact acest lucru (și un pic mai mult, după cum am văzut anterior). Ia un tokenizer atunci când este instanțiat (pentru a știi ce token de completare să folosească și dacă modelul se așteaptă la padding în stânga sau în dreapta) și va face tot ce aveți nevoie. Umplerea dinamică poate fi aplicată în funcție de necesități la fiecare batch ș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-urile preferă forme fixe, chiar și atunci când ar putea necesita padding suplimentar. + +{:else} + +Funcția care este responsabilă de reunirea eșantioanelor în cadrul unui batch 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). În cazul nostru, va fi de asemenea să aplicați umplutură pentru a avea toate intrările de aceeași lungime. Clasa `DataCollatorWithPadding` face exact acest lucru (și un pic mai mult, după cum am văzut anterior). Ia un tokenizer atunci când este instanțiat (pentru a știi ce token de completare să folosească și dacă modelul se așteaptă la padding în stânga sau în dreapta) și va face tot ce aveți nevoie. Umplerea dinamică poate fi aplicată în funcție de necesități la fiecare batch ș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-urile preferă forme fixe, chiar și atunci când ar putea necesita padding 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 știi 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 batch: + +```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 batch ar trebui să fie umplute la o lungime de 67, lungimea maximă din cadrul batch-ului. 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 batch-ul î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 batch-uri 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 batch-uri ș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 împacheta un `tf.data.Dataset` cu funcția de colaționare încorporată. Dacă utilizați `DataCollatorWithPadding`, acest lucru nu va fi o problemă deoarece ea funcționează cu dicționare, liste și tensori NumPy, precum și cu tensori TensorFlow! + +```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..5da0fc0dd --- /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..73d2fa64b --- /dev/null +++ b/chapters/rum/chapter3/3_tf.mdx @@ -0,0 +1,197 @@ + + +# 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 a realiza fine-tuning-ul 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 `fine-tuning` (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 batch-ului, 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 batch-uri, +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..797e7d9c9 --- /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 batch-uri. Dar, înainte de a putea defini acele dataloaders, 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 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ă 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 dataloader-ul nostru de antrenament și pentru că împachetăm (padding) la lungimea maximă în interiorul batch-ului. + +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 batch-ul 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 batch, 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 batch-uri 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 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') +``` + +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 batch-uri pentru noi în timp ce parcurgem bucla de predicție, cu metoda `add_batch()`. Odată ce am acumulat toate batch-urile, 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 dataloader-urilor 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ă batch-ul 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..3feba0bfa --- /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 eșantioanele într-un batch.", + explain: "Corect! Poți transmite funcția de collate ca argument al unui DataLoader. Am folosit funcția DataCollatorWithPadding, care împachetează toate elementele dintr-un batch 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 batch-urilor 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 diff --git a/chapters/rum/chapter4/1.mdx b/chapters/rum/chapter4/1.mdx new file mode 100644 index 000000000..4ef56abf4 --- /dev/null +++ b/chapters/rum/chapter4/1.mdx @@ -0,0 +1,22 @@ +# Platforma Hugging Face Hub[[the-hugging-face-hub]] + + + +Platforma [Hugging Face Hub](https://huggingface.co/) –- site-ul nostru principal –- reprezintă o platformă care permite oricărui utilizator să descopere, utilizeze și contribuie la noi modele și dataseturi. Acesta găzduiește o varietate vastă de modele, cu peste 10.000 disponibile public. Ne vom concentra discuția asupra modelelor în acest capitol, iar pentru dataseturi vom vorbi în Capitolul 5. + +Modelele din Hub nu sunt limitate la 🤗 Transformers sau chiar și NLP. Există și alte modele, cum ar fi cele de la [Flair](https://github.com/flairNLP/flair) și [AllenNLP](https://github.com/allenai/allennlp) pentru NLP, [Asteroid](https://github.com/asteroid-team/asteroid) și [pyannote](https://github.com/pyannote/pyannote-audio) pentru speech, precum și cele de la [timm](https://github.com/rwightman/pytorch-image-models) pentru vision, doar să menționăm câteva. + +Fiecare dintre aceste modele este găzduit ca un repository Git, ceea ce permite crearea versiunilor și reproducerea acestuia. Oferirea modelului pe Hub înseamnă deschiderea sa comunității, făcându-l accesibil oricărui utilizator care dorește să îl utilizeze cu ușurință, eliminând astfel nevoia de a-și antrena propriul model și simplificând utilizarea acestora. + +În plus, oferirea unui model pe Hub face deploy automat la un Inference API pentru acel model. Orice membru al comunității poate testa direct pe pagina modelului cu inputuri personalizate și widget-uri corespunzătoare. + +Cel mai bun lucru este că oferirea și utilizarea oricărui model public pe Hub este complet gratuit! Existează și [planuri plătite](https://huggingface.co/pricing) dacă doriți să oferiți modele în privat. + +Video-ul de mai jos arată modul în care puteți naviga prin Hub. + + + +Crearea unui cont huggingface.co este necesară pentru a continua această parte, deoarece vom crea și gestiona repositorii pe Platforma Hugging Face Hub: [creați un cont](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/rum/chapter4/2.mdx b/chapters/rum/chapter4/2.mdx new file mode 100644 index 000000000..bbe94669b --- /dev/null +++ b/chapters/rum/chapter4/2.mdx @@ -0,0 +1,98 @@ + + +# Utilizarea modelelor preantrenate[[using-pretrained-models]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Hubul Model oferă o modalitate simplă de a selecta modelul adecvat, astfel încât utilizarea sa în orice bibliotecă downstream poate fi efectuată în câteva linii de cod. Să vedem cum se utilizează efectiv unul dintre aceste modele și cum putem contribui înapoi la comunitate. + + +Să presupunem că noi căutăm un French-based model ce poate face mask filling. + +
+Selecting the Camembert model. +
+ +Alegem checkpointul „camembert-base” pentru al încerca. Identificatorul `camembert-base` este tot de ce avem nevoie pentru a începe. În capitolele precedente, am văzut cum putem inițializa modelul folosind funcția `pipeline()`: + +```py +from transformers import pipeline + +camembert_fill_mask = pipeline("fill-mask", model="camembert-base") +results = camembert_fill_mask("Le camembert est :)") +``` + +```python out +[ + {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, + {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, + {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, + {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, + {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} +] +``` + +În mod evident, încărcarea unui model într-un pipeline este extrem de simplu. Singurul lucru la care trebuie să atrageți atenția este că checkpointul ales este adecvat pentru sarcina pe care urmează să o execute. De exemplu, în momentul în care încărăm checkpoint-ul „camembert-base” în pipelineul `fill-mask`, este perfect în regulă. Dar dacă îl încărcam în pipelineul `text-classification`, rezultatele nu vor avea nici o logică, pentru că headul „camembert-base” nu este adecvat pentru această sarcină! Recomandăm utilizarea task selectorului în interfața Hugging Face în scopul selectării checkpointurilor adecvate: + +
+Selectorul de sarcini pe interfața web. +
+ +Puteți inițializa checkpointul în mod direct folosind arhitectura modelului: + +{#if fw === 'pt'} +```py +from transformers import CamembertTokenizer, CamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = CamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Însă, recomandăm utilizarea [claselor `Auto*`](https://huggingface.co/transformers/model_doc/auto?highlight=auto#auto-classes), deoarece acestea sunt proiectate să fie architecture-agnostic. În timp ce codul precedent limita utilizatorii la checkpoints loadable în CamemBERT architecture, utilizarea claselor `Auto*` face schimbarea checkpointurilor simplă: + +```py +from transformers import AutoTokenizer, AutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = AutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{:else} +```py +from transformers import CamembertTokenizer, TFCamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = TFCamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Însă, recomandăm utilizarea [claselor `TFAuto*`](https://huggingface.co/transformers/model_doc/auto?highlight=auto#auto-classes), deoarece acestea sunt proiectate să fie architecture-agnostic. În timp ce codul precedent limita utilizatorii la checkpoints loadable în CamemBERT architecture, utilizarea claselor `TFAuto*` face schimbarea checkpointurilor simplă: + +```py +from transformers import AutoTokenizer, TFAutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{/if} + + +În momentul în care folosiți un model preantrenat, asigurați-vă să verificați cum a fost antrenat și pe ce date se bazează. De asemenea, trebuie să cunoasceți limitele și prejudecățile sale. Toată această informație va fi indicată pe cartea modelului. + + diff --git a/chapters/rum/chapter4/3.mdx b/chapters/rum/chapter4/3.mdx new file mode 100644 index 000000000..dc0a68d61 --- /dev/null +++ b/chapters/rum/chapter4/3.mdx @@ -0,0 +1,641 @@ + + +# Partajarea modelelor preantrenate[[sharing-pretrained-models]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +În următoarele etape, vom explora modalitățile cele mai ușoare de a partaja modelele preantrenate pe 🤗 Hub. Există tooluri și utilities disponibile care fac posibilă partajarea și actualizarea modelelor direct pe Hub, despre care vom vorbi mai jos. + + + +Recomandăm tuturor utilizatorilor care antrenează modele să contribuie prin partajarea lor cu comunitatea - partajarea modelelor, chiar și atunci când sunt antrenate pe dateseturi foarte specifice, va ajuta la economisind timpul altor utilizatori și resurselor și va oferi acces la artefacte antrenate folositoare. În schimb, puteți beneficia de munca altora! + +Există trei moduri de a crea noi repositorii cu modele: + +- Utilizând API-ul `push_to_hub` +- Utilizând biblioteca Python `huggingface_hub` +- Utilizând interfața web + +Odată ce aveți creat un repository, puteți încărca fișiere în el folosind git și git-lfs. Vom trece împreună prin crearea model repositories și încărcarea fișierelor în el în următoarele secțiuni. + + +## Utilizând API-ul `push_to_hub`[[using-the-pushtohub-api]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Cea mai simplă modalitate de a încărca fișiere pe Hub este utilizând API-ul `push_to_hub`. + +Înainte de a continua, aveți nevoie să creați un token de autentificare astfel încât API-ul `huggingface_hub` să știe cine sunteți și care sunt namespaces la care aveți acces de scriere. Asigurați-vă că sunteți într-un environment unde aveți instalat `transformers` (vedeti [Configurarea](/course/chapter0)). Dacă sunteți într-un notebook, puteți folosi următoarea funcție pentru a vă autentifica: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +În terminal, puteți rula următoarea comandă: + +```bash +huggingface-cli login +``` + +În ambele cazuri, veți fi rugați să introduceți numele utilizatorului și parola, care sunt aceleași cu cele folosite la autentificare pe Hub. Dacă nu aveți încă un profil pe Hub, vă rugăm să creați unul [aici](https://huggingface.co/join). + +Excelent! Acum aveți tokenul de autentificare stocat în folder-ul cache. Vom crea acum niște repositorii! + +{#if fw === 'pt'} + +Dacă ați lucrat cu API-ul `Trainer` pentru a antrena un model, cea mai ușoară modalitate de a o încărca pe Hub este să setați `push_to_hub=True` când definiți `TrainingArguments`: + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments( + "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True +) +``` + +Când chemați `trainer.train()`, `Trainer` va încărca modelul pe Hub de fiecare dată când este salvat (aici, la fiecare epocă) într-un repository namespace-ul tău. Acest repository va fi numit după output directory-ul ales (aici `bert-finetuned-mrpc`), dar puteți alege un alt nume cu `hub_model_id = "un_nume_diferit"`. + +Pentru a încărca modelul într-o organizație din care faceți parte, este suficient să introduceți aceasta în `hub_model_id = "organizația_mea/numele_repositoriului"`. + +Odată ce antrenarea s-a terminat, trebuie să faceți `trainer.push_to_hub()` pentru a încărca ultima versiune a modelului. Acesta va genera apoi un model card cu toate informațiile relevante, inclusiv metadatele despre hyperparametrii utilizați și rezultatele evaluării! Aici este exemplul unui astfel de model card: + +
+ Un exemplu de un card auto-generat. +
+ +{:else} + +Dacă utilizați Keras pentru a antrena modelul dumneavoastră, cea mai ușoară modalitate de a încărca-o pe Hub este să transmiteți un `PushToHubCallback` când chemați `model.fit()`: + +```py +from transformers import PushToHubCallback + +callback = PushToHubCallback( + "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer +) +``` + +Apoi, adăugați `callbacks=[callback]` în apelul la `model.fit()`. Callback-ul va încărca apoi modelul pe Hub de fiecare dată când se salvează (aici, la fiecare epocă) într-un repository din namespace-ul tău. Acest repository va fi numit după output directory ales (aici `bert-finetuned-mrpc`), dar puteți alege un alt nume cu `hub_model_id = "un_nume_diferit"`. + +Pentru a încărca modelul într-o organizație din care faceți parte, este suficient să introduceți aceasta în `hub_model_id = "organizația_mea/numele_repositoriului"`. + +{/if} + +La un nivel inferior, accesarea Hub-ului se poate face direct pe modele, tokenizers și configurations objects prin metoda `push_to_hub()`. Această metodă se ocupă atât de crearea repositoriului cât si de încărcarea modelului și tokenizerului direct în el. Nu este nevoie de o interacțiune manuală, precum fața de API-ul pe care îl vom vedea mai jos. + +Pentru a înțelege cum funcționează, luați în considerare inițializarea unui model și a unui tokenizer: + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{/if} + +Sunteți liberi să faceți orice ați vrea cu acestea - adăugați tokens la tokenizer, antrenați modelul sau faceți fine-tune. Odată ce sunteți mulțumiți de modelul obținut, weighturile și tokenizerul acestuia , puteți folosi metoda `push_to_hub()` disponibilă direct pe obiectul `model`: + +```py +model.push_to_hub("dummy-model") +``` +Acest lucru va crea un nou repository `dummy-model` în profilul tău și o va popula cu fișierele modelului tău. +Încercați același lucru cu tokenizerul, astfel încât toate fișierele să fie acum disponibile în acest repository: + +```py +tokenizer.push_to_hub("dummy-model") +``` + +Dacă aparțineți unei organizații, specificați doar argumentul `organization` pentru a încărca în namespace-ul acestei organizații: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface") +``` + +Dacă doriți să utilizați un anumit token Hugging Face, sunteți liberi să specificați acest lucru metodei `push_to_hub()`: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") +``` + +Acum, mergeți la Model Hub pentru a găsi modelul încărcat: *https://huggingface.co/user-or-organization/dummy-model*. + +Apăsați pe tabul "Files and versions", iar acum ar trebui să vedeți fișierele vizibile în următorul screenshot: + +{#if fw === 'pt'} +
+Dummy model containing both the tokenizer and model files. +
+{:else} +
+Dummy model containing both the tokenizer and model files. +
+{/if} + + + +✏️ **Încercați!** Luați modelul și tokenizerul asociat cu checkpointul `bert-base-cased` și încărcați-l pe un repo în namespace-ul tău folosind metoda `push_to_hub()`. Verificați că repo-ul apare în pagina dumneavoastră înainte de a-l șterge. + + + +Ați văzut că metoda `push_to_hub()` acceptă mai multe argumente, ceea ce permite încărcarea într-un repository specific sau namespace al unei organizații, sau utilizarea unui token API diferit. Vă recomandăm să vă uitați la specificația metodei disponibilă direct în [documentația 🤗 Transformers](https://huggingface.co/transformers/model_sharing) pentru a înțelge ceea ce este posibil. + +Metoda `push_to_hub()` este backed de packageul Python [`huggingface_hub`](https://github.com/huggingface/huggingface_hub), care oferă un API direct la Hub-ul Hugging Face. Acesta este integrat în 🤗 Transformers și în mai multe biblioteci pentru machine learning, ca de exemplu [`allenlp`](https://github.com/allenai/allennlp). Deși ne concentrăm pe integrarea în 🤗 Transformers în acest capitol, integrarea sa în propriul dumneavoastră cod sau bibliotecă este simplu. + +Săriți la ultima secțiune pentru a vedea cum să încărcați fișierele în noul repository-ul creat! + +## Utilizarea bibliotecii Python `huggingface_hub`[[using-the-huggingfacehub-python-library]] + +Biblioteca Python `huggingface_hub` este un package care oferă un set de instrumente pentru model și datasets hubs. Oferă metode simple și clase pentru sarcini comune ca +obținerea informațiilor despre repositorii pe hub și gestionarea lor. Oferă API-uri simple care lucrează pe lângă git pentru a gestiona conținutul acestor repositorii și integrarea Hub-ului +în proiectele și bibliotecile tale. + +Similar cu utilizarea API-ului `push_to_hub`, asta va necesita un token de autentificare salvat în cache. Pentru a face acest lucru, veți avea nevoie să folosiți comanda `login` din CLI, cum am menționat în secțiunea anterioară (din nou, asigurați-vă prefixați această comandă cu simbolul `!` dacă rulați în Google Colab): + +```bash +huggingface-cli login +``` + +Packageul `huggingface_hub` oferă mai multe metode și clase utile pentru noi. În primul rând, există câteva metode pentru crearea, ștergerea și gestionarea repositoriilor: + +```python no-format +from huggingface_hub import ( + # Gestionarea utilizatorilor + login, + logout, + whoami, + + # Crearea și gestionarea repositoriilor + create_repo, + delete_repo, + update_repo_visibility, + + # Și câteva metode pentru a obține/schimba informațiile despre conținut + list_models, + list_datasets, + list_metrics, + list_repo_files, + upload_file, + delete_file, +) +``` + + + + +De asemenea, el oferă clasa foarte puternică `Repository` pentru gestionarea unei repository local. Vom explora aceste metode și clasa în următoarele secțiuni pentru a înțelege cum să le utilizați. + +Metoda `create_repo` poate fi utilizată pentru crearea unui nou repository în hub: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model") +``` + +Acesta va crea repositarul `dummy-model` în namespace-ul tău. Dacă doriți, puteți specifica care organizație să aparțină repositoriului folosind argumentul `organization`: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model", organization="huggingface") +``` + +Acesta va crea repositoriul `dummy-model` în spațiul organizației `huggingface`, cu condiția că dumneavoastră să apartineți acestei organizații. +Alte argumente care pot fi utile sunt: + +- `private`, pentru a specifica dacă repositarul trebuie să fie vizibil celorlați sau nu. +- `token`, dacă doriți să vă înlocuiți token-ul stocat în cache cu un anumit token. +- `repo_type`, dacă doriți să creați un `dataset` sau `space` în loc de model. Valorile acceptate sunt `"dataset"` și `"space"`. + +Odată ce repositoriul este creat, trebuie să adăugați fișierele în el! Săriți la secțiunea următoare pentru a vedea cele trei moduri în care se poate face acest lucru. + +## Utilizarea interfeței web[[using-the-web-interface]] + +Interfața web oferă instrumente pentru a gestiona repositorii direct în Hub. Cu ajutorul acestei interfețe, puteți crea ușor repositorii, adăuga fișiere (și chiar și fișiere mari!), explora modele, vizualiza diffuri, și multe altele. + +Pentru a crea un nou repositoriu, vizitați [huggingface.co/new](https://huggingface.co/new): + +
+Pagină care prezintă modelul utilizat pentru crearea unui nou repository. +
+ +În primul rând, specificați deținătorul repositoriului: acesta puteți fi fie dumneavoastră, fie orice organizație cu care sunteți afiliat. Dacă alegeți o organizație, modelul va fi prezent pe pagina organizației și toți membrii acesteia vor avea posibilitatea să contribuie la repositoriu. + +În al doilea rând, introduceți numele modelului dumneavoastră. Acest lucru va fi și denumirea repositoriului. În final, puteți specifica dacă doriți ca modelul dumneavoastră să fie public sau privat. Modelele private nu sunt vizibile ceilorlalți. + +După crearea repositoriului, ar trebui să vedeți o pagină ca aceasta: + +
+O pagină goală a modelului după crearea unui nou repository. +
+ +Acesta este locul unde va fi găzduit modelul. Pentru a începe să populați acesta, puteți adăuga un fișier README direct din interfața web. + +
+Fișierul README care arată capacitățile Markdown. +
+ +Fișierul README este scris în format Markdown - vă rugăm să fiți creativi cu el! A treia parte a acestui capitol se ocupă de crearea unui model card. Acestea sunt foarte importante pentru a aduce valoare modelului dumneavoastră, deoarece este acolo unde spuneți celorlalți ce poate face. + +Dacă vă uitați la secțiunea "Fișiere și versiuni", veți vedea că nu sunt prea multe fișiere acolo încă - doar *README.md* pe care l-ați creat dumneavoastră și *.gitattributes* care urmărește fișierele mari. + +
+Secțiunea 'Fișiere și versiuni' arată doar fișierul .gitattributes și README.md. +
+ +Vom vedea mai târziu cum să adăugați câteva fișiere noi. + +## Încărcarea fișierelor modelului[[uploading-the-model-files]] + +Sistemul pentru a gestiona fișierele în Hugging Face Hub se bazează pe git pentru fișierele obișnuite, și pe git-lfs (care se descrifrează ca [Git Large File Storage](https://git-lfs.github.com/)) pentru fișierele mai mari. + +În secțiunea următoare, vom discuta despre trei metode diferite de încărcare a fișierelor în Hub: prin `huggingface_hub` și prin comanda git. + +### Metoda `upload_file`[[the-uploadfile-approach]] + +Prin `upload_file` nu este necesar să instalați git și git-lfs pe sistemul dumneavoastră. Acest lucru încarcă direct fișierele în 🤗 Hub folosind HTTP POST requests. O limitare a acestei metode este că ea nu se ocupă de fișiere care sunt mai mari de 5GB. +Dacă fișierele dumneavoastră sunt mai mari decât 5 GB, vă rugăm să urmați celelalte două metode descrise mai jos. + +API-ul poate fi folosit astfel: + +```py +from huggingface_hub import upload_file + +upload_file( + "/config.json", + path_in_repo="config.json", + repo_id="/dummy-model", +) +``` + +Acest lucru va încărca fișierul `config.json` disponibil la `` la rootul repositoriului ca `config.json`, în `dummy-model` repository. +Alte argumente utile pot fi: + +- `token`, dacă doriți să înlocuiți tokenul stocat în cache cu un alt token. +- `repo_type`, dacă doriți să încărcați într-un `dataset` sau într-un `space` în locul modelului. Valorile acceptate sunt `"dataset"` și `"space"`. + +### Clasa `Repository`[[the-repository-class]] + +Clasa `Repository` gestionează un repository local în-tro manieră asemănătoare gitului. Ea abstracționează majoritatea problemelor pe care le întâlniți cu git pentru a oferi toate caracteristicile necesare. + +Utilizarea acestei clase necesită ca dumneavoastră să aveți instalat git și git-lfs, așadar asigurați-vă că aveți instalat git-lfs (vezi [aici](https://git-lfs.github.com/) pentru instrucțiunile de instalare) și setat înainte de a începe. + +Pentru a vă juca cu repositoriul pe care l-am creat, puteți porni prin inițializarea lui ca un folder local printr-un clonarea repositoriului remote: + +```py +from huggingface_hub import Repository + +repo = Repository("", clone_from="/dummy-model") +``` + +Acesta a creat folderele `` în folderul de lucru. Acest folder conține doar fișierul `.gitattributes` pentru că acesta este singurul fișier creat atunci când inițializați repositoriul prin `create_repo`. + +Acum puteți folosi majoritatea metodelor tradiționale ale gitului: + +```py +repo.git_pull() +repo.git_add() +repo.git_commit() +repo.git_push() +repo.git_tag() +``` + +Și altele! Vă recomandăm să vă uitați la documentația `Repository` disponibilă [aici](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) pentru o înțelegere generală a tuturor metodelor disponibile. + +Pentru moment, avem un model și un tokenizer pe care doriți să îl trimiteți în hub. Am reușit să clonăm repositoriul local, deci putem salva fișierele în acesta. + +În primul rând asigurați-vă că copia locală este la zi cu ultimele schimbări: + +```py +repo.git_pull() +``` + +Odată ce am facut asta, salvați fișierele modelului și al tokenizerul: + +```py +model.save_pretrained("") +tokenizer.save_pretrained("") +``` + +Folder-ul `` acum conține toate fișierele modelului dumneavoastră și ale tokenizerul. Vom urma fluxul tradițional de git adăugând fișiere în zona de pregătire, făcând commit și push fișierelor în hub: + +```py +repo.git_add() +repo.git_commit("Adăugarea fișierelor modelului și ale tokenizerului") +repo.git_push() +``` + +Felicitări! Ați reușit să încărcați primele fișiere în hub. + +### Folosirea Git[[the-git-based-approach]] + +Acesta este abordarea cea mai de bază pentru încărcarea fișierelor: vom face asta cu ajutorul git și git-lfs direct. Cea mai grea parte este abstractizată prin abordări anterioare, dar există câteva excepții cu metoda următoare, așadar ne vom referi la un use-case mai complicat. + +Pentru această clasă se cere să aveți instalate git și git-lfs, așadar asigurați-vă că aveți [git-lfs](https://git-lfs.github.com/) instalat (vedeți aici instrucțiunile de instalare) și configurat înainte să începeți. + +În primul rând, inițializați git-lfs: + +```bash +git lfs install +``` + +```bash +Updated git hooks. +Git LFS initialized. +``` + +După ce ați făcut acest lucru, primul pas este să faceți o copie a repositoriului dumneavoastră: + +```bash +git clone https://huggingface.co// +``` + +Numele meu de utilizator este `lysandre` și am folosit numele modelului `dummy`, așadar comanda mea se termină cu următorul rezultat: + +``` +git clone https://huggingface.co/lysandre/dummy +``` + +Acum am un folder denumit *dummy* în folderul meu de lucru. Pot intra în folder cu ajutorul `cd` și pot examina conținutul: + +```bash +cd dummy && ls +``` + +```bash +README.md +``` + +Dacă ați creat repositoriul dumneavoastră folosind metodă din partea Hugging Face Hub, `create_repo` , acest folder ar trebui să conțină doar un fișier cu numele *.gitattributes*. Dacă ați urmat instrucțiunile din secțiunea anterioară pentru crearea repositoriului folosind interfața web, atunci folderul ar trebui să conțină un singur fișier *README.md* și, de asemenea, un fișier cu numele *.gitattributes*, pe care nu-l puteți vedea. + +Adăugarea unui fișier de mărime obișnuită, precum un fișier de configurare, un vocabulary file sau orice alt tip de fișier care au mai puțin de câțiva megabytes se realizează exact așa cum ați face în orice sistem git. Cu toate acestea, pentru a încărca fișiere mai mari, este necesară utilizarea a git-lfs pentru a le trimite spre *huggingface.co*. + +Să ne întoarcem la Python pentru a genera un model și tokenizer pe care am dori să le facem commit în repositoriul nostru dummy: + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Faceți orice cu modelul, antrenați-l, aplicați fine-tuning... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Faceți orice cu modelul, antrenați-l, aplicați fine-tuning... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{/if} + +Acum am salvat câteva artefacte de model și tokenizer, așadar să examinăm din nou folderul *dummy*: + +```bash +ls +``` + +{#if fw === 'pt'} +```bash +config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json +``` +Dacă priviți la dimensiunile fișierelor (de exemplu, folosind `ls -lh`), ar trebui să vedeți că singurul outlier este model state dict file (*pytorch_model.bin*), care are mai mult de 400 MB. + +{:else} +```bash +config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json +``` +Dacă priviți la dimensiunile fișierelor (de exemplu, folosind `ls -lh`), ar trebui să vedeți că singurul outlier este model state dict file (*tf_model.h5*), care are mai mult de 400 MB. + +{/if} + + +✏️ Dacă creați repositoriul folosind interfața web, fișierul *.gitattributes* va fi automat configurat pentru a considera anumite extensii de fișiere, precum *.bin* și *.h5*, ca fiind fișiere mari. În acest caz nu este necesară nici o setare suplimentară din partea ta, pentru că git-lfs le va urmări automat. + + +Acum putem continua procesul în felul nostru obișnuit cu repositoriurile Git tradiționale. Putem adăuga toate fișierele în mediul de stocare a Git folosind comanda `git add`: + +```bash +git add . +``` + +Puteți verifica fișierele curente care sunt în stare de stocare utilizând următoarea comandă: + +```bash +git status +``` + + +{#if fw === 'pt'} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: pytorch_model.bin + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tokenizer.json + new file: tokenizer_config.json +``` +{:else} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tf_model.h5 + new file: tokenizer.json + new file: tokenizer_config.json +``` +{/if} + +De asemenea, putem verifica dacă git-lfs monitorizează fisierele corecte folosind comanda sa `status`: + +```bash +git lfs status +``` + + +{#if fw === 'pt'} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + pytorch_model.bin (LFS: 35686c2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Vom observa că toate fișierele au `Git` ca handler, cu excepția *pytorch_model.bin* și *sentencepiece.bpe.model*, care au `LFS`. Excelent! + +{:else} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tf_model.h5 (LFS: 86fce29) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Observăm că toate fișierele au `Git` ca handler, cu excepția *tf_model.h5*, care are `LFS`. Excelent! + +{/if} + +Acum vom continua cu ultimii pași: commiting și pushing la repo-urile *huggingface.co*: + +``` +git commit -m "Prima versiune a modelului" +``` + + +{#if fw === 'pt'} +```bash +[main b08aab1] Prima versiune a modelului + 7 files changed, 29027 insertions(+) + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 pytorch_model.bin + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{:else} +```bash +[main b08aab1] Prima versiune a modelului + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tf_model.h5 + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{/if} + +Pushingul poate lua câteva minute, în funcție de viteza conexiunii tale la internet și mărimea fișierelor: + +```bash +git push +``` + +```bash +Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done. +Enumerating objects: 11, done. +Counting objects: 100% (11/11), done. +Delta compression using up to 12 threads +Compressing objects: 100% (9/9), done. +Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done. +Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 +To https://huggingface.co/lysandre/dummy + 891b41d..b08aab1 main -> main +``` + +{#if fw === 'pt'} +Dacă ne uităm la repositoriul modelului când acest lucru este finalizat, putem vedea toate fișierele recent adăugate: + +
+Tabul 'Files and versions' conține acum toate fișierele recente adăugate. +
+ +Interfața permite explorarea fișierelor modelului și a commiturilor și vizualizarea diferenței introduse de fiecare commit: + +
+Diferența introdusă de ultimul commit. +
+{:else} +Dacă ne uităm la repositoriul modelului când acest lucru este finalizat, putem vedea toate fișierele recent adăugate: + +
+Tabul 'Files and versions' conține acum toate fișierele recente adăugate. +
+ +Interfața permite explorarea fișierelor modelului și a commiturilor și vizualizarea diferenței introduse de fiecare commit: + +
+Diferența introdusă de ultimul commit. +
+{/if} diff --git a/chapters/rum/chapter4/4.mdx b/chapters/rum/chapter4/4.mdx new file mode 100644 index 000000000..5615a1651 --- /dev/null +++ b/chapters/rum/chapter4/4.mdx @@ -0,0 +1,88 @@ +# Crearea unui card de model[[building-a-model-card]] + + + + +Model card-ul este un fișier care este la fel de important ca și fișierele de model si tokenizer dintr-un repositoriu. Este definiția centrală a modelului, asigurând reutilizarea de către membrii comunității și reproducerea rezultatelor, oferind o platformă pe care alți membri pot construi artefactele lor. + +Documentarea procesului de antrenare și evaluare ajută pe ceilalți să înțeleagă la ce să aștepte de la un model - iar oferirea informațiilor suficiente referitoare la datele folosite si preprocesarea acestora (înainte și după antrenare) vor ajuta la identificarea și înțelegerea limitelor, biasurile și contextului în care modelul este și nu este folositor. + +Prin urmare, crearea unui model card care definește modelul dvs. este un pas foarte important. Aici, oferim câteva sfaturi care v-ar putea ajuta în acest sens. Crearea model cardului se face prin intermediul fișierului `README.md`, pe care l-ați văzut anterior, care este un fișier Markdown. + +Conceptul de "model card" provine dintr-o direcție de cercetare a Google, publicată inițial în articolul ["Model Cards for Model Reporting"](https://arxiv.org/abs/1810.03993) scrisă de Margaret Mitchell et al. O parte semnificativă dintre informațiile conținute aici sunt bazate pe acest articol, iar noi vă recomandăm să verificați dacă doriți să înțelegeți de ce model cardurile sunt atât importante într-o lume care valorifică reproducerea, reutilizarea și echitatea. + +Model cardul începe cu o prezentare scurtă și generală a ceea ce este modelul - urmată de detalii suplimentare în secțiunile următoare: + +- Descrierea modelului +- Utilizări intenționate și limitările modelului +- Cum se utilizează +- Limitări și bias +- Datele de antrenare +- Procedura de antrenare +- Rezultatele evaluării + +Hai să vedem ce ar trebui să conțină fiecare dintre aceste secțiuni. + +### Descrierea modelului[[model-description]] + +Secțiunea de descriere a modelului conține detalii generale despre model. Aceasta include arhitectura, versiunea, dacă a fost introdus într-un articol științific, dacă este disponibilă o implementare originală, autorul și informații generale despre model. Orice drepturi de autor ar trebui să fie atribuite în această secțiune. Informația generală despre procedurile de antrenare, parametrii și avertismente pot fi menționate aici. + +### Utilizări intenționate și limitările modelului[[intended-uses-limitations]] + +Aici descrieți utilizările pentru care modelul este destinat - inclusiv limba și domeniile unde se poate aplica. Această secțiune din model card poate documenta și zone cunoscute ca nefiind în scopul modelului sau unde este probabil să funcționeze suboptimal. + +### Cum se utilizează[[how-to-use]] + +Acesta secțiune ar trebui să includă exemple despre cum să utilizați modelul. Acest lucru poate prezenta utilizarea funcției `pipeline()`, utilizarea claselor de model si tokenizer, și orice cod care credeți că ar putea fi util. + +### Datele de antrenare[[training-data]] + +Această secțiune trebuie să indice datasetul(urile) folosit pentru instruirea modelului. O descriere scurtă a datasetului(urilor) este binevenită. + +### Procedura de antrenare[[training-procedure]] + +Aici descrieți toate aspectele relevante ale antrenarii care sunt utile din punct de vedere al reproducerii. Acest lucru include orice preprocessing sau postprocessing efectuat pe date, precum și detalii ca numărul de epoci pe care a fost antrenat modelul, dimensiunea batch-ului, learning rateul și alte astfel de detalii. + +### Variabilele si metrice[[variable-and-metrics]] + +Aici descrieți metricile utilizate la evaluarea rezultatelor, precum și diferite aspecte pe care le măsurați. Menționați metricile folosite, pe ce dataset și care dataset split, astfel încât să fie ușor de comparat performanțele modelului cu cele ale altor modele. + +### Rezultatele evaluării[[evaluation-results]] + +În sfârșit, indicați cum s-a comportat modelul pe datasetul de evaluare. Dacă modelul foloseste un decision threshold, atunci includeți acest decision threshold în secțiunea respectivă - sau explicați că rezultatele sunt prezentate la diferite threshold-uri pentru utilizările preconizate. + +## Exemplu[[example]] + +Aici puteți găsi câteva exemple ale unor Model Card: + +- [`bert-base-cased`](https://huggingface.co/bert-base-cased) +- [`gpt2`](https://huggingface.co/gpt2) +- [`distilbert`](https://huggingface.co/distilbert-base-uncased) + +Câteva exemple din alte organizații și companii pot fi găsite [aici](https://github.com/huggingface/model_card/blob/master/examples.md). + +## Notă[[note]] + +Model cardurile nu sunt o cerință obligatorie când se publică modelele și nu este necesar să includeți toate secțiunile descrise mai sus în Model Card. Cu toate acestea, documentarea explicită a modelului poate beneficia utilizatorilor viitori, deci vă recomandăm să completați cât mai multe dintre secțiuni. + +## Metadatele cardului de model[[model-card-metadata]] + +Dacă ați explorat puțin Hugging Face Hub atunci ar trebui să fi văzut că anumite modele aparțin anumitor categorii: puteți filtra acestea după diverse domenii. Categoriile modelului sunt identificate prin metadata adăugată în secțiunea de header a cardului. + +Spre exemplu, dacă vă uitați la modelul [`camembert-base`](https://huggingface.co/camembert-base/blob/main/README.md) atunci veți observa următoarea linii din headerul cardului: + +``` +--- +language: fr +license: mit +datasets: +- oscar +--- +``` + +Metadatele acestei secțiuni sunt analizate de Hugging Face Hub, care apoi identifică acest model ca fiind un model francez, cu licență MIT și antrenat pe datasetul Oscar. + +[Toate specificările cardului](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) permite specificarea a mai multor metadate, inclusiv limba, licența, etichetele, dataseturile folosite la antrenare și metricile evaluate. \ No newline at end of file diff --git a/chapters/rum/chapter4/5.mdx b/chapters/rum/chapter4/5.mdx new file mode 100644 index 000000000..deb1921ae --- /dev/null +++ b/chapters/rum/chapter4/5.mdx @@ -0,0 +1,12 @@ +# Partea 1 este completă! + + + +Acesta este sfârșitul primei părți a cursului! Partea 2 va fi lansată pe data de 15 noiembrie, cu un mare eveniment comunitar, mai multe informații găsiți [aici](https://huggingface.co/blog/course-launch-event). + +Acum, sunteți capabili să faceți fine-tune unui model preantrenat pe o problemă de clasificare a textului (o singură sau perechi de propoziții) și să încarci rezultatele la Model Hub. Să fii sigur că ai învățat această primă secțiune, ar trebui să faci exact acest lucru pe o problemă care ți se pare interesantă (și nu neapărat în limba engleză dacă vorbiți altă limbă)! Puteți găsi ajutor în [forumurile Hugging Face](https://discuss.huggingface.co/) și să împărtășiți proiectul dvs. pe [acest subiect](https://discuss.huggingface.co/t/share-your-projects/6803) odată ce ați terminat. + +Ne așteptăm cu nerăbdare să vedem ceea ce veți construi cu aceste cunoștințe :)! \ No newline at end of file diff --git a/chapters/rum/chapter4/6.mdx b/chapters/rum/chapter4/6.mdx new file mode 100644 index 000000000..e5d79271c --- /dev/null +++ b/chapters/rum/chapter4/6.mdx @@ -0,0 +1,233 @@ + + + + +# Quiz la final de capitol[[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. Ce 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 diff --git a/chapters/rum/chapter5/1.mdx b/chapters/rum/chapter5/1.mdx new file mode 100644 index 000000000..99ac985b9 --- /dev/null +++ b/chapters/rum/chapter5/1.mdx @@ -0,0 +1,22 @@ +# Introducere[[introduction]] + + + +În [Capitolul 3](/course/chapter3) ați încercat 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 aceasta este doar o mică parte a ceea ce poate face 🤗 Datasets! În acest capitol, ne vom aprofunda î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! \ No newline at end of file diff --git a/chapters/rum/chapter5/2.mdx b/chapters/rum/chapter5/2.mdx new file mode 100644 index 000000000..9ed66fd46 --- /dev/null +++ b/chapters/rum/chapter5/2.mdx @@ -0,0 +1,169 @@ +# Ce fac dacă dataset-ul meu nu este pe Hub?[[what-if-my-dataset-isnt-on-the-hub]] + + + +Știți cum să folosiți [Hugging Face Hub](https://huggingface.co/datasets) pentru a descărca dataseturi, dar vă veți găsi adesea lucrând cu date care sunt stocate fie pe laptopul dumneavoastră, fie pe un server remote. Î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ă scripturi de încărcare pentru a gestiona încărcarea dataseturilor locale și remote. Suportă mai multe formate de date comune, 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")` | + +Așa cum se arată în tabel, pentru fiecare format de date trebuie doar să specificăm tipul scriptului de încărcare în funcția `load_dataset()`, alături de un argument `data_files` care specifică calea către unul sau mai multe fișiere. Să începem prin încărcarea unui dataset din fișiere locale; mai târziu vom vedea cum să facem același lucru cu fișiere remote. + +## Încărcarea unui dataset local[[loading-a-local-dataset]] + +Pentru acest exemplu vom folosi [datasetul SQuAD-it](https://github.com/crux82/squad-it/), care este un dataset la scară largă pentru întrebări și răspunsuri în italiană. + +Seturile de antrenare și test sunt găzduite pe GitHub, deci le putem descărca cu o comandă simplă `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 +``` + +Aceasta va descărca două fișiere comprimate numite *SQuAD_it-train.json.gz* și *SQuAD_it-test.json.gz*, pe care le putem decomprima cu comanda Linux `gzip`: + +```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 vedea că fișierele comprimate au fost înlocuite cu _SQuAD_it-train.json_ și _SQuAD_it-test.json_, și că datele sunt stocate în formatul JSON. + + + +✎ Dacă vă întrebați de ce există un caracter `!` în comenzile shell de mai sus, aceasta este pentru că le rulăm într-un notebook Jupyter. Pur și simplu eliminați prefixul dacă doriți să descărcați și să dezarhivați datasetul într-un terminal. + + + +Pentru a încărca un fișier JSON cu funcția `load_dataset()`, trebuie doar să știm dacă avem de-a face cu JSON obișnuit (similar cu un dicționar imbricat) sau JSON Lines (JSON separat pe linii). Ca multe dataseturi de întrebări și răspunsuri, SQuAD-it folosește formatul imbricat, cu tot textul stocat într-un câmp `data`. Aceasta înseamnă că putem încărca datasetul specificând argumentul `field` după cum urmează: + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +În mod implicit, încărcarea fișierelor locale creează un obiect `DatasetDict` cu un split `train`. Putem vedea acest lucru prin inspectarea obiectului `squad_it_dataset`: + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +Aceasta ne arată numărul de rânduri și numele coloanelor asociate cu setul de antrenare. Putem vizualiza unul dintre exemple prin indexarea în splitul `train` după cum urmează: + +```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 încărcat primul nostru dataset local! Dar în timp ce acest lucru a funcționat pentru setul de antrenare, ceea ce dorim cu adevărat este să includem atât spliturile `train` cât și `test` într-un singur obiect `DatasetDict` astfel încât să putem aplica funcțiile `Dataset.map()` pe ambele splituri deodată. Pentru a face acest lucru, putem furniza un dicționar argumentului `data_files` care mapează fiecare nume de split la un fișier asociat cu acel 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") +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) + test: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 48 + }) +}) +``` + +Acesta este exact ceea ce am dorit. Acum, putem aplica diverse tehnici de preprocesare pentru a curăța datele, tokeniza recenziile și așa mai departe. + + + +Argumentul `data_files` al funcției `load_dataset()` este destul de flexibil și poate fi fie o singură cale de fișier, o listă de căi de fișiere, sau un dicționar care mapează numele spliturilor la căile fișierelor. De asemenea, puteți folosi glob pentru fișiere care se potrivesc unui model specificat conform regulilor folosite de shell-ul Unix (de exemplu, puteți face glob pentru toate fișierele JSON dintr-un director ca un singur split prin setarea `data_files="*.json"`). Consultați [documentația](https://huggingface.co/docs/datasets/loading#local-and-remote-files) 🤗 Datasets pentru mai multe detalii. + + + +Scripturile de încărcare din 🤗 Datasets suportă de fapt decomprimarea automată a fișierelor de intrare, deci am fi putut să sărim peste folosirea `gzip` prin indicarea argumentului `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 doriți să decomprimați manual multe fișiere GZIP. Decomprimarea automată se aplică și altor formate comune precum ZIP și TAR, deci trebuie doar să pointați `data_files` către fișierele comprimate și sunteți gata! + +Acum că știți cum să încărcați fișiere locale pe laptopul sau desktop-ul dumneavoastră, să aruncăm o privire la încărcarea fișierelor remote. + +## Încărcarea unui dataset remote[[loading-a-remote-dataset]] + +Dacă lucrați ca data scientist sau programator într-o companie, există o șansă bună ca dataseturile pe care doriți să le analizați să fie stocate pe un server remote. Din fericire, încărcarea fișierelor remote este la fel de simplă ca încărcarea celor locale! În loc să furnizați o cale către fișiere locale, pointați argumentul `data_files` al `load_dataset()` către unul sau mai multe URL-uri unde sunt stocate fișierele remote. De exemplu, pentru datasetul SQuAD-it găzduit pe GitHub, putem pur și simplu să pointăm `data_files` către URL-urile _SQuAD_it-*.json.gz_ după cum urmează: + +```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") +``` + +Aceasta returnează același obiect `DatasetDict` obținut mai sus, dar ne economisește pasul de a descărca și decomprima manual fișierele _SQuAD_it-*.json.gz_. Aceasta încheie incursiunea noastră în diversele modalități de încărcare a dataseturilor care nu sunt găzduite pe Hugging Face Hub. Acum că avem un dataset cu care să ne jucăm, să explorăm diverse tehnici de manipulare a datelor! + + + +✏️ **Încercați!** Alegeți un 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 tehnicile 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 mai multe informații despre aceste formate). + + + + + + diff --git a/chapters/rum/chapter5/3.mdx b/chapters/rum/chapter5/3.mdx new file mode 100644 index 000000000..b79c2453a --- /dev/null +++ b/chapters/rum/chapter5/3.mdx @@ -0,0 +1,741 @@ +# E timpul să tăiem și să analizăm datele[[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ă: + +``` +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[[creating-new-columns]] + +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ține viteza "rapidă" a tokenizerilor pe care îi vom întâlni în [Capitolul 6](/course/chapter6), care pot repede să tokenizeze listelor mari de texte. De exemplu, pentru tokenizarea 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 sunt susț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ția `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 +``` + + + + +✏️ **Î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 fancy 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/rum/chapter5/4.mdx b/chapters/rum/chapter5/4.mdx new file mode 100644 index 000000000..9375a8af4 --- /dev/null +++ b/chapters/rum/chapter5/4.mdx @@ -0,0 +1,287 @@ +# Big data? 🤗 Datasets vine î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ă coloane în datasetul nostru – e foarte mult! + + + +✎ De abia acum, 🤗 Datasets va descompresa fișierele necesare pentru încărcarea datasetului. Dacă doriți să salvați spațiu pe hard drive-ul dvs. , 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/rum/chapter5/5.mdx b/chapters/rum/chapter5/5.mdx new file mode 100644 index 000000000..ce2dbbbc7 --- /dev/null +++ b/chapters/rum/chapter5/5.mdx @@ -0,0 +1,404 @@ +# Crearea propriului tău 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 repositoriile 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]] + + + +Acum că avem datasetul nostru augmentat, este timpul să îi facem push pe Hub pentru a-l oferi comunității! Încărcarea unui dataset este foarte simplu: la fel ca modelele și tokenizerrii din 🤗 Transformers, putem utiliza o metodă `push_to_hub()` pentru a face push unui dataset. Pentru a face asta, avem nevoie de un token de autentificare, care poate fi obținut prin autentificarea pe Hugging Face Hub cu funcția `notebook_login()`: + +```py +from huggingface_hub import notebook_login + +notebook_login() +``` + +Acest lucru va crea un widget unde poți să scrii usernameul și parola ta, iar un API token va fi salvat în *~/.huggingface/token*. Dacă rulezi codeul într-un terminal, te poți loga cu ajutor CLI: +This will create a widget where you can enter your username and password, and an API token will be saved in *~/.huggingface/token*. If you're running the code in a terminal, you can log in via the CLI instead: + +```bash +huggingface-cli login +``` +O dată ce ai făcut asta, putem încărca datasetul rulând: + +```py +issues_with_comments_dataset.push_to_hub("github-issues") +``` + +De acum, orice poate să descarce datasetul, utilizând `load_dataset()` cu ID-ul repositoriului ca `path` argument: + +```py +remote_dataset = load_dataset("lewtun/github-issues", split="train") +remote_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 +}) +``` + +Cool, am încărcat datasetul nostru pe Hub și acum este disponibil pentru alții să îl utilizeze! Mai este doar un lucru important de făcut: adăugarea unui _dataset card_ care explică cum a fost creat corpusul și oferă alte informații utile pentru comunitate. + + + +💡 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/rum/chapter5/6.mdx b/chapters/rum/chapter5/6.mdx new file mode 100644 index 000000000..2fce7ed1e --- /dev/null +++ b/chapters/rum/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 and 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` mai î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/rum/chapter5/7.mdx b/chapters/rum/chapter5/7.mdx new file mode 100644 index 000000000..39922875f --- /dev/null +++ b/chapters/rum/chapter5/7.mdx @@ -0,0 +1,16 @@ +# 🤗 Datasets, verificare![[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/rum/chapter5/8.mdx b/chapters/rum/chapter5/8.mdx new file mode 100644 index 000000000..fc619da62 --- /dev/null +++ b/chapters/rum/chapter5/8.mdx @@ -0,0 +1,228 @@ +# Quiz de final 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 diff --git a/chapters/rum/chapter6/1.mdx b/chapters/rum/chapter6/1.mdx new file mode 100644 index 000000000..4140b8b84 --- /dev/null +++ b/chapters/rum/chapter6/1.mdx @@ -0,0 +1,19 @@ +# Introducere[[introducere]] + + + +În [Capitolul 3](/course/chapter3), am examinat cum să facem fine-tune unui model pentru o anumită sarcină. Când facem acest lucru, utilizăm același tokenizer cu care modelul a fost antrenat - dar ce facem când dorim să antrenăm un model de la zero? În așa cazuri, utilizarea unui tokenizer care a fost antrenat pe un corpus dintr-un alt domeniu sau limbă este, de obicei, suboptimal. De exemplu, un tokenizer antrenat pe un corpus în limba engleză va funcționa rău pe un corpus de texte în limba japoneză, deoarece utilizarea spațiilor și a punctuației este foarte diferită în cele două limbi. + +În acest capitol, veți învăța cum să antrenați un tokenizer complet nou pe un corpus de texte, astfel încât să poată fi utilizat pentru a antrena un model de limbaj. Acest lucru va fi realizat cu ajutorul bibliotecii [🤗 Tokenizers](https://github.com/huggingface/tokenizers), care oferă tokenizerii "rapizi" din biblioteca [🤗 Transformers](https://github.com/huggingface/transformers). Vom examina îndeaproape caracteristicile pe care această bibliotecă le oferă și vom explora cum tokenizatorii rapizi diferă de versiunile "lente". + +Subiectele pe care le vom acoperi includ: + +* Cum să antrenați un tokenizer nou, similar celui utilizat de un anumit checkpoint pe un corpus nou de texte +* Caracteristicile speciale ale tokenizerilor rapizi +* Diferențele dintre cei trei algoritmi principali de subword tokenization utilizate în NLP în prezent +* Cum să construiți un tokenizer de la zero cu biblioteca 🤗 Tokenizers și să îl antrenați pe anumite date + +Tehnicile prezentate în acest capitol vă vor pregăti pentru secțiunea din [Capitolul 7](/course/chapter7/6), unde vom examina crearea unui model de limbaj pentru codul sursă Python. Să începem prin a explora ce înseamnă să "antrenați" un tokenizer în primul rând. diff --git a/chapters/rum/chapter6/10.mdx b/chapters/rum/chapter6/10.mdx new file mode 100644 index 000000000..37ed8da60 --- /dev/null +++ b/chapters/rum/chapter6/10.mdx @@ -0,0 +1,283 @@ + + +# Quiz de sfârșit de capitol[[end-of-chapter-quiz]] + + + +Hai să testăm ceea ce ai învățat în acest capitol! + +### 1. Când ar trebui să antrenezi un nou tokenizer? + + + +### 2. Care este avantajul utilizării unui generator de liste de texte în comparație cu o listă de liste de texte atunci când utilizați `train_new_from_iterator()`? + +train_new_from_iterator() îl acceptă.", + explain: "O listă de liste de texte este un tip special de generator de liste de texte, astfel încât metoda o va accepta și pe aceasta. Încercați din nou!" + }, + { + text: "Veți evita încărcarea întregului dataset în memorie.", + explain: "Corect! Fiecare batch de texte va fi eliberat din memorie atunci când iterați, iar câștigul va fi vizibil mai ales dacă utilizați 🤗 Datasets pentru a stoca textele.", + correct: true + }, + { + text: "Acest lucru va permite bibliotecii 🤗 Tokenizers să utilizeze multiprocessing.", + explain: "Nu, oricum va folosi multiprocessing." + }, + { + text: "Tokenizerul pe care îl vei antrena va genera texte mai bune.", + explain: "Tokenizerul nu generează text - îl confundați cu un model lingvistic?" + } + ]} +/> + +### 3. Care sunt avantajele utilizării unui tokenizer "rapid"? + + + +### 4. Cum tratează pipelineul `token-classification` entitățile care se întind pe mai mulți tokeni? + + + +### 5. Cum gestionează pipelineul `question-answering` contextele lungi? + + + +### 6. Ce este normalizarea? + + + +### 7. Ce este pre-tokenizarea pentru un subword tokenizer? + + + +### 8. Selectați propozițiile care se aplică modelului de tokenizare BPE. + + + +### 9. Selectați propozițiile care se aplică modelului de tokenizare WordPiece. + + + +### 10. Selectați propozițiile care se aplică modelului de tokenizare Unigram. + + diff --git a/chapters/rum/chapter6/2.mdx b/chapters/rum/chapter6/2.mdx new file mode 100644 index 000000000..955c275c1 --- /dev/null +++ b/chapters/rum/chapter6/2.mdx @@ -0,0 +1,257 @@ +# Antrenarea unui nou tokenizer dintr-unul vechi[[training-a-new-tokenizer-from-an-old-one]] + + + +Dacă un model de limbaj nu este disponibil în limba dorită sau dacă corpusul tău este foarte diferit de cel pe care modelul de limbaj a fost antrenat, este probabil că veți dori să antrenați modelul de la zero, folosind un tokenizer adaptat datelor tale. Acest lucru va necesita antrenarea unui nou tokenizer pe datasetul tău. Dar ce înseamnă exact asta? Când am examinat pentru prima dată tokenizatorii în [Capitolul 2](/course/chapter2), am văzut că majoritatea modelelor Transformer folosesc un _algoritm de subword tokenization_. Pentru a identifica care subcuvinte sunt de interes și apar cel mai frecvent în corpusul respectiv, tokenizerul trebuie să examineze cu atenție toate textele din corpus - un proces pe care îl numim *antrenare*. Regulile exacte care conduc această antrenare depind de tipul de tokenizer utilizat și vom prezenta cei trei algoritmi principali mai târziu în acest capitol. + + + + + +⚠️ Antrenarea unui tokenizer nu este același lucru ca antrenarea unui model! Antrenarea modelului folosește stochastic gradient descent pentru a face pierderea puțin mai mică pentru fiecare batch. Este randomizată prin natură (ceea ce înseamnă că trebuie să setați niște seeduri pentru a obține aceleași rezultate atunci când faceți aceeași antrenare de două ori). Antrenarea unui tokenizer este un proces statistic care încearcă să identifice care subcuvinte sunt cele mai bune pentru a fi selectate pentru un anumit corpus, și regulile exacte utilizate pentru a le selecta depind de algoritmul de tokenizare. Este determinist, ceea ce înseamnă că întotdeauna obțineți aceleași rezultate atunci când antrenați cu același algoritm pe același corpus. + + + +## Asamblarea unui corpus[[assembling-a-corpus]] + +Există o interfață API foarte simplă în 🤗 Transformers pe care o puteți utiliza pentru a antrena un nou tokenizer cu aceleași caracteristici ca unul existent: `AutoTokenizer.train_new_from_iterator()`. Pentru a vedea acest lucru în acțiune, să zicem că vrem să antrenăm GPT-2 de la zero, dar într-o altă limbă decât engleza. Prima noastră sarcină va fi să adunăm multe date în acea limbă într-un corpus de antrenare. Pentru a oferi exemple pe care toată lumea le poate înțelege, nu vom folosi o limbă ca rusă sau chineza aici, ci mai degrabă o limbă engleză specializată: codul Python. + +Biblioteca [🤗 Datasets](https://github.com/huggingface/datasets) ne poate ajuta să asamblăm un corpus de cod sursă Python. Vom folosi funcția obișnuită `load_dataset()` pentru a descărca și a păstra în cache datasetul [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Acest dataset a fost creat pentru [Provocarea CodeSearchNet](https://wandb.ai/github/CodeSearchNet/benchmark) și conține milioane de funcții din biblioteci open-source de pe GitHub în mai multe limbaje de programare. Aici, vom încărca partea Python a acestui dataset: + +```py +from datasets import load_dataset + +# Acest lucru poate dura câteva minute pentru a încărca, așa că luați o pauză și beți o ceașcă de cafea sau ceai în timp ce așteptați! +raw_datasets = load_dataset("code_search_net", "python") +``` + +Putem să ne uităm la splitul de antrenare pentru a vedea la care coloane avem acces: + +```py +raw_datasets["train"] +``` + +```python out +Dataset({ + features: ['repository_name', 'func_path_in_repository', 'func_name', 'whole_func_string', 'language', + 'func_code_string', 'func_code_tokens', 'func_documentation_string', 'func_documentation_tokens', 'split_name', + 'func_code_url' + ], + num_rows: 412178 +}) +``` + +Putem vedea că dataset-ul separă docstringurile de cod și sugerează o tokenizare a ambelor. Aici, vom folosi doar coloana `whole_func_string` pentru a antrena tokenizerul nostru. Putem să ne uităm la un exemplu al unei astfel de funcții prin indexarea în splitul de antrenare: + +```py +print(raw_datasets["train"][123456]["whole_func_string"]) +``` + +care ar trebui să printeze următorul lucru: + +```out +def handle_simple_responses( + self, timeout_ms=None, info_cb=DEFAULT_MESSAGE_CALLBACK): + """Accepts normal responses from the device. + + Args: + timeout_ms: Timeout in milliseconds to wait for each response. + info_cb: Optional callback for text sent from the bootloader. + + Returns: + OKAY packet's message. + """ + return self._accept_responses('OKAY', info_cb, timeout_ms=timeout_ms) +``` + +Primul lucru pe care trebuie să-l facem este să transformăm setul de date într-un _iterator_ de liste de texte - de exemplu, o listă de liste de texte. Utilizarea listelor de texte va permite tokenizerului nostru să funcționeze mai rapid (antrenându-se pe batch-uri de texte în loc de a procesa texte individuale unul câte unul), iar acesta ar trebui să fie un iterator dacă dorim să evităm să avem tot în memoria RAM deodată. Dacă corpusul tău este uriaș, veți dori să profitați de faptul că 🤗 Datasets nu încarcă totul în memoria RAM, ci stochează elementele datasetului pe disc. + +Următoarea operație ar crea o listă de liste de 1.000 de texte fiecare, dar ar încărca totul în memorie: + +```py +# Nu faceți uncomment următoarei linii dacă datasetul vostru este mare, ci doar dacă este mic! +# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)] +``` + +Utilizând un generator Python, putem evita ca Python să încarce orice în memorie până când este realmente necesar. Pentru a crea un astfel de generator, trebuie doar să înlocuiți parantezele pătrate cu paranteze rotunde: + +```py +training_corpus = ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) +) +``` + +Această linie de cod nu extrage niciun element al setului de date; doar creează un obiect pe care îl puteți utiliza într-un `for` loop din Python. Textele vor fi încărcate doar atunci când veți avea nevoie de ele(adică atunci când sunteți la pasul loopului `for` care le solicită), iar doar 1.000 de texte vor fi încărcate la un moment dat. Acest mod vă permite să nu epuizați memoria RAM chiar dacă prelucrați un set de date uriaș. + +Problema cu un obiect generator este că poate fi utilizat doar o dată, așadar, în loc să ne ofere lista primelor 10 cifre de două ori: + +```py +gen = (i for i in range(10)) +print(list(gen)) +print(list(gen)) +``` + +noi le primim o dată și apoi o listă goală: + +```python out +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[] +``` + +Din acest motive noi definim o funcție ce returnează în schimb un generator: + +```py +def get_training_corpus(): + return ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) + ) + + +training_corpus = get_training_corpus() +``` + +În același timp poți defini un generator înăuntrul unui `for` loop folosing statementul `yield`: + +```py +def get_training_corpus(): + dataset = raw_datasets["train"] + for start_idx in range(0, len(dataset), 1000): + samples = dataset[start_idx : start_idx + 1000] + yield samples["whole_func_string"] +``` + +ceea ce va produce același generator ca înainte, dar îți va permite să folosești o logică mai complexă decât cea pe care ai putea să o folosești într-un list comprehension. + +## Antrenarea unui nou tokenizer[[training-a-new-tokenizer]] + +Acum că avem corpusul nostru sub forma unui iterator de batch-uri de texte, suntem gata să antrenăm un nou tokenizer. Pentru a face acest lucru, trebuie mai întâi să încărcăm tokenizerul pe care dorim să-l asociem cu modelul nostru (aici, GPT-2): + +```py +from transformers import AutoTokenizer + +old_tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Chiar dacă urmează să antrenăm un nou tokenizer, este o idee bună să facem acest lucru pentru a evita să începem să facem tot de la zero. Astfel, nu vom fi nevoiți să specificăm nimic despre algoritmul de tokenizare sau despre special tokens pe care îi vom utiliza; noul nostru tokenizer va fi exact la fel ca GPT-2, iar singur lucrul care se va schimba este vocabularul, care va fi determinat de antrenarea pe corpusul nostru. + +Mai întâi, hai să vedem cum ar interpreta acest tokenizer un exemplu de funcție: + +```py +example = '''def add_numbers(a, b): + """Add the two numbers `a` and `b`.""" + return a + b''' + +tokens = old_tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'n', 'umbers', '(', 'a', ',', 'Ġb', '):', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', + 'Ġnumbers', 'Ġ`', 'a', '`', 'Ġand', 'Ġ`', 'b', '`', '."', '""', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +Acest tokenizer are câteva simboluri speciale, cum ar fi `Ġ` și `Ċ`, care denotă spații și noi linii, respectiv. Așa cum se poate vedea, acest lucru nu este prea eficient: tokenizerul returnează tokenuri individuale pentru fiecare spațiu, când ar putea grupa împreună nivelurile de indentare (deoarece având seturi de patru sau opt spații va fi foarte comun în cod). De asemenea, a divizat numele funcției într-un mod ciudat, nefiind obișnuit să vadă cuvinte care conțin caracterele `_`. + +Acum hai să antrenăm un nou tokenizer și să vedem dacă rezolvă aceste probleme. Pentru aceasta, vom utiliza metoda `train_new_from_iterator()`: + +```py +tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) +``` + +Această comandă ar putea dura puțin timp dacă corpusul este foarte mare, dar pentru acest dataset de 1,6 GB de texte este extrem de rapid (1 minut și 16 secunde pe un procesor AMD Ryzen 9 3900X cu 12 nuclee). + +Reține că `AutoTokenizer.train_new_from_iterator()` funcționează doar dacă tokenizerul pe care îl utilizați este un tokenizer "rapid". Așa cum veți vedea în următoarea secțiune, biblioteca 🤗 Transformers conține două tipuri de tokenizeri: unii sunt scriși în pur Python, iar alții (cei rapizi) sunt susținuți de biblioteca 🤗 Tokenizers, care este scrisă în limbajul de programare Rust. Python este limbajul cel mai frecvent utilizat pentru aplicații de data science și deep learning, dar atunci când orice trebuie să fie paralelizat pentru a fi rapid, trebuie să fie scris într-un alt limbaj de programare. De exemplu, multiplicările matricelor care sunt la baza calculelor modelului sunt scrise în CUDA, o bibliotecă C optimizată pentru GPU-uri. + +Antrenarea unui tokenizer nou în pur Python ar fi extrem de lent, de aceea am dezvoltat biblioteca 🤗 Tokenizers. Reține că, la fel cum nu a trebuit să învățați limbajul CUDA pentru a putea executa modelul pe un batch de inputuri pe un GPU, nu veți avea nevoie să învățați Rust pentru a utiliza un tokenizer rapid. Biblioteca 🤗 Tokenizers oferă legături Python pentru multe metode care apelează intern unele bucăți de cod în Rust; de exemplu, pentru a paraleliza antrenarea noului tokenizer sau, așa cum am văzut în [Capitolul 3](/course/chapter3), tokenizarea unui batch de inputuri. + +Majoritatea modelelor Transformer au un tokenizer rapid disponibil (există unele excepții pe care le puteți verifica [aici](https://huggingface.co/transformers/#supported-frameworks)), iar API-ul `AutoTokenizer` selectează întotdeauna tokenizerul rapid pentru tine dacă este disponibil. În următoarea secțiune, vom examina unele dintre celelalte caracteristici speciale ale tokenizerilor rapizi, care vor fi foarte utile pentru sarcini precum clasificarea tokenilor și răspunderea la întrebări. Înainte de a face acest lucru, totuși, să încercăm noul nostru tokenizer pe exemplul anterior: + +```py +tokens = tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`', + 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +Aici din nou vedem simboluri speciale ca `Ġ` sau `Ċ` care denotă spații sau linii noi, dar în același timp putem vedea că tokenizerul nostru a învățat câțiva tokens care sunt foarte specifici la corpusul de funcții Python: de exemplu, tokenul `ĊĠĠĠ` care reprezintă indentarea, sau tokenul `Ġ"""` care reprezintă cele trei ghilimele cu care se începe un docstring. Tokenizerul, de asemenea face split corect numelui funției pe `_`. Aceasta chiar este o reprezentare compactă: comparativ, utilizând limba tokenizerului englez pe același exemplu ne va da o propoziție mai lungă: + +```py +print(len(tokens)) +print(len(old_tokenizer.tokenize(example))) +``` + +```python out +27 +36 +``` + +Hai să ne uităm la un alt exemplu: + +```python +example = """class LinearLayer(): + def __init__(self, input_size, output_size): + self.weight = torch.randn(input_size, output_size) + self.bias = torch.zeros(output_size) + + def __call__(self, x): + return x @ self.weights + self.bias + """ +tokenizer.tokenize(example) +``` + +```python out +['class', 'ĠLinear', 'Layer', '():', 'ĊĠĠĠ', 'Ġdef', 'Ġ__', 'init', '__(', 'self', ',', 'Ġinput', '_', 'size', ',', + 'Ġoutput', '_', 'size', '):', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'weight', 'Ġ=', 'Ġtorch', '.', 'randn', '(', 'input', '_', + 'size', ',', 'Ġoutput', '_', 'size', ')', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'bias', 'Ġ=', 'Ġtorch', '.', 'zeros', '(', + 'output', '_', 'size', ')', 'ĊĊĠĠĠ', 'Ġdef', 'Ġ__', 'call', '__(', 'self', ',', 'Ġx', '):', 'ĊĠĠĠĠĠĠĠ', + 'Ġreturn', 'Ġx', 'Ġ@', 'Ġself', '.', 'weights', 'Ġ+', 'Ġself', '.', 'bias', 'ĊĠĠĠĠ'] +``` + +În plus față de tokenul corespunzător unei indentări, aici putem vedea și un token pentru o indentare dublă: `ĊĠĠĠĠĠĠĠ`. Cuvintele speciale din Python, cum ar fi `class`, `init`, `call`, `self` și `return`, sunt tokenizate fiecare ca un singur token, și putem vedea că, pe lângă divizarea la `_` și `.`, tokenizerul divizează corect chiar și numele scrise în stil camel-case: `LinearLayer` este tokenizeat ca `["ĠLinear", "Layer"]`. + +## Salvarea tokenizerului[[saving-the-tokenizer]] + +Pentru a ne asigura că îl putem utiliza mai târziu, trebuie să salvăm noul nostru tokenizer. Asemănător salvării unui model, acest lucru se realizează cu metoda `save_pretrained()`: + +```py +tokenizer.save_pretrained("code-search-net-tokenizer") +``` + +Acest lucru va crea un nou folder numit *code-search-net-tokenizer*, care va conține toate fișierele necesare pentru a reîncărca tokenizerul. Dacă doriți să partajați acest tokenizer cu colegii și prietenii tăi, îl puteți încărca pe Hub prin conectarea la contul tău. Dacă lucrați într-un notebook, există un convenience function pentru a vă ajuta cu acest lucru: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Acest lucru va afișa un widget în care puteți introduce credențialele de conectare Hugging Face. Dacă nu lucrați într-un notebook, introduceți simplu următoarea linie în terminal: + +```bash +huggingface-cli login +``` + +După ce v-ați conectat, puteți face push tokenizerului prin executarea următoarei comenzi: + +```py +tokenizer.push_to_hub("code-search-net-tokenizer") +``` + +Acest lucru va crea un nou repositoriu în namespace-ul tău cu numele `code-search-net-tokenizer`, care va conține fișierul tokenizerului. După aceea, puteți încărca tokenizerul de oriunde cu metoda `from_pretrained()`: + +```py +# Înlocuiți "huggingface-course" mai jos cu namespace-ul tău pentru a utiliza propriul tokenizer +tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") +``` + +Acum sunteți gata să antrenați un model de limbaj de la zero și să îl ajustați pentru sarcina voastră! Vom face acest lucru în [Capitolul 7](/course/chapter7), dar mai întâi, în continuarea acestui capitol, vom arunca o privire mai atentă asupra tokenizerilor rapizi și vom explora în detaliu ce se întâmplă atunci când apelați metoda `train_new_from_iterator()`. \ No newline at end of file diff --git a/chapters/rum/chapter6/3.mdx b/chapters/rum/chapter6/3.mdx new file mode 100644 index 000000000..6f79c5ab7 --- /dev/null +++ b/chapters/rum/chapter6/3.mdx @@ -0,0 +1,473 @@ + + +# Superputerile tokenizerilor rapizi[[fast-tokenizers-special-powers]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +În această secțiune, vom analiza mai atent capacitățile tokenizerilor din 🤗 Transformers. Până acum, i-am folosit doar pentru tokenizarea inputurilor sau decodificarea ID-urilor înapoi în text, dar tokenizerii - în special cei susținuți de biblioteca 🤗 Tokenizers - pot face mult mai multe. Pentru a ilustra aceste funcții suplimentare, vom explora modul de reproducere a rezultatelor pipeline-urilor `token-classification` (pe care le-am numit `ner`) și `question-answering` pe care le-am întâlnit pentru prima dată în [Capitolul 1](/course/chapter1). + + + +În discuția următoare, vom face adesea distincția între tokenizatori "lenți" și "rapizi". Tokenizerii lenți sunt cei scriși în Python în interiorul bibliotecii 🤗 Transformers, în timp ce versiunile rapide sunt cele furnizate de 🤗 Tokenizers, care sunt scrise în Rust. Dacă vă amintiți de tabelul din [Capitolul 5](/course/chapter5/3) care a raportat cât timp a durat un tokenizer rapid și unul lent pentru a tokeniza datasetul Drug Review, ar trebui să aveți o idee despre de ce îi numim lenți și rapizi: + +| | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10,8s | 4min41s +`batched=False` | 59,2s | 5min3s + + + +⚠️ Atunci când tokenizați o singură propoziție, nu veți vedea întotdeauna o diferență de viteză între versiunea lentă și rapidă ale aceluiași tokenizer. De fapt, versiunea rapidă poate fi chiar mai lentă! Abia atunci când tokenizați multe texte în paralel, în același timp, veți putea observa clar diferența. + + + +## Batch encoding[[batch-encoding]] + + + +Rezultatul unui tokenizer nu este un simplu dicționar Python; ceea ce obținem este de fapt un obiect special `BatchEncoding`. Este o subclasă a unui dicționar (de aceea am putut indexa acel rezultat fără nici o problemă mai devreme), dar cu metode suplimentare care sunt utilizate în principal de către tokenizerii rapizi. + +Pe lângă capacitățile lor de paralelizare, funcționalitatea principală a tokenizerilor rapizi este aceea că aceștia țin întotdeauna evidența intervalului original de texte din care provin tokenii finali - o funcționalitate pe care o numim *offset mapping*. Acest lucru deblochează funcții precum mappingul fiecărui cuvânt la tokens pe care îi generează sau mappingul fiecărui caracter al textului original la tokenul în care se află și invers. + +Acum hai să aruncăm o privire la un exemplu: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +encoding = tokenizer(example) +print(type(encoding)) +``` + +Așa cum s-a menționat anterior, în outputul tokenizerului obținem un obiect `BatchEncoding`: + +```python out + +``` + +Deoarece clasa `AutoTokenizer` selectează un tokenizer rapid în mod implicit, putem utiliza metodele suplimentare pe care acest obiect `BatchEncoding` le oferă. Avem două metode de verificare dacă tokenizerul nostru este unul lent sau rapid. Putem verifica fie atributul `is_fast` al tokenizer-ului: + +```python +tokenizer.is_fast +``` + +```python out +True +``` + +sau același atribut al `encoding`: + +```python +encoding.is_fast +``` + +```python out +True +``` + +Hai să vedem ce ne permite un tokenizer rapid să facem. În primul rând, putem accesa tokenul fără a fi nevoie să facem convert ID-urile înapoi în tokens: + +```py +encoding.tokens() +``` + +```python out +['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in', + 'Brooklyn', '.', '[SEP]'] +``` + +În acest caz tokenul la indexul 5 este `##yl`, ceea ce este o parte a cuvântului "Sylvain" în propoziția originală. În același timp putem folosi metoda `word_ids()` pentru a obține indexul cuvântului din care provine fiecare token: + +```py +encoding.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 3, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, None] + +Putem vedea că tokenizerul are tokeni speciali `[CLS]` și `[SEP]` care sunt mapped la `None`, iar apoi fiecare token este mapped la cuvântul din care provine. Acest lucru este deosebit de util pentru a determina dacă un token este la începutul unui cuvânt sau dacă două tokenuri sunt în același cuvânt. Ne-am putea baza pe prefixul `##` pentru aceasta, dar funcționează doar pentru tokenizeri de tip BERT; această metodă funcționează pentru orice tip de tokenizator, atâta timp cât este unul rapid. În capitolul următor, vom vedea cum putem utiliza această capabilitate pentru a aplica labeluri pe care le avem pentru fiecare cuvânt în mod corespunzător tokenurilor în sarcini precum named entity recognition (NER) și part-of-speech (POS). De asemenea, îl putem utiliza pentru a face mask tuturor tokenurilor care provin din același cuvânt în masked language modeling(o tehnică numită _whole word masking_). + + + +Noțiunea de ceea ce este un cuvânt este complicată. De exemplu, "I'll" (o prescurtare a "I will") contează ca unul sau două cuvinte? Acest lucru depinde de tokenizer și de operațiunea de pre-tokenizare pe care o aplică. Unii tokenizeri se divid doar pe spații, așa că vor considera acest lucru ca un singur cuvânt. Alții folosesc punctuația pe lângă spații, deci vor considera două cuvinte. + +✏️ **Încercați!** Creați un tokenizer din checkpointurile `bert-base-cased` și `roberta-base` și tokenizați "81s" cu ele. Ce observați? Care sunt ID-urile cuvintelor? + + + +În mod similar, există o metodă `sentence_ids()` pe care o putem utiliza pentru a face map unui token la propoziția din care provine (deși, în acest caz, `token_type_ids` returnate de tokenizer ne pot oferi aceeași informație). + +În cele din urmă, putem face map oricărui cuvânt sau token la caracterele din textul original și invers, prin intermediul metodelor `word_to_chars()` sau `token_to_chars()` și `char_to_word()` sau `char_to_token()`. De exemplu, metoda `word_ids()` ne-a spus că `##yl` face parte din cuvântul cu indicele 3, dar ce cuvânt este în propoziție? Putem afla astfel: + +```py +start, end = encoding.word_to_chars(3) +example[start:end] +``` + +```python out +Sylvain +``` + +Așa cum am menționat anterior, toate acestea sunt posibile datorită faptului că tokenizerul rapid ține evidența spanului de text de la care provine fiecare token într-o listă de *offseturi*. Pentru a ilustra modul în care se utilizează acestea, în continuare vă vom arăta cum să replicați rezultatele pipelineului `token-classification` manual. + + + +✏️ **Încercați!** Creați propriul exemplu de text și încercați să înțelegeți care tokenuri sunt asociate cu ID-ul cuvântului și, de asemenea, cum să extrageți spanurile pentru singur cuvânt. Pentru puncte bonus, încercați să utilizați două propoziții ca inputuri și să vedeți dacă ID-urile propozițiilor au sens pentru voi. + + + +## În interiorul pipelineului `token-classification`[[inside-the-token-classification-pipeline]] + +În [Capitolul 1](/course/chapter1) am avut primul nostru contact aplicând NER - unde sarcina constă în identificarea părților textului care corespund entităților precum persoane, locații sau organizații - cu funcția `pipeline()` din 🤗 Transformers. Apoi, în [Capitolul 2](/course/chapter2), am văzut cum un pipeline grupează cele trei etape necesare pentru a obține predicțiile de la un text "raw": tokenizare, trecerea inputurilor prin model și post-procesare. Primele două etape din pipelineul `token-classification` sunt la fel ca în orice alt pipeline, dar post-procesarea este puțin mai complexă - hai să vedem cum! + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +### Obținerea rezultatelor de bază cu pipelineul[[getting-the-base-results-with-the-pipeline]] + +În primul rând, trebuie să luăm un token classification pipeline pentru a obține câteva rezultate ca să le putem compara manual. Modelul utilizat în mod implicit este [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english); acesta aplică NER pe propoziții: + +```py +from transformers import pipeline + +token_classifier = pipeline("token-classification") +token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Modelul a identificat fiecare token generdat de "Sylvain" ca o persoană, fiecare token generat de "Hugging Face" ca o organizație, și fiecare token "Brooklin" ca o locație". În același timp putem întreba pipelineul să grupeze împreună tokenurile care corespund cu aceeași entitate. + +```py +from transformers import pipeline + +token_classifier = pipeline("token-classification", aggregation_strategy="simple") +token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +`Aggregation_strategy` aleasă va modifica scorurile calculate pentru fiecare entitate grupată. Cu `"simple"`, scorul este doar media scorurilor pentru fiecare token din entitatea dată: de exemplu, scorul pentru "Sylvain" este media scorurilor pe care le-am văzut în exemplele anterioare pentru token-urile `S`, `##yl`, `##va` și `##in`. Alte strategii disponibile sunt: + +- `"first"`, unde scorul fiecărei entități este scorul primului token al acelei entități (astfel, pentru "Sylvain" ar fi 0.993828, scorul token-ului `S`) +- `"max"`, unde scorul fiecărei entități este scorul maxim al tokenilor din acea entitate (astfel, pentru "Hugging Face" ar fi 0.98879766, scorul "Face") +- `"average"`, unde scorul fiecărei entități este media scorurilor cuvintelor care compun acea entitate (astfel, pentru "Sylvain" nu ar exista nicio diferență față de strategia `"simple"`, dar "Hugging Face" ar avea un scor de 0.9819, media scorurilor pentru "Hugging", 0.975 și "Face", 0.98879) + +Acum să vedem cum putem obține aceste rezultate fără a folosi funcția `pipeline()`! + +### De la inputuri la predicții[[from-inputs-to-predictions]] + +{#if fw === 'pt'} + +În primul rând trebuie să tokenizăm inputurile și sp le trecem prin model. Acest lucru este făcut excat ca în [Capitolul 2](/course/chapter2); noi am inițializat tokenizerul și modelul folosind clasa `AutoXxx` și apoi am folosit-o pe exemplul nostru: + +```py +from transformers import AutoTokenizer, AutoModelForTokenClassification + +model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModelForTokenClassification.from_pretrained(model_checkpoint) + +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +inputs = tokenizer(example, return_tensors="pt") +outputs = model(**inputs) +``` + +Deoarece aici folosim `AutoModelForTokenClassification` , noi primim un set de logits pentru fiecare token în input sequence: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +torch.Size([1, 19]) +torch.Size([1, 19, 9]) +``` + +{:else} + +În primul rând trebuie să tokenizăm inputurile și să le trecem prin model. Acest lucru este făcut excat ca în [Capitolul 2](/course/chapter2); noi am inițializat tokenizerul și modelul folosind clasa `TFAutoXxx` și apoi am folosit-o pe exemplul nostru: + +```py +from transformers import AutoTokenizer, TFAutoModelForTokenClassification + +model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = TFAutoModelForTokenClassification.from_pretrained(model_checkpoint) + +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +inputs = tokenizer(example, return_tensors="tf") +outputs = model(**inputs) +``` + +Deoarece aici folosim `TFAutoModelForTokenClassification`, noi primim un set de logits pentru fiecare token în input sequence: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +(1, 19) +(1, 19, 9) +``` + +{/if} + +Noi avem un batch cu 1 secvență din 19 tokenuri și modelul are 9 labeluri diferite, deci outputul modelului are un shape de 1 x 19 x 9. Ca și pentru text classification pipeline, noi folosim o funcție softmax pentru a face convert logiturilor în probabilități și luăm argmax pentru a obține predicții(atrage atenția asupra fatpului că putem lua argmax pe logituri deoarece softmax nu schimbă ordinea): + +{#if fw === 'pt'} + +```py +import torch + +probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)[0].tolist() +predictions = outputs.logits.argmax(dim=-1)[0].tolist() +print(predictions) +``` + +{:else} + +```py +import tensorflow as tf + +probabilities = tf.math.softmax(outputs.logits, axis=-1)[0] +probabilities = probabilities.numpy().tolist() +predictions = tf.math.argmax(outputs.logits, axis=-1)[0] +predictions = predictions.numpy().tolist() +print(predictions) +``` + +{/if} + +```python out +[0, 0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 6, 6, 6, 0, 8, 0, 0] +``` + +Atributul `model.config.id2label` con;ine mappingul indexilor la labeluri pe care le putem folosi pentru a înțelege predicțiile: + +```py +model.config.id2label +``` + +```python out +{0: 'O', + 1: 'B-MISC', + 2: 'I-MISC', + 3: 'B-PER', + 4: 'I-PER', + 5: 'B-ORG', + 6: 'I-ORG', + 7: 'B-LOC', + 8: 'I-LOC'} +``` + +Cum am văzut mai devreme, există 9 labeluri: `O` este labelul pentru tokenurile care nu se află în nicio entitate numită(aceasta reprezintă "exteriorul"), și avem apoi două labeluri pentru fiecare tip de entitate (divers, persoană, organizație și locație). Eticheta `B-XXX` indică faptul că tokenul se află la începutul entității `XXX` și eticheta `I-XXX` indică faptul că tokenul se află în interiorul entității `XXX`. De exemplu, în exemplul curent ne-am aștepta ca modelul nostru să clasifice tokenul `S` ca `B-PER` (începutul unei entități de-tip persoană) și tokenurile `##yl`, `##va` și `##in` ca `I-PER` (în interiorul unei entități de tip persoană). + +S-ar putea să credeți că modelul a greșit în acest caz, deoarece a atribuit eticheta `I-PER` tuturor acestor patru tokeni, dar acesta nu este în întregime adevărat. Există, de fapt, două formate pentru labelurile `B-` și `I-`: *IOB1* și *IOB2*. Formatul IOB2 (în roz mai jos), este cel pe care l-am introdus, în timp ce formatul IOB1 (în albastru), utilizează labelurile care încep cu `B-` doar pentru a separa două entități adiacente de același tip. Modelul pe care îl utilizăm a fost fine-tuned pe un dataset care utilizează acel format, ceea ce explică de ce atribuie labelul `I-PER` tokenului `S`. + +
+IOB1 vs IOB2 format + +
+ +Cu maparea aceasta, suntem gat a să reproducem(aproape în total) rezultat primului pipeline -- noi putem lua scorul și labelul fiecărui token care nu a fost clasificat ca `O`: + +```py +results = [] +tokens = inputs.tokens() + +for idx, pred in enumerate(predictions): + label = model.config.id2label[pred] + if label != "O": + results.append( + {"entity": label, "score": probabilities[idx][pred], "word": tokens[idx]} + ) + +print(results) +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S'}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl'}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va'}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in'}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu'}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging'}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face'}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn'}] +``` + +Acest lucru este foarte similar cu ce am avut mai devreme, cu o excepție: pipelineul de asemenea ne-a oferit informație despre `start` și `end` al fiecărei entități în propoziția originală. Acum e momentul când offset mappingul nostru ne va ajuta. Pentru a obține offseturile, noi trebuie să setăm `return_offsets_mapping=True` când aplicăm tokenizerul pe inputurile noastre: + +```py +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +inputs_with_offsets["offset_mapping"] +``` + +```python out +[(0, 0), (0, 2), (3, 7), (8, 10), (11, 12), (12, 14), (14, 16), (16, 18), (19, 22), (23, 24), (25, 29), (30, 32), + (33, 35), (35, 40), (41, 45), (46, 48), (49, 57), (57, 58), (0, 0)] +``` + +Fiecare tuple este spanul de text care corespunde fiecărui token, unde `(0, 0)` este rezervat pentru tokenii speciali. Noi am văzut înainte că tokenul la indexul 5 este `##yl`, care are aici `(12, 14)` ca offsets. Dacă luăm sliceul corespunzător în exemplul nostru: + +```py +example[12:14] +``` + +noi obținem spanul propriu de text fără `##`: + +```python out +yl +``` + +Folosind aceasta, putem acum completa rezultatele anterioare: + + +```py +results = [] +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +tokens = inputs_with_offsets.tokens() +offsets = inputs_with_offsets["offset_mapping"] + +for idx, pred in enumerate(predictions): + label = model.config.id2label[pred] + if label != "O": + start, end = offsets[idx] + results.append( + { + "entity": label, + "score": probabilities[idx][pred], + "word": tokens[idx], + "start": start, + "end": end, + } + ) + +print(results) +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Acest răspuns e același răspuns pe care l-am primit de la primul pipeline: + +### Gruparea entităților[[grouping-entities]] + +Utilizarea offseturilor pentru a determina cheile de start și de sfârșit pentru fiecare entitate este util, dar această informație nu este strict necesară. Când dorim să grupăm entitățile împreună, totuși, offseturile ne vor salva o mulțime de messy code. De exemplu, dacă am dori să grupăm împreună tokenii `Hu`, `##gging` și `Face`, am putea crea reguli speciale care să spună că primele două ar trebui să fie atașate și să înlăturăm `##`, iar `Face` ar trebui adăugat cu un spațiu, deoarece nu începe cu `##` -- dar acest lucru ar funcționa doar pentru acest tip particular de tokenizer. Ar trebui să scriem un alt set de reguli pentru un tokenizer SentencePiece sau unul Byte-Pair-Encoding (discutat mai târziu în acest capitol). + +Cu offseturile, tot acel cod custom dispare: pur și simplu putem lua spanul din textul original care începe cu primul token și se termină cu ultimul token. Deci, în cazul tokenurilor `Hu`, `##gging` și `Face`, ar trebui să începem la caracterul 33 (începutul lui `Hu`) și să ne oprim înainte de caracterul 45 (sfârșitul lui `Face`): + +```py +example[33:45] +``` + +```python out +Hugging Face +``` + +Pentru a scrie codul care post-procesează predicțiile în timp ce grupăm entitățile, vom grupa entitățile care sunt consecutive și labeled cu `I-XXX`, cu excepția primeia, care poate fi labeled ca `B-XXX` sau `I-XXX` (decidem să oprim gruparea unei entități atunci când întâlnim un `O`, un nou tip de entitate, sau un `B-XXX` care ne spune că o entitate de același tip începe): + +```py +import numpy as np + +results = [] +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +tokens = inputs_with_offsets.tokens() +offsets = inputs_with_offsets["offset_mapping"] + +idx = 0 +while idx < len(predictions): + pred = predictions[idx] + label = model.config.id2label[pred] + if label != "O": + # Remove the B- or I- + label = label[2:] + start, _ = offsets[idx] + + # Grab all the tokens labeled with I-label + all_scores = [] + while ( + idx < len(predictions) + and model.config.id2label[predictions[idx]] == f"I-{label}" + ): + all_scores.append(probabilities[idx][pred]) + _, end = offsets[idx] + idx += 1 + + # The score is the mean of all the scores of the tokens in that grouped entity + score = np.mean(all_scores).item() + word = example[start:end] + results.append( + { + "entity_group": label, + "score": score, + "word": word, + "start": start, + "end": end, + } + ) + idx += 1 + +print(results) +``` + +Și obținem aceleași răspuns ca de la pipelineul secundar! + +```python out +[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Alt exemplu de sarcină unde offseturile sunt extrem de useful pentru răspunderea la întrebări. Scufundându-ne în pipelineuri, un lucru pe care îl vom face în următoarea secțiune, ne vom premite să ne uităm peste o caracteristică a tokenizerului în librăria 🤗 Transformers: vom avea de-a face cu overflowing tokens când truncăm un input de o anumită lungime. + diff --git a/chapters/rum/chapter6/3b.mdx b/chapters/rum/chapter6/3b.mdx new file mode 100644 index 000000000..0a7997708 --- /dev/null +++ b/chapters/rum/chapter6/3b.mdx @@ -0,0 +1,643 @@ + + +# Tokenizerii rapizi în pipeline-ul de QA[[fast-tokenizers-in-the-qa-pipeline]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Acum ne vom aprofunda în pipelineul `question-answering` și să vedem cum putem valorifica offesturile pentru a primi răspunzuri la întrebări la îndemână din context, asemănător cum am făcut cu entitățile grupate în secțiunea precedentă. Pe urmă vom vedea cum vom face față contextelor foarte lungi care ajung truncate. Puteți trece peste această secțiune dacă nu sunteți interesat în întrebarea care răspunde la sarcina aceasta. + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +## Folosind `question-answering` pipeline[[using-the-question-answering-pipeline]] + +Cum am văzut în [Capitolul 1](/course/chapter1), noi putem folosi pipelineul `question-answering` ca acesta pentru a răspunde la o întrebare: + +```py +from transformers import pipeline + +question_answerer = pipeline("question-answering") +context = """ +🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch, and TensorFlow — with a seamless integration +between them. It's straightforward to train your models with one before loading them for inference with the other. +""" +question = "Which deep learning libraries back 🤗 Transformers?" +question_answerer(question=question, context=context) +``` + +```python out +{'score': 0.97773, + 'start': 78, + 'end': 105, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +Spre deosebire de alte pipelineuri, care nu put trunca și face split la text care este mai lung decât lungimea maxim acceptată de model(și, prin urmare, pot pierde informații la sfârșitul unui document), accest pipeline poate face față contextelor foarte lungi și va returna răspunsul la întrebare chiar dacă aceasta se află la sfârșit: + +```py +long_context = """ +🤗 Transformers: State of the Art NLP + +🤗 Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction, +question answering, summarization, translation, text generation and more in over 100 languages. +Its aim is to make cutting-edge NLP easier to use for everyone. + +🤗 Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and +then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and +can be modified to enable quick research experiments. + +Why should I use transformers? + +1. Easy-to-use state-of-the-art models: + - High performance on NLU and NLG tasks. + - Low barrier to entry for educators and practitioners. + - Few user-facing abstractions with just three classes to learn. + - A unified API for using all our pretrained models. + - Lower compute costs, smaller carbon footprint: + +2. Researchers can share trained models instead of always retraining. + - Practitioners can reduce compute time and production costs. + - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages. + +3. Choose the right framework for every part of a model's lifetime: + - Train state-of-the-art models in 3 lines of code. + - Move a single model between TF2.0/PyTorch frameworks at will. + - Seamlessly pick the right framework for training, evaluation and production. + +4. Easily customize a model or an example to your needs: + - We provide examples for each architecture to reproduce the results published by its original authors. + - Model internals are exposed as consistently as possible. + - Model files can be used independently of the library for quick experiments. + +🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch and TensorFlow — with a seamless integration +between them. It's straightforward to train your models with one before loading them for inference with the other. +""" +question_answerer(question=question, context=long_context) +``` + +```python out +{'score': 0.97149, + 'start': 1892, + 'end': 1919, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +Hai să vedem cum el face toate astea! + +## Folosind un model pentru răspunderea la întrebări[[using-a-model-for-question-answering]] + +Ca în cazul oricărui altui pipeline, începem prin tokenizarea datelor de intrare și apoi le trimitem prin model. Checkpointul utilizat în mod implicit pentru pipelineul `question-answering` este [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) ("squad" din nume provine de la datasetul pe care modelul a fost ajustat; vom vorbi mai multe despre datasetul SQuAD în [Capitolul 7](/course/chapter7/7)): + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModelForQuestionAnswering + +model_checkpoint = "distilbert-base-cased-distilled-squad" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) + +inputs = tokenizer(question, context, return_tensors="pt") +outputs = model(**inputs) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModelForQuestionAnswering + +model_checkpoint = "distilbert-base-cased-distilled-squad" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) + +inputs = tokenizer(question, context, return_tensors="tf") +outputs = model(**inputs) +``` + +{/if} + +Observați că noi tokenizăm întrebrea și contextul ca o perecehe, cu întrebarea prima. + + +
+An example of tokenization of question and context + +
+ +Modelele create pentru răspunderea la întrebări funcționează puțin diferit de modelele pe care le-am văzut până acum. Folosind imaginea de mai sus ca exemplu, modelul a fost antrenat pentru a prezice indicele tokenului cu care începe răspunsului (aici 21) și indicele simbolului la care se termină răspunsul (aici 24). Acesta este motivul pentru care modelele respective nu returnează un singur tensor de logits, ci două: unul pentru logits-ul corespunzători tokenului cu care începe răspunsului și unul pentru logits-ul corespunzător tokenului de sfârșit al răspunsului. Deoarece în acest caz avem un singur input care conține 66 de token-uri, obținem: + +```py +start_logits = outputs.start_logits +end_logits = outputs.end_logits +print(start_logits.shape, end_logits.shape) +``` + +{#if fw === 'pt'} + +```python out +torch.Size([1, 66]) torch.Size([1, 66]) +``` + +{:else} + +```python out +(1, 66) (1, 66) +``` + +{/if} + +Pentru a converti acești logits în probabilități, vom aplica o funcție softmax - dar înainte de aceasta, trebuie să ne asigurăm că mascăm indicii care nu fac parte din context. Inputul nostru este `[CLS] întrebare [SEP] context [SEP]`, deci trebuie să mascăm token-urile întrebării, precum și tokenul `[SEP]`. Cu toate acestea, vom păstra simbolul `[CLS]`, deoarece unele modele îl folosesc pentru a indica faptul că răspunsul nu se află în context. + +Deoarece vom aplica ulterior un softmax, trebuie doar să înlocuim logiturile pe care dorim să le mascăm cu un număr negativ mare. Aici, folosim `-10000`: + +{#if fw === 'pt'} + +```py +import torch + +sequence_ids = inputs.sequence_ids() +# Mask everything apart from the tokens of the context +mask = [i != 1 for i in sequence_ids] +# Unmask the [CLS] token +mask[0] = False +mask = torch.tensor(mask)[None] + +start_logits[mask] = -10000 +end_logits[mask] = -10000 +``` + +{:else} + +```py +import tensorflow as tf + +sequence_ids = inputs.sequence_ids() +# Mask everything apart from the tokens of the context +mask = [i != 1 for i in sequence_ids] +# Unmask the [CLS] token +mask[0] = False +mask = tf.constant(mask)[None] + +start_logits = tf.where(mask, -10000, start_logits) +end_logits = tf.where(mask, -10000, end_logits) +``` + +{/if} + +Acum că am mascat în mod corespunzător logiturile corespunzătoare pozițiilor pe care nu dorim să le prezicem, putem aplica softmax: + +{#if fw === 'pt'} + +```py +start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1)[0] +end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1)[0] +``` + +{:else} + +```py +start_probabilities = tf.math.softmax(start_logits, axis=-1)[0].numpy() +end_probabilities = tf.math.softmax(end_logits, axis=-1)[0].numpy() +``` + +{/if} + +La acest stadiu, am putea lua argmax al probabilităților de început și de sfârșit - dar am putea ajunge la un indice de început care este mai mare decât indicele de sfârșit, deci trebuie să luăm câteva precauții suplimentare. Vom calcula probabilitățile fiecărui `start_index` și `end_index` posibil în cazul în care `start_index <= end_index`, apoi vom lua un tuple `(start_index, end_index)` cu cea mai mare probabilitate. + +Presupunând că evenimentele "The answer starts at `start_index`" și "The answer ends at `end_index`" sunt independente, probabilitatea ca răspunsul să înceapă la `start_index` și să se termine la `end_index` este: + +$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ + +Deci, pentru a calcula toate scorurile, trebuie doar să calculăm toate produsele \\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\) unde `start_index <= end_index`. + +Mai întâi hai să calculăm toate produsele posibile: + +```py +scores = start_probabilities[:, None] * end_probabilities[None, :] +``` + +{#if fw === 'pt'} + +Apoi vom masca valorile în care `start_index > end_index` prin stabilirea lor la `0` (celelalte probabilități sunt toate numere pozitive). Funcția `torch.triu()` returnează partea triunghiulară superioară a tensorului 2D trecut ca argument, deci va face această mascare pentru noi: + +```py +scores = torch.triu(score) +``` + +{:else} + +Apoi vom masca valorile în care `start_index > end_index` prin stabilirea lor la `0` (celelalte probabilități sunt toate numere pozitive). Funcția `np.triu()` returnează partea triunghiulară superioară a tensorului 2D trecut ca argument, deci va face această mascare pentru noi: + +```py +import numpy as np + +scores = np.triu(scores) +``` + +{/if} + +Acum trebuie doar să obținem indicele maximului. Deoarece PyTorch va returna indicele în tensorul aplatizat, trebuie să folosim operațiile floor division `//` și modulusul `%` pentru a obține `start_index` și `end_index`: + +```py +max_index = scores.argmax().item() +start_index = max_index // scores.shape[1] +end_index = max_index % scores.shape[1] +print(scores[start_index, end_index]) +``` + +Nu am terminat încă, dar cel puțin avem deja scorul corect pentru răspuns (puteți verifica acest lucru comparându-l cu primul rezultat din secțiunea anterioară): + +```python out +0.97773 +``` + + + +✏️ **Încercați!** Calculați indicii de început și de sfârșit pentru cele mai probabile cinci răspunsuri. + + + +Avem `start_index` și `end_index` ale răspunsului în termeni de tokens, deci acum trebuie doar să convertim în character indices în context. Acesta este momentul în care offseturile vor fi foarte utile. Putem să le luăm și să le folosim așa cum am făcut în sarcina de clasificare a tokenurilor: + +```py +inputs_with_offsets = tokenizer(question, context, return_offsets_mapping=True) +offsets = inputs_with_offsets["offset_mapping"] + +start_char, _ = offsets[start_index] +_, end_char = offsets[end_index] +answer = context[start_char:end_char] +``` + +Acum trebuie doar să formatăm totul pentru a obține rezultatul nostru: + +```py +result = { + "answer": answer, + "start": start_char, + "end": end_char, + "score": scores[start_index, end_index], +} +print(result) +``` + +```python out +{'answer': 'Jax, PyTorch and TensorFlow', + 'start': 78, + 'end': 105, + 'score': 0.97773} +``` + +Grozav! Este la fel ca în primul nostru exemplu! + + + +✏️ **Încercați!** Utilizați cele mai bune scoruri pe care le-ați calculat anterior pentru a afișa cele mai probabile cinci răspunsuri. Pentru a vă verifica rezultatele, întoarceți-vă la primul pipeline și introduceți `top_k=5` atunci când îl apelați. + + + +## Gestionarea contextelor lungi[[handling-long-contexts]] + +Dacă încercăm să tokenizăm întrebarea și contextul lung pe care le-am folosit ca un exemplu anterior, vom obține un număr de tokenuri mai mare decât lungimea maximă utilizată în pipelineul `question-answering` (care este 384): + +```py +inputs = tokenizer(question, long_context) +print(len(inputs["input_ids"])) +``` + +```python out +461 +``` + +Prin urmare, va trebui să trunchiem inputurile la lungimea maximă. Există mai multe modalități prin care putem face acest lucru, dar nu dorim să trunchiem întrebarea, ci doar contextul. Deoarece contextul este a doua propoziție, vom utiliza strategia de trunchiere `"only_second"`. Problema care apare atunci este că răspunsul la întrebare poate să nu fie în contextul trunchiat. Aici, de exemplu, am ales o întrebare la care răspunsul se află spre sfârșitul contextului, iar atunci când îl trunchiem, răspunsul nu este prezent: + +```py +inputs = tokenizer(question, long_context, max_length=384, truncation="only_second") +print(tokenizer.decode(inputs["input_ids"])) +``` + +```python out +""" +[CLS] Which deep learning libraries back [UNK] Transformers? [SEP] [UNK] Transformers : State of the Art NLP + +[UNK] Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction, +question answering, summarization, translation, text generation and more in over 100 languages. +Its aim is to make cutting-edge NLP easier to use for everyone. + +[UNK] Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and +then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and +can be modified to enable quick research experiments. + +Why should I use transformers? + +1. Easy-to-use state-of-the-art models: + - High performance on NLU and NLG tasks. + - Low barrier to entry for educators and practitioners. + - Few user-facing abstractions with just three classes to learn. + - A unified API for using all our pretrained models. + - Lower compute costs, smaller carbon footprint: + +2. Researchers can share trained models instead of always retraining. + - Practitioners can reduce compute time and production costs. + - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages. + +3. Choose the right framework for every part of a model's lifetime: + - Train state-of-the-art models in 3 lines of code. + - Move a single model between TF2.0/PyTorch frameworks at will. + - Seamlessly pick the right framework for training, evaluation and production. + +4. Easily customize a model or an example to your needs: + - We provide examples for each architecture to reproduce the results published by its original authors. + - Model internal [SEP] +""" +``` + +Aceasta înseamnă că modelul va avea dificultăți în a alege răspunsul corect. Pentru a rezolva acest lucru, pipelineul `question-answering` ne permite să împărțim contextul în bucăți mai mici, specificând lungimea maximă. Pentru a ne asigura că nu împărțim contextul exact în locul nepotrivit pentru a face posibilă găsirea răspunsului, aceasta include și o anumită suprapunere între bucăți. + +Putem cere tokenizerului (rapid sau lent) să facă acest lucru pentru noi adăugând `return_overflowing_tokens=True`, și putem specifica suprapunerea dorită cu argumentul `stride`. Iată un exemplu, folosind o propoziție mai mică: + +```py +sentence = "This sentence is not too long but we are going to split it anyway." +inputs = tokenizer( + sentence, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 +) + +for ids in inputs["input_ids"]: + print(tokenizer.decode(ids)) +``` + +```python out +'[CLS] This sentence is not [SEP]' +'[CLS] is not too long [SEP]' +'[CLS] too long but we [SEP]' +'[CLS] but we are going [SEP]' +'[CLS] are going to split [SEP]' +'[CLS] to split it anyway [SEP]' +'[CLS] it anyway. [SEP]' +``` + +După cum putem vedea, propoziția a fost împărțită în bucăți astfel încât fiecare intrare din `inputs["input_ids"]` să aibă cel mult 6 token-uri (aici ar trebui să adăugăm padding pentru ca ultima intrare să aibă aceeași dimensiune ca celelalte) și există o suprapunere de 2 tokenuri între fiecare intrare. + +Să aruncăm o privire mai atentă la rezultatul tokenizării: + +```py +print(inputs.keys()) +``` + +```python out +dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) +``` + +Așa cum era de așteptat, obținem ID-uri de intrare și un attention mask. Ultima cheie, `overflow_to_sample_mapping`, este o hartă care ne spune cărei propoziții îi corespunde fiecare dintre rezultate - aici avem 7 rezultate care provin toate din (singura) propoziție pe care am transmis-o tokenizerului: + +```py +print(inputs["overflow_to_sample_mapping"]) +``` + +```python out +[0, 0, 0, 0, 0, 0, 0] +``` + +Acest lucru este mai util atunci când tokenizăm mai multe propoziții împreună. De exemplu, aceasta: + +```py +sentences = [ + "This sentence is not too long but we are going to split it anyway.", + "This sentence is shorter but will still get split.", +] +inputs = tokenizer( + sentences, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 +) + +print(inputs["overflow_to_sample_mapping"]) +``` + +gets us: + +```python out +[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] +``` + +ceea ce înseamnă că prima propoziție este împărțită în 7 fragmente ca înainte, iar următoarele 4 fragmente provin din a doua propoziție. + +Acum să ne întoarcem la contextul nostru lung. În mod implicit, pipelineul `question-answering` utilizează o lungime maximă de 384, așa cum am menționat mai devreme, și un stride de 128, care corespund modului în care modelul a fost fine-tuned (puteți ajusta acești parametri prin trecerea argumentelor `max_seq_len` și `stride` atunci când apelați pipelineul). Astfel, vom utiliza acești parametri la tokenizare. Vom adăuga, de asemenea, padding (pentru a avea sampleuri de aceeași lungime, astfel încât să putem construi tensori), precum și pentru a solicita offsets: + +```py +inputs = tokenizer( + question, + long_context, + stride=128, + max_length=384, + padding="longest", + truncation="only_second", + return_overflowing_tokens=True, + return_offsets_mapping=True, +) +``` + +Aceste "inputuri" vor conține ID-urile de input și attention maskurile așteptate de model, precum și offseturile și "overflow_to_sample_mapping" despre care tocmai am vorbit. Deoarece cei doi nu sunt parametri utilizați de model, îi vom scoate din `inputs` (și nu vom stoca harta, deoarece nu este utilă aici) înainte de a-l converti într-un tensor: + +{#if fw === 'pt'} + +```py +_ = inputs.pop("overflow_to_sample_mapping") +offsets = inputs.pop("offset_mapping") + +inputs = inputs.convert_to_tensors("pt") +print(inputs["input_ids"].shape) +``` + +```python out +torch.Size([2, 384]) +``` + +{:else} + +```py +_ = inputs.pop("overflow_to_sample_mapping") +offsets = inputs.pop("offset_mapping") + +inputs = inputs.convert_to_tensors("tf") +print(inputs["input_ids"].shape) +``` + +```python out +(2, 384) +``` + +{/if} + +Contextul nostru lung a fost împărțit în două, ceea ce înseamnă că, după ce trece prin modelul nostru, vom avea două seturi de logits de început și de sfârșit: + +```py +outputs = model(**inputs) + +start_logits = outputs.start_logits +end_logits = outputs.end_logits +print(start_logits.shape, end_logits.shape) +``` + +{#if fw === 'pt'} + +```python out +torch.Size([2, 384]) torch.Size([2, 384]) +``` + +{:else} + +```python out +(2, 384) (2, 384) +``` + +{/if} + +Ca și înainte, mai întâi mascăm tokenii care nu fac parte din context înainte de a lua softmax. De asemenea, mascăm toți padding tokens(marcate de attention mask): + +{#if fw === 'pt'} + +```py +sequence_ids = inputs.sequence_ids() +# Mask everything apart from the tokens of the context +mask = [i != 1 for i in sequence_ids] +# Unmask the [CLS] token +mask[0] = False +# Mask all the [PAD] tokens +mask = torch.logical_or(torch.tensor(mask)[None], (inputs["attention_mask"] == 0)) + +start_logits[mask] = -10000 +end_logits[mask] = -10000 +``` + +{:else} + +```py +sequence_ids = inputs.sequence_ids() +# Mask everything apart from the tokens of the context +mask = [i != 1 for i in sequence_ids] +# Unmask the [CLS] token +mask[0] = False +# Mask all the [PAD] tokens +mask = tf.math.logical_or(tf.constant(mask)[None], inputs["attention_mask"] == 0) + +start_logits = tf.where(mask, -10000, start_logits) +end_logits = tf.where(mask, -10000, end_logits) +``` + +{/if} + +Then we can use the softmax to convert our logits to probabilities: + +{#if fw === 'pt'} + +```py +start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1) +end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1) +``` + +{:else} + +```py +start_probabilities = tf.math.softmax(start_logits, axis=-1).numpy() +end_probabilities = tf.math.softmax(end_logits, axis=-1).numpy() +``` + +{/if} + +Următorul pas este similar cu ceea ce am făcut pentru contextul mic, dar îl repetăm pentru fiecare dintre cele două chunkuri. Atribuim un scor tuturor intervalelor posibile de răspuns, apoi luăm intervalul cu cel mai bun scor: + +{#if fw === 'pt'} + +```py +candidates = [] +for start_probs, end_probs in zip(start_probabilities, end_probabilities): + scores = start_probs[:, None] * end_probs[None, :] + idx = torch.triu(scores).argmax().item() + + start_idx = idx // scores.shape[1] + end_idx = idx % scores.shape[1] + score = scores[start_idx, end_idx].item() + candidates.append((start_idx, end_idx, score)) + +print(candidates) +``` + +{:else} + +```py +candidates = [] +for start_probs, end_probs in zip(start_probabilities, end_probabilities): + scores = start_probs[:, None] * end_probs[None, :] + idx = np.triu(scores).argmax().item() + + start_idx = idx // scores.shape[1] + end_idx = idx % scores.shape[1] + score = scores[start_idx, end_idx].item() + candidates.append((start_idx, end_idx, score)) + +print(candidates) +``` + +{/if} + +```python out +[(0, 18, 0.33867), (173, 184, 0.97149)] +``` + +Cei doi candidați corespund celor mai bune răspunsuri pe care modelul le-a putut găsi în fiecare parte. Modelul este mult mai încrezător că răspunsul corect se află în a doua parte (ceea ce este un semn bun!). Acum trebuie doar să facem map celor două intervale de tokenuri cu intervalele de caractere din context (trebuie să o punem în corespondență doar pe a doua pentru a avea răspunsul nostru, dar este interesant să vedem ce a ales modelul în prima parte). + + + +✏️ **Încercați!** Adaptați codul de mai sus pentru a returna scorurile și spanurile intervalele pentru cele mai probabile cinci răspunsuri (în total, nu pe chunk). + + + +`offsets`-urile pe care le-am luat mai devreme este de fapt o listă de offsets, cu o listă pentru fiecare chunk de text: + +```py +for candidate, offset in zip(candidates, offsets): + start_token, end_token, score = candidate + start_char, _ = offset[start_token] + _, end_char = offset[end_token] + answer = long_context[start_char:end_char] + result = {"answer": answer, "start": start_char, "end": end_char, "score": score} + print(result) +``` + +```python out +{'answer': '\n🤗 Transformers: State of the Art NLP', 'start': 0, 'end': 37, 'score': 0.33867} +{'answer': 'Jax, PyTorch and TensorFlow', 'start': 1892, 'end': 1919, 'score': 0.97149} +``` + +Dacă ignorăm primul rezultat, obținem același rezultat ca și pipelineul noastru pentru acest context lung - yay! + + + +✏️ **Încercați!** Utilizați cele mai bune scoruri pe care le-ați calculat înainte pentru a afișa cele mai probabile cinci răspunsuri (pentru întregul context, nu pentru fiecare chunk). Pentru a vă verifica rezultatele, întoarceți-vă la primul pipeline și introduceți `top_k=5` atunci când îl apelați. + + + +Aici se încheie scufundarea noastră în capacitățile tokenizerului. Vom pune toate acestea din nou în practică în capitolul următor, când vă vom arăta cum să ajustați un model pentru o serie de sarcini NLP comune. diff --git a/chapters/rum/chapter6/4.mdx b/chapters/rum/chapter6/4.mdx new file mode 100644 index 000000000..d19b1777a --- /dev/null +++ b/chapters/rum/chapter6/4.mdx @@ -0,0 +1,122 @@ +# Normalizare și pre-tokenizare[[normalization-and-pre-tokenization]] + + + +Înainte de a analiza în profunzime cei mai comuni trei algoritmi de subword tokenization utilizați cu modelele Transformer (Byte-Pair Encoding [BPE], WordPiece și Unigram), vom arunca mai întâi o privire la preprocesarea pe care fiecare tokenizer o aplică textului. Iată o prezentare generală a etapelor din pipelineul de tokenizare: + +
+The tokenization pipeline. + +
+ +Înainte de a împărți un text în subtokens (în conformitate cu modelul său), tokenizerul efectuează doi pași: _normalization__ și _pre-tokenization_. + +## Normalization[[normalization]] + + + +Etapa de normalizare implică o curățare generală, cum ar fi eliminarea spațiilor inutile, a le face minuscule, și/sau ștergerea accentelor. Dacă sunteți familiarizat cu [Unicode normalization](http://www.unicode.org/reports/tr15/) (cum ar fi NFC sau NFKC), acest lucru poate fi aplicat și de tokenizer. + +`Tokenizer`-ul 🤗 Transformers are un atribut numit `backend_tokenizer` care oferă acces la tokenizatorul de bază din biblioteca 🤗 Tokenizers: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") +print(type(tokenizer.backend_tokenizer)) +``` + +```python out + +``` + +Atributul `normalizer` al obiectului `tokenizer` are o metodă `normalize_str()` pe care o putem folosi pentru a vedea cum se realizează normalizarea: + +```py +print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +'hello how are u?' +``` + +În acest exemplu, din moment ce am ales checkpointul `bert-base-uncased`, normalizarea a aplicat scrierea cu minusculă și a eliminat accentele. + + + +✏️ **Încercați!** Încărcați un tokenizer din checkpointul `bert-base-cased` și treceți-i același exemplu. Care sunt principalele diferențe pe care le puteți observa între versiunile cased și uncased ale tokenizerului? + + + +## Pre-tokenization[[pre-tokenization]] + + + +După cum vom vedea în secțiunile următoare, un tokenizer nu poate fi antrenat doar pe text raw. În schimb, trebuie mai întâi să împărțim textele în entități mici, cum ar fi cuvintele. Aici intervine etapa de pre-tokenizare. După cum am văzut în [Capitolul 2](/course/chapter2), un tokenizer bazat pe cuvinte poate împărți pur și simplu un text raw în cuvinte pe baza spațiului și a punctuației. Aceste cuvinte vor fi limitele subtokenurilor pe care tokenizerul le poate învăța în timpul instruirii sale. + +Pentru a vedea cum un tokenizator rapid efectuează pre-tokenizarea, putem utiliza metoda `pre_tokenize_str()` a atributului `pre_tokenizer` al obiectului `tokenizer`: + +```py +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))] +``` + +Observați cum tokenizatorul ține deja evidența offseturilor, acesta fiind modul în care ne poate oferi mappingul offseturilor pe care l-am folosit în secțiunea anterioară. Aici, tokenizatorul ignoră cele două spații și le înlocuiește cu unul singur, dar offsetul sare între `are` și `you` pentru a ține cont de acest lucru. + +Deoarece utilizăm un tokenizer BERT, pre-tokenizarea implică separarea spațiilor și a punctuației. Alți tokenizatori pot avea reguli diferite pentru acest pas. De exemplu, dacă folosim tokenizatorul GPT-2: + +```py +tokenizer = AutoTokenizer.from_pretrained("gpt2") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +va despărți în spații și punctuație, dar va păstra spațiile și le va înlocui cu un simbol `Ġ`, permițându-i să recupereze spațiile originale dacă facem decode tokenilor: + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), + ('?', (19, 20))] +``` + +De asemenea, rețineți că, spre deosebire de tokenizatorul BERT, acest tokenizator nu ignoră spațiul dublu. + +Pentru un ultim exemplu, să aruncăm o privire la tokenizerul T5, care se bazează pe algoritmul SentencePiece: + +```py +tokenizer = AutoTokenizer.from_pretrained("t5-small") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +```python out +[('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))] +``` + +Ca și tokenizatorul GPT-2, acesta păstrează spațiile și le înlocuiește cu un token specific (`_`), dar tokenizatorul T5 separă doar spațiile, nu și punctuația. De asemenea, observați că a adăugat un spațiu implicit la începutul propoziției (înainte de `Hello`) și a ignorat spațiul dublu dintre `are` și `you`. + +Acum că am văzut puțin din modul în care diferite tokenizere procesează textul, putem începe să explorăm algoritmii care stau la baza acestora. Vom începe cu o privire rapidă asupra SentencePiece, care se aplică pe scară largă; apoi, în următoarele trei secțiuni, vom examina modul în care funcționează cei trei algoritmi principali utilizați pentru tokenizarea subcuvintelor. + +## SentencePiece[[sentencepiece]] + +[SentencePiece](https://github.com/google/sentencepiece) este un algoritm de tokenizare pentru preprocesarea textului pe care îl puteți utiliza cu oricare dintre modelele pe care le vom vedea în următoarele trei secțiuni. Acesta consideră textul ca o secvență de caractere Unicode și înlocuiește spațiile cu un caracter special, `▁`. Folosit împreună cu algoritmul Unigram (a se vedea [secțiunea 7](/course/chapter7/7)), nu necesită nici măcar o etapă de pre-tokenizare, ceea ce este foarte util pentru limbile în care nu se folosește caracterul spațiu (cum ar fi chineza sau japoneza). + +Cealaltă caracteristică principală a SentencePiece este *reversible tokenization*: deoarece nu există un tratament special al spațiilor, decodarea tokenurilor se face pur și simplu prin concatenarea lor și înlocuirea `_` cu spații - acest lucru rezultă în textul normalizat. După cum am văzut mai devreme, tokenizatorul BERT elimină spațiile care se repetă, deci tokenizarea sa nu este reversibilă. + +## Prezentare generală a algoritmului[[algorithm-overview]] + +În următoarele secțiuni, vom analiza cei trei algoritmi principali de tokenizare a subcuvintelor: BPE (utilizat de GPT-2 și alții), WordPiece (utilizat de exemplu de BERT) și Unigram (utilizat de T5 și alții). Înainte de a începe, iată o scurtă prezentare generală a modului în care funcționează fiecare dintre acestea. Nu ezitați să reveniți la acest tabel după ce citiți fiecare dintre secțiunile următoare, dacă încă nu are sens pentru dumneavoastră. + +Model | BPE | WordPiece | Unigram +:----:|:---:|:---------:|:------: +Training | Pornește de la un vocabular restrâns și învață reguli de îmbinare a tokenilor | Pornește de la un vocabular restrâns și învață reguli de îmbinare a tokenilor | Pornește de la un vocabular mare și învață regulile de eliminare a tokenilor +Training step | Combină tokenii corespunzători celei mai comune perechi | Combină tokenii corespunzători perechii cu cel mai bun scor pe baza frecvenței perechii, privilegiind perechile în care fiecare token individual este mai puțin frecvent| Elimină toți tokenii din vocabular care vor minimiza pierderea calculată pe întregul corpus +Learns | Reguli de combinare și un vocabular| Doar un vocabular| Un vocabular cu un anumit scor pentru fiecare token +Encoding | Împarte un cuvânt în caractere și aplică îmbinările învățate în timpul antrenării | Găsește cel mai lung subcuvânt începând de la început care se află în vocabular, apoi face același lucru pentru restul cuvântului | Găsește cea mai probabilă împărțire în tokens, folosind scorurile învățate în timpul antrenării + +Acum hai să trecem la BPE! \ No newline at end of file diff --git a/chapters/rum/chapter6/5.mdx b/chapters/rum/chapter6/5.mdx new file mode 100644 index 000000000..91c26b82f --- /dev/null +++ b/chapters/rum/chapter6/5.mdx @@ -0,0 +1,360 @@ +# Tokenizarea Byte-Pair Encoding[[byte-pair-encoding-tokenization]] + + + +Byte-Pair Encoding (BPE) a fost inițial dezvoltat ca un algoritm de comprimare a textelor și apoi utilizat de OpenAI pentru tokenizare la preantrenarea modelului GPT. Acesta este utilizat de o mulțime de modele Transformers, inclusiv GPT, GPT-2, RoBERTa, BART și DeBERTa. + + + + + +💡 Această secțiune acoperă BPE în profunzime, mergând până la prezentarea unei implementări complete. Puteți sări la sfârșit dacă doriți doar o prezentare generală a algoritmului de tokenizare. + + + +## Algoritmul de antrenare[[training-algorithm]] + +Antrenarea BPE începe prin calcularea setului unic de cuvinte utilizate în corpus (după finalizarea etapelor de normalizare și pre-tokenizare), apoi construirea vocabularului prin preluarea tuturor simbolurilor utilizate pentru scrierea acestor cuvinte. Ca un exemplu foarte simplu, să spunem că corpusul nostru utilizează aceste cinci cuvinte: + +``` +"hug", "pug", "pun", "bun", "hugs" +``` + +Vocabularul de bază va fi atunci `["b", "g", "h", "n", "p", "s", "u"]`. Pentru cazurile din lumea reală, vocabularul de bază va conține cel puțin toate caracterele ASCII și, probabil, și unele caractere Unicode. Dacă un exemplu pe care îl tokenizați utilizează un caracter care nu se află în corpusul de antrenare, acel caracter va fi convertit într-un token necunoscut. Acesta este unul dintre motivele pentru care o mulțime de modele NLP sunt foarte proaste la analizarea conținutului cu emoji, de exemplu. + + + +Tokenizerele GPT-2 și RoBERTa (care sunt destul de asemănătoare) au o modalitate inteligentă de a rezolva acest lucru: ele nu privesc cuvintele ca fiind scrise cu caractere Unicode, ci cu bytes. În acest fel, vocabularul de bază are o dimensiune mică (256), dar fiecare caracter la care vă puteți gândi va fi inclus și nu va ajunge să fie convertit într-un token necunoscut. Acest truc se numește *byte-level BPE*. + + + +După obținerea acestui vocabular de bază, adăugăm noi tokeni până când se atinge dimensiunea dorită a vocabularului prin învățarea prin *merges*, care sunt reguli de merge a două elemente ale vocabularului existent într-unul nou. Astfel, la început, aceste fuziuni vor crea tokenuri cu două caractere, iar apoi, pe măsură ce antrenamentul progresează, subwords mai lungi. + +În orice etapă din timpul antrenării tokenizerului, algoritmul BPE va căuta cea mai frecventă pereche de tokenuri existente (prin "pereche" înțelegem aici doi tokeni consecutivi într-un cuvânt). Acea pereche cea mai frecventă este cea care va fi mergea, iar noi ștergem și repetăm pentru pasul următor. + +Revenind la exemplul nostru anterior, să presupunem că cuvintele au următoarele frecvențe: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +înțelegând că `"hug"` a fost prezent de 10 ori în corpus, `"pug"` de 5 ori, `"pun"` de 12 ori, `"bun"` de 4 ori, iar `"hugs"` de 5 ori. Începem antrenamentul împărțind fiecare cuvânt în caractere (cele care formează vocabularul nostru inițial), astfel încât să putem vedea fiecare cuvânt ca pe o listă de tokeni: + +``` +("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5) +``` + +Apoi ne uităm la perechi. Perechea `("h", "u")` este prezentă în cuvintele `"hug"` și `"hugs"`, deci de 15 ori în total în corpus. Totuși, nu este cea mai frecventă pereche: această onoare revine perechii `("u", "g")`, care este prezentă în cuvintele `"hug"`, `"pug"` și `"hugs"`, pentru un total de 20 de ori în vocabular. + +Astfel, prima regulă de merge învățată de tokenizer este `("u", "g") -> "ug"`, ceea ce înseamnă că `"ug"` va fi adăugat la vocabular, iar perechea ar trebui să fie merged în toate cuvintele din corpus. La sfârșitul acestei etape, vocabularul și corpus-ul arată astfel: + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug"] +Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5) +``` + +Acum avem câteva perechi care rezultă într-un token mai lung de două caractere: perechea `("h", "ug")`, de exemplu (prezentă de 15 ori în corpus). Cea mai frecventă pereche în această etapă este `("u", "n")`, prezentă însă de 16 ori în corpus, astfel încât a doua regulă de îmbinare învățată este `("u", "n") -> "un"`. Adăugarea acestei reguli la vocabular și mergingul tuturor aparițiilor existente ne conduce la: + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"] +Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5) +``` + +Acum, cea mai frecventă pereche este `("h", "ug")`, așa că învățăm regula de erge `("h", "ug") -> "hug"`, care ne oferă primul nostru simbol din trei litere. După fuzionare, corpusul arată astfel: + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"] +Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5) +``` + +Și continuăm astfel până când ajungem la dimensiunea dorită a vocabularului. + + + +✏️ **Acum e rândul tău!** Care crezi că va fi următoarea regulă de fuziune? + + + +## Algoritmul de tokenizare[[tokenization-algorithm]] + +Tokenizarea urmează îndeaproape procesul de antrenare, în sensul că noile inputuri sunt tokenizate prin aplicarea următoarelor etape: + +1. Normalizare +2. Pre-tokenizare +3. Divizarea cuvintelor în caractere individuale +4. Aplicarea regulilor de merge învățate în ordine asupra acestor împărțiri + +Să luăm exemplul pe care l-am folosit în timpul antrenamentului, cu cele trei reguli de merge învățate: + +``` +("u", "g") -> "ug" +("u", "n") -> "un" +("h", "ug") -> "hug" +``` + +Cuvântul `"bug"` va fi tokenizat ca `["b", "ug"]`. Cu toate acestea, cuvântul `"mug"` va fi tokenizat ca `["[UNK]", "ug"]` deoarece litera `"m"` nu a fost în vocabularul de bază. De asemenea, cuvântul `"thug"` va fi tokenizat ca `["[UNK]", "hug"]`: litera `"t"` nu se află în vocabularul de bază, iar aplicarea regulilor de merge duce mai întâi la fuzionarea lui `"u"` și `"g"` și apoi la fuzionarea lui `"h"` și `"ug"`. + + + +✏️ **Acum e rândul tău!** Cum crezi că va fi tokenizat cuvântul `"unhug"`? + + + +## Implementarea BPE[[implementing-bpe]] + +Acum să aruncăm o privire la implementarea algoritmului BPE. Aceasta nu va fi o versiune optimizată pe care o puteți utiliza pe un corpus mare; dorim doar să vă arătăm codul pentru a putea înțelege algoritmul puțin mai bine. + +În primul rând avem nevoie de un corpus, așa că haideți să creăm unul simplu cu câteva propoziții: + +```python +corpus = [ + "This is the Hugging Face Course.", + "This chapter is about tokenization.", + "This section shows several tokenizer algorithms.", + "Hopefully, you will be able to understand how they are trained and generate tokens.", +] +``` + +Apoi, trebuie să pre-tokenizăm acest corpus în cuvinte. Deoarece replicăm un tokenizator BPE (precum GPT-2), vom utiliza tokenizatorul `gpt2` pentru pre-tokenizare: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Apoi, calculăm frecvențele fiecărui cuvânt din corpus la fel ca în cazul pre-tokenizării: + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) + +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +print(word_freqs) +``` + +```python out +defaultdict(int, {'This': 3, 'Ġis': 2, 'Ġthe': 1, 'ĠHugging': 1, 'ĠFace': 1, 'ĠCourse': 1, '.': 4, 'Ġchapter': 1, + 'Ġabout': 1, 'Ġtokenization': 1, 'Ġsection': 1, 'Ġshows': 1, 'Ġseveral': 1, 'Ġtokenizer': 1, 'Ġalgorithms': 1, + 'Hopefully': 1, ',': 1, 'Ġyou': 1, 'Ġwill': 1, 'Ġbe': 1, 'Ġable': 1, 'Ġto': 1, 'Ġunderstand': 1, 'Ġhow': 1, + 'Ġthey': 1, 'Ġare': 1, 'Ġtrained': 1, 'Ġand': 1, 'Ġgenerate': 1, 'Ġtokens': 1}) +``` + +Următorul pas este calcularea vocabularului de bază, format din toate caracterele utilizate în corpus: + +```python +alphabet = [] + +for word in word_freqs.keys(): + for letter in word: + if letter not in alphabet: + alphabet.append(letter) +alphabet.sort() + +print(alphabet) +``` + +```python out +[ ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', + 't', 'u', 'v', 'w', 'y', 'z', 'Ġ'] +``` + +De asemenea, adăugăm tokenurile speciale utilizate de model la începutul vocabularului respectiv. În cazul GPT-2, singurul simbol special este `"<|endoftext|>"`: + +```python +vocab = ["<|endoftext|>"] + alphabet.copy() +``` + +Acum trebuie să împărțim fiecare cuvânt în caractere individuale, pentru a putea începe antrenarea: + +```python +splits = {word: [c for c in word] for word in word_freqs.keys()} +``` + +Acum că suntem pregătiți pentru antrenare, să scriem o funcție care calculează frecvența fiecărei perechi. Va trebui să folosim această funcție la fiecare etapă a antrenare: + +```python +def compute_pair_freqs(splits): + pair_freqs = defaultdict(int) + for word, freq in word_freqs.items(): + split = splits[word] + if len(split) == 1: + continue + for i in range(len(split) - 1): + pair = (split[i], split[i + 1]) + pair_freqs[pair] += freq + return pair_freqs +``` + +Să aruncăm o privire la o parte din acest dicționar după separările inițiale: + +```python +pair_freqs = compute_pair_freqs(splits) + +for i, key in enumerate(pair_freqs.keys()): + print(f"{key}: {pair_freqs[key]}") + if i >= 5: + break +``` + +```python out +('T', 'h'): 3 +('h', 'i'): 3 +('i', 's'): 5 +('Ġ', 'i'): 2 +('Ġ', 't'): 7 +('t', 'h'): 3 +``` + +Acum, pentru a găsi cea mai frecventă pereche este nevoie doar de o loop rapid: + +```python +best_pair = "" +max_freq = None + +for pair, freq in pair_freqs.items(): + if max_freq is None or max_freq < freq: + best_pair = pair + max_freq = freq + +print(best_pair, max_freq) +``` + +```python out +('Ġ', 't') 7 +``` + +Așadar, prima îmbinare care trebuie învățată este `('Ġ', 't') -> 'Ġt'`, și adăugăm `'Ġt'` la vocabular: + +```python +merges = {("Ġ", "t"): "Ġt"} +vocab.append("Ġt") +``` + +Pentru a continua, trebuie să aplicăm aceast merge în dicționarul nostru `splits`. Să scriem o altă funcție pentru acest lucru: + +```python +def merge_pair(a, b, splits): + for word in word_freqs: + split = splits[word] + if len(split) == 1: + continue + + i = 0 + while i < len(split) - 1: + if split[i] == a and split[i + 1] == b: + split = split[:i] + [a + b] + split[i + 2 :] + else: + i += 1 + splits[word] = split + return splits +``` + +Și putem arunca o privire la rezultatul primului merge: + +```py +splits = merge_pair("Ġ", "t", splits) +print(splits["Ġtrained"]) +``` + +```python out +['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] +``` + +Acum avem tot ce ne trebuie pentru a face bucle până când vom învăța toate mergeu-rile dorite. Ne focusăm pe o mărime a vocabularui de 50: + +```python +vocab_size = 50 + +while len(vocab) < vocab_size: + pair_freqs = compute_pair_freqs(splits) + best_pair = "" + max_freq = None + for pair, freq in pair_freqs.items(): + if max_freq is None or max_freq < freq: + best_pair = pair + max_freq = freq + splits = merge_pair(*best_pair, splits) + merges[best_pair] = best_pair[0] + best_pair[1] + vocab.append(best_pair[0] + best_pair[1]) +``` + +Ca rezultat, am învățat 19 reguli de merge(vocabularul inițial avea o dimensiune de 31 -- 30 de caractere din alfabet, plus simbolul special): + +```py +print(merges) +``` + +```python out +{('Ġ', 't'): 'Ġt', ('i', 's'): 'is', ('e', 'r'): 'er', ('Ġ', 'a'): 'Ġa', ('Ġt', 'o'): 'Ġto', ('e', 'n'): 'en', + ('T', 'h'): 'Th', ('Th', 'is'): 'This', ('o', 'u'): 'ou', ('s', 'e'): 'se', ('Ġto', 'k'): 'Ġtok', + ('Ġtok', 'en'): 'Ġtoken', ('n', 'd'): 'nd', ('Ġ', 'is'): 'Ġis', ('Ġt', 'h'): 'Ġth', ('Ġth', 'e'): 'Ġthe', + ('i', 'n'): 'in', ('Ġa', 'b'): 'Ġab', ('Ġtoken', 'i'): 'Ġtokeni'} +``` + +Iar vocabularul este compus din simbolul special, alfabetul inițial și toate rezultatele merge-urilor: + +```py +print(vocab) +``` + +```python out +['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', + 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'Ġ', 'Ġt', 'is', 'er', 'Ġa', 'Ġto', 'en', 'Th', 'This', 'ou', 'se', + 'Ġtok', 'Ġtoken', 'nd', 'Ġis', 'Ġth', 'Ġthe', 'in', 'Ġab', 'Ġtokeni'] +``` + + + +💡 Folosind `train_new_from_iterator()` pe același corpus nu va rezulta exact același vocabular. Acest lucru se datorează faptului că atunci când există o alegere a celei mai frecvente perechi, am selectat-o pe prima întâlnită, în timp ce biblioteca 🤗 Tokenizers o selectează pe prima pe baza ID-urilor sale interne. + + + +Pentru a tokeniza un text nou, îl pre-tokenizăm, îl împărțim, apoi aplicăm toate regulile de merge învățate: + +```python +def tokenize(text): + pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in pre_tokenize_result] + splits = [[l for l in word] for word in pre_tokenized_text] + for pair, merge in merges.items(): + for idx, split in enumerate(splits): + i = 0 + while i < len(split) - 1: + if split[i] == pair[0] and split[i + 1] == pair[1]: + split = split[:i] + [merge] + split[i + 2 :] + else: + i += 1 + splits[idx] = split + + return sum(splits, []) +``` + +Putem încerca acest lucru pe orice text compus din caractere din alfabet: + +```py +tokenize("This is not a token.") +``` + +```python out +['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.'] +``` + + + +⚠️ Implementarea noastră va arunca o eroare dacă există un caracter necunoscut, deoarece nu am făcut nimic pentru a le gestiona. GPT-2 nu are de fapt un token necunoscut (este imposibil să obțineți un caracter necunoscut atunci când utilizați BPE la nivel de bytes), dar acest lucru s-ar putea întâmpla aici deoarece nu am inclus toate byte-urile posibile în vocabularul inițial. Acest aspect al BPE depășește domeniul de aplicare al acestei secțiuni, așa că am omis detaliile. + + + +Asta e tot pentru algoritmul BPE! În continuare, ne vom uita la WordPiece. \ No newline at end of file diff --git a/chapters/rum/chapter6/6.mdx b/chapters/rum/chapter6/6.mdx new file mode 100644 index 000000000..ffa06beff --- /dev/null +++ b/chapters/rum/chapter6/6.mdx @@ -0,0 +1,375 @@ +# Tokenizarea WordPiece[[wordpiece-tokenization]] + + + +WordPiece este algoritmul de tokenizare dezvoltat de Google pentru preantrenarea BERT. De atunci, acesta a fost reutilizat în numeroase modele Transformers bazate pe BERT, cum ar fi DistilBERT, MobileBERT, Funnel Transformers și MPNET. Este foarte similar cu BPE în ceea ce privește antrenarea, dar tokenizarea efectivă se face diferit. + + + + + +💡 Această secțiune acoperă WordPiece în profunzime, mergând până la prezentarea unei implementări complete. Puteți sări la sfârșit dacă doriți doar o prezentare generală a algoritmului de tokenizare. + + + +## Algoritmul de antrenare[[training-algorithm]] + + + +⚠️ Google nu a publicat niciodată implementarea algoritmului de formare a WordPiece, astfel încât ceea ce urmează este cea mai bună presupunere a noastră bazată pe literatura publicată. Este posibil să nu fie 100% exactă. + + + +La fel ca BPE, WordPiece pornește de la un vocabular restrâns care include simbolurile speciale utilizate de model și alfabetul inițial. Deoarece identifică subcuvinte prin adăugarea unui prefix (cum ar fi `##` pentru BERT), fiecare cuvânt este inițial împărțit prin adăugarea prefixului respectiv la toate caracterele din cuvânt. Astfel, de exemplu, `"word"` este împărțit astfel: + +``` +w ##o ##r ##d +``` + +Astfel, alfabetul inițial conține toate caracterele prezente la începutul unui cuvânt și caracterele prezente în interiorul unui cuvânt cu prefixul WordPiece. + +Apoi, la fel ca BPE, WordPiece învață reguli de merge. Principala diferență este modul în care este selectată perechea care urmează să fie merged. În loc să selecteze cea mai frecventă pereche, WordPiece calculează un scor pentru fiecare pereche, utilizând următoarea formulă: + +$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ + +Împărțind frecvența perechii la produsul frecvențelor fiecărei părți a acesteia, algoritmul prioritizează fuzionarea perechilor în care părțile individuale sunt mai puțin frecvente în vocabular. De exemplu, nu va fuziona neapărat `("un", "##able")` chiar dacă această pereche apare foarte frecvent în vocabular, deoarece cele două perechi `"un"` și `"##able"` vor apărea probabil fiecare într-o mulțime de alte cuvinte și vor avea o frecvență ridicată. În schimb, o pereche precum `("hu", "##gging")` va fi probabil fuzionată mai repede (presupunând că cuvântul "hugging" apare frecvent în vocabular), deoarece `"hu"` și `"##gging"` sunt probabil mai puțin frecvente individual. + +Să ne uităm la același vocabular pe care l-am folosit în exemplul de antrenare BPE: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +Spliturile aici vor fi: + +``` +("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) +``` + +deci vocabularul inițial va fi `["b", "h", "p", "##g", "##n", "##s", "##u"]` (dacă uităm deocamdată de tokenurile speciale). Cea mai frecventă pereche este `("##u", "##g")` (prezentă de 20 de ori), dar frecvența individuală a lui `"##u"` este foarte mare, astfel încât scorul său nu este cel mai mare (este 1 / 36). Toate perechile cu un `"##u"` au de fapt același scor (1 / 36), astfel încât cel mai bun scor revine perechii `("##g", "##s")` - singura fără un `"##u"` - cu 1 / 20, iar prima îmbinare învățată este `("##g", "##s") -> ("##gs")`. + +Rețineți că atunci când facem merge, eliminăm `##` dintre cele două tokenuri, deci adăugăm `"##gs"` la vocabular și aplicăm merge în cuvintele din corpus: + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"] +Corpus: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5) +``` + +În acest moment, `"##u"` se află în toate perechile posibile, deci toate au același scor. Să spunem că în acest caz, prima pereche este merged, deci `("h", "##u") -> "hu"`. Acest lucru ne duce la: + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu"] +Corpus: ("hu" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) +``` + +Apoi, următorul scor cu cel mai bun rezultat este împărțit de `("hu", "##g")` și `("hu", "##gs")` (cu 1/15, comparativ cu 1/21 pentru toate celelalte perechi), astfel încât prima pereche cu cel mai mare scor este merged + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"] +Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) +``` + +și continuăm astfel până când ajungem la dimensiunea dorită a vocabularului. + + + +✏️ **Acum e rândul tău!** Care va fi următoarea regulă de merge? + + + +## Algoritm de tokenizare[[tokenization-algorithm]] + +Tokenizarea diferă în WordPiece și BPE prin faptul că WordPiece salvează doar vocabularul final, nu și regulile de merge învățate. Pornind de la cuvântul de tokenizat, WordPiece găsește cel mai lung subcuvânt care se află în vocabular, apoi îl împarte. De exemplu, dacă folosim vocabularul învățat în exemplul de mai sus, pentru cuvântul `"hugs"` cel mai lung subcuvânt de la început care se află în vocabular este `"hug"`, așa că împărțim acolo și obținem `["hug", "##s"]`. Apoi continuăm cu `"##s"`, care se află în vocabular, deci tokenizarea lui `"hugs"` este `["hug", "##s"]`. + +Cu BPE, am fi aplicat mergeurile învățate în ordine și am fi tokenizat acest lucru ca `["hu", "##gs"]`, deci encodingul este diferit. + +Ca un alt exemplu, să vedem cum ar fi tokenizat cuvântul `"bugs"`. `"b"` este cel mai lung subcuvânt care începe de la începutul cuvântului care se află în vocabular, așa că îl împărțim acolo și obținem `["b", "##ugs"]`. Apoi, `"##u"` este cel mai lung subcuvânt care începe de la începutul cuvântului `"##ugs"` care se află în vocabular, deci îl separăm și obținem `["b", "##u, "##gs"]`. În cele din urmă, `"##gs"` se află în vocabular, deci această ultimă listă este tokenizarea lui `"bugs"`. + +Atunci când tokenizarea ajunge într-un stadiu în care nu este posibilă găsirea unui subcuvânt în vocabular, întregul cuvânt este tokenizat ca necunoscut - astfel, de exemplu, `"mug"` ar fi tokenizat ca `["[UNK]"]`, la fel ca `"bum"` (chiar dacă putem începe cu `"b"` și `"##u"`, `"##m"` nu face parte din vocabular, iar tokenizarea rezultată va fi `["[UNK]"]`, nu `["b", "##u", "[UNK]"]`). Aceasta este o altă diferență față de BPE, care ar clasifica doar caracterele individuale care nu se află în vocabular ca necunoscute. + + + +✏️ **Acum e rândul tău!** Cum va fi tokenizat cuvântul `"pugs"`? + + + +## Implementând WordPiece[[implementing-wordpiece]] + +Acum să aruncăm o privire la o implementare a algoritmului WordPiece. La fel ca în cazul BPE, acest lucru este doar pedagogic și nu veți putea să îl utilizați pe un corpus mare. + +Vom utiliza același corpus ca în exemplul BPE: + +```python +corpus = [ + "This is the Hugging Face Course.", + "This chapter is about tokenization.", + "This section shows several tokenizer algorithms.", + "Hopefully, you will be able to understand how they are trained and generate tokens.", +] +``` + +În primul rând, trebuie să pre-tokenizăm corpusul în cuvinte. Deoarece replicăm un tokenizator WordPiece (precum BERT), vom utiliza tokenizatorul `bert-base-cased` pentru pre-tokenizare: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Apoi, calculăm frecvențele fiecărui cuvânt din corpus la fel ca în cazul pre-tokenizării: + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +word_freqs +``` + +```python out +defaultdict( + int, {'This': 3, 'is': 2, 'the': 1, 'Hugging': 1, 'Face': 1, 'Course': 1, '.': 4, 'chapter': 1, 'about': 1, + 'tokenization': 1, 'section': 1, 'shows': 1, 'several': 1, 'tokenizer': 1, 'algorithms': 1, 'Hopefully': 1, + ',': 1, 'you': 1, 'will': 1, 'be': 1, 'able': 1, 'to': 1, 'understand': 1, 'how': 1, 'they': 1, 'are': 1, + 'trained': 1, 'and': 1, 'generate': 1, 'tokens': 1}) +``` + +După cum am văzut mai devreme, alfabetul este setul unic compus din toate primele litere ale cuvintelor și toate celelalte litere care apar în cuvintele cu prefixul `##`: + +```python +alphabet = [] +for word in word_freqs.keys(): + if word[0] not in alphabet: + alphabet.append(word[0]) + for letter in word[1:]: + if f"##{letter}" not in alphabet: + alphabet.append(f"##{letter}") + +alphabet.sort() +alphabet + +print(alphabet) +``` + +```python out +['##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', '##l', '##m', '##n', '##o', '##p', '##r', '##s', + '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', + 'w', 'y'] +``` + +De asemenea, adăugăm simbolurile speciale utilizate de model la începutul vocabularului respectiv. În cazul BERT, este vorba de lista `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]``: + +```python +vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() +``` + +Apoi trebuie să împărțim fiecare cuvânt, cu toate literele care nu sunt primele cu prefixul `##`: + +```python +splits = { + word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)] + for word in word_freqs.keys() +} +``` + +Acum că suntem pregătiți pentru antrenare, să scriem o funcție care calculează scorul fiecărei perechi. Va trebui să folosim această funcție la fiecare etapă a antrenării: + +```python +def compute_pair_scores(splits): + letter_freqs = defaultdict(int) + pair_freqs = defaultdict(int) + for word, freq in word_freqs.items(): + split = splits[word] + if len(split) == 1: + letter_freqs[split[0]] += freq + continue + for i in range(len(split) - 1): + pair = (split[i], split[i + 1]) + letter_freqs[split[i]] += freq + pair_freqs[pair] += freq + letter_freqs[split[-1]] += freq + + scores = { + pair: freq / (letter_freqs[pair[0]] * letter_freqs[pair[1]]) + for pair, freq in pair_freqs.items() + } + return scores +``` + +Să aruncăm o privire la o parte din acest dicționar după separările inițiale: + +```python +pair_scores = compute_pair_scores(splits) +for i, key in enumerate(pair_scores.keys()): + print(f"{key}: {pair_scores[key]}") + if i >= 5: + break +``` + +```python out +('T', '##h'): 0.125 +('##h', '##i'): 0.03409090909090909 +('##i', '##s'): 0.02727272727272727 +('i', '##s'): 0.1 +('t', '##h'): 0.03571428571428571 +('##h', '##e'): 0.011904761904761904 +``` + +Acum, pentru a găsi perechea cu cel mai bun scor este nevoie doar de un loop rapid: + +```python +best_pair = "" +max_score = None +for pair, score in pair_scores.items(): + if max_score is None or max_score < score: + best_pair = pair + max_score = score + +print(best_pair, max_score) +``` + +```python out +('a', '##b') 0.2 +``` + +Așadar, primul merge de învățat este `('a', '##b') -> 'ab'`, iar noi adăugăm `'ab'` la vocabular: + +```python +vocab.append("ab") +``` + +Pentru a continua, trebuie să aplicăm acest merge în dicționarul nostru `splits`. Să scriem o altă funcție pentru acest lucru: + +```python +def merge_pair(a, b, splits): + for word in word_freqs: + split = splits[word] + if len(split) == 1: + continue + i = 0 + while i < len(split) - 1: + if split[i] == a and split[i + 1] == b: + merge = a + b[2:] if b.startswith("##") else a + b + split = split[:i] + [merge] + split[i + 2 :] + else: + i += 1 + splits[word] = split + return splits +``` + +Și putem arunca o privire la rezultatul primului merge: + +```py +splits = merge_pair("a", "##b", splits) +splits["about"] +``` + +```python out +['ab', '##o', '##u', '##t'] +``` + +Acum avem tot ce ne trebuie pentru a face bucle până când vom învăța toate merge-urile dorite. Să ne propunem un vocabular de mărimea 70: + +```python +vocab_size = 70 +while len(vocab) < vocab_size: + scores = compute_pair_scores(splits) + best_pair, max_score = "", None + for pair, score in scores.items(): + if max_score is None or max_score < score: + best_pair = pair + max_score = score + splits = merge_pair(*best_pair, splits) + new_token = ( + best_pair[0] + best_pair[1][2:] + if best_pair[1].startswith("##") + else best_pair[0] + best_pair[1] + ) + vocab.append(new_token) +``` + +Ne putem uita apoi la vocabularul generat: + +```py +print(vocab) +``` + +```python out +['[PAD]', '[UNK]', '[CLS]', '[SEP]', '[MASK]', '##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', + '##l', '##m', '##n', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', + 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', 'w', 'y', 'ab', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully', + 'Th', 'ch', '##hm', 'cha', 'chap', 'chapt', '##thm', 'Hu', 'Hug', 'Hugg', 'sh', 'th', 'is', '##thms', '##za', '##zat', + '##ut'] +``` + +După cum putem vedea, în comparație cu BPE, acest tokenizator învață părțile din cuvinte ca tokenuri puțin mai repede. + + + +💡 Folosind `train_new_from_iterator()` pe același corpus nu va rezulta exact același vocabular. Acest lucru se datorează faptului că biblioteca 🤗 Tokenizers nu implementează WordPiece pentru antrenare (deoarece nu suntem complet siguri cum funcționează intern), ci utilizează BPE în schimb. + + + +Pentru a tokeniza un text nou, îl pre-tokenizăm, îl împărțim, apoi aplicăm algoritmul de tokenizare pe fiecare cuvânt. Adică, căutăm cel mai mare subcuvânt începând de la începutul primului cuvânt și îl împărțim, apoi repetăm procesul pentru a doua parte și așa mai departe pentru restul acelui cuvânt și pentru următoarele cuvinte din text: + +```python +def encode_word(word): + tokens = [] + while len(word) > 0: + i = len(word) + while i > 0 and word[:i] not in vocab: + i -= 1 + if i == 0: + return ["[UNK]"] + tokens.append(word[:i]) + word = word[i:] + if len(word) > 0: + word = f"##{word}" + return tokens +``` + +Haideți să-l testăm pe un cuvânt care se află în vocabular și pe altul care nu se află: + + +```python +print(encode_word("Hugging")) +print(encode_word("HOgging")) +``` + +```python out +['Hugg', '##i', '##n', '##g'] +['[UNK]'] +``` + +Acum, scriem o funcție care tokenizează un text: + +```python +def tokenize(text): + pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in pre_tokenize_result] + encoded_words = [encode_word(word) for word in pre_tokenized_text] + return sum(encoded_words, []) +``` + +Îl putem încerca pe orice text: + +```python +tokenize("This is the Hugging Face course!") +``` + +```python out +['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s', + '##e', '[UNK]'] +``` + +Asta e tot pentru algoritmul WordPiece! Acum să aruncăm o privire la Unigram. diff --git a/chapters/rum/chapter6/7.mdx b/chapters/rum/chapter6/7.mdx new file mode 100644 index 000000000..3bc245c67 --- /dev/null +++ b/chapters/rum/chapter6/7.mdx @@ -0,0 +1,381 @@ +# Tokenizarea Unigram[[unigram-tokenization]] + + + +Algoritmul Unigram este adesea utilizat în SentencePiece, care este algoritmul de tokenizare utilizat de modele precum AlBERT, T5, mBART, Big Bird și XLNet. + + + + + +💡 Această secțiune acoperă Unigram în profunzime, mergând până la prezentarea unei implementări complete. Puteți sări la sfârșit dacă doriți doar o prezentare generală a algoritmului de tokenizare. + + + +## Algoritm de antrenare[[training-algorithm]] + +În comparație cu BPE și WordPiece, Unigram lucrează în cealaltă direcție: pornește de la un vocabular mare și elimină tokeni din acesta până când ajunge la dimensiunea dorită. Există mai multe opțiuni pentru a construi acel vocabular de bază: putem lua, de exemplu, cele mai comune substrings din cuvintele pre-tokenizate sau putem aplica BPE pe corpusul inițial cu o dimensiune mare a vocabularului. + +La fiecare etapă a antrenării, algoritmul Unigram calculează o pierdere pe corpus oferit, având în vedere vocabularul curent. Apoi, pentru fiecare simbol din vocabular, algoritmul calculează cu cât ar crește pierderea globală dacă simbolul ar fi eliminat și caută simbolurile care ar crește cel mai puțin pierderea. Aceste simboluri au cel mai redus efect asupra pierderii globale din corpus, deci, într-un fel, sunt "mai puțin necesare" și sunt cei mai buni candidați pentru eliminare. + +Aceasta este o operațiune foarte costisitoare, așa că nu eliminăm doar simbolul asociat cu cea mai mică creștere a pierderii, ci procentul \\(p\\) (\\(p\\) fiind un hyperparametru pe care îl poți controla, de obicei 10 sau 20) din simbolurile asociate cu cea mai mică creștere a pierderilor. Acest proces este se repetă până când vocabularul atinge dimensiunea dorită. + +Rețineți că nu eliminăm niciodată caracterele de bază, pentru a ne asigura că orice cuvânt poate fi tokenizat. + +Acum, acest lucru este încă puțin vag: partea principală a algoritmului este de a calcula o pierdere asupra corpusului și de a vedea cum se schimbă atunci când eliminăm unele tokenuri din vocabular, dar nu am explicat încă cum să facem acest lucru. Acest pas se bazează pe algoritmul de tokenizare al unui model Unigram, așa că îl vom analiza în continuare. + +Vom reutiliza corpusul din exemplele anterioare: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +iar pentru acest exemplu, vom lua toate substringurile stricte pentru vocabularul inițial: + +``` +["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] +``` + +## Algoritm de tokenizare[[tokenization-algorithm]] + +Un model Unigram este un tip de model lingvistic care consideră că fiecare token este independent de tokenii anteriori. Este cel mai simplu model lingvistic, în sensul că probabilitatea simbolului X având în vedere contextul anterior este doar probabilitatea simbolului X. Astfel, dacă am utiliza un model lingvistic Unigram pentru a genera text, am prezice întotdeauna simbolul cel mai frecvent. + +Probabilitatea unui token dat este frecvența sa (numărul de ori în care îl găsim) în corpusul original, împărțită la suma tuturor aparițiilor tuturor tokenilor din vocabular (pentru a ne asigura că probabilitățile sunt egale cu 1). De exemplu, `"ug"` este prezent în `"hug"`, `"pug"`, și `"hugs"`, deci are o frecvență de 20 în corpusul nostru. + +Iată frecvențele tuturor subcuvintelor posibile din vocabular: + +``` +("h", 15) ("u", 36) ("g", 20) ("hu", 15) ("ug", 20) ("p", 17) ("pu", 17) ("n", 16) +("un", 16) ("b", 4) ("bu", 4) ("s", 5) ("hug", 15) ("gs", 5) ("ugs", 5) +``` + +Astfel, suma tuturor frecvențelor este 210, iar probabilitatea subcuvântului `"ug"` este 20/210. + + + +✏️ **Acum este rândul tău!** Scrie codul pentru a calcula frecvențele de mai sus și verifică de două ori dacă rezultatele afișate sunt corecte, precum și suma totală. + + + +Acum, pentru a tokeniza un cuvânt dat, ne uităm la toate segmentările posibile în tokeni și calculăm probabilitatea fiecăruia în conformitate cu modelul Unigram. Deoarece toate token-urile sunt considerate independente, această probabilitate este doar produsul probabilității fiecărui token. De exemplu, tokenizarea `["p", "u", "g"]` a lui `"pug"` are probabilitatea: + +$$P([``p", ``u", ``g"]) = P(``p") \times P(``u") \times P(``g") = \frac{5}{210} \times \frac{36}{210} \times \frac{20}{210} = 0.000389$$ + +Comparativ, tokenizarea `["pu", "g"]` are probabilitatea: + +$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$ + +astfel încât una este mult mai probabilă decât alta. În general, tokenizările cu cei mai puțini tokeni posibili vor avea cea mai mare probabilitate (din cauza acelei împărțiri la 210 repetată pentru fiecare token), ceea ce corespunde cu ceea ce dorim intuitiv: să împărțim un cuvânt în cel mai mic număr de tokenuri posibil. + +Tokenizarea unui cuvânt cu modelul Unigram este atunci tokenizarea cu cea mai mare probabilitate. În exemplul `"pug"`, iată probabilitățile pe care le-am obține pentru fiecare segmentare posibilă: + +``` +["p", "u", "g"] : 0.000389 +["p", "ug"] : 0.0022676 +["pu", "g"] : 0.0022676 +``` + +Astfel, `"pug"` ar fi tokenizat ca `["p", "ug"]` sau `["pu", "g"]`, în funcție de care dintre aceste segmentări este întâlnită prima (rețineți că într-un corpus mai mare, cazurile de egalitate ca acesta vor fi rare). + +În acest caz, a fost ușor să găsim toate segmentările posibile și să le calculăm probabilitățile, dar în general va fi puțin mai greu. Există un algoritm clasic utilizat pentru acest lucru, numit *algoritmul Viterbi*. În esență, putem construi un grafic pentru a detecta segmentările posibile ale unui cuvânt dat, spunând că există o ramură de la caracterul _a_ la caracterul _b_ dacă subcuvântul de la _a_ la _b_ se află în vocabular, și atribuind ramurii respective probabilitatea subcuvântului. + +Pentru a găsi calea din acest grafic care va avea cel mai bun scor, algoritmul Viterbi determină, pentru fiecare poziție din cuvânt, segmentarea cu cel mai bun scor care se termină la poziția respectivă. Deoarece mergem de la început la sfârșit, cel mai bun scor poate fi găsit prin parcurgerea în buclă a tuturor subcuvintelor care se termină la poziția curentă și apoi folosind cel mai bun scor de tokenizare de la poziția la care începe acest subcuvânt. Apoi, trebuie doar să derulăm calea parcursă pentru a ajunge la sfârșit. + +Să aruncăm o privire la un exemplu folosind vocabularul nostru și cuvântul `"unhug"`. Pentru fiecare poziție, subcuvintele cu cele mai bune scoruri care se termină acolo sunt următoarele: + +``` +Character 0 (u): "u" (score 0.171429) +Character 1 (n): "un" (score 0.076191) +Character 2 (h): "un" "h" (score 0.005442) +Character 3 (u): "un" "hu" (score 0.005442) +Character 4 (g): "un" "hug" (score 0.005442) +``` + +Astfel, `"unhug"` ar fi tokenizat ca `["un", "hug"]`. + + + +✏️ **Acum e rândul tău!** Determinați tokenizarea cuvântului `"huggun"` și scorul acestuia. + + + +## Înapoi la antrenare[[back-to-training]] + +Acum că am văzut cum funcționează tokenizarea, putem analiza mai în profunzime pierderea utilizată în timpul antrenării. În orice etapă dată, această pierdere este calculată prin tokenizarea fiecărui cuvânt din corpus, utilizând vocabularul curent și modelul Unigram determinat de frecvențele fiecărui token din corpus (după cum am văzut mai devreme). + +Fiecare cuvânt din corpus are un scor, iar pierderea este negative log likelihood a acestor scoruri - adică suma pentru toate cuvintele din corpus a tuturor `-log(P(word))`. + +Să ne întoarcem la exemplul nostru cu următorul corpus: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +Tokenizarea fiecărui cuvânt cu scorurile lor respective este: + +``` +"hug": ["hug"] (score 0.071428) +"pug": ["pu", "g"] (score 0.007710) +"pun": ["pu", "n"] (score 0.006168) +"bun": ["bu", "n"] (score 0.001451) +"hugs": ["hug", "s"] (score 0.001701) +``` + +Deci, pierderea este: + +``` +10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 +``` + +Acum trebuie să calculăm modul în care eliminarea fiecărui token afectează pierderea. Acest lucru este destul de plictisitor, așa că îl vom face doar pentru doi tokeni aici și vom păstra întregul proces pentru atunci când vom avea cod care să ne ajute. În acest caz (foarte) special, aveam două tokenizări echivalente ale tuturor cuvintelor: după cum am văzut mai devreme, de exemplu, `"pug"` ar putea fi tokenizat `["p", "ug"]` cu același scor. Astfel, eliminarea simbolului `"pu"` din vocabular va produce exact aceeași pierdere. + +Pe de altă parte, eliminarea lui `"hug"` va agrava pierderea, deoarece tokenizarea lui `"hug"` și `"hugs"` va deveni: + +``` +"hug": ["hu", "g"] (score 0.006802) +"hugs": ["hu", "gs"] (score 0.001701) +``` + +Aceste modificări vor determina creșterea pierderii cu: + +``` +- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 +``` + +Prin urmare, tokenul `"pu"` va fi probabil eliminat din vocabular, dar nu și `"hug"`. + +## Implementarea Unigram[[implementarea-unigram]] + +Acum să implementăm în cod tot ceea ce am văzut până acum. Ca și în cazul BPE și WordPiece, aceasta nu este o implementare eficientă a algoritmului Unigram (dimpotrivă), dar ar trebui să vă ajute să-l înțelegeți puțin mai bine. + +Vom folosi ca exemplu același corpus ca și până acum: + +```python +corpus = [ + "This is the Hugging Face Course.", + "This chapter is about tokenization.", + "This section shows several tokenizer algorithms.", + "Hopefully, you will be able to understand how they are trained and generate tokens.", +] +``` + +De data aceasta, vom folosi `xlnet-base-cased` ca modelul nostru: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") +``` + +Ca și pentru BPE și WordPiece, începem prin a număra numărul de apariții ale fiecărui cuvânt în corpus: + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +word_freqs +``` + +Apoi, trebuie să inițializăm vocabularul nostru la ceva mai mare decât dimensiunea vocabularului pe care o vom dori la final. Trebuie să includem toate caracterele de bază (altfel nu vom putea tokeniza fiecare cuvânt), dar pentru substringurile mai mari le vom păstra doar pe cele mai comune, așa că le vom sorta după frecvență: + +```python +char_freqs = defaultdict(int) +subwords_freqs = defaultdict(int) +for word, freq in word_freqs.items(): + for i in range(len(word)): + char_freqs[word[i]] += freq + # Loop through the subwords of length at least 2 + for j in range(i + 2, len(word) + 1): + subwords_freqs[word[i:j]] += freq + +# Sortarea subcuvintelor după frecvență +sorted_subwords = sorted(subwords_freqs.items(), key=lambda x: x[1], reverse=True) +sorted_subwords[:10] +``` + +```python out +[('▁t', 7), ('is', 5), ('er', 5), ('▁a', 5), ('▁to', 4), ('to', 4), ('en', 4), ('▁T', 3), ('▁Th', 3), ('▁Thi', 3)] +``` + +Grupăm caracterele cu cele mai bune subcuvinte pentru a ajunge la un vocabular inițial de dimensiunea 300: + +```python +token_freqs = list(char_freqs.items()) + sorted_subwords[: 300 - len(char_freqs)] +token_freqs = {token: freq for token, freq in token_freqs} +``` + + + +💡 SentencePiece utilizează un algoritm mai eficient numit Enhanced Suffix Array (ESA) pentru a crea vocabularul inițial. + + + +În continuare, calculăm suma tuturor frecvențelor, pentru a converti frecvențele în probabilități. Pentru modelul nostru, vom stoca logaritmii probabilităților, deoarece este mai stabil din punct de vedere numeric să adăugăm logaritmi decât să multiplicăm numere mici, iar acest lucru va simplifica calcularea pierderii modelului: + +```python +from math import log + +total_sum = sum([freq for token, freq in token_freqs.items()]) +model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} +``` + +Acum funcția principală este cea care tokenizează cuvintele folosind algoritmul Viterbi. După cum am văzut mai devreme, acest algoritm calculează cea mai bună segmentare a fiecărui substring din cuvânt, pe care o vom stoca într-o variabilă numită `best_segmentations`. Vom stoca un dicționar pentru fiecare poziție din cuvânt (de la 0 la lungimea totală a acestuia), cu două chei: indicele de început al ultimului token din cea mai bună segmentare și scorul celei mai bune segmentări. Cu ajutorul indicelui de început al ultimului token, vom putea extrage segmentarea completă odată ce lista este complet populată. + +Popularea listei se face cu doar două bucle: bucla principală trece peste fiecare poziție de început, iar a doua buclă încearcă toate subcuvintele care încep la acea poziție de început. Dacă substringul se află în vocabular, avem o nouă segmentare a cuvântului până la acea poziție finală, pe care o comparăm cu cea din `best_segmentations`. + +Odată ce bucla principală este terminată, pornim de la sfârșit și sărim de la o poziție de început la alta, înregistrând tokenii pe parcurs, până când ajungem la începutul cuvântului: + +```python +def encode_word(word, model): + best_segmentations = [{"start": 0, "score": 1}] + [ + {"start": None, "score": None} for _ in range(len(word)) + ] + for start_idx in range(len(word)): + # This should be properly filled by the previous steps of the loop + best_score_at_start = best_segmentations[start_idx]["score"] + for end_idx in range(start_idx + 1, len(word) + 1): + token = word[start_idx:end_idx] + if token in model and best_score_at_start is not None: + score = model[token] + best_score_at_start + # If we have found a better segmentation ending at end_idx, we update + if ( + best_segmentations[end_idx]["score"] is None + or best_segmentations[end_idx]["score"] > score + ): + best_segmentations[end_idx] = {"start": start_idx, "score": score} + + segmentation = best_segmentations[-1] + if segmentation["score"] is None: + # We did not find a tokenization of the word -> unknown + return [""], None + + score = segmentation["score"] + start = segmentation["start"] + end = len(word) + tokens = [] + while start != 0: + tokens.insert(0, word[start:end]) + next_start = best_segmentations[start]["start"] + end = start + start = next_start + tokens.insert(0, word[start:end]) + return tokens, score +``` + +Putem încerca deja modelul nostru inițial pe câteva cuvinte: + +```python +print(encode_word("Hopefully", model)) +print(encode_word("This", model)) +``` + +```python out +(['H', 'o', 'p', 'e', 'f', 'u', 'll', 'y'], 41.5157494601402) +(['This'], 6.288267030694535) +``` + +Acum este ușor de calculat pierderea modelului pe corpus! + +```python +def compute_loss(model): + loss = 0 + for word, freq in word_freqs.items(): + _, word_loss = encode_word(word, model) + loss += freq * word_loss + return loss +``` + +Putem verifica dacă funcționează pe modelul pe care îl avem: + +```python +compute_loss(model) +``` + +```python out +413.10377642940875 +``` + +Nici calcularea scorurilor pentru fiecare token nu este foarte dificilă; trebuie doar să calculăm pierderea pentru modelele obținute prin ștergerea fiecărui tokeb: + +```python +import copy + + +def compute_scores(model): + scores = {} + model_loss = compute_loss(model) + for token, score in model.items(): + # We always keep tokens of length 1 + if len(token) == 1: + continue + model_without_token = copy.deepcopy(model) + _ = model_without_token.pop(token) + scores[token] = compute_loss(model_without_token) - model_loss + return scores +``` + +Îl putem încerca pe un token dat: + +```python +scores = compute_scores(model) +print(scores["ll"]) +print(scores["his"]) +``` + +Deoarece `"ll"` este folosit în tokenizarea lui `"Hopefully"`, iar eliminarea lui ne va face, probabil, să folosim tokenul `"l"` de două ori în schimb, ne așteptăm să aibă o pierdere pozitivă. `"his"` este folosit doar în interiorul cuvântului `"This"`, care este tokenizat ca el însuși, deci ne așteptăm să aibă o pierdere zero. Iată rezultatele: + +```python out +6.376412403623874 +0.0 +``` + + + +💡 Această abordare este foarte ineficientă, astfel încât SentencePiece utilizează o aproximare a pierderii modelului fără simbolul X: în loc să înceapă de la zero, înlocuiește simbolul X cu segmentarea sa în vocabularul rămas. În acest fel, toate scorurile pot fi calculate odată, în același timp cu pierderea modelului. + + + +Cu toate acestea la locul lor, ultimul lucru pe care trebuie să îl facem este să adăugăm la vocabular tokeni speciali utilizate de model, apoi să facem o buclă până când am eliminat suficienți tokeni din vocabular pentru a ajunge la dimensiunea dorită: + +```python +percent_to_remove = 0.1 +while len(model) > 100: + scores = compute_scores(model) + sorted_scores = sorted(scores.items(), key=lambda x: x[1]) + # Remove percent_to_remove tokens with the lowest scores. + for i in range(int(len(model) * percent_to_remove)): + _ = token_freqs.pop(sorted_scores[i][0]) + + total_sum = sum([freq for token, freq in token_freqs.items()]) + model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} +``` + +Apoi, pentru a tokeniza un text, trebuie doar să aplicăm pre-tokenizarea și apoi să folosim funcția `encode_word()`: + +```python +def tokenize(text, model): + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in words_with_offsets] + encoded_words = [encode_word(word, model)[0] for word in pre_tokenized_text] + return sum(encoded_words, []) + + +tokenize("This is the Hugging Face course.", model) +``` + +```python out +['▁This', '▁is', '▁the', '▁Hugging', '▁Face', '▁', 'c', 'ou', 'r', 's', 'e', '.'] +``` + +Asta e tot pentru Unigram! Sperăm că până acum vă simțiți ca un expert în toate lucrurile legate de tokenizer. În secțiunea următoare, vom aprofunda elementele de bază ale bibliotecii 🤗 Tokenizers și vă vom arăta cum le puteți utiliza pentru a vă construi propriul tokenizer. diff --git a/chapters/rum/chapter6/8.mdx b/chapters/rum/chapter6/8.mdx new file mode 100644 index 000000000..a1a2be892 --- /dev/null +++ b/chapters/rum/chapter6/8.mdx @@ -0,0 +1,566 @@ +# Construirea unui tokenizer, bloc cu bloc[[building-a-tokenizer-block-by-block]] + + + +După cum am văzut în secțiunile anterioare, tokenizarea cuprinde mai multe etape: + +- Normalizare (orice curățare a textului care este considerată necesară, cum ar fi eliminarea spațiilor sau a accentelor, normalizarea Unicode etc.) +- Pre-tokenizarea (împărțirea inputului în cuvinte) +- Rularea inputului prin model (utilizarea cuvintelor pre-tokenizate pentru a produce o secvență de tokeni) +- Post-procesare (adăugarea tokenilor speciali ai tokenizerului, generarea attention maskului și a ID-urilor de tip token) + +Ca un reminder, iată o altă perspectivă asupra procesului general: + +
+The tokenization pipeline. + +
+ +Biblioteca 🤗 Tokenizers a fost construită pentru a oferi mai multe opțiuni pentru fiecare dintre acești pași, pe care le puteți amesteca și combina împreună. În această secțiune vom vedea cum putem construi un tokenizer de la zero, spre deosebire de antrenarea unui tokenizer nou dintr-unul vechi, așa cum am făcut în [secțiunea 2](/course/chapter6/2). Veți putea apoi să construiți orice fel de tokenizer la care vă puteți gândi! + + + +Mai exact, biblioteca este construită în jurul unei clase centrale `Tokenizer` cu building grupate în submodule: + +- `normalizers` conține toate tipurile posibile de `Normalizer` pe care le puteți folosi (lista completă [aici](https://huggingface.co/docs/tokenizers/api/normalizers)). +- `pre_tokenizers` conține toate tipurile de `PreTokenizer` pe care le poți folosi(lista completă [aici](https://huggingface.co/docs/tokenizers/api/pre-tokenizers)). +- `models` conține diferitele tipuri de `Model` pe care le puteți folosi, precum `BPE`, `WordPiece` și `Unigram` (lista completă [aici](https://huggingface.co/docs/tokenizers/api/models)). +- `trainers` conține toate tipurile diferite de `Trainer` pe care le puteți folosi pentru a vă antrena modelul pe un corpus (unul pentru fiecare tip de model; lista completă [aici](https://huggingface.co/docs/tokenizers/api/trainers)). +- `post_processors` conține diferitele tipuri de `PostProcessor` pe care le puteți utiliza (lista completă [aici](https://huggingface.co/docs/tokenizers/api/post-processors)). +- `decoders` conține diferitele tipuri de `Decoder` pe care le puteți utiliza pentru a decoda rezultatele tokenizării (lista completă [aici](https://huggingface.co/docs/tokenizers/components#decoders)). + +Puteți găsi întreaga listă de blocuri [aici](https://huggingface.co/docs/tokenizers/components). + +## Obținerea unui corpus[[acquiring-a-corpus]] + +Pentru a antrena noul nostru tokenizer, vom utiliza un corpus mic de text (astfel încât exemplele să ruleze rapid). Pașii pentru obținerea corpusului sunt similari cu cei pe care i-am urmat la [începutul acestui capitol](/course/chapter6/2), dar de data aceasta vom utiliza datasetul [WikiText-2](https://huggingface.co/datasets/wikitext): + +```python +from datasets import load_dataset + +dataset = load_dataset("wikitext", name="wikitext-2-raw-v1", split="train") + + +def get_training_corpus(): + for i in range(0, len(dataset), 1000): + yield dataset[i : i + 1000]["text"] +``` + +Funcția `get_training_corpus()` este un generator care va produce batch-uri de 1 000 de texte, pe care le vom utiliza pentru a antrena tokenizerul. + +🤗 Tokenizers pot fi, de asemenea, antrenate direct pe fișiere text. Iată cum putem genera un fișier text care să conțină toate textele/inputurile din WikiText-2 pe care le putem utiliza local: + +```python +with open("wikitext-2.txt", "w", encoding="utf-8") as f: + for i in range(len(dataset)): + f.write(dataset[i]["text"] + "\n") +``` + +În continuare vă vom arăta cum să vă construiți propriile tokenizere BERT, GPT-2 și XLNet, bloc cu bloc. Acest lucru ne va oferi un exemplu pentru fiecare dintre cei trei algoritmi principali de tokenizare: WordPiece, BPE și Unigram. Să începem cu BERT! + +## Construirea unui tokenizator WordPiece de la zero[[building-a-wordpiece-tokenizer-from-scratch]] + +Pentru a construi un tokenizer cu biblioteca 🤗 Tokenizers, începem prin a inițializa un obiect `Tokenizer` cu un `model`, apoi îi setăm atributele `normalizer`, `pre_tokenizer`, `post_processor` și `decoder` la valorile dorite. + +Pentru acest exemplu, vom crea un `Tokenizer` cu un model WordPiece: + +```python +from tokenizers import ( + decoders, + models, + normalizers, + pre_tokenizers, + processors, + trainers, + Tokenizer, +) + +tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) +``` + +Trebuie să specificăm `unk_token` astfel încât modelul să știe ce să returneze atunci când întâlnește caractere necunoscute. Alte argumente pe care le putem seta aici includ `vocab` al modelului nostru (vom antrena modelul, deci nu este nevoie să setăm acest lucru) și `max_input_chars_per_word`, care specifică o lungime maximă pentru fiecare cuvânt (cuvintele mai lungi decât valoarea trecută vor fi divizate). + +Primul pas al tokenizării este normalizarea, așa că să începem cu aceasta. Deoarece BERT este utilizat pe scară largă, există un `BertNormalizer` cu opțiunile clasice pe care le putem seta pentru BERT: `lowercase` și `strip_accents`, care se explică de la sine; `clean_text` pentru a elimina toate caracterele de control și a înlocui spațiile repetate cu unul singur; și `handle_chinese_chars`, care plasează spații în jurul caracterelor chinezești. Pentru a replica tokenizerul `bert-base-uncased`, putem seta doar acest normalizator: + +```python +tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) +``` + +În general, atunci când construiți un nou tokenizer, nu veți avea acces la un normalizator atât de util, deja implementat în biblioteca 🤗 Tokenizers - așa că să vedem cum să creăm manual normalizatorul BERT. Biblioteca oferă un normalizator `Lowercase` și un normalizator `StripAccents` și puteți compune mai multe normalizatoare folosind un `Sequence`: + +```python +tokenizer.normalizer = normalizers.Sequence( + [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] +) +``` + +De asemenea, folosim un normalizator Unicode `NFD`, deoarece în caz contrar normalizatorul `StripAccents` nu va recunoaște corect caracterele accentuate și astfel nu le va elimina. + +După cum am mai văzut, putem folosi metoda `normalize_str()` a `normalizer` pentru a verifica efectele pe care le are asupra unui text dat: + +```python +print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +hello how are u? +``` + + + +**Pentru a merge mai departe** Dacă testați cele două versiuni ale normalizatorilor anteriori pe un șir care conține caracterul Unicode `u"\u0085"` veți observa cu siguranță că acești doi normalizatori nu sunt exact echivalenți. +Pentru a nu complica prea mult versiunea cu `normalizers.Sequence` , nu am inclus înlocuirile Regex pe care `BertNormalizer` le cere atunci când argumentul `clean_text` este setat la `True` - care este comportamentul implicit. Dar nu vă faceți griji: este posibil să obțineți exact aceeași normalizare fără a utiliza utilul `BertNormalizer` prin adăugarea a două `normalizers.Replace` la secvența normalizers. + + + + +Urmează etapa de pre-tokenizare. Din nou, există un `BertPreTokenizer` pre-construit pe care îl putem utiliza: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() +``` + +Sau îl putem construi de la zero: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() +``` + +Rețineți că pre-tokenizatorul `Whitespace` separă spațiul și toate caracterele care nu sunt litere, cifre sau caracterul underscore, deci tehnic separă spațiul și punctuația: + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), + ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] +``` + +Dacă doriți să separați doar spațiile, ar trebui să utilizați în schimb pre-tokenizerul `WhitespaceSplit`: + +```python +pre_tokenizer = pre_tokenizers.WhitespaceSplit() +pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[("Let's", (0, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre-tokenizer.', (14, 28))] +``` + +Ca și în cazul normalizatorilor, puteți utiliza un `Sequence` pentru a compune mai mulți pre-tokenizeri: + +```python +pre_tokenizer = pre_tokenizers.Sequence( + [pre_tokenizers.WhitespaceSplit(), pre_tokenizers.Punctuation()] +) +pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), + ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] +``` + +Următorul pas în pipelineul de tokenizare este rularea inputurilor prin model. Am specificat deja modelul nostru în inițializare, dar mai trebuie să îl antrenăm, ceea ce va necesita un `WordPieceTrainer`. Principalul lucru de reținut atunci când inițializați un trainer în 🤗 Tokenizers este că trebuie să îi transmiteți toți tokenii speciali pe care intenționați să îi utilizați - în caz contrar, acesta nu le va adăuga la vocabular, deoarece acestea nu se află în corpusul de antrenare: + +```python +special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] +trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) +``` + +Pe lângă specificarea `vocab_size` și `special_tokens`, putem seta `min_frequency` (numărul de ori în care un simbol trebuie să apară pentru a fi inclus în vocabular) sau putem schimba `continuing_subword_prefix` (dacă dorim să folosim ceva diferit de `##`). + +Pentru a antrena modelul nostru folosind iteratorul pe care l-am definit anterior, trebuie doar să executăm această comandă: + +```python +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +De asemenea, putem utiliza fișiere text pentru a ne antrena tokenizerul, care ar arăta astfel (în prealabil, reinițializăm modelul cu un `WordPiece` gol): + +```python +tokenizer.model = models.WordPiece(unk_token="[UNK]") +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +În ambele cazuri, putem apoi testa tokenizerul pe un text prin apelarea metodei `encode()`: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] +``` + +`encoding`-ul obținut este un `Encoding`, care conține toate rezultatele necesare ale tokenizerului în diferitele sale atribute: `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask`, și `overflowing`. + +Ultimul pas în pipelineul de tokenizare este postprocesarea. Trebuie să adăugăm tokenul `[CLS]` la început și tokenul `[SEP]` la sfârșit (sau după fiecare propoziție, dacă avem o pereche de propoziții). Vom folosi un `TemplateProcessor` pentru aceasta, dar mai întâi trebuie să cunoaștem ID-urile tokenilor `[CLS]` și `[SEP]` din vocabular: + +```python +cls_token_id = tokenizer.token_to_id("[CLS]") +sep_token_id = tokenizer.token_to_id("[SEP]") +print(cls_token_id, sep_token_id) +``` + +```python out +(2, 3) +``` + +Pentru a scrie templateul pentru `TemplateProcessor`, trebuie să specificăm cum să tratăm o singură propoziție și o pereche de propoziții. Pentru ambele, scriem tokeni speciali pe care dorim să îi folosim; prima (sau singura) propoziție este reprezentată de `$A`, în timp ce a doua propoziție (dacă facem encoding unei perechi) este reprezentată de `$B`. Pentru fiecare dintre acestea (tokeni speciali și propoziții), specificăm și ID-ul tipului de token corespunzător după două puncte. + +Modelul clasic BERT este astfel definit după cum urmează: + +```python +tokenizer.post_processor = processors.TemplateProcessing( + single=f"[CLS]:0 $A:0 [SEP]:0", + pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1", + special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)], +) +``` + +Rețineți că trebuie să transmitem ID-urile tokenilor speciali, astfel încât tokenizerul să le poată converti corect în ID-urile lor. + +Odată ce acest lucru este adăugat, revenind la exemplul nostru anterior vom obține: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] +``` + +Și pe o pereche de propoziții, obținem rezultatul corect: + +```python +encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences.") +print(encoding.tokens) +print(encoding.type_ids) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '...', '[SEP]', 'on', 'a', 'pair', 'of', 'sentences', '.', '[SEP]'] +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +Aproape am terminat de construit acest tokenizer de la zero - ultimul pas este să includem un decodor: + +```python +tokenizer.decoder = decoders.WordPiece(prefix="##") +``` + +Haideți să-l testăm pe `encoding`-ul nostru anterior: + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"let's test this tokenizer... on a pair of sentences." +``` + +Grozav! Putem salva tokenizatorul nostru într-un singur fișier JSON, astfel: + +```python +tokenizer.save("tokenizer.json") +``` + +Apoi putem reîncărca acel fișier într-un obiect `Tokenizer` cu metoda `from_file()`: + +```python +new_tokenizer = Tokenizer.from_file("tokenizer.json") +``` + +Pentru a utiliza acest tokenizer în 🤗 Transformers, trebuie să îl încorporăm în `PreTrainedTokenizerFast`. Putem fie să folosim clasa generică, fie, dacă tokenizerul nostru corespunde unui model existent, să folosim clasa respectivă (aici, `BertTokenizerFast`). Dacă aplicați această lecție pentru a construi un tokenizer nou, va trebui să utilizați prima opțiune. + +Pentru a include tokenizatorul într-un `PreTrainedTokenizerFast`, putem fie să transmitem tokenizerul construit ca `tokenizer_object`, fie să transmitem fișierul tokenizerului salvat ca `tokenizer_file`. Cel mai important lucru de reținut este că trebuie să setăm manual toți tokenii speciali, deoarece această clasă nu poate deduce din obiectul `tokenizer` care token este tokenul mască, tokenul `[CLS]`, etc.: + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + # tokenizer_file="tokenizer.json", # You can load from the tokenizer file, alternatively + unk_token="[UNK]", + pad_token="[PAD]", + cls_token="[CLS]", + sep_token="[SEP]", + mask_token="[MASK]", +) +``` + +Dacă utilizați o clasă specifică de tokenizer (cum ar fi `BertTokenizerFast`), va trebui să specificați doar tokenii speciali care sunt diferiți de cei impliciți (aici, niciunul): + +```python +from transformers import BertTokenizerFast + +wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) +``` + +Apoi puteți utiliza acest tokenizer ca orice alt tokenizer 🤗 Transformers. Îl puteți salva cu metoda `save_pretrained()` sau îl puteți încărca în Hub cu metoda `push_to_hub()`. + +Acum că am văzut cum să construim un tokenizer WordPiece, hai să facem același lucru pentru un tokenizer BPE. Vom merge un pic mai repede, deoarece cunoașteți toți pașii, și vom evidenția doar diferențele. + +## Construirea unui tokenizer BPE de la zero[[building-a-bpe-tokenizer-from-scratch]] + +Să construim acum un tokenizer GPT-2. Ca și pentru tokenizer BERT, începem prin inițializarea unui `Tokenizer` cu un model BPE: + +```python +tokenizer = Tokenizer(models.BPE()) +``` + +De asemenea, la fel ca în cazul BERT, am putea inițializa acest model cu un vocabular, dacă am avea unul (în acest caz, ar trebui să oferim `vocab` și `merges`), dar din moment ce vom antrena de la zero, nu avem nevoie să facem acest lucru. De asemenea, nu trebuie să specificăm un `unk_token` deoarece GPT-2 utilizează BPE la nivel de bytes, care nu necesită acest lucru. + +GPT-2 nu utilizează un normalizator, deci sărim peste acest pas și trecem direct la pre-tokenizare: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) +``` + +Opțiunea pe care am adăugat-o aici la `ByteLevel` nu este pentru a adăuga un spațiu unei propoziții (care este implicit în caz contrar). Putem arunca o privire la pre-tokenizarea unui text exemplu ca înainte: + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!") +``` + +```python out +[('Let', (0, 3)), ("'s", (3, 5)), ('Ġtest', (5, 10)), ('Ġpre', (10, 14)), ('-', (14, 15)), + ('tokenization', (15, 27)), ('!', (27, 28))] +``` + +Urmează modelul, care are nevoie de antrenare. Pentru GPT-2, singurul token special este tokenul de sfârșit de text: + +```python +trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Ca și în cazul `WordPieceTrainer`, precum și `vocab_size` și `special_tokens`, putem specifica `min_frequency` dacă dorim, sau dacă avem un sufix de sfârșit de cuvânt (cum ar fi ``), îl putem seta cu `end_of_word_suffix`. + +Acest tokenizer poate fi antrenat și pe fișiere text: + +```python +tokenizer.model = models.BPE() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Să aruncăm o privire la tokenizarea unui exemplu de text: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] +``` + +Aplicăm postprocesarea la nivel de bytes pentru tokenizerul GPT-2 după cum urmează: + +```python +tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) +``` + +Opțiunea `trim_offsets = False` indică post-procesorului că ar trebui să lăsăm offseturile tokenilor care încep cu "Ġ" așa cum sunt: în acest fel, începutul offseturilor va indica spațiul dinaintea cuvântului, nu primul caracter al cuvântului (deoarece spațiul face parte din punct de vedere tehnic din token). Să aruncăm o privire asupra rezultatului cu textul pe căruia tocmai i-am făcut encoding, unde `'Ġtest'` este tokenul de la indexul 4: + +```python +sentence = "Let's test this tokenizer." +encoding = tokenizer.encode(sentence) +start, end = encoding.offsets[4] +sentence[start:end] +``` + +```python out +' test' +``` + +În cele din urmă, adăugăm un decoder la nivel de bytes: + +```python +tokenizer.decoder = decoders.ByteLevel() +``` + +și putem verifica de două ori dacă funcționează corect: + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"Let's test this tokenizer." +``` + +Grozav! Acum că am terminat, putem salva tokenizatorul ca înainte și îl putem încorpora într-un `PreTrainedTokenizerFast` sau `GPT2TokenizerFast` dacă dorim să îl folosim în 🤗 Transformers: + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", +) +``` + +or: + +```python +from transformers import GPT2TokenizerFast + +wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) +``` + +Ca un ultim exemplu, vă vom arăta cum să construiți un tokenizer Unigram de la zero. + +## Construirea unui tokenizer Unigram de la zero[[building-a-unigram-tokenizer-from-scratch]] + +Să construim acum un tokenizer XLNet. Ca și în cazul tokenizerelor anterioare, începem prin inițializarea unui `Tokenizer` cu un model Unigram: + +```python +tokenizer = Tokenizer(models.Unigram()) +``` + +Din nou, am putea inițializa acest model cu un vocabular, dacă am avea unul. + +Pentru normalizare, XLNet utilizează câteva înlocuiri (care provin din SentencePiece): + +```python +from tokenizers import Regex + +tokenizer.normalizer = normalizers.Sequence( + [ + normalizers.Replace("``", '"'), + normalizers.Replace("''", '"'), + normalizers.NFKD(), + normalizers.StripAccents(), + normalizers.Replace(Regex(" {2,}"), " "), + ] +) +``` + +Acest lucru înlocuitește `` și '' cu " și orice secvență de două sau mai multe spații cu un singur spațiu, precum și ștergerea accentelor în textele ce trebuie tokenizate. + +Pre-tokenizerul care trebuie utilizat pentru orice tokenizer SentencePiece este `Metaspace`: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() +``` + +Putem arunca o privire la pre-tokenizarea unui exemplu de text ca mai înainte: + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!") +``` + +```python out +[("▁Let's", (0, 5)), ('▁test', (5, 10)), ('▁the', (10, 14)), ('▁pre-tokenizer!', (14, 29))] +``` + +Urmează modelul, care are nevoie de antrenare. XLNet are destul de mulți tokeni speciali: + +```python +special_tokens = ["", "", "", "", "", "", ""] +trainer = trainers.UnigramTrainer( + vocab_size=25000, special_tokens=special_tokens, unk_token="" +) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Un argument foarte important care nu trebuie uitat pentru `UnigramTrainer` este `unk_token`. Putem trece și alte argumente specifice algoritmului Unigram, cum ar fi `shrinking_factor` pentru fiecare pas în care eliminăm tokeni (valoarea implicită este 0,75) sau `max_piece_length` pentru a specifica lungimea maximă a unui token dat (valoarea implicită este 16). + +Acest tokenizer poate fi antrenat și pe fișiere text: + +```python +tokenizer.model = models.Unigram() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Să aruncăm o privire la tokenizarea unui exemplu de text: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] +``` + +O particularitate a XLNet este că pune tokenul `` la sfârșitul propoziției, cu un ID de tip 2 (pentru a-l distinge de ceilalți tokeni). Ca urmare, este făcut padding la stânga. Putem trata toți tokenii speciali și ID-urile de tip ale tokenilor cu un template, ca în cazul BERT, dar mai întâi trebuie să obținem ID-urile tokenilor `` și ``: + +```python +cls_token_id = tokenizer.token_to_id("") +sep_token_id = tokenizer.token_to_id("") +print(cls_token_id, sep_token_id) +``` + +```python out +0 1 +``` + +Templateul arată astfel: + +```python +tokenizer.post_processor = processors.TemplateProcessing( + single="$A:0 :0 :2", + pair="$A:0 :0 $B:1 :1 :2", + special_tokens=[("", sep_token_id), ("", cls_token_id)], +) +``` + +Și putem testa că funcționează prin codificarea unei perechi de propoziții: + +```python +encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!") +print(encoding.tokens) +print(encoding.type_ids) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.', '.', '.', '', '▁', 'on', '▁', 'a', '▁pair', + '▁of', '▁sentence', 's', '!', '', ''] +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2] +``` + +În cele din urmă, adăugăm un decoder `Metaspace`: + +```python +tokenizer.decoder = decoders.Metaspace() +``` + +și am terminat cu acest tokenizer! Putem salva tokenizerul ca înainte și îl putem încorpora într-un `PreTrainedTokenizerFast` sau `XLNetTokenizerFast` dacă dorim să îl folosim în 🤗 Transformers. Un lucru de reținut atunci când se utilizează `PreTrainedTokenizerFast` este că, pe lângă tokenii speciali, trebuie să spunem bibliotecii 🤗 Transformers să facă padding la stânga: + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="", + eos_token="", + unk_token="", + pad_token="", + cls_token="", + sep_token="", + mask_token="", + padding_side="left", +) +``` + +Sau alternativ: + +```python +from transformers import XLNetTokenizerFast + +wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) +``` + +Acum că ați văzut cum sunt utilizate diferitele blocuri de construcție pentru a construi tokenizeri existenți, ar trebui să puteți scrie orice tokenizer doriți cu biblioteca 🤗 Tokenizers și să îl puteți utiliza în 🤗 Transformers. diff --git a/chapters/rum/chapter6/9.mdx b/chapters/rum/chapter6/9.mdx new file mode 100644 index 000000000..3898e9d3a --- /dev/null +++ b/chapters/rum/chapter6/9.mdx @@ -0,0 +1,16 @@ +# Tokenizeri, verificare![[tokenizers-check]] + + + +Bună treabă la finalizarea acestui capitol! + +După această scufundare adâncă în tokenizers, ar trebui să: + +- Să fii capabil să antrenezi un nou tokenizer folosind unul vechi ca model +- Să înțelegi modului de utilizare a offseturilor pentru a face map tokenilor în intervalul lor original de text +- Cunoști diferențele dintre BPE, WordPiece și Unigram +- Fii capabil să combini blocurile furnizate de biblioteca 🤗 Tokenizers pentru a vă construi propriul tokenizer +- Să poți folosi acest tokenizer în cadrul bibliotecii 🤗 Transformers