Text generator สร้างคำขวัญวันเด็กจาก Deep learning model
Text generator using LSTM on Google Colab
ถ้าศึกษาโจทย์เกี่ยวกับ NLP แล้ว อาจพบว่ามีอะไรทำเล่นเยอะมาก Text generator ก็เป็นอีกเรื่องนึงที่น่าสนใจ Blog ตอนนี้เป็นการทดลอง Train model ด้วย Recurrent neural network อย่าง LSTM เพื่อสร้างประโยคอย่างง่าย เช่นการสร้างคำขวัญวันเด็ก โดยใช้ข้อมูลคำขวัญวันเด็กที่ผ่านมาในอดีตทั้งหมดเป็น Input
Data
เหตุผลที่เลือกเป็นคำขวัญวันเด็ก เพราะว่าธีมของคำขวัญวันเด็กของไทยมีเพียงไม่กี่อย่าง เช่น รักชาติ ประหยัด มีวินัย ใฝ่เรียนรู้ คู่คุณธรรม พัฒนาชาติ ฯลฯ เคยเห็นคนแซวว่าคำขวัญวันเด็กของไทย แต่งไม่ยากเพียงแค่เอาของปีก่อนหน้า มาสลับคำ สลับตำแหน่งก็ได้เป็นคำขวัญใหม่ของปีนี้แล้ว
รู้สึกว่าถูกใจ Assumption นี้ เลยคิดว่าเมื่อ Trained model เสร็จแล้วคงจะได้คำขวัญใหม่ที่สลับคำไปมาเช่นกัน ดังนั้นเรื่องที่ Data จำกัดอยู่เพียงไม่กี่ปี จึงไม่ใช่ประเด็น คำขวัญวันเด็กย้อนหลัง สามารถหาได้จาก Wikipedia ตาม Link ด้านล่าง
Preprocessing
อาจเป็นข้อดีสำหรับข้อมูล Input ที่เป็นคำขวัญ เพราะจัดว่าเป็นข้อความที่ Clean มาก เช่นคำฟุ่มเฟือย หรือสัญลักษณ์ต่าง ๆ ที่ไม่จำเป็นอาจมีน้อย ในที่นี้เลยขอเก็บทุกคำที่มีในคำขวัญวันเด็กไว้ทั้งหมด
เมื่อไม่ต้องทำความสะอาดข้อมูลเยอะ ก็สามารถใช้เพียงแค่ Word tokenizer ในการตัดประโยคให้เป็นคำ และเก็บคำแยกไว้ในรูปแบบสมาชิกของ List
จากนั้นให้ทำ Extract ข้อความที่อยู่ใน List เก็บไว้เป็น String ในรูปแบบของ DataFrame เพื่อใช้ใน Keras Tokenizer ต่อไป
Keras Tokenizer
Keras Tokenizer เป็นเครื่องมือสำหรับการทำงานบน NLP ที่ช่วยในการสร้าง Corpus จาก Text ที่มีอยู่ ตัวอย่างการใช้งาน Keras Tokenizer เช่น
tokenizer = Tokenizer(lower = False)# Example text
example = [
'Machine learning model',
'Deep learning model',
'Machine Learning',
'Deep learning'
]
# Fit to text
tokenizer.fit_on_texts(example)
เริ่มต้นจากประกาศตัวแปร Object Tokenizer()
เพื่อรับข้อมูลที่เป็น List of string ผ่าน .fit_on_texts()
จากนั้นสามารถใช้คำสั่งกับ Object Tokenizer()
ได้เช่น .document_count
เป็นการนับ Corpus ทั้งหมดจากข้อมูล Input จากตัวอย่างคำที่ไม่ซ้ำกันเลย เกิดขึ้นทั้งหมด 5 คำ โดยที่ learning
กับ Learning
นับแยกเป็น 2 คำ เพราะสะกดด้วยตัวเล็กใหญ่ไม่เหมือนกัน ดังนั้น Corpus จึงเท่ากับ 5
print(f'Words count: {tokenizer.word_counts}')
หากต้องการนับความถี่ของคำที่เกิดขึ้นว่า ปรากฎขึ้นทั้งหมดกี่ครั้งในข้อมูล สามารถใช้ .word_counts
ผลลัพธ์ที่ได้คือ Dictionary ordered โดยเรียงลำดับจากคำที่เจอก่อน เช่น Machine
คือสมาชิกตัวแรกใน List แรกมีความถี่เท่ากับ 2
ตามด้วย learning
คือสมาชิกตัวที่ 2 ใน List แรกมีความถี่เท่ากับ 3
เป็นต้น
Words count: OrderedDict([('Machine', 2), ('learning', 3), ('model', 2), ('Deep', 2), ('Learning', 1)])
ต่อมาเป็นการ Assign index ให้กับคำที่อยู่ในข้อมูลด้วย .word_index
เพื่อใช้เป็นตำแหน่งอ้างอิงกับ Deep learning model
print(f'Words index: {tokenizer.word_index}')
โดยผลลัพธ์ที่ได้อยู่ในรูปแบบของ Dictionary โดยให้ Index แรกกับคำที่มีความถี่มากที่สุดก่อน หากคำมีความถี่เท่ากัน จะพิจารณาจากลำดับข้อมูลก่อนหลัง จากตัวอย่างคำว่า learning
มีความถี่มากที่สุดจึงโดย Assign ให้อยู่ใน Index ที่ 1
ส่วนกรณีที่ความถี่กันของ Machine, model, Deep
จะพิจารณาลำดับการเกิดก่อนหลัง โดยที่จากทั้งหมด 3 คำ Machine
อยู่ในลำดับแรกจึงโดย Assign ให้อยู่ใน Index ที่ 2
เป็นต้น
Words index: {'learning': 1, 'Machine': 2, 'model': 3, 'Deep': 4, 'Learning': 5}
ใช้หลักการที่ได้อธิบายไปของ Keras Tokenizer กับข้อมูลคำขวัญวันเด็ก โดย Corpus ทั้งหมดมีค่าเท่ากับ 189 คำ ต้องเก็บจำนวนคำนี้ไว้ สำหรับขั้นตอนต่อไปในการสร้าง Deep learning model
Total unique word: 189
Text to sequences
ต่อมาเป็นเรื่องของการสร้าง Sequences ที่เป็นหลักการสำคัญสำหรับ LSTM Model เพราะเป็นการใช้ข้อมูลก่อนหน้า เพื่อทำนายข้อมูลที่มีโอกาสเกิดขึ้นถัดไป LSTM จึงเหมาะกับ Text generator model เพราะอาศัยข้อมูลก่อนหน้านี้เป็น Input เช่น หากมีคำว่า “กิน” คำต่อไปที่มีโอกาสเกิดขึ้นได้ย่อมเป็นสิ่งของที่กินได้ เป็นต้น แต่ก่อนอื่นต้องแทนค่าคำเหล่านี้ด้วยตัวเลข เพื่อให้ Model แปลผลได้เสียก่อน
# Create token sequential
sequences = tokenizer.texts_to_sequences(example)
print(f'Token sequences generated: {sequences}')
การใช้งาน .texts_to_sequences()
เป็นการนำ Index ใน Corpus มาแทนค่าในคำที่เกิดขึ้นในประโยค ตัวอย่างเช่น ข้อความแรกอย่าง Machine learning model
สามารถแทนค่าได้ [2, 1, 3]
เพราะจาก .word_index
ประกอบไปด้วย ‘Machine’: 2, ‘learning’: 1, ’model’: 3
เป็นต้น
Token sequences generated: [[2, 1, 3], [4, 1, 3], [2, 5], [4, 1]]
เมื่อแทนค่าด้วย Index ทั้งหมดในคำขวัญวันเด็กแล้ว สามารถสร้างเป็น Sequences โดยหลักการคือค่อย ๆ เพิ่มคำในแต่ละประโยคทีละ 1 คำ เพื่อสร้างรูปแบบการเดาคำถัดไปจากคำก่อนหน้านี้
หากต้องการแสดงผลลัพธ์ในรูปแบบคำเหมือนเดิม แทนที่จะเป็นตัวเลข สามารถใช้ sequences_to_texts()
เพื่อ Inverse ตัวเลขเหล่านี้กลับเป็นคำตามเดิม
สำหรับขั้นตอนต่อมาคือการ Pad sequences เพื่อให้มิติของข้อมูลมีขนาดเท่ากัน ก่อนทำการ Training ด้วย LSTM โดยหลักการคือการหา List ที่มีจำนวนสมาชิกเยอะที่สุด หมายความว่าเป็นข้อความที่ยาวที่สุด จากนั้นเติม 0 (Zero padding) ไว้ข้างหน้าให้กับ List อื่น ๆ จนมีขนาดเท่ากับ Length ที่ยาวที่สุด
ต่อมาเป็นการสร้าง Features จาก Sequences ใช้หลักการเดิมคือการใช้คำก่อนหน้านี้เป็น Input เพื่อทำนายคำต่อไป ดังนั้นจากข้อมูลที่ทำมาทั้งหมด เพียงแค่ตัดสมาชิกตัวสุดท้ายของแต่ละ List ออกไป ก็ได้เป็น Features ที่ต้องการแล้ว
เช่นเดียวกันกับ Target คือสมาชิกตัวสุดท้ายที่โดนตัดออกจาก Features
ต่อมาเป็นการ Encoding target variables ด้วย One-Hot Encoding คือการทำให้เป็น Sparse matrix หรือ Matrix ที่ประกอบไปด้วย 0 เป็นจำนวนมาก โดย Columns ที่เป็นไปได้มากที่สุด มีค่าเท่ากับจำนวนของ Corpus ที่หาไว้ก่อนหน้าคือ 189 คำ
แสดงตัวอย่างของ Label[0]
คือ Target แรกสุดใน Array โดยที่ Sequences แรกคือ จง บำเพ็ญตน
เมื่อตัดข้อมูลออกเป็น Features และ Target จึงทำให้ Label[0]
มีเลข 1
อยู่ในตำแหน่งที่ 80
ตาม Index ใน Corpus จึงมีค่าเท่ากับ
บำเพ็ญตน
Model
สร้าง Network แบบ LSTM ปกติ แต่ไม่ได้ใส่ Bidirectional layer เพราะเนื่องจากข้อมูลเป็นประเภทคำขวัญ และขนาดมีจำกัด จึงอยากให้ Training ไปในทิศทางเดียวมากกว่า
กำหนด Epochs และ Learning rate อาจต้องกำหนดจำนวนรอบการเทรนให้เยอะหน่อย และ Learning rate ที่ไม่สูงมาก เพื่อให้ Network ค่อย ๆ เรียนรู้ไป
เนื่องจากอาจต้องมีการ Re-training model หลายรอบ เพื่อให้เกิดความแม่นยำพอที่จะนำไปใช้งานได้ ดังนั้นอาจไม่จำเป็นต้องรู้ Loss/Accuracy ในแต่ละ Ephoch สามารถกำหนด verbose = 0
เพื่อปิด Log ไปได้เลย
เมื่อ Accuracy เข้าใกล้ 1 และ Loss เข้าใกล้ 0 ก็สามารถนำ Model นี้ไปใช้งานต่อได้ เนื่องจากใช้เวลา Training ค่อนข้างนาน เลยขอ .save()
เก็บโมเดลไว้กันพลาด
Result
เขียนเป็นฟังก์ชั่นขึ้นมาเพื่อทดสอบการใช้งาน Model โดยภายในฟังก์ชั่นรับ Input ที่เป็น Text จากนั้นให้ผ่านขั้นตอน Data preprocessing ตั้งแต่ Tokenization → Text to sequences → Zero padding เพื่อให้มิติข้อมูลมีขนาดเท่ากับที่อยู่ใน LSTM Model จากนั้นใช้ Model เพื่อทำนายคำต่อไปที่มีโอกาสเกิดขึ้นมากที่สุด และใช้คำที่ Model ทำนายไปก่อนหน้านี้ เป็น Input สำหรับ Loop ถัดไป โดยที่กำหนด Condition ในการหยุดเป็น Argument ของฟังก์ชั่นนี้
ทดสอบ Model ด้วยการใส่ Input อะไรก็ได้เข้าไป เพื่อให้ Text generator model สร้างเป็นคำขวัญวันเด็กตามที่ Model ได้เรียนรู้ไว้
เด็ก ยุคใหม่ จิต อาสา ร่วม พัฒนา ชาติ รุ่งเรือง
เด็ก ไทย วิถี ใหม่ รวม ไทย สร้างชาติ ด้วย
ชาติ ไทย ประหยัด เป็น คุณสมบัติ ของ เด็ก ไทย
ชาติ พัฒนา ศาสน์ กษัตริย์ คุณสมบัติ ของ ของ เยาวชน
เด็ก ใน วันนี้ จง หมั่น เรียน เพียร กระ ทำดี
Conclusion
ขั้นตอนทั้งหมดนี้คือการสร้าง Text generator model แบบง่าย และพยายามอธิบายหลักการทำงานของโมเดลให้ละเอียดเท่าที่จะทำได้แล้ว แต่ในความเป็นจริง ยังมีวิธีที่สามารถพัฒนา Text generator model ให้ดีกว่านี้ได้มาก คงเป็นเนื้อหาสำหรับอนาคต
สำหรับ Colab notebook ของ Blog ตอนนี้สามารถดูได้ที่ Link ด้านบน