Phân loại văn bản tiếng Việt sử dụng machine learning

Data Science, Natural Language Processing

Phân loại văn bản (Text classification) là một bài toán phổ biến trong xử lý ngôn ngữ tự nhiên (Nature language processing). Đối với phân loại văn bản tiếng Việt, sẽ có đôi chút khác biệt so với phân loại văn bản tiếng anh. Trong bài viết này, Lập Trình Không Khó (LTKK) sẽ hướng dẫn bạn chi tiết, từng bước quá trình xây dựng một số mô hình phân loại văn bản tiếng Việt. Bài viết sẽ đặc biệt tập trung vào quá trình tiền xử lý dữ liệu tiếng Việt làm sao để có được một mô hình phân loại đủ tốt.

Sau khi đọc xong bài viết này, bạn sẽ nắm được một số kiến thức như sau:

  1. Nắm được cấu trúc tổng quát của một mô hình phân loại văn bản (Text classification)
  2. Quy trình chi tiết cách xây dựng một hệ thống phân loại văn bản
  3. Các công việc cần làm khi tiền xử lý dữ liệu tiếng Việt
  4. Thử nghiệm và đánh giá trên một số mô hình phân loại khác nhau.

Bài toán phân loại văn bản

Phân loại văn bản (Text Classification) là bài toán thuộc nhóm học có giám sát (Supervised learning) trong học máy. Bài toán này yêu cầu dữ liệu cần có nhãn (label). Mô hình sẽ học từ dữ liệu có nhãn đó, sau đó được dùng để dự đoán nhãn cho các dữ liệu mới mà mô hình chưa gặp.

Lấy ví dụ, bạn cần xây dựng một mô hình học máy để dự đoán chủ đề (Kinh tế, Xã hội, Thể thao,…) của một bài báo bất kỳ. Khi đó, bạn cần rất nhiều dữ liệu có gán nhãn; tức là bạn cần rất nhiều bài báo mà mỗi bài báo đó chúng ta phải biết trước nó nằm trong chủ đề nào rồi. Vấn đề dữ liệu có lẽ là vấn đề nan giải nhất của mô hình học giám sát này ^^.

Hình ảnh dưới đây sẽ cho chúng ta cái nhìn tổng quát về cách hoạt động của một bài toán phân loại văn bản:

Mô hình phân loại văn bản tiếng Việt tự động với Machine learning
Mô hình phân loại văn bản tiếng Việt tự động với Machine learning (credit: MonkeyLearn.com)
  • Giai đoạn (a): Huấn luyện (training) là giai đoạn học tập của mô hình phân loại văn bản. Ở bước này, mô hình sẽ học từ dữ liệu có nhãn (trong ảnh trên nhãn là Possitive, Negative, Neutral). Dữ liệu văn bản sẽ được số hóa thông qua bộ trích xuất đặc trưng (feature extractor) để mỗi mẫu dữ liệu trong tập huấn luyện trở thành 1 vector nhiều chiều (đặc trưng). Thuật toán máy học sẽ học và tối ưu các tham số để đạt được kết quả tốt trên tập dữ liệu này. Nhãn của dữ liệu được dùng để đánh giá việc mô hình học tốt không và dựa vào đó để tối ưu.
  • Giai đoạn (b): Dự đoán (prediction), là giai đoạn sử dụng mô hình học máy sau khi nó đã học xong. Ở giai đoạn này, dữ liệu cần dự đoán cũng vẫn thực hiện các bước trích xuất đặc trưng. Mô hình đã học sau đó nhận đầu vào là đặc trưng đó và đưa ra kết quả dự đoán.

Tiếp sau đây, chúng ta sẽ đi vào từng bước của quá trình xây dựng một mô hình phân loại văn bản tin tức tiếng Việt sau:

Bài toán: Xây dựng mô hình phân loại văn bản tin tức tiếng Việt cho trang báo điện tử X. Mỗi khi một bài báo được đăng, chương trình cần phải tự động xác định được bài báo đó nằm trong danh mục nào. Các danh mục gồm có: Kinh tế, Thể thao, Giáo dục, Sức khỏe, Du lịch, Pháp luật,…

Chuẩn bị dữ liệu

Dữ liệu là yếu tố quan trọng nhất và cũng là vấn đề mà chúng ta cần quan tâm nhất. Trong quá trình xây dựng một hệ thống phân loại văn bản, bước chuẩn bị và tiền xử lý dữ liệu quyết định tới thành bại của hệ thống hơn cả.

Với bài toán phân loại văn bản tin tức tiếng Việt, dữ liệu bạn cần chuẩn bị là dữ liệu các bài báo tiếng Việt kèm theo chủ đề của bài báo đó. Loại dữ liệu này nhìn chung là rất dễ kiếm, bởi vì có vô số website tin tức mỗi ngày đăng vài chục tin cơ mà.

Nếu bạn quan tâm tới việc thu thập dữ liệu tự động, có thể bạn nên đọc bài viết này:

Đọc thêm: Thu thập dữ liệu trang tin tức bất kỳ chỉ với 5 dòng code


Trong trường hợp bạn muốn có sẵn dữ liệu, bạn có thể tham khảo 1 trong 2 nguồn dữ liệu tin tức dưới đây:

  1. binhvq/news-corpus, lưu ý tải bản 76GB của tác giả (định dạng mongodb dump)
  2. duyvuleo/VNTC

Đây là các nguồn dữ liệu có kích thước tương đối lớn có thể dùng để thực hành bài toán phân loại văn bản. Các bạn sau khi tại về có thể xử lý và loại bỏ các thành phần không sử dụng trong dữ liệu. Chỉ giữ lại nội dung bài báo và chủ đề bài báo – 2 thành phần mà chúng ta quan tâm trong bài toán này.

Bước tiếp theo, chúng ta sẽ tiến hành tiền xử lý dữ liệu trước khi đưa vào huấn luyện mô hình phân loại văn bản. Việc tiền xử lý dữ liệu là hết sức quan trọng để đảm bảo mô hình đạt được kết quả tốt.

Tiền xử lý dữ liệu

Bước tiền xử lý dữ liệu là bước đầu tiên cần làm, nó cần được làm trước bước feature extractor. Việc tiền sử lý dữ liệu là quá trình chuẩn hóa dữ liệu và loại bỏ các thành phần không có ý nghĩa cho việc phân loại văn bản.

Tiền xử lý dữ liệu tiếng Việt cho bài toán phân loại văn bản thường gồm các việc sau:

  • Xóa HTML code (nếu có)
  • Chuẩn hóa bảng mã Unicode (đưa về Unicode tổ hợp dựng sẵn)
  • Chuẩn hóa kiểu gõ dấu tiếng Việt (dùng òa úy thay cho oà uý)
  • Thực hiện tách từ tiếng Việt (sử dụng thư viện tách từ như pyvi, underthesea, vncorenlp,…)
  • đưa về văn bản lower (viết thường)
  • Xóa các ký tự đặc biệt: “.”, “,”, “;”, “)”, …

Nếu bạn chưa từng xử lý dữ liệu văn bản với Python, bạn nên đọc trước 2 bài viết dưới đây trước khi tiếp tục:

  1. Xử lý tiếng Việt trong Python (NLP)
  2. 20+ linux command hữu ích dành cho Data Scientist

Xóa HTML code trong dữ liệu

Dữ liệu được thu thập từ các website đôi khi vẫn còn sót lại các đoạn mã HTML. Các mã HTML code này là rác, chẳng những không có tác dụng cho việc phân loại mà còn làm kết quả phân loại văn bản bị kém đi.

Việc xóa các HTML code này cũng khá đơn giản, chúng ta có thể sử dụng regex trong Python để xóa đi một cách đơn giản:  

def remove_html(txt):
    return re.sub(r'<[^>]*>', '', txt)
 
txt = "<p class=\"par\">This is an example</p>"
remove_html(txt)
>>> 'This is an example'

Chuẩn hóa Unicode tiếng Việt

Hiện nay, có 2 loại mã Unicode được sử dụng phổ biến, Unicode tổ hợp và Unicode dựng sẵn. Điều đó dẫn tới vấn đề sau đây:

>>> 'hiếu' == 'hiếu'
True
>>> 'hiếu' == 'hiếu'
False

Hướng xử lý: Đưa về 1 chuẩn Unicode dựng sẵn (thằng này phổ biến hơn). Chi tiết cách xử lý thì mình đã trình bày ở bài Xử lý tiếng Việt trong NLP đã nhắc ở trên.

import regex as re
 
uniChars = "àáảãạâầấẩẫậăằắẳẵặèéẻẽẹêềếểễệđìíỉĩịòóỏõọôồốổỗộơờớởỡợùúủũụưừứửữựỳýỷỹỵÀÁẢÃẠÂẦẤẨẪẬĂẰẮẲẴẶÈÉẺẼẸÊỀẾỂỄỆĐÌÍỈĨỊÒÓỎÕỌÔỒỐỔỖỘƠỜỚỞỠỢÙÚỦŨỤƯỪỨỬỮỰỲÝỶỸỴÂĂĐÔƠƯ"
unsignChars = "aaaaaaaaaaaaaaaaaeeeeeeeeeeediiiiiooooooooooooooooouuuuuuuuuuuyyyyyAAAAAAAAAAAAAAAAAEEEEEEEEEEEDIIIOOOOOOOOOOOOOOOOOOOUUUUUUUUUUUYYYYYAADOOU"
 
 
def loaddicchar():
    dic = {}
    char1252 = 'à|á|ả|ã|ạ|ầ|ấ|ẩ|ẫ|ậ|ằ|ắ|ẳ|ẵ|ặ|è|é|ẻ|ẽ|ẹ|ề|ế|ể|ễ|ệ|ì|í|ỉ|ĩ|ị|ò|ó|ỏ|õ|ọ|ồ|ố|ổ|ỗ|ộ|ờ|ớ|ở|ỡ|ợ|ù|ú|ủ|ũ|ụ|ừ|ứ|ử|ữ|ự|ỳ|ý|ỷ|ỹ|ỵ|À|Á|Ả|Ã|Ạ|Ầ|Ấ|Ẩ|Ẫ|Ậ|Ằ|Ắ|Ẳ|Ẵ|Ặ|È|É|Ẻ|Ẽ|Ẹ|Ề|Ế|Ể|Ễ|Ệ|Ì|Í|Ỉ|Ĩ|Ị|Ò|Ó|Ỏ|Õ|Ọ|Ồ|Ố|Ổ|Ỗ|Ộ|Ờ|Ớ|Ở|Ỡ|Ợ|Ù|Ú|Ủ|Ũ|Ụ|Ừ|Ứ|Ử|Ữ|Ự|Ỳ|Ý|Ỷ|Ỹ|Ỵ'.split(
        '|')
    charutf8 = "à|á|ả|ã|ạ|ầ|ấ|ẩ|ẫ|ậ|ằ|ắ|ẳ|ẵ|ặ|è|é|ẻ|ẽ|ẹ|ề|ế|ể|ễ|ệ|ì|í|ỉ|ĩ|ị|ò|ó|ỏ|õ|ọ|ồ|ố|ổ|ỗ|ộ|ờ|ớ|ở|ỡ|ợ|ù|ú|ủ|ũ|ụ|ừ|ứ|ử|ữ|ự|ỳ|ý|ỷ|ỹ|ỵ|À|Á|Ả|Ã|Ạ|Ầ|Ấ|Ẩ|Ẫ|Ậ|Ằ|Ắ|Ẳ|Ẵ|Ặ|È|É|Ẻ|Ẽ|Ẹ|Ề|Ế|Ể|Ễ|Ệ|Ì|Í|Ỉ|Ĩ|Ị|Ò|Ó|Ỏ|Õ|Ọ|Ồ|Ố|Ổ|Ỗ|Ộ|Ờ|Ớ|Ở|Ỡ|Ợ|Ù|Ú|Ủ|Ũ|Ụ|Ừ|Ứ|Ử|Ữ|Ự|Ỳ|Ý|Ỷ|Ỹ|Ỵ".split(
        '|')
    for i in range(len(char1252)):
        dic[char1252[i]] = charutf8[i]
    return dic
 
 
dicchar = loaddicchar()
 
# Đưa toàn bộ dữ liệu qua hàm này để chuẩn hóa lại
def covert_unicode(txt):
    return re.sub(
        r'à|á|ả|ã|ạ|ầ|ấ|ẩ|ẫ|ậ|ằ|ắ|ẳ|ẵ|ặ|è|é|ẻ|ẽ|ẹ|ề|ế|ể|ễ|ệ|ì|í|ỉ|ĩ|ị|ò|ó|ỏ|õ|ọ|ồ|ố|ổ|ỗ|ộ|ờ|ớ|ở|ỡ|ợ|ù|ú|ủ|ũ|ụ|ừ|ứ|ử|ữ|ự|ỳ|ý|ỷ|ỹ|ỵ|À|Á|Ả|Ã|Ạ|Ầ|Ấ|Ẩ|Ẫ|Ậ|Ằ|Ắ|Ẳ|Ẵ|Ặ|È|É|Ẻ|Ẽ|Ẹ|Ề|Ế|Ể|Ễ|Ệ|Ì|Í|Ỉ|Ĩ|Ị|Ò|Ó|Ỏ|Õ|Ọ|Ồ|Ố|Ổ|Ỗ|Ộ|Ờ|Ớ|Ở|Ỡ|Ợ|Ù|Ú|Ủ|Ũ|Ụ|Ừ|Ứ|Ử|Ữ|Ự|Ỳ|Ý|Ỷ|Ỹ|Ỵ',
        lambda x: dicchar[x.group()], txt)

Nếu bạn không xử lý vấn đề này, khi đưa vào mô hình học máy tính sẽ hiểu đó là các từ khác nhau mặc dù ta đang nhìn thấy chúng chẳng khác nhau gì.

Chuấn hóa kiểu gõ dấu

Kiểu gõ dấu khác nhau thì bạn nhìn mắt thường cũng sẽ thấy được sự khác nhau: òa với  lần lượt là kiểu gõ cũ (phổ biến hơn) và kiểu gõ mới. Chi tiết về vấn đề này bạn có thể tham khảo tại Wikipedia.

Script dưới đây sẽ giúp chúng ta chuẩn hóa lại dữ liệu về kiểu gõ dấu cũ:

def chuan_hoa_dau_tu_tieng_viet(word):
    if not is_valid_vietnam_word(word):
        return word
 
    chars = list(word)
    dau_cau = 0
    nguyen_am_index = []
    qu_or_gi = False
    for index, char in enumerate(chars):
        x, y = nguyen_am_to_ids.get(char, (-1, -1))
        if x == -1:
            continue
        elif x == 9:  # check qu
            if index != 0 and chars[index - 1] == 'q':
                chars[index] = 'u'
                qu_or_gi = True
        elif x == 5:  # check gi
            if index != 0 and chars[index - 1] == 'g':
                chars[index] = 'i'
                qu_or_gi = True
        if y != 0:
            dau_cau = y
            chars[index] = bang_nguyen_am[x][0]
        if not qu_or_gi or index != 1:
            nguyen_am_index.append(index)
    if len(nguyen_am_index) < 2:
        if qu_or_gi:
            if len(chars) == 2:
                x, y = nguyen_am_to_ids.get(chars[1])
                chars[1] = bang_nguyen_am[x][dau_cau]
            else:
                x, y = nguyen_am_to_ids.get(chars[2], (-1, -1))
                if x != -1:
                    chars[2] = bang_nguyen_am[x][dau_cau]
                else:
                    chars[1] = bang_nguyen_am[5][dau_cau] if chars[1] == 'i' else bang_nguyen_am[9][dau_cau]
            return ''.join(chars)
        return word
 
    for index in nguyen_am_index:
        x, y = nguyen_am_to_ids[chars[index]]
        if x == 4 or x == 8:  # ê, ơ
            chars[index] = bang_nguyen_am[x][dau_cau]
            # for index2 in nguyen_am_index:
            #     if index2 != index:
            #         x, y = nguyen_am_to_ids[chars[index]]
            #         chars[index2] = bang_nguyen_am[x][0]
            return ''.join(chars)
 
    if len(nguyen_am_index) == 2:
        if nguyen_am_index[-1] == len(chars) - 1:
            x, y = nguyen_am_to_ids[chars[nguyen_am_index[0]]]
            chars[nguyen_am_index[0]] = bang_nguyen_am[x][dau_cau]
            # x, y = nguyen_am_to_ids[chars[nguyen_am_index[1]]]
            # chars[nguyen_am_index[1]] = bang_nguyen_am[x][0]
        else:
            # x, y = nguyen_am_to_ids[chars[nguyen_am_index[0]]]
            # chars[nguyen_am_index[0]] = bang_nguyen_am[x][0]
            x, y = nguyen_am_to_ids[chars[nguyen_am_index[1]]]
            chars[nguyen_am_index[1]] = bang_nguyen_am[x][dau_cau]
    else:
        # x, y = nguyen_am_to_ids[chars[nguyen_am_index[0]]]
        # chars[nguyen_am_index[0]] = bang_nguyen_am[x][0]
        x, y = nguyen_am_to_ids[chars[nguyen_am_index[1]]]
        chars[nguyen_am_index[1]] = bang_nguyen_am[x][dau_cau]
        # x, y = nguyen_am_to_ids[chars[nguyen_am_index[2]]]
        # chars[nguyen_am_index[2]] = bang_nguyen_am[x][0]
    return ''.join(chars)
 
 
def is_valid_vietnam_word(word):
    chars = list(word)
    nguyen_am_index = -1
    for index, char in enumerate(chars):
        x, y = nguyen_am_to_ids.get(char, (-1, -1))
        if x != -1:
            if nguyen_am_index == -1:
                nguyen_am_index = index
            else:
                if index - nguyen_am_index != 1:
                    return False
                nguyen_am_index = index
    return True
 
 
def chuan_hoa_dau_cau_tieng_viet(sentence):
    """
        Chuyển câu tiếng việt về chuẩn gõ dấu kiểu cũ.
        :param sentence:
        :return:
        """
    sentence = sentence.lower()
    words = sentence.split()
    for index, word in enumerate(words):
        cw = re.sub(r'(^\p{P}*)([p{L}.]*\p{L}+)(\p{P}*$)', r'\1/\2/\3', word).split('/')
        # print(cw)
        if len(cw) == 3:
            cw[1] = chuan_hoa_dau_tu_tieng_viet(cw[1])
        words[index] = ''.join(cw)
    return ' '.join(words)
 
 
"""
    End section: Chuyển câu văn về cách gõ dấu kiểu cũ: dùng òa úy thay oà uý
    Xem tại đây: https://vi.wikipedia.org/wiki/Quy_tắc_đặt_dấu_thanh_trong_chữ_quốc_ngữ
"""
if __name__ == '__main__':
    print(chuan_hoa_dau_cau_tieng_viet('anh hoà, đang làm.. gì'))
 

Tách từ tiếng Việt

Như các bạn đã học ở tiểu học hay trung học gì đó (quên rồi), đơn vị từ trong tiếng Việt bao gồm từ đơn (yêu) và từ ghép (học sinh). Nên chúng ta cần phải nói cho mô hình học máy biết đâu là từ đơn, đâu là từ ghép. Nếu không thì từ nào cũng sẽ là từ đơn hết.

Bởi vì mô hình của chúng ta sẽ coi các từ là đặc trưng, tách nhau theo dấu cách. Do đó, chúng ta phải nối các từ ghép lại thành một từ để không bị tách sai:

Học sinh học sinh học ⇒ Học_sinh học sinh_học

Bài toán này là một bài toán cơ sở trong NLP – bài toán tách từ (word tokenize). Thật may là hiện nay có khá nhiều thư viện mã nguồn mở của bài toán này. Do đó, chúng ta chỉ việc cài đặt và sử dụng.

Với ngôn ngữ Python, bạn có thể lựa chọn underthesea hoặc pyvi nhé.

>>> from underthesea import word_tokenize 
>>> sentence = 'Chàng trai 9X Quảng Trị khởi nghiệp từ nấm sò'   
>>> word_tokenize(sentence) 
['Chàng trai', '9X', 'Quảng Trị', 'khởi nghiệp', 'từ', 'nấm', 'sò']   
>>> word_tokenize(sentence, format="text") 
'Chàng_trai 9X Quảng_Trị khởi_nghiệp từ nấm sò'  

Đưa về viết thường (lowercase)

Việc đưa dữ liệu về chữ viết thường là rất cần thiết. Bởi vì đặc trưng này không có tác dụng ở bài toán phân loại văn bản. Đưa về chữ viết thường giúp giảm số lượng đặc trưng (vì máy tính hiểu hoa thường là 2 từ khác nhau) và tăng độ chính xác hơn cho mô hình.

Xóa các ký tự không cần thiết

Như mình đã nói, tiền xử lý bao gồm việc loại bỏ các dữ liệu không có tác dụng cho việc phân loại văn bản. Việc này giúp:

  • Giảm số chiều đặc trưng, tăng tốc độ học và xử lý
  • Tránh làm ảnh hưởng xấu tới kết quả của mô hình

Các dấu ngắt câu, số đếm và các ký tự đặc biệt khác không giúp bạn phân loại một văn bản thuộc chuyên mục nào. Do đó, chúng ta nên loại bỏ nó đi.

Riêng với số đếm, ngày tháng, email (Các token đặc biệt). Nếu có thể bạn nên đưa nó về các token chung như: <number>, <date>, <email>, … Việc này có thể không giúp ích cho mô hình học tốt hơn nhưng sẽ giúp ích cho bạn trong việc giữ được mạch của dữ liệu.

Và đây là hàm tiền xử lý tổng hợp tất cả các bước ở trên, bạn tham khảo và bổ sung thêm nếu cần:

def text_preprocess(document):
    # xóa html code
    document = remove_html(document)
    # chuẩn hóa unicode
    document = convert_unicode(document)
    # chuẩn hóa cách gõ dấu tiếng Việt
    document = chuan_hoa_dau_cau_tieng_viet(document)
    # tách từ
    document = word_tokenize(document, format="text")
    # đưa về lower
    document = document.lower()
    # xóa các ký tự không cần thiết
    document = re.sub(r'[^\s\wáàảãạăắằẳẵặâấầẩẫậéèẻẽẹêếềểễệóòỏõọôốồổỗộơớờởỡợíìỉĩịúùủũụưứừửữựýỳỷỹỵđ_]',' ',document)
    # xóa khoảng trắng thừa
    document = re.sub(r'\s+', ' ', document).strip()
    return document

Mình thử ví dụ xem nó tiền xử lý dữ liệu mình ra sản phẩm gì nhé.

Dữ liệu gốc, copy đại 1 bài báo trên Vnexpress:

TP HCM phạt người không đeo khẩu trang nơi công cộng Người dân ở thành phố không đeo khẩu trang nơi công cộng sẽ bị xử phạt mức cao nhất 300.000 đồng, từ ngày 5/8.   Yêu cầu này được Chủ tịch UBND thành phố Nguyễn Thành Phong đưa ra tại cuộc họp Ban chỉ đạo phòng chống dịch bệnh Covid-19 của TP HCM chiều 3/8.   Việc xử phạt không đeo khẩu trang nơi công cộng được TP HCM cũng như các địa phương khác thực hiện từ cuối tháng 3 khi Covid-19 bùng phát. Tuy nhiên, sau khi hết thực hiện cách ly xã hội từ ngày 23/4, việc đeo khẩu trang nơi công cộng chỉ dừng lại ở mức khuyến cáo.   Theo Nghị định số 176/2013, người dân không đeo khẩu trang nơi công cộng sẽ bị xử phạt từ 100.000 đến 300.000 đồng. Trong khoảng một tháng áp dụng trước đó, TP HCM đã xử phạt hơn 4.300 trường hợp với gần 870 triệu đồng.   Theo ông Phong, việc đeo khẩu trang đã được khẳng định có thể tránh lây lan dịch bệnh cho người khác và bảo vệ sức khỏe cho người sử dụng. "Sở Công thương phải nắm nguồn cung ứng khẩu trang, chủ động thông báo các điểm bán để người dân dễ dàng mua vì đã xử phạt thì phải bảo đảm đủ nguồn cung", ông Phong nói.   Đội trật tự đô thị phường Bến Nghé, quận 1, xử phạt người không đeo khẩu trang trên phố đi bộ Nguyễn Huệ, chiều 15/4. Ảnh: Quỳnh Trần. Đội trật tự đô thị phường Bến Nghé, quận 1, xử phạt người không đeo khẩu trang trên phố đi bộ Nguyễn Huệ, chiều 15/4. Ảnh: Quỳnh Trần.   Bí thư Thành uỷ Nguyễn Thiện Nhân cũng cho rằng việc đeo khẩu trang là một trong những biện pháp cơ bản để tránh dịch bệnh lây lan. Việc này rất dễ làm, không tốn nhiều tiền nhưng nhiều nước bỏ lơi và đã bị "vỡ trận".   "Ngoài đường hiện có ít nhất 20% người không đeo khẩu trang. Người không đeo không những tự rước bệnh vào mình mà còn nguy cơ lây cho người khác. Đeo khẩu trang hơi cực tí thôi nhưng đi đâu cũng nên đeo để giữ an toàn", ông Nhân nói và khẳng định thành phố bảo đảm không thiếu khẩu trang cho người dân.   Chủ tịch UBND thành phố Nguyễn Thành Phong cũng cho biết đã đồng ý việc tái lập các chốt kiểm soát ở cửa ngõ TP HCM để phòng chống Covid-19.   Trước đó, thành phố đã lập 62 chốt kiểm soát, hoạt động 24/24 từ ngày 4/4 để phòng chống dịch. Lực lượng tham gia là Công an thành phố, Sở Y tế, Bộ Tư lệnh thành phố, Thanh tra giao thông, Ban Quản lý An toàn thực phẩm, quản lý thị trường.   Trong đó, 16 chốt chính (cấp thành phố) đặt tại: Trạm thu phí Long Phước (cao tốc TP HCM - Long Thành - Dầu Giây), cao tốc Trung Lương, cầu Đôi (đường Trần Văn Giàu), đường Ba Làng, đường Xuyên Á (quốc lộ 22), cầu Phú Cường, cầu Vĩnh Bình, cầu vượt Sóng Thần, quốc lộ 1K, quốc lộ 50, quốc lộ 1A, cầu Đồng Nai, Bến xe Miền Tây, Bến xe miền Đông, sân bay Tân Sơn Nhất, cảng Cát Lái.   Đến ngày 23/4, chính quyền thành phố dừng hoạt động các chốt này vì dịch bệnh đã được khống chế, TP HCM dừng cách ly xã hội theo Chỉ thị 19 của Thủ tướng.   Sau 19 ngày hoạt động, các chốt chính đã kiểm tra gần 270.000 xe, trong đó có 235.000 ôtô; gần 600.000 người được kiểm tra y tế, đo thân nhiệt, bao gồm cả 3.000 người nước ngoài; hơn 130.000 người được yêu cầu khai báo y tế. 

Và dữ liệu sau khi xử lý:

tp hcm phạt người không đeo khẩu_trang nơi công_cộng người dân ở thành_phố không đeo khẩu_trang nơi công_cộng sẽ bị xử_phạt mức cao nhất 300 000 đồng từ ngày 58 yêu_cầu này được chủ_tịch ubnd thành_phố nguyễn_thành phong đưa ra tại cuộc họp ban chỉ_đạo phòng_chống dịch_bệnh covid 19 của tp hcm chiều 38 việc xử_phạt không đeo khẩu_trang nơi công_cộng được tp hcm cũng như các địa_phương khác thực_hiện từ cuối tháng 3 khi covid 19 bùng_phát tuy_nhiên sau khi hết thực_hiện cách_ly xã_hội từ ngày 234 việc đeo khẩu_trang nơi công_cộng chỉ dừng lại ở mức khuyến_cáo theo nghị_định số 1762013 người_dân không đeo khẩu_trang nơi công_cộng sẽ bị xử_phạt từ 100 000 đến 300 000 đồng trong khoảng một tháng áp_dụng trước đó tp hcm đã xử_phạt hơn 4 300 trường_hợp với gần 870 triệu đồng theo ông phong việc đeo khẩu_trang đã được khẳng_định có_thể tránh lây_lan dịch_bệnh cho người khác và bảo_vệ sức_khỏe cho người sử_dụng sở công_thương phải nắm nguồn cung_ứng_khẩu_trang chủ_động thông_báo các điểm bán để người dân dễ_dàng mua vì đã xử_phạt thì phải bảo_đảm đủ nguồn cung ông phong nói đội trật_tự đô_thị phường bến_nghé quận 1 xử_phạt người không đeo khẩu_trang trên phố đi bộ nguyễn_huệ chiều 154 ảnh quỳnh trần đội trật_tự đô_thị phường bến_nghé quận 1 xử_phạt người không đeo khẩu_trang trên phố đi bộ nguyễn_huệ chiều 154 ảnh quỳnh trần bí_thư thành_ủy nguyễn_thiện_nhân cũng cho rằng việc đeo khẩu_trang là một trong những biện_pháp cơ_bản để tránh dịch_bệnh lây_lan việc này rất dễ làm không tốn nhiều tiền nhưng nhiều nước bỏ_lơi và đã bị vỡ trận ngoài đường hiện có ít_nhất 20 người không đeo khẩu_trang người không đeo không_những tự rước bệnh vào mình mà_còn nguy_cơ lây cho người khác đeo khẩu_trang hơi cực tí thôi nhưng đi đâu cũng nên đeo để giữ an_toàn ông nhân nói và khẳng_định thành_phố bảo_đảm không thiếu khẩu_trang cho người dân chủ_tịch ubnd thành_phố nguyễn_thành phong cũng cho biết đã đồng_ý việc tái_lập các chốt kiểm_soát ở cửa_ngõ tp hcm để phòng_chống covid 19 trước đó thành_phố đã lập 62 chốt kiểm_soát hoạt_động 2424 từ ngày 44 để phòng_chống dịch lực_lượng tham_gia là công_an thành_phố sở y_tế bộ_tư_lệnh thành_phố thanh_tra giao_thông ban quản_lý an_toàn thực_phẩm quản_lý thị_trường trong đó 16 chốt chính cấp thành_phố đặt tại trạm thu phí long phước cao_tốc tp hcm long thành dầu giây cao_tốc trung_lương cầu đôi đường trần_văn_giàu đường ba làng đường xuyên á quốc_lộ 22 cầu phú_cường cầu vĩnh_bình cầu_vượt sóng_thần quốc_lộ 1k quốc_lộ 50 quốc_lộ 1a cầu đồng nai bến_xe miền tây bến_xe miền đông sân_bay tân_sơn nhất cảng cát_lái đến ngày 234 chính_quyền thành_phố dừng hoạt_động các chốt này vì dịch_bệnh đã được khống_chế tp hcm dừng cách_ly xã_hội theo chỉ_thị 19 của thủ_tướng sau 19 ngày hoạt_động các chốt chính đã kiểm_tra gần 270 000 xe trong đó có 235 000 ôtô gần 600 000 người được kiểm_tra y_tế đo thân_nhiệt bao_gồm cả 3 000 người nước_ngoài hơn 130 000 người được yêu_cầu khai_báo y_tế  

Mình quan sát thì thấy vừa ý rồi, nên mình sẽ chuyển sang bước tiếp theo.

Loại bỏ các stopword tiếng Việt

Cũng giống như việc loại bỏ các ký tự đặc biệt ở trên, nhưng bây giờ là các từ tiếng Việt hẳn hoi. Vậy tại sao cần loại bỏ?

  • Stopword là các từ xuất hiện nhiều ở tất cả các chuyên mục cần phân loại. Do đó, chúng là các đặc trưng không có tác dùng cho việc phân loại văn bản.
  • Các stopword thường là các từ nối (của, là, có, được, những,…) và các từ đặc trưng của dữ liệu (ví dụ như các từ “máy bay, tiếp viên” là các stopword nếu làm bài phân loại đánh giá khách hàng của doanh nghiệp vận tải hàng không.

Vậy làm sao để xây dựng bộ stopword?

Danh sách stopword phải nên được xây dựng từ bộ dữ liệu văn bản lớn, tầm cỡ vài chục GB là hợp lý. Nếu là dữ liệu trong miền bài toán bạn đang làm thì càng tốt!

Việc xây dựng bộ từ điển stopword tiếng Việt cũng rất đơn giản. Bạn chỉ cần thống kê các từ xuất hiện nhiều trong tất cả các chuyên mục và lấy top đầu là được.

Ví dụ, với dữ liệu tin tức của mình, thống kê trên khoảng 2000 bài báo mỗi chủ đề, thì mình được kết quả như sau (Đã sắp xếp giảm dần, lấy demo một ít top đầu):

và 14255
của 13177
là 9983
có 9162
được 9131
trong 8654
một 7575
cho 7483
với 7195
không 6591
các 6300
người 6088
khi 6011
này 5301
đến 5165
để 5123
đã 4431
nhiều 4167
trên 3842
từ 3820
vào 3617
đó 3207
những 3097
ở 2943
ra 2767
tại 2756
vn 2738
lại 2673
cũng 2622
phải 2615
còn 2589
...

Việc lấy bao nhiêu từ trong danh sách kết quả này cần dựa vào quan sát & loại trừ. Giả sử như mình trong bài này, mình chỉ lấy 100 từ đầu tiên để làm stopword.

Trong trường hợp bạn không muốn xây dựng bộ stopword hoặc không có dữ liệu để làm, bạn có thể tham khảo một số bộ stopword tiếng Việt đã được chia sẻ trên internet.

Loại stopword khỏi dữ liệu ra sao?

Việc cần làm bây giờ của bạn là duyệt qua từng bản ghi dữ liệu và xóa tất cả các từ trong dữ liệu mà có trong danh sách stopword.

# Danh sách stopword
stopword = set()
 
def remove_stopwords(line):
    words = []
    for word in line.strip().split():
        if word not in stopword:
            words.append(word)
    return ' '.join(words)

Sau khi thực hiện tiền xử lý dữ liệu xong, giờ chúng ta bắt tay vào xây dựng các mô hình học máy cho bài toán phân loại văn bản trên dữ liệu đã tiền xử lý.

Đọc thêm: Khôi phục dấu tiếng Việt sử dụng Beam search

Xây dựng mô hình phân loại văn bản

Trước khi huấn luyện mô hình phân loại văn bản, ta cần xây dựng tập huấn luyện và tập kiểm thử. Việc này là cần thiết để đánh giá kết quả huấn luyện, lựa chọn mô hình cũng như tinh chỉnh để mô hình cho tốt hơn.

Xây dựng tập train/test

Dữ liệu dùng cho bài toán phân loại văn bản của mình sau khi tiền xử lý được lưu thành 1 file duy nhất. Mỗi dòng là một bài báo kèm theo thông tin danh mục của nó.

__label__thể_thao sanchez dự trận khai màn nh arsenal bóng_đá tuyển_thủ chile kịp hồi_phục thể_lực pháo thủ_thành london tham_dự trận đầu_tiên premier_league 2015 2016 diễn 9 8 arsenal singapore liverpool đổ_bộ xuống thái_lan đội_bóng danh_tiếng giải ngoại_hạng_anh bắt_đầu chuyến du_đấu châu á thầy_trò hlv brendan_rodgers có_mặt thái_lan chuẩn_bị trận_đấu diễn 14 7 trả_lời phỏng_vấn chuyến du_đấu singapore hlv arsene_wenger xác_nhận tiền_đạo alexis_sanchez vắng_mặt trận khai màn premier_league chiến_lược gia pháp cựu ngôi_sao barca cần nghỉ_ngơi tham_dự copa_america quê_hương alexis trở_lại arsenal 8 cầu_thủ thường lấy thể_lực khoảng tuần có_thể thi_đấu alexis có_thể nghỉ ít tuy_nhiên ấy chắc_chắn tham_dự trận_đấu đầu_tiên mùa_giải trang bbc dẫn bài phỏng_vấn hlv wenger trận xông đất premier_league sanchez góp_mặt trận tranh community_shield chelsea bởi trận_đấu diễn 8 chân_sút 26 tuổi trở_lại arsenal sanchez nhân_tố then_chốt giúp chile vô_địch copa_america bbc mùa_giải vừa_qua cựu tiền_đạo barcelona thi_đấu thuyết_phục ghi 25 bàn 52 trận giúp pháo_thủ vô_địch fa_cup cán đích vị_trí thứ premier_league arsenal giành vé dự vòng bảng champions_league mùa copa_america vừa_qua sanchez ghi bàn tuy_nhiên giới chuyên_môn đánh_giá mắt_xích quan_trọng giúp chủ_nhà chile tiến chung_kết trận_đấu cuối_cùng sanchez đồng_đội xuất_sắc đánh_bại argentina vô_địch giải_đấu danh_giá nam_mỹ lịch arsenal nổ tiếng súng lệnh premier_league mùa tiếp west_ham sân_nhà lúc 19h30 9 8 giống alexis_sanchez bộ đôi hậu_vệ pablo_zabaleta martin_demichelis kịp manchester_city đá trận khai_mạc premier_league manchester_united nhận phục_vụ angel_di_maria marcos_rojo bốn cầu_thủ đá trọn trận chung_kết copa_america đội_tuyển argentina arsenal f c câu_lạc_bộ bóng_đá arsenal biệt_danh pháo_thủ câu_lạc_bộ bóng_đá trụ_sở holloway london câu_lạc_bộ hiện chơi giải bóng_đá ngoại_hạng_anh câu_lạc_bộ thành_công bóng_đá arsenal giành tổng_cộng 13 danh_hiệu vô_địch quốc_gia 1913 đội_bóng chuyển phía bắc lấy sân_vận_động highbury sân_nhà thi_đấu suốt 93 2006 đội_bóng chuyển sân_vận_động emirates lấy sân_nhà nay arsenal câu_lạc_bộ kình_địch đội_bóng khu_vực tottenham_hotspur thierry_henry cầu_thủ ghi_bàn câu_lạc_bộ 228 bàn_thắng 1999 2007 2012 thành_lập 1886 biệt_danh pháo_thủ sân_vận_động emirates
__label__âm_nhạc sao việt hóa_thân thành phó nháy mv nhạc_việt miu_lê tiêu_châu_như_quỳnh phùng_ngọc_huy chọn hình_tượng nhiếp_ảnh_gia xuất_hiện sản_phẩm riêng sao việt hóa_thân thành phó nháy mv miu_lê tiêu_châu_như_quỳnh phùng_ngọc_huy chọn hình_tượng nhiếp_ảnh_gia xuất_hiện sản_phẩm riêng miu lê lặng thầm yêu lặng_thầm yêu mv đánh_dấu trưởng_thành miu_lê 2012 sản_phẩm cô hóa_thân thành nàng nhiếp_ảnh_gia xinh_đẹp cá_tính công_việc dường_như dành phái_mạnh cô cơ_duyên gặp_gỡ tình_yêu hot boy tuấn_kiệt chụp cô đem_lòng yêu chàng hot boy nụ_cười tỏa nắng tuy_nhiên khoảng_cách quá miu lê cô_gái nghề nhiếp_ảnh cá_tính tuấn_kiệt hot boy điển_trai tương_lai rộng_mở quan_trọng cô nhiếp_ảnh chàng_trai tìm một_nửa chấp_nhận miu lê chôn giấu tình_yêu sâu lòng cầu_chúc tình_yêu người_thương mỗi lúc nhớ tuấn_kiệt miu_lê thường ngắm bức hình chụp tất_cả tấm cô đau_lòng cào xé tâm_can cuối_cùng cô quyết_định xa tìm bình_yên tâm_hồn will 365 nơi thuộc nơi thuộc mv khá đặc_biệt 365 đầu_tiên ngô_thanh_vân cho_phép học_trò tình_tứ gái lạ mô típ chàng_trai 365 hóa_thân thành hình_tượng đáng chú_ý kể will hình_ảnh chàng nhiếp_ảnh_gia điển_trai đầy cá_tính thói_quen lưu_giữ hình_ảnh yêu hình_ảnh will trở_nên nổi_bật ấn_tượng so thành_viên mv chia_tay đau_đớn nhớ kỷ_niệm chẳng thể_nào quên xưa rainboy_boys ước_mơ ước_mơ mv thứ nhóm nhạc rainboy_boys mv ra_mắt đầu_tiên ấn_tượng walk_away mv 7 chàng_trai_hóa_thân câu_chuyện tình khá dễ_thương chàng_trai điển_trai ryan_hải_đăng hô biến thành chàng nhiếp_ảnh_gia đường_phố ghi khoảnh_cách cuộc_sống con_người sài_gòn cạnh chàng ghi khoảnh_khắc tình_yêu tuyệt_diệu thành_viên rainboy_boys nổi_bật gây ấn_tượng đặc_biệt khán_giả chính hình_ảnh cuối_cùng anh_chàng ira_hoàng_thy giúp gái ngồi xe_lăn dạo phố_phường khoảnh_khắc đầy ý_nghĩa khép ước_mơ v music xinh_tươi mv nổi_tiếng v music lê_thiên_bảo chưa tách nhóm xinh_tươi góp_phần giúp học_trò hồ_ngọc_hà gây_dựng hình_ảnh đẹp lòng công_chúng bốn chàng_trai hóa_thân thành chàng nhiếp_ảnh_gia dễ_thương điển_trai khắp miền ghi hình_ảnh đẹp quê_hương đất_nước hành_trình ghi khoảnh_khắc khó quên con_gái tà áo_dài mv khoảnh_khắc ý_nghĩa chàng nhiếp_ảnh_gia v music thực_sự sản_phẩm đáng nhớ xem tiêu_châu_như_quỳnh bao hợp_tác rapper antonio_maximus bao mv tiêu_châu_như quỳnh rời khỏi giọng hát việt 2012 mv khán_giả hình_ảnh cuộc_sống thường_nhật sài_gòn năng_động ồn_ã đầy bình_yên hóa_thân nữ nhiếp_ảnh_gia đường_phố cá_tính xinh_đẹp quỳnh ghi khoảnh_khắc ấn_tượng thông_qua ống_kính hành_trình rong_ruổi nọ cô gặp em bé bán hoa dạo trong_trẻo ngây_thơ lam_lũ cô_bé suy_nghĩ nhiếp_ảnh_gia trẻ tuổi con_người thầm_lặng tạo sắc_màu rực_rỡ thủ_đô tươi đẹp mv chuyển_tải mọi thông_điệp ý_nghĩa chịu_khó quan_sát cuộc_sống quanh thật đẹp_đẽ ấm_áp biết_bao phùng_ngọc_huy déjà_vu déjà_vu mv đánh_dấu trở_lại hồng_mơ giải tiếng hát truyền_hình ẩn mv diễn cô nam ca_sĩ điển_trai phùng_ngọc_huy mv phùng_ngọc_huy vai anh_chàng nhiếp_ảnh đẹp_trai vô_tình cô_gái bán giỏi đàn hát đêm mơ cô xuất_hiện sân_khấu ca_nhạc vai_trò ca_sĩ nổi_tiếng cảm_giác thoáng hiện_thực anh_chàng vô_tình gặp cô đất_việt
__label__nhịp_sống vợ quỳ gối nói_chuyện cười bợm nhậu nói_chuyện dạo mày nhậu sợ vợ la_ó sao ôi trời vợ tao sợ tao lắm vậy mày bản_lĩnh giống tao rồi mày tao nhậu vợ quỳ gối nói_chuyện tao thế thì mày đứt tao rồi tao dễ ợt nhậu tao chui xuống gầm giường định_nghĩa kết_hôn mỗi phái định_nghĩa kết_hôn hoàn_toàn
__label__công_nghệ lumia 520 giá 9 triệu đồng công_nghệ dự_kiến sản_phẩm bán chính thị_trường đầu_tiên nokia chọn phát_hành chiếc điện_thoại chạy windows_phone 8 lumia 520 giá 9 triệu đồng dự_kiến sản_phẩm bán chính thị_trường đầu_tiên nokia chọn phát_hành chiếc điện_thoại chạy windows_phone 8 lumia 520 chiếc smartphone nokia ra_mắt triển_lãm mwc vừa_qua sản_phẩm nhắm phân_khúc smartphone tầm thấp cấu_hình khá ấn_tượng cụ_thể lumia 520 tích_hợp màn_hình inch độ_phân_giải 480x800 pixel chip lõi kép tốc_độ ghz ram 512 mb camera megapixel đèn flash bán lumia 520 điện_thoại windows_phone 8 rẻ thị_trường xem cấu_hình mơ_ước đối_với sản_phẩm tầm thấp chưa kể nó tích_hợp tính_năng cảm_ứng siêu_nhạy super_sensitive_touch khả_năng thao_tác găng_tay dày bộ_nhớ_trong máy 8 gb máy mỏng 9 mm nặng 124 gram cạnh chiếc lumia 720 ra_mắt thị_trường việt đợt phát_hành giá 7 triệu đồng lumia 720 đánh_giá cao mwc vừa_qua model hướng phân_khúc smartphone tầm trung màn_hình inch độ_phân_giải wvga 480 x 800 pixel chip lõi kép tộc độ ghz ram 512 mb camera 7 megapixel camera lumia 720 sử_dụng ống_kính góc rộng carl_zeiss f 9 quảng_cáo nokia chất_lượng chụp ngang_ngửa smartphone giá cao lumia 720 giá 7 triệu đồng lumia 720 đánh_giá chiếc smartphone đẹp nay gia_đình nhà lumia độ mỏng hợp_lý 9 mm nặng 128 gram thiết_kế nguyên khối chắc_chắn model tích_hợp tính_năng sạc dây bộ vỏ dự_kiến bộ đôi phát_hành thị_trường hiện_tại chuỗi cửa_hàng viettel_store chuẩn_bị nhận đặt sản_phẩm 15 31 phần quà hấp_dẫn chẳng_hạn tặng phiếu mua hoặc trúng chiếc lumia 520 hoặc 720 miễn_phí thành_duy_theo infonet
__label__thể_thao hà_nội t t chốt danh_sách dự afc cup thể_thao tiền_vệ lối chơi sáng_tạo bậc hà_nội t t benicio hlv phan_thanh_hùng loại khỏi danh_sách dự afc cup giành sức sân_chơi v league hà_nội t t chốt danh_sách dự afc cup tiền_vệ lối chơi sáng_tạo bậc hà_nội t t benicio hlv phan_thanh_hùng loại khỏi danh_sách dự afc cup giành sức sân_chơi v league hôm_qua hà_nội t t chính_thức chốt danh_sách cầu_thủ tham_dự sân_chơi afc_cup bất_chấp lực_lượng khá mỏng mùa_giải nay hlv phan_thanh_hùng quyết_định sử_dụng quân bài tốt sân_chơi châu_lục nội_binh hồng_sơn hồng_minh văn_biển công_vinh ngoại_binh cristiano gonzalo matias vắng_mặt đáng tiếc duy_nhất danh_sách dự afc_cup hà_nội t t tiền_vệ lối chơi sáng_tạo benicio có_lẽ hlv phan_thanh_hùng quyết_định cất cầu_thủ con cưng mục_tiêu bảo_vệ chức vô_địch v league kết_quả chia bảng afc_cup 2011 lđbđ châu_á afc công_bố đương_kim vô_địch v league hà_nội t t nằm bảng g clb muang_thong_united thái_lan tampines_rovers singapore victory sc maldives bảng đấu khá cân_bằng thầy_trò hlv phan_thanh_hùng vất_vả tranh vé tiếp đội mạnh thái_lan singapore dù vậy chơi đúng sức hà_nội t t hoàn_toàn khả_năng ghi tên vòng đấu_loại trực_tiếp giống thành_tích b bình dương shb_đà_nẵng được_mùa thầy_trò hlv phan_thanh_hùng chính_thức bước sân_chơi châu_lục chuyến làm_khách clb muang_thong_united lâm_thỏa_theo bưu_điện
__label__thời_sự vụ xe điên gây tai_nạn sài_gòn tài_xế tâm_thần thời_sự khống_chế gây tai_nạn liên_hoàn tài_xế xe bmw phun nước_bọt vung chân đạp loạn_xạ cảnh_sát gia_đình cho_hay ông bệnh_tâm_thần tâm_lý bất_ổn tối 21 11 công_an quận tp hcm rõ một_số tình_tiết liên_quan vụ xe điên gây tai_nạn rồi tông csgt bỏ chạy tài_xế ông phạm_cao_v ngụ quận mất_tích gia_đình cơ_quan_chức_năng truy_tìm 13h30 v lái_xe bmw màu trắng bks 63a lưu_thông đường điện_biên_phủ hướng nam_kỳ_khởi_nghĩa ngã tư hàng_xanh quận bình_thạnh đường tài_xế gây tai_nạn xe_máy do chị đỗ_thị_liên_h 29 tuổi ngụ quận tân_bình điều_khiển ôtô dừng bỏ chạy đường đuổi tài_xế tông xe đặc_chủng bỏ chạy nguyễn_thành chiếc bmw chạy giao_lộ điện_biên_phủ hai_bà_trưng thì csgt chặn bắt tuy_nhiên tài_xế cố_tình tông xe đặc_chủng thoát_thân csgt rút súng bắn chỉ_thiên cảnh_cáo do đường tắc tài_xế tấp lề_đường hai_bà_trưng chốt cửa cố_thủ xe 30 phút trung_tá lê_văn_thanh tổ_trưởng điều_tra xử_lý tai_nạn đội csgt công_an quận thuyết_phục ông v chịu mở_cửa tuy_nhiên ta phun nước_miếng anh_em vung chân đạp loạn_xạ cào_cấu tôi ông thanh nói một_số đường bức_xúc xông đánh tài_xế csgt can_ngăn lúc người_nhà xuất_hiện ông v dần bình_tâm rồi bỏ clip csgt khống_chế tài_xế ôtô điên tài_xế ôtô tông cô_gái xe_máy rồi bỏ chạy cảnh_sát giao_thông yêu_cầu dừng thì nghi_can tông lực_lượng chức_năng cố_thủ xe gia_đình cung_cấp giấy_tờ xe bmw giấy_tờ cá_nhân liên_quan ông v ôtô đứng_tên công_ty tiền_giang nơi mẹ ông v làm_việc ông v bệnh_án bệnh nan_y bệnh_viện xác_nhận tâm_thần bệnh đàn_ông giấy_phép lái ôtô gia_đình ông v bệnh nan_y do dùng thuốc điều_trị dấu_hiệu tâm_thần tâm_lý bất_ổn gần ông ta liên_tục đòi lấy ôtô chơi gia_đình thì đòi tuyệt_thực mặt mẹ vợ_con trưa 21 11 mọi vắng ông v lấy ôtô lòng_vòng gây vụ_việc lái_xe bmw tông cảnh_sát giao_thông sài_gòn tài_xế xe bmw tông cô_gái xe_máy rồi bỏ chạy lực_lượng chức_năng yêu_cầu dừng tài_xế tông xe cảnh_sát giao_thông xe đặc_chủng cố_thủ
__label__thế_giới lính nga nhảy_dù xuống tảng băng trôi bắc_cực thế_giới 50 binh_sĩ nga đồng_loạt nhảy khỏi máy_bay ilyushin 76 xuống tảng băng trôi gần bắc_cực cuộc tập_trận tìm_kiếm cứu nạn nguy_hiểm đại_tá yevgeny_meshkov phát_ngôn_viên lực_lượng lính_dù dù thời_tiết giá_lạnh kèm gió binh_sĩ thực_hiện tốt nhiệm_vụ tiếp đất nhận hàng_hóa họ sử_dụng hệ_thống dù arbalet cho_phép nhảy máy_bay di_chuyển vận_tốc 400 km h binh_sĩ nhảy_dù khỏi máy_bay ilyushin 76 itar tass quân_đội nga cuộc tập_trận nhằm mục_đích tăng_cường khả_năng tìm_kiếm cứu nạn ứng_phó kịp_thời tình_huống khẩn_cấp bắc_cực tham_gia tập_trận dựng trại điều_kiện khắc_nghiệt dao súng_trường giúp họ chống loài động_vật hoang_dã con gấu_bắc_cực đói ăn va_chạm tảng băng trôi bề_mặt băng sụt lún có_thể họ trả_giá mạng sống cờ crimea tung bay rt binh_sĩ tham_gia huấn_luyện cắm cờ crimea khu_vực đích_thân thủ_tướng crimea ông sergey_aksyonov trao lá cờ binh_sĩ bán bán_đảo crimea nơi đặt trụ_sở hạm_đội biển_đen nga
__label__thời_trang ngắm vĩnh_thụy đầy chất chơi thời_trang khoảnh_khắc ngẫu_hứng chụp tình_cờ vĩnh_thụy nơi công_cộng vẽ giải bạc siêu_mẫu mạnh_mẽ nam_tính đầy chất chơi ngắm vĩnh_thụy đầy chất chơi khoảnh_khắc ngẫu_hứng chụp tình_cờ vĩnh_thụy nơi công_cộng vẽ giải bạc siêu_mẫu mạnh_mẽ nam_tính đầy chất chơi tháng ròng_rã đoàn phim gần tháng quay ngoại_cảnh đà_lạt vai diễn vĩnh_thụy bộ phim nhật_ký bạch_tuyết kết_thúc hiện_tại vĩnh_thụy dành nghỉ_ngơi chuẩn_bị bước giai_đoạn tập_luyện cao_độ cuộc thi mister_international 2009 vĩnh_thụy người_mẫu đại_diện tham_dự mister_international cuộc_thi tiến_đoàn đăng_quang 2008 nay cuộc thi diễn tháng 11 đài_loan chia_sẻ kế_hoạch sắp vĩnh_thụy tâm_sự hiện_tại vài bộ phim mời thụy tham_gia thụy cân_nhắc mặt đảm_bảo tập_luyện cuộc_thi mister_international sắp cuộc_thi thụy mong cơ_hội tự trao dồi kiến_thức nghề dịp bước cọ_xát hội_nhập người_mẫu mời ngắm bộ mới_mẻ vĩnh_thụy dân_trí
__label__du_lịch bố_mẹ dừng tàu lượn siêu_tốc lo con_gái du_lịch quá giận con chơi trò mạo_hiểm phép vị phụ_huynh dừng đoàn tàu lượn siêu_tốc công_viên giải_trí trung_quốc scmp sự_việc diễn công_viên giải_trí queershan liễu_châu khu_tự_trị dân_tộc choang_quảng_tây hôm thiếu_nữ 14 tuổi muốn tàu lượn siêu_tốc bố_mẹ đồng_ý lúc họ sơ_ý em lẻn ngồi xe lo_lắng tức_giận hành_động con_gái bố_mẹ em trung_tâm điều_khiển trò_chơi yêu_cầu dừng tàu lượn ngay_lập_tức họ đưa con_gái khỏi đoàn tàu nhân_viên khu giải_trí trèo đường_ray đưa cô_gái xuống scmp đoàn tàu lượn dừng lăn bánh đoạn ngắn sự_việc bất_ngờ hành_khách tưởng sự_cố kỹ_thuật nhân_viên khu giải_trí giải_thích rằng chuyến ngừng do phụ_huynh đồng_ý con_gái tham_gia sự_việc lên_tiếng chỉ_trích cho_rằng ông bố bà mẹ hành_động thái quá tuy_vậy tỏ thông_cảm lo_lắng cặp vợ_chồng đặc_biệt vụ tai_nạn thiếu_nữ 14 tuổi văng khỏi đu_quay thiệt_mạng xảy hôm công_viên zhaohua trùng_khánh trung_quốc
 

Mục đích mình để theo format này là để về sau train với thư viện Fasttext

Thống kê dữ liệu sử dụng:

Và đây là thống kê dữ liệu được mình sử dụng trên mỗi nhãn (mình dùng ít cho nhanh):

__label__thể_thao 2611
__label__âm_nhạc 2595
__label__nhịp_sống 2613
__label__công_nghệ 2595
__label__thời_sự 2607
__label__thế_giới 2602
__label__thời_trang 2596
__label__du_lịch 2593
__label__sống_trẻ 2602
__label__giáo_dục 2603
__label__kinh_doanh 2597
__label__pháp_luật 2592
__label__giải_trí 2604
__label__phim_ảnh 2596
__label__xe_360 2602
__label__ẩm_thực 2482
__label__xuất_bản 2599
__label__sức_khỏe 2589

Các nhãn có số lượng khá cân bằng rồi, nên việc xử lý dữ liệu mất cân bằng không cần phải lo. Nếu bạn dùng thực tế, nên thêm dữ liệu nhiều hơn gấp 10 lần nhé ^^.

Mình sẽ sử dụng thư viện sklearn trong Python giúp mình tách dữ liệu làm 2 tập train/test riêng biệt:

# Chia tập train/test
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
 
# tỉ lệ tập test - train là 8 : 2
test_percent = 0.2
 
text = []
label = []
 
for line in open('news_categories.prep'):
    words = line.strip().split()
    label.append(words[0])
    text.append(' '.join(words[1:]))
 
X_train, X_test, y_train, y_test = train_test_split(text, label, test_size=test_percent, random_state=42)
 
# Lưu train/test data
# Giữ nguyên train/test để về sau so sánh các mô hình cho công bằng
with open('train.txt', 'w') as fp:
    for x, y in zip(X_train, y_train):
        fp.write('{} {}\n'.format(y, x))
 
with open('test.txt', 'w') as fp:
    for x, y in zip(X_test, y_test):
        fp.write('{} {}\n'.format(y, x))
 
# encode label
label_encoder = LabelEncoder()
label_encoder.fit(y_train)
print(list(label_encoder.classes_), '\n')
y_train = label_encoder.transform(y_train)
y_test = label_encoder.transform(y_test)

Ở trên, mình thực hiện các công việc sau:

  1. Đọc dữ liệu từ file và tách làm 2 list text (dữ liệu) và label (nhãn). Dữ liệu text[i] sẽ có nhãn là label[i].
  2. Chia làm 2 tập train (X_train, y_train) và test (X_test, y_test) theo tỉ lệ 80% train, 20% test.
  3. Lưu train/test data ra file để sử dụng cho việc train với thư viện Fasttext.
  4. Đưa label về dạng vector để tiện cho tính toán sử dụng LabelEncoder.

Ok, bậy giờ chúng ta sẽ sử dụng tập train để đi thử nghiệm với các mô hình phân loại văn bản tiếng Việt khác nhau nhé. Mình sẽ thử nghiệm trên các mô hình mà các bạn có thể dễ dàng cài đặt:

  • Naive Bayes (thư viện sklearn)
  • Logistic Regression (thư viện sklearn)
  • Support Vector Machine (thư viện sklearn)
  • Fasttext (https://fasttext.cc/)

Với các thuật toán machine learning truyền thống sử dụng thư viện sklearn, mình sẽ xây dựng bộ trích xuất đặc trưng (feature extractor) sử dụng TF-IDF.

Sử dụng Naive Bayes

import pickle
import time
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
 
start_time = time.time()
text_clf = Pipeline([('vect', CountVectorizer(ngram_range=(1,1),
                                             max_df=0.8,
                                             max_features=None)), 
                     ('tfidf', TfidfTransformer()), 
                     ('clf', MultinomialNB())
                    ])
text_clf = text_clf.fit(X_train, y_train)
 
train_time = time.time() - start_time
print('Done training Naive Bayes in', train_time, 'seconds.')
 
# Save model
pickle.dump(text_clf, open(os.path.join(MODEL_PATH, "naive_bayes.pkl"), 'wb'))

Sử dụng Logistic Regression

from sklearn.linear_model import LogisticRegression
    
start_time = time.time()
text_clf = Pipeline([('vect', CountVectorizer(ngram_range=(1,1),
                                             max_df=0.8,
                                             max_features=None)), 
                     ('tfidf', TfidfTransformer()),
                     ('clf', LogisticRegression(solver='lbfgs', 
                                                multi_class='auto',
                                                max_iter=10000))
                    ])
text_clf = text_clf.fit(X_train, y_train)
 
train_time = time.time() - start_time
print('Done training Linear Classifier in', train_time, 'seconds.')
 
# Save model
pickle.dump(text_clf, open(os.path.join(MODEL_PATH, "linear_classifier.pkl"), 'wb'))

Sử dụng mô hình SVM

from sklearn.svm import SVC
 
start_time = time.time()
text_clf = Pipeline([('vect', CountVectorizer(ngram_range=(1,1),
                                             max_df=0.8,
                                             max_features=None)), 
                     ('tfidf', TfidfTransformer()),
                     ('clf', SVC(gamma='scale'))
                    ])
text_clf = text_clf.fit(X_train, y_train)
 
train_time = time.time() - start_time
print('Done training SVM in', train_time, 'seconds.')
 
# Save model
pickle.dump(text_clf, open(os.path.join(MODEL_PATH, "svm.pkl"), 'wb'))
 

Sử dụng thư viện Fasttext

Đầu tiên, bạn cần cài đặt thư viện này như sau:

# Cài đặt fastText cho Python
!pip3 install numpy scipy pybind11 cmake
!wget -nc "https://github.com/facebookresearch/fastText/archive/v0.9.2.zip" > /dev/null
!unzip -n "v0.9.2.zip" > /dev/null
!cd fastText-0.9.2 && pip3 install .
!rm -rf v0.9.2.zip fastText-0.9.2

Tiếp theo, bạn cần chuẩn bị dữ liệu giống như 2 file “train.txt” và “test.txt” mà mình đã chuẩn bị ở phần Xây dựng tập train/test:

import fasttext
 
start_time = time.time()
model = fasttext.train_supervised(
                                input='train.txt',
                                dim=100,
                                epoch=5,
                                lr=0.1,
                                wordNgrams=2,
                                label='__label__',
                                minCount=5
)
 
train_time = time.time() - start_time
print('Done training Fasttext in', train_time, 'seconds.')
 
# Compress model files with quantization
model.quantize(input='train.txt', retrain=True)
model.save_model(os.path.join(MODEL_PATH,"fasttext.ftz"))

Kết quả phân loại văn bản

Trong mỗi mô hình trên, mình đều tiến hành đo thời gian huấn luyện. Bạn có thể dựa vào thông tin đó để biết mô hình nào huấn luyện nhanh hay chậm, mất bao lâu để huấn luyện xong.

So sánh các mô hình

Còn bây giờ, chúng ta sẽ đánh giá các mô hình vừa rồi để xem mô hình nào cho kết quả tốt nhất trên tập test:

import numpy as np
# Naive Bayes
model = pickle.load(open(os.path.join(MODEL_PATH,"naive_bayes.pkl"), 'rb'))
y_pred = model.predict(X_test)
print('Naive Bayes, Accuracy =', np.mean(y_pred == y_test))
 
# Linear Classifier
model = pickle.load(open(os.path.join(MODEL_PATH,"linear_classifier.pkl"), 'rb'))
y_pred = model.predict(X_test)
print('Linear Classifier, Accuracy =', np.mean(y_pred == y_test))
 
# SVM
model = pickle.load(open(os.path.join(MODEL_PATH,"svm.pkl"), 'rb'))
y_pred = model.predict(X_test)
print('SVM, Accuracy =', np.mean(y_pred == y_test))
 
# Fasttext
def print_results(N, p, r):
    print("Fasttext, Precision = {}, Recall = {}".format(p, r))
    
model = fasttext.load_model(os.path.join(MODEL_PATH,"fasttext.ftz"))
print_results(*model.test('test.txt'))

Và đây là kết quả sau khi chạy đoạn code đánh giá trên:

Naive Bayes, Accuracy = 0.8324764353041988
Linear Classifier, Accuracy = 0.883140531276778
SVM, Accuracy = 0.8822836332476436
Fasttext, Precision = 0.7548200514138818, Recall = 0.7548200514138818
 

Mình cũng thử xem kết quả trên từng nhãn của mô hình naive bayes xem thế nào:

# Xem kết quả trên từng nhãn
from sklearn.metrics import classification_report
 
nb_model = pickle.load(open(os.path.join(MODEL_PATH,"naive_bayes.pkl"), 'rb'))
y_pred = nb_model.predict(X_test)
print(classification_report(y_test, y_pred, target_names=list(label_encoder.classes_)))
 

Thì được kết quả như sau:

                     precision    recall  f1-score   support
 
 __label__công_nghệ       0.91      0.92      0.92       532
   __label__du_lịch       0.79      0.88      0.83       551
  __label__giáo_dục       0.82      0.88      0.85       528
  __label__giải_trí       0.59      0.74      0.66       487
__label__kinh_doanh       0.78      0.84      0.81       498
 __label__nhịp_sống       0.85      0.49      0.62       497
  __label__phim_ảnh       0.90      0.76      0.82       525
 __label__pháp_luật       0.90      0.92      0.91       543
  __label__sống_trẻ       0.62      0.64      0.63       510
  __label__sức_khỏe       0.79      0.88      0.83       496
  __label__thế_giới       0.91      0.83      0.87       549
  __label__thể_thao       0.95      0.95      0.95       508
   __label__thời_sự       0.82      0.77      0.79       496
__label__thời_trang       0.86      0.77      0.81       521
    __label__xe_360       0.97      0.94      0.96       502
  __label__xuất_bản       0.87      0.93      0.90       519
   __label__âm_nhạc       0.83      0.87      0.85       554
   __label__ẩm_thực       0.90      0.94      0.92       520
 
          micro avg       0.83      0.83      0.83      9336
          macro avg       0.84      0.83      0.83      9336
       weighted avg       0.84      0.83      0.83      9336
 

Nhận xét & đánh giá

  • Mô hình Logistic Regression và SVM cho kết quả vượt trội hơn Naive Bayes. Điều này do khả năng học của 2 mô hình này tốt hơn một mô hình ngây thơ (naive).
  • Một số nhãn cho độ chính xác phân loại thấp (giải trí, nhịp sống, sống trẻ). Nguyên nhân có thể do các chuyên mục này không thực sự quá rõ ràng, nổi bật so với các chuyên mục khác nên mô hình dễ bị sai hơn. Ví dụ: Bài “Cô giáo dành cả thành xuân để dạy các em vùng cao” thì có thể là ở Nhịp sống hoặc Sống trẻ đều ổn. Về vấn đề này, chúng ta cần phải dành thời gian để quan sát dữ liệu, lọc ra các bài báo bị phân loại sai và tìm hiểu nguyên nhân.
  • Mô hình sử dụng thư viện Fasttext cho kết quả thấp nhất (không như kỳ vọng của mình). Nguyên nhân mình đoán là do hơi ít dữ liệu nên chưa học tốt việc biểu diễn từ. Nếu có thể, bạn hãy train lại với pretrain word2vec hoặc sử dụng nhiều dữ liệu hơn để nó có thể cho kết quả tốt hơn.
  • Tăng số lượng dữ liệu huấn luyện cho các mô hình sẽ giúp các mô hình trên đây học tốt hơn đó.

Demo & tự thực hành

Source code phân loại văn bản tiếng việt trình bày trong bài viết này được lưu tại Github, bạn mở nó lên và click vào mục Demo để thực hành nhé!

Bạn có thể xem code & thực hành trực tiếp tại đây

Mọi ý kiến đóng góp, nhận xét cũng như các thắc mắc liên quan tới bài viết. Các bạn vui lòng để lại bình luận phía dưới bài viết. Xin cám ơn!

Tài liệu tham khảo

  1. Dữ liệu huấn luyện trong bài viết
  2. Thư viện fasttext của Facebook
  3. Thư viện học máy scikit-learn

(Nguồn nguyenvanhieu.vn)

Hits: 48