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

577 lines
21 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": [
"# Tekstklassifikationsopgave\n",
"\n",
"Som nævnt vil vi fokusere på en simpel tekstklassifikationsopgave baseret på **AG_NEWS**-datasættet, som går ud på at klassificere nyhedsoverskrifter i en af 4 kategorier: Verden, Sport, Erhverv og Videnskab/Teknologi.\n",
"\n",
"## Datasættet\n",
"\n",
"Dette datasæt er indbygget i [`torchtext`](https://github.com/pytorch/text)-modulet, så vi kan nemt få adgang til det.\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": [
"Her indeholder `train_dataset` og `test_dataset` samlinger, der returnerer par af label (nummer på klasse) og tekst henholdsvis, for eksempel:\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": [
"Så lad os udskrive de første 10 nye overskrifter fra vores datasæt:\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": [
"Fordi datasæt er iteratorer, hvis vi vil bruge dataene flere gange, skal vi konvertere det til en liste:\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": [
"## Tokenisering\n",
"\n",
"Nu skal vi konvertere tekst til **tal**, som kan repræsenteres som tensorer. Hvis vi ønsker repræsentation på ordniveau, skal vi gøre to ting:\n",
"* bruge en **tokenizer** til at opdele teksten i **tokens**\n",
"* opbygge et **ordforråd** af disse 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": [
"Ved hjælp af ordforråd kan vi nemt kode vores tokeniserede streng til et sæt tal:\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": [
"## Bag of Words tekstrepræsentation\n",
"\n",
"Fordi ord repræsenterer betydning, kan vi nogle gange forstå meningen med en tekst blot ved at se på de enkelte ord, uanset deres rækkefølge i sætningen. For eksempel, når vi klassificerer nyheder, er ord som *vejr*, *sne* sandsynligvis indikatorer for *vejrudsigter*, mens ord som *aktier*, *dollar* ville pege på *finansielle nyheder*.\n",
"\n",
"**Bag of Words** (BoW) vektorrepræsentation er den mest almindeligt anvendte traditionelle vektorrepræsentation. Hvert ord er knyttet til en vektorindeks, og vektorelementet indeholder antallet af forekomster af et ord i et givet dokument.\n",
"\n",
"![Billede, der viser, hvordan en bag of words-vektorrepræsentation er repræsenteret i hukommelsen.](../../../../../translated_images/bag-of-words-example.606fc1738f1d7ba98a9d693e3bcd706c6e83fa7bf8221e6e90d1a206d82f2ea4.da.png) \n",
"\n",
"> **Note**: Du kan også tænke på BoW som en sum af alle one-hot-kodede vektorer for de enkelte ord i teksten.\n",
"\n",
"Nedenfor er et eksempel på, hvordan man genererer en bag of words-repræsentation ved hjælp af Scikit Learn python-biblioteket:\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": [
"For at beregne bag-of-words-vektoren fra vektorrepræsentationen af vores AG_NEWS-datasæt, kan vi bruge følgende funktion:\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": [
"> **Bemærk:** Her bruger vi den globale `vocab_size`-variabel til at angive standardstørrelsen på ordforrådet. Da ordforrådsstørrelsen ofte er ret stor, kan vi begrænse størrelsen på ordforrådet til de mest hyppige ord. Prøv at sænke værdien af `vocab_size` og køre koden nedenfor, og se hvordan det påvirker nøjagtigheden. Du bør forvente et vist fald i nøjagtighed, men ikke dramatisk, til fordel for højere ydeevne.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Træning af BoW-klassifikator\n",
"\n",
"Nu hvor vi har lært, hvordan man opbygger en Bag-of-Words-repræsentation af vores tekst, lad os træne en klassifikator ovenpå den. Først skal vi konvertere vores datasæt til træning på en måde, så alle positionelle vektorrepræsentationer bliver konverteret til Bag-of-Words-repræsentation. Dette kan opnås ved at bruge `bowify`-funktionen som `collate_fn`-parameter til standard 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": [
"Lad os nu definere et simpelt klassifikations-neuralt netværk, der indeholder et lineært lag. Størrelsen på inputvektoren er lig med `vocab_size`, og outputstørrelsen svarer til antallet af klasser (4). Da vi løser en klassifikationsopgave, er den endelige aktiveringsfunktion `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 vil vi definere standard PyTorch-træningsloop. Da vores datasæt er ret stort, vil vi til undervisningsformål kun træne i én epoch, og nogle gange endda i mindre end én epoch (angivelse af `epoch_size`-parameteren giver os mulighed for at begrænse træningen). Vi vil også rapportere akkumuleret træningsnøjagtighed under træningen; frekvensen for rapportering angives ved hjælp af `report_freq`-parameteren.\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": [
"## BiGrams, TriGrams og N-Grams\n",
"\n",
"En begrænsning ved en bag of words-tilgang er, at nogle ord indgår i udtryk med flere ord. For eksempel har ordet 'hot dog' en helt anden betydning end ordene 'hot' og 'dog' i andre sammenhænge. Hvis vi altid repræsenterer ordene 'hot' og 'dog' med de samme vektorer, kan det forvirre vores model.\n",
"\n",
"For at løse dette bruges **N-gram-repræsentationer** ofte i metoder til dokumentklassifikation, hvor frekvensen af hvert ord, to-ords eller tre-ords udtryk er en nyttig funktion til at træne klassifikatorer. I bigram-repræsentation, for eksempel, tilføjer vi alle ordpar til ordforrådet, ud over de oprindelige ord.\n",
"\n",
"Nedenfor er et eksempel på, hvordan man genererer en bigram bag of words-repræsentation ved hjælp af 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": [
"Den største ulempe ved N-gram-metoden er, at ordforrådet begynder at vokse ekstremt hurtigt. I praksis er vi nødt til at kombinere N-gram-repræsentation med nogle teknikker til reduktion af dimensioner, såsom *embeddings*, som vi vil diskutere i næste enhed.\n",
"\n",
"For at bruge N-gram-repræsentation i vores **AG News**-datasæt, skal vi opbygge et specielt ngram-ordforråd:\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": [
"Vi kunne derefter bruge den samme kode som ovenfor til at træne klassifikatoren, men det ville være meget hukommelsesineffektivt. I den næste enhed vil vi træne en bigram-klassifikator ved hjælp af embeddings.\n",
"\n",
"> **Note:** Du kan kun beholde de ngrams, der forekommer i teksten mere end det angivne antal gange. Dette vil sikre, at sjældne bigrams udelades, og vil reducere dimensionaliteten betydeligt. For at gøre dette skal du indstille `min_freq`-parameteren til en højere værdi og observere ændringen i ordforrådets længde.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Termfrekvens og Invers Dokumentfrekvens (TF-IDF)\n",
"\n",
"I BoW-repræsentationen vægtes ordforekomster ens, uanset hvilket ord der er tale om. Det er dog tydeligt, at hyppige ord som *en*, *i* osv. er langt mindre vigtige for klassificeringen end specialiserede termer. Faktisk er nogle ord mere relevante end andre i de fleste NLP-opgaver.\n",
"\n",
"**TF-IDF** står for **termfrekvensinvers dokumentfrekvens**. Det er en variation af bag of words, hvor man i stedet for en binær 0/1-værdi, der angiver, om et ord optræder i et dokument, bruger en flydende værdi, som relaterer sig til hyppigheden af ordets forekomst i korpuset.\n",
"\n",
"Mere formelt defineres vægten $w_{ij}$ af et ord $i$ i dokumentet $j$ som:\n",
"$$\n",
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
"$$\n",
"hvor\n",
"* $tf_{ij}$ er antallet af forekomster af $i$ i $j$, dvs. den BoW-værdi, vi har set tidligere\n",
"* $N$ er antallet af dokumenter i samlingen\n",
"* $df_i$ er antallet af dokumenter, der indeholder ordet $i$ i hele samlingen\n",
"\n",
"TF-IDF-værdien $w_{ij}$ stiger proportionalt med, hvor mange gange et ord optræder i et dokument, og justeres i forhold til antallet af dokumenter i korpuset, der indeholder ordet. Dette hjælper med at tage højde for, at nogle ord optræder hyppigere end andre. For eksempel, hvis ordet optræder i *alle* dokumenter i samlingen, er $df_i=N$, og $w_{ij}=0$, og disse termer vil blive fuldstændigt ignoreret.\n",
"\n",
"Du kan nemt oprette TF-IDF-vektorisering af tekst ved hjælp af 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": [
"## Konklusion\n",
"\n",
"Selvom TF-IDF-repræsentationer giver vægt til forskellige ord baseret på deres frekvens, er de ikke i stand til at repræsentere betydning eller rækkefølge. Som den berømte lingvist J. R. Firth sagde i 1935: \"Den fulde betydning af et ord er altid kontekstuel, og ingen undersøgelse af betydning uden for kontekst kan tages seriøst.\" Senere i kurset vil vi lære, hvordan man fanger kontekstuel information fra tekst ved hjælp af sprogmodellering.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Ansvarsfraskrivelse**: \nDette dokument er blevet oversat ved hjælp af AI-oversættelsestjenesten [Co-op Translator](https://github.com/Azure/co-op-translator). Selvom vi bestræber os på at sikre nøjagtighed, skal du være opmærksom på, at automatiserede oversættelser kan indeholde fejl eller unøjagtigheder. Det originale dokument på dets oprindelige sprog bør betragtes som den autoritative kilde. For kritisk information anbefales professionel menneskelig oversættelse. Vi påtager os ikke ansvar for eventuelle misforståelser eller fejltolkninger, der måtte opstå som følge af brugen af denne oversættelse.\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-28T17:53:26+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
"language_code": "da"
}
},
"nbformat": 4,
"nbformat_minor": 2
}