[AIviVN 3 - Vietnamese tone prediction] 1st place solution

vietnamese_tone_prediction

#1

I. Giới thiệu

Mình xin đóng góp lời giải của mình cho bài toán thêm dấu tiếng Việt. Mình sử dụng một character-level seq2seq model, với beam search bằng n-gram language model. Mô hình dựa trên 4 quan sát chính sau:

  1. Chỉ có các chữ cái a, e, i, o, u, y, d cần phải thêm dấu.
  2. Mỗi a, e, i, o, u, y, d có 1 tập target nhất định. Ví dụ, với “i” đó là {i, í, ì, ỉ, ĩ, ị}.
  3. Output sequence có thể được decode từ trái qua phải hay từ phải qua trái như nhau.
  4. Độ dài của output sequence và input sequence bằng nhau.

II. Mô hình character-level BiLSTM seq2seq

  • Mình thực hiện một số bước tiền xử lí như sau:
    • lowercase
    • chuẩn hóa dấu câu về 1 kí tự "-"
    • chuẩn hóa các kí tự đặc biệt (ví dụ như các chữ Hán) về 1 kí tự "?"
    • dấu cách, a-z, 0-9 giữ nguyên
    • bỏ các câu có độ dài trên 300 kí tự
    • tập train cuối cùng có 2 triệu câu, được chia thành 4 file nhỏ hơn do RAM của máy mình hơi hạn chế
    • tập validation có ~75k câu, tất cả được shuffle và chia một cách ngẫu nhiên
  • Từ quan sát [3][4], mình sử dụng một mô hình character-level seq2seq có decoder 2 chiều:
    • embedding layer and encoder:
      • phần này mình giữ nguyên như mọi mô hình lstm seq2seq bình thường. encoder sử dụng BiLSTM, có 2 lớp, và hidden size là 300.
    • decoder:
      • có 3 decoder, mỗi decoder có 1 lớp softmax riêng ở cuối:
        • 1 decoder từ trái qua phải
        • 1 decoder từ phải qua trái
        • 1 decoder là gộp của 2 decoder trên bằng cách concat lstm output states
      • giá trị loss cuối cùng là tổng loss của 3 decoder trên:
        • L_all = L_ltr + L_rtl + L_combined
    • teacher forcing:
      • mô hình có sử dụng teacher forcing cho các kí tự không phải a, e, i, o, u, y, d
      • từ quan sát [1], ta có thể sử dụng teacher forcing ở cả training time và test time. Tại training time, tỉ lệ teacher forcing mình sử dụng là 50%. Tại test time, tỉ lệ teacher forcing mình sử dụng là 100%.
    • masked softmax:
      • từ quan sát [2], ta có thể mask một số giá trị của lớp linear cuối cùng (trước khi đi qua hàm softmax). Ví dụ, đối với kí tự “i”, ta mask các giá trị tương ứng với các kí tự không phải “i, í, ì, ỉ, ĩ, ị” về “-inf” để giá trị softmax của nó bằng 0.
      • tương tự như teacher forcing, masked softmax có thể được sử dụng ở cả training time và test time. Tại training time, tỉ lệ masked softmax mình sử dụng là 50%. Tại test time, tỉ lệ masked softmax mình sử dụng là 100%.
    • training:
      • mình trên cho đến khi nào độ chính xác trên tập validation không tăng nữa. Mô hình cuối cùng mình sử dụng được train qua 26 epochs.
      • mô hình này (trước khi sử dụng beam search) đạt 97.119% trên BXH mở.

III. Beam search

  • Mình sử dụng một n-gram language model truyền thống để rank candidate trong beam search:
    • 4-gram, word-level
    • bao gồm cả dấu câu không chuẩn hóa
    • 27.5GB raw text được lấy từ repo của @binhvq (Full TXT V2)
    • từ và các dấu câu được tách bằng regex
    • thư viện mình sử dụng để tính language model là kenlm
      • khi build_binary mình chọn option sử dụng cấu trúc dữ liệu trie thay vì mặc định là probing do máy không đủ RAM. nếu máy >= 32GB RAM thì tác giả khuyến khích sử dụng probing vì nó nhanh hơn trie.
      • để xây dựng một 4-gram LM trên 27.5GB text chỉ mất khoảng hơn 1 tiếng sử dụng kenlm.
  • Từ quan sát [3][4], mình cũng sử dụng một beam search 2 chiều đệ quy đơn giản như sau:

    • phòng trường hợp đệ quy rơi vào vòng lặp vô hạn, mình sử dụng exhaustive search sau 1 số lượng bước nhất định (10)
  • Kết quả:
    • kết quả thực nghiệm trên BXH mở cho thấy mô hình ngôn ngữ 4-gram có dấu câu (99.6%) > mô hình 3-gram có dấu câu (99.2%) > mô hình 3-gram không có dấu câu (99.0%).
    • mình sử dụng beam size rất lớn (500) nên chạy rất lâu, có lẽ đây là điểm yếu lớn nhất trong lời giải của mình. Do một phần mình không có nhiều thời gian để thử nhiều giá trị khác nhau, nên sau khi chốt version code cuối cùng, mình dùng luôn 500 mà không thử các giá trị khác.
    • beam search tách biệt khỏi mô hình seq2seq lúc train nên có thể được dùng được với bất cứ model nào khác.

IV. Reproduce kết quả submission

  • Nếu bạn muốn train lại mô hình từ đầu:
    • Đối với mô hình seq2seq:
      • Download dữ liệu train mình sử dụng tại đây
      • Giữ nguyên random seed và các tham số trong file train.py
    • Đối với mô hình ngôn ngữ
      • Download dữ liệu text raw tại repo của @binhvq
      • Chạy hàm preprocess_lm.py
      • Chạy kenlm trên dữ liệu vừa được xử lí, chọn option 4-gram
      • Giữ nguyên các từ điển, file test.txt và test_cleaned.txt trong thư mục data
      • Giữ nguyên các tham số trong file predict.py
      • Sau khi file prediction được ghi ra, chạy qua script utils.py do BTC cung cấp
  • Nếu bạn muốn sử dụng mô hình mình đã train trước:
    • Download mô hình seq2seq tại đây
    • Download mô hình ngôn ngữ tại đây
    • Giữ nguyên các file vocab trong thư mục checkpoint
    • Giữ nguyên các từ điển, file test.txt và test_cleaned.txt trong thư mục data
    • Giữ nguyên các tham số trong file predict.py
    • Sau khi file prediction được ghi ra, chạy qua script utils.py do BTC cung cấp
  • File prediction sinh ra nên giống hệt file mình đã sử dụng cho submission cuối cùng (99.613% trên BXH mở).

V. Kết luận

  • Mặc dù có độ chính xác tốt, nhưng mô hình của mình chạy rất chậm do beam size lớn. Mình chưa có đủ thời gian để thử nghiệm nhiều beam size khác nhau xem ảnh hưởng của nó tới độ chính xác như thế nào.
  • Mô hình seq2seq của mình chưa được thử nhiều config khác nhau nên chưa biết phần nào đem lại hiệu quả lớn nhất, hoặc phần nào không thực sự có ích (decoder 2 chiều, teacher forcing, hay masked softmax?).
  • Code của mình có một số đoạn không được gọn và chắc chắn còn nhiều thiếu sót, hy vọng được mọi người góp ý.
  • Xin cảm ơn BTC đã tổ chức một cuộc thi nữa nhiều ý nghĩa. Mình cũng muốn cảm ơn anh Khoi Nguyen với đoạn code làm sạch dữ liệu test, github @binhvq với bộ dữ liệu raw text tiếng Việt quý giá, và github @undertheseanlp với bộ từ điển tiếng Việt mình sử dụng trong beam search. Một phần code của mình được lấy từ repo của IBM.

Source code:


#2

Hi Iota,

Nếu bạn sử dụng https://github.com/binhvq/news-corpus thì có khả năng overfit test set. Mình xem qua thì news-corpus v1 đã bao gồm luôn bộ test của cuộc thi.


#3

Hi infinitas9,

Cảm ơn bạn đã phản hồi. Đây là sơ suất của mình khi không check kĩ dữ liệu.


#4

Beamsearch khủng như này mình nghĩ không cần output của model kết quả cũng cao lắm rồi ấy nhỉ


#5

Cái này train hết bao lâu vậy ?


#6

UPDATE 05/08/2019:

Mình chạy lại language model trên tập dữ liệu news-corpus v2 đã bỏ 9356 câu giống hoặc gần giống với tập test của BTC (7640 câu giống hệt; 1716 câu khác mội vài dấu câu hoặc một vài cụm từ).

Corpus mới: https://drive.google.com/open?id=1z5yrUtSbqa3zyFmuXwR25bx3ZIVnZKpp

Language model mới: https://drive.google.com/open?id=1yLBspcx3yo33LhkuuKn9HZXEcdmqgkEs

File prediction mới: https://drive.google.com/open?id=1HKhnBLXCVxVKq5V0ATk5kZBKNpbRO658

File submission mới: https://drive.google.com/open?id=1lRCjjCErOXLCYRDTLtZORmHYTLh4xMi6

Model seq2seq mình chỉ train trên tập 2 triệu câu của BTC nên không thay đổi.

Kết quả file submission có 1630 (trên tổng số 453446) thay đổi so với submission ban đầu của mình (dùng diff). Nếu tất cả thay đổi này từ đúng thành sai thì độ chính xác giảm 0.359%, xuống dưới model của nhóm VietAI. Như vậy model của nhóm VietAI là tốt nhất.

@Van_Long Mình nghĩ trong lời giải của mình beam search đóng vai trò quan trọng hơn model seq2seq. Model seq2seq của mình không tốt hơn model của nhóm VietAI hay của anh Khoi Nguyen. Một phần mình cũng muốn thử nghiệm độ khả thi của mô hình character-level vì mình thấy nhiều đội làm word-level rồi.

@naruto Mình train trong 1 tuần trên 1 card GTX 1070.

Mọi người có thể tham khảo lời giải của nhóm VietAI ở đây: [AiviVN 3 - Vietnamese tone prediction] 2nd place solution

Mình cảm ơn nhóm VietAI đã góp ý cho lời giải của mình. Mĩnh cũng xin lỗi mọi người và BTC vì sơ suất căn bản này.


#7

Mình thấy beam search 2 chiều Iota dùng hay và lạ (mình không rành beam search lắm), bạn có thử khác biệt về performance khi dùng beam search 1 chiều và 2 chiều không?


#8

Mình có thử nghiệm beam search 1 chiều, nhưng với beam size bé hơn rất nhiều (10), và language model dùng để search lúc đó là 3-gram không dấu câu (thay vì 4-gram có dấu câu như phiên bản hiện tại), nên mình cũng không chắc cải tiến thực sự nằm ở đâu. Lúc đó độ chính xác của model tăng từ 97.1% (chưa beam search) lên 97.6%.

Ngay từ đầu mình muốn làm tất cả mọi thứ 2 chiều nên chưa test nhiều các khả năng. Một phần mình cũng muốn thử cái gì đấy mới mới tự nghĩ ra.


#9

Dạ em mới tìm hiểu về ML, có vài chỗ không hiểu lắm, anh có thể giải thích giúp em ở phần Teacher forcing tỉ lệ 0.5 với masked softmax tỉ lệ 0.5 là như thế nào với ạ. Em cảm ơn.


#10

Hi bạn,

Chỗ này có nghĩa là mình chỉ dùng teacher forcing cho 1 nửa số kí tự trong câu, và mask 1 nửa số kí tự trong câu (chọn một cách ngẫu nhiên).

Teacher forcing và masked softmax có thể hiểu như là một cách làm tăng tốc độ hội tụ và tăng độ ổn định của model, nhất là trong những bước training đầu tiên, bằng cách dùng target thực thay vì giá trị được dự đoán bởi model. Có lẽ một bài giải thích trên mạng sẽ dễ hiểu hơn: https://machinelearningmastery.com/teacher-forcing-for-recurrent-neural-networks/. Masked softmax cũng tương tự, nếu kí tự đang cần thêm dấu là “i” thì nó không thể biến thành “o”, nên mình “mask” các giá trị khác “i, ỉ, ĩ, …” lại. Về lý thuyết model cần học được cái này.

Việc mình chỉ áp dụng cho một nửa số kí tự là để khuyến khích model học một nửa còn lại, thay vì phụ thuộc hoàn toàn vào target thực. Con số 0.5 này là con số hay được mọi người sử dụng nên mình chọn luôn thôi, còn nó hoàn toàn có thể là một con số nào đó khác, thường sẽ được đưa vào hyper-parameter search.


#11

Vâng. Với teacher forcing, ban đầu em tưởng là sẽ dùng 1 nửa data dùng teacher forcing, 1 nửa không.