495 lines
40 KiB
Plaintext
495 lines
40 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# เครือข่ายการสร้างสรรค์\n",
|
|
"\n",
|
|
"Recurrent Neural Networks (RNNs) และรูปแบบเซลล์ที่มีการควบคุม เช่น Long Short Term Memory Cells (LSTMs) และ Gated Recurrent Units (GRUs) ได้มอบกลไกสำหรับการสร้างแบบจำลองภาษา กล่าวคือ พวกมันสามารถเรียนรู้การเรียงลำดับคำและให้การคาดการณ์คำถัดไปในลำดับได้ สิ่งนี้ทำให้เราสามารถใช้ RNNs สำหรับ **งานสร้างสรรค์** เช่น การสร้างข้อความทั่วไป การแปลภาษา และแม้กระทั่งการสร้างคำบรรยายภาพ\n",
|
|
"\n",
|
|
"ในสถาปัตยกรรม RNN ที่เราได้พูดถึงในหน่วยก่อนหน้า แต่ละหน่วย RNN จะสร้างสถานะซ่อนถัดไปเป็นผลลัพธ์ อย่างไรก็ตาม เราสามารถเพิ่มผลลัพธ์อีกตัวให้กับแต่ละหน่วย RNN ซึ่งจะช่วยให้เราสามารถสร้าง **ลำดับ** (ที่มีความยาวเท่ากับลำดับต้นฉบับ) นอกจากนี้ เรายังสามารถใช้หน่วย RNN ที่ไม่รับข้อมูลเข้าในแต่ละขั้นตอน โดยใช้เพียงเวกเตอร์สถานะเริ่มต้น และสร้างลำดับของผลลัพธ์ออกมา\n",
|
|
"\n",
|
|
"ในโน้ตบุ๊กนี้ เราจะมุ่งเน้นไปที่โมเดลการสร้างสรรค์แบบง่ายที่ช่วยให้เราสร้างข้อความได้ เพื่อความเรียบง่าย เรามาสร้าง **เครือข่ายระดับตัวอักษร** ซึ่งสร้างข้อความทีละตัวอักษร ในระหว่างการฝึก เราจำเป็นต้องนำข้อความจากคลังข้อมูล และแบ่งมันออกเป็นลำดับตัวอักษร\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",
|
|
"import numpy as np\n",
|
|
"\n",
|
|
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## สร้างคลังคำศัพท์ตัวอักษร\n",
|
|
"\n",
|
|
"ในการสร้างเครือข่ายแบบสร้างสรรค์ระดับตัวอักษร เราจำเป็นต้องแยกข้อความออกเป็นตัวอักษรแต่ละตัวแทนที่จะเป็นคำ `TextVectorization` layer ที่เราเคยใช้ก่อนหน้านี้ไม่สามารถทำได้ ดังนั้นเรามีสองตัวเลือก:\n",
|
|
"\n",
|
|
"* โหลดข้อความด้วยตนเองและทำการแบ่งคำ 'ด้วยมือ' ตามตัวอย่าง [Keras อย่างเป็นทางการนี้](https://keras.io/examples/generative/lstm_character_level_text_generation/)\n",
|
|
"* ใช้คลาส `Tokenizer` สำหรับการแบ่งคำในระดับตัวอักษร\n",
|
|
"\n",
|
|
"เราจะเลือกตัวเลือกที่สอง `Tokenizer` ยังสามารถใช้เพื่อแบ่งคำในระดับคำได้ ดังนั้นจึงสามารถเปลี่ยนจากการแบ่งคำระดับตัวอักษรไปเป็นระดับคำได้อย่างง่ายดาย\n",
|
|
"\n",
|
|
"ในการแบ่งคำระดับตัวอักษร เราจำเป็นต้องส่งพารามิเตอร์ `char_level=True`:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"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",
|
|
"tokenizer = keras.preprocessing.text.Tokenizer(char_level=True,lower=False)\n",
|
|
"tokenizer.fit_on_texts([x['title'].numpy().decode('utf-8') for x in ds_train])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"เรายังต้องการใช้โทเค็นพิเศษหนึ่งตัวเพื่อระบุ **จุดสิ้นสุดของลำดับ** ซึ่งเราจะเรียกว่า `<eos>` มาลองเพิ่มมันเข้าไปในคลังคำด้วยตนเอง:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"eos_token = len(tokenizer.word_index)+1\n",
|
|
"tokenizer.word_index['<eos>'] = eos_token\n",
|
|
"\n",
|
|
"vocab_size = eos_token + 1"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"[[48, 2, 10, 10, 5, 44, 1, 25, 5, 8, 10, 13, 78]]"
|
|
]
|
|
},
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"tokenizer.texts_to_sequences(['Hello, world!'])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## การฝึก RNN เชิงกำเนิดเพื่อสร้างหัวข้อข่าว\n",
|
|
"\n",
|
|
"วิธีที่เราจะฝึก RNN เพื่อสร้างหัวข้อข่าวมีดังนี้ ในแต่ละขั้นตอน เราจะนำหัวข้อข่าวหนึ่งหัวข้อมาใส่ใน RNN และสำหรับแต่ละตัวอักษรที่ป้อนเข้าไป เราจะให้เครือข่ายสร้างตัวอักษรถัดไป:\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"สำหรับตัวอักษรสุดท้ายของลำดับ เราจะให้เครือข่ายสร้างโทเค็น `<eos>` \n",
|
|
"\n",
|
|
"ความแตกต่างหลักของ RNN เชิงกำเนิดที่เราใช้ในที่นี้คือ เราจะนำผลลัพธ์จากแต่ละขั้นตอนของ RNN มาใช้ ไม่ใช่แค่จากเซลล์สุดท้ายเท่านั้น ซึ่งสามารถทำได้โดยการกำหนดพารามิเตอร์ `return_sequences` ให้กับเซลล์ RNN\n",
|
|
"\n",
|
|
"ดังนั้น ในระหว่างการฝึก ข้อมูลที่ป้อนเข้าเครือข่ายจะเป็นลำดับของตัวอักษรที่ถูกเข้ารหัสในความยาวที่กำหนด และผลลัพธ์จะเป็นลำดับที่มีความยาวเท่ากัน แต่ถูกเลื่อนออกไปหนึ่งตำแหน่งและสิ้นสุดด้วย `<eos>` มินิแบตช์จะประกอบด้วยลำดับหลายชุด และเราจำเป็นต้องใช้ **padding** เพื่อจัดแนวลำดับทั้งหมดให้ตรงกัน\n",
|
|
"\n",
|
|
"เรามาสร้างฟังก์ชันที่จะเปลี่ยนรูปแบบชุดข้อมูลให้เรา เนื่องจากเราต้องการเติม padding ในระดับมินิแบตช์ เราจะทำการจัดชุดข้อมูลเป็นแบตช์โดยเรียกใช้ `.batch()` ก่อน จากนั้นจึงใช้ `map` เพื่อทำการเปลี่ยนรูปแบบ ดังนั้น ฟังก์ชันการเปลี่ยนรูปแบบจะรับมินิแบตช์ทั้งหมดเป็นพารามิเตอร์:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def title_batch(x):\n",
|
|
" x = [t.numpy().decode('utf-8') for t in x]\n",
|
|
" z = tokenizer.texts_to_sequences(x)\n",
|
|
" z = tf.keras.preprocessing.sequence.pad_sequences(z)\n",
|
|
" return tf.one_hot(z,vocab_size), tf.one_hot(tf.concat([z[:,1:],tf.constant(eos_token,shape=(len(z),1))],axis=1),vocab_size)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"สิ่งสำคัญบางอย่างที่เราทำในที่นี้:\n",
|
|
"* เราเริ่มต้นด้วยการดึงข้อความจริงจาก string tensor\n",
|
|
"* `text_to_sequences` แปลงรายการของสตริงให้เป็นรายการของ integer tensors\n",
|
|
"* `pad_sequences` เติมเต็ม tensors เหล่านั้นให้มีความยาวสูงสุด\n",
|
|
"* สุดท้าย เราทำการ one-hot encode ตัวอักษรทั้งหมด รวมถึงการเลื่อนและเพิ่ม `<eos>` เราจะได้เห็นในไม่ช้าว่าทำไมเราถึงต้องการ one-hot-encoded characters\n",
|
|
"\n",
|
|
"อย่างไรก็ตาม ฟังก์ชันนี้เป็น **Pythonic** ซึ่งหมายความว่ามันไม่สามารถแปลงเป็น Tensorflow computational graph ได้โดยอัตโนมัติ เราจะพบข้อผิดพลาดหากพยายามใช้ฟังก์ชันนี้โดยตรงในฟังก์ชัน `Dataset.map` เราจำเป็นต้องครอบคลุมการเรียก Pythonic นี้โดยใช้ตัวห่อ `py_function`:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def title_batch_fn(x):\n",
|
|
" x = x['title']\n",
|
|
" a,b = tf.py_function(title_batch,inp=[x],Tout=(tf.float32,tf.float32))\n",
|
|
" return a,b"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **หมายเหตุ**: การแยกแยะระหว่างฟังก์ชันการแปลงแบบ Pythonic และ Tensorflow อาจดูซับซ้อนเกินไป และคุณอาจสงสัยว่าทำไมเราไม่แปลงชุดข้อมูลโดยใช้ฟังก์ชัน Python มาตรฐานก่อนที่จะส่งไปยัง `fit` แม้ว่าสิ่งนี้จะสามารถทำได้ แต่การใช้ `Dataset.map` มีข้อได้เปรียบอย่างมาก เพราะกระบวนการแปลงข้อมูลจะถูกดำเนินการโดยใช้กราฟการคำนวณของ Tensorflow ซึ่งใช้ประโยชน์จากการคำนวณด้วย GPU และลดความจำเป็นในการส่งข้อมูลระหว่าง CPU/GPU\n",
|
|
"\n",
|
|
"ตอนนี้เราสามารถสร้างเครือข่าย generator ของเราและเริ่มการฝึกได้ มันสามารถอิงจาก recurrent cell ใด ๆ ที่เราได้พูดถึงในหน่วยก่อนหน้า (simple, LSTM หรือ GRU) ในตัวอย่างของเราจะใช้ LSTM\n",
|
|
"\n",
|
|
"เนื่องจากเครือข่ายรับตัวอักษรเป็นอินพุต และขนาดของคำศัพท์ค่อนข้างเล็ก เราไม่จำเป็นต้องมี embedding layer อินพุตที่ถูก one-hot-encoded สามารถส่งตรงไปยัง LSTM cell ได้เลย ชั้นเอาต์พุตจะเป็น `Dense` classifier ที่จะแปลงผลลัพธ์ของ LSTM ให้เป็นตัวเลขโทเค็นที่ถูก one-hot-encoded\n",
|
|
"\n",
|
|
"นอกจากนี้ เนื่องจากเรากำลังจัดการกับลำดับที่มีความยาวแปรผัน เราสามารถใช้ชั้น `Masking` เพื่อสร้างมาสก์ที่จะละเลยส่วนที่เติมเต็มของสตริงได้ แม้ว่าสิ่งนี้จะไม่จำเป็นอย่างเคร่งครัด เพราะเราไม่ได้สนใจอะไรมากนักที่เกินโทเค็น `<eos>` แต่เราจะใช้มันเพื่อให้ได้ประสบการณ์กับชั้นประเภทนี้ `input_shape` จะเป็น `(None, vocab_size)` โดยที่ `None` บ่งบอกถึงลำดับที่มีความยาวแปรผัน และรูปร่างของเอาต์พุตคือ `(None, vocab_size)` เช่นกัน ดังที่คุณสามารถเห็นได้จาก `summary`:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Model: \"sequential\"\n",
|
|
"_________________________________________________________________\n",
|
|
"Layer (type) Output Shape Param # \n",
|
|
"=================================================================\n",
|
|
"masking (Masking) (None, None, 84) 0 \n",
|
|
"_________________________________________________________________\n",
|
|
"lstm (LSTM) (None, None, 128) 109056 \n",
|
|
"_________________________________________________________________\n",
|
|
"dense (Dense) (None, None, 84) 10836 \n",
|
|
"=================================================================\n",
|
|
"Total params: 119,892\n",
|
|
"Trainable params: 119,892\n",
|
|
"Non-trainable params: 0\n",
|
|
"_________________________________________________________________\n",
|
|
"15000/15000 [==============================] - 229s 15ms/step - loss: 1.5385\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<tensorflow.python.keras.callbacks.History at 0x7fa40c1245e0>"
|
|
]
|
|
},
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model = keras.models.Sequential([\n",
|
|
" keras.layers.Masking(input_shape=(None,vocab_size)),\n",
|
|
" keras.layers.LSTM(128,return_sequences=True),\n",
|
|
" keras.layers.Dense(vocab_size,activation='softmax')\n",
|
|
"])\n",
|
|
"\n",
|
|
"model.summary()\n",
|
|
"model.compile(loss='categorical_crossentropy')\n",
|
|
"\n",
|
|
"model.fit(ds_train.batch(8).map(title_batch_fn))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## การสร้างผลลัพธ์\n",
|
|
"\n",
|
|
"เมื่อเราได้ฝึกโมเดลเรียบร้อยแล้ว ขั้นตอนต่อไปคือการใช้มันเพื่อสร้างผลลัพธ์ ก่อนอื่นเราต้องหาวิธีถอดรหัสข้อความที่แสดงในรูปแบบลำดับของตัวเลขโทเค็น สำหรับการทำเช่นนี้ เราสามารถใช้ฟังก์ชัน `tokenizer.sequences_to_texts` ได้ อย่างไรก็ตาม ฟังก์ชันนี้ไม่ทำงานได้ดีนักกับการโทเค็นในระดับตัวอักษร ดังนั้นเราจะใช้พจนานุกรมของโทเค็นจากตัว tokenizer (เรียกว่า `word_index`) สร้างแผนที่ย้อนกลับ และเขียนฟังก์ชันถอดรหัสของเราเอง:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"reverse_map = {val:key for key, val in tokenizer.word_index.items()}\n",
|
|
"\n",
|
|
"def decode(x):\n",
|
|
" return ''.join([reverse_map[t] for t in x])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"ตอนนี้เราจะเริ่มการสร้างข้อความ เราจะเริ่มต้นด้วยสตริง `start` และเข้ารหัสมันเป็นลำดับ `inp` จากนั้นในแต่ละขั้นตอน เราจะเรียกเครือข่ายของเราเพื่อคาดเดาตัวอักษรถัดไป\n",
|
|
"\n",
|
|
"ผลลัพธ์ของเครือข่าย `out` คือเวกเตอร์ที่มีองค์ประกอบ `vocab_size` ซึ่งแสดงความน่าจะเป็นของแต่ละโทเค็น และเราสามารถหาหมายเลขโทเค็นที่น่าจะเป็นไปได้มากที่สุดโดยใช้ `argmax` จากนั้นเราจะเพิ่มตัวอักษรนี้ลงในรายการโทเค็นที่สร้างขึ้น และดำเนินการสร้างต่อไป กระบวนการนี้ในการสร้างตัวอักษรหนึ่งตัวจะถูกทำซ้ำ `size` ครั้งเพื่อสร้างจำนวนตัวอักษรที่ต้องการ และเราจะหยุดก่อนกำหนดเมื่อพบ `eos_token`\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'Today #39;s lead to strike for the strike for the strike for the strike (AFP)'"
|
|
]
|
|
},
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"def generate(model,size=100,start='Today '):\n",
|
|
" inp = tokenizer.texts_to_sequences([start])[0]\n",
|
|
" chars = inp\n",
|
|
" for i in range(size):\n",
|
|
" out = model(tf.expand_dims(tf.one_hot(inp,vocab_size),0))[0][-1]\n",
|
|
" nc = tf.argmax(out)\n",
|
|
" if nc==eos_token:\n",
|
|
" break\n",
|
|
" chars.append(nc.numpy())\n",
|
|
" inp = inp+[nc]\n",
|
|
" return decode(chars)\n",
|
|
" \n",
|
|
"generate(model)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## การสุ่มผลลัพธ์ระหว่างการฝึกอบรม\n",
|
|
"\n",
|
|
"เนื่องจากเราไม่มีตัวชี้วัดที่มีประโยชน์ เช่น *ความแม่นยำ* วิธีเดียวที่เราจะเห็นได้ว่าโมเดลของเรากำลังพัฒนาขึ้นคือการ **สุ่ม** สตริงที่สร้างขึ้นระหว่างการฝึกอบรม เพื่อทำสิ่งนี้ เราจะใช้ **callbacks** หรือฟังก์ชันที่เราสามารถส่งผ่านไปยังฟังก์ชัน `fit` และจะถูกเรียกใช้งานเป็นระยะระหว่างการฝึกอบรม\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Epoch 1/3\n",
|
|
"15000/15000 [==============================] - 226s 15ms/step - loss: 1.2703\n",
|
|
"Today #39;s a lead in the company for the strike\n",
|
|
"Epoch 2/3\n",
|
|
"15000/15000 [==============================] - 227s 15ms/step - loss: 1.2057\n",
|
|
"Today #39;s the Market Service on Security Start (AP)\n",
|
|
"Epoch 3/3\n",
|
|
"15000/15000 [==============================] - 226s 15ms/step - loss: 1.1752\n",
|
|
"Today #39;s a line on the strike to start for the start\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<tensorflow.python.keras.callbacks.History at 0x7fa40c74e3d0>"
|
|
]
|
|
},
|
|
"execution_count": 13,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"sampling_callback = keras.callbacks.LambdaCallback(\n",
|
|
" on_epoch_end = lambda batch, logs: print(generate(model))\n",
|
|
")\n",
|
|
"\n",
|
|
"model.fit(ds_train.batch(8).map(title_batch_fn),callbacks=[sampling_callback],epochs=3)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"ตัวอย่างนี้สร้างข้อความที่ค่อนข้างดีอยู่แล้ว แต่ยังสามารถปรับปรุงให้ดียิ่งขึ้นได้ในหลายวิธี:\n",
|
|
"\n",
|
|
"* **เพิ่มข้อความ** เราใช้เพียงแค่หัวข้อสำหรับงานนี้ แต่คุณอาจต้องการทดลองใช้ข้อความเต็มรูปแบบ โปรดจำไว้ว่า RNNs ไม่ค่อยเก่งในการจัดการกับลำดับที่ยาวมากนัก ดังนั้นจึงสมเหตุสมผลที่จะตัดข้อความออกเป็นประโยคสั้นๆ หรือฝึกอบรมด้วยความยาวลำดับที่กำหนดไว้ล่วงหน้า `num_chars` (เช่น 256) คุณอาจลองเปลี่ยนตัวอย่างข้างต้นให้เป็นสถาปัตยกรรมแบบนี้ โดยใช้ [ตัวอย่าง Keras อย่างเป็นทางการ](https://keras.io/examples/generative/lstm_character_level_text_generation/) เป็นแรงบันดาลใจ\n",
|
|
"\n",
|
|
"* **LSTM หลายชั้น** การลองใช้ LSTM 2 หรือ 3 ชั้นก็สมเหตุสมผลเช่นกัน ดังที่เราได้กล่าวถึงในหน่วยก่อนหน้า แต่ละชั้นของ LSTM จะดึงรูปแบบบางอย่างจากข้อความออกมา และในกรณีของตัวสร้างข้อความระดับตัวอักษร เราสามารถคาดหวังได้ว่าชั้น LSTM ระดับล่างจะรับผิดชอบการดึงพยางค์ และชั้นที่สูงขึ้นจะรับผิดชอบคำและการผสมคำ สิ่งนี้สามารถทำได้ง่ายๆ โดยการส่งพารามิเตอร์จำนวนชั้นไปยังตัวสร้าง LSTM\n",
|
|
"\n",
|
|
"* คุณอาจต้องการทดลองใช้ **GRU units** และดูว่าแบบใดทำงานได้ดีกว่า รวมถึง **ขนาดของชั้นซ่อนที่แตกต่างกัน** ชั้นซ่อนที่ใหญ่เกินไปอาจทำให้เกิดการ overfitting (เช่น เครือข่ายจะเรียนรู้ข้อความแบบเป๊ะๆ) และขนาดที่เล็กเกินไปอาจไม่สร้างผลลัพธ์ที่ดี\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## การสร้างข้อความแบบนุ่มนวลและค่าอุณหภูมิ\n",
|
|
"\n",
|
|
"ในคำจำกัดความก่อนหน้านี้ของ `generate` เราเลือกตัวอักษรที่มีความน่าจะเป็นสูงสุดเป็นตัวอักษรถัดไปในข้อความที่สร้างขึ้นเสมอ ซึ่งส่งผลให้ข้อความมักจะ \"วนซ้ำ\" ระหว่างลำดับตัวอักษรเดิมซ้ำไปซ้ำมา เช่นในตัวอย่างนี้:\n",
|
|
"```\n",
|
|
"today of the second the company and a second the company ...\n",
|
|
"```\n",
|
|
"\n",
|
|
"อย่างไรก็ตาม หากเราดูการแจกแจงความน่าจะเป็นสำหรับตัวอักษรถัดไป อาจพบว่าความแตกต่างระหว่างความน่าจะเป็นสูงสุดสองสามตัวไม่ได้มากนัก เช่น ตัวอักษรหนึ่งอาจมีความน่าจะเป็น 0.2 และอีกตัวหนึ่งมี 0.19 เป็นต้น ตัวอย่างเช่น เมื่อมองหาตัวอักษรถัดไปในลำดับ '*play*' ตัวอักษรถัดไปอาจเป็นช่องว่างหรือ **e** (เช่นในคำว่า *player*) ได้เท่าๆ กัน\n",
|
|
"\n",
|
|
"สิ่งนี้นำเราไปสู่ข้อสรุปว่าไม่ใช่เรื่อง \"ยุติธรรม\" เสมอไปที่จะเลือกตัวอักษรที่มีความน่าจะเป็นสูงกว่า เพราะการเลือกตัวอักษรที่มีความน่าจะเป็นรองลงมาอาจยังคงนำไปสู่ข้อความที่มีความหมายได้ การสุ่มเลือกตัวอักษรจากการแจกแจงความน่าจะเป็นที่ได้จากผลลัพธ์ของเครือข่ายจึงเป็นวิธีที่ชาญฉลาดกว่า\n",
|
|
"\n",
|
|
"การสุ่มนี้สามารถทำได้โดยใช้ฟังก์ชัน `np.multinomial` ซึ่งเป็นการใช้งาน **การแจกแจงแบบมัลติโนเมียล** ฟังก์ชันที่ใช้สร้างข้อความแบบ **นุ่มนวล** นี้ถูกกำหนดไว้ด้านล่าง:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 33,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"\n",
|
|
"--- Temperature = 0.3\n",
|
|
"Today #39;s strike #39; to start at the store return\n",
|
|
"On Sunday PO to Be Data Profit Up (Reuters)\n",
|
|
"Moscow, SP wins straight to the Microsoft #39;s control of the space start\n",
|
|
"President olding of the blast start for the strike to pay <b>...</b>\n",
|
|
"Little red riding hood ficed to the spam countered in European <b>...</b>\n",
|
|
"\n",
|
|
"--- Temperature = 0.8\n",
|
|
"Today countie strikes ryder missile faces food market blut\n",
|
|
"On Sunday collores lose-toppy of sale of Bullment in <b>...</b>\n",
|
|
"Moscow, IBM Diffeiting in Afghan Software Hotels (Reuters)\n",
|
|
"President Ol Luster for Profit Peaced Raised (AP)\n",
|
|
"Little red riding hood dace on depart talks #39; bank up\n",
|
|
"\n",
|
|
"--- Temperature = 1.0\n",
|
|
"Today wits House buiting debate fixes #39; supervice stake again\n",
|
|
"On Sunday arling digital poaching In for level\n",
|
|
"Moscow, DS Up 7, Top Proble Protest Caprey Mamarian Strike\n",
|
|
"President teps help of roubler stepted lessabul-Dhalitics (AFP)\n",
|
|
"Little red riding hood signs on cash in Carter-youb\n",
|
|
"\n",
|
|
"--- Temperature = 1.3\n",
|
|
"Today wits flawer ro, pSIA figat's co DroftwavesIs Talo up\n",
|
|
"On Sunday hround elitwing wint EU Powerburlinetien\n",
|
|
"Moscow, Bazz #39;s sentries olymen winnelds' next for Olympite Huc?\n",
|
|
"President lost securitys from power Elections in Smiltrials\n",
|
|
"Little red riding hood vides profit, exponituity, profitmainalist-at said listers\n",
|
|
"\n",
|
|
"--- Temperature = 1.8\n",
|
|
"Today #39;It: He deat: N.KA Asside\n",
|
|
"On Sunday i arry Par aldeup patient Wo stele1\n"
|
|
]
|
|
},
|
|
{
|
|
"ename": "KeyError",
|
|
"evalue": "0",
|
|
"output_type": "error",
|
|
"traceback": [
|
|
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
|
"\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)",
|
|
"\u001b[0;32m<ipython-input-33-db32367a0feb>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"\\n--- Temperature = {i}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mj\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 20\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgenerate_soft\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0msize\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m300\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mstart\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwords\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mj\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mtemperature\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
|
"\u001b[0;32m<ipython-input-33-db32367a0feb>\u001b[0m in \u001b[0;36mgenerate_soft\u001b[0;34m(model, size, start, temperature)\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0mchars\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0minp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0minp\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnc\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 13\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mchars\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 14\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0mwords\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m'Today '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'On Sunday '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'Moscow, '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'President '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'Little red riding hood '\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
|
"\u001b[0;32m<ipython-input-10-3f5fa6130b1d>\u001b[0m in \u001b[0;36mdecode\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mreverse_map\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
|
"\u001b[0;32m<ipython-input-10-3f5fa6130b1d>\u001b[0m in \u001b[0;36m<listcomp>\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mreverse_map\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
|
"\u001b[0;31mKeyError\u001b[0m: 0"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"def generate_soft(model,size=100,start='Today ',temperature=1.0):\n",
|
|
" inp = tokenizer.texts_to_sequences([start])[0]\n",
|
|
" chars = inp\n",
|
|
" for i in range(size):\n",
|
|
" out = model(tf.expand_dims(tf.one_hot(inp,vocab_size),0))[0][-1]\n",
|
|
" probs = tf.exp(tf.math.log(out)/temperature).numpy().astype(np.float64)\n",
|
|
" probs = probs/np.sum(probs)\n",
|
|
" nc = np.argmax(np.random.multinomial(1,probs,1))\n",
|
|
" if nc==eos_token:\n",
|
|
" break\n",
|
|
" chars.append(nc)\n",
|
|
" inp = inp+[nc]\n",
|
|
" return decode(chars)\n",
|
|
"\n",
|
|
"words = ['Today ','On Sunday ','Moscow, ','President ','Little red riding hood ']\n",
|
|
" \n",
|
|
"for i in [0.3,0.8,1.0,1.3,1.8]:\n",
|
|
" print(f\"\\n--- Temperature = {i}\")\n",
|
|
" for j in range(5):\n",
|
|
" print(generate_soft(model,size=300,start=words[j],temperature=i))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"เราได้แนะนำพารามิเตอร์อีกตัวหนึ่งที่เรียกว่า **temperature** ซึ่งใช้เพื่อบ่งบอกว่าเราควรยึดติดกับความน่าจะเป็นสูงสุดมากแค่ไหน หาก temperature เท่ากับ 1.0 เราจะทำการสุ่มแบบ multinomial อย่างยุติธรรม และเมื่อ temperature เพิ่มขึ้นจนถึงค่าอนันต์ ความน่าจะเป็นทั้งหมดจะเท่ากัน และเราจะสุ่มเลือกตัวอักษรถัดไปแบบสุ่ม ในตัวอย่างด้านล่าง เราสามารถสังเกตได้ว่าข้อความจะไม่มีความหมายเมื่อเราเพิ่มค่า temperature มากเกินไป และข้อความจะคล้ายกับข้อความที่ถูกสร้างขึ้นแบบ \"วนซ้ำ\" เมื่อค่า temperature เข้าใกล้ 0\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**ข้อจำกัดความรับผิดชอบ**: \nเอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษา AI [Co-op Translator](https://github.com/Azure/co-op-translator) แม้ว่าเราจะพยายามให้การแปลมีความถูกต้องมากที่สุด แต่โปรดทราบว่าการแปลโดยอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่ถูกต้อง เอกสารต้นฉบับในภาษาดั้งเดิมควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ ขอแนะนำให้ใช้บริการแปลภาษามืออาชีพ เราจะไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความผิดที่เกิดจากการใช้การแปลนี้\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"interpreter": {
|
|
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "Python 3.8.12 ('py38')",
|
|
"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"
|
|
},
|
|
"coopTranslator": {
|
|
"original_hash": "9fbb7d5fda708537649f71f5f646fcde",
|
|
"translation_date": "2025-08-29T10:38:37+00:00",
|
|
"source_file": "lessons/5-NLP/17-GenerativeNetworks/GenerativeTF.ipynb",
|
|
"language_code": "th"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
} |