{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Tekstklassifiseringsoppgave\n", "\n", "Som nevnt tidligere, skal vi fokusere på en enkel tekstklassifiseringsoppgave basert på **AG_NEWS**-datasettet, der målet er å klassifisere nyhetsoverskrifter i én av fire kategorier: Verden, Sport, Næringsliv og Vitenskap/Teknologi.\n", "\n", "## Datasettet\n", "\n", "Dette datasettet er innebygd i [`torchtext`](https://github.com/pytorch/text)-modulen, så vi kan enkelt få tilgang 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 inneholder `train_dataset` og `test_dataset` samlinger som returnerer par av etikett (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å, la oss skrive ut de første 10 nye overskriftene fra datasettet vårt:\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 datasett er iteratorer, hvis vi vil bruke dataene flere ganger må 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", "Nå må vi konvertere tekst til **tall** som kan representeres som tensorer. Hvis vi ønsker representasjon på ordnivå, må vi gjøre to ting:\n", "* bruke **tokenizer** for å dele opp tekst i **tokens**\n", "* bygge et **ordforråd** av 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": [] }, { "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 tekstrepresentasjon\n", "\n", "Fordi ord representerer mening, kan vi noen ganger forstå meningen med en tekst bare ved å se på de enkelte ordene, uavhengig av rekkefølgen i setningen. For eksempel, når vi klassifiserer nyheter, er ord som *vær*, *snø* sannsynligvis indikatorer på *værmelding*, mens ord som *aksjer*, *dollar* kan peke mot *finansnyheter*.\n", "\n", "**Bag of Words** (BoW) vektorrepresentasjon er den mest brukte tradisjonelle vektorrepresentasjonen. Hvert ord er knyttet til en vektorindeks, og hvert element i vektoren inneholder antall forekomster av et ord i et gitt dokument.\n", "\n", "![Bilde som viser hvordan en bag of words-vektorrepresentasjon lagres i minnet.](../../../../../translated_images/no/bag-of-words-example.606fc1738f1d7ba9.webp) \n", "\n", "> **Merk**: Du kan også tenke på BoW som en sum av alle én-hot-kodede vektorer for de individuelle ordene i teksten.\n", "\n", "Nedenfor er et eksempel på hvordan man kan generere en bag of words-representasjon ved hjelp av 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 å beregne bag-of-words-vektor fra vektorrepresentasjonen av vårt AG_NEWS-datasett, kan vi bruke følgende funksjon:\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 **Merk:** Her bruker vi den globale variabelen `vocab_size` for å spesifisere standardstørrelsen på ordforrådet. Siden ordforrådsstørrelsen ofte er ganske stor, kan vi begrense størrelsen på ordforrådet til de mest brukte ordene. Prøv å redusere verdien av `vocab_size` og kjøre koden nedenfor, og se hvordan det påvirker nøyaktigheten. Du bør forvente et visst fall i nøyaktighet, men ikke dramatisk, til fordel for høyere ytelse.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Trening av BoW-klassifiserer\n", "\n", "Nå som vi har lært hvordan vi bygger Bag-of-Words-representasjon av teksten vår, la oss trene en klassifiserer basert på den. Først må vi konvertere datasettet vårt for trening på en slik måte at alle posisjonsvektorrepresentasjoner blir konvertert til Bag-of-Words-representasjon. Dette kan oppnås ved å sende `bowify`-funksjonen 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": [ "La oss nå definere et enkelt klassifiserings-nevralt nettverk som inneholder ett lineært lag. Størrelsen på inputvektoren er lik `vocab_size`, og outputstørrelsen tilsvarer antall klasser (4). Fordi vi løser en klassifiseringsoppgave, er den endelige aktiveringsfunksjonen `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": [ "Nå skal vi definere standard treningssløyfe i PyTorch. Fordi datasettet vårt er ganske stort, vil vi for undervisningsformål kun trene i én epoke, og noen ganger til og med mindre enn én epoke (spesifisering av parameteren `epoch_size` lar oss begrense treningen). Vi vil også rapportere akkumulert treningsnøyaktighet under treningen; frekvensen for rapportering spesifiseres ved hjelp av parameteren `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": [ "## BiGrammer, TriGrammer og N-Grammer\n", "\n", "En begrensning med en bag-of-words-tilnærming er at noen ord er en del av flerspråklige uttrykk. For eksempel har ordet 'hot dog' en helt annen betydning enn ordene 'hot' og 'dog' i andre sammenhenger. Hvis vi alltid representerer ordene 'hot' og 'dog' med de samme vektorene, kan det forvirre modellen vår.\n", "\n", "For å løse dette brukes ofte **N-gram-representasjoner** i metoder for dokumentklassifisering, der frekvensen av hvert enkeltord, to-ordsuttrykk eller tre-ordsuttrykk er en nyttig funksjon for å trene klassifikatorer. I en bigram-representasjon, for eksempel, vil vi legge til alle ordpar i vokabularet, i tillegg til de opprinnelige ordene.\n", "\n", "Nedenfor er et eksempel på hvordan man kan generere en bigram bag-of-words-representasjon ved hjelp av 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 ulempen med N-gram-tilnærmingen er at vokabularstørrelsen begynner å vokse ekstremt raskt. I praksis må vi kombinere N-gram-representasjon med noen teknikker for dimensjonsreduksjon, som *embeddings*, som vi skal diskutere i neste enhet.\n", "\n", "For å bruke N-gram-representasjon i vårt **AG News**-datasett, må vi bygge et spesielt ngram-vokabular:\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 deretter bruke den samme koden som ovenfor for å trene klassifisereren, men det ville være svært minne-ineffektivt. I neste enhet vil vi trene bigram-klassifisereren ved hjelp av embeddings.\n", "\n", "> **Note:** Du kan kun beholde de ngrammene som forekommer i teksten mer enn et spesifisert antall ganger. Dette vil sørge for at sjeldne bigrammer blir utelatt, og vil redusere dimensjonaliteten betydelig. For å gjøre dette, sett `min_freq`-parameteren til en høyere verdi, og observer hvordan vokabularlengden endrer seg.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Termfrekvens Invers Dokumentfrekvens (TF-IDF)\n", "\n", "I BoW-representasjon blir ordforekomster vektet likt, uavhengig av selve ordet. Det er imidlertid tydelig at hyppige ord, som *en*, *i*, osv., er langt mindre viktige for klassifisering enn spesialiserte termer. Faktisk er noen ord mer relevante enn andre i de fleste NLP-oppgaver.\n", "\n", "**TF-IDF** står for **termfrekvens–invers dokumentfrekvens**. Det er en variant av bag of words, der man i stedet for en binær 0/1-verdi som indikerer tilstedeværelsen av et ord i et dokument, bruker en flyttallsverdi som er relatert til hvor ofte ordet forekommer i korpuset.\n", "\n", "Mer formelt er vekten $w_{ij}$ til et ord $i$ i dokumentet $j$ definert som:\n", "$$\n", "w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n", "$$\n", "hvor\n", "* $tf_{ij}$ er antall forekomster av $i$ i $j$, altså BoW-verdien vi har sett tidligere\n", "* $N$ er antall dokumenter i samlingen\n", "* $df_i$ er antall dokumenter som inneholder ordet $i$ i hele samlingen\n", "\n", "TF-IDF-verdien $w_{ij}$ øker proporsjonalt med antall ganger et ord vises i et dokument og justeres ned basert på antall dokumenter i korpuset som inneholder ordet. Dette hjelper med å kompensere for det faktum at noen ord forekommer oftere enn andre. For eksempel, hvis ordet vises i *alle* dokumentene i samlingen, er $df_i=N$, og $w_{ij}=0$, og disse termene vil bli fullstendig ignorert.\n", "\n", "Du kan enkelt lage TF-IDF-vektorisering av tekst ved hjelp av 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": [ "## Konklusjon\n", "\n", "Selv om TF-IDF-representasjoner gir frekvensvekt til ulike ord, er de ikke i stand til å representere mening eller rekkefølge. Som den kjente lingvisten J. R. Firth sa i 1935: \"Den fullstendige betydningen av et ord er alltid kontekstuelt, og ingen studie av betydning utenfor kontekst kan tas seriøst.\" Senere i kurset vil vi lære hvordan vi kan fange opp kontekstuell informasjon fra tekst ved hjelp av språkmodellering.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n---\n\n**Ansvarsfraskrivelse**: \nDette dokumentet er oversatt ved hjelp av AI-oversettelsestjenesten [Co-op Translator](https://github.com/Azure/co-op-translator). Selv om vi streber etter nøyaktighet, vær oppmerksom på at automatiserte oversettelser kan inneholde feil eller unøyaktigheter. Det originale dokumentet på sitt opprinnelige språk bør anses som den autoritative kilden. For kritisk informasjon anbefales profesjonell menneskelig oversettelse. Vi er ikke ansvarlige for misforståelser eller feiltolkninger som oppstår ved bruk av denne oversettelsen.\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:54:16+00:00", "source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb", "language_code": "no" } }, "nbformat": 4, "nbformat_minor": 2 }