Thêm dấu tiếng việt cho câu không dấu

- Phạm Duy Tùng
Ở bài viết này, chúng ta sẽ đề cập đến một bài toán hết sức thú vị trong xử lý ngôn ngữ tự nhiên, đó là thêm dấu tiếng việt. Các bạn thử đoán xem câu 'me noi rang em rat dam dang' khi thêm dấu sẽ ra như thế nào?.

Thêm dấu tiếng việt là một trong những bài toán khá hay trong xử lý ngôn ngữ tự nhiên. Ở đây, mình đã tiến hành thu thập dữ liệu bài báo của nhiều nguồn khác nhau như zing.vn, vnexpress, kenh14.vn … làm kho ngữ liệu và xây dựng mô hình.

Để tiến hành thực nghiệm, mình sẽ lấy một số đoạn văn mẫu ở trang tin tức của thế giới di động (https.www.thegioididong.com) (mình không crawl nội dung tin tức ở trang này làm dữ liệu học).

Ở bài viết link https://www.thegioididong.com/tin-tuc/3-ngay-cuoi-tuan-mua-laptop-online-tang-them-pmh-den-400k-tra-gop-0--1151334, mình lấy đoạn mở đầu “Từ ngày 153 đến 173, nhiều mẫu laptop tại Thế Giới Di Động sẽ được ưu đãi mạnh, tặng phiếu mua hàng đến 400 ngàn đồng, trả góp 0% và nhiều quà tặng hấp dẫn khác khi mua theo hình thức ONLINE. Nếu đang có nhu cầu mua laptop, bạn hãy nhanh chóng xem qua danh sách sản phẩm dưới đây nhé.”, bỏ dấu của câu đi, thì mình được câu

“Tu ngay 153 den 173, nhieu mau laptop tai The Gioi Di Dong se duoc uu dai manh, tang phieu mua hang den 400 ngan dong, tra gop 0% va nhieu qua tang hap dan khac khi mua theo hinh thuc ONLINE. Neu dang co nhu cau mua laptop, ban hay nhanh chong xem qua danh sach san pham duoi day nhe.”

Sử dụng mô hình mình đã huấn luyện, thu được kết quả như sau:

“Từ ngày 153 đến 173 m t m, nhiều mẫu laptoP tạI thế giỚi di động sẽ được ưu đãi mạnh, tang phiếu mua hàng đến 400 ngàn đồng, trả góp 0 r% và nhiều quà tặng hấp dẫn khác khi mua theo hìNH THỨc Onfine. nếu đang có nhu cầu mua laptop, bạn hãy nhanh chóng xem qua danh sách sản phẩm dưới”

Kết quả khá khả quan phải không các bạn, còn một số lỗi nhỏ ở phần nhận dạng ký tự hoa nữa. Mình sẽ fix lại ở các bài viết sau.

Mình thí nghiệm tiếp với phần đầu bài viết https://www.thegioididong.com/tin-tuc/apple-ban-ra-thi-truong-35-trieu-cap-tai-nghe-airpods-nam-2018-1155181. Đoạn “Hôm nay, báo cáo của Counterpoint Research cho thấy, trong năm 2018 Apple đã bán được khoảng 35 triệu cặp tai nghe không dây AirPods. Theo hãng phân tích này, AirPods hiện là tai nghe không dây phổ biến nhất.”, bỏ dấu tiếng việt là thu được “Hom nay, bao cao cua Counterpoint Research cho thay, trong nam 2018 Apple da ban duoc khoang 35 trieu cap tai nghe khong day AirPods. Theo hang phan tich nay, AirPods hien la tai nghe khong day pho bien nhat.”

Kết quả của mô hình: “Hôm nay, bạo cáo của Coorteenria eEeeroa c ttt, trong năm 2018 apple đã bán được khoảng 35 triệu cặp tại nghe không đầy aitcoDs. theo Hàng phân tích này, airxoDs Hiện là tai nghe không dạy phổ biến nhất.”

Mô hình của mình cho lặp 50 lần. Mình tiến hành thí nghiệm và publish mô hình ở lần lặp thứ 10.

Mã nguồn file predict

from keras.models import load_model
model = load_model('a_best_weight.h5')

from collections import Counter

import numpy as np

import utils
import string
import re

alphabet = set('\x00 _' + string.ascii_lowercase + string.digits + ''.join(utils.ACCENTED_TO_BASE_CHAR_MAP.keys()))

print("alphabet",alphabet)
codec = utils.CharacterCodec(alphabet, utils.MAXLEN)

def guess(ngram):
    text = ' '.join(ngram)
    text += '\x00' * (utils.MAXLEN - len(text))
    if utils.INVERT:
        text = text[::-1]
    preds = model.predict_classes(np.array([codec.encode(text)]), verbose=0)
    rtext = codec.decode(preds[0], calc_argmax=False).strip('\x00')
    if len(rtext)>0:
        index = rtext.find('\x00')
        if index>-1:
            rtext = rtext[:index]
    return rtext


def add_accent(text):
    # lowercase the input text as we train the model on lowercase text only
    # but we keep the map of uppercase characters to restore cases in output
    is_uppercase_map = [c.isupper() for c in text]
    text = utils.remove_accent(text.lower())

    outputs = []
    words_or_symbols_list = re.findall('\w[\w ]*|\W+', text)

    # print(words_or_symbols_list)

    for words_or_symbols in words_or_symbols_list:
        if utils.is_words(words_or_symbols):
            outputs.append(_add_accent(words_or_symbols))
        else:
            outputs.append(words_or_symbols)
        # print(outputs)
    output_text = ''.join(outputs)

    # restore uppercase characters
    output_text = ''.join(c.upper() if is_upper else c
                            for c, is_upper in zip(output_text, is_uppercase_map))
    return output_text

def _add_accent(phrase):
    grams = list(utils.gen_ngram(phrase.lower(), n=utils.NGRAM, pad_words=utils.PAD_WORDS_INPUT))
    
    guessed_grams = list(guess(gram) for gram in grams)
    # print("phrase",phrase,'grams',grams,'guessed_grams',guessed_grams)
    candidates = [Counter() for _ in range(len(guessed_grams) + utils.NGRAM - 1)]
    for idx, gram in enumerate(guessed_grams):
        for wid, word in enumerate(re.split(' +', gram)):
            candidates[idx + wid].update([word])
    output = ' '.join(c.most_common(1)[0][0] for c in candidates if c)
    return output.strip('\x00 ')



# print(add_accent('do,'))
# print(add_accent('7.3 inch,'))
# print(add_accent('Truoc do, tren san khau su kien SDC 2018, giam doc cao cap mang marketing san pham di dong cua Samsung, ong Justin Denison da cam tren tay nguyen mau cua thiet bi nay. Ve co ban, no chang khac gi mot chiec may tinh bang 7.3 inch, duoc cau thanh tu nhieu lop phu khac nhau nhu polyme, lop man chong soc, lop phan cuc voi do mong gan mot nua so voi the he truoc, lop kinh linh hoat va mot tam lung da nang co the bien thanh man hinh. Tat ca se duoc ket dinh bang mot loai keo cuc ben, cho phep chiec may nay co the gap lai hang tram ngan lan ma khong bi hu hong.'))
# print(add_accent('man hinh. Tat ca se duoc ket dinh bang mot loai keo cuc ben, cho phep chiec may nay co the gap lai hang tram ngan lan ma khong bi hu hong.'))
print(add_accent('Hom nay, bao cao cua Counterpoint Research cho thay, trong nam 2018 Apple da ban duoc khoang 35 trieu cap tai nghe khong day AirPods. Theo hang phan tich nay, AirPods hien la tai nghe khong day pho bien nhat.'))

Mã nguồn file utils

import re
import string
import time
from contextlib import contextmanager
import numpy as np



# maximum string length to train and predict
# this is set based on our ngram length break down below
MAXLEN = 32

# minimum string length to consider
MINLEN = 3

# how many words per ngram to consider in our model
NGRAM = 5

# inverting the input generally help with accuracy
INVERT = True

# mini batch size
BATCH_SIZE = 128

# number of phrases set apart from training set to validate our model
VALIDATION_SIZE = 100000

# using g2.2xl GPU is ~5x faster than a Macbook Pro Core i5 CPU
HAS_GPU = True

PAD_WORDS_INPUT  = True

### Ánh xạ từ không dấu sang có dấu

ACCENTED_CHARS = {
    'a': u'a á à ả ã ạ â ấ ầ ẩ ẫ ậ ă ắ ằ ẳ ẵ ặ',
    'o': u'o ó ò ỏ õ ọ ô ố ồ ổ ỗ ộ ơ ớ ờ ở ỡ ợ',
    'e': u'e é è ẻ ẽ ẹ ê ế ề ể ễ ệ',
    'u': u'u ú ù ủ ũ ụ ư ứ ừ ử ữ ự',
    'i': u'i í ì ỉ ĩ ị',
    'y': u'y ý ỳ ỷ ỹ ỵ',
    'd': u'd đ',
}

### Ánh xạ từ có dấu sang không dấu
ACCENTED_TO_BASE_CHAR_MAP = {}
for c, variants in ACCENTED_CHARS.items():
    for v in variants.split(' '):
        ACCENTED_TO_BASE_CHAR_MAP[v] = c

# \x00 ký tự padding

### Những ký tự cơ bản, bao gồm ký tự padding, các chữ cái và các chữ số
BASE_ALPHABET = set('\x00 _' + string.ascii_lowercase + string.digits)

### Bộ ký tự bao gồm những ký tự cơ bản và những ký tự có dấu
ALPHABET = BASE_ALPHABET.union(set(''.join(ACCENTED_TO_BASE_CHAR_MAP.keys())))


def is_words(text):
    return re.fullmatch('\w[\w ]*', text)

# Hàm bỏ dấu khỏi một câu
def remove_accent(text):
    """ remove accent from text """
    return u''.join(ACCENTED_TO_BASE_CHAR_MAP.get(char, char) for char in text)

#hàm thêm padding vào một câu
def pad(phrase, maxlen):
    """ right pad given string with \x00 to exact "maxlen" length """
    return phrase + u'\x00' * (maxlen - len(phrase))


def gen_ngram(words, n=3, pad_words=True):
    """ gen n-grams from given phrase or list of words """
    if isinstance(words, str):
        words = re.split('\s+', words.strip())

    if len(words) < n:
        if pad_words:
            words += ['\x00'] * (n - len(words))
        yield tuple(words)
    else:
        for i in range(len(words) - n + 1):
            yield tuple(words[i: i + n])

def extract_phrases(text):
    """ extract phrases, i.e. group of continuous words, from text """
    return re.findall(r'\w[\w ]+', text, re.UNICODE)


@contextmanager
def timing(label):
    begin = time.monotonic()
    print(label, end='', flush=True)
    try:
        yield
    finally:
        duration = time.monotonic() - begin
    print(': took {:.2f}s'.format(duration))

class CharacterCodec(object):
    def __init__(self, alphabet, maxlen):
        self.alphabet = list(sorted(set(alphabet)))
        self.index_alphabet = dict((c, i) for i, c in enumerate(self.alphabet))
        self.maxlen = maxlen

    def encode(self, C, maxlen=None):
        maxlen = maxlen if maxlen else self.maxlen
        X = np.zeros((maxlen, len(self.alphabet)))
        for i, c in enumerate(C[:maxlen]):
            X[i, self.index_alphabet[c]] = 1
        return X

    def try_encode(self, C, maxlen=None):
        try:
            return self.encode(C, maxlen)
        except KeyError:
            return None

    def decode(self, X, calc_argmax=True):
        if calc_argmax:
            X = X.argmax(axis=-1)
        return ''.join(self.alphabet[x] for x in X)

link donwnload mô hình ở lần lặp thứ 10 ở https://github.com/AlexBlack2202/alexmodel/blob/master/a_best_weight.h5?raw=true

À, kết quả của câu nói phần mở đầu là “mẹ nói rằng em rất đậm đang”. Hi hi, may quá.

Cảm ơn các bạn đã theo dõi. Hẹn gặp bạn ở các bài viết tiếp theo.


Bài viết khác
comments powered by Disqus