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

456 lines
40 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# បណ្តាញសរសៃទន្និនយន្តវិញ្ញាបនបត្រ\n",
"\n",
"នៅក្នុងម៉ូឌុលមុន យើងបានគ្របដណ្តប់លើការតំណាងន័យសម្បូរបែបនៃអត្ថបទ។ រចនាសម្ព័ន្ធដែលយើងបានប្រើប្រាស់ចាប់យកន័យសរុបនៃពាក្យនៅក្នុងប្រយោគមួយ ប៉ុន្តែមិនបានគិតទុកនូវ **លំដាប់** នៃពាក្យទេ ព្រោះប្រតិបត្តិការប្រមូលបញ្ចូលដែលត្រូវបានអនុវត្តបន្ទាប់ពីការបញ្ចូលព័ត៌មានលម្អិតបានយកព័ត៌មាននេះចេញពីអត្ថបទដើម។ ដោយសារថាតំណាងទាំងនេះមិនអាចបង្ហាញលំដាប់ពាក្យបាន ពួកវាដូចជាមិនអាចដោះស្រាយបញ្ហាស្មុគស្មាញ ឬបញ្ហាដែលមានភាពច្របូកច្របល់ដូចជា ការបង្កើតអត្ថបទ ឬការឆ្លើយសំណួរ។\n",
"\n",
"ដើម្បីចាប់យកន័យនៃខ្សែអក្សរមួយ យើងនឹងប្រើរចនាសម្ព័ន្ធបណ្តាញសរសៃទន្និនដែលហៅថា **បណ្តាញសរសៃទន្និនយន្តវិញ្ញាណបត្រ**, ឬ RNN។ ការប្រើប្រាស់ RNN គឺ យើងផ្ញើប្រយោគរបស់យើងឲ្យបណ្តាញមួយដោយយកតែទ្រង់ទ្រាយមួយភាគតូចក្នុងពេលណាមួយ ហើយបណ្តាញនោះបញ្ចេញ **ស្ថានភាព** មួយ ដែលយើងបន្ទាប់មកផ្ញើត្រឡប់ទៅបណ្តាញវិញជាមួយភាគតូចបន្ទាប់។\n",
"\n",
"![រូបភាពបង្ហាញឧទាហរណ៍នៃការបង្កើតបណ្តាញសរសៃទន្និនយន្តវិញ្ញាណបត្រ។](../../../../../translated_images/km/rnn.27f5c29c53d727b5.webp)\n",
"\n",
"ដោយអនុវត្តលំដាប់នៃទ្រង់ទ្រាយ $X_0,\\dots,X_n$, RNN បង្កើតជាលំដាប់នៃប្លុកបណ្តាញសរសៃទន្និនយន្តវិញ្ញាណបត្រ ហើយបង្រៀនលំដាប់នេះពីដើមដល់ចប់ដោយប្រើការបត់បញ្ច្រាសតាមវិលក្រោយ។ ប្លុកបណ្តាញនីមួយៗទទួលគូ $(X_i,S_i)$ ជាកាន់ការបញ្ចូល ហើយបង្ហាញ $S_{i+1}$ ជាលទ្ធផល។ ស្ថានភាពចុងក្រោយ $S_n$ ឬលទ្ធផល $Y_n$ ត្រូវបានដាក់ចូលទៅក្នុងម៉ាស៊ីនចាត់ថ្នាក់បែបអញ្ញើញរកលទ្ធផល។ ប្លុកបណ្តាញទាំងអស់ចែករំលែកទម្ងន់ដូចគ្នា ហើយបានបង្រៀនពីដើមដល់ចប់ដោយបង្វិលតែមួយ។\n",
"\n",
"> រូបភាពខាងលើបង្ហាញបណ្តាញសរសៃទន្និនយន្តវិញ្ញាណបត្រ នៅក្នុងទម្រង់បម៉ោងប្រែរបិទ (នៅខាងឆ្វេង) និងទម្រង់តំណាងបណ្តាញសរសៃទន្និនប្រមូល (នៅខាងស្ដាំ)។ វាសំខាន់ក្នុងការយល់ថា កោសិកា RNN ទាំងអស់មាន **ទម្ងន់ដែលអាចចែករំលែក** ដូចគ្នា។\n",
"\n",
"ដោយសារvectorស្ថានភាព $S_0,\\dots,S_n$ ត្រូវបានបញ្ជូនតាមបណ្តាញ RNN អាចរៀនពាក់ព័ន្ធតាមលំដាប់រវាងពាក្យបាន។ ឧទាហរណ៍ កាលណាពាក្យ *not* បង្ហាញនៅខាងមួយក្នុងលំដាប់ វាអាចរៀនច្រានចោលធាតុខ្លះនៅលើវ៉ិចទ័រស្ថានភាព។\n",
"\n",
"ខាងក្នុង អ្នកលេងកោសិកា RNN ម្នាក់នីមួយៗមានម៉ាទ្រីសទម្ងន់ពីរ ដូចជា $W_H$ និង $W_I$ និងផ្ទុក bias $b$។ នៅជំហាន RNN នីមួយៗ យើងមានបញ្ចូល $X_i$ និងស្ថានភាពបញ្ចូល $S_i$ ស្ថានភាពចេញលោកគិតដោយ $S_{i+1} = f(W_H\\times S_i + W_I\\times X_i+b)$ ដែល $f$ ជាអនុគមន៍បំលែង (ជាទូទៅជារាង $\\tanh$)។\n",
"\n",
"> សម្រាប់បញ្ហាដូចជា ការបង្កើតអត្ថបទ (ដែលយើងនឹងគ្របដណ្តប់ក្នុងឯកត្តបន្ទាប់) ឬបកប្រែម៉ាស៊ីន យើងក៏ចង់បានតម្លៃលទ្ធផលនៅរៀងរាល់ជំហាន RNN ផងដែរ។ ក្នុងករណីនេះ មានម៉ាទ្រីសមួយទៀតគឺ $W_O$ ហើយលទ្ធផលគិតថា $Y_i=f(W_O\\times S_i+b_O)$។\n",
"\n",
"ចង់ឲ្យមើលថាបណ្តាញសរសៃទន្និនយន្តវិញ្ញាណបត្រ អាចជួយយើងចាត់ថ្នាក់ពីកម្រិតទិន្នន័យព័ត៌មានថ្មីបានយ៉ាងដូចម្តេច។\n",
"\n",
"> សម្រាប់បរិវេណ sandbox យើងត្រូវរត់កូដខាងក្រោម ដើម្បីធានាថាចែកចាយបណ្ណាល័យត្រូវបានដំឡើង និងទិន្នន័យត្រូវបានទាញយកមុន។ បើអ្នករត់នៅលើកុំព្យូទ័រផ្ទាល់ខ្លួន អ្នកអាចរំលងកូដខាងក្រោម។\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": [
"នៅពេលបណ្តុះបណ្តាលម៉ូដែលទំហំធំ ការចំណាយចងចាំ GPU អាចក្លាយទៅជាបញ្ហា។ យើងអាចត្រូវតែសាកល្បងជាមួយទំហំមីនីបាទខុសៗគ្នា ដើម្បីឲ្យទិន្នន័យសម្របសម្រួលក្នុងចងចាំរបស់ GPU របស់យើង ប៉ុន្តែការបណ្តុះបណ្តាលត្រូវបានរហ័សគ្រប់គ្រាន់។ ប្រសិនបើអ្នកកំពុងរត់កូដនេះលើម៉ាស៊ីន GPU ផ្ទាល់របស់អ្នក អ្នកអាចសាកល្បងកែប្រែទំហំមីនីបាទដើម្បីលឿនការបណ្តុះបណ្តាល។\n",
"\n",
"> **Note**: វើស្យុងខ្លះៗនៃអ្នកបើកបរ NVidia ត្រូវបានគេដឹងថាមិនបញ្ចេញចងចាំបន្ទាប់ពីបណ្តុះបណ្តាលម៉ូដែលឡើយ។ យើងកំពុងរត់ឧទាហរណ៍ជាច្រើនក្នុងសៀវភៅកំណត់ត្រានេះ ហើយវាអាចបណ្តាលឲ្យចងចាំលែងមាននៅក្នុងការរៀបចំខ្លះៗ ជាពិសេសប្រសិនបើអ្នកកំពុងធ្វើចំណាំផ្ទាល់របស់អ្នកជាផ្នែកនៃសៀវភៅកំណត់ត្រាដូចគ្នា។ ប្រសិនបើអ្នកប្រទះឃើញកំហុសចំណាស់ៗពេលចាប់ផ្តើមបណ្តុះបណ្តាលម៉ូដែល អ្នកអាចចង់ចាប់ផ្តើម kernel សៀវភៅកំណត់ត្រាថ្មីម្តងទៀត។\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": [
"## ម៉ាស៊ីនចំណាត់ថ្នាក់ RNN សាមញ្ញ\n",
"\n",
"ក្នុងករណី RNN សាមញ្ញ, មួយឯកតាការវិលត្រីមាត្រគឺជាបណ្តាញបន្ទាត់សាមញ្ញមួយ ដែលទទួលវ៉ិចទ័រសំណូល និងវ៉ិចទ័របំណែង ហើយផលិតវ៉ិចទ័របំណែងថ្មី។ ក្នុង Keras, នេះអាចត្រូវបានបង្ហាញដោយស្រទាប់ `SimpleRNN` ។\n",
"\n",
"បើទោះបីពួកយើងអាចផ្ញើតូកែនដែលបានកូដជា one-hot ទៅស្រទាប់ RNN ត្រង់ៗក៏ដោយ នោះមិនមែនជាគំនិតល្អទេ ព្រោះមានវិមាត្រខ្ពស់បំផុត។ ដូច្នេះ យើងនឹងប្រើស្រទាប់ embedding ដើម្បីចុះវិមាត្ររបស់វ៉ិចទ័រពាក្យ បន្ទាប់មកស្រទាប់ RNN ហើយចុងក្រោយជាម៉ាស៊ីនចំណាត់ថ្នាក់ `Dense` ។\n",
"\n",
"> **កំណត់សម្គាល់**: ក្នុងករណីដែលវិមាត្រមិនខ្ពស់ទេ ជាឧទាហរណ៍ពេលប្រើ tokenization កម្រិតអក្សរ វាអាចមានហេតុផលក្នុងការផ្ញើតូកែនដែលបានកូដជា one-hot ចូលទៅក្នុងកោសិកា RNN ផ្ទាល់។\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": [
"> **កំណត់ចំណាំ៖** យើងប្រើស្រទាប់ embedding មិនបានហ្វឹកហាត់នៅទីនេះសម្រាប់ភាពសាមញ្ញ ប៉ុន្ត่าสำหรับលទ្ធផលល្អជាងនេះ យើងអាចប្រើស្រទាប់ embedding ដែលបានហ្វឹកហាត់រួចជាមួយ Word2Vec ដូចបានពិពណ៌នានៅឯកត្តជំពូកមុន។ វាជាការហាត់ដំណើរល្អសម្រាប់អ្នកក្នុងការតម្រូវកូដនេះឱ្យដំណើរការជាមួយ embeddings ដែលបានហ្វឹកហាត់រួច។ \n",
"\n",
"ឥឡូវនេះមកហ្វឹកហាត់ RNN របស់យើង។ RNN ទូទៅមានភាពលំបាកក្នុងការហ្វឹកហាត់ ព្រោះពេលដែលកោសិការបស់ RNN ត្រូវបានបម្លែង (unrolled) ទៅតាមប្រវែងរដ្ឋសភាព (sequence length) ចំនួនស្រទាប់ដែលចូលរួមក្នុងការត្រួតពិនិត្យត្រឡប់ក្រោយ (backpropagation) គឺនឹងធំជាច្រើន។ ដូចនេះយើងត្រូវជ្រើសរើសអត្រាការសិក្សា (learning rate) តូចជាងមុន ហើយហ្វឹកហាត់បណ្តាញលើឌាតាសិក្សាធំជាង ដើម្បីបានលទ្ធផលល្អ។ វាអាចចាលខ្សែពេលយូរណាស់ ដូច្នេះការប្រើ GPU គឺផ្តល់អាទិភាព។ \n",
"\n",
"ដើម្បីល្បឿនឡើង យើងនឹងហ្វឹកហាត់ម៉ូឌែល RNN លើចំណងជើងដំណឹងតែប៉ុណ្ណោះ ខ្វះពាក្យពិពណ៌នា។ អ្នកអាចសាកល្បងហ្វឹកហាត់ជាមួយពាក្យពិពណ៌នារួម និងមើលថាតើអាចធ្វើឲ្យម៉ូឌែលហ្វឹកហាត់បានឬទេ។\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": [
"> **កំណត់ចំណាំ** ថា ភាពត្រឹមត្រូវប្រហែលជានឹងទាបជាងនេះ ដោយសារតែយើងកំពុងបណ្តុះបណ្តាលតែលើចំណងជើងដំណឹងប៉ុណ្ណោះ។\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## ការត្រលប់មកវិញលំហាត់តួអក្សរផ្សេងប្លែក \n",
"\n",
"ចងចាំថាស្រទាប់ `TextVectorization` នឹងបន្ថែមសញ្ញា pad ដើម្បីបំពេញខ្សែតួអក្សរផ្សេងប្រវែងក្នុងមីនីបាច់ដោយស្វ័យប្រវត្តិ។ វាជាក់ថាសញ្ញាទាំងនោះក៏ចូលរួមក្នុងការបណ្តុះបណ្តាលផងដែរ ហើយវាប្រហែលជាសំប៉ាយកានតម្រូវនៃម៉ូដែល។\n",
"\n",
"មានវិធីច្រើនដែលយើងអាចប្រើដើម្បីល៉ូតបំផុតបរិមាណការបំពេញ។ ក្នុងនោះមានជំហានមួយគឺធ្វើរៀបចំទិន្នន័យជាសៀគ្វីតាមប្រវែងនៃខ្សែទិន្នន័យ ហើយក្រុមគ្រប់ខ្សែតួអក្សរតាមទំហំ។ វាអាចធ្វើបានដោយប្រើមុខងារ `tf.data.experimental.bucket_by_sequence_length` (មើល [ឯកសារ](https://www.tensorflow.org/api_docs/python/tf/data/experimental/bucket_by_sequence_length))។ \n",
"\n",
"វិធីមួយទៀតគឺប្រើ **masking**។ នៅក្នុង Keras មានស្រទាប់ខ្លះដែលគាំទ្រជំរុញបញ្ចូលបន្ថែមដែលបង្ហាញថាតួអក្សរណាដែលត្រូវគិតប្រាក់ទៅពេលបណ្តុះបណ្តាល។ ដើម្បីបញ្ចូលការម៉ាស្កីងក្នុងម៉ូដែលរបស់យើង យើងអាចបញ្ចូលស្រទាប់ `Masking` ផ្សេងទៀត ([ឯកសារ](https://keras.io/api/layers/core_layers/masking/)) ឬប្រើប៉ារ៉ាម៉ែត្រ `mask_zero=True` នៃស្រទាប់ `Embedding` របស់យើង។\n",
"\n",
"> **កំណត់សម្គាល់**៖ ការបណ្តុះបណ្តាលនេះនឹងចំណាយពេលប្រហែល ៥ នាទីដើម្បីបញ្ចប់មួយ Epoch នៅទាំងមូលឯកសារទិន្នន័យ។ អ្នកអាចសម្រាកបន្ទាន់ការបណ្តុះបណ្តាលនៅពេលណាក៏បាន ប្រសិនបើអ្នកអស់ចំណោម។ អ្នកក៏អាចកំណត់ចំនួនទិន្នន័យ​ដែលប្រើសម្រាប់បណ្តុះបណ្តាល ជាមួយការ​បន្ថែម `.take(...)` បន្ទាប់ពីឌីស្ទឺត `ds_train` និង `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": [
"ឥឡូវនេះយើងកំពុងប្រើការបំលែងមុខមាត់ (masking) ហើយយើងអាចបង្រៀនម៉ូឌែលលើទិន្នន័យបូកបញ្ចូលទាំងស្រុងនៃចំណងជើង និងការពិពណ៌នា។\n",
"\n",
"> **សម្គាល់**: តើអ្នកបាន​ដឹងទេថាយើងបានប្រើ vectorizer ដែលបានបង្រៀនលើចំណងជើងព័ត៌មាន ទេ មិនមែនលើរាងកាយទាំងមូលនៃអត្ថបទ? ប្រហែលជា វាអាចបណ្តាលឲ្យខ្លះនៃតូខេន ត្រូវបានមើលរំលង ដូច្នេះគួរតែបង្រៀនឡើងវិញ vectorizer។ ប៉ុន្តែ វាអាចមានផលប៉ះពាល់តិចតួចតែប៉ុណ្ណោះ ដូច្នេះយើងនឹងនៅតែប្រើ vectorizer ដែលបានបង្រៀនមុននេះសម្រាប់ភាពងាយស្រួល។\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## LSTM: អង្គចងចាំរយៈពេលខ្លីនិងយូរ\n",
"\n",
"មួយក្នុងចំណោមបញ្ហាសំខាន់ៗ​នៃ RNN គឺ **កំណើន gradients ដែលបាត់បង់**។ RNN អាចមានប្រវែងយូរ ហើយអាចមានការលំបាកក្នុងការបញ្ជូន gradients ត្រឡប់ទៅបន្ទាត់ទីមួយនៃបណ្តាញនៅពេល backpropagation។ នៅពេលនេះកើតឡើង នេះបណ្តាញមិនអាចរៀនទំនាក់ទំនងរវាង token ដែលស្ថិតឆ្ងាយគ្នាបានទេ។ មូលហេតុ​មួយដើម្បីជៀសវាងបញ្ហានេះគឺ ដាក់បន្ថែម **ការគ្រប់គ្រងស្ថានភាពយ៉ាងច្បាស់លាស់** ដោយប្រើ **ទ្វារជ្រាប**។ ស្ថាបត្យកម្មទាំងពីរដែលជាទូទៅបង្ហាញទ្វារជ្រាបគឺ **អង្គចងចាំរយៈពេលខ្លីនិងយូរ** (LSTM) និង **ឯកតាជ្រាបប្រព័ន្ធផ្សព្វផ្សាយ** (GRU)។ យើងនឹងបញ្ជាក់អំពី LSTM នៅទីនេះ។\n",
"\n",
"![រូបភាពបង្ហាញគំរូអង្គចងចាំរយៈពេលខ្លីនិងយូរ](../../../../../lessons/5-NLP/16-RNN/images/long-short-term-memory-cell.svg)\n",
"\n",
"បណ្តាញ LSTM ត្រូវបានរៀបចំជាប្រភេទដូច RNN ប៉ុន្តែមានស្ថានភាពពីរដែលត្រូវបញ្ជូនពីជាន់មួយទៅជាន់មួយ គឺស្ថានភាពពិត $c$ និង វិទ័រលាក់ $h$។ នៅក្នុងឯកតាទីមួយ វិទ័រលាក់ $h_{t-1}$ ត្រូវបានបញ្ចូលជាមួយនឹងវ៉ិចទ័រ input $x_t$ ហើយពួកវាគ្រប់គ្រងអ្វីដែលកើតឡើងចំពោះស្ថានភាព $c_t$ និងចេញ $h_{t}$ តាមរយៈ **ទ្វារជ្រាប**។ ទ្វារ​រៀងរាល់ទ្វារមានគម្លាត sigmoid (ចេញក្នុងជួរនៃ $[0,1]$) ដែលអាចគិតជាផ្នែក bitwise mask នៅពេលគុណជាមួយវិទ័រស្ថានភាព។ LSTM មានទ្វារដូចខាងក្រោម (ពីឆ្វេងទៅស្តាំនៅលើរូបភាពខាងលើ):\n",
"* **ទ្វារភ្លេចបាត់** ដែលកំណត់ថា ធាតុណាដែលនៅក្នុងវិទ័រ $c_{t-1}$ គួរត្រូវភ្លេចបាត់ ហើយធាតុណាដែលគួរត្រូវបញ្ជូនតទៅ។\n",
"* **ទ្វារបញ្ចូល** ដែលកំណត់ថាព័ត៌មានប៉ុន្មានពីវិទ័របញ្ចូលនិងវិទ័រលាក់មុនគួរត្រូវបញ្ចូលទៅក្នុងវិទ័រស្ថានភាព។\n",
"* **ទ្វារចេញ** ដែលយកវិទ័រស្ថានភាពថ្មី ហើយសម្រេចថាធាតុណាដែល នឹងត្រូវបានប្រើសម្រាប់បង្កើតវិទ័រលាក់ថ្មី $h_t$។\n",
"\n",
"ធាតុនៃស្ថានភាព $c$ អាចគិតជាទង់ដែលអាចបិទ និងបើកបាន។ ឧទាហរណ៍ នៅពេលយើងជួបឈ្មោះ *Alice* ក្នុងលំដាប់ពាក្យ យើងគិតថាឈ្មោះនេះហៅដល់ស្ត្រី ហើយបើកទង់នៅក្នុងស្ថានភាពដែលបង្ហាញថាយើងមាននាមស្រីក្នុងប្រយោគ។ នៅពេលយើងបន្តជួបពាក្យ *and Tom* យើងនឹងបើកទង់ដែលបង្ហាញថាយើងមាននាមពហុបុគ្គល។ ដូច្នេះ ដោយការគ្រប់គ្រងស្ថានភាព យើងអាចតាមដានលក្ខណៈវេយ្យាករណ៍នៃប្រយោគ។\n",
"\n",
"> **កត់សម្គាល់**: នេះជាភស្តុតាងល្អសម្រាប់យល់ពីរចនាសម្ព័ន្ធខាងក្នុងនៃ LSTM: [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) ដោយ Christopher Olah។\n",
"\n",
"នៅពេលបន្ទះខាងក្នុងនៃកោសិកា LSTM អាចមើលទៅស្មុគស្មាញ ការប្រែប្រួលនេះត្រូវបានលាក់ខាងក្នុងជាន់ `LSTM` របស់ Keras ដូច្នេះអ្វីដែលយើងត្រូវធ្វើនៅក្នុងឧទាហរណ៍ខាងលើគឺប្ដូរជាន់ recurrent ។\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": [
"> **កំណត់សម្គាល់** ការបណ្តុះបណ្តាល LSTM ផងដែរ មានល្បឿនយឺត ដូច្នេះ អ្នកអាចមិនឃើញការកើនឡើងខ្លះៗនៃភាពត្រឹមត្រូវ នៅដើមនៃការបណ្តុះបណ្តាលនោះទេ។ អ្នកអាចត្រូវបន្តបណ្តុះបណ្តាលរយៈពេលមួយ ដើម្បីទទួលបានភាពត្រឹមត្រូវល្អ។\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## RNN ទិស​បូក និង RNN បច្របាំង​ច្រើន​ស្រទាប់\n",
"\n",
"ក្នុងឧទាហរណ៍របស់យើងទៅរួចនេះ បណ្ដាញ recurrent ប្រតិបត្តិការពីដើមជួរ​ដល់ចុងជួរ។ វាហាក់ដូចជា​ធម្មតាសម្រាប់យើង ពីព្រោះវាស្របទៅតាមទិសដៅដដែលនៃការអានឬការស្តាប់សំឡេង។ ទោះជាយ៉ាងណា សម្រាប់ករណីដែលត្រូវការចូលប្រើជួរដាក់ទិន្នន័យដោយចៃដន្យ វាមានហេតុផលថា គួរឱ្យដំណើរការជាន់បណ្ដួតិងទិសពីរទិស។ RNN ដែលអនុញ្ញាតឱ្យគណនាណ៍ពីទិសពីរ ត្រូវបានហៅថា **bidirectional** RNN ហើយអ្នកអាចបង្កើតវាបានដោយបញ្ឈរជាន់ recurrent ជាមួយជាន់ពិសេស `Bidirectonal`។\n",
"\n",
"> **កំណត់សម្គាល់**៖ ជាន់ `Bidirectional` បង្កើតចម្លងពីរនៃជាន់នៅក្នុងវា ហើយកំណត់លក្ខណៈ `go_backwards` សម្រាប់ចម្លងមួយក្នុងចំណោមទាំងពីរទៅជា `True` ធ្វើឲ្យវាទៅក្នុងទិសវិញនៅមុខជួរដាក់។ \n",
"\n",
"បណ្ដាញ recurrent ទាំងជាន់តែមួយ (unidirectional) ឬទ្វិទិស (bidirectional) ពន្លឿនតម្រងក្នុងមួយជួរ ហើយរក្សាទុកវាទៅក្នុងវ៉ិកទ័ររដ្ឋ ឬត្រឡប់វាជាផលចេញ។ ដូចជា បណ្ដាញមានការស្ងោ (convolutional networks) ផងដែរ អ្នកអាចបង្កើតជាន់ recurrent ថ្មីមួយបន្ទាប់ពីជាន់ដំបូង ដើម្បីចាប់យកផ្ទាំងកម្រិតខ្ពស់ ដែលបង្កើតឡើងពីផ្ទាំងកម្រិតទាបដែលចាប់យកដោយជាន់ដំបូង។ វាដឹកនាំយើងដល់គំនិតនៃ **multi-layer RNN** ដែលមានបណ្ដាភាពច្រើនជាន់ recurrent ច្រើនជាងពីរកន្លែងដែលផលចេញពីជាន់មុនត្រូវបានផ្ញើទៅជាជួរដាក់បញ្ចូលរបស់ជាន់បន្ទាប់។\n",
"\n",
"![រូបថតបង្ហាញពី Multilayer long-short-term-memory- RNN](../../../../../translated_images/km/multi-layer-lstm.dd975e29bb2a59fe.webp)\n",
"\n",
"*រូបភាពពី [អត្ថបទដ៏អស្ចារ្យនេះ](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) ដោយ Fernando López។*\n",
"\n",
"Keras ធ្វើឱ្យការបង្កើតបណ្ដាញទាំងនេះក្លាយជាការងារងាយស្រួល ពីព្រោះអ្នកត្រឹមតែបន្ថែមជាន់ recurrent ច្រើនជាងនៅក្នុងម៉ូដែលប៉ុណ្ណោះ។ សម្រាប់ជាន់ទាំងអស់ក្រៅតែជាន់ចុងក្រោយ អ្នកត្រូវបញ្ជាក់ប៉ារ៉ាម៉ែត្រ `return_sequences=True` ដូច្នេះជាន់នោះនឹងត្រលប់អោយរដ្ឋរង់ចាំពីរ្វ่ายขายទាំងមូល មិនមែនតែរដ្ឋចុងក្រោយនៃការគណនា recurrent។\n",
"\n",
"ចំណាំជូន យើងនឹងសង់ LSTM ទ្វិទិសពីរស្រទាប់សម្រាប់បញ្ហាការបែងចែកចំណាត់ថ្នាក់របស់យើង។\n",
"\n",
"> **កំណត់សម្គាល់** ៖ កូដនេះម្តងទៀតចំណាយពេលវេលាខ្លះ តែវាផ្តល់នូវភាពត្រឺមត្រូវខ្ពស់បំផុត ដែលយើងបានឃើញរហូតមកដល់ពេលនេះ។ ដូច្នេះប្រហែលជាគួរលើកទឹកចិត្តរង់ចាំ និងមើលលទ្ធផល។\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 សម្រាប់ការងារផ្សេងទៀត\n",
"\n",
"រហូតមកដល់ឥឡូវនេះ យើងបានផ្តោតលើការប្រើប្រាស់ RNN ដើម្បីចាត់ថ្នាក់លំដាប់អក្សរប្រយោគ។ ប៉ុន្តែវាអាចដោះស្រាយការងារផ្សេងទៀតបានច្រើន ដូចជាការបង្កើតអត្ថបទ និងការប្រែភាសាគណនា &mdash; យើងនឹងពិចារណាការងារទាំងនេះនៅឯកិតបន្ទាប់។\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n\n<!-- CO-OP TRANSLATOR DISCLAIMER START -->\n**ការបដិសេធ**៖ \nឯកសារនេះត្រូវបានបកប្រែដោយប្រើសេវាកម្មបកប្រែ AI [Co-op Translator](https://github.com/Azure/co-op-translator)។ ទោះបីយើងខិតខំសម្រាប់ភាពត្រឹមត្រូវ ក៏សូមដឹងថាការបកប្រែដោយស្វ័យប្រវត្តិក្នុងខ្លះអាចមានកំហុស ឬភាពមិនត្រឹមត្រូវ។ ឯកសារដើមនៅភាសាដើមគួរត្រូវបានគេពិចារណាថាជាអ្នកផ្គត់ផ្គង់ព័ត៌មានដែលមានសមត្ថកិច្ច។ សម្រាប់ព័ត៌មានសំខាន់ៗ សូមពិចារណាផ្នែកបកប្រែដោយមនុស្សជំនាញ។ យើងមិនទទួលខុសត្រូវចំពោះការយល់ច្រឡំ ឬការបកប្រែខុសពីការប្រើប្រាស់ការបកប្រែនេះឡើយ។\n<!-- CO-OP TRANSLATOR DISCLAIMER END -->\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"
}
},
"nbformat": 4,
"nbformat_minor": 4
}