LLM Course documentation
E timpul să tăiem și să analizăm datele
E timpul să tăiem și să analizăm datele
În cea mai mare parte, datele cu care lucrezi nu vor fi perfect pregătite pentru antrenarea modelelor. În această secțiune vom explora features variate pe care 🤗 Datasets le oferă pentru curățirea dataseturilor.
Slicing și dicing asupra datelor
Asemenea Pandas, 🤗 Datasets oferă mai multe funcții pentru a manipula conținutul obiectelor Dataset
și DatasetDict
. Am întâlnit deja metoda Dataset.map()
în Capitolul 3, iar în această secțiune vom explora alte funcții de care dispunem.
În acest exemplu, vom folosi Drug Review Dataset găzduit pe UC Irvine Machine Learning Repository, care conține reviewurile pacienților privind diverse medicamente, alături de bolile care sunt tratate și o evaluare de 10 stele a satisfacției pacientului.
În primul rând trebuie să descărcăm și să extragem datele, ceea ce se poate de făcut cu comenzile wget
și unzip
:
!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip"
!unzip drugsCom_raw.zip
Deoarece TSV este o variantă a CSV care folosește taburi în loc de virgulă ca separator, putem încărca aceste fișiere prin folosirea scriptului de încărcare csv
și specificarea argumentului delimiter
în funcția load_dataset()
astfel:
from datasets import load_dataset
data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"}
# \t este caracterul tab de Python
drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t")
O practică bună atunci când faceți orice fel de analiză a datelor este să vă luați un mic random sample pentru a înțelege cu ce tip de date lucrați. În 🤗 Datasets, putem crea o colecție aleatorie prin legarea funcțiilor Dataset.shuffle()
și Dataset.select()
:
drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000))
# Vizualizați primele câteva exemple
drug_sample[:3]
{'Unnamed: 0': [87571, 178045, 80482],
'drugName': ['Naproxen', 'Duloxetine', 'Mobic'],
'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'],
'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"',
'"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."',
'"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'],
'rating': [9.0, 3.0, 10.0],
'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'],
'usefulCount': [36, 13, 128]}
Atrageți atenția că am fixat seedul în Dataset.shuffle()
pentru posibilitatea de reproducere. Dataset.select()
se așteaptă la un iterabil cu indices, deci noi am scris range(1000)
pentru a primi primele 1000 de exemple din datasetul amestecat. Din acest sample putem vedea câteva ciudățenii în datasetul nostru:
- Coloana
Unnamed: 0
are un aspect neobișnuit, care sugerează că este un anonymized ID a fiecărui pacient. - Coloana
condition
conține labeluri majuscule și minuscule. - Recenziile au lungimi variate și conțin caractere Python precum
\r\n
şi caractere HTML ca&\#039;
.
Hai să vedem cum putem folosi 🤗 Datasets pentru a face față fiecărei dintre aceste probleme. Pentru a testa ipoteza identificării pacientului pentru coloana Unnamed: 0
, putem folosi funcția Dataset.unique()
pentru a verifica dacă numărul de ID-uri corespunde cu numărul de rânduri în fiecare split:
for split in drug_dataset.keys():
assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0"))
Aceasta pare să confirme ipoteza, deci putem curăța datasetul puțin, redenumind coloana Unnamed: 0
pentru a-i da un nume mai interpretabil. Putem folosi funcția DatasetDict.rename_column()
pentru a renumea coloana în același timp în ambele splituri:
drug_dataset = drug_dataset.rename_column(
original_column_name="Unnamed: 0", new_column_name="patient_id"
)
drug_dataset
DatasetDict({
train: Dataset({
features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'],
num_rows: 161297
})
test: Dataset({
features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'],
num_rows: 53766
})
})
✏️ Încearcă! Folosiți funcția
Dataset.unique()
pentru a găsi numărul de medicamente și condiții unice în seturile de antrenare și testare.
În continuare, vom normaliza toate condition
labels folosind Dataset.map()
. La fel cum am făcut cu tokenizarea în Capitolul 3, putem defini o funcție simplă care poate fi aplicată pe toate rândurile fiecărui split din drug_dataset
:
def lowercase_condition(example):
return {"condition": example["condition"].lower()}
drug_dataset.map(lowercase_condition)
AttributeError: 'NoneType' object has no attribute 'lower'
Oh no, am întâmpinat o problemă cu funcția map! Din eroarea noastră se poate deduce că unele intrări din coloana condition
sunt None
, care nu pot fi convertite la caracterul mic pentru că nu sunt string-uri. Vom elimina aceste rânduri folosind Dataset.filter()
, care funcționează în mod similar cu Dataset.map()
și se așteaptă o funcție care primește un exemplu al datasetului.
În loc de a scrie o funcție explicită ca:
def filter_nones(x):
return x["condition"] is not None
și apoi să rulăm drug_dataset.filter(filter_nones)
, putem face acest lucru într-o linie folosind o funcție lambda. În Python, funcțiile lambda sunt funcții mici care pot fi definite fără a le numi. Ele au forma generală:
lambda <argumente> : <expresie>
unde lambda
este unul dintre cuvintele cheie Python, <argumente>
reprezintă o listă/set de valori separate prin virgulă care definesc inputurile funcției și <expresie>
reprezintă operațiile pe care dorim să le executăm. De exemplu, putem defini o funcție lambda care ridică un număr la pătrat:
lambda x: x * x
Pentru a aplica această funcție la un input, trebuie să îi facem wrap și pe să punem inputul în paranteze:
(lambda x: x * x)(3)
9
La fel, putem defini funcții lambda cu mai multe argumente prin separarea acestora prin virgulă. De exemplu, putem calcula suprafața unui triunghi ca:
(lambda base, height: 0.5 * base * height)(4, 8)
16.0
Funcțiile lambda sunt utile atunci când dorim să definim funcții mici, pentru o singură folosire (pentru mai multe informații despre ele, recomandăm citirea excelentului Real Python tutorial scris de Andre Burgaud). În contextul 🤗 Datasets, putem utiliza funcțiile lambda pentru a defini operații simple de map și filter, astfel încât să eliminăm intrările None
din datasetul nostru:
drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None)
Cu intrările None
eliminate, putem normaliza coloana condition
:
drug_dataset = drug_dataset.map(lowercase_condition)
# Verificăm dacă lowercasing a funcționat
drug_dataset["train"]["condition"][:3]
['left ventricular dysfunction', 'adhd', 'birth control']
Funcționează! Acum că am curățat labelurile, să vedem cum putem curăți și recenziile.
Crearea de noi coloane
Atunci când lucrați cu recenziile clienților, o practică bună este să verificați numărul de cuvinte în fiecare recenzie. O recenzie poate fi doar un singur cuvânt, cum ar fi “Excelent!” sau un eseu complet care are sute de cuvinte și depinde de cazul pe care îl aveți la vedere, aici trebuie să vă asigurați că faceți față acestor extreme diferit. Pentru a calcula numărul de cuvinte în fiecare recenzie, vom folosi un heuristic aproximativ bazat pe splittingul textului prin spații.
Vom defini o funcție simplă care numără numărul de cuvinte din fiecare recenzie:
def compute_review_length(example):
return {"review_length": len(example["review"].split())}
Spre deosebire de funcția lowercase_condition()
, compute_review_length()
returnează un dicționar ale cărui key nu corespund uneia dintre numele coloanelor din dataset. În acest caz, atunci când compute_review_length()
este transmis în Dataset.map()
, el va fi aplicat pe toate rândurile din dataset pentru a crea o nouă coloană review_length
:
drug_dataset = drug_dataset.map(compute_review_length)
# Inspectăm primul exemplu de training
drug_dataset["train"][0]
{'patient_id': 206461,
'drugName': 'Valsartan',
'condition': 'left ventricular dysfunction',
'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"',
'rating': 9.0,
'date': 'May 20, 2012',
'usefulCount': 27,
'review_length': 17}
Așa cum era de așteptat, putem vedea o nouă coloană review_length
adăugată la setul de antrenare. Putem sorta această nouă coloană cu Dataset.sort()
pentru a vedea cum valorile extreme arată:
drug_dataset["train"].sort("review_length")[:3]
{'patient_id': [103488, 23627, 20558],
'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'],
'condition': ['birth control', 'muscle spasm', 'pain'],
'review': ['"Excellent."', '"useless"', '"ok"'],
'rating': [10.0, 1.0, 6.0],
'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'],
'usefulCount': [5, 2, 10],
'review_length': [1, 1, 1]}
Precum am presupus, unele recenii conțin doar un singur cuvânt, ceea ce, deși ar putea fi OK pentru analiza sentimentului, nu ar fi informativ dacă vrem să prezicem condiției.
🙋 O alternativă la adăugarea unei noi coloane într-un dataset este funcția
Dataset.add_column()
. Aceasta permite să oferiți coloana ca o listă Python sau array NumPy și poate fi utilă în situații în careDataset.map()
nu este bine adaptat pentru analiza dumneavoastră.
Hai să folosim funcția Dataset.filter()
pentru a elimina recenziile care conțin mai puțin de 30 de cuvinte. Similar cum am făcut în cazul coloanei condition
, putem elimina recenziile foarte scurte cerând ca recenziile să aibă o lungime mai mare decât acest prag:
drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30)
print(drug_dataset.num_rows)
{'train': 138514, 'test': 46108}
După cum vedeți, aceasta a eliminat aproximativ 15% din recenziile noastrem, din seturile originale de antrenare și testare.
✏️ Încercați! Folosiți funcția
Dataset.sort()
pentru a inspecta recenziile cu cele mai mari numere de cuvinte. Vezi documentația pentru a vedea ce argument trebuie să folosești pentru a sorta recenziile în ordine descrescătoare.
Ultima chestie de care trebuie să ne ocupăm este prezența caracterelor HTML în recenziile noastre. Putem folosi modulul html
din Python pentru a face unescape acestor caractere:
import html
text = "I'm a transformer called BERT"
html.unescape(text)
"I'm a transformer called BERT"
Vom folosi Dataset.map()
pentru a face unescape toate caracterele HTML din corpus:
drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])})
În mod evident, metoda Dataset.map()
este foarte utilă pentru procesarea datelor – și nu am abordat decât o mică parte din ceea ce poate face!
Superputerile metodei map()
Metoda Dataset.map()
acceptă un argument batched
care, dacă este setat pe True
, cauzează ca ea să trimită un batch de exemple la funcția map în același timp (dimensiunea batchului poate fi configurată dar defaultul este 1.000). De exemplu, anterior am folosit o funcție map care a făcut unescaped toate caracterele HTML din recenziile noastre și i-a luat câteva secunde să execute (puteți citi timpul pe progress bars). Putem accelera acest lucru prin procesarea mai multor elemente în același timp folosind list comprehension.
Când specificați batched=True
funcția primește un dicționar cu câmpurile datasetului, dar fiecare valoare este acum list of values și nu doar o singură valoare. Valoarea de return a Dataset.map()
ar trebui să fie la fel: un dicționar cu câmpurile pe care dorim să le actualizăm sau adăugăm în datasetul nostru, și o listă de valori. De exemplu, mai jos este alt mod de a face unescape tuturor caracterelor HTML din recenziile noastre, folosind batched=True
:
new_drug_dataset = drug_dataset.map(
lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True
)
Dacă executați acest cod într-un notebook, veți vedea că această comandă se execută mult mai rapid decât cea anterioră. Și nu pentru că recenziile noastre au fost deja HTML-unescaped – dacă reexecutați instrucția precedentă (fără batched=True
), ea va lua același timp ca înainte. Acest lucru se datorează faptului că list comprehension sunt mai rapide decât executarea aceluiași cod într-un for
loop, și am câștigat, de asemenea, puțină performanțp accesând multe elemente în același timp, în loc unul câte unul.
Folosirea Dataset.map()
cu batched=True
este esențială pentru a obține viteza “rapidă” a tokenizerilor pe care îi vom întâlni în Capitolul 6, care pot repede să tokenizeze listelor mari de texte. De exemplu, pentru tokenizarea tuturor recenziilor medicamentelor cu un tokenizer rapid, putem folosi o funcție ca aceasta:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
def tokenize_function(examples):
return tokenizer(examples["review"], truncation=True)
Așa cum am văzut în Capitolul 3, putem transmite un singur sau câteva exemple către tokenizer, așadar putem folosi această funcție cu sau fără batched=True
. Hai să ne folosim de această oportunitate și să comparăm performanța diferitelor opțiuni. Într-un notebook puteți măsura timpul unei instrucțiie de o line prin adăugarea %time
înaintea acelei linii de cod pe care doriți să o măsurați:
%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True)
Puteți și să măsurați un întreg cell prin scrierea %%time
la începutul celulei. În hardware-ul pe care l-am executat, acest lucru a arătat 10.8s pentru această instrucție (este numărul scris după “Wall time”).
✏️ Încercați! Executați aceeași instrucție cu și fără
batched=True
, apoi încercați-o cu un tokenizer lent (adaugațiuse_fast=False
în metodaAutoTokenizer.from_pretrained()
), astfel să puteți vedea ce numere obțineți pe hardwareul vostru.
Aici sunt rezultatele pe care le-am obținut cu și fără batching, folosind un tokenizer rapid și lent:
Options | Fast tokenizer | Slow tokenizer |
---|---|---|
batched=True | 10.8s | 4min41s |
batched=False | 59.2s | 5min3s |
Aceasta înseamnă că utilizarea unui tokenizer rapid cu opțiunea batched=True
este de 30 de ori mai rapidă decât varianta lentă fără batching - acest lucru este pur și simplu uimitor! Acesta este motivul principal pentru care tokenizerii rapizi sunt setați implicit când se utilizează AutoTokenizer
(și de ce sunt numiți “rapizi”). Ei pot atinge o asemenea accelerație datorită faptului că codul de tokenizare este executat în Rust, care este un limbaj care facilitează paralelizarea execuției.
Parallelization este și motivul pentru care tokenizerul rapid realizează o accelerare de aproape 6 ori cu batching: nu puteți paraleliza o singură operație de tokenizare, dar atunci când doriți să tokenizați multe texte în același timp, puteți să faceți split execuției pe mai multe procese, fiecare răspunzând pentru propriile texte.
Dataset.map()
are și o capacitate de parallelization proprie. Deoarece nu sunt susținute de Rust, nu pot să le ofere aceeași accelerație tokenizerilori înceți ca tokenizerilor rapizi, dar pot încă fi utili (în special dacă utilizați un tokenizer care nu are o variantă rapidă). Pentru a activa multiprocessingul, folosiți argumentul num_proc
și specificați numărul de procese să fie utilizate în apelul Dataset.map()
:
slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False)
def slow_tokenize_function(examples):
return slow_tokenizer(examples["review"], truncation=True)
tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8)
Puteți experimenta puțin cu timpii pentru a determina numărul de procese optime; în cazul nostru 8 s-a dovedit a produce cea mai mare accelerație. Aici sunt rezultatele pe care le-am obținut cu și fără multiprocessing:
Options | Fast tokenizer | Slow tokenizer |
---|---|---|
batched=True | 10.8s | 4min41s |
batched=False | 59.2s | 5min3s |
batched=True , num_proc=8 | 6.52s | 41.3s |
batched=False , num_proc=8 | 9.49s | 45.2s |
Aceste rezultate sunt mult mai bune pentru tokenizerul lent, dar și performanța tokenizerului rapid a fost semnificativ îmbunătățită. Cu toate acestea, trebuie să reamintim că aceasta nu va fi întotdeauna cazul - testele noastre au arătat că este mai rapid să utilizați batched=True
fără acest argument în cazurile în care valoarea lui num_proc
diferă de 8. În general, nu vă recomandăm utilizarea multiplicării proceselor pentru tokenizorii rapizi cu batched=True
.
Utilizarea num_proc
pentru a accelera procesarea este de obicei o idee excelentă, atâta timp cât funcția pe care o utilizați nu utilizează deja multiprocessing.
Toate aceste funcționalități condensate într-o singură metodă este foarte impresionant, dar asta nu e totul! Cu Dataset.map()
și batched=True
puteți modifica numărul de elemente din datasetul dumneavoastră. Acesta este extrem de util în numeroase situații în care doriți să creați mai multe caracteristici de antrenare dintr-un singur exemplu, și vom avea nevoie de acest lucru ca parte a preprocesării pentru câteva dintre sarcinile NLP pe care le vom discuta în Capitolul 7.
💡 În machine learning, un exemplu este de obicei definit ca fiind un set de features care se oferă modelului. În unele contexte, acestea vor fi seturile de coloane dintr-un Dataset
, dar în altele (ca și aici și pentru răspunderea la întrebări) mai multe caracteristici pot fi extrase dintr-un singur exemplu și să aparțină unei singure coloane.
Hai să vedem cum funcționează! Aici vom tokeniza exemplele și le vom face truncatela lungimea maximă de 128, dar vom cere tokenizerului să returneze toate chunkurile de text în loc de prima. Acest lucru poate fi făcut cu return_overflowing_tokens=True
:
def tokenize_and_split(examples):
return tokenizer(
examples["review"],
truncation=True,
max_length=128,
return_overflowing_tokens=True,
)
Hai să testăm acest lucru pe un exemplu înainte de a folosi Dataset.map()
pentru întreg datasetul:
result = tokenize_and_split(drug_dataset["train"][0])
[len(inp) for inp in result["input_ids"]]
[128, 49]
Așadar, primul nostru exemplu din setul de antrenare a devenit două features pentru că a fost tokenizat mai mult decât lungimea maximă de tokenuri pe care am specificat-o: prima cu lungimea 128 și a doua cu lungimea 49. Acum trebuie să facem acest lucru pentru toate elementele din dataset!
tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True)
ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000
Oh no! Acesta nu a funcționat! De ce? Citind eroarea vom afla motivul: existența unei incompatibilități în lungimea uneia dintre coloane - una cu o lungime de 1,463 și alta de 1,000. Dacă ați privit documentația Dataset.map(), puteți să vă reamintiți că este numărul de sampleuri care sunt oferite funcției pe care noi le facem mapping; aici aceste 1,000 exemplare creează 1,463 features noi, ceea ce duce la un shape error.
Problema este că încercăm să amestecăm două dataseturi cu mărimi diferite: coloanele drug_dataset
vor avea un număr determinat de exemple (cele 1,000 din eroare), dar tokenized_dataset
pe care îl construim va fi mai mare (cel cu 1,463 din eroare; el este mai mare decât 1,000 pentru că tokenizăm reviewrile lungi în mai multe exemple folosind return_overflowing_tokens=True
). Acest lucru nu funcționează pentru un Dataset
, așadar trebuie să eliminăm sau să modificămm coloanele din datasetul vechi pentru a se potrivi dimensiunea cu cea din noul dataset. Putem face ultima din cele două opțiuni folosind argumentul remove_columns
:
tokenized_dataset = drug_dataset.map(
tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names
)
Acum acest lucru funcționează fără erori. Putem verifica că noul dataset are mai multe elemente decât datasetul original prin compararea lungimilor:
len(tokenized_dataset["train"]), len(drug_dataset["train"])
(206772, 138514)
Am menționat că putem rezolva problema lungimilor diferite are coloanelor prin schimbarea vechilor coloane la aceeași dimensiune cu cele noi. Pentru aceasta, vom avea nevoie de câmpul overflow_to_sample_mapping
returnat de tokenizer atunci când setăm return_overflowing_tokens=True
. El ne oferă un mapping de la un nou feature index la indicele sampleului din care a provenit. Prin intermediul acesta, putem asocia fiecărei key prezentă în datasetul original cu o listă de valori de dimensiune corectă prin repetarea valorilor fiecărui exemplu atâta timp cât produce caracteristici noi:
def tokenize_and_split(examples):
result = tokenizer(
examples["review"],
truncation=True,
max_length=128,
return_overflowing_tokens=True,
)
# Extragem maparea între noul și vechiul indice
sample_map = result.pop("overflow_to_sample_mapping")
for key, values in examples.items():
result[key] = [values[i] for i in sample_map]
return result
Putem observa că funcționează cu Dataset.map()
fără ca noi să avem nevoie să eliminăm coloanele vechi:
tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True)
tokenized_dataset
DatasetDict({
train: Dataset({
features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'],
num_rows: 206772
})
test: Dataset({
features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'],
num_rows: 68876
})
})
Obținem același număr de features training ca și înainte, dar acum am păstrat toate câmpurile vechi. Dacă ai nevoie de ele pentru post-procesare după aplicarea modelului, ar fi util să folosiți această abordare.
Acum ați învățat cum pot fi utilizate 🤗 Datasets pentru preprocesarea datelor prin metode diferite. Deși funcțiile de preprocesare ale 🤗 Datasets vor acoperi majoritatea nevoilor de antrenare a modelului,
există momente în care veți avea nevoie să treceți la Pandas pentru accesul la caracteristici mai puternice, cum ar fi DataFrame.groupby()
sau high-level APIs pentru vizualizare. Din fericire, 🤗 Dataset a fost proiectat astfel încât să fie interoperabil cu biblioteci precum Pandas, NumPy, PyTorch, TensorFlow și JAX. Hai să vedem cum se face acest lucru.
De la Dataset s la DataFrame s și înapoi
Pentru a permite conversia între diferite biblioteci, 🤗 Datasets oferă o funcția Dataset.set_format()
. Această funcție schimbă doar output format al datasetului, deci puteți ușor să treceți la un alt format fără a afecta data format de bază, care este Apache Arrow. Formatarea se face direct. Pentru a demonstra acest lucru, hai să convertim datasetul nostru în Pandas:
drug_dataset.set_format("pandas")
Acum, atunci când accesăm elementele din dataset, obținem pandas.DataFrame
în loc de un dicționar:
drug_dataset["train"][:3]
patient_id | drugName | condition | review | rating | date | usefulCount | review_length | |
---|---|---|---|---|---|---|---|---|
0 | 95260 | Guanfacine | adhd | "My son is halfway through his fourth week of Intuniv..." | 8.0 | April 27, 2010 | 192 | 141 |
1 | 92703 | Lybrel | birth 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.0 | December 14, 2009 | 17 | 134 |
2 | 138000 | Ortho Evra | birth control | "This is my first time using any form of birth control..." | 8.0 | November 3, 2015 | 10 | 89 |
În continuare, vom crea un pandas.DataFrame
pentru întregul set de antrenare prin selectarea tuturor elementelor din drug_dataset["train"]
:
train_df = drug_dataset["train"][:]
🚨 În spatele scenei,
Dataset.set_format()
schimbă formatul returnat pentru__getitem__()
dunder method a datasetului. Asta înseamnă că atunci când dorim să creăm un nou obiect catrain_df
dintr-unDataset
în formatul"pandas"
, trebuie să tăiem întreg datasetul pentru a obține unpandas.DataFrame
. Puteți verifica voi înşivă că tipul luidrug_dataset["train"]
esteDataset
, indiferent de output format.
Acum putem utiliza toate funcționalitățile Pandas pe care le dorim. De exemplu, putem face fancy chaining pentru a calcula distribuția clasei printre intrările condition
:
frequencies = (
train_df["condition"]
.value_counts()
.to_frame()
.reset_index()
.rename(columns={"index": "condition", "condition": "frequency"})
)
frequencies.head()
condition | frequency | |
---|---|---|
0 | birth control | 27655 |
1 | depression | 8023 |
2 | acne | 5209 |
3 | anxiety | 4991 |
4 | pain | 4744 |
Și odată ce suntem gata cu analiza noastră Pandas, putem crea întotdeauna un nou obiect Dataset
prin utilizarea funcției Dataset.from_pandas()
:
from datasets import Dataset
freq_dataset = Dataset.from_pandas(frequencies)
freq_dataset
✏️ Încercați! Calculați media ratingului per medicament și salvați rezultatul într-un nou
Dataset
.
Acest lucru completează turul nostru de tehnici de preprocesare disponibile în 🤗 Datasets. Pentru a finisa secțiunea, vom crea un set de validare pentru a pregăti datasetul pentru antrenarea unui clasificator. Înainte de a face asta, noi vom reseta output formatul drug_dataset
de la "pandas"
la `“arrow” :
drug_dataset.reset_format()
Crearea unui set de validare
Deși avem deja un set de testare pe care îl putem folosi pentru evaluare, este o bună practică să lăsăm setul de test neschimbat și să creăm un set de validare în timpul developmentului. Odată ce sunteți fericiți cu performanța modelului pe setul de validare, puteți face o verificare finală a setului de test. Acest proces ajută la reducerea riscului ca să vă adaptați prea mult setul de test și să depuneți un model care poate eșua analizând date reale.
🤗 Datasets oferă o funcție Dataset.train_test_split()
bazată pe funcționalitatea celebră din scikit-learn
. O vom folosi pentru a împărți setul nostru de antrenare în split-uri de train
și validation
(setăm argumentul seed
pentru reproductabilitate):
drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42)
# Renumește implicit "test" split-ul la "valide"
drug_dataset_clean["validation"] = drug_dataset_clean.pop("test")
# Adaugă setul de test în `DatasetDict`
drug_dataset_clean["test"] = drug_dataset["test"]
drug_dataset_clean
DatasetDict({
train: Dataset({
features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'],
num_rows: 110811
})
validation: Dataset({
features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'],
num_rows: 27703
})
test: Dataset({
features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'],
num_rows: 46108
})
})
Foarte bine, am pregătit acum un dataset care este gata pentru antrenarea unor modele! În Secțiunea 5 vom arăta cum puteți încărca seturile de date în Hugging Face Hub, dar în acest moment hai să punem capăt analizei noastre prin examinarea a câteva modalități de salvare a seturilor de date pe dispozitivele locale.
Salvarea unui dataset
Deși 🤗 Datasets va face cache fiecărui dataset și a operațiilor efectuate asupra acestuia, există momente în care veți dori să salvați un dataset pe disc (de exemplu, în cazul în care cache-ul se șterge). După cum vedeți în tabelul de mai jos, 🤗 Datasets oferă trei funcții principale pentru salvarea datelor în formate diferite:
Format de date | Funcție |
---|---|
Arrow | Dataset.save_to_disk() |
CSV | Dataset.to_csv() |
JSON | Dataset.to_json() |
Spre exemplu, hai să salvăm datasetul nostru curățat în formatul Arrow:
drug_dataset_clean.save_to_disk("drug-reviews")
Acest lucru va crea un folder cu următoarea structură:
drug-reviews/
├── dataset_dict.json
├── test
│ ├── dataset.arrow
│ ├── dataset_info.json
│ └── state.json
├── train
│ ├── dataset.arrow
│ ├── dataset_info.json
│ ├── indices.arrow
│ └── state.json
└── validation
├── dataset.arrow
├── dataset_info.json
├── indices.arrow
└── state.json
unde se poate vedea că fiecare split este asociat cu propriul său tabel dataset.arrow
, iar unele metadate în dataset_info.json
și state.json
. Poți să te gândești la formatul Arrow ca fiind un tabel fancy de coloane și rânduri, optimizat pentru construirea aplicațiilor high-performance care procesează și transportă dataseturi mari.
Odată ce setul de date este salvat, putem încărca-o folosind funcția load_from_disk()
următoarea:
from datasets import load_from_disk
drug_dataset_reloaded = load_from_disk("drug-reviews")
drug_dataset_reloaded
DatasetDict({
train: Dataset({
features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'],
num_rows: 110811
})
validation: Dataset({
features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'],
num_rows: 27703
})
test: Dataset({
features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'],
num_rows: 46108
})
})
Pentru formatele CSV și JSON, trebuie să stocați fiecare split într-un fișier separat. Un mod de a face acest lucru estw iterarea asupra cheilor și valorilor obiectului DatasetDict
:
for split, dataset in drug_dataset_clean.items():
dataset.to_json(f"drug-reviews-{split}.jsonl")
Acesta salvează fiecare split în JSON Lines format, unde fiecare rând din setul de date este stocat ca o singură linie JSON. Aici puteți vedea cum arată primul exemplu:
!head -n 1 drug-reviews-train.jsonl
{"patient_id":141780,"drugName":"Escitalopram","condition":"depression","review":"\"I seemed to experience the regular side effects of LEXAPRO, insomnia, low sex drive, sleepiness during the day. I am taking it at night because my doctor said if it made me tired to take it at night. I assumed it would and started out taking it at night. Strange dreams, some pleasant. I was diagnosed with fibromyalgia. Seems to be helping with the pain. Have had anxiety and depression in my family, and have tried quite a few other medications that haven't worked. Only have been on it for two weeks but feel more positive in my mind, want to accomplish more in my life. Hopefully the side effects will dwindle away, worth it to stick with it from hearing others responses. Great medication.\"","rating":9.0,"date":"May 29, 2011","usefulCount":10,"review_length":125}
Putem apoi folosi tehnicile de la Secțiunea 2 pentru încărcarea fișierelor JSON:
data_files = {
"train": "drug-reviews-train.jsonl",
"validation": "drug-reviews-validation.jsonl",
"test": "drug-reviews-test.jsonl",
}
drug_dataset_reloaded = load_dataset("json", data_files=data_files)
Și ăsta este finalul excursiei noastre în lumea manipulării datelor cu 🤗 Datasets! Acum că avem un dataset curat, pregătit pentru antrenarea unui model, aici sunt câteva idei pe care le puteți încerca:
- Folosiți tehnicile din Capitolul 3 pentru a antrena un classifier care poate prezice starea pacientului pe baza recenziei medicamentului.
- Folosiți pipelineul
summarization
din Capitolul 1 pentru a genera rezumate ale recenziilor.
În următoarea secțiune, vom vedea cum 🤗 Datasets vă permite să lucrați cu dataseturi mari fără ca laptopul tău să explodeze :)!
Update on GitHub