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

460 lines
38 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# เครือข่ายประสาทเทียมแบบวนซ้ำ\n",
"\n",
"ในโมดูลก่อนหน้านี้ เราได้พูดถึงการแสดงความหมายเชิงลึกของข้อความ สถาปัตยกรรมที่เราใช้นั้นสามารถจับความหมายรวมของคำในประโยคได้ แต่ไม่ได้คำนึงถึง **ลำดับ** ของคำ เนื่องจากการดำเนินการรวมที่ตามมาหลังจากการฝังคำจะลบข้อมูลนี้ออกจากข้อความต้นฉบับ เนื่องจากโมเดลเหล่านี้ไม่สามารถแสดงลำดับของคำได้ จึงไม่สามารถแก้ปัญหาที่ซับซ้อนหรือคลุมเครือ เช่น การสร้างข้อความหรือการตอบคำถามได้\n",
"\n",
"เพื่อจับความหมายของลำดับข้อความ เราจะใช้สถาปัตยกรรมเครือข่ายประสาทเทียมที่เรียกว่า **เครือข่ายประสาทเทียมแบบวนซ้ำ** หรือ RNN เมื่อใช้ RNN เราจะส่งประโยคของเราผ่านเครือข่ายทีละโทเค็น และเครือข่ายจะสร้าง **สถานะ** ซึ่งเราจะส่งกลับเข้าไปในเครือข่ายอีกครั้งพร้อมกับโทเค็นถัดไป\n",
"\n",
"![ภาพแสดงตัวอย่างการสร้างเครือข่ายประสาทเทียมแบบวนซ้ำ](../../../../../translated_images/th/rnn.27f5c29c53d727b5.webp)\n",
"\n",
"เมื่อได้รับลำดับโทเค็นอินพุต $X_0,\\dots,X_n$ RNN จะสร้างลำดับของบล็อกเครือข่ายประสาทเทียม และฝึกฝนลำดับนี้แบบ end-to-end โดยใช้ backpropagation แต่ละบล็อกเครือข่ายจะรับคู่ $(X_i,S_i)$ เป็นอินพุต และสร้าง $S_{i+1}$ เป็นผลลัพธ์ สถานะสุดท้าย $S_n$ หรือผลลัพธ์ $Y_n$ จะถูกส่งไปยังตัวจำแนกเชิงเส้นเพื่อสร้างผลลัพธ์ บล็อกเครือข่ายทั้งหมดใช้เวทเดียวกัน และถูกฝึกแบบ end-to-end โดยใช้การ backpropagation เพียงครั้งเดียว\n",
"\n",
"> ภาพด้านบนแสดงเครือข่ายประสาทเทียมแบบวนซ้ำในรูปแบบที่คลี่ออก (ทางซ้าย) และในรูปแบบวนซ้ำที่กระชับกว่า (ทางขวา) สิ่งสำคัญคือต้องตระหนักว่าทุกเซลล์ RNN มี **เวทที่สามารถแชร์ได้** เหมือนกัน\n",
"\n",
"เนื่องจากเวกเตอร์สถานะ $S_0,\\dots,S_n$ ถูกส่งผ่านเครือข่าย RNN จึงสามารถเรียนรู้ความสัมพันธ์เชิงลำดับระหว่างคำได้ ตัวอย่างเช่น เมื่อคำว่า *not* ปรากฏที่ใดที่หนึ่งในลำดับ มันสามารถเรียนรู้ที่จะปฏิเสธองค์ประกอบบางอย่างภายในเวกเตอร์สถานะได้\n",
"\n",
"ภายในแต่ละเซลล์ RNN จะมีเมทริกซ์เวทสองตัว: $W_H$ และ $W_I$ และไบแอส $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 อาจกลายเป็นปัญหาได้ นอกจากนี้ เราอาจต้องทดลองปรับขนาดของ minibatch เพื่อให้ข้อมูลสามารถใส่ลงในหน่วยความจำ GPU ได้ และยังคงมีความเร็วในการฝึกโมเดลที่เพียงพอ หากคุณกำลังรันโค้ดนี้บนเครื่อง GPU ของคุณเอง คุณสามารถทดลองปรับขนาดของ minibatch เพื่อเพิ่มความเร็วในการฝึกโมเดลได้\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",
"> **Note**: ในกรณีที่มิติข้อมูลไม่ได้สูงมาก เช่น เมื่อใช้การแยกโทเค็นในระดับตัวอักษร อาจมีเหตุผลที่จะส่งโทเค็นที่เข้ารหัสแบบ 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 layer ที่ไม่ได้ผ่านการฝึกฝนเพื่อความเรียบง่าย แต่ถ้าต้องการผลลัพธ์ที่ดีกว่า เราสามารถใช้ embedding layer ที่ผ่านการฝึกฝนมาแล้ว เช่น Word2Vec ตามที่ได้อธิบายไว้ในหน่วยก่อนหน้า จะเป็นการฝึกฝนที่ดีหากคุณลองปรับโค้ดนี้ให้ทำงานร่วมกับ pretrained embeddings\n",
"\n",
"ตอนนี้เรามาเริ่มฝึกฝน RNN กัน RNN โดยทั่วไปแล้วค่อนข้างยากที่จะฝึกฝน เพราะเมื่อ RNN cells ถูกขยายออกตามความยาวของลำดับ จำนวนเลเยอร์ที่เกี่ยวข้องกับการ 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 tokens) โดยอัตโนมัติ ซึ่งโทเค็นเหล่านี้จะมีส่วนร่วมในการฝึกโมเดลด้วย และอาจทำให้การปรับค่าของโมเดลซับซ้อนขึ้น\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 เข้าไปในโมเดลของเรา เราสามารถเพิ่มเลเยอร์ `Masking` แยกต่างหาก ([เอกสาร](https://keras.io/api/layers/core_layers/masking/)) หรือกำหนดพารามิเตอร์ `mask_zero=True` ในเลเยอร์ `Embedding` ของเรา\n",
"\n",
"> **Note**: การฝึกโมเดลนี้จะใช้เวลาประมาณ 5 นาทีต่อหนึ่ง 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",
"หนึ่งในปัญหาหลักของ RNNs คือ **การสูญเสียค่าความชัน** (vanishing gradients) RNNs อาจมีความยาวมาก และอาจมีปัญหาในการส่งค่าความชันกลับไปยังเลเยอร์แรกของเครือข่ายในระหว่างการถอยกลับ (backpropagation) เมื่อเกิดเหตุการณ์นี้ เครือข่ายจะไม่สามารถเรียนรู้ความสัมพันธ์ระหว่างโทเค็นที่อยู่ห่างกันได้ วิธีหนึ่งในการหลีกเลี่ยงปัญหานี้คือการแนะนำ **การจัดการสถานะอย่างชัดเจน** โดยใช้ **เกต** สถาปัตยกรรมที่มีการใช้เกตที่พบได้บ่อยที่สุดคือ **long short-term memory** (LSTM) และ **gated relay unit** (GRU) ในที่นี้เราจะพูดถึง LSTMs\n",
"\n",
"![ภาพแสดงตัวอย่างเซลล์หน่วยความจำระยะยาวและระยะสั้น](../../../../../lessons/5-NLP/16-RNN/images/long-short-term-memory-cell.svg)\n",
"\n",
"เครือข่าย LSTM ถูกจัดระเบียบในลักษณะที่คล้ายกับ RNN แต่มีสองสถานะที่ถูกส่งผ่านจากเลเยอร์หนึ่งไปยังอีกเลเยอร์หนึ่ง: สถานะจริง $c$ และเวกเตอร์ซ่อน $h$ ในแต่ละหน่วย เวกเตอร์ซ่อน $h_{t-1}$ จะถูกผสมกับอินพุต $x_t$ และทั้งสองจะควบคุมสิ่งที่เกิดขึ้นกับสถานะ $c_t$ และผลลัพธ์ $h_{t}$ ผ่าน **เกต** แต่ละเกตมีการทำงานแบบ sigmoid (ผลลัพธ์อยู่ในช่วง $[0,1]$) ซึ่งสามารถคิดได้ว่าเป็นหน้ากากบิต (bitwise mask) เมื่อคูณกับเวกเตอร์สถานะ LSTMs มีเกตดังต่อไปนี้ (จากซ้ายไปขวาในภาพด้านบน):\n",
"* **forget gate** ซึ่งกำหนดว่าองค์ประกอบใดของเวกเตอร์ $c_{t-1}$ ที่เราต้องลืม และองค์ประกอบใดที่ต้องส่งผ่าน\n",
"* **input gate** ซึ่งกำหนดว่าข้อมูลจากเวกเตอร์อินพุตและเวกเตอร์ซ่อนก่อนหน้าควรถูกนำมารวมเข้ากับเวกเตอร์สถานะมากน้อยเพียงใด\n",
"* **output gate** ซึ่งนำเวกเตอร์สถานะใหม่และตัดสินว่าองค์ประกอบใดของมันจะถูกใช้เพื่อสร้างเวกเตอร์ซ่อนใหม่ $h_t$\n",
"\n",
"องค์ประกอบของสถานะ $c$ สามารถคิดได้ว่าเป็นธงที่สามารถเปิดหรือปิดได้ ตัวอย่างเช่น เมื่อเราเจอชื่อ *Alice* ในลำดับ เราคาดเดาว่ามันหมายถึงผู้หญิง และยกธงในสถานะที่บอกว่าเรามีคำนามเพศหญิงในประโยค เมื่อเราพบคำว่า *and Tom* ต่อไป เราจะยกธงที่บอกว่าเรามีคำนามพหูพจน์ ดังนั้นโดยการจัดการสถานะ เราสามารถติดตามคุณสมบัติทางไวยากรณ์ของประโยคได้\n",
"\n",
"> **Note**: นี่คือแหล่งข้อมูลที่ยอดเยี่ยมสำหรับการทำความเข้าใจโครงสร้างภายในของ LSTMs: [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) โดย Christopher Olah\n",
"\n",
"แม้ว่าโครงสร้างภายในของเซลล์ LSTM อาจดูซับซ้อน Keras ซ่อนการใช้งานนี้ไว้ภายในเลเยอร์ `LSTM` ดังนั้นสิ่งเดียวที่เราต้องทำในตัวอย่างด้านบนคือการแทนที่เลเยอร์ 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": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## RNN แบบสองทิศทางและหลายชั้น\n",
"\n",
"ในตัวอย่างที่ผ่านมาของเรา เครือข่ายประสาทแบบวนซ้ำ (recurrent networks) ทำงานจากจุดเริ่มต้นของลำดับไปจนถึงจุดสิ้นสุด ซึ่งดูเป็นธรรมชาติสำหรับเราเพราะมันเป็นทิศทางเดียวกับที่เราอ่านหรือฟังคำพูด อย่างไรก็ตาม สำหรับสถานการณ์ที่ต้องการการเข้าถึงลำดับข้อมูลแบบสุ่ม การคำนวณแบบวนซ้ำในทั้งสองทิศทางอาจมีความเหมาะสมมากกว่า RNN ที่สามารถคำนวณในทั้งสองทิศทางเรียกว่า **bidirectional** RNN และสามารถสร้างได้โดยการห่อชั้น recurrent ด้วยชั้นพิเศษที่เรียกว่า `Bidirectional`\n",
"\n",
"> **Note**: ชั้น `Bidirectional` จะสร้างสำเนาของชั้น recurrent ขึ้นมาสองชุด และตั้งค่าคุณสมบัติ `go_backwards` ของหนึ่งในสำเนาเหล่านั้นเป็น `True` เพื่อให้มันทำงานในทิศทางตรงกันข้ามกับลำดับ\n",
"\n",
"เครือข่ายประสาทแบบวนซ้ำ ไม่ว่าจะเป็นแบบทิศทางเดียวหรือสองทิศทาง จะจับรูปแบบภายในลำดับและเก็บไว้ในเวกเตอร์สถานะ (state vectors) หรือส่งคืนเป็นผลลัพธ์ เช่นเดียวกับเครือข่ายแบบคอนโวลูชัน (convolutional networks) เราสามารถสร้างชั้น recurrent อีกชั้นหนึ่งตามหลังชั้นแรกเพื่อจับรูปแบบในระดับที่สูงขึ้น ซึ่งสร้างขึ้นจากรูปแบบระดับต่ำที่ชั้นแรกดึงออกมาได้ สิ่งนี้นำเราไปสู่แนวคิดของ **multi-layer RNN** ซึ่งประกอบด้วยเครือข่ายประสาทแบบวนซ้ำสองชั้นหรือมากกว่า โดยที่ผลลัพธ์ของชั้นก่อนหน้าจะถูกส่งไปยังชั้นถัดไปเป็นข้อมูลนำเข้า\n",
"\n",
"![ภาพแสดง Multilayer long-short-term-memory- RNN](../../../../../translated_images/th/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` เพราะเราต้องการให้ชั้นนั้นส่งคืนสถานะระหว่างทั้งหมด ไม่ใช่แค่สถานะสุดท้ายของการคำนวณแบบวนซ้ำ\n",
"\n",
"มาสร้าง LSTM แบบสองชั้นและสองทิศทางสำหรับปัญหาการจัดประเภทของเรากันเถอะ\n",
"\n",
"> **Note** โค้ดนี้ใช้เวลาค่อนข้างนานในการรันจนเสร็จสมบูรณ์ แต่ให้ความแม่นยำสูงสุดที่เราเคยเห็นมา ดังนั้นอาจจะคุ้มค่าที่จะรอและดูผลลัพธ์\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": [
"## RNNs สำหรับงานอื่นๆ\n",
"\n",
"จนถึงตอนนี้ เราได้มุ่งเน้นไปที่การใช้ RNNs เพื่อจัดประเภทลำดับของข้อความ แต่จริงๆ แล้ว RNNs สามารถจัดการงานอื่นๆ ได้อีกมากมาย เช่น การสร้างข้อความและการแปลภาษา ซึ่งเราจะพิจารณางานเหล่านั้นในหน่วยถัดไป\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**ข้อจำกัดความรับผิดชอบ**: \nเอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษา AI [Co-op Translator](https://github.com/Azure/co-op-translator) แม้ว่าเราจะพยายามให้การแปลมีความถูกต้อง แต่โปรดทราบว่าการแปลอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่แม่นยำ เอกสารต้นฉบับในภาษาต้นทางควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ ขอแนะนำให้ใช้บริการแปลภาษามนุษย์ที่เป็นมืออาชีพ เราจะไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความที่ผิดพลาดซึ่งเกิดจากการใช้การแปลนี้\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-29T10:51:37+00:00",
"source_file": "lessons/5-NLP/16-RNN/RNNTF.ipynb",
"language_code": "th"
}
},
"nbformat": 4,
"nbformat_minor": 4
}