← Back to list

Thai Gaming Toxic Detection

สร้างระบบตรวจจับข้อความ Toxic ในแชทเกมไทยด้วย Sentence Embedding และ KNN

Mindpuri · 2026-06-13 15:54 · 0 claps · 7.5 min read
#ai #toxic #normal #text-classification #thai
Open on Medium ↗
Wiki topics: RAG · RAG & Retrieval ML · Machine Learning AI · AI · General 🎮 · Gaming

Thai Gaming Toxic Detection

สร้างระบบตรวจจับข้อความ Toxic ในแชทเกมไทยด้วย Sentence Embedding และ KNN

เวลาพูดถึงเกมออนไลน์ สิ่งหนึ่งที่หลายคนเจอแทบทุกเกมคือ “แชท toxic”

บางครั้งมันมาในรูปแบบคำด่าตรง ๆ บางครั้งมาในรูปแบบการกดดันเพื่อนร่วมทีม บางครั้งไม่ได้หยาบมาก แต่เจตนาชัดเจนว่าโจมตีหรือทำให้คนอื่นรู้สึกแย่

ปัญหาคือ แชทเกมภาษาไทยไม่ได้ตรวจจับง่ายด้วยการหาคำหยาบอย่างเดียว เพราะคำบางคำสามารถใช้ได้ทั้งในบริบทปกติและบริบทที่เป็นพิษ

ตัวอย่างเช่นคำว่า:

เล่น
ทีม
ไม่
มึง
ช่วย

คำเหล่านี้อาจอยู่ในประโยคปกติก็ได้ หรืออาจอยู่ในประโยคที่ toxic ก็ได้ ขึ้นอยู่กับบริบทของทั้งประโยค

โปรเจกต์นี้จึงเกิดขึ้นเพื่อทดลองว่า Machine Learning สามารถช่วยตรวจจับข้อความ Toxic ในแชทเกมภาษาไทยได้ดีแค่ไหน

เหตุผลที่คิดจะทำโปรเจกต์นี้

สาเหตุแรกคือ แชทเกมเป็นพื้นที่ที่ข้อความสั้นมาก แต่ความหมายแรงมาก

ข้อความในเกมมักไม่ได้เป็นประโยคยาว ๆ แบบบทความ แต่เป็นประโยคสั้น ๆ เช่น:

เล่นกากมาก
ทีมพังเพราะมึง
ถอยก่อน เดี๋ยวตาย

บางข้อความดูคล้ายกันมาก แต่เจตนาไม่เหมือนกันเลย ประโยคหนึ่งอาจเป็นการชมเพื่อนร่วมทีมจริง ๆ แต่อีกประโยคอาจเป็นการประชด เสียดสี หรือกดดันผู้เล่นคนอื่น

เช่น คำว่า สุดยอด

นายนี่มันโคตรสุดยอดว่ะรอบนี้

ประโยคนี้มีแนวโน้มเป็นคำชม เพราะบริบทโดยรวมสื่อว่าเพื่อนร่วมทีมเล่นได้ดีตอนนั้น

กับ

โคตรสุดยอดเลยนาย สุดจะบรรยายจริงๆ

ประโยคนี้อาจไม่ได้เป็นคำชมเสมอไป เพราะบริบทของประโยคสามารถตีความได้ว่าเป็นการประชด โดยเฉพาะถ้าเกิดหลังจากผู้เล่นทำพลาดในเกม

ด้วยเหตุนี้การตรวจจับ Toxic ด้วย keyword อย่างเดียวไม่เพียงพอ เพราะคำเดียวกันสามารถเปลี่ยนความหมายได้ตามบริบท น้ำเสียง และคำรอบข้าง ระบบจึงต้องพยายามเข้าใจทั้งประโยค ไม่ใช่แค่ตรวจว่ามีคำว่า “สุดยอด” หรือมีคำหยาบอยู่ในข้อความหรือไม่

สาเหตุที่สองคือ ระบบกรองคำหยาบแบบเดิมยังไม่พอ

ต่อจากสาเหตุแรกถ้าใช้แค่ blacklist หรือ keyword detection ระบบอาจจับผิดข้อความที่ไม่ได้ toxic หรือพลาดข้อความที่ toxic แต่ไม่มีคำหยาบตรง ๆ เช่น การประชด การกดดัน หรือการใช้คำธรรมดาในบริบทที่โจมตีคนอื่น

สาเหตุที่สามคือ ภาษาไทยมีความท้าทายเฉพาะตัว

ภาษาไทยไม่มีการเว้นวรรคระหว่างคำแบบภาษาอังกฤษเสมอไป และในแชทเกมยังมีภาษาพูด คำย่อ คำทับศัพท์ และคำที่พิมพ์ผิดปะปนอยู่ตลอด ทำให้การตรวจจับด้วยวิธีง่าย ๆ ทำได้ไม่แม่นพอ

สาเหตุสุดท้ายคือ อยากทดลองทำโปรเจกต์ Machine Learning ที่ไม่ได้จบแค่การวัดผลใน Notebook แต่สามารถนำไปเชื่อมกับระบบจริงได้

ดังนั้นโปรเจกต์นี้จึงไม่ได้หยุดแค่การเทรนโมเดล แต่ต่อยอดไปถึง Discord Bot Demo เพื่อให้เห็นภาพว่าโมเดลสามารถถูกนำไปใช้ตรวจข้อความในชุมชนเกมจริงได้อย่างไร

เป้าหมายของโปรเจกต์

เป้าหมายของโปรเจกต์นี้คือการสร้างระบบต้นแบบที่สามารถจำแนกข้อความแชทเกมภาษาไทยออกเป็น 2 ประเภท:

Normal
Toxic

ตัวอย่างข้อความ Normal:

gg wp ทุกคนเล่นดีมากเลย

ตัวอย่างข้อความ Toxic:

เล่นกากมาก ทีมพังหมด

ระบบนี้ยังไม่ใช่ระบบ moderation ที่สมบูรณ์สำหรับใช้งานจริงทันที แต่เป็น prototype เพื่อทดลองว่าแนวทาง Machine Learning โดยเฉพาะ Sentence Embedding สามารถเข้าใจบริบทของข้อความภาษาไทยได้มากแค่ไหน

Dataset

โปรเจกต์นี้ใช้ dataset ที่สร้างขึ้นสำหรับข้อความแชทเกมภาษาไทยโดยเฉพาะ

Main dataset มีทั้งหมด:

1,100 ตัวอย่าง

แบ่งเป็น:

Normal  550
Toxic   550

นอกจากนี้ยังมี clean unseen test set สำหรับทดสอบโมเดลกับข้อความที่ไม่เคยเห็นมาก่อน

Clean unseen test set มีทั้งหมด:

244 ตัวอย่าง

แบ่งเป็น:

Normal  122
Toxic   122

ชุดทดสอบนี้ถูกตรวจสอบและลบข้อความที่ซ้ำกับ main dataset แล้ว เพื่อให้การวัดผลสะท้อนความสามารถของโมเดลในการเจอข้อความใหม่จริง ๆ

การทำความเข้าใจข้อมูลเบื้องต้น

ก่อนสร้างโมเดล ผมเริ่มจากการสำรวจข้อมูลเบื้องต้นเพื่อดูว่า dataset มีความสมดุลหรือไม่ และมี pattern ใดที่อาจช่วยหรือหลอกโมเดลได้บ้าง

จาก dataset หลัก พบว่ามีข้อมูลทั้งหมด 1,100 ตัวอย่าง แบ่งเป็น Normal 550 ตัวอย่าง และ Toxic 550 ตัวอย่าง ซึ่งถือว่าเป็น dataset ที่สมดุล ไม่มีปัญหา class imbalance และไม่พบข้อมูลซ้ำในชุดข้อมูลนี้

เมื่อดูความยาวข้อความเฉลี่ย พบว่า:

Normal mean length = 26.53
Toxic mean length  = 26.48

ความยาวเฉลี่ยของข้อความทั้งสอง class ใกล้เคียงกันมาก แปลว่าเราไม่สามารถใช้ความยาวของข้อความเป็นตัวแยกได้ง่าย ๆ เช่น ข้อความ Toxic ไม่ได้ยาวกว่าหรือสั้นกว่า Normal อย่างชัดเจน

นอกจากนี้ เมื่อดูคำที่พบบ่อยที่สุดใน dataset จะเห็นว่าคำหลายคำไม่ได้เป็นตัวบอก class ได้โดยตรง เช่น “เล่น”, “ทีม”, “ไม่”, “ได้”, “ไป” หรือแม้แต่คำอย่าง “มึง” ก็ยังพบได้ทั้งในข้อความ Normal และ Toxic

ตัวอย่างเช่น คำว่า “เล่น” พบทั้งหมด 288 ครั้ง โดยอยู่ในข้อความ Toxic 205 ครั้ง และ Normal 83 ครั้ง แปลว่าคำนี้มีแนวโน้มไปทาง Toxic ใน dataset นี้ แต่ก็ไม่ได้แปลว่าทุกข้อความที่มีคำว่า “เล่น” จะต้องเป็น Toxic เสมอไป

ในทางกลับกัน คำว่า “ก่อน” พบ 227 ครั้ง และพบในข้อความ Normal มากกว่า Toxic อย่างชัดเจน แต่ก็ยังมีบางส่วนที่อยู่ในข้อความ Toxic เช่นกัน

Insight สำคัญจาก EDA คือ ข้อความแชทเกมภาษาไทยไม่สามารถตัดสินได้จากคำเดี่ยวหรือความยาวของข้อความเพียงอย่างเดียว เพราะคำเดียวกันสามารถปรากฏได้ทั้งในบริบทปกติและบริบทที่เป็นพิษ

ดังนั้นโมเดลจำเป็นต้องเรียนรู้บริบทของทั้งประโยค ซึ่งเป็นเหตุผลที่โปรเจกต์นี้เลือกทดลอง Sentence Embedding เพื่อแปลงข้อความให้เป็น vector ที่สะท้อนความหมายโดยรวมของประโยคมากกว่าการพึ่งพา keyword เพียงอย่างเดียว

ทำไม Keyword Detection อย่างเดียวไม่พอ

วิธีที่ง่ายที่สุดในการตรวจจับข้อความ Toxic คือการสร้าง blacklist หรือ keyword list เช่น ถ้ามีคำหยาบก็ให้จัดเป็น Toxic

แต่วิธีนี้มีข้อจำกัดมาก

เพราะคำบางคำไม่ได้ toxic ด้วยตัวมันเอง แต่ toxic เพราะบริบทของประโยค

ตัวอย่างเช่น:

มึงถอยก่อน เดี๋ยวโดนจับ

ในบางบริบท ประโยคนี้อาจเป็นการเตือนเพื่อนร่วมทีม ไม่ใช่การด่า

แต่ถ้าเป็น:

มึงเล่นกากมาก ทีมพังหมด

บริบทเปลี่ยนไปทันที และข้อความมีลักษณะโจมตีผู้เล่นคนอื่นชัดเจนขึ้น

ดังนั้นการตรวจจับ Toxic ในภาษาไทย โดยเฉพาะในแชทเกม จึงต้องใช้วิธีที่เข้าใจความหมายของทั้งประโยคมากกว่าการหาคำบางคำ

วิธีที่ทดลอง

โปรเจกต์นี้ทดลองหลายวิธีเพื่อเปรียบเทียบประสิทธิภาพของโมเดล

วิธีที่ทดลองมีดังนี้:

  1. Keyword / Blacklist baseline
  2. TF-IDF + Logistic Regression
  3. TF-IDF + Linear SVM
  4. Sentence Embedding MiniLM + Logistic Regression
  5. Sentence Embedding MiniLM + Linear SVM
  6. Sentence Embedding MiniLM + Random Forest
  7. Dense Layer + Fixed Seed
  8. Sentence Embedding MiniLM + KNN5
  9. Sentence Embedding MiniLM + KNN9
  10. Sentence Embedding MiniLM + KNN13

โมเดล embedding ที่ใช้คือ:

sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2

โมเดลนี้รองรับหลายภาษา รวมถึงภาษาไทย และสามารถแปลงข้อความทั้งประโยคให้กลายเป็น vector ที่ใช้แทนความหมายของประโยคนั้นได้

โมเดลที่ดีที่สุด

จากการทดลอง โมเดลที่ให้ผลดีที่สุดคือ:

Sentence Embedding MiniLM + KNN13

ผลลัพธ์บน clean unseen test set คือ:

Accuracy: 86.07%
Macro F1-score: 0.8607

Classification Report:

                   precision    recall  f1-score   support
      Normal       0.86      0.86      0.86       122
       Toxic       0.86      0.86      0.86       122
    accuracy                           0.86       244
   macro avg       0.86      0.86      0.86       244
weighted avg       0.86      0.86      0.86       244

KNN คืออะไร

KNN ย่อมาจาก K-Nearest Neighbors หรือแปลตรงตัวได้ว่า “เพื่อนบ้านที่ใกล้ที่สุดจำนวน K ตัว”

แนวคิดของ KNN ค่อนข้างเรียบง่าย คือเมื่อมีข้อความใหม่เข้ามา โมเดลจะนำข้อความนั้นไปเปรียบเทียบกับข้อความตัวอย่างใน dataset ที่เคยเรียนรู้ไว้ แล้วดูว่า “ข้อความที่อยู่ใกล้ที่สุด” มี label เป็นอะไรบ้าง

ในโปรเจกต์นี้ ข้อความแต่ละประโยคจะถูกแปลงเป็นตัวเลขในรูปแบบ vector ด้วย Sentence Embedding ก่อน โดยโมเดล paraphrase-multilingual-MiniLM-L12-v2 จะแปลงข้อความหนึ่งประโยคให้อยู่ในรูปของ vector 384 มิติ

พูดง่าย ๆ คือ แทนที่คอมพิวเตอร์จะมองข้อความเป็นตัวอักษรธรรมดา เช่น “เล่นกากมาก” หรือ “gg wp ทุกคนเล่นดีมากเลย” ระบบจะเปลี่ยนข้อความเหล่านั้นให้กลายเป็นชุดตัวเลข 384 ค่า ซึ่งใช้แทนความหมายโดยรวมของประโยคนั้น

หลังจากนั้น KNN จะนำ vector ของข้อความใหม่ไปเปรียบเทียบกับ vector ของข้อความใน training set แล้วดูว่าข้อความใดมีความหมายใกล้เคียงกันมากที่สุด

ตัวอย่างเช่น ถ้าใช้ KNN13 หมายความว่าโมเดลจะดูข้อความที่ใกล้ที่สุด 13 ตัวอย่าง แล้วให้ทั้ง 13 ตัวอย่างนั้นโหวตว่า ข้อความใหม่นี้ควรเป็น Normal หรือ Toxic

ถ้าเพื่อนบ้านส่วนใหญ่เป็น Toxic โมเดลก็จะทายว่า Toxic ถ้าเพื่อนบ้านส่วนใหญ่เป็น Normal โมเดลก็จะทายว่า Normal

ตัวอย่างเช่น ถ้าเพื่อนบ้าน 11 จาก 13 ตัวอย่างเป็น Toxic โมเดลก็จะทายว่า Toxic และค่า Toxic probability จะอยู่ประมาณ 84.62%

ข้อดีของ KNN คือเข้าใจง่าย และเหมาะกับงานที่เราต้องการดูความคล้ายของข้อความจาก embedding แต่ข้อจำกัดคือโมเดลต้องเปรียบเทียบข้อความใหม่กับข้อมูลตัวอย่างเดิมทุกครั้ง จึงอาจช้าลงเมื่อ dataset มีขนาดใหญ่มาก

ผลการเปรียบเทียบโมเดล

จากผลการทดลองจะเห็นว่า Keyword baseline ให้ผลต่ำที่สุด ในขณะที่กลุ่ม Sentence Embedding ให้ผลดีกว่าอย่างชัดเจน

สิ่งนี้สนับสนุนแนวคิดว่า การตรวจจับข้อความ Toxic ในแชทเกมไทยต้องใช้บริบทของประโยค ไม่ใช่แค่การค้นหาคำบางคำ

ทำไม KNN13 ถึงดีที่สุด

ในกลุ่ม KNN มีการทดลองหลายค่า ได้แก่ KNN5, KNN9 และ KNN13

ผลลัพธ์คือ:

KNN5   Accuracy: 84.43%
KNN9   Accuracy: 85.66%
KNN13  Accuracy: 86.07%

KNN13 ให้ผลดีที่สุดในการทดลองนี้ เพราะใช้จำนวนเพื่อนบ้านมากพอที่จะลดผลกระทบจากข้อความตัวอย่างที่เป็น noise และยังเป็นจำนวนคี่ จึงช่วยลดปัญหาการโหวตเสมอกัน

โมเดลนี้ใช้ cosine distance เพราะเหมาะกับการเปรียบเทียบ sentence embedding vector มากกว่า Euclidean distance เนื่องจาก Cosine Distance มุ่งเน้นไปที่ทิศทางของ Vector (เจตนา/ความหมายของประโยค) มากกว่าขนาดของ Vector (ความยาวของประโยค) ทำให้เหมาะกับข้อความแชทเกมที่สั้นยาวไม่เท่ากัน

อีกจุดที่น่าสนใจคือ probability ของ KNN สามารถตีความได้จากสัดส่วนการโหวตของเพื่อนบ้าน เช่น ถ้าเพื่อนบ้าน 11 จาก 13 ตัวโหวตว่า Toxic ค่า Toxic probability จะอยู่ประมาณ 84.62%

แล้วจะรู้ได้อย่างไรว่า Accuracy 86.07% ดีจริง

คำถามสำคัญคือ ถ้าโมเดลทำ Accuracy ได้ 86.07% เราจะรู้ได้อย่างไรว่าตัวเลขนี้ “ดีจริง” ไม่ใช่แค่ตัวเลขที่ดูสูงเฉย ๆ

คำตอบคือ ต้องดูจากตัวเปรียบเทียบ ไม่ใช่ดูตัวเลขเดี่ยว ๆ

ในโปรเจกต์นี้มีตัวเปรียบเทียบอยู่ 3 ระดับ

ระดับแรกคือการเทียบกับ baseline ที่ง่ายที่สุด คือ Keyword / Blacklist baseline

วิธีนี้เป็นการตรวจจากคำหรือ pattern บางอย่าง ถ้าข้อความมีคำที่อยู่ในกลุ่มเสี่ยง ก็จัดเป็น Toxic

ผลลัพธ์ของ Keyword baseline คือ:

Accuracy: 59.84%
Macro F1-score: 0.5211

ในขณะที่โมเดลที่ดีที่สุด คือ Sentence Embedding MiniLM + KNN13 ได้ผลลัพธ์:

Accuracy: 86.07%
Macro F1-score: 0.8607

ช่องว่างระหว่าง 59.84% กับ 86.07% แสดงให้เห็นชัดเจนว่า การใช้บริบทของประโยคผ่าน Sentence Embedding ช่วยให้โมเดลทำงานได้ดีกว่าการตรวจ keyword อย่างเดียวมาก

ระดับที่สองคือการเทียบกับโมเดลอื่นที่ทดลองบน dataset และ clean unseen test set ชุดเดียวกัน

โมเดลอย่าง TF-IDF + Logistic Regression ได้ Accuracy 78.69% TF-IDF + Linear SVM ได้ 80.74% Sentence Embedding + Linear SVM ได้ 82.79% Dense Layer + Fixed Seed ได้ 82.79% KNN9 ได้ 85.66% และ KNN13 ได้ 86.07%

ดังนั้น KNN13 ไม่ได้ดีเพราะตัวเลข 86% ดูสูงเพียงอย่างเดียว แต่ดีเพราะเป็นโมเดลที่ทำคะแนนสูงสุดในการทดลองเดียวกัน ภายใต้ข้อมูลทดสอบชุดเดียวกัน

ระดับที่สามคือการเทียบกับงานภายนอกแบบระมัดระวัง

งานด้าน toxicity detection ภาษาไทยและหลายภาษามีผลลัพธ์แตกต่างกันมาก ขึ้นอยู่กับ dataset, ภาษา, จำนวน label, ความยากของข้อความ และ metric ที่ใช้ ตัวอย่างเช่น งาน Thai Twitter Toxicity Corpus ของ Haruechaiyasak และ Kongthon (LREC 2018) รายงานผลการจำแนกข้อความพิษในภาษาไทยด้วยวิธี supervised classification ที่มี accuracy อยู่ในช่วงประมาณ 0.80 บนชุดข้อมูลที่ใช้ในการศึกษา (Haruechaiyasak & Kongthon, 2018, Proceedings of the LREC 2018 Workshop on Resources and Evaluation for Social Media Text Processing, pp. 31–36) ขณะที่งาน toxic comment ภาษาอังกฤษบนชุดข้อมูล Jigsaw Toxic Comment Classification Challenge มีรายงานว่าโมเดลสมัยใหม่หลายแบบสามารถทำคะแนนได้สูงกว่า 98% ในบาง metric หรือบางการตั้งค่าการทดลอง (Kaggle Jigsaw Toxic Comment Classification Challenge, https://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge))

อย่างไรก็ตาม ควรพิจารณาบริบทของการพัฒนาโมเดลร่วมด้วย เพราะงานระดับ benchmark เหล่านี้มักใช้ Dataset ขนาดใหญ่กว่า ใช้เวลาพัฒนาและปรับแต่งโมเดลมากกว่า มีทรัพยากรด้านคอมพิวเตอร์และงบประมาณสูงกว่า รวมถึงอาจใช้ทีมวิจัยหรือผู้เข้าแข่งขันจำนวนมากที่มีประสบการณ์เฉพาะทาง ดังนั้นแม้ตัวเลขผลลัพธ์จะสูงกว่า แต่ไม่สามารถนำมาเปรียบเทียบกับโปรเจกต์ขนาดเล็กหรือการทดลองในสภาพแวดล้อมที่มีข้อจำกัดด้านเวลา งบประมาณ และสเปกเครื่องคอมพิวเตอร์ได้โดยตรง

ดังนั้นวิธีที่ยุติธรรมที่สุดสำหรับโปรเจกต์นี้คือการเทียบโมเดลหลายตัวบน clean unseen test set ชุดเดียวกัน ซึ่ง KNN13 ได้ผลดีที่สุด

สรุปคือ 86.07% ถือว่าเป็นผลลัพธ์ที่ดีสำหรับ prototype นี้ เพราะ:

  • ดีกว่า Keyword baseline อย่างชัดเจน
  • ดีกว่า TF-IDF, SVM, Random Forest และ Dense Layer ที่ทดลองในโปรเจกต์เดียวกัน
  • ทดสอบบน clean unseen test set ที่ไม่มีข้อความซ้ำกับชุด train
  • ใช้ Macro F1-score ประกอบ ไม่ได้ดู Accuracy อย่างเดียว
  • ผลลัพธ์อยู่ในระดับที่ใกล้เคียงหรือสูงกว่างานภาษาไทยบางชุดที่เคยรายงาน แต่ยังต้องระวังว่าไม่สามารถเทียบกับ benchmark คนละ dataset ได้โดยตรง

อย่างไรก็ตาม 86.07% ไม่ได้แปลว่าโมเดลพร้อมใช้ลงโทษผู้ใช้จริงทันที เพราะยังมีโอกาสเกิด False Positive และ False Negative ได้ ระบบแบบนี้จึงควรมี confidence threshold, error analysis และ human review ก่อนนำไปใช้จริง

ตัวอย่างโค้ดสำคัญในโปรเจกต์

เพื่อให้เห็นภาพว่าโปรเจกต์นี้ทำงานอย่างไร ส่วนนี้จะยกตัวอย่างโค้ดหลักบางส่วน ตั้งแต่การโหลดข้อมูล การสร้าง embedding การเทรนโมเดล KNN13 ไปจนถึงการนำโมเดลไปใช้กับ Discord Bot

1. โหลดและทำความสะอาดข้อมูล

ขั้นแรกคือการโหลด dataset จากไฟล์ CSV แล้วทำความสะอาดข้อมูลเบื้องต้น เช่น ลบแถวว่าง และตัดช่องว่างหัวท้ายของข้อความกับ label

import pandas as pd

main_dataset_path = "thai_gaming_toxic_dataset_v2_simple.csv"
df = pd.read_csv(main_dataset_path)
df = df.dropna(subset=["text", "label"]).copy()
df["text"] = df["text"].astype(str).str.strip()
df["label"] = df["label"].astype(str).str.strip()
print("Total rows:", len(df))
print(df["label"].value_counts())
print("Duplicates:", df.duplicated(subset=["text", "label"]).sum())

โค้ดส่วนนี้ช่วยให้มั่นใจว่า dataset ไม่มีค่าว่างใน column สำคัญ และ label อยู่ในรูปแบบที่พร้อมใช้สำหรับการเทรนโมเดล

2. แปลงข้อความเป็น Sentence Embedding

โมเดลที่ใช้แปลงข้อความคือ paraphrase-multilingual-MiniLM-L12-v2 ซึ่งรองรับหลายภาษา รวมถึงภาษาไทย

ข้อความแต่ละประโยคจะถูกแปลงเป็น vector 384 มิติ เพื่อให้คอมพิวเตอร์สามารถเปรียบเทียบความหมายของข้อความได้

from sentence_transformers import SentenceTransformer
embedder = SentenceTransformer(
    "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
)
X_train_emb = embedder.encode(
    list(X_train_text),
    show_progress_bar=True,
    convert_to_numpy=True
)
print("Embedding shape:", X_train_emb.shape)

ถ้า training set มี 770 ข้อความ ผลลัพธ์จะได้ matrix ขนาดประมาณ:

(770, 384)

หมายความว่าแต่ละข้อความถูกแทนด้วยตัวเลข 384 ค่า

3. เทรนโมเดล KNN13

หลังจากได้ embedding แล้ว จึงนำ vector เหล่านี้ไปเทรนโมเดล KNN โดยใช้ n_neighbors=13 และใช้ cosine เป็น metric สำหรับวัดความคล้ายของข้อความ

from sklearn.neighbors import KNeighborsClassifier

knn13_model = KNeighborsClassifier(
    n_neighbors=13,
    metric="cosine"
)
knn13_model.fit(X_train_emb, y_train)
print("KNN13 model ready.")

แนวคิดคือ เมื่อมีข้อความใหม่เข้ามา โมเดลจะดูว่า vector ของข้อความใหม่นั้นใกล้กับข้อความตัวอย่างใดมากที่สุด 13 ตัว แล้วใช้การโหวตของเพื่อนบ้านเหล่านั้นเพื่อตัดสินว่าเป็น Normal หรือ Toxic

4. ทดสอบโมเดลกับ Clean Unseen Test Set

เพื่อให้การวัดผลน่าเชื่อถือขึ้น โปรเจกต์นี้ใช้ clean unseen test set ที่ไม่มีข้อความซ้ำกับ main dataset

from sklearn.metrics import accuracy_score, classification_report, f1_score

unseen_dataset_path = "thai_gaming_toxic_unseen_test_clean_244.csv"
unseen_df = pd.read_csv(unseen_dataset_path)
unseen_df = unseen_df.dropna(subset=["text", "label"]).copy()
unseen_df["text"] = unseen_df["text"].astype(str).str.strip()
unseen_df["label"] = unseen_df["label"].astype(str).str.strip()
unseen_texts = unseen_df["text"].tolist()
unseen_labels = unseen_df["label"].tolist()
X_unseen_emb = embedder.encode(
    unseen_texts,
    show_progress_bar=True,
    convert_to_numpy=True
)
knn13_pred = knn13_model.predict(X_unseen_emb)
accuracy = accuracy_score(unseen_labels, knn13_pred)
macro_f1 = f1_score(unseen_labels, knn13_pred, average="macro")
print("Accuracy:", accuracy)
print("Macro F1:", macro_f1)
print(classification_report(unseen_labels, knn13_pred))

ผลลัพธ์ที่ได้จากโมเดล KNN13 คือ:

Accuracy: 86.07%
Macro F1-score: 0.8607

5. ดู Probability ของแต่ละข้อความ

สำหรับ KNN ค่า probability มาจากสัดส่วนการโหวตของเพื่อนบ้าน เช่น ถ้าเพื่อนบ้าน 11 จาก 13 ตัวโหวตว่า Toxic ค่า Toxic probability จะอยู่ประมาณ 84.62%

probability = knn13_model.predict_proba(X_unseen_emb)
probability_rows = []
for text, true_label, pred_label, proba in zip(
    unseen_texts,
    unseen_labels,
    knn13_pred,
    probability
):
    row = {
        "text": text,
        "true_label": true_label,
        "prediction": pred_label
    }
    for label, prob in zip(knn13_model.classes_, proba):
        row[f"{label}_probability"] = prob
    probability_rows.append(row)
probability_df = pd.DataFrame(probability_rows)
probability_df.head()

ตารางนี้ช่วยให้เห็นว่าโมเดลมั่นใจแค่ไหนในแต่ละข้อความ ไม่ใช่แค่รู้ว่าโมเดลตอบ Normal หรือ Toxic

6. Error Analysis แบบย่อ

หลังจากได้ผลลัพธ์แล้ว สิ่งสำคัญคือการดูว่าโมเดลทายผิดตรงไหนบ้าง

wrong_df = probability_df[
    probability_df["true_label"] != probability_df["prediction"]
].copy()

def get_error_type(row):
    if row["true_label"] == "Normal" and row["prediction"] == "Toxic":
        return "False Positive"
    elif row["true_label"] == "Toxic" and row["prediction"] == "Normal":
        return "False Negative"
    else:
        return "Other"

wrong_df["error_type"] = wrong_df.apply(get_error_type, axis=1)
print("Total wrong predictions:", len(wrong_df))
print(wrong_df["error_type"].value_counts())
wrong_df.head()

Error Analysis ช่วยให้เห็นว่าโมเดลพลาดแบบไหน เช่น

  • False Positive: ข้อความจริงเป็น Normal แต่โมเดลทายว่า Toxic
  • False Negative: ข้อความจริงเป็น Toxic แต่โมเดลทายว่า Normal

สำหรับระบบ moderation ข้อผิดพลาดสองแบบนี้มีผลกระทบต่างกันมาก เพราะ False Positive อาจทำให้ผู้เล่นปกติถูกลงโทษ ส่วน False Negative อาจทำให้ข้อความ Toxic หลุดรอดไปได้

7. ตัวอย่างการเชื่อมกับ Discord Bot

หลังจากมีฟังก์ชัน predict_toxicity() แล้ว สามารถนำไปเชื่อมกับ Discord Bot ได้

ตัวอย่างนี้คือส่วนที่ให้บอทอ่านข้อความใน Discord แล้วตรวจว่าเป็น Normal หรือ Toxic

import discord
from datetime import timedelta

DISCORD_TOKEN = "ใส่ Discord Bot Token ของตัวเองตรงนี้"
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)

@client.event
async def on_ready():
    print(f"Logged in as {client.user}")
    print("Bot is ready and listening to messages.")

@client.event
async def on_message(message):
    print("on_message triggered")

    if message.author.bot:
        print("Ignored bot message")
        return

    text = message.content.strip()

    print("Author:", message.author)
    print("Channel:", message.channel)
    print("Message:", text)

    if text == "":
        print("Empty message")
        return

    prediction, normal_prob, toxic_prob = predict_toxicity(text)

    print("Prediction:", prediction)
    print("Normal:", normal_prob)
    print("Toxic:", toxic_prob)

    await message.channel.send(
        f"🔎 Checked: **{prediction}**\n"
        f"Normal: `{normal_prob:.2%}` | Toxic: `{toxic_prob:.2%}`"
    )

    if prediction == "Toxic":
        try:
            await message.author.timeout(
                timedelta(seconds=5),
                reason="Detected toxic message by Thai Gaming Toxic Detector"
            )

            await message.channel.send(
                f"🚨 {message.author.mention} ถูก timeout 5 วินาที"
            )

        except discord.Forbidden:
            await message.channel.send(
                "⚠️ Toxic แต่ timeout ไม่ได้: บอทไม่มีสิทธิ์ Moderate Members หรือ role ต่ำกว่าคนนี้"
            )

        except discord.HTTPException as e:
            await message.channel.send(
                f"⚠️ Timeout ไม่สำเร็จ: `{e}`"
            )

client.run(DISCORD_TOKEN)

ในตัวอย่างนี้ ถ้าข้อความถูกทำนายว่า Toxic บอทจะ timeout ผู้ส่งเป็นเวลา 5 วินาที

อย่างไรก็ตาม ในการใช้งานจริงควรตั้ง threshold ให้รัดกุมกว่านี้ และควรมี human review เพื่อป้องกันการลงโทษผิดคนจาก False Positive

ภาพรวมคือ

มีข้อความในแชท
       ↓
บอทรับข้อความ → เป็น Bot ไหม? → ใช่ → หยุด
       ↓ ไม่ใช่
ส่งข้อความให้ AI วิเคราะห์
       ↓
แจ้งผลในแชท (Normal/Toxic + เปอร์เซ็นต์)
       ↓
Toxic? → ใช่ → Timeout 5 วินาที + แจ้งแชท
       ↓ ไม่ใช่
จบการทำงาน

หมายเหตุเรื่อง Discord Token

ในตัวอย่างโค้ดมีบรรทัดนี้:

DISCORD_TOKEN = "ใส่ Discord Bot Token ของตัวเองตรงนี้"

ให้ใส่ Token ของตัวเอง

Discord Bot Demo

หลังจากเลือกโมเดลสุดท้ายแล้ว โปรเจกต์นี้ได้นำโมเดลไปเชื่อมกับ Discord Bot โดยใช้ discord.py

บอทสามารถตรวจข้อความใน Discord server และตอบผลการจำแนกได้ เช่น:

Checked: Toxic
Normal: 15.38% | Toxic: 84.62%

ในเวอร์ชัน demo ผมตั้งให้บอท timeout ผู้ใช้ชั่วคราวเมื่อพบข้อความที่มีแนวโน้ม Toxic ไป 5 วิ

อย่างไรก็ตาม การลงโทษผู้ใช้โดยอัตโนมัติควรใช้ด้วยความระมัดระวัง เพราะโมเดลยังมีโอกาสทายผิดได้ ในระบบจริงควรมี human review หรือ confidence threshold ที่เข้มงวดกว่านี้

Discord Bot นี้เป็น demo เพื่อแสดงว่าโมเดลสามารถเชื่อมกับระบบใช้งานจริงได้ แต่ยังไม่ควรใช้ลงโทษผู้ใช้จริงโดยไม่มี threshold และ human review

ตัวอย่างการใช้งาน

[embed]

ข้อความปกติ

ข้อความปกติ

ข้อความ Toxic

ข้อความ Toxic

Error Analysis

นอกจากดู Accuracy และ F1-score แล้ว โปรเจกต์นี้ยังทำ Error Analysis เพื่อดูว่าโมเดลพลาดแบบไหน

ประเภท error ที่ดู ได้แก่:

  • False Positive: ข้อความจริงเป็น Normal แต่โมเดลทายว่า Toxic
  • False Negative: ข้อความจริงเป็น Toxic แต่โมเดลทายว่า Normal
  • Uncertain Cases: ข้อความที่โมเดลไม่มั่นใจสูง
  • High Confident Wrong Cases: ข้อความที่โมเดลมั่นใจในการตอบแต่ผิด

Error Analysis สำคัญมาก เพราะระบบ moderation ไม่ได้มีแค่คำถามว่า “แม่นกี่เปอร์เซ็นต์” แต่ต้องดูด้วยว่าถ้าพลาดแล้วพลาดแบบไหน

False Positive อาจทำให้ผู้เล่นที่ไม่ได้ทำผิดถูกกล่าวหา False Negative อาจทำให้ข้อความ Toxic หลุดรอดไปได้

สิ่งที่ได้เรียนรู้จากโปรเจกต์นี้

จากการทดลองนี้ สิ่งที่เห็นได้ชัดคือ:

  • การตรวจจับข้อความ Toxic ภาษาไทยต้องใช้บริบท
  • Keyword detection อย่างเดียวไม่เพียงพอ
  • Dataset ที่สมดุลช่วยให้ประเมินผลได้ชัดเจนขึ้น
  • Sentence Embedding ให้ผลดีกว่า Keyword baseline และ TF-IDF ในการทดลองนี้
  • KNN13 เป็นโมเดลที่เรียบง่ายแต่ให้ผลดีเกินความคาดหมายบนชุดทดสอบนี้
  • การเชื่อมโมเดลกับ Discord Bot ช่วยให้เห็นภาพการใช้งานจริงมากขึ้น
  • Error Analysis สำคัญมากสำหรับระบบที่เกี่ยวข้องกับการ moderation

ข้อจำกัดของโปรเจกต์

โปรเจกต์นี้ยังเป็น prototype และมีข้อจำกัดหลายอย่าง เช่น:

  • Dataset ยังมีขนาดไม่ใหญ่มาก
  • ข้อความ Toxic บางแบบต้องอาศัยบริบทของบทสนทนาก่อนหน้า
  • KNN probability เป็นสัดส่วนการโหวตของเพื่อนบ้าน ไม่ใช่ probability ที่ calibrate แล้ว
  • โมเดลยังมีโอกาสเกิด False Positive และ False Negative
  • ไม่ควรใช้ลงโทษผู้ใช้จริงโดยไม่มี human review หรือระบบตรวจสอบเพิ่มเติม

แนวทางพัฒนาต่อ

สิ่งที่สามารถพัฒนาต่อได้ ได้แก่:

  • เพิ่ม dataset จากข้อความจริงที่หลากหลายขึ้น
  • เพิ่ม label ย่อย เช่น Insult, Harassment, Threat, Spam, Compliment
  • ทดลอง fine-tune transformer model สำหรับภาษาไทยและบริบทเกมโดยเฉพาะ
  • เพิ่ม confidence threshold ก่อนดำเนินการลงโทษ
  • เพิ่มระบบ human review สำหรับ admin
  • โค้ด Deploy bot บน cloud server แทนการรันในเครื่องหรือ Colab
  • สร้าง dashboard สำหรับดู log และ error case

สรุป

โปรเจกต์นี้แสดงให้เห็นว่า Sentence Embedding สามารถช่วยตรวจจับข้อความ Toxic ในแชทเกมภาษาไทยได้ดีกว่าวิธี keyword-based อย่างชัดเจน

โมเดลที่ดีที่สุดคือ MiniLM Sentence Embedding + KNN13 ซึ่งทำ Accuracy ได้ 86.07% และ Macro F1-score 0.8607 บน clean unseen test set

แม้โมเดลจะยังไม่สมบูรณ์สำหรับ production แต่โปรเจกต์นี้แสดง workflow ที่ครบตั้งแต่:

Dataset → Model Comparison → Final Model → Error Analysis → Discord Bot Demo

และเป็นจุดเริ่มต้นที่ดีสำหรับการพัฒนาระบบ moderation ภาษาไทยที่เข้าใจบริบทมากกว่าการตรวจคำหยาบแบบเดิม ในงานนี้ สิ่งที่โมเดลต้องเรียนรู้จึงไม่ใช่แค่ว่า “มีคำอะไรอยู่ในข้อความ” แต่ต้องเรียนรู้ว่า “คำเหล่านั้นถูกใช้ด้วยเจตนาแบบไหน”


메타데이터
post_id
3befd329fe8a
slug
thai-gaming-toxic-detection-3befd329fe8a
url
https://medium.com/@mindpuri/thai-gaming-toxic-detection-3befd329fe8a
canonical_url
https://medium.com/@mindpuri/thai-gaming-toxic-detection-3befd329fe8a
author_url
https://medium.com/@mindpuri
status
ok
fetched_at
2026-06-21 15:33:18