AI-For-Beginners/translations/ro/lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.i...

577 lines
22 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Sarcina de clasificare a textului\n",
"\n",
"Așa cum am menționat, ne vom concentra pe o sarcină simplă de clasificare a textului bazată pe datasetul **AG_NEWS**, care presupune clasificarea titlurilor de știri în una dintre cele 4 categorii: Lume, Sport, Afaceri și Știință/Tehnologie.\n",
"\n",
"## Datasetul\n",
"\n",
"Acest dataset este integrat în modulul [`torchtext`](https://github.com/pytorch/text), așa că putem avea acces la el cu ușurință.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import torch\n",
"import torchtext\n",
"import os\n",
"import collections\n",
"os.makedirs('./data',exist_ok=True)\n",
"train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
"classes = ['World', 'Sports', 'Business', 'Sci/Tech']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Aici, `train_dataset` și `test_dataset` conțin colecții care returnează perechi de etichetă (numărul clasei) și text, respectiv, de exemplu:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(3,\n",
" \"Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\\\band of ultra-cynics, are seeing green again.\")"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(train_dataset)[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Așadar, să afișăm primele 10 titluri noi din setul nostru de date:\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"**Sci/Tech** -> Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\band of ultra-cynics, are seeing green again.\n",
"**Sci/Tech** -> Carlyle Looks Toward Commercial Aerospace (Reuters) Reuters - Private investment firm Carlyle Group,\\which has a reputation for making well-timed and occasionally\\controversial plays in the defense industry, has quietly placed\\its bets on another part of the market.\n",
"**Sci/Tech** -> Oil and Economy Cloud Stocks' Outlook (Reuters) Reuters - Soaring crude prices plus worries\\about the economy and the outlook for earnings are expected to\\hang over the stock market next week during the depth of the\\summer doldrums.\n",
"**Sci/Tech** -> Iraq Halts Oil Exports from Main Southern Pipeline (Reuters) Reuters - Authorities have halted oil export\\flows from the main pipeline in southern Iraq after\\intelligence showed a rebel militia could strike\\infrastructure, an oil official said on Saturday.\n",
"**Sci/Tech** -> Oil prices soar to all-time record, posing new menace to US economy (AFP) AFP - Tearaway world oil prices, toppling records and straining wallets, present a new economic menace barely three months before the US presidential elections.\n"
]
}
],
"source": [
"for i,x in zip(range(5),train_dataset):\n",
" print(f\"**{classes[x[0]]}** -> {x[1]}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Deoarece seturile de date sunt iteratoare, dacă dorim să folosim datele de mai multe ori, trebuie să le convertim într-o listă:\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
"train_dataset = list(train_dataset)\n",
"test_dataset = list(test_dataset)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Tokenizare\n",
"\n",
"Acum trebuie să transformăm textul în **numere** care pot fi reprezentate ca tensori. Dacă dorim o reprezentare la nivel de cuvinte, trebuie să facem două lucruri:\n",
"* utilizăm un **tokenizator** pentru a împărți textul în **tokeni**\n",
"* construim un **vocabular** al acelor tokeni.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['he', 'said', 'hello']"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tokenizer = torchtext.data.utils.get_tokenizer('basic_english')\n",
"tokenizer('He said: hello')"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"counter = collections.Counter()\n",
"for (label, line) in train_dataset:\n",
" counter.update(tokenizer(line))\n",
"vocab = torchtext.vocab.vocab(counter, min_freq=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Folosind vocabularul, putem codifica cu ușurință șirul nostru tokenizat într-un set de numere:\n"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Vocab size if 95810\n"
]
},
{
"data": {
"text/plain": [
"[599, 3279, 97, 1220, 329, 225, 7368]"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vocab_size = len(vocab)\n",
"print(f\"Vocab size if {vocab_size}\")\n",
"\n",
"stoi = vocab.get_stoi() # dict to convert tokens to indices\n",
"\n",
"def encode(x):\n",
" return [stoi[s] for s in tokenizer(x)]\n",
"\n",
"encode('I love to play with my words')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Reprezentarea textului prin metoda Bag of Words\n",
"\n",
"Deoarece cuvintele transmit semnificație, uneori putem înțelege sensul unui text doar analizând cuvintele individuale, indiferent de ordinea lor în propoziție. De exemplu, atunci când clasificăm știri, cuvinte precum *vreme*, *zăpadă* sunt probabil să indice *prognoza meteo*, în timp ce cuvinte precum *acțiuni*, *dolar* ar putea sugera *știri financiare*.\n",
"\n",
"Reprezentarea vectorială **Bag of Words** (BoW) este cea mai utilizată metodă tradițională de reprezentare vectorială. Fiecare cuvânt este asociat unui index vectorial, iar elementul vectorului conține numărul de apariții ale unui cuvânt într-un document dat.\n",
"\n",
"![Imagine care arată cum este reprezentată în memorie metoda bag of words.](../../../../../translated_images/ro/bag-of-words-example.606fc1738f1d7ba9.webp) \n",
"\n",
"> **Note**: Poți să te gândești la BoW și ca la o sumă a tuturor vectorilor one-hot-encoded pentru cuvintele individuale din text.\n",
"\n",
"Mai jos este un exemplu despre cum să generezi o reprezentare bag of words folosind biblioteca python Scikit Learn:\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from sklearn.feature_extraction.text import CountVectorizer\n",
"vectorizer = CountVectorizer()\n",
"corpus = [\n",
" 'I like hot dogs.',\n",
" 'The dog ran fast.',\n",
" 'Its hot outside.',\n",
" ]\n",
"vectorizer.fit_transform(corpus)\n",
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pentru a calcula vectorul bag-of-words din reprezentarea vectorială a datasetului nostru AG_NEWS, putem folosi următoarea funcție:\n"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"tensor([2., 1., 2., ..., 0., 0., 0.])\n"
]
}
],
"source": [
"vocab_size = len(vocab)\n",
"\n",
"def to_bow(text,bow_vocab_size=vocab_size):\n",
" res = torch.zeros(bow_vocab_size,dtype=torch.float32)\n",
" for i in encode(text):\n",
" if i<bow_vocab_size:\n",
" res[i] += 1\n",
" return res\n",
"\n",
"print(to_bow(train_dataset[0][1]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Notă:** Aici folosim variabila globală `vocab_size` pentru a specifica dimensiunea implicită a vocabularului. Deoarece dimensiunea vocabularului este adesea destul de mare, putem limita dimensiunea vocabularului la cele mai frecvente cuvinte. Încercați să reduceți valoarea `vocab_size` și să rulați codul de mai jos, și observați cum afectează acuratețea. Ar trebui să vă așteptați la o scădere a acurateței, dar nu dramatică, în schimbul unei performanțe mai ridicate.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Antrenarea clasificatorului BoW\n",
"\n",
"Acum că am învățat cum să construim reprezentarea Bag-of-Words a textului nostru, să antrenăm un clasificator pe baza acesteia. Mai întâi, trebuie să convertim setul nostru de date pentru antrenare astfel încât toate reprezentările vectoriale poziționale să fie transformate în reprezentări Bag-of-Words. Acest lucru poate fi realizat prin transmiterea funcției `bowify` ca parametru `collate_fn` la `DataLoader` standard din torch:\n"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"from torch.utils.data import DataLoader\n",
"import numpy as np \n",
"\n",
"# this collate function gets list of batch_size tuples, and needs to \n",
"# return a pair of label-feature tensors for the whole minibatch\n",
"def bowify(b):\n",
" return (\n",
" torch.LongTensor([t[0]-1 for t in b]),\n",
" torch.stack([to_bow(t[1]) for t in b])\n",
" )\n",
"\n",
"train_loader = DataLoader(train_dataset, batch_size=16, collate_fn=bowify, shuffle=True)\n",
"test_loader = DataLoader(test_dataset, batch_size=16, collate_fn=bowify, shuffle=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Acum să definim o rețea neuronală clasificatoare simplă care conține un singur strat liniar. Dimensiunea vectorului de intrare este egală cu `vocab_size`, iar dimensiunea de ieșire corespunde numărului de clase (4). Deoarece rezolvăm o sarcină de clasificare, funcția de activare finală este `LogSoftmax()`.\n"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"net = torch.nn.Sequential(torch.nn.Linear(vocab_size,4),torch.nn.LogSoftmax(dim=1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Acum vom defini bucla standard de antrenament PyTorch. Deoarece setul nostru de date este destul de mare, pentru scopul nostru educativ vom antrena doar pentru o epocă, și uneori chiar pentru mai puțin de o epocă (specificarea parametrului `epoch_size` ne permite să limităm antrenamentul). De asemenea, vom raporta acuratețea acumulată a antrenamentului în timpul procesului; frecvența raportării este specificată folosind parametrul `report_freq`.\n"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"def train_epoch(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.NLLLoss(),epoch_size=None, report_freq=200):\n",
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
" net.train()\n",
" total_loss,acc,count,i = 0,0,0,0\n",
" for labels,features in dataloader:\n",
" optimizer.zero_grad()\n",
" out = net(features)\n",
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
" loss.backward()\n",
" optimizer.step()\n",
" total_loss+=loss\n",
" _,predicted = torch.max(out,1)\n",
" acc+=(predicted==labels).sum()\n",
" count+=len(labels)\n",
" i+=1\n",
" if i%report_freq==0:\n",
" print(f\"{count}: acc={acc.item()/count}\")\n",
" if epoch_size and count>epoch_size:\n",
" break\n",
" return total_loss.item()/count, acc.item()/count"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.8028125\n",
"6400: acc=0.8371875\n",
"9600: acc=0.8534375\n",
"12800: acc=0.85765625\n"
]
},
{
"data": {
"text/plain": [
"(0.026090790722161722, 0.8620069296375267)"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"train_epoch(net,train_loader,epoch_size=15000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## BiGrame, TriGrame și N-Grame\n",
"\n",
"O limitare a abordării sacului de cuvinte este că unele cuvinte fac parte din expresii formate din mai multe cuvinte. De exemplu, cuvântul „hot dog” are un sens complet diferit față de cuvintele „hot” și „dog” în alte contexte. Dacă reprezentăm întotdeauna cuvintele „hot” și „dog” prin aceleași vectori, acest lucru poate deruta modelul nostru.\n",
"\n",
"Pentru a rezolva această problemă, **reprezentările N-gram** sunt adesea utilizate în metodele de clasificare a documentelor, unde frecvența fiecărui cuvânt, bi-cuvânt sau tri-cuvânt este o caracteristică utilă pentru antrenarea clasificatorilor. În reprezentarea bigramelor, de exemplu, vom adăuga toate perechile de cuvinte în vocabular, pe lângă cuvintele originale.\n",
"\n",
"Mai jos este un exemplu despre cum să generăm o reprezentare de tip sac de cuvinte cu bigrame folosind Scikit Learn:\n"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Vocabulary:\n",
" {'i': 7, 'like': 11, 'hot': 4, 'dogs': 2, 'i like': 8, 'like hot': 12, 'hot dogs': 5, 'the': 16, 'dog': 0, 'ran': 14, 'fast': 3, 'the dog': 17, 'dog ran': 1, 'ran fast': 15, 'its': 9, 'outside': 13, 'its hot': 10, 'hot outside': 6}\n"
]
},
{
"data": {
"text/plain": [
"array([[1, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
" dtype=int64)"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\\b\\w+\\b', min_df=1)\n",
"corpus = [\n",
" 'I like hot dogs.',\n",
" 'The dog ran fast.',\n",
" 'Its hot outside.',\n",
" ]\n",
"bigram_vectorizer.fit_transform(corpus)\n",
"print(\"Vocabulary:\\n\",bigram_vectorizer.vocabulary_)\n",
"bigram_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Principalul dezavantaj al abordării N-gram este că dimensiunea vocabularului începe să crească extrem de rapid. În practică, trebuie să combinăm reprezentarea N-gram cu unele tehnici de reducere a dimensionalității, cum ar fi *embeddings*, pe care le vom discuta în unitatea următoare.\n",
"\n",
"Pentru a utiliza reprezentarea N-gram în setul nostru de date **AG News**, trebuie să construim un vocabular special de tip ngram:\n"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Bigram vocabulary length = 1308842\n"
]
}
],
"source": [
"counter = collections.Counter()\n",
"for (label, line) in train_dataset:\n",
" l = tokenizer(line)\n",
" counter.update(torchtext.data.utils.ngrams_iterator(l,ngrams=2))\n",
" \n",
"bi_vocab = torchtext.vocab.vocab(counter, min_freq=1)\n",
"\n",
"print(\"Bigram vocabulary length = \",len(bi_vocab))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Am putea folosi același cod de mai sus pentru a antrena clasificatorul, totuși, acest lucru ar fi foarte ineficient din punct de vedere al memoriei. În unitatea următoare, vom antrena un clasificator bigram folosind embeddings.\n",
"\n",
"> **Notă:** Poți păstra doar acele ngrame care apar în text de mai multe ori decât numărul specificat. Acest lucru va asigura că bigramele rare vor fi omise și va reduce semnificativ dimensiunea vocabularului. Pentru a face acest lucru, setează parametrul `min_freq` la o valoare mai mare și observă cum se schimbă lungimea vocabularului.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Frecvența Termenilor și Frecvența Inversă a Documentelor (TF-IDF)\n",
"\n",
"În reprezentarea BoW, aparițiile cuvintelor sunt ponderate uniform, indiferent de cuvântul în sine. Totuși, este evident că anumite cuvinte frecvente, precum *un*, *în*, etc., sunt mult mai puțin importante pentru clasificare decât termenii specializați. De fapt, în majoritatea sarcinilor NLP, unele cuvinte sunt mai relevante decât altele.\n",
"\n",
"**TF-IDF** este abrevierea pentru **frecvența termenilor frecvența inversă a documentelor**. Este o variație a modelului bag of words, unde, în loc de o valoare binară 0/1 care indică apariția unui cuvânt într-un document, se folosește o valoare în virgulă mobilă, care este legată de frecvența apariției cuvântului în corpus.\n",
"\n",
"Mai formal, greutatea $w_{ij}$ a unui cuvânt $i$ în documentul $j$ este definită astfel:\n",
"$$\n",
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
"$$\n",
"unde\n",
"* $tf_{ij}$ este numărul de apariții ale lui $i$ în $j$, adică valoarea BoW pe care am văzut-o anterior\n",
"* $N$ este numărul de documente din colecție\n",
"* $df_i$ este numărul de documente care conțin cuvântul $i$ în întreaga colecție\n",
"\n",
"Valoarea TF-IDF $w_{ij}$ crește proporțional cu numărul de apariții ale unui cuvânt într-un document și este ajustată în funcție de numărul de documente din corpus care conțin acel cuvânt, ceea ce ajută la compensarea faptului că unele cuvinte apar mai frecvent decât altele. De exemplu, dacă un cuvânt apare în *fiecare* document din colecție, $df_i=N$, și $w_{ij}=0$, iar acești termeni vor fi complet ignorați.\n",
"\n",
"Poți crea cu ușurință o vectorizare TF-IDF a textului folosind Scikit Learn:\n"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0.43381609, 0. , 0.43381609, 0. , 0.65985664,\n",
" 0.43381609, 0. , 0. , 0. , 0. ,\n",
" 0. , 0. , 0. , 0. , 0. ,\n",
" 0. ]])"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"vectorizer = TfidfVectorizer(ngram_range=(1,2))\n",
"vectorizer.fit_transform(corpus)\n",
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Concluzie\n",
"\n",
"Totuși, deși reprezentările TF-IDF oferă o pondere bazată pe frecvența diferitelor cuvinte, ele nu pot reprezenta sensul sau ordinea. Așa cum a spus faimosul lingvist J. R. Firth în 1935: „Sensul complet al unui cuvânt este întotdeauna contextual, iar niciun studiu al sensului în afara contextului nu poate fi luat în serios.”. Vom învăța mai târziu în curs cum să capturăm informațiile contextuale din text folosind modelarea limbajului.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Declinare de responsabilitate**: \nAcest document a fost tradus folosind serviciul de traducere AI [Co-op Translator](https://github.com/Azure/co-op-translator). Deși ne străduim să asigurăm acuratețea, vă rugăm să fiți conștienți că traducerile automate pot conține erori sau inexactități. Documentul original în limba sa natală ar trebui considerat sursa autoritară. Pentru informații critice, se recomandă traducerea profesională realizată de un specialist uman. Nu ne asumăm responsabilitatea pentru eventualele neînțelegeri sau interpretări greșite care pot apărea din utilizarea acestei traduceri.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
},
"kernelspec": {
"display_name": "Python 3.8.12 ('py38')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"coopTranslator": {
"original_hash": "7b9040985e748e4e2d4c689892456ad7",
"translation_date": "2025-08-30T01:12:11+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
"language_code": "ro"
}
},
"nbformat": 4,
"nbformat_minor": 2
}