AI-For-Beginners/translations/nl/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": [
"# Tekstclassificatietaak\n",
"\n",
"Zoals we hebben vermeld, richten we ons op een eenvoudige tekstclassificatietaak gebaseerd op het **AG_NEWS**-dataset, waarbij nieuwsheadlines worden geclassificeerd in een van de 4 categorieën: Wereld, Sport, Zakelijk en Wetenschap/Technologie.\n",
"\n",
"## Het Dataset\n",
"\n",
"Dit dataset is ingebouwd in de [`torchtext`](https://github.com/pytorch/text) module, waardoor we er gemakkelijk toegang toe hebben.\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": [
"Hier bevatten `train_dataset` en `test_dataset` collecties die respectievelijk paren van label (nummer van klasse) en tekst retourneren, bijvoorbeeld:\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": [
"Dus, laten we de eerste 10 nieuwe koppen uit onze dataset afdrukken:\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": [
"Omdat datasets iterators zijn, moeten we de gegevens omzetten naar een lijst als we deze meerdere keren willen gebruiken:\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": [
"## Tokenisatie\n",
"\n",
"Nu moeten we tekst omzetten in **nummers** die kunnen worden weergegeven als tensors. Als we een representatie op woordniveau willen, moeten we twee dingen doen:\n",
"* een **tokenizer** gebruiken om tekst op te splitsen in **tokens**\n",
"* een **vocabulaire** opbouwen van die tokens.\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": [
"Met behulp van vocabulaire kunnen we onze getokeniseerde string eenvoudig coderen in een reeks cijfers:\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": [
"## Zak met Woorden tekstrepresentatie\n",
"\n",
"Omdat woorden betekenis vertegenwoordigen, kunnen we soms de betekenis van een tekst achterhalen door alleen naar de afzonderlijke woorden te kijken, ongeacht hun volgorde in de zin. Bijvoorbeeld, bij het classificeren van nieuws, zullen woorden zoals *weer*, *sneeuw* waarschijnlijk wijzen op *weersvoorspelling*, terwijl woorden zoals *aandelen*, *dollar* eerder zouden duiden op *financieel nieuws*.\n",
"\n",
"**Zak met Woorden** (BoW) vectorrepresentatie is de meest gebruikte traditionele vectorrepresentatie. Elk woord is gekoppeld aan een vectorindex, en het vectorelement bevat het aantal keren dat een woord voorkomt in een bepaald document.\n",
"\n",
"![Afbeelding die laat zien hoe een zak met woorden vectorrepresentatie in het geheugen wordt weergegeven.](../../../../../translated_images/nl/bag-of-words-example.606fc1738f1d7ba9.webp) \n",
"\n",
"> **Note**: Je kunt BoW ook zien als de som van alle one-hot-gecodeerde vectoren voor individuele woorden in de tekst.\n",
"\n",
"Hieronder staat een voorbeeld van hoe je een zak met woorden representatie kunt genereren met behulp van de Scikit Learn python bibliotheek:\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": [
"Om de bag-of-words vector te berekenen uit de vectorrepresentatie van onze AG_NEWS dataset, kunnen we de volgende functie gebruiken:\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": [
"> **Opmerking:** Hier gebruiken we de globale variabele `vocab_size` om de standaardgrootte van de woordenschat op te geven. Aangezien de woordenschat vaak behoorlijk groot is, kunnen we de grootte van de woordenschat beperken tot de meest voorkomende woorden. Probeer de waarde van `vocab_size` te verlagen en de onderstaande code uit te voeren, en kijk hoe dit de nauwkeurigheid beïnvloedt. Je kunt een lichte daling in nauwkeurigheid verwachten, maar niet dramatisch, in ruil voor betere prestaties.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Een BoW-classificator trainen\n",
"\n",
"Nu we hebben geleerd hoe we een Bag-of-Words-representatie van onze tekst kunnen maken, laten we een classificator hierop trainen. Eerst moeten we onze dataset zo omzetten voor training, dat alle positionele vectorrepresentaties worden omgezet naar een bag-of-words-representatie. Dit kan worden bereikt door de functie `bowify` door te geven als de parameter `collate_fn` aan de standaard torch `DataLoader`:\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": [
"Laten we nu een eenvoudige classifier-neuraal netwerk definiëren dat één lineaire laag bevat. De grootte van de invoervector is gelijk aan `vocab_size`, en de uitvoergrootte komt overeen met het aantal klassen (4). Omdat we een classificatietaak oplossen, is de uiteindelijke activatiefunctie `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": [
"Nu zullen we de standaard PyTorch-trainingslus definiëren. Omdat onze dataset vrij groot is, zullen we voor ons onderwijsdoel slechts één epoch trainen, en soms zelfs minder dan een epoch (het specificeren van de parameter `epoch_size` stelt ons in staat om de training te beperken). We zouden ook de geaccumuleerde trainingsnauwkeurigheid tijdens de training rapporteren; de frequentie van rapportage wordt gespecificeerd met de parameter `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": [
"## BiGrammen, TriGrammen en N-Grammen\n",
"\n",
"Een beperking van de bag-of-words aanpak is dat sommige woorden deel uitmaken van meerwoorduitdrukkingen. Bijvoorbeeld, het woord 'hot dog' heeft een compleet andere betekenis dan de woorden 'hot' en 'dog' in andere contexten. Als we de woorden 'hot' en 'dog' altijd met dezelfde vectoren representeren, kan dat ons model verwarren.\n",
"\n",
"Om dit probleem aan te pakken, worden **N-gram representaties** vaak gebruikt bij methoden voor documentclassificatie, waarbij de frequentie van elk woord, bi-woord of tri-woord een nuttige eigenschap is voor het trainen van classifiers. In een bigram-representatie voegen we bijvoorbeeld alle woordparen toe aan de vocabulaire, naast de oorspronkelijke woorden.\n",
"\n",
"Hieronder staat een voorbeeld van hoe je een bigram bag-of-words representatie kunt genereren met behulp van 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": [
"Het grootste nadeel van de N-gram aanpak is dat de omvang van de woordenschat extreem snel begint te groeien. In de praktijk moeten we de N-gram representatie combineren met enkele technieken voor dimensiereductie, zoals *embeddings*, die we in de volgende eenheid zullen bespreken.\n",
"\n",
"Om de N-gram representatie te gebruiken in ons **AG News** dataset, moeten we een speciale ngram-woordenschat opbouwen:\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": [
"We zouden dezelfde code als hierboven kunnen gebruiken om de classifier te trainen, maar dat zou erg geheugeninefficiënt zijn. In de volgende eenheid zullen we een bigram-classifier trainen met behulp van embeddings.\n",
"\n",
"> **Opmerking:** Je kunt alleen die ngrams behouden die vaker in de tekst voorkomen dan een opgegeven aantal keren. Dit zorgt ervoor dat zeldzame bigrams worden weggelaten en de dimensie aanzienlijk wordt verkleind. Stel hiervoor de parameter `min_freq` in op een hogere waarde en observeer hoe de lengte van de woordenschat verandert.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Termfrequentie Inverse Documentfrequentie TF-IDF\n",
"\n",
"In de BoW-representatie worden woordvoorkomens gelijk gewogen, ongeacht het woord zelf. Het is echter duidelijk dat frequente woorden, zoals *een*, *in*, enz., veel minder belangrijk zijn voor de classificatie dan gespecialiseerde termen. In feite zijn bij de meeste NLP-taken sommige woorden relevanter dan andere.\n",
"\n",
"**TF-IDF** staat voor **termfrequentieinverse documentfrequentie**. Het is een variatie op bag of words, waarbij in plaats van een binaire 0/1-waarde die aangeeft of een woord in een document voorkomt, een drijvende-kommawaarde wordt gebruikt die gerelateerd is aan de frequentie van woordvoorkomen in de corpus.\n",
"\n",
"Meer formeel wordt het gewicht $w_{ij}$ van een woord $i$ in document $j$ gedefinieerd als:\n",
"$$\n",
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
"$$\n",
"waarbij\n",
"* $tf_{ij}$ het aantal voorkomens van $i$ in $j$ is, oftewel de BoW-waarde die we eerder hebben gezien\n",
"* $N$ het aantal documenten in de collectie is\n",
"* $df_i$ het aantal documenten is waarin het woord $i$ voorkomt in de hele collectie\n",
"\n",
"De TF-IDF-waarde $w_{ij}$ neemt proportioneel toe met het aantal keren dat een woord in een document voorkomt en wordt gecorrigeerd door het aantal documenten in de corpus waarin het woord voorkomt. Dit helpt om te compenseren voor het feit dat sommige woorden vaker voorkomen dan andere. Bijvoorbeeld, als het woord in *elke* document in de collectie voorkomt, dan geldt $df_i=N$, en $w_{ij}=0$, en die termen worden volledig genegeerd.\n",
"\n",
"Je kunt eenvoudig een TF-IDF-vectorisatie van tekst maken met behulp van 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": [
"## Conclusie\n",
"\n",
"Hoewel TF-IDF-representaties frequentiegewicht toekennen aan verschillende woorden, zijn ze niet in staat om betekenis of volgorde weer te geven. Zoals de beroemde taalkundige J. R. Firth in 1935 zei: \"De volledige betekenis van een woord is altijd contextueel, en geen enkele studie van betekenis los van context kan serieus worden genomen.\" Later in de cursus zullen we leren hoe we contextuele informatie uit tekst kunnen vastleggen met behulp van taalmodellen.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Disclaimer**: \nDit document is vertaald met behulp van de AI-vertalingsservice [Co-op Translator](https://github.com/Azure/co-op-translator). Hoewel we streven naar nauwkeurigheid, dient u zich ervan bewust te zijn dat geautomatiseerde vertalingen fouten of onnauwkeurigheden kunnen bevatten. Het originele document in zijn oorspronkelijke taal moet worden beschouwd als de gezaghebbende bron. Voor cruciale informatie wordt professionele menselijke vertaling aanbevolen. Wij zijn niet aansprakelijk voor misverstanden of verkeerde interpretaties die voortvloeien uit het gebruik van deze vertaling.\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-28T21:59:07+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
"language_code": "nl"
}
},
"nbformat": 4,
"nbformat_minor": 2
}