diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index e5d8e859b..40c971827 100644 --- a/chapters/it/_toctree.yml +++ b/chapters/it/_toctree.yml @@ -39,3 +39,23 @@ - local: chapter4/6 title: Quiz di fine capitolo quiz: 4 + +- title: 5. La libreria 🤗 Datasets + sections: + - local: chapter5/1 + title: Introduzione + - local: chapter5/2 + title: E se il mio dataset non è sull'Hub? + - local: chapter5/3 + title: È arrivato il momento di tagliuzzare + - local: chapter5/4 + title: Big data? Ci pensa 🤗 Datasets! + - local: chapter5/5 + title: Creare il proprio dataset + - local: chapter5/6 + title: Ricerca semantica con FAISS + - local: chapter5/7 + title: 🤗 Datasets, check! + - local: chapter5/8 + title: Quiz di fine capitolo + quiz: 5 diff --git a/chapters/it/chapter5/1.mdx b/chapters/it/chapter5/1.mdx new file mode 100644 index 000000000..e23cf502e --- /dev/null +++ b/chapters/it/chapter5/1.mdx @@ -0,0 +1,17 @@ +# Introduzione + +Nel [Capitolo 3](/course/chapter3) hai mosso i primi passi nella libreria 🤗 Datasets, e hai scoperto i tre passaggi fondamentali nell'ottimizzazione dei modelli: + +1. Si carica un dataset dell'Hub Hugging Face. +2. Si processano i dati con `Dataset.map()`. +3. Si caricano e si elaborano le metriche. + +Ma questo non è che un assaggio di ciò che 🤗 Datasets è in grado di fare! In questo capitolo approfondiremo le potenzialità della libreria. Durante questo percorso, troverai risposta alle seguenti domande: + +* Cosa fare quando un dataset non è presente nell'Hub? +* Come fare a tagliuzzare il dataset? (E cosa succede se devi _proprio_ usare Pandas?) +* Cosa fare quando un dataset è tanto grande da sciogliere la RAM del tuo portatile? +* Cosa cavolo sono il "mappamento di memoria" e Apache Arrow? +* Come fare per creare il proprio dataset e pubblicarlo sull'Hub? + +Le tecniche che imparerai ti prepareranno a compiti più avanzati di tokenizzazione e fine-tuning che troverai nei capitoli [Chapter 6](/course/chapter6) e [Chapter 7](/course/chapter7) -- quindi preparati una tazza di caffè e iniziamo! diff --git a/chapters/it/chapter5/2.mdx b/chapters/it/chapter5/2.mdx new file mode 100644 index 000000000..93a7d01f6 --- /dev/null +++ b/chapters/it/chapter5/2.mdx @@ -0,0 +1,167 @@ +# E se il mio dataset non è sull'Hub? + + + +Sai come usare l'[Hub Hugging Face](https://huggingface.co/datasets) per scaricare i dataset, ma spessa dovrai lavorare con dati che si trovano sul tuo computer, o so un server remoto. In questa sezione vederemo come usare 🤗 Datasets per caricare dataset che non sono disponibile nell'Hub Hugging Face. + + + +## Lavorare con dataset locali e in remoto + +🤗 Datasets mette a tua disposizione diversi script per caricare dataset in locale e in remoto. Sono supportati diversi formati di dati, tra cui: + +| Formato dati | Script | Esempio | +| :----------------: | :------------: | :-----------------------------------------------------: | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| File di testo | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| DataFrame serializzati in Pickle | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | + +Come mostrato nella tabella, per ogni formato di dati abbiamo bisogno di specificare, all'interno della funzione `load_dataset()`, il tipo di script da utilizzare, assieme a `data_files`, che specifica il percorso verso uno o più file. Iniziamo a caricare un dataset proveniente da file locali; più tardi vederemo come fare la stessa cosa con file in remoto. + +## Caricare un dataset locale + +Per questo esempio useremo il [dataset SQuAD-it](https://github.com/crux82/squad-it/), un ampio dataset per il question answering in italiano + +Le sezioni di addestramento e di test si trovano su GitHub, quindi possiamo scaricarle con un semplice comando `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 +``` + +Questo scaricherà due file compressi chiamati *SQuAD_it-train.json.gz* e *SQuAD_it-test.json.gz*, che possiamo decomprimere con il comandi 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 +``` + +Vediamo che i dati compressi sono stati sostituiti da _SQuAD_it-train.json_ e _SQuAD_it-text.json_, e che i dati sono archiviati in formato JSON. + + + +✎ Se ti stai chiedendo perché c'è un `!` nei comandi di shell precedenti, è perché li stiamo eseguendo da un notebook Jupyter. Se vuoi scaricare e decomprimere i dataset da un terminale, non devi fare altro che rimuovere il prefisso. + + + +Per caricare un file JSON con la funzione `load_dataset()`, ci serve solo sapere se abbiamo a che fare con un normale JSON (simile a un dizionario annidato) o con un JSON Lines (JSON separato da righe). Come molti dataset per il question asnwring, SQuAD-it usa il formato annidato, con tutto il testo immagazzinato nel campo `data`. Questo significa che possiamo caricare il dataset specificando l'argomento `field` come segue: + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +Di default, caricare file locali create un oggetto `DatasetDict` con una sezione `train`. Possiamo vederlo ispezionando l'oggetto `squad_it_dataset`: + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +Questo ci mostra il numero di righe e i nomi delle colonne associate con il set di addestraento. Possiamo vedere uno degli esempi indicizzando la sezione `train`, come segue: + +```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?", + }, + ... + ], + }, + ... + ], +} +``` + +Benissimo, abbiamo caricare il nostro primo dataset locale! Ma anche se questo ha funzionato per la sezione di addestramento, vogliamo includere entrambe le sezioni `train` e `test` in un unico oggetto `DatasetDict` così da poter applicare le funzioni `Dataset.map()` su entrambi i dataset simultaneamente. Per fare questo, possiamo dare un dizionaro all'argomento `data_files`, per mappare ogni sezione a un file associato con quella sezione: + +```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 + }) +}) +``` + +Questo è proprio ciò che volevamo. Ora possiamo applicare diverse tecniche di preprocessamento per pulire i dati, tokenizzare le revisioni, e altro. + + + +L'argomento `data_files` della funzione `load_dataset()` è molto flessibile, e può essere usato con un percorso file singolo, con una lista di percorsi file, o un dizionario che mappa i nomi delle sezioni ai percorsi file. È anche possibile usare comandi glob per recuperare tutti i file che soddisfano uno specifico pattern secondo le regole dello shell di Unix (ad esempio, è possibile recuperare tutti i file JSON presenti in una cartella usando il pattern `data_files="*.json"`). Consulta la [documentazione](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) 🤗 Datasets per maggiori informazioni. + + + +Gli script presenti in 🤗 Datasets supportano la decompressione atuomatica dei file in input, quindi possiamo saltare l'uso di `gzip` puntando `data_files` direttamente ai file compressi: + +```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") +``` + +Questo può essere utile se non vuoi decomprimere manualmente molti file GZIP. La decompressione automatica si applica inoltre ad altri formati comuni, come ZIP e TAR, basta solo puntare `data_files` ai file compressi ed è fatta! + +Ora che sai come caricare i file locali dal tuo computer, guardiamo come caricare i file remoti. + +## Caricare un dataset in remoto + +Se lavori come data scientist o come programmatore per un'azienda, ci sono buone probabilità che i dataset da analizzare sono archiaviati su un qualche server in remoto. Per fortuna, caricare file remoti è semplice come caricare quelli locali! Invece di dare un percorso a file locali, puntiamo l'argomento `data_files` di `load_dataset()` a uno o più URL dove si trovano i file in remoto. Ad esempio, per il dataset SQuAD-it presente su GitHub, possiamo puntare `data_files` agli URL _SQuAD_it-*.json.gz_ come segue: + +```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") +``` + +Questo codice restituisce lo stesso oggetto `DatasetDict` visto in precedenza, ma ci risparmia il passaggio manuale di scaricare e decomprimere i file _SQuAD_it-*.json.gz_. Questo conclude la nostra incursione nei diversi modi di caricare dataset che non sono presenti nell'Hub Hugging Face. Ora che abbiamo un dataset con cui giocare, sporchiamoci le mani con diverse tecniche di data-wrangling! + + + +✏️ **Prova tu!** Scegli un altro dataset presente su GitHub o sulla [Repository di Machine Learning UCI](https://archive.ics.uci.edu/ml/index.php) e cerca di caricare sia in locale che in remoto usando le tecniche introdotte in precedenza. Per punti extra, prova a caricare un dataset archiviato in formato CSV o testuale (vedi la [documentazione](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) per ulteriori informazioni su questi formati). + + + + diff --git a/chapters/it/chapter5/3.mdx b/chapters/it/chapter5/3.mdx new file mode 100644 index 000000000..8c44362ed --- /dev/null +++ b/chapters/it/chapter5/3.mdx @@ -0,0 +1,740 @@ +# È arrivato il momento di tagliuzzare + + + +La maggior parte delle volte, i dati su cui lavorerai non saranno perfettamente pronti a essere usati per l'addestramento. In questa sezione esploreremo alcune funzionalità di 🤗 Datasets per pulire i tuoi dataset. + + + +## Tagliuzzare i tuoi dati + +Proprio come Pandas, 🤗 Datasets offre diverse funzionalità per manipolare il contenuto degli oggetti `Dataset` e `DatasetDict`. Abbiamo già visto il metodo `Dataset.map()` nel [Capitolo 3](/course/chapter3), e in questa sezione esploreremo altre funzioni a nostra disposizione. + +Ai fini di quest'esempio useremo il [Drug Review Dataset](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29), che raccoglie le recensioni di pazienti su vari medicinali, assieme alla condizione curata e a una valutazione da 0 a 10 del grado di soddisfazione del paziente. +Prima di tutto scarichiamo ed estraiamo i dati, utilizzando i comandi `wget` e `unzip`: + +```py +!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" +!unzip drugsCom_raw.zip +``` + +Poiché TSV non è altro che una variante di CSV che usa come separatore tabulatori al posto delle virgole, caricheremo questi file utilizzando lo script `csv` e specificando l'argomento `delimiter` nella funzione `load_dataset()`, come segue: + +```py +from datasets import load_dataset + +data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} +# \t rappresenta il tabulatore in Python +drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") +``` + +È buona prassi nell'analisi dati recuperare un piccolo campione casuale per farsi un'idea del tipo di dati con cui si sta lavorando. Utilizzando 🤗 Datasets, possiamo crare un campione casuale concatenando le funzioni `Dataset.shuffle()` e `Dataset.select()`: + +```py +drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) +# Diamo un'occhiata ai primi esempi +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]} +``` + +Da notare che abbiamo impostato il seed in `Dataset.shuffle()` per motivi di riproducibilità. `Dataset.select()` ha bisogno di un iterabile di indici, per cui abbiamo utilizzato `range(1000)` per recuperare i primi 1.000 esempi dal dataset mescolato. Da questo campione possiamo già vedere alcune particolarità del nostor dataset: + +* La colonna `Unnamed: 0` assomiglia molto a un ID anonimizzato per ognuno dei pazienti. +* La colonna `condizione` include un mix di etichette maiuscole e minuscole. +* Le recensioni sono di diversa lunghezza e contengono un mix di separatori di riga Python (`\r\n`) e di codici di caratteri HTML come `&\#039`. + +Ora vediamo come utilizzare 🤗 Datasets per risolvere alcuni di questi problemi. Per confermare l'ipotesi che la colonna `Unnamed: 0` rappresenti gli ID dei pazienti, possiamo usare la funzione `Dataset.unique()` per verificare che il numero di ID corrisponda al numero delle righe in ognuna delle sezioni: + + +```py +for split in drug_dataset.keys(): + assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) +``` + +Questo sembra confermare la nostra ipotesi, quindi puliamo un po' il nostro dataset cambiando il nome della colonna `Unnamed: 0` in qualcosa di un po' più comprensibile. Possiamo usare la funzione `DatasetDict.rename_column()` per rinominare la colonna in entrambe le sezioni: + + +```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 + }) +}) +``` + + + +✏️ **Prova tu!** Usa la funzione `Dataset.unique()` per trovare il numero di medicine diverse e condizioni nelle sezioni di addestramento e di test. + + + +Ora, normaliziamo le etichette in `condition` utilizzando `Dataset.map()`. Così come abbiamo fatto con la tokenizzazione nel [Capitolo 3](/course/chapter3), possiamo definire una semplice funzione che può essere applicata a tutte le righe di ogni sezione nel `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, abbiamo incontrato un problema con la nostra funzione! Dall'errore possiamo dedurre che alcuni dei valori nella colonna `condition` sono `None`, che non essendo stringhe non possono essere convertiti in lettere minuscole. Eliminiamo queste righe utilizzando `Dataset.filter()`, che funziona come `Dataset.map()` e accetta una funziona che riceve un singolo esempio del dataset. Invece di scrivere una funzione esplicita come: + + +```py +def filter_nones(x): + return x["condition"] is not None +``` + +e utilizzare `drug_dataset.filter(filter_nones)`, possiamo utilizzare una _funzione lambda_ e completare tutto in un'unica riga. In Python, le funzioni lambda sono funzioni che possiamo definire senza nominarle esplicitamente. Hanno la forma generale: + +``` +lambda : +``` + +dove `lambda' è una delle [keyword](https://docs.python.org/3/reference/lexical_analysis.html#keywords) speciali di Python, `` è una lista/set di valori separati da virgole che definisce l'input della funzione, e `` rappresenta le operazioni che vogliamo eseguire. Ad esempio, posiamo definire una semplice funzione lamda che calcola il quadrato di un numero: + +``` +lambda x : x * x +``` + +Per applicare questa funzione a un input, dobbiamo includere sia la funzione che l'input in parentesi: + +```py +(lambda x: x * x)(3) +``` + +```python out +9 +``` + +Allo stesso modo, possiamo definire funzioni lmabda con argomenti multipli separandoli con virgoli. Ad esempio, possiamo calcolare l'area di un triangolo come segue: + +```py +(lambda base, altezza: 0.5 * base * altezza)(4, 8) +``` + +```python out +16.0 +``` + +Le funzioni lambda sono utili quando vogliamo definire piccole funzioni monouso (per maggiori informazioni, invitiamo alla lettura dell'ottimo [tutorial di Real Python](https://realpython.com/python-lambda/) di Andre Burgaud). In 🤗 Datasets, possiamo usare le funzioni lambda per definire semplici operazioni di mappatura e filtraggio. Utilizziamo questo trucchetto per eliminare i valori `None` nel nostro dataset: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) +``` + +Una volta rimosse le voci `None`, possiamo normalizzare la colonna `condition`: + +```py +drug_dataset = drug_dataset.map(lowercase_condition) +# Check that lowercasing worked +drug_dataset["train"]["condition"][:3] +``` + +```python out +['left ventricular dysfunction', 'adhd', 'birth control'] +``` + +Funziona! Or ache abbiamo pulito le nostre etichette, diamo un'occhiata a come pulire le recensioni. + +## Creare nuove colonne + +Quando abbiamo a che fare con le recensioni di clienti, è buona pratica controllare il numero di parole in ogni recensione. Una recensione potrebbe contenere solo una parola com "Ottimo!" o un vero e proprio saggio di migliaia di parole, e a seconda dell'uso che ne farai dovrai affrontare queste situazioni in maniera diversa. Per calculare il numero di parole in ogni recensione, useremo un'euristica grezza basata sulla divisione dei testi sugli spazi. + + +Definiamo una semplice funzione che conta il numero di parole in ogni recensione: + +```py +def compute_review_length(example): + return {"review_length": len(example["review"].split())} +``` + +A differenza della nostra funzione `lowercase_condition()`, `compute_review_length()` ritorna un dizionario le cui chiavi non corrispondono a nessuna delle colonne nel dataset. In questo caso, quando `compute_review_length()` è passata a `Dataset.map()`, si applicherà a tutte le righe nel dataset per creare una nuova colonna `review_lenght`; + +```py +drug_dataset = drug_dataset.map(compute_review_length) +# Inspect the first training example +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} +``` + +Come previsto, una colonna `review_length` è stata aggiunta al nostro set di addestramento. Possiamo ordinare questa nuova colonna utilizzando `Dataset.sort()` per dare un'occhiata ai valori estremi: + +```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]} +``` + +Come sospettato, alcune revisioni contengono una sola parola che, benché potrebbe essere utile per la sentiment analysis, non dà informazioni utili per predirre la condizione. + + + +🙋Un altro modo per aggiungere nuove colonne a un dataset è attraverso la funzione `Dataset.add_column()`. Questo ti permette di inserire le colonne come una lista Python o unarray NumPy, e può tornare utile in situazioni in cui `Dataset.map()` non è indicata per le tue analisi. + + + +Usiamo la funzione `Dataset.filter()` per rimuovere le recensioni che contengono meno di 30 parole. Proprio come abbiamo fatto per la colonna `condizione`, possiamo eliminare le recensioni più brevi aggiungendo un filtro che lascia passare solo le recensioni più lunghe di una certa soglia: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) +print(drug_dataset.num_rows) +``` + +```python out +{'train': 138514, 'test': 46108} +``` + +Come puoi vedere, questo ha rimosso circa il 15% delle recensioni nelle sezioni di training e di test. + + + +✏️ **Prova tu!** Usa la funzione `Dataset.sort()` per analizzare le revisioni con il maggior numero di parole. Controlla la [documentazione](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.sort) per vedere quali argomenti bisogna usare per ordinare le recensioni in ordine decrescente di lunghezza. + + + +L'ultima cosa che ci resta da risolvere è la presenza di codici HTML di caratteri nelle nostre recensioni. Possiamo usare il modulo Python `html` per sostituirli, così: + +```py +import html + +text = "I'm a transformer called BERT" +html.unescape(text) +``` + +```python out +"I'm a transformer called BERT" +``` + +We'll use `Dataset.map()` to unescape all the HTML characters in our corpus: + +```python +drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) +``` +Come puoi vedere, il metodo `Dataset.map()` è molto utile per processare i dati -- e questo non è che la punta dell'iceberg di ciò che è in grado di fare! + +## I superpoteri del metodo `map()` + +Il metodo `Dataset.map()` accetta un argomento `batched` che, se impostato su `True`, gli fa inviare un batch di esempi alla funzione map in una sola volta (la grandezza del batch è configurabile, ma di default è impostta a 1.000). Ad esempio, l'esecuzione delle funzione map precedente che ha sostituito tutti i caratteri HTML è stata un po' lenta (puoi leggere il tempo impiegato dalle barre di progresso). Possiamo accelerare questo processo processando diversi elementi in contemporanea usando una comprensione di lista. + +Quando si specifica `batched=True` la funzione riceva un dizionario con i campi del dataset, ma ogni valore è ora una _lista di valori_, e non un valore singolo. Il valore ritornato da `Dataset.map()` dovrebbe essere lo stesso: un dizionario con i campi che vogliano aggiornare o aggiungere al nostro dataset, e una lista di valori. Ad esempio, ecco un altro modo per sostituire tutti i carattere HTML, ma utilizzando `batched=True`: + +```python +new_drug_dataset = drug_dataset.map( + lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True +) +``` + +Se utilizzi questo codice in un notebook, noterai che questo comando è molto più veloce del precedente. E non perché le nostre recensioni già state preprocessate, se esegui nuovamente le istruzioni della sezione precedente (senza `batched=True'), ci metterà lo stesso tempo di prima. Questo è perchè le comprensioni di lista sono solitamente più veloci delle loro controparti con ciclo `for`, e inoltre abbiamo guadagnato performance permettendo l'accesso a molti elementi in contemporanea invece di uno per volta. + +Utilizzare `Dataset.map()` con `batched=True` sarà essenziale per sbloccare la velocità dei tokenizzatori "fast" che incontreremo nel [Capitolo 6](/course/chapter6), che permettono di tokenizzare velocemente grandi liste di testi. Ad esempio, per tokenizzare tutte le recensioni di medicinali con un tokenizzatore veloce, potremmo usare una funzione come questa: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + + +def tokenize_function(examples): + return tokenizer(examples["review"], truncation=True) +``` + +Come visto nel [Capitolo 3](/course/chapter3), possiamo passare uno o più esempi al tokenizzatore. Le funzione può essere usata con o senza `batched=True`. Approfittiamo di quest'occasione per paragonare la performance delle diverse opzioni. In un notebook, possiamo cronomotrare un'istruzione su una singola riga aggiungendo `%time` prima della riga di codice che desideri cronometrare: + +```python no-format +%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) +``` + +Possiamo cronometrare anche un'intera cella inserento `%%time` all'inizio della cella. Sull'hardware che stiamo utilizzando, mostrava 10.8s pe rquest'istruzione (è il numero scritto dopo "Wall time"). + + + +✏️ **Prova tu!** Esegui la stessa istruzione con e senza `batched=True`, poi prova con un tokenizzatore lento (aggiungi `add_fast=False` al metodo `AutoTokenizer.from_pretrained()`) così che puoi controllare i tempi sul tuo hardware. + + +Ecco i risultati che otteniamo con e senza utilizzare batch, con un tokenizzatore lento e uno veloce: + +Opzioni | Tokenizzatore veloce |Tokenizzatore lento +:--------------:|:--------------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + +Questo significa che utilizzare un tokenizzatore veloce con l'opzione `batched=True` è 30 volte più veloce della sua controparte lenta con `batched=False` -- ottimo! Questa è la ragione principale per cui i tokenizzatori veloci sono di default utilizzando `AutoTokenizer` (e il motivo per cui vengono chiamati "fast"). Sono in grado di raggiungere certe velocità perché dietro le quinte il codice di tokenizzazione è eseguito in Rust, un linguaggio che rende semplice l'esecuzione di codici in parallelo. + +L'esecuzione in parallelo è anche il motivo per l'aumento di velocità x6 che il tokenizzatore veloce ottiene con `batched=True`: non è possibile eseguire in parallelo una sola operazione di tokenizzazione, ma quando vuoi tokenizzare molti testi contemporaneamente puoi dividere l'esecuzione su vari processi, ognuno responsabile dei propri testi. + +`Dataset.map()` possiede inoltre alcune capacità di parallelizzazione per conto proprio. Non avendo però Rust alle proprie spalle, non può permettere a un tokenizzatore lento di raggiungere uno veloce, ma possono comunque tornare utili (soprattutto se stai utilizzando un tokenizatore che non possiede una versione veloce). Per abilitare il multiprocessing, usa l'argomenti `num_proc` e specifica il numero di processi da utilizzare quando evoci `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) +``` + +Puoi sperimentare con le tempistiche per determinare il numero ottimale di processi da utilizzare; nel nostro caso 8 sembra produrre i risultati migliori. Ecco i numeri che abbiamo ottenuto con e senza multiprocessing: + +Opzioni | Tokenizzatore veloce | Tokenizzatore lento +:----------------------------:|:--------------------:|:-------------: +`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 + +Questi sono dei risultati molto più accettabili per il tokenizzatore lento, ma anche la performance dei tokenizzatori veloci è notevolmente migliorata. Notare, comunque, che non è sempre questo il caso: per valori di `num_proc` diversi da 8, i nostri test hanno mostrato che è più veloce utilizzare `batched=True` senza l'opzione `num_proc`. In generale, non raccomandiamo l'utilizzo di multiprocessing Python per tokenizzatori veloci con `batched=True`. + + + +Utilizzare `num_proc` per accelerare i processi è generalmente una buona idea, a patto che la funzione che stai utilizzando non stia già usando un qualche tipo di multiprocessing per conto proprio. + + + +Tutte queste funzionalità condensate in un unico metodo sono già molto utili, ma c'è altro! Con `Dataset.map()` e `batched=True`, è possibile modificare il numero di elementi nel tuo dataset. È particolarmente utile quando vuoi creare diverse feature di addestramento da un unico esempio, e ne avremo bisogno come parte di preprocessing per molti dei task NLP che affronteremo nel [Capitolo 7](/course/chapter7). + + + +💡 Nel machine learning, un _esempio_ è solitamente definito come un insieme di _feature_ che diamo in pasto al modello. In alcuni contesti, queste feature saranno l'insieme delle colonne in un `Dataset`, ma in altri casi (come ad esempio questo, o per il question answering), molte feature possono essere estratte da un singolo esempio, e appartenere a una sola colonna. + + + +Diamo un'occhiata a come funziona! Tokenizziamo i nostri esempi e tronchiamoli a una lunghezza massima di 128, ma chiediamo al tokenizzatore di restituire *tutti* i pezzi di testo e non solo il primo. Questo può essere fatto con `return_overflowing_tokens=True`: + +```py +def tokenize_and_split(examples): + return tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) +``` + +Testiamo questa funzione su un esempio prima di utilizzare `Dataset.map()` sull'intero dataset: + +```py +result = tokenize_and_split(drug_dataset["train"][0]) +[len(inp) for inp in result["input_ids"]] +``` + +```python out +[128, 49] +``` + +Quindi, il nostro primo esempio nel set di train è stato trasformaro in due feature perché tokenizzato in un numero maggiore di token di quelli specificati: il primo gruppo di lunghezza 128 token e il secondo di lunghezza 49. Facciamo la stessa cosa per tutti gli elementi del 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! Non ha funzionato! Perché? Il messaggio di errore ci dà un indizio: c'è una discordanza tra la lungheza di una delle colonne (una è lunga 1.463 e l'altra 1.000). Se hai guardato la [documentazione](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map) di `Dataset.map()`, ricorderai che quello è il numero di campioni passati alla funzione map; qui quei 1.000 esempi danno 1.463 nuove feature, che risulta in un errore di shape. + +Il problema è che stiamo cercando di mescolare due dataset diversi di grandezze diverse: le colonne del `drug_dataset` avranno un certo numero di esempi (il 1.000 del nostro errore), ma il `tokenized_dataset` che stiamo costruendo ne avrà di più (il 1.463 nel nostro messaggio di errore). Non va bene per un `Dataset`, per cui abbiamo bisogno o di rimuovere le colonne dal dataset vecchio, o renderle della stessa dimensione del nuovo dataset. La prima opzione può essere effettuata utilizzando l'argomento `remove_columns`: + + +```py +tokenized_dataset = drug_dataset.map( + tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names +) +``` + +Ora funziona senza errori. Possiamo controllare che il nostro nuovo dataset contiene più elementi del dataset originale paragonando le lunghezze: + +```py +len(tokenized_dataset["train"]), len(drug_dataset["train"]) +``` + +```python out +(206772, 138514) +``` +Abbiamo già menzionato che possiamo risolvere il problema delle lunghezze discordanti cambiando la dimenzione delle vecchie colonne. Per far ciò, abbiamo bisogno del campo `overflow_to_sample_mapping` restituito dal tokenizzatore quando impostiamo `return_overflowing_tokens=True`. Così facendo avremo una mappatura degli indici delle nuove feature all'indice di campioni da cui sono state generate. Usando questa mappatura, possiamo associare a ogni chiava presente nel nostro dataset originale una lista di valori delle dimensioni giuste, ripetendo il valore di ogni esempio finché genera nuove feature: + +```py +def tokenize_and_split(examples): + result = tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) + # Estraiamo la mappatura tra gli indici vecchi e quelli nuovi + 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 +``` + +Possiamo vedere come funziona con `Dataset.map()` senza aver bisogno di rimuovere le colonne vecchie: + +```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 + }) +}) +``` + +Otteniamo lo stesso numero di feature di addestramento di prima, ma qui abbiamo conservato i campi originali. Se ti servono per un post-processing dopo aver applicato il tuo modello, potresti usare quest'approccio. + +Ora abbiamo visto come usare 🤗 Datasets per preprocessare un dataset in diversi modi. Benché le funzioni di processamento di 🤗 Datasets soddisferà la maggior parte delle esigenze del modello che vuoi addestrare, ci saranno momenti in cui avrai bisogno di utilizzare Pandas per avere funzionalità ancora più potenti, come `DataFrame.groupby()` o API di alto livello per visualizzazione. Per fortuna, 🤗 Datasets è progettato per essere utilizzato con librerie come Pandas, NumPy, PyTorch, TensorFlow e JAX. Diamo un'occhiata a come funziona. + +## Da `Dataset` a `DataFrame` e ritorno + + + +Per permettere la conversione tra librerie terze, 🤗 Datasets fornisce una funzione `Dataset.set_format()`. Questa funzione cambia il _formato di output_ del dataset, così che puoi passare a un altro formato senza modificare il _formato di dati_ soggiacente, che è Apache Arrow. La formattazione avviene direttamente _in place_. Per provare, convertiamo il nostro dataset per Pandas: + +```py +drug_dataset.set_format("pandas") +``` + +Ora quando accediamo agli elementi del dataset otteniamo un `pandas.DataFrame` e non un dizionario: + +```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
+ +Creiamo un `pandas.DataFrame` per l'intero set di addestramento selezionando tutti gli elementi di `drug_dataset["train"]`: + +```py +train_df = drug_dataset["train"][:] +``` + + + +🚨 Dietro le quinte, `Dataset.set_format()` modifica il formato di restituzione del meteodo dunder `__getitem__()` del dataset. Questo significa che quando vogliamo creare un nuovo oggetto come ad esempio `train_df` da un `Dataset` in formato `"pandas"`, abbiamo bisogno di suddividere l'intero dataset per ottenere un `pandas.DataFrame`. Puoi verificare da te che `drug_dataset["train"]` ha come tipo `Dataset`, a prescindere dal formato di output. + + + +Da qui possiamo usare tutte le funzionalità Pandas che vogliamo. Ad esempio, possiamo creare un concatenamento per calcolare la distribuzione delle classi nelle voci `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
+ + +E una volta che abbiamo finito con le nostre analisi su Pandas, possiamo sempre creare un nuovo oggetto `Dataset` utilizzando la funzione `Dataset.from_pandas()`: + +```py +from datasets import Dataset + +freq_dataset = Dataset.from_pandas(frequencies) +freq_dataset +``` + +```python out +Dataset({ + features: ['condition', 'frequency'], + num_rows: 819 +}) +``` + + + +✏️ **Prova tu!** Calcola la valutazione media per i medicinali, e salviamo i risultati in un nuovo `Dataset`. + + + +Questo conclude il nostro tour delle diverse tecniche di prepocessamento disponibile in 🤗 Datasets. Per riepilogare la sezione, creiamo un set di validazione per preparare il dataset su cui addestreremo un classificatore. Prima di far ciò, resettiamo il formato di output di `drug_dataset` da `"pandas"` a `"arrow"`: + +```python +drug_dataset.reset_format() +``` + +## Creare un set di validazione + +Pur avendo un set di test che potremmo usare per la valutazione, è buona prassi lasciare il set di test intatto e creare un set di validazione sepearato durante lo sviluppo de lmodello. Una volta che sei soddisfatto della performance del tuo modello sul set di validazione, puoi proseguire con un ultimo check sul set di test. Questo processo aiuta a ridurre i rischi di overfitting sul set di test e di creare un modello che fallisce sui dati del mondo reale. + +🤗 Datasets possiede una funzione `Dataset.train_test_split()`, basata sulla famosa funzionalità da `scikit-learn`. Proviamo a utilizzarla per dividere il nostro set di addestramento in sezioni di `addestramento` e di `validazione` (impostiamo l'argomento `seed` per motivi di riproducibilità): + +```py +drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) +# Rinominare la sezione di "test" in "validazione" +drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") +# Aggiungere il set "test" al nostor `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 + }) +}) +``` + +Bene! Abbiamo preparato un dataset che è pronto per l'addestramento di modelli! Nella [sezione 5](/course/chapter5/5) ti mostreremo come caricare i dataset nell'Hub di Hugging Face, ma per ora concludiamo la nostra analisi esplorando alcuni modi per salvare i dataset sulla tua macchina locale. + +## Salvare un dataset + + + +Benché 🤗 Datasets memorizzi in cache tutti i dataset scaricati e le operazioni effettuate, ci sono momenti in cui vorrai salvare un dataset su disco (ad esempio, nel caso la cache venga eliminata). Come mostrato nella tabella successiva, 🤗 Datasets fornisce tre funzioni principali per salvare il tuo dataset in diversi formati: + +| Formato dati | Funzione | +| :---------: | :--------------------: | +| Arrow | `Dataset.save_to_disk()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | + +Ad esempio, salviamo il nostro dataset pulito in formato Arrow: + +```py +drug_dataset_clean.save_to_disk("drug-reviews") +``` + +Questo creerà un dizionario con la seguente struttura: + +``` +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 +``` + +dove possiamo vedere che ogni sezione è associata alla propria tavola *dataset.arrow*, e alcuni metadata sono salvati in *dataset_info.json* e *state.json*. Puoi pensare al formato Arrow come a una tavola sofisticata di colonne e righe, ottimizzata per costruire applicazioni ad alte prestazioni che processano e trasportanto grandi dataset. + +Una volta che il dataset è stato salvato, possiamo caricarlo utilizzando la funzione `load_from_disk()`: + +```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 + }) +}) +``` +Per i formati CSV e JSON, dobbiamo salvare ogni sezione come file separato. Un modo per farlo è iterando sulle chiavi e i valori dell'oggetti `DatasetDict`: + +```py +for split, dataset in drug_dataset_clean.items(): + dataset.to_json(f"drug-reviews-{split}.jsonl") +``` + +Questo salva ogni sezione in [formato JSON Lines](https://jsonlines.org), in cui ogni riga del dataset è salvata come una singola riga di JSON. Ecco come appare il primo esempio: + +```py +!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} +``` + +Possiamo usare le tecniche studiate nella [sezione 2](/course/chapter5/2) per caricare i file JSON come segue: + +```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) +``` +E questo è tutto per la nostra visita nel data wrangling con 🤗 Datasets! Ora che abbiamo un dataset pulito su cui addestrare un modello, ecco alcune idee che potresti testare: + +1. Usa le tecniche studiate nel [Capitolo 3](/course/chapter3) per addestrare un classificatore che può predirre la condizione del pazionte sulla base della recensione del medicinale. +2. Usa la pipeline `summarization` del [Capitolo 1](/course/chapter1) per generare riassunti delle recensioni. + +In seguito, daremo un'occhiata a come 🤗 Datasets ti permette di lavorare su enormi dataset senza far scoppiare il tuo portatile! diff --git a/chapters/it/chapter5/4.mdx b/chapters/it/chapter5/4.mdx new file mode 100644 index 000000000..682ef8719 --- /dev/null +++ b/chapters/it/chapter5/4.mdx @@ -0,0 +1,288 @@ +# Big data? Ci pensa 🤗 Datasets! + + + + +Al giorno d'oggi non è raro trovarsi a lavorare con dataset grandi diversi gigabyte, soprattutto quando si vuole addestrare un transformer come BERT o GPT-2 da zero. In questi casi, persino _caricare_ i dati può essere un'impresa difficile. Ad esempio, il corpus WebText utilizzato per preaddestrare GPT-2 contiente più di 8 milioni di documenti e 40gb di testo -- caricare un dataset del genere sulla RAM del tuo portatile gli farebbe venire un colpo! + +Per fortuna, 🤗 Datasets è stato sviluppato per superare queste limitazioni, e può risolvere i problemi relativi alla gestione della memoria trattando i dataset come file _memory-mapped_, e quelli relativi ai limiti del disco rigido attraverso lo _stream processing_ delle voci del corpus. + + + +In questa sezione esploreremo queste funzionalità di 🤗 Datasets con un enorme corpus di 825 GB conosciuto come [Pile](https://pile.eleuther.ai). Iniziamo! + +## Cos'è Pile? + +The Pile è un corpus testuale creato da [EleutherAI](https://www.eleuther.ai) per addestrare modelli di linguaggio su grande scala. Include un grande varietà di dataset, a partire da articoli scientifici, repository di codici da GitHub, e testi dal web filtrati. Il corpus di addestramento è disponibili in [frammenti da 14 GB](https://mystic.the-eye.eu/public/AI/pile/), ed è possibile scaricare diverse delle [componenti singole](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Iniziamo dando uno sguardo al dataset PubMed Abstracts, un corpus di abstract da 15 milioni di pubblicazioni in ambito biomedico da [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Il dataset è in [formato JSON Lines](https://jsonlines.org) ed è stato compressato usando la libreria `zstandard`, per cui dobbiamo prima installarla: + + +```py +!pip install zstandard +``` + +Ora, possiamo caricare il dataset utilizzando il meotodo per file remoti che abbiamo visto nella [sezione 2](/course/chapter5/2): + +```py +from datasets import load_dataset + +# Ci vuole qualche minuto per l'esecuzione, quindi preparati un tè o un caffè nell'attesa :) +data_files = "https://mystic.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 +}) +``` + +Possiamo vedere che ci sono 15.518.009 righe e 2 colonne nel nostro dataset -- un bel po'! + + + +✎ Di base, 🤗 Datasets decomprimerà i file necessari a caricare un dataset. Se vuoi risparmiare sullo spazio dell'hard disk, puoi passare `DownloadConfig(delete_extracted_True)` all'argomento `download_config` di `load_dataset()`. Per maggiori dettagli leggi la [documentazione](https://huggingface.co/docs/datasets/package_reference/builder_classes.html?#datasets.utils.DownloadConfig). + + + +Ispezioniamo i contenuti del primo esempio: + +```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, questo sembra proprio l'abstract di un articolo di medicina. Ora vediamo quanta RAM è stata usata per caricare il dataset! + +## La magia del memory mapping + +Un modo semplice per calcolare l'uso di memoria su Python è utilizzando la libreria [`psutil`](https://psutil.readthedocs.io/en/latest/), che può essere installata con `pip` come segue: + +```python +!pip install psutil +``` + +`psutil` offre una classe `Process` che permette di controllare l'utilizzo della memoria del processo attuale come segue:: + +```py +import psutil + +# Process.memory_info mostra i dati in byte, quindi convertiamo in megabyte +print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +``` + +```python out +RAM used: 5678.33 MB +``` + +L'attributo `rss` qui fa riferimento alla _grandezza del resident set_, che equivale alla frazione di memoria che il processo occupa nella RAM. Questo valore include inoltre la memoria utilizzata dall'interprete Python e dalle librerie caricate, per cui l'ammontare effettivo utilizzato per caricare il dataset è un po' più piccolo. Per fare un confronto, vediamo quant'è grande il dataset su disco utilizzando l'attributo `dataset_size`. Come prima, il risultato è espresso in byte, e abbiamo bisogno di convertirlo in 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 +``` + +Bene -- nonostante sia grande quasi 30 GB, siamo in grado di caricare e accedere al dataset utilizzando molta meno RAM! + + + +✏️ **Provaci tu!** Scegli uno dei [subset](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) di Pile che è più grande della RAM del tuo PC o del tuo portatile, caricalo utilizzando 🤗 Datasets e calcola la quantità di RAM utilizzata. Nota che per avere un valore preciso, dovrai creare un nuovo processo. Puoi trovare le grandezze decompresse di ogni subset nella Tavola 1 dell'[articolo su Pile](https://arxiv.org/abs/2101.00027) + + + +Se hai dimestichezza con Pandas, questo risultato potrebbe sorprenderti, vista la famosa [regola di Wes Kinney](https://wesmckinney.com/blog/apache-arrow-pandas-internals/), ovvero che, in linea di massima, serve una RAM 5-10 volte più grande del dataset che vuoi caricare. Come fa 🤗 Datasets a risolvere questo problema di gestione della memoria? 🤗 Datasets tratta ogni dataset come un [file mappato in memoria](https://it.wikipedia.org/wiki/File_mappato_in_memoria), il che permette di avere un mapping tra la RAM e l'archiviazione dei file di sistema, che permette alla librera di accedere e operare su elementi del dataset senza doverli caricare completamente in memoria. + +I file mappati in memoria possono inoltre essre condivisi su più processi, il che permette a metodi come `Dataset.map()` di poter essere eseguiti in parallelo senza bisogno di spostare o copiare il dataset. Dietro le quinte, tutto ciò è realizzato dal formato di memoria [Apache Arrow](https://arrow.apache.org) e dalla libreria [`pyarrow`](https://arrow.apache.org/docs/python/index.html), che rendono più veloci il caricamento e il processamento dei dati. (per maggiori dettagli su Apache Arrow, e per un confronto con Pandas, dai un'occhiata al [post di Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) Per vederlo in azione, eseguiamo un piccolo test di velocità con un loop su tutti gli elementi nel dataset 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' +``` + +Abbiamo usato il modulo di Python `timeit` per calcolare il tempo di esecuzione impiegato da `code_snippet`. Tipicamente l'iterazione su un dataset impiega un tempo che va da un decimo di GB al secondo, a diversi GB al secondo. Questo funziona perfettamente per la maggior parte delle applicazioni, ma a volte avrai bisogno di lavorare con un dataset che è troppo grande persino per essere salvato sul tuo portatile. Ad esempio, se cercassimo di scaricare Pile per intero, avremo bisogno di 825 GB di spazio libero su disko! In questi casi, 🤗 Datasets permette di utilizzare processi di streaming che ci permettono di scaricare e accedere al volo ai dati, senza bisogno di scaricare l'intero dataset. Diamo un'occhiata a come funziona. + + + +💡 Nei notebook Jupyter, puoi cronometrare le celle utilizzando la [funzione magica `%%timeit`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit) + + + +## Streaming di dataset + +Per abilitare lo streaming dei dataset devi semplicemente passare l'argomento `streaming=True` alla funzione `load_dataset()`. Ad esempio, carichiamo un'altra volta il dataset PubMed Abstract, ma in modalità streaming: + +```py +pubmed_dataset_streamed = load_dataset( + "json", data_files=data_files, split="train", streaming=True +) +``` + +Invece del solito `Dataset` che abbiamo incontrato in precedenza in questo capitolo, l'oggetto ritornato con `streaming=True' è un `IterableDataset`. Come suggerito dal nome, per accedere agli elementi di un `IterableDataset`, dobbiamo iterare di esso. Possiamo accedere al primo elemento del nostro dataset in streaming come segue: + +```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 ...'} +``` + +Gli elementi di un dataset in streaming possono essere processati al volo utilizzando `IterableDataset.map()`, che è utile durante l'addestramento se hai bisogno di tokenizzare gli input. Il processo è uguale a quello che abbiamo utilizzato per tokenizzare il nostro dataset nel [Capitolo 3](/course/chapter3), con l'unica differenza che ora ritorneremo gli output uno alla volta: + +```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, ...]} +``` + + + +💡 Per velocizzare la tokenizzazione con lo streaming puoi passare `batchet=True`, come abbiamo visto nell'ultima sezione. Questo processerà gli esempi per batch. Di default, la grandezza di un batch è 1.000, e può essere specificata attraverso l'argomento `batch_size`. + + + +È anche possibile mescolare un dataset in streaming utilizzato `Iterabledataset.shuffle()`, ma a differenza di `Dataset.shuffle()`, questo metodo mescola solo gli elementi in un `buffer_size` predefinito: + +```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 ...'} +``` + +In questo esempio, abbiamo selezionato un esempio casuale dai primi 10.000 esempi nel buffer. Una volta che accediamo a un esempio, il suo posto nel buffer è subito occupato dall'esempio successivo nel corpus (in questo caso l'esempio 10.0001). Puoi inoltre selezionare gli elementi da un dataset in streaming utilizzando le funzioni `IterableDataset.take()` a `IterableDataset.skip()`, che funzionano un po' come `Dataset.select()`. Ad esempio, per selezionare i primi 5 esempi nel dataset PubMed Abstract dovremmo fare come segue: + +```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 ...'}] +``` + +Allo stesso modo, è possibile utilizzare la funzione `IterableDataset.skip()` per creare sezioni di addestramento e di validazione da un dataset mescolato, come segue: + +```py +# Salta i primi 1.000 esempi, il resto viene incluso nell'insieme di addestramento +train_dataset = shuffled_dataset.skip(1000) +# Includi i primi 1.000 esempi nell'insieme di validazione +validation_dataset = shuffled_dataset.take(1000) +``` + +Concludiamo la nostra ricognizione dello streaming di dataset con un'applicazione comune: la combinazione di più dataset per creare un unico corpus. 🤗 Datasets fornisce una funzione `interleave_datasets()`, che converte una lista di oggetti `IterableDataset` in un unico `IterableDataset`, dove gli elementi del nuovo dataset sono ottenuti alternando tra gli esempi forniti. Questa funzione è particolarmente utile quando cerchiamo di combinare dataset di grandi dimensioni, come esempio possiamo utilizzare in streaming la sezione FreeLaw del Pile, un dataset di 51 GB di pareri legali dai tribunali statunitensi: + + +```py +law_dataset_streamed = load_dataset( + "json", + data_files="https://mystic.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...'} +``` + +Questo dataset è abbastanza grande da mettere sotto sforzo la RAM di molto portatili, ma siamo riusciti a caricarlo e accedervi senza alcun problema! Ora cominiamo gli esempi di FreeLaw e di PubMed Abstracts con la funzione `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...'}] +``` + +Abbiamo utilizzato la funzione `islice()` del modulo Python `itertools` per selezionare i primi due esempi dai dataset combinati, e abbiamo visto che corrispondono ai primi esempi di ognuno dei due dataset originali. + +Infine, se vuoi processare il Pile in streaming, in tutti i suoi 825 GB, puoi recuperare tutti i file preparati, come segue: + +```py +base_url = "https://mystic.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...'} +``` + + + +✏️ **Prova tu!** Usa uno dei corpora Common Crawl come [`mc4`](https://huggingface.co/datasets/mc4) oppure [`oscar`](https://huggingface.co/datasets/oscar) per crare un dataset multilingue in streaming, che rappresenta le proporzioni delle lingue parlate in un paese a tua scelta. Ad esempio, le quattro lingue ufficiali in Svizzera sono il tedesco, il francesce, l'italiano e il romancio, per cui potresti creare un corpus della Svizzera raccogliendo i campioni da Oscar, secondo la percentuale di parlanti di ognuna. + + + +Ora hai a tua disposizione tutti gli strumenti per caricare e processare dataset di ogni tipo -- ma a meno che tu non sia estremamente fortunato, arriverà un momento nel tuo cammino in cui dovrai effettivamente creare un dataset per risolvere i tuoi problemi. Questo sarà argomento della prossima sezione! diff --git a/chapters/it/chapter5/5.mdx b/chapters/it/chapter5/5.mdx new file mode 100644 index 000000000..be262d885 --- /dev/null +++ b/chapters/it/chapter5/5.mdx @@ -0,0 +1,470 @@ +# Creare il proprio dataset + + + +A volte il dataset che ti serve per la tua applicazione NLP non esiste, per cui dovrai crearlo da te. In questa sezione ti mostreremo come creare un corpus di [issue da GitHub](https://github.com/features/issues), usate solitamente per tenere traccia dei bug e delle feature nelle repository su GitHub. Questo corpus può essere usato in diversi modi, ad esempio: + +* Esplorare il tempo impiegato per chiudere un issue, o per effettuare dei pull +* Addestrare un _classificatore multiclasse_ che assegna a ogni issue dei metadati sulla base della descrizione dell'issue (ad esempio, "bug", "enhancement", "question") +* Creare un motore di ricerca semantico per trovare quale issue corrisponde a una richiesta dell'utente + +Ci focalizzeremo sulla creazione del corpus, e nella prossima sezione affronteremo la creazione di un motore di ricerca semantico. Useremo gli issue GitHub associate a un progetto open source molto popolare: 🤗 Datasets! Diamo un'occhiata a come recuperare i dati e come esplorare le informazioni contenute negli issue. + +## Recuperare i dati + +Puoi trovare tutte gli issue in 🤗 Datasets navigando nella [sezione Issues della repository](https://github.com/huggingface/datasets/issues). Come si vede dallo screenshot, al momento della scrittura c'erano 331 issue aperti e 668 issue chiusi. + +
+The GitHub issues associated with 🤗 Datasets. +
+ +Se clicchi su una di questi issue vedrai che contiene un titolo, una descrizione, e un set di etichette che caratterizzano l'issue. Un esempio è mostrato nello screenshot successivo. + +
+A typical GitHub issue in the 🤗 Datasets repository. +
+ +Per scaricare gli issue della repository, useremo la [REST API di GitHub](https://docs.github.com/en/rest) per interrogare l'[endpoint `Issues`](https://docs.github.com/en/rest/reference/issues#list-repository-issues). Questo endpoint restituisce una lista di oggetti JSON, e ogni oggetto contiene un gran numero di campi, tra cui il titolo e la descrizione, così come dei metadati circo lo status dell'issue e altro ancora. + +Una maniera conveniente di scaricare gli issue è attraverso la libreria `requests`, che rappresenta il metodo standard di fare richieste HTTP su Python. Puoi installa la libreria attraverso il codice: + +```python +!pip install requests +``` + +Una volta che la libreria è stata installata, puoi effettuare una richiesta GET all'endpoint `Issues` utilizzando la funzione `requests.get()`. Ad esempio, puoi eseguire il comando mostrato di seguito per recuperare il primo issue nella prima pagina: + +```py +import requests + +url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1" +response = requests.get(url) +``` + +L'oggetto `response` contiene un sacco di informazioni utili sulla richiesta, compreso il codice di stato HTTP: + +```py +response.status_code +``` + +```python out +200 +``` + +Lo status `200` indica che la richiesta ha avuto buon fine (puoi trovare una lista di codici di stato HTTTP [qui](https://it.wikipedia.org/wiki/Codici_di_stato_HTTP)). Ma ciò che ci interessa davvero è il _payload_, a cui è possibile accedere utilizzando diversi formati come byte, stringh, o JSON. Visto che sappiamo che i nostri issue sono in formato JSON, diamo un'occhiata al payload come segue: + +```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}] +``` + +Wow, quante informazioni! Possiamo vedere alcuni campi utili come `title`, `body` e `number` che descrivono l'issue, così come informazioni sull'utente che l'ha aperto. + + + +✏️ **Prova tu!** Clicca su alcuni degli URL nel payload JSON per farti un'idea del tipo di informazione a cui è collegato ogni issue GitHub. + + + +Come descritto nella [documentazione di GitHub](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), le richieste senza autenticazione sono limitate a 60 ogni ora. Benché possiamo aumentare il parametro della query `per_page` per ridurre il numero di richieste, raggiungerai comunque il limite su qualunque repository che ha qualche migliaio di issue. Quindi, dovresti seguire le [istruzioni](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) su come creare un _token di accesso personale_ così che puoi aumentare il limite a 5.000 richieste ogni ora. Una volta che hai ottenuto il tuo token, puoi includerlo come parte dell'header della richiesta: + +```py +GITHUB_TOKEN = xxx # inserisci qui il tuo token GitHub +headers = {"Authorization": f"token {GITHUB_TOKEN}"} +``` + + + +⚠️ Fai attenzione a non condividere un notebook con il tuo `GITHUB_TOKEN` al suo interno. Ti consigliamo di cancellare l'ultima cella una volta che l'hai eseguita per evitare di far trapelare quest'informazione accidentalmente. Meglio ancora, salva il tuo token in un file *.env* e usa la [libreria `python-dotenv`](https://github.com/theskumar/python-dotenv) per caricarlo automaticamente come una variabile d'ambiente. + + + +Ora che abbiamo il nostro token di accesso, creiamo una funzione che scarichi tutti gli issue da una repository 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 # Numero di issue da restituire per pagina + num_pages = math.ceil(num_issues / per_page) + base_url = "https://api.github.com/repos" + + for page in tqdm(range(num_pages)): + # La query ha state=all per ottenere sia gli issue aperti che quelli chiusi + 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 = [] # puliamo la batch per il termine successivo + 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" + ) +``` + +Ora quando eseguiremo `fetch_issues()`, scaricherà tutti gli issue in batch per evitare di superare il limite di GitHub del numero di richieste per ora; il risultato sarà conservato in un file _repository_name-issues.jsonl_, in cui ogni linea è un oggetto JSON che rappresenta un issue. Usiamo questa funzione per recuperare tutti gli issue da 🤗 Datasets: + +```py +# A seconda della tua connessione internet, ci potrebbe volere qualche secondo... +fetch_issues() +``` + +Una volta che gli issue sono stati scaricati, possiamo caricarli in locale usando le nuove abilità imparate nella [sezione 2](/course/chaper5/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 +}) +``` + +Benissimo, abbiamo creato il nostro primo dataset da zero! Ma perché ci sono migliaia di issue quando la [sezione Issues](https://github.com/huggingface/datasets/issues) della repository 🤗 Datasets mostra circa 1,000 issue in totale 🤔? Come indicato nella [documentazione di GitHub](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user), è perché abbiamo scaricato anche le richieste di pull: + +> 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. + +(_La REST API v3 di GitHub considera ogni richiesta di pull un issue, ma non ogni issue è una richiesta di pull. Per questa ragione, gli endpoint "Issues" potrebbe tornare sia gli issue che le richieste di pull. È possibile identificare le richieste di pull utilizzando la chiave `pull_request`. Tieni presente che l'`id` di una richiesta di pull resituita dagli endpoint `Issues` sarà un id di un issue._) + +Poichè i contenuti degli issue e delle richieste di pull sono molto diversi, facciamo un po' di preprocessing per permetterci di distinguere tra i due. + +## Pulire i dati + +Il frammento precedente della documentazione di GitHub ci dice che la colonna `pull_request` può essere utilizzata per distinguere gli issue e le richieste di pull. Diamo uno sguardo a un esempio casuale per vedere qual è la differenza. Come abbiamo fatto nella [sezione 3](/course/chapter5/3), concateneremo `Dataset.shuffle()` e `Dataset.select()` per creare un campione random, e poi zipperemo le colonne `html_url` e `pull_request` così da poter paragonare i diversi URL: + +```py +sample = issues_dataset.shuffle(seed=666).select(range(3)) + +# Stampiamo le entrate `URL` e `pull_request` +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'} +``` + +Possiamo vedere che ogni richiesta di pull è associata a diversi URL, mentre i comuni issue hanno un'entrata `None`. Possiamo usare questa distinzione per crare una nuova colonna `is_pull_request` che controlla se il campo `pull_request` sia `None` o meno: + +```py +issues_dataset = issues_dataset.map( + lambda x: {"is_pull_request": False if x["pull_request"] is None else True} +) +``` + + + +✏️ **Prova tu!** Calcola il tempo medio che ci vuole a chiudere un issue su 🤗 Datasets. Potrebbe essere utile usare la funzione `Dataset.filter()` per eliminare le richieste di pull e gli issue aperti, e puoi usare la funzione `Dataset.set_format()` per convertire il dataset in un `DataFrame` così che puoi facilmente manipolare i timestamp `created_at` e `closed_at`. Per dei punti bonus, calcola il tempo medio che ci vuole a chiudere le richieste di pull. + + + +Benché potremmo procedere e pulire ulteriormente il dataset eliminando o rinominando alcune colonne, è solitamente buona prassi lasciare il dataset quando più intatto è possibile in questo stadio, così che può essere utilizzato facilmente in più applicazioni. + +Prima di caricare il nostro dataset sull'Hub Hugging Face, dobbiamo occuparci di una cosa che manca: i commenti associati a ogni issue e richiesta di pull. Hai indovinato, li aggiungeremo utilizzando la REST API di GitHub! + +## Estendere il dataset + +Come mostrato negli screenshot di seguito, i commenti associati a un issue o una richiesta di pull offrono una fonte molto ricca di informazioni, soprattutto se siamo interessati a costruire un motore di ricerca per rispondere alle richieste degli utenti sulla libreria. + +
+Comments associated with an issue about 🤗 Datasets. +
+ +La REST API di GitHub offre un [endpoint `Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) che restituisce tutti i commenti associati con un numero di issue. Testiamo quest'endpoint per vedere cosa restituisce: + +```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}] +``` + +Possiamo vedere che il commento è archiviato nel campo `body`, quindi possiamo scvrivere una semplice funzione che restituisce tutti i commenti associati con un issue estraendo i contenuti di `body` per ogni elemento in `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()] + + +# Testiamo la nostra funzione +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?"] +``` + +Sembra andar bene, quindi possiamo usare `Dataset.map()` per aggiungere una nuova colonna `comments` a ogni usse nel nostro dataset: + +```py +# A seconda della tua connessione, potrebbe volerci qualche secondo... +issues_with_comments_dataset = issues_dataset.map( + lambda x: {"comments": get_comments(x["number"])} +) +``` + +Come passaggio finale, salviamo il dataset esteso assieme ai nostri dati non processati, così da poter caricare entrambi sull'Hub: + +```py +issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") +``` + +## Caricare il dataset sull'Hub Hugging Face + + + +Ora che abbiamo il nostro dataset esteso, è arrivato il momento di caricarlo sull'Hub, così da poterlo condividere con la community! Per caricare il dataset useremo la [libreria 🤗 Hub](https://github.com/huggingface/huggingface_hub), che ci permette di interagire con l'Hub di Hugging Face attraverso un'API di Python. 🤗 Hub è preinstallato con 🤗 Transformers, così possiamo usarlo da subito. Ad esempio, possiamo usare la funzione `list_datastes()` per avere informazioni su tutti i dataset pubblici attualmente presenti sull'Hub: + +```py +from huggingface_hub import list_datasets + +all_datasets = list_datasets() +print(f"Number of datasets on Hub: {len(all_datasets)}") +print(all_datasets[0]) +``` + +```python out +Number of datasets on Hub: 1487 +Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K + +✏️ **Prova tu!** Usa le tue credenziali dell'Hub Hugging Face per ottenere un token e creare una repository vuota chiamata `github-issues`. Ricordati di **non salvere mai le tue credenziali** su Colab o qualunque altra repository, perché potrebbero essere recuperate da malintenzionati. + + + +Ora, cloniamo la repository dall'Hub alla nostra macchina e copiamo al suo interno i file del nostro dataset. 🤗 Hub contiene una classe `Repository` che ha al suo interno molti dei comandi più comuni di Git, per cui per clonare la repository in remoto dobbiamo semplicemente fornire l'URL e il percorso locale in cui desideriamo clonare: + +```py +from huggingface_hub import Repository + +repo = Repository(local_dir="github-issues", clone_from=repo_url) +!cp issues-datasets-with-comments.jsonl github-issues/ +``` + +Di default, diverse estensioni file (ad esempio *.bin*, *.gz* e *.zip*) sono registrate da Git LFS, così che i file di grandi dimensioni possono essere gestiti all'interno dello stesso workflow. Puoi trovare una lista delle estensioni di file monitorati nel file *.gitattributes* della repository. Per includere il formato JSON Lines a questa lista, possiamo utilizzare il comando: + +```py +repo.lfs_track("*.jsonl") +``` + +Ora possiamo usare `Repository.push_to_hub()` per caricare il dataset sull'Hub: + +```py +repo.push_to_hub() +``` + +Se navighiamo fino all'URL contenuto in `repo_url`, vedremo che il file del nostro dataset è stato caricato. + +
+Our dataset repository on the Hugging Face Hub. +
+ +Da qui, chiunque può scaricare il dataset semplicemente inserendo l'ID della repository come argomento `path` di `load_dataset()`: + +```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 +}) +``` + +Bene, abbiamo caricato il nostro dataset sull'Hub, e può essere utilizzato da tutti! C'è un'altra cosa importante che dobbiamo fare: aggiungere una _dataset card_ che spiega come è stato creato il corpus, e offre altre informazioni utili per la community. + + + +💡 Puoi caricare un dataset nell'Hub di Hugging Face anche direttamente dal terminale usando `huggingface-cli` e un po' di magia Git. La [guida a 🤗 Datasets](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) spiega come farlo. + + + +## Creare una dataset card + +I dataset ben-documentati sono più utili agli altri utenti (compreso il futuro te!), poiché spiegano il contesto per permettere agli utenti di decidere se un dataset può essere utile, e valutare gli eventuali bias o rischi associati nell'utilizzo del dataset. + +Sull'Hug di Hugging Face, queste informazioni si trovano nel file *README.md* della repository. Ci sono due passaggi principali che dovresti seguire prima di creare questo file: + +1. Usa l'[applicatione `datasets-tagging`](https://huggingface.co/datasets/tagging/) per creare tag di metadati in formato YAML. Questi tag sono usato per una serie di funzioni di ricerca sull'Hub di Hugging Face, e assicurano che il tuo dataset possa essere facilmente trovato dai membri della community. Poichè abbiamo creato un nostro dataset, dovrai clonare la repository `datasets-tagging`, ed eseguire l'applicazione in locale. Ecco com'è l'interfaccia: + +
+The `datasets-tagging` interface. +
+ +2. Leggi la [guida 🤗 Datasets](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sulla creazione di dataset card informative, e usala come template. + +Puoi creare il file *README.md* direttamente sull'Hub, e puoi trovare un modello per una dataset card nella repository `lewtun/github-issues`. Di seguito è mostrato uno screenshot di una dataset card già compilata. + +
+A dataset card. +
+ + + +✏️ **Prova tu!** Usa l'applicazione `dataset-tagging` e la [guida 🤗 Datasets](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) per completare il file *README.md* per il tuo dataset di issue di GitHub. + + +È tutto! Abbiamo visto in questa sezione che creare un buon dataset può essere un'impresa, ma per fortuna caricarlo e condividerlo con la community è molto più semplice. Nella prossima sezione useremo il nostro nuovo dataset per creare un motore di ricerca semantico con 🤗 Datasets, che abbina alle domande gli issue e i commenti più rilevanti. + + + +✏️ **Prova tu!** Segui i passi che abbiamo eseguito in questa sezione per creare un dataset di issue GitHub per la tua libreria open source preferita (ovviamente scegli qualcosa di diverso da 🤗 Datasets!). Per punti bonus, esegui il fine-tuning di un classificatore multiclasse per predirre i tag presenti nel campo `labels`. + + + + diff --git a/chapters/it/chapter5/6.mdx b/chapters/it/chapter5/6.mdx new file mode 100644 index 000000000..087e457c6 --- /dev/null +++ b/chapters/it/chapter5/6.mdx @@ -0,0 +1,531 @@ + + +# Ricerca semantica con FAISS + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Nella [sezione 5](/course/chapter5/5) abbiamo creato un dataset di issue e commenti dalla repository GitHub di 🤗 Datasets. In questa sezione useremo queste informazioni per costrure un motore di ricerca semantico che ci può aiutare a trovare risposte alle nostre domande urgenti sulla libreria! + + + +## Usare gli embedding per la ricerca semantica + +Come abbiamo visto nel [Capitolo 1](/course/chapter1), i language model basati su Transformer rappresentano ogni token in un testo come un _vettore_, detto _embedding_. È possibile "mettere insieme" i diversi embedding per creare una rappresentazione vettoriale di un'intera frase, paragrafo o (in alcuni casi) documento. Questi embedding possono essere usati per trovare documenti simili in un corpus calcolandone la similarità, ad esempio usando il prodotto scalere (o altre misure di similarità) tra ogni embedding, e restituendo i documenti più simili. + +In questa sezione useremo gli embedding per sviluppare un motore di ricerca semantico. Questi motori di ricerca offrono diversi vantagig rispetto ai metodo convenzionali, basati sulla ricerca, all'interno dei documenti, delle parole chiavi presente in una query. + +
+Semantic search. + +
+ +## Caricare e preparare il dataset + +La prima cosa che dobbiamo fare è scaricare il nostro dataset di issue, quindi utilizziamo la libreria 🤗 Hub per scaricare i file usando l'URL dell'Hub Hugging Face: + +```py +from huggingface_hub import hf_hub_url + +data_files = hf_hub_url( + repo_id="lewtun/github-issues", + filename="datasets-issues-with-comments.jsonl", + repo_type="dataset", +) +``` + +Se conseriamo l'URL iin `data_files`, possiamo caricare il dataset utilizzando il metodo introdotto nella [sezione 2](/course/chapter5/2): + +```py +from datasets import load_dataset + +issues_dataset = load_dataset("json", data_files=data_files, 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 +}) +``` + +Qui abbiamo specificato la sezione di defaul `train` in `load_dataset()`, così che questa funzione resituisce un `Dataset` invece di un `DatasetDict`. La prima cosa da fare è filtrare le richieste di pull, poichè queste tendono a essere usate raramente come risposta alle domande degli utenti, e introdurrebbero rumore nel nostro motore di ricerca. Come dovrebbe esser enoto, possiamo usare la funzione `Dataset.filter()` per escludere questi dati dal nostro dataset. Già che ci siamo, eliminiamo anche le righe senza commenti, poiché queste non presentano nessuna risposta alle domande degli utenti: + +```py +issues_dataset = issues_dataset.filter( + lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0) +) +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: 771 +}) +``` + +Possiamo vedere che ci sono molte colonne nel nostro dataset, molte delle quali non servono alla costruzione del nostro motore di ricerca. Da una prospettiva di ricerca, le colonne maggiormente informative sono `title`, `body`, e `comments`, mentre `html_url` ci fornisce un link all'issue originale. Usiamo la funzione `Dataset.remove_columns()` per eliminare le colonne rimanenti: + +```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 +}) +``` + +Per crare i nostri embedding arricchiremo ognu commento con il titolo e il corpo dell'issue, visto che questi campi spesso includono informazioni utili sul contesto. Poiché la nostra colonna `comment` è al momento una lista di commenti per ogni issue, dobbiamo "farla esplodere" così che ogni riga consista in una tupla `(html_url, title, body, comment)`. In panda è possibile farlo utilizzando la [funzione `Dataframe.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), che crea una nuova riga per ogni elemento in una colonna in formato di lista, ripetendo i valori di tutte le altre colonne. Per vederlo in azione, prima di tutto passiamo al formato `DataFrame`: + +```py +issues_dataset.set_format("pandas") +df = issues_dataset[:] +``` + +Se diamo un'occhiata alla prima riga di questo `DataFrame`, possiamo vedere che ci sono quattro commenti associati con quest'issue: + +```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...'] +``` + +Quando "esplodiamo" `df`, ci aspettiamo di avere una riga per ognuno di questi commenti. Controlliamo se è così: + +```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...
+ +bene, possiamo vedere che le righe sono state duplicate, e che la colonna `comment` contiene i diversi comment! Ora che abbiamo finito con Pandas, possiamo passare velocemente a `Dataset` caricando il `DataFrame` in memoria: + +```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 +}) +``` + +Perfetto, ora abbiamo qualche migliaio di commenti con cui lavorare! + + + + +✏️ **Prova tu!** Prova ad utilizzare `Dataset.map()` per far esplodere la colonna `commenti` di `issues_dataset` _senza_ utilizzare Pandas. È un po' difficile: potrebbe tornarti utile la sezione ["Batch mapping"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) della documentazione di 🤗 Datasets. + + + +Ora che abbiamo un commento per riga, creiamo una nuova colonna `comments_length` che contiene il numero di parole per ogni commento: + +```py +comments_dataset = comments_dataset.map( + lambda x: {"comment_length": len(x["comments"].split())} +) +``` + +Possiamo usare questa nuova colonna per eliminare i commenti brevi, che solitamente includono cose del tipo "cc @lewtun" o "Grazie!", che non sono pertinenti per il nostro motore di ricerca. Non abbiamo un numero preciso da selezionare per questo filtro, ma 15 parole dovrebbero andare bene: + +```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 +}) +``` + +Una volta data una pulizia al nostro dataset, possiamo concatenare il titolo, la descrizione e i commenti delle issue in una nuova colonna `text`. Come al solito , scriveremo una semplice funzione che possiamo passare a `Dataset.map()`: + +```py +def concatenate_text(examples): + return { + "text": examples["title"] + + " \n " + + examples["body"] + + " \n " + + examples["comments"] + } + + +comments_dataset = comments_dataset.map(concatenate_text) +``` + +Siamo finalmente pronti a creare degli embedding! Diamo un'occhiata. + +## Creare i text embedding + +Abbiamo visto nel [Capitolo 2](/course/chapter2) che possiamo ottenere i token embedding utilizando la classe `AutoModel`. Dobbiamo solo scegliere un checkpoint valido da cui caricare il modell. Per fortuna, esiste una libreria chiamata `sentence-transformers`, dedicata alla creazione di embedding. Seguendo la descrizione nella [documentazione](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search)della libreria, il nostro caso d'uso è un esempio di _asymmetric semantic search_ perché abbiamo una breve query per cui vogliamo trovare risposte in un documento lungo, come ad esempio un commento a un issue. La [scheda di riepilogo dei modelli](https://www.sbert.net/docs/pretrained_models.html#model-overview) nella documentazione ci indica che il checkpoint `multi-qa-mpnet-base-dot-v1` ha mostrato la performance migliore per la ricerca semantica, quindi è quello che useremo per la nostra applicazione. Caricheremo anche il tokenizzatore usando lo stesso 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) +``` + +Per accelerare il processo di embedding, è bene usare la GPU per il modello e gli input, quindi: + +```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) +``` + +Nota che abbiamo impostato `from_pt=True` come argomento del metodo `from_pretrained()`. Questo perchè il checkpoint `multi-qa-mpnet-base-dot-v1` ha solo pesi PyTorch, quindi impostare `from_pt=True` li convertirà automaticamente in formato TensorFlow. Come puoi vedere, è molto facile passare dall'uno all'altro su 🤗 Transformers! + +{/if} + +Come abbiamo già detto prima, vorremmo rappresentare ogni entrata nel nostro corpus di issue GitHub come un vettore singolo, per cui avremo bisogno di calcolare la media, o il "pool" dei nostri token embedding. Un metodo comune è di effettuare un *CLS pooling* sull'output del nostro modello: questa tecnica su basa sul recuperare semplicemente l'ultimo stato nascosto del token speciale `[CLS]`. La funzione seguente fa proprio questo: + +```py +def cls_pooling(model_output): + return model_output.last_hidden_state[:, 0] +``` + +Poi, creeremo una funzione di supporto che: tokenizza una lista di documenti, inserire i tensori sulla GPU, li usa come input per il modello, e infine applica il CLS pooling agli output: + +{#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) +``` + +Possiamo testare la funzione sul primo testo nel nostro corpus, e ispezionandone le dimensioni dell'ouput: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +Bene, abbiamo convertito la prima voce del nostro corpus in un vettore a 768 dimensioni! Possiamo usare `Dataset.map()` per applicare la nostra funzione `get_embedding()` a ogni riga del nostro corpus, quindi creiamo una nuova colonna `embedding` così: + +```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) +``` + +Possiamo testare la funzione dandole in input la prima voce testuale del nostro corpus e studiando le dimensioni dell'output: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +TensorShape([1, 768]) +``` + +Bene, abbiamo convertito la prima voce del nostro corpus in un vettore a 768 dimensioni! Possiamo usare `Dataset.map()` per applicare la nostra funzione `get_embedding()` a ogni riga del nostro corpus, quindi creiamo una nuova colonna `embedding` così: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} +) +``` + +{/if} + +Node che abbiamo convertito gli embedding in array NumPy -- questo perchè 🤗 Datasets ha bisogno di questo formato per indicizzare gli embedding con FAISS, che è ciò che faremo nella prossima sezione. + + +## Usare FAISS per ricerca di similarità efficiente + +Ora che abbiamo un dataset di embedding, abbiamo bisogno di un modo per effettuare una ricerca. Per far ciò, useremo una struttura specialie di 🤗 Datasets +chiamato _indice FAISS_. [FAISS](https://faiss.ai/) (Facebook AI Similarity Search) è una libreria che permette di utilizzare algoritmi efficient per ricercare e raggruppare gli embedding. + +L'idea di base dietro FAISS è di creare un formato speciale di dati chiamato _indice_ che permette di trovare quali embedding sono simili a un embedding in input. Creare un indice FAISS su 🤗 Datasets è semplice -- usiamo la funzione `Dataset.add_faiss_index()` e specificare quale colonna nel nostro dataset vorremmo indicizzare: + +```py +embeddings_dataset.add_faiss_index(column="embeddings") +``` + +Ora possiamo eseguire dele query su questo indice effettuando una ricerca degli elementi più vicini usando la funzione `Dataset.get_nearest_examples()`. Testiamolo creando un embedding per una domanda. + +{#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} + +Proprio come con i documenti, ora abbiamo un vettore di 768 dimensioni che rappresenta la query, che possiamo confrontare con l'intero corpus per trovare gli embedding più simili: + +```py +scores, samples = embeddings_dataset.get_nearest_examples( + "embeddings", question_embedding, k=5 +) +``` + +La funzione `Dataset.get_nearest_examples()` restituisce una tupla di valori che valutano la sovrapposizione tra la query e il documento, e un set corrispondente di campioni (in questo caso, le 5 corrispondenze migliori). Salviamole in un `pandas.DataFrame`, così che possiamo ordinarle facilmente: + +```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) +``` + +Ora possiamo iterare sulle prime righe per vedere quanto bene la nostra query corrisponde ai commenti disponibili: + +```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 +================================================== +""" +``` + +Non male! Il nostro secondo risultato sembra soddisfare la nostra richiesta. + + + +✏️ **Prova tu!** Crea la tua query e prova a trovare una risposta tra i documenti raccolti. Potresti aver bisogno di aumentare il parametro `k` in `Dataset.get_nearest_examples()` per allargare la ricerca. + + diff --git a/chapters/it/chapter5/7.mdx b/chapters/it/chapter5/7.mdx new file mode 100644 index 000000000..118b04511 --- /dev/null +++ b/chapters/it/chapter5/7.mdx @@ -0,0 +1,10 @@ +# 🤗 Datasets, check! + +Beh, abbiamo fatto un bel giro nella libreria 🤗 Datasets: complimenti per aver raggiunto quest'obiettivo! Con le conoscenze che hai ottenuto da questo capitolo, sei in grado di: +- Caricare dataset da ogni luogo, sia esso l'Hub di Hugging Face, il tuo portatile, o un server in remoto della tua compagnia. +- Fare data-wrangling usando un mix delle funzioni `Dataset.map()` e `Dataset.filter()`. +- Passare velocemente tra diversi formati di dati domce Pandas e NumPy usando `Dataset.set_format()`. +- Creare il tuo dataset e condividerlo sull'Hub Hugging Face. +- Creare embedding dei tuoi documenti usando un modello Transformer, e costruire un motore di ricerca semantico usando FAISS. + +Nel [Capitolo 7](/course/chapter7), faremo buon uso di tutto questo mentre ci avventureremo nei task principali NLP, per i quali i modelli Transformer sono un'ottima soluzione. Prima di andare oltre, però, metti alla prova la tua conoscenza di 🤗 Datasets con un quiz! diff --git a/chapters/it/chapter5/8.mdx b/chapters/it/chapter5/8.mdx new file mode 100644 index 000000000..5fece0b6a --- /dev/null +++ b/chapters/it/chapter5/8.mdx @@ -0,0 +1,225 @@ + + +# Quiz di fine capitolo + +In questo capitolo abbiamo fatto un bel po' di strada! Non preoccuparti se non hai colto tutti i dettagli; i capitoli successivi ti aiuteranno a capire come funzionano le cose dietro le quinte! + +Prima di andare oltre, mettiamo alla prova ciò che hai imparato in questo capitolo. + +### 1. Usando la funzione `load_dataset()` in 🤗 Datasets, da dove puoi caricare un dataset? + +data_files di load_dataset() per caricare dataset locali.", + correct: true + }, + { + text: "L'Hub Hugging Face.", + explain: "Corretto! Puoi caricare i dataset presenti sull'Hub fornendo l'ID del dataset, ad esempio load_dataset('emotion').", + correct: true + }, + { + text: "Un server remoto", + explain: "Corretto! Puoi passare un URL nell'argomento data_files di load_dataset() per caricare file in remoto.", + correct: true + }, + ]} +/> + +### 2. Immagina di caricare uno dei task GLUE come segue: + +```py +from datasets import load_dataset + +dataset = load_dataset("glue", "mrpc", split="train") +``` + +Quale dei comandi seguenti produce un campione di 50 elementi casuali da `dataset`? + +dataset.sample(50)", + explain: "Questa risposta è sbagliata -- non esiste nessun metodo Dataset.sample()." + }, + { + text: "dataset.shuffle().select(range(50))", + explain: "Corretto! Come hai visto in questo capitolo, puoi mescolare il dataset e selezionarne i campioni.", + correct: true + }, + { + text: "dataset.select(range(50)).shuffle()", + explain: "Questa risposta è sbagliata -- anche se il codice verrebbe eseguito, mescolerebbe solo i primi 50 elementi del dataset" + } + ]} +/> + +### 3. Immagina di avere un dataset sugli animali domestici, chiamto `pets_dataset`, che ha una colonna `name` che denota il nome di ogni animale. Quale degli approcci ci permetterebbe di filtrare il dataset e lasciare solo gli animali il cui nome inizia con la lettera "L"? +pets_dataset.filter(lambda x : x['name'].startswith('L'))", + explain: "Corretto! Usare una funzione lambda di Python per questi filtri veloci è un'ottima idea. Riesci a pensare a un'altra soluzione?", + correct: true + }, + { + text: "pets_dataset.filter(lambda x['name'].startswith('L'))", + explain: "Questa risposta è sbagliata: una funzione lambda ha la forma generica lambda *argomenti* : *espressione*, per cui devi esplicitare gli argomenti in questo caso." + }, + { + text: "Creare una funzione come def filter_names(x): return x['name'].startswith('L') ed eseguire pets_dataset.filter(filter_names).", + explain: "Corretto! Proprio come Dataset.map(), puoi passare delle funzioni esplicite a Dataset.filter(). Quest'opzione è utile quando hai un'espressione complessa che non è adatta a una funzione lambda. Quale altra soluzione potrebbe funzionare?", + correct: true + } + ]} +/> + +### 4. Cos'è il memory mapping? + + + +### 5. Quali dei seguenti sono i principali vantaggi del memory mapping? + + + +### 6. Cosa causa un errore nel codice seguente? + +```py +from datasets import load_dataset + +dataset = load_dataset("allocine", streaming=True, split="train") +dataset[0] +``` + +IterableDataset.", + explain: "Corretto! Un IterableDataset è un generatore e non un contenitore, per cui puoi accedere ai suoi elementi solo usando next(iter(dataset)).", + correct: true + }, + { + text: "Il dataset allocine non ha una sezione train.", + explain: "Questa risposta è sbagliata -- controlla le [informazioni sul dataset allocine](https://huggingface.co/datasets/allocine) sull'Hub per vedere quali sezioni contiente." + } + ]} +/> + +### 7. Quali dei seguenti sono i vantaggi principali di creare una dataset card? + + + + +### 8. Cos'è la ricerca semantica? + + + +### 9. Nelle ricerche semantiche asimmetriche, solitamente si hanno: + + + +### 10. Posso usare 🤗 Datasets per caricare dati utilizzabili in altri domini, come processamento del parlato? + +dataset MNIST sull'Hub per un esempio di dati per visione artificiale." + }, + { + text: "Sì", + explain: "Questa risposta è corretta! Controlla gli eccitanti sviluppi per il parlato e la visione artificiale nella libreria 🤗 Transformers per vedere come è utilizzato 🤗 Datasets in questi domini.", + correct : true + }, + ]} +/>