AI-For-Beginners/translations/sv/lessons/5-NLP/16-RNN/RNNTF.ipynb

460 lines
23 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Rekurrenta neurala nätverk\n",
"\n",
"I den föregående modulen gick vi igenom rika semantiska representationer av text. Arkitekturen vi har använt fångar den aggregerade betydelsen av orden i en mening, men den tar inte hänsyn till **ordningen** på orden, eftersom aggregeringsoperationen som följer efter inbäddningarna tar bort denna information från den ursprungliga texten. Eftersom dessa modeller inte kan representera ordningen på orden, kan de inte lösa mer komplexa eller tvetydiga uppgifter som textgenerering eller frågesvar.\n",
"\n",
"För att fånga betydelsen av en textsekvens kommer vi att använda en neural nätverksarkitektur som kallas **rekurrenta neurala nätverk**, eller RNN. När vi använder en RNN skickar vi vår mening genom nätverket en token i taget, och nätverket producerar ett **tillstånd**, som vi sedan skickar vidare till nätverket tillsammans med nästa token.\n",
"\n",
"![Bild som visar ett exempel på generering med rekurrenta neurala nätverk.](../../../../../translated_images/sv/rnn.27f5c29c53d727b5.webp)\n",
"\n",
"Givet en inmatningssekvens av token $X_0,\\dots,X_n$, skapar RNN en sekvens av neurala nätverksblock och tränar denna sekvens från början till slut med hjälp av backpropagation. Varje nätverksblock tar ett par $(X_i,S_i)$ som indata och producerar $S_{i+1}$ som resultat. Det slutliga tillståndet $S_n$ eller utdata $Y_n$ skickas till en linjär klassificerare för att producera resultatet. Alla nätverksblock delar samma vikter och tränas från början till slut med en enda backpropagation-pass.\n",
"\n",
"> Figuren ovan visar ett rekurrent neuralt nätverk i utvecklad form (till vänster) och i en mer kompakt rekurrent representation (till höger). Det är viktigt att förstå att alla RNN-celler har samma **delbara vikter**.\n",
"\n",
"Eftersom tillståndsvektorerna $S_0,\\dots,S_n$ skickas genom nätverket, kan RNN lära sig sekventiella beroenden mellan ord. Till exempel, när ordet *inte* dyker upp någonstans i sekvensen, kan det lära sig att neka vissa element inom tillståndsvektorn.\n",
"\n",
"Inuti varje RNN-cell finns två viktmatriser: $W_H$ och $W_I$, samt en bias $b$. Vid varje RNN-steg, givet indata $X_i$ och inmatningstillstånd $S_i$, beräknas utgångstillståndet som $S_{i+1} = f(W_H\\times S_i + W_I\\times X_i+b)$, där $f$ är en aktiveringsfunktion (ofta $\\tanh$).\n",
"\n",
"> För problem som textgenerering (som vi kommer att gå igenom i nästa enhet) eller maskinöversättning vill vi också få ett utvärde vid varje RNN-steg. I detta fall finns det också en annan matris $W_O$, och utdata beräknas som $Y_i=f(W_O\\times S_i+b_O)$.\n",
"\n",
"Låt oss se hur rekurrenta neurala nätverk kan hjälpa oss att klassificera vår nyhetsdatamängd.\n",
"\n",
"> För sandlådemiljön behöver vi köra följande cell för att säkerställa att det nödvändiga biblioteket är installerat och att data är förhämtad. Om du kör lokalt kan du hoppa över följande cell.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"!{sys.executable} -m pip install --quiet tensorflow_datasets==4.4.0\n",
"!cd ~ && wget -q -O - https://mslearntensorflowlp.blob.core.windows.net/data/tfds-ag-news.tgz | tar xz"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import tensorflow as tf\n",
"from tensorflow import keras\n",
"import tensorflow_datasets as tfds\n",
"import numpy as np\n",
"\n",
"# We are going to be training pretty large models. In order not to face errors, we need\n",
"# to 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",
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
]
},
{
"cell_type": "markdown",
"metadata": {
"nteract": {
"transient": {
"deleting": false
}
}
},
"source": [
"När man tränar stora modeller kan GPU-minnesallokering bli ett problem. Vi kan också behöva experimentera med olika minibatch-storlekar, så att datan får plats i GPU-minnet samtidigt som träningen är tillräckligt snabb. Om du kör den här koden på din egen GPU-maskin kan du experimentera med att justera minibatch-storleken för att snabba upp träningen.\n",
"\n",
"> **Note**: Vissa versioner av NVidia-drivrutiner är kända för att inte frigöra minnet efter att modellen har tränats. Vi kör flera exempel i den här notebooken, och det kan leda till att minnet tar slut i vissa konfigurationer, särskilt om du gör egna experiment i samma notebook. Om du stöter på konstiga fel när du börjar träna modellen kan det vara en bra idé att starta om notebook-kärnan.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": true,
"jupyter": {
"outputs_hidden": false,
"source_hidden": false
},
"nteract": {
"transient": {
"deleting": false
}
}
},
"outputs": [],
"source": [
"batch_size = 16\n",
"embed_size = 64"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Enkel RNN-klassificerare\n",
"\n",
"I fallet med en enkel RNN är varje återkommande enhet ett enkelt linjärt nätverk som tar in en inmatningsvektor och en tillståndsvektor och producerar en ny tillståndsvektor. I Keras kan detta representeras av lagret `SimpleRNN`.\n",
"\n",
"Även om vi kan skicka one-hot-kodade token direkt till RNN-lagret, är detta inte en bra idé på grund av deras höga dimension. Därför kommer vi att använda ett inbäddningslager för att minska dimensionen på ordvektorer, följt av ett RNN-lager och slutligen en `Dense`-klassificerare.\n",
"\n",
"> **Note**: I fall där dimensionen inte är så hög, till exempel vid användning av teckennivåkodning, kan det vara vettigt att skicka one-hot-kodade token direkt till RNN-cellen.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"sequential\"\n",
"_________________________________________________________________\n",
"Layer (type) Output Shape Param # \n",
"=================================================================\n",
"text_vectorization (TextVect (None, None) 0 \n",
"_________________________________________________________________\n",
"embedding (Embedding) (None, None, 64) 1280000 \n",
"_________________________________________________________________\n",
"simple_rnn (SimpleRNN) (None, 16) 1296 \n",
"_________________________________________________________________\n",
"dense (Dense) (None, 4) 68 \n",
"=================================================================\n",
"Total params: 1,281,364\n",
"Trainable params: 1,281,364\n",
"Non-trainable params: 0\n",
"_________________________________________________________________\n"
]
}
],
"source": [
"vocab_size = 20000\n",
"\n",
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(\n",
" max_tokens=vocab_size,\n",
" input_shape=(1,))\n",
"\n",
"model = keras.models.Sequential([\n",
" vectorizer,\n",
" keras.layers.Embedding(vocab_size, embed_size),\n",
" keras.layers.SimpleRNN(16),\n",
" keras.layers.Dense(4,activation='softmax')\n",
"])\n",
"\n",
"model.summary()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Note:** Här använder vi ett otränat inbäddningslager för enkelhetens skull, men för bättre resultat kan vi använda ett förtränat inbäddningslager med Word2Vec, som beskrivs i föregående avsnitt. Det kan vara en bra övning för dig att anpassa denna kod för att fungera med förtränade inbäddningar.\n",
"\n",
"Nu ska vi träna vår RNN. RNN:er är generellt ganska svåra att träna, eftersom när RNN-cellerna vecklas ut längs sekvenslängden blir antalet lager som är involverade i backpropagation ganska stort. Därför behöver vi välja en mindre inlärningshastighet och träna nätverket på en större dataset för att få bra resultat. Detta kan ta ganska lång tid, så det är att föredra att använda en GPU.\n",
"\n",
"För att snabba upp processen kommer vi endast att träna RNN-modellen på nyhetstitlar och utelämna beskrivningen. Du kan prova att träna med beskrivningen och se om du kan få modellen att träna.\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Training vectorizer\n"
]
}
],
"source": [
"def extract_title(x):\n",
" return x['title']\n",
"\n",
"def tupelize_title(x):\n",
" return (extract_title(x),x['label'])\n",
"\n",
"print('Training vectorizer')\n",
"vectorizer.adapt(ds_train.take(2000).map(extract_title))"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"7500/7500 [==============================] - 82s 11ms/step - loss: 0.6629 - acc: 0.7623 - val_loss: 0.5559 - val_acc: 0.7995\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7f3e0030d350>"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
"model.fit(ds_train.map(tupelize_title).batch(batch_size),validation_data=ds_test.map(tupelize_title).batch(batch_size))"
]
},
{
"cell_type": "markdown",
"metadata": {
"nteract": {
"transient": {
"deleting": false
}
}
},
"source": [
"> **Observera** att noggrannheten sannolikt är lägre här, eftersom vi endast tränar på nyhetstitlar.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Återbesök av variabla sekvenser\n",
"\n",
"Kom ihåg att lagret `TextVectorization` automatiskt kommer att fylla ut sekvenser med variabel längd i en minibatch med utfyllnadstokens. Det visar sig att dessa tokens också deltar i träningen, och de kan försvåra modellens konvergens.\n",
"\n",
"Det finns flera tillvägagångssätt vi kan använda för att minimera mängden utfyllnad. Ett av dem är att omordna datasetet efter sekvenslängd och gruppera alla sekvenser efter storlek. Detta kan göras med funktionen `tf.data.experimental.bucket_by_sequence_length` (se [dokumentation](https://www.tensorflow.org/api_docs/python/tf/data/experimental/bucket_by_sequence_length)).\n",
"\n",
"Ett annat tillvägagångssätt är att använda **maskering**. I Keras stöder vissa lager ytterligare indata som visar vilka tokens som ska beaktas vid träning. För att inkludera maskering i vår modell kan vi antingen lägga till ett separat `Masking`-lager ([dokumentation](https://keras.io/api/layers/core_layers/masking/)), eller så kan vi ange parametern `mask_zero=True` i vårt `Embedding`-lager.\n",
"\n",
"> **Note**: Den här träningen kommer att ta ungefär 5 minuter för att slutföra en epok på hela datasetet. Känn dig fri att avbryta träningen när som helst om du tappar tålamodet. Vad du också kan göra är att begränsa mängden data som används för träning genom att lägga till `.take(...)`-satsen efter dataseten `ds_train` och `ds_test`.\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"7500/7500 [==============================] - 371s 49ms/step - loss: 0.5401 - acc: 0.8079 - val_loss: 0.3780 - val_acc: 0.8822\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7f3dec118850>"
]
},
"execution_count": 7,
"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",
"model = keras.models.Sequential([\n",
" vectorizer,\n",
" keras.layers.Embedding(vocab_size,embed_size,mask_zero=True),\n",
" keras.layers.SimpleRNN(16),\n",
" keras.layers.Dense(4,activation='softmax')\n",
"])\n",
"\n",
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Nu när vi använder maskering kan vi träna modellen på hela datasetet av titlar och beskrivningar.\n",
"\n",
"> **Note**: Har du märkt att vi har använt en vektorisering som är tränad på nyhetstitlar, och inte hela artikelns innehåll? Detta kan potentiellt leda till att vissa av tokenarna ignoreras, så det är bättre att träna om vektoriseringen. Dock kan effekten vara mycket liten, så vi kommer att hålla oss till den tidigare förtränade vektoriseringen för enkelhetens skull.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## LSTM: Långtidsminne med korttidsminne\n",
"\n",
"Ett av de största problemen med RNNs är **försvinnande gradienter**. RNNs kan vara ganska långa och kan ha svårt att propagera gradienterna hela vägen tillbaka till det första lagret i nätverket under backpropagation. När detta händer kan nätverket inte lära sig relationer mellan avlägsna tokens. Ett sätt att undvika detta problem är att införa **explicit tillståndshantering** genom att använda **grindar**. De två vanligaste arkitekturerna som introducerar grindar är **långtidsminne med korttidsminne** (LSTM) och **gated relay unit** (GRU). Vi kommer att fokusera på LSTMs här.\n",
"\n",
"![Bild som visar ett exempel på en långtidsminnescell](../../../../../lessons/5-NLP/16-RNN/images/long-short-term-memory-cell.svg)\n",
"\n",
"Ett LSTM-nätverk är organiserat på ett sätt som liknar ett RNN, men det finns två tillstånd som skickas från lager till lager: det faktiska tillståndet $c$ och den dolda vektorn $h$. Vid varje enhet kombineras den dolda vektorn $h_{t-1}$ med input $x_t$, och tillsammans styr de vad som händer med tillståndet $c_t$ och output $h_{t}$ genom **grindar**. Varje grind har sigmoid-aktivering (output inom intervallet $[0,1]$), vilket kan ses som en bitmask när den multipliceras med tillståndsvektorn. LSTMs har följande grindar (från vänster till höger på bilden ovan):\n",
"* **glömskegrind** som avgör vilka komponenter i vektorn $c_{t-1}$ vi behöver glömma och vilka som ska passera vidare.\n",
"* **inmatningsgrind** som avgör hur mycket information från inmatningsvektorn och den tidigare dolda vektorn som ska införlivas i tillståndsvektorn.\n",
"* **utgångsgrind** som tar den nya tillståndsvektorn och bestämmer vilka av dess komponenter som ska användas för att producera den nya dolda vektorn $h_t$.\n",
"\n",
"Komponenterna i tillståndet $c$ kan ses som flaggor som kan slås på och av. Till exempel, när vi stöter på namnet *Alice* i sekvensen, gissar vi att det hänvisar till en kvinna och höjer flaggan i tillståndet som säger att vi har ett kvinnligt substantiv i meningen. När vi senare stöter på orden *och Tom*, höjer vi flaggan som säger att vi har ett plural substantiv. Genom att manipulera tillståndet kan vi alltså hålla reda på de grammatiska egenskaperna i meningen.\n",
"\n",
"> **Note**: Här är en fantastisk resurs för att förstå LSTMs internstruktur: [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) av Christopher Olah.\n",
"\n",
"Även om den interna strukturen i en LSTM-cell kan verka komplex, döljer Keras denna implementation inuti `LSTM`-lagret, så det enda vi behöver göra i exemplet ovan är att ersätta det rekurrenta lagret:\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"15000/15000 [==============================] - 188s 13ms/step - loss: 0.5692 - acc: 0.7916 - val_loss: 0.3441 - val_acc: 0.8870\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7f3d6af5c350>"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model = keras.models.Sequential([\n",
" vectorizer,\n",
" keras.layers.Embedding(vocab_size, embed_size),\n",
" keras.layers.LSTM(8),\n",
" keras.layers.Dense(4,activation='softmax')\n",
"])\n",
"\n",
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
"model.fit(ds_train.map(tupelize).batch(8),validation_data=ds_test.map(tupelize).batch(8))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Bidirektionella och flerskiktade RNN:er\n",
"\n",
"I våra tidigare exempel har de rekurrenta nätverken bearbetat sekvenser från början till slut. Detta känns naturligt för oss eftersom det följer samma riktning som vi läser eller lyssnar på tal. Men för scenarier som kräver slumpmässig åtkomst av inmatningssekvensen är det mer logiskt att köra den rekurrenta beräkningen i båda riktningarna. RNN:er som tillåter beräkningar i båda riktningarna kallas **bidirektionella** RNN:er, och de kan skapas genom att omsluta den rekurrenta lagret med ett speciellt `Bidirectional`-lager.\n",
"\n",
"> **Note**: `Bidirectional`-lagret skapar två kopior av lagret inom sig och ställer in egenskapen `go_backwards` för en av dessa kopior till `True`, vilket gör att den går i motsatt riktning längs sekvensen.\n",
"\n",
"Rekurrenta nätverk, oavsett om de är enkelriktade eller bidirektionella, fångar mönster inom en sekvens och lagrar dem i tillståndsvektorer eller returnerar dem som output. Precis som med konvolutionella nätverk kan vi bygga ett annat rekurrent lager efter det första för att fånga högre nivåmönster, byggda från lägre nivåmönster som extraherats av det första lagret. Detta leder oss till begreppet **flerskiktad RNN**, som består av två eller fler rekurrenta nätverk, där output från det föregående lagret skickas till nästa lager som input.\n",
"\n",
"![Bild som visar ett flerskiktat lång-korttidsminnes-RNN](../../../../../translated_images/sv/multi-layer-lstm.dd975e29bb2a59fe.webp)\n",
"\n",
"*Bild från [detta fantastiska inlägg](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) av Fernando López.*\n",
"\n",
"Keras gör det enkelt att konstruera dessa nätverk, eftersom du bara behöver lägga till fler rekurrenta lager till modellen. För alla lager utom det sista måste vi ange parametern `return_sequences=True`, eftersom vi behöver att lagret returnerar alla mellanliggande tillstånd och inte bara det slutliga tillståndet av den rekurrenta beräkningen.\n",
"\n",
"Låt oss bygga ett tvåskiktat bidirektionellt LSTM för vårt klassificeringsproblem.\n",
"\n",
"> **Note** denna kod tar återigen ganska lång tid att köra, men den ger oss den högsta noggrannheten vi har sett hittills. Så kanske det är värt att vänta och se resultatet.\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5044/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r5045/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706"
]
}
],
"source": [
"model = keras.models.Sequential([\n",
" vectorizer,\n",
" keras.layers.Embedding(vocab_size, 128, mask_zero=True),\n",
" keras.layers.Bidirectional(keras.layers.LSTM(64,return_sequences=True)),\n",
" keras.layers.Bidirectional(keras.layers.LSTM(64)), \n",
" keras.layers.Dense(4,activation='softmax')\n",
"])\n",
"\n",
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
"model.fit(ds_train.map(tupelize).batch(batch_size),\n",
" validation_data=ds_test.map(tupelize).batch(batch_size))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## RNN:er för andra uppgifter\n",
"\n",
"Hittills har vi fokuserat på att använda RNN:er för att klassificera textsekvenser. Men de kan hantera många fler uppgifter, såsom textgenerering och maskinöversättning — vi kommer att titta närmare på dessa uppgifter i nästa enhet.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Ansvarsfriskrivning**: \nDetta dokument har översatts med hjälp av AI-översättningstjänsten [Co-op Translator](https://github.com/Azure/co-op-translator). Även om vi strävar efter noggrannhet, bör det noteras att automatiska översättningar kan innehålla fel eller inexaktheter. Det ursprungliga dokumentet på dess originalspråk bör betraktas som den auktoritativa källan. För kritisk information rekommenderas professionell mänsklig översättning. Vi ansvarar inte för eventuella missförstånd eller feltolkningar som uppstår vid användning av denna översättning.\n"
]
}
],
"metadata": {
"kernel_info": {
"name": "conda-env-py37_tensorflow-py"
},
"kernelspec": {
"display_name": "py37_tensorflow",
"language": "python",
"name": "conda-env-py37_tensorflow-py"
},
"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.7.9"
},
"nteract": {
"version": "nteract-front-end@1.0.0"
},
"coopTranslator": {
"original_hash": "81351e61f619b432ff51010a4f993194",
"translation_date": "2025-08-28T17:40:29+00:00",
"source_file": "lessons/5-NLP/16-RNN/RNNTF.ipynb",
"language_code": "sv"
}
},
"nbformat": 4,
"nbformat_minor": 4
}