647 lines
26 KiB
Plaintext
647 lines
26 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Tekstiluokittelutehtävä\n",
|
|
"\n",
|
|
"Tässä moduulissa aloitamme yksinkertaisella tekstiluokittelutehtävällä, joka perustuu **[AG_NEWS](http://www.di.unipi.it/~gulli/AG_corpus_of_news_articles.html)**-aineistoon: luokittelemme uutisotsikot yhteen neljästä kategoriasta: Maailma, Urheilu, Liiketoiminta ja Tiede/Tekniikka.\n",
|
|
"\n",
|
|
"## Aineisto\n",
|
|
"\n",
|
|
"Aineiston lataamiseen käytämme **[TensorFlow Datasets](https://www.tensorflow.org/datasets)**-rajapintaa.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import tensorflow as tf\n",
|
|
"from tensorflow import keras\n",
|
|
"import tensorflow_datasets as tfds\n",
|
|
"\n",
|
|
"# In this tutorial, we will be training a lot of models. In order to use GPU memory cautiously,\n",
|
|
"# we will set tensorflow option to grow GPU memory allocation when required.\n",
|
|
"physical_devices = tf.config.list_physical_devices('GPU') \n",
|
|
"if len(physical_devices)>0:\n",
|
|
" tf.config.experimental.set_memory_growth(physical_devices[0], True)\n",
|
|
"\n",
|
|
"dataset = tfds.load('ag_news_subset')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Voimme nyt käyttää aineiston harjoitus- ja testiosia käyttämällä `dataset['train']` ja `dataset['test']` vastaavasti:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Length of train dataset = 120000\n",
|
|
"Length of test dataset = 7600\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"ds_train = dataset['train']\n",
|
|
"ds_test = dataset['test']\n",
|
|
"\n",
|
|
"print(f\"Length of train dataset = {len(ds_train)}\")\n",
|
|
"print(f\"Length of test dataset = {len(ds_test)}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Tulostetaan ensimmäiset 10 uutta otsikkoa aineistostamme:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3 (Sci/Tech) -> b'AMD Debuts Dual-Core Opteron Processor' b'AMD #39;s new dual-core Opteron chip is designed mainly for corporate computing applications, including databases, Web services, and financial transactions.'\n",
|
|
"1 (Sports) -> b\"Wood's Suspension Upheld (Reuters)\" b'Reuters - Major League Baseball\\\\Monday announced a decision on the appeal filed by Chicago Cubs\\\\pitcher Kerry Wood regarding a suspension stemming from an\\\\incident earlier this season.'\n",
|
|
"2 (Business) -> b'Bush reform may have blue states seeing red' b'President Bush #39;s quot;revenue-neutral quot; tax reform needs losers to balance its winners, and people claiming the federal deduction for state and local taxes may be in administration planners #39; sights, news reports say.'\n",
|
|
"3 (Sci/Tech) -> b\"'Halt science decline in schools'\" b'Britain will run out of leading scientists unless science education is improved, says Professor Colin Pillinger.'\n",
|
|
"1 (Sports) -> b'Gerrard leaves practice' b'London, England (Sports Network) - England midfielder Steven Gerrard injured his groin late in Thursday #39;s training session, but is hopeful he will be ready for Saturday #39;s World Cup qualifier against Austria.'\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"classes = ['World', 'Sports', 'Business', 'Sci/Tech']\n",
|
|
"\n",
|
|
"for i,x in zip(range(5),ds_train):\n",
|
|
" print(f\"{x['label']} ({classes[x['label']]}) -> {x['title']} {x['description']}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Tekstin vektorisointi\n",
|
|
"\n",
|
|
"Nyt meidän täytyy muuntaa teksti **numeroiksi**, jotka voidaan esittää tensoreina. Jos haluamme sanatasoisen esityksen, meidän täytyy tehdä kaksi asiaa:\n",
|
|
"\n",
|
|
"* Käyttää **tokenisoijaa** jakamaan teksti **tokeneiksi**.\n",
|
|
"* Rakentaa näistä tokeneista **sanasto**.\n",
|
|
"\n",
|
|
"### Sanaston koon rajoittaminen\n",
|
|
"\n",
|
|
"AG News -aineiston esimerkissä sanaston koko on melko suuri, yli 100 000 sanaa. Yleisesti ottaen emme tarvitse sanoja, jotka esiintyvät tekstissä harvoin — vain muutamassa lauseessa niitä on, eikä malli opi niistä. Siksi on järkevää rajoittaa sanaston koko pienemmäksi antamalla argumentti vektorisointikerroksen konstruktorille:\n",
|
|
"\n",
|
|
"Molemmat näistä vaiheista voidaan hoitaa käyttämällä **TextVectorization**-kerrosta. Luodaan vektorisointiobjekti ja kutsutaan sitten `adapt`-metodia, jotta käydään läpi kaikki teksti ja rakennetaan sanasto:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"vocab_size = 50000\n",
|
|
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size)\n",
|
|
"vectorizer.adapt(ds_train.take(500).map(lambda x: x['title']+' '+x['description']))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **Huomaa**, että käytämme vain osajoukkoa koko aineistosta sanaston rakentamiseen. Teemme tämän nopeuttaaksemme suoritusaikaa, jotta sinun ei tarvitse odottaa. Otamme kuitenkin riskin, että jotkut sanat koko aineistosta eivät sisälly sanastoon ja ne ohitetaan koulutuksen aikana. Koko sanaston koon käyttäminen ja koko aineiston läpikäyminen `adapt`-vaiheen aikana voisi parantaa lopullista tarkkuutta, mutta ei merkittävästi.\n",
|
|
"\n",
|
|
"Nyt voimme käyttää varsinaista sanastoa:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"['', '[UNK]', 'the', 'to', 'a', 'in', 'of', 'and', 'on', 'for']\n",
|
|
"Length of vocabulary: 5335\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"vocab = vectorizer.get_vocabulary()\n",
|
|
"vocab_size = len(vocab)\n",
|
|
"print(vocab[:10])\n",
|
|
"print(f\"Length of vocabulary: {vocab_size}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Vektorisoijaa käyttämällä voimme helposti koodata minkä tahansa tekstin numerosarjaksi:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<tf.Tensor: shape=(7,), dtype=int64, numpy=array([ 112, 3695, 3, 304, 11, 1041, 1], dtype=int64)>"
|
|
]
|
|
},
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"vectorizer('I love to play with my words')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Bag-of-words-tekstin esitys\n",
|
|
"\n",
|
|
"Koska sanat edustavat merkitystä, joskus voimme ymmärtää tekstin merkityksen pelkästään tarkastelemalla yksittäisiä sanoja, riippumatta niiden järjestyksestä lauseessa. Esimerkiksi uutisia luokitellessa sanat kuten *sää* ja *lumi* viittaavat todennäköisesti *sääennusteeseen*, kun taas sanat kuten *osakkeet* ja *dollari* liittyvät *talousuutisiin*.\n",
|
|
"\n",
|
|
"**Bag-of-words** (BoW) -vektoriesitys on perinteisistä vektoriesityksistä yksinkertaisin ymmärtää. Jokainen sana yhdistetään vektorin indeksiin, ja vektorin elementti sisältää kunkin sanan esiintymiskertojen määrän tietyssä dokumentissa.\n",
|
|
"\n",
|
|
" \n",
|
|
"\n",
|
|
"> **Note**: Voit myös ajatella BoW:n olevan summa kaikista yksittäisten sanojen yksi-kuuma-koodatuista vektoreista tekstissä.\n",
|
|
"\n",
|
|
"Alla on esimerkki siitä, miten bag-of-words-esitys voidaan luoda Scikit Learn -python-kirjaston avulla:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)"
|
|
]
|
|
},
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"from sklearn.feature_extraction.text import CountVectorizer\n",
|
|
"sc_vectorizer = CountVectorizer()\n",
|
|
"corpus = [\n",
|
|
" 'I like hot dogs.',\n",
|
|
" 'The dog ran fast.',\n",
|
|
" 'Its hot outside.',\n",
|
|
" ]\n",
|
|
"sc_vectorizer.fit_transform(corpus)\n",
|
|
"sc_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Voimme myös käyttää yllä määrittelemäämme Keras-vektoroijaa, muuntaen jokaisen sanan numeron yksi-hot-koodaukseksi ja yhteenlaskemalla kaikki nämä vektorit:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"array([0., 5., 0., ..., 0., 0., 0.], dtype=float32)"
|
|
]
|
|
},
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"def to_bow(text):\n",
|
|
" return tf.reduce_sum(tf.one_hot(vectorizer(text),vocab_size),axis=0)\n",
|
|
"\n",
|
|
"to_bow('My dog likes hot dogs on a hot day.').numpy()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **Huom**: Saatat yllättyä, että tulos poikkeaa aiemmasta esimerkistä. Syynä on se, että Keras-esimerkissä vektorin pituus vastaa sanaston kokoa, joka rakennettiin koko AG News -aineistosta, kun taas Scikit Learn -esimerkissä rakensimme sanaston lennossa näytetekstistä.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## BoW-luokittelijan kouluttaminen\n",
|
|
"\n",
|
|
"Nyt kun olemme oppineet rakentamaan tekstimme bag-of-words-esityksen, koulutetaan luokittelija, joka käyttää sitä. Ensin meidän täytyy muuntaa datamme bag-of-words-esitykseksi. Tämä voidaan tehdä käyttämällä `map`-funktiota seuraavalla tavalla:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"batch_size = 128\n",
|
|
"\n",
|
|
"ds_train_bow = ds_train.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)\n",
|
|
"ds_test_bow = ds_test.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Nyt määritellään yksinkertainen luokittelijaneuroverkko, joka sisältää yhden lineaarisen kerroksen. Syötteen koko on `vocab_size`, ja ulostulon koko vastaa luokkien määrää (4). Koska ratkaistaan luokittelutehtävää, lopullinen aktivointifunktio on **softmax**:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"938/938 [==============================] - 66s 70ms/step - loss: 0.6144 - acc: 0.8427 - val_loss: 0.4416 - val_acc: 0.8697\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<keras.callbacks.History at 0x20c70a947f0>"
|
|
]
|
|
},
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model = keras.models.Sequential([\n",
|
|
" keras.layers.Dense(4,activation='softmax',input_shape=(vocab_size,))\n",
|
|
"])\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
|
"model.fit(ds_train_bow,validation_data=ds_test_bow)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Koska meillä on 4 luokkaa, yli 80 %:n tarkkuus on hyvä tulos.\n",
|
|
"\n",
|
|
"## Luokittelijan kouluttaminen yhtenä verkostona\n",
|
|
"\n",
|
|
"Koska vektoroija on myös Keras-kerros, voimme määritellä verkoston, joka sisältää sen, ja kouluttaa sen päästä päähän. Tällä tavalla meidän ei tarvitse vektoroida datasettiä käyttämällä `map`-funktiota, vaan voimme yksinkertaisesti syöttää alkuperäisen datasetin verkoston syötteeksi.\n",
|
|
"\n",
|
|
"> **Note**: Meidän täytyisi silti soveltaa map-toimintoja datasettiimme, jotta voimme muuntaa sanakirjojen kentät (kuten `title`, `description` ja `label`) tupleiksi. Kuitenkin, kun lataamme dataa levyltä, voimme alusta alkaen rakentaa datasetin tarvittavalla rakenteella.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Model: \"model\"\n",
|
|
"_________________________________________________________________\n",
|
|
" Layer (type) Output Shape Param # \n",
|
|
"=================================================================\n",
|
|
" input_1 (InputLayer) [(None, 1)] 0 \n",
|
|
" \n",
|
|
" text_vectorization (TextVec (None, None) 0 \n",
|
|
" torization) \n",
|
|
" \n",
|
|
" tf.one_hot (TFOpLambda) (None, None, 5335) 0 \n",
|
|
" \n",
|
|
" tf.math.reduce_sum (TFOpLam (None, 5335) 0 \n",
|
|
" bda) \n",
|
|
" \n",
|
|
" dense_2 (Dense) (None, 4) 21344 \n",
|
|
" \n",
|
|
"=================================================================\n",
|
|
"Total params: 21,344\n",
|
|
"Trainable params: 21,344\n",
|
|
"Non-trainable params: 0\n",
|
|
"_________________________________________________________________\n",
|
|
"938/938 [==============================] - 73s 77ms/step - loss: 0.6057 - acc: 0.8414 - val_loss: 0.4202 - val_acc: 0.8736\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<keras.callbacks.History at 0x20c721521f0>"
|
|
]
|
|
},
|
|
"execution_count": 13,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"def extract_text(x):\n",
|
|
" return x['title']+' '+x['description']\n",
|
|
"\n",
|
|
"def tupelize(x):\n",
|
|
" return (extract_text(x),x['label'])\n",
|
|
"\n",
|
|
"inp = keras.Input(shape=(1,),dtype=tf.string)\n",
|
|
"x = vectorizer(inp)\n",
|
|
"x = tf.reduce_sum(tf.one_hot(x,vocab_size),axis=1)\n",
|
|
"out = keras.layers.Dense(4,activation='softmax')(x)\n",
|
|
"model = keras.models.Model(inp,out)\n",
|
|
"model.summary()\n",
|
|
"\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
|
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Bigrammit, trigrammit ja n-grammit\n",
|
|
"\n",
|
|
"Yksi bag-of-words-lähestymistavan rajoitus on, että jotkin sanat kuuluvat monisanaisiin ilmauksiin. Esimerkiksi sana 'hot dog' tarkoittaa jotain täysin erilaista kuin sanat 'hot' ja 'dog' muissa yhteyksissä. Jos edustamme sanoja 'hot' ja 'dog' aina samoilla vektoreilla, se voi hämmentää malliamme.\n",
|
|
"\n",
|
|
"Tämän ratkaisemiseksi käytetään usein **n-grammi-edustuksia** dokumenttien luokittelumenetelmissä, joissa jokaisen sanan, kahden sanan yhdistelmän tai kolmen sanan yhdistelmän esiintymistiheys on hyödyllinen ominaisuus luokittelijoiden kouluttamisessa. Esimerkiksi bigrammi-edustuksissa lisäämme sanastoon kaikki sanaparit alkuperäisten sanojen lisäksi.\n",
|
|
"\n",
|
|
"Alla on esimerkki siitä, miten bigrammi bag-of-words -edustus voidaan luoda käyttämällä Scikit Learnia:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"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": 14,
|
|
"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": [
|
|
"N-gram-lähestymistavan suurin haitta on, että sanaston koko alkaa kasvaa erittäin nopeasti. Käytännössä meidän täytyy yhdistää n-gram-esitys ulottuvuuksien vähentämistekniikkaan, kuten *upotuksiin*, joita käsittelemme seuraavassa osiossa.\n",
|
|
"\n",
|
|
"Jotta voimme käyttää n-gram-esitystä **AG News** -aineistossamme, meidän täytyy välittää `ngrams`-parametri `TextVectorization`-rakentajalle. Bigram-sanaston pituus on **merkittävästi suurempi**, meidän tapauksessamme yli 1,3 miljoonaa tokenia! Siksi on järkevää rajoittaa myös bigram-tokenit kohtuulliseen määrään.\n",
|
|
"\n",
|
|
"Voisimme käyttää samaa koodia kuin yllä luokittelijan kouluttamiseen, mutta se olisi erittäin muistitehotonta. Seuraavassa osiossa koulutamme bigram-luokittelijan käyttämällä upotuksia. Sillä välin voit kokeilla bigram-luokittelijan kouluttamista tässä muistikirjassa ja katsoa, saatko paremman tarkkuuden.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## BoW-vektoreiden automaattinen laskeminen\n",
|
|
"\n",
|
|
"Yllä olevassa esimerkissä laskimme BoW-vektorit käsin yhteenlaskemalla yksittäisten sanojen yksi-kuuma-koodaukset. Uusin TensorFlow-versio mahdollistaa kuitenkin BoW-vektoreiden automaattisen laskemisen välittämällä `output_mode='count`-parametrin vektorisoinnin konstruktoriin. Tämä tekee mallin määrittelystä ja kouluttamisesta huomattavasti helpompaa:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Training vectorizer\n",
|
|
"938/938 [==============================] - 7s 7ms/step - loss: 0.5929 - acc: 0.8486 - val_loss: 0.4168 - val_acc: 0.8772\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<keras.callbacks.History at 0x20c725217c0>"
|
|
]
|
|
},
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model = keras.models.Sequential([\n",
|
|
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='count'),\n",
|
|
" keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n",
|
|
"])\n",
|
|
"print(\"Training vectorizer\")\n",
|
|
"model.layers[0].adapt(ds_train.take(500).map(extract_text))\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
|
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Term frequency - inverse document frequency (TF-IDF)\n",
|
|
"\n",
|
|
"BoW-esityksessä sanan esiintymisiä painotetaan samalla tavalla riippumatta itse sanasta. On kuitenkin selvää, että yleiset sanat kuten *a* ja *in* ovat paljon vähemmän merkityksellisiä luokittelun kannalta kuin erikoistuneet termit. Useimmissa NLP-tehtävissä jotkut sanat ovat tärkeämpiä kuin toiset.\n",
|
|
"\n",
|
|
"**TF-IDF** tarkoittaa **term frequency - inverse document frequency**. Se on muunnelma bag-of-words-menetelmästä, jossa binäärisen 0/1-arvon sijaan, joka ilmaisee sanan esiintymisen dokumentissa, käytetään liukulukuarvoa, joka liittyy sanan esiintymisen tiheyteen korpuksessa.\n",
|
|
"\n",
|
|
"Tarkemmin määriteltynä sanan $i$ paino $w_{ij}$ dokumentissa $j$ määritellään seuraavasti:\n",
|
|
"$$\n",
|
|
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
|
|
"$$\n",
|
|
"missä\n",
|
|
"* $tf_{ij}$ on sanan $i$ esiintymiskertojen määrä dokumentissa $j$, eli BoW-arvo, jonka olemme aiemmin nähneet\n",
|
|
"* $N$ on kokoelman dokumenttien lukumäärä\n",
|
|
"* $df_i$ on niiden dokumenttien lukumäärä, jotka sisältävät sanan $i$ koko kokoelmassa\n",
|
|
"\n",
|
|
"TF-IDF-arvo $w_{ij}$ kasvaa suhteessa siihen, kuinka monta kertaa sana esiintyy dokumentissa, ja sitä tasapainotetaan korpuksen dokumenttien määrällä, jotka sisältävät kyseisen sanan. Tämä auttaa korjaamaan sen, että jotkut sanat esiintyvät useammin kuin toiset. Esimerkiksi, jos sana esiintyy *jokaisessa* kokoelman dokumentissa, $df_i=N$, ja $w_{ij}=0$, jolloin nämä termit jätetään kokonaan huomiotta.\n",
|
|
"\n",
|
|
"Voit helposti luoda tekstin TF-IDF-vektorisoinnin käyttämällä Scikit Learnia:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"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": 16,
|
|
"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": [
|
|
"Kerasissa `TextVectorization`-kerros voi automaattisesti laskea TF-IDF-taajuudet käyttämällä `output_mode='tf-idf'`-parametria. Toistetaan yllä käyttämämme koodi nähdäksemme, lisääkö TF-IDF:n käyttö tarkkuutta:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Training vectorizer\n",
|
|
"938/938 [==============================] - 12s 12ms/step - loss: 0.4197 - acc: 0.8662 - val_loss: 0.3432 - val_acc: 0.8849\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<keras.callbacks.History at 0x20c729dfd30>"
|
|
]
|
|
},
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model = keras.models.Sequential([\n",
|
|
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='tf-idf'),\n",
|
|
" keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n",
|
|
"])\n",
|
|
"print(\"Training vectorizer\")\n",
|
|
"model.layers[0].adapt(ds_train.take(500).map(extract_text))\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
|
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Johtopäätös\n",
|
|
"\n",
|
|
"Vaikka TF-IDF-esitykset antavat sanakohtaisia painotuksia niiden esiintymistiheyden perusteella, ne eivät pysty ilmaisemaan merkitystä tai järjestystä. Kuten kuuluisa kielitieteilijä J. R. Firth totesi vuonna 1935: \"Sanalla on aina täydellinen merkitys vain kontekstissaan, eikä merkityksen tutkimista ilman kontekstia voida ottaa vakavasti.\" Kurssin myöhemmässä vaiheessa opimme, kuinka tekstistä voidaan saada kontekstuaalista tietoa kielenmallinnuksen avulla.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Vastuuvapauslauseke**: \nTämä asiakirja on käännetty käyttämällä tekoälypohjaista käännöspalvelua [Co-op Translator](https://github.com/Azure/co-op-translator). Vaikka pyrimme tarkkuuteen, huomioithan, että automaattiset käännökset voivat sisältää virheitä tai epätarkkuuksia. Alkuperäistä asiakirjaa sen alkuperäisellä kielellä tulisi pitää ensisijaisena lähteenä. Kriittisen tiedon osalta suositellaan ammattimaista ihmiskäännöstä. Emme ole vastuussa väärinkäsityksistä tai virhetulkinnoista, jotka johtuvat tämän käännöksen käytöstä.\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"interpreter": {
|
|
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
|
},
|
|
"kernel_info": {
|
|
"name": "conda-env-py37_tensorflow-py"
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "py37_tensorflow",
|
|
"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"
|
|
},
|
|
"nteract": {
|
|
"version": "nteract-front-end@1.0.0"
|
|
},
|
|
"coopTranslator": {
|
|
"original_hash": "19b43951d55b377a76209c24c1f017e4",
|
|
"translation_date": "2025-08-28T22:00:56+00:00",
|
|
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb",
|
|
"language_code": "fi"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
} |