.. raw:: html .. _sec_fasttext: Embedding từ con ================ .. raw:: html Các từ tiếng Anh thường có những cấu những trúc nội tại và phương thức cấu thành. Chẳng hạn, ta có thể suy ra mối quan hệ giữa các từ “dog”, “dogs” và “dogcatcher” thông qua cách viết của chúng. Tất cả các từ đó có cùng từ gốc là “dog” nhưng có hậu tố khác nhau làm thay đổi nghĩa của từ. Hơn nữa, sự liên kết này có thể được mở rộng ra đối với các từ khác. Chẳng hạn, mối quan hệ giữa từ “dog” và “dogs” đơn giản giống như mối quan hệ giữa từ “cat” và “cats”. Mối quan hệ giữa từ “boy” và “boyfriend” đơn giản giống mối quan hệ giữa từ “girl” và “girlfriend”. Đặc tính này không phải là duy nhất trong tiếng Anh. Trong tiếng Pháp và Tây Ban Nha, rất nhiều động từ có thể có hơn 40 dạng khác nhau tùy thuộc vào ngữ cảnh. Trong tiếng Phần Lan, một danh từ có thể có hơn 15 dạng. Thật vậy, hình thái học (*morphology*) là một nhánh quan trọng của ngôn ngữ học chuyên nghiên cứu về cấu trúc và hình thái của các từ. .. raw:: html fastText -------- .. raw:: html Trong word2vec, ta không trực tiếp sử dụng thông tin hình thái học. Trong cả mô hình skip-gram và túi từ (*bag-of-word*) liên tục, ta sử dụng các vector khác nhau để biểu diễn các từ ở các dạng khác nhau. Chẳng hạn, “dog” và “dogs” được biểu diễn bởi hai vector khác nhau, trong khi mối quan hệ giữa hai vector đó không biểu thị trực tiếp trong mô hình. Từ quan điểm này, fastText :cite:`Bojanowski.Grave.Joulin.ea.2017` đề xuất phương thức embedding từ con (*subword embedding*), thông qua việc thực hiện đưa thông tin hình thái học vào trong mô hình skip-gram trong word2vec. .. raw:: html Trong fastText, mỗi từ trung tâm được biểu diễn như một tập hợp của các từ con. Dưới đây ta sử dụng từ “where” làm ví dụ để hiểu cách các từ tố được tạo thành. Trước hết, ta thêm một số ký tự đặc biệt “<” và “>” vào phần bắt đầu và kết thúc của từ để phân biệt các từ con được dùng làm tiền tố và hậu tố. Rồi ta sẽ xem từ này như một chuỗi các ký tự để trích xuất :math:`n`-grams. Chẳng hạn, khi :math:`n=3`, ta có thể nhận tất cả từ tố với chiều dài là :math:`3`: .. math:: \textrm{""}, .. raw:: html và từ con đặc biệt :math:`\textrm{""}`. .. raw:: html Trong fastText, với một từ :math:`w`, ta ghi tập hợp của tất cả các từ con của nó với chiều dài từ :math:`3` đến :math:`6` và các từ con đặc biệt là :math:`\mathcal{G}_w`. Do đó, từ điển này là tập hợp các từ con của tất cả các từ. Giả sử vector của từ con :math:`g` trong từ điển này là :math:`\mathbf{z}_g`. Thì vector từ trung tâm :math:`\mathbf{u}_w` cho từ :math:`w` trong mô hình skip-gram có thể biểu diễn là .. math:: \mathbf{u}_w = \sum_{g\in\mathcal{G}_w} \mathbf{z}_g. .. raw:: html Phần còn lại của tiến trình xử lý trong fastText đồng nhất với mô hình skip-gram, vì vậy ta không mô tả lại ở đây. Như chúng ta có thể thấy, so sánh với mô hình skip-gram, từ điển của fastText lớn hơn dẫn tới nhiều tham số mô hình hơn. Hơn nữa, vector của một từ đòi hỏi tính tổng của tất cả vector từ con dẫn tới độ phức tạp tính toán cao hơn. Tuy nhiên, ta có thể thu được các vector tốt hơn cho nhiều từ phức hợp ít thông dụng, thậm chí cho cả các từ không hiện diện trong từ điển này nhờ tham chiếu tới các từ khác có cấu trúc tương tự. .. raw:: html .. _subsec_Byte_Pair_Encoding: Mã hoá cặp byte --------------- .. raw:: html Trong fastText, tất cả các từ con được trích xuất phải nằm trong khoảng độ dài cho trước, ví dụ như từ :math:`3` đến :math:`6`, do đó kích thước bộ từ vựng không thể được xác định trước. Để cho phép các từ con có độ dài biến thiên trong bộ từ vựng có kích thước cố định, chúng ta có thể áp dụng thuật toán nén gọi là *mã hoá cặp byte* (*Byte Pair Encoding* -BPE) để trích xuất các từ con :cite:`Sennrich.Haddow.Birch.2015`. .. raw:: html Mã hóa cặp byte thực hiện phân tích thống kê tập dữ liệu huấn luyện để tìm các ký hiệu chung trong một từ, chẳng hạn như các ký tự liên tiếp có độ dài tùy ý. Bắt đầu từ các ký hiệu có độ dài bằng :math:`1`, mã hóa cặp byte lặp đi lặp lại việc gộp các cặp ký hiệu liên tiếp thường gặp nhất để tạo ra các ký hiệu mới dài hơn. Lưu ý rằng để tăng hiệu năng, các cặp vượt qua ranh giới từ sẽ không được xét. Cuối cùng, chúng ta có thể sử dụng các ký hiệu đó như từ con để phân đoạn các từ. Mã hóa cặp byte và các biến thể của nó đã được sử dụng để biểu diễn đầu vào trong các mô hình tiền huấn luyện cho xử lý ngôn ngữ tự nhiên phổ biến như GPT-2 :cite:`Radford.Wu.Child.ea.2019` và RoBERTa :cite:`Liu.Ott.Goyal.ea.2019`. Tiếp theo, chúng tôi sẽ minh hoạ cách hoạt động của mã hoá cặp byte. .. raw:: html Đầu tiên, ta khởi tạo bộ từ vựng của các ký hiệu dưới dạng tất cả các ký tự viết thường trong tiếng Anh và hai ký hiệu đặc biệt: ký hiệu kết thúc của từ ``'_'`` , và ký hiệu không xác định ``'[UNK]'``. .. code:: python import collections symbols = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '_', '[UNK]'] .. raw:: html Vì không xét các cặp ký hiệu vượt qua ranh giới của các từ, chúng ta chỉ cần một từ điển ``raw_token_freqs`` ánh xạ các từ tới tần suất của chúng (số lần xuất hiện) trong một tập dữ liệu. Lưu ý rằng ký hiệu đặc biệt ``'_'`` được thêm vào mỗi từ để có thể dễ dàng khôi phục chuỗi từ (ví dụ: “a taller man”) từ chuỗi ký hiệu đầu ra (ví dụ: “a\_ tall er\_ man”). Vì chúng ta bắt đầu quá trình gộp một từ vựng chỉ gồm các ký tự đơn và các ký hiệu đặc biệt, khoảng trắng được chèn giữa mọi cặp ký tự liên tiếp trong mỗi từ (các khóa của từ điển ``token_freqs``). Nói cách khác, khoảng trắng là ký tự phân cách (*delimiter*) giữa các ký hiệu trong một từ. .. code:: python raw_token_freqs = {'fast_': 4, 'faster_': 3, 'tall_': 5, 'taller_': 4} token_freqs = {} for token, freq in raw_token_freqs.items(): token_freqs[' '.join(list(token))] = raw_token_freqs[token] token_freqs .. parsed-literal:: :class: output {'f a s t _': 4, 'f a s t e r _': 3, 't a l l _': 5, 't a l l e r _': 4} .. raw:: html Chúng ta định nghĩa hàm ``get_max_freq_pair`` trả về cặp ký hiệu liên tiếp thường gặp nhất trong một từ, với từ là các khóa của từ điển đầu vào ``token_freqs``. .. code:: python def get_max_freq_pair(token_freqs): pairs = collections.defaultdict(int) for token, freq in token_freqs.items(): symbols = token.split() for i in range(len(symbols) - 1): # Key of `pairs` is a tuple of two consecutive symbols pairs[symbols[i], symbols[i + 1]] += freq return max(pairs, key=pairs.get) # Key of `pairs` with the max value .. raw:: html Là một thuật toán tham lam dựa trên tần suất của các ký hiệu liên tiếp nhau, mã hoá cặp byte sẽ dùng hàm ``merge_symbols`` để gộp cặp ký hiệu thường gặp nhất để tạo ra những ký hiệu mới. .. code:: python def merge_symbols(max_freq_pair, token_freqs, symbols): symbols.append(''.join(max_freq_pair)) new_token_freqs = dict() for token, freq in token_freqs.items(): new_token = token.replace(' '.join(max_freq_pair), ''.join(max_freq_pair)) new_token_freqs[new_token] = token_freqs[token] return new_token_freqs .. raw:: html Bây giờ ta thực hiện vòng lặp giải thuật biểu diễn cặp byte với các khóa của từ điển ``token_freqs``. Ở vòng lặp đầu tiên, cặp biểu tượng liền kề có tần suất cao nhất là ``'t'`` và ``'a'``, do đó biểu diễn cặp byte ghép chúng lại để tạo ra một biểu tượng mới là ``'ta'``. Ở vòng lặp thứ hai, biểu diễn cặp byte tiếp tục ghép 2 biểu tượng ``'ta'`` và ``'l'`` tạo ra một biểu tượng mới khác là ``'tal'``. .. code:: python num_merges = 10 for i in range(num_merges): max_freq_pair = get_max_freq_pair(token_freqs) token_freqs = merge_symbols(max_freq_pair, token_freqs, symbols) print(f'merge #{i + 1}:', max_freq_pair) .. parsed-literal:: :class: output merge #1: ('t', 'a') merge #2: ('ta', 'l') merge #3: ('tal', 'l') merge #4: ('f', 'a') merge #5: ('fa', 's') merge #6: ('fas', 't') merge #7: ('e', 'r') merge #8: ('er', '_') merge #9: ('tall', '_') merge #10: ('fast', '_') .. raw:: html Sau 10 vòng lặp biểu diễn cặp byte, ta có thể thấy là danh sách ``symbols`` lúc này chứa hơn 10 biểu tượng đã được lần lượt ghép từ các biểu tượng khác. .. code:: python print(symbols) .. parsed-literal:: :class: output ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '_', '[UNK]', 'ta', 'tal', 'tall', 'fa', 'fas', 'fast', 'er', 'er_', 'tall_', 'fast_'] .. raw:: html Với cùng tập dữ liệu đặc tả trong các khóa của từ điển ``raw_token_freqs``, mỗi từ trong tập dữ liệu này bây giờ được phân đoạn bởi các từ con là “fast\_”, “fast”, “er\_”, “tall\_”, và “tall” theo giải thuật biểu diễn cặp byte. Chẳng hạn, từ “faster\_” và từ “taller\_” được phân đoạn lần lượt là “fast er\_” và “tall er\_”. .. code:: python print(list(token_freqs.keys())) .. parsed-literal:: :class: output ['fast_', 'fast er_', 'tall_', 'tall er_'] .. raw:: html Chú ý là kết quả của biểu diễn cặp byte tùy thuộc vào tập dữ liệu đang được sử dụng. Ta cũng có thể dùng các từ con đã học từ một tập dữ liệu để phân đoạn các từ của một tập dữ liệu khác. Với cách tiếp cận tham lam, hàm ``segment_BPE`` sau đây cố gắng tách các từ thành các từ con dài nhất có thể từ đối số đầu vào ``symbols``. .. code:: python def segment_BPE(tokens, symbols): outputs = [] for token in tokens: start, end = 0, len(token) cur_output = [] # Segment token with the longest possible subwords from symbols while start < len(token) and start < end: if token[start: end] in symbols: cur_output.append(token[start: end]) start = end end = len(token) else: end -= 1 if start < len(token): cur_output.append('[UNK]') outputs.append(' '.join(cur_output)) return outputs .. raw:: html Trong phần tiếp theo, ta sử dụng các từ con trong danh sách ``symbols`` đã được học từ tập dữ liệu ở trên để phân đoạn các ``tokens`` biểu diễn tập dữ liệu khác. .. code:: python tokens = ['tallest_', 'fatter_'] print(segment_BPE(tokens, symbols)) .. parsed-literal:: :class: output ['tall e s t _', 'fa t t er_'] Tóm tắt ------- .. raw:: html - FastText đề xuất phương pháp embedding cho từ con. Dựa trên mô hình skip-gram trong word2vec, phương pháp này biểu diễn vector từ trung tâm thành tổng các vector từ con của từ đó. - Embedding cho từ con sử dụng nguyên tắc trong hình thái học, thường giúp cải thiện chất lượng biểu diễn của các từ ít gặp. - Mã hoá cặp byte thực hiện phân tích thống kê trên tập dữ liệu huấn luyện để phát hiện các ký hiệu chung trong một từ. Là một giải thuật tham lam, mã hoá cặp byte lần lượt gộp các cặp ký hiệu liên tiếp thường gặp nhất lại với nhau. Bài tập ------- .. raw:: html 1. Khi có quá nhiều từ con (ví dụ, 6 từ trong tiếng Anh có thể tạo ra :math:`3\times 10^8` các tổ hợp khác nhau), vấn đề gì sẽ xảy ra? Bạn có thể giải quyết vấn đề trên không? Gợi ý: Tham khảo đoạn cuối phần 3.2 của bài báo fastText [1]. 2. Làm sao để thiết kế một mô hình embedding cho từ con dựa trên mô hình túi từ liên tục CBOW ? 3. Để thu được bộ từ vựng có kích thước :math:`m`, bao nhiêu phép gộp cần được thực hiện khi bộ từ vựng ký hiệu ban đầu có kích thước là :math:`n`? 4. Ta có thể mở rộng ý tưởng của thuật toán mã hoá cặp byte để trích xuất các cụm từ bằng cách nào? Thảo luận --------- - Tiếng Anh: `MXNet `__ - Tiếng Việt: `Diễn đàn Machine Learning Cơ Bản `__ Những người thực hiện --------------------- Bản dịch trong trang này được thực hiện bởi: - Đoàn Võ Duy Thanh - Nguyễn Văn Quang - Nguyễn Lê Quang Nhật - Lê Khắc Hồng Phúc - Phạm Hồng Vinh - Nguyễn Mai Hoàng Long - Phạm Đăng Khoa - Nguyễn Văn Cường *Lần cập nhật gần nhất: 12/09/2020. (Cập nhật lần cuối từ nội dung gốc: 30/06/2020)*