12 เมษายน 2563

Published เมษายน 12, 2563 by with 0 comment

Machine Translation : การแปลภาษาอังกฤษเป็นภาษาไทยด้วยกฎ

บทความนี้เรามาลองเขียนโปรแกมภาษา Python โดยเราจะมาเขียนโปรแกรมการแปลภาษาอังกฤษเป็นภาษาไทยด้วยกฎ หรือ English-Thai Rule-based machine translation
เครื่องแปลภาษา (Machine Translation) คือ โปรแกรม หรือ เครื่องแปลภาษา เป็นส่วนหนึ่งของงานด้านการประมวลผลภาษาธรรมชาติ (Natural Language Processing) เป็นงานที่ท้าทายข้อจำกัดทางด้านภาษาของมนุษย์ เพื่อให้ผู้คนสามารถติดต่อสื่อสารกันได้โดยไม่มีกำแพงทางภาษามาขวางกั้นไว้

เครื่องแปลภาษา เริ่มต้นโดยใช้ความรู้ทางด้านภาษาศาสตร์มาประยุกต์กับทางคอมพิวเตอร์เขียนกฎสำหรับแปลภาษาในคอมพิวเตอร์ เรียกการแปลแบบนี้ว่า Rule-based machine translation (RBMT) ซึ่งพอเวลาผ่านไปสักระยะผู้คนเริ่มเห็นข้อจำกัดการแปลด้วยเทคนิคด้วยกฎ เพราะภาษามนุษย์มีไวยากรณ์มากมายและยากที่จะเขียนให้ครอบคลุม ต่อมาจึงมีเทคนิค Statistical machine translation (SMT) แปลด้วยสถิติ และล่าสุดเป็น Neural machine translation (NMT) ที่ใช้เทคนิคการเรียนรู้เชิงลึก (Deep learning) สามารถแปลได้แม่นยำที่สุดในปัจจุบัน

เพื่อให้เข้าใจ Rule-based machine translation (RBMT) มากยิ่งขึ้น เรามาลองเขียนโปรแกรมแปลภาษาอังกฤษเป็นภาษาไทยด้วยกฎ หรือ English-Thai Rule-based machine translation กัน

เนื่องจากเราจะเขียนขึ้นมาใหม่ ไม่อิงเอกสารด้านนี้ที่เคยใช้งานมา และ ใช้เครื่องมือในปัจจุบันช่วยทำโปรแกรมแปลภาษาอังกฤษเป็นภาษาไทยด้วยกฎ

สิ่งที่ต้องเตรียม
การทำงาน
  • รับประโยคภาษาอังกฤษ 1 ประโยคเข้ามา
  • ตัดประโยคภาษาอังกฤษ
  • นำคำไปผ่าน Stemmer ให้เรียบร้อย
  • นำประโยคที่ผ่านการตัดคำไปหาชนิดของคำ (part of speech)
  • นำแต่ละคู่คำกับชนิดของคำ ไปค้นคำแปลจากพจานานุกรมภาษาอังกฤษ -> ภาษาไทย ให้ได้คู่คำแปลพร้อมกับชนิดของคำ
  • นำคู่คำแปลพร้อมกับชนิดของคำ มาจัดเรียงจากไวยากรณ์ภาษาอังกฤษให้เป็นภาษาไทย
  • นำคำแปลที่ผ่านการจัดเรียงมาแสดงผลจะได้คำแปล
เครื่องมือที่เราจะใช้ในการเขียนโปรแกรมแปลภาษาอังกฤษเป็นภาษาไทยด้วยกฎในภาษาไพทอน Python
  • NLTK - ใช้ตัดคำภาษาอังกฤษ, Stemmer และชนิดของคำ
  • sqlite3 - ใช้สำหรับเชื่อมต่อกับพจนานุกรมคำศัพท์ภาษาอังกฤษ - ภาษาไทย
มาลงมือเขียนโปรแกรมกัน ก่อนใช้งานให้โหลดชุดข้อมูลของ NLTK ก่อนใช้งานดังนี้
import nltk
nltk.download('averaged_perceptron_tagger')
nltk.download('universal_tagset')
nltk.download('punkt')
nltk.download('stopwords')
nltk.download('wordnet')


ส่วนที่ 1 แปลภาษาแบบคำต่อคำตามชนิดของคำ

ค้นคำแปลจากคำศัพท์และตามชนิดของคำ

อย่างแรก เขียนฟังก์ชันค้นหาคำจากฐานข้อมูลพจนานุกรมคำศัพท์ภาษาอังกฤษ - ภาษาไทย ซึ่งเป็นฐานข้อมูล SQLite

ในไฟล์ lexitron.db จะมี TABLE ชื่อ eng2thai ประกอบไปด้วย id, esearch, eentry, tentry,ecat,ethai,esyn และ eant เราจะค้นทั้งคำศัพท์และตามชนิดของคำ เราจึงต้องค้นจาก esearch กับ ecat

โดยเขียนเป็นฟังก์ชันสำหรับค้น ชื่อ word2thai ได้ดังนี้
import sqlite3

conn = sqlite3.connect('lexitron.db')
c = conn.cursor()
def word2thai(word:str,tag:str)->str:
  word = word.lower()
  if tag == '':
    find = c.execute("SELECT * FROM eng2thai WHERE esearch='%s'" % (word))
  else:
    find = c.execute("SELECT * FROM eng2thai WHERE esearch='%s' and ecat='%s'" % (word,tag))
  for row in find:
    if '(' not in row[3] and 'คำ' not in row[3]:
      return row[3]
ฟังก์ชันสำหรับทำ Stemmer
เราใช้ NLTK ในการทำ Stemmer โดยเราเขียนฟังก์ชันชื่อ prepro ไว้
from nltk.stem.porter import PorterStemmer
stemmer = PorterStemmer()
from nltk.stem import WordNetLemmatizer 
lemmatizer = WordNetLemmatizer()
from nltk.corpus import stopwords
st = stopwords.words('english')
from nltk.stem.snowball import SnowballStemmer

snowBallStemmer = SnowballStemmer("english", ignore_stopwords=True)
from nltk.stem.lancaster import *

lancasterStemmer = LancasterStemmer()
def prepro(listword:list)->list:
    temp= [stemmer.stem(i) for i in listword]
    sn = [snowBallStemmer.stem(word) for word in temp]
    la = [lancasterStemmer.stem(i) for i in sn]
    return [i for i in listword]

ต่อไปเรามาเขียนฟังก์ชันสำหรับแปลคำกับชนิดคำ ภาษาอังกฤษเป็นภาษาไทยกัน
แปลคำตามชนิดคำจากภาษาอังกฤษเป็นภาษาไทย
สมมติ เรามีประโยค
sent = "I love you."
เราเอามาแปลงเป็นตัวพิมพ์เล็กทั้งหมด
sent = sent.lower()
จากนั้นนำมาตัดคำด้วยคำสั่ง
import nltk
token = nltk.word_tokenize(sent)
แล้วนำมาหา part of speech โดยเราจะใช้ Universal Part-of-Speech Tagset เพื่อให้ง่ายต่อการเทียบ part of speech ในการหาคำแปลตามชนิดของคำจากฐานข้อมูลพจนานุกรมคำศัพท์ภาษาอังกฤษ - ภาษาไทย
tag = pos_tag(token,tagset='universal')
เนื่องจาก part of speech ของ Universal Part-of-Speech Tagset กับ LEXiTRON มีค่าไม่ตรงกัน เราจึงต้องเขียนตัวแปลจาก part of speech ของ Universal Part-of-Speech Tagset ให้เป็น part of speech ของ LEXiTRON เพื่อให้เวลาค้นหาคำ เจอคำแปลที่ตรงกับไวยากรณ์ที่เขียน

เราสร้าง DICT สำหรับเทียบ part of speech ของ Universal Part-of-Speech Tagset กับ part of speech ของ LEXiTRON ขึ้นมา
tag2lexitron = {
    "ADJ" : "ADJ",
    "ADP" : "PREP",
    "ADV" : "ADV",
    "AUX" : "AUX",
    "CCONJ" : "CONJ",
    "DET" : "DET",
    "INTJ" : "INT",
    "NOUN" : "N",
    "NUM" : "",
    "PART" : "",
    "PRON" : "PRON",
    "PROPN" : "",
    "PUNCT" : "",
    "SCONJ" : "CONJ",
    "SYM" : "",
    "VERB" : "VERB", # VT VI
    "X" : "",
    "." : "",
    "CONJ" : "CONJ",
    "PRT":"PREP"
}

จากนั้น เขียนฟังก์ชันแปลกัน
def translate(sent:str)->list:
  sent = sent.lower()
  token = nltk.word_tokenize(sent)
  tag = [(i[0],tag2lexitron[i[1]]) for i in nltk.pos_tag(token,tagset='universal')]
  thai = [word2thai(i[0],i[1]) for i in tag]
  print(tag)
  print(thai)
  return thai

ลองเรียกใช้
print(translate("my phone"))
ผลลัพธ์
[('my', 'PRON'), ('phone', 'N')]
['ของฉัน', 'ูโทรศัพท์']
['ของฉัน', 'ูโทรศัพท์']


ตอนนี้เราสามารถแปลภาษาแบบคำเทียบคำได้แล้ว แต่พอลองประโยคแบบนี้
print(translate("I lost phone."))
ผลลัพธ์
[('i', 'N'), ('lost', 'VERB'), ('phone', 'N'), ('.', '')]
[None, None, 'ูโทรศัพท์', None]
[None, None, 'ูโทรศัพท์', None]


จะเห็นที่เป็น None เพราะค้นจาก lexitron.db ไม่เจอ เราจึงต้องเขียนพจนานุกรมเพิ่มเติมใส่ลงไปนอกจากเหนือจากของ LEXiTRON
MY_DICT = {
    "i,N" : "ฉัน",
    "lost,VERB" : "สูญเสีย"
}

และปรับปรุงฟังก์ชัน word2thai ได้ดังนี้
def word2thai(word:str,tag:str)->str:
  word = word.lower()
  if word+","+tag in list(MY_DICT.keys()): # ถ้ามีคำแปลและ postag นี้ใน dict
    return MY_DICT[word+","+tag]
  elif word == '.':
    return ''
  elif tag == '':
    find = c.execute("SELECT * FROM eng2thai WHERE esearch='%s'" % (word))
  else:
    find = c.execute("SELECT * FROM eng2thai WHERE esearch='%s' and ecat='%s'" % (word,tag))
  for row in find:
    if '(' not in row[3] and 'คำ' not in row[3]:
      return row[3]

ลอง
print(translate("I lost phone."))
ผลลัพธ์
[('i', 'N'), ('lost', 'VERB'), ('phone', 'N'), ('.', '')]
['ฉัน', 'สูญเสีย', 'ูโทรศัพท์', '']
['ฉัน', 'สูญเสีย', 'ูโทรศัพท์', '']


ลองเล่น CoLab ส่วนแปลคำต่อคำ ได้ที่ https://colab.research.google.com/drive/1_hjtUxlVRWwJdK4QhlxwnwWTVDApCHsl


ส่วนที่ 2 แปลภาษาตามกฎไวยากรณ์

พอเรามาลองประโยคที่ซับซ้อนขึ้นมา
print(translate("I lost my phone."))
ผลลัพธ์
[('i', 'N'), ('lost', 'VERB'), ('my', 'PRON'), ('phone', 'N'), ('.', '')]
['ฉัน', 'สูญเสีย', 'ของฉัน', 'ูโทรศัพท์', '']
['ฉัน', 'สูญเสีย', 'ของฉัน', 'ูโทรศัพท์', '']


จะเห็นได้ว่า การแปลภาษาแบบคำต่อคำ ไม่สามารถแปลให้เข้าใจได้เมื่อประโยคมีความซับซ้อยขึ้น จึงต้องเขียนกฎเทียบไวยากรณ์ภาษาอังกฤษกับภาษาไทยเพิ่มขึ้นมา


ก่อนอื่น เรามาเทียบไวยากรณ์ที่ได้จากประโยค I lost my phone. กัน
I lost my phone.
N VERB PRON N
ฉัน สูญเสีย ของฉัน โทรศัพท์
แปลให้คำแปลออกมาเข้าใจง่ายตามภาษาไทยจะได้
N VERB N PRON
ฉัน สูญเสีย โทรศัพท์ ของฉัน
ดังนั้น "N VERB PRON N" ควรจัดเรียงใหม่เป็น "N VERB N PRON" เพื่อให้ประโยคภาษาอังกฤษที่แปลออกมาอ่านเข้าใจในภาษาไทย

ส่วนโค้ด เราจะเทียบไวยากรณ์อย่างไร หลาย ๆ คนคงนึกถึง CFG เทียบ Tree ไวยากรณ์ แต่บทความนี้เราเน้นความง่ายเป็นหลัก โดยใช้การเทียบไวยากรณ์กับไวยากรณ์เลย

พอได้ไวยากรณ์ที่เทียบกันแล้วเราจึงเขียนกฎขึ้นมาได้ดังนี้
rule = {
 "N VERB PRON N2" : "N VERB N2 PRON" # N2 คือ Noun ตัวที่ 2
}

เขียนนำไวยากรณ์ไปเทียบ การทำงาน คือ นำ part of speech ออกมาเป็น str แล้วใช้ rule เทียบ แล้วส่งไวยากรณ์ที่จัดเรียงไว้ที่ตรงกับกฎไวยากรณ์ออกมาจัดเรียงประโยคอีกครั้ง
def sent2thai(listsent:list)->list: # จัดเรียงประโยคภาษาอังกฤษ -> ภาษาไทย [(คำ , ชนิดของคำ),...]
  i_d = {}
  g = []
  d = {}

  for i in listsent:
    #print(i)
    word = i[0]
    tag = i[1]
    temp=""
    if tag not in list(i_d.keys()):
      temp = tag
      i_d[tag]=2
    else:
      temp = tag + str(i_d[tag])
      i_d[tag]+=1
    d[temp] = word
    g.append(temp)
  g=' '.join(g)
  if g in list(rule.keys()):
    g = rule[g]
  listnew = g.split(' ')
  sent = [d[i] for i in listnew]
  return sent

ไปปรับแต่ง translate โดยเปลี่ยนแปลงโค้ดเป็น
def translate(sent:str)->list:
  sent = sent.lower()
  token = nltk.word_tokenize(sent)
  tag = [(i[0],tag2lexitron[i[1]]) for i in nltk.pos_tag(token,tagset='universal')]
  thai = [(word2thai(i[0],i[1]), i [1]) for i in tag]
  print(tag)
  print(thai)
  return sent2thai(thai)

ลองเรียกใช้งาน
print(translate("I lost my phone"))
ผลลัพธ์
[('i', 'N'), ('lost', 'VERB'), ('my', 'PRON'), ('phone', 'N')]
[('ฉัน', 'N'), ('สูญเสีย', 'VERB'), ('ของฉัน', 'PRON'), ('ูโทรศัพท์', 'N')]
N VERB PRON N2
['ฉัน', 'สูญเสีย', 'ูโทรศัพท์', 'ของฉัน']

เรียบร้อย ประโยคที่เราแปลสามารถจัดเรียงคำแปลตามกฎไวยากรณ์ได้อย่างสวยงาม

ลองเล่น CoLab ส่วนที่ 2 แปลภาษาตามกฎไวยากรณ์ ได้ที่ https://colab.research.google.com/drive/1xO9Tt21U06vacyjSn9RSJx03dn9O9daP

เนื่องจากภาษามีกฎไวยากรณ์เยอะมาก ๆ และข้อยกเว้นมากมาย เช่น สำนวนต่าง ๆ เป็นต้น ยากที่จะเขียนกฎไวยากรณ์แปลได้ครบทุกไวยากรณ์และครบทุกกฎ การแปลภาษาด้วยเครื่องทางสถิติ (statistical machine translation) จึงได้รับความนิยมในยุคต่อมา เนื่องจากเราไม่ต้องมาเขียนไวยากรณ์เทียบแบบนี้
สำนวน "raining cats and dogs" เป็นอีกข้อยกเว้น หากเราจะทำโปรแกรมแปลภาษาอังกฤษเป็นภาษาไทยด้วยกฎไวยากรณ์
สำนวน "raining cats and dogs" เป็นอีกข้อยกเว้น หากเราจะทำโปรแกรมแปลภาษาอังกฤษเป็นภาษาไทยด้วยกฎไวยากรณ์ ที่มารูป : File:George Cruikshank - Very unpleasant Weather.jpg
ToDo สำหรับผู้อ่าน
  • ปรับปรุงวิธีการแปลให้การแปลดียิ่งขึ้น
  • ปรับปรุงการประมวลผลคำก่อนนำไปแปล เช่น ตัด -ing หรือ -ed ทิ้ง, แปลคำกริยาให้เป็นช่องที่ 1 เป็นต้น
  • ปรับปรุงให้รองรับการแปลสำนวน
  • เปลี่ยนเป็นแปลจากภาษาไทยเป็นภาษาอังกฤษ (ยาก)
เขียนโดย นาย วรรณพงษ์ ภัททิยไพบูลย์
นักศึกษาสาขาวิทยาการคอมพิวเตอร์และสารสนเทศ
คณะวิทยาศาสตร์ประยุกต์และวิศวกรรมศาสตร์
มหาวิทยาลัยขอนแก่น วิทยาเขตหนองคาย

0 ความคิดเห็น:

แสดงความคิดเห็น

แสดงความคิดเห็นได้ครับ :)