{ "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": [ "" ] }, "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": [ "" ] }, "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": [ "" ] }, "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 }