.. 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)*