.. raw:: html
.. _sec_word2vec_pretraining:
Tiền huấn luyện word2vec
========================
.. raw:: html
Trong phần này, ta sẽ huấn luyện một mô hình skip-gram đã được định
nghĩa ở :numref:`sec_word2vec`.
.. raw:: html
Đầu tiên, ta nhập các gói thư viện và mô-đun cần thiết cho thí nghiệm,
và nạp tập dữ liệu PTB.
.. code:: python
from d2l import mxnet as d2l
from mxnet import autograd, gluon, np, npx
from mxnet.gluon import nn
npx.set_np()
batch_size, max_window_size, num_noise_words = 512, 5, 5
data_iter, vocab = d2l.load_data_ptb(batch_size, max_window_size,
num_noise_words)
.. raw:: html
Mô hình Skip-Gram
-----------------
.. raw:: html
Ta sẽ lập trình mô hình skip-gram bằng cách sử dụng các tầng embedding
và phép nhân minibatch. Các phương pháp này cũng thường được sử dụng để
lập trình các ứng dụng xử lý ngôn ngữ tự nhiên khác.
.. raw:: html
Tầng Embedding
~~~~~~~~~~~~~~
.. raw:: html
Để thu được các embedding từ, ta sử dụng tầng embedding, có thể được tạo
bằng một thực thể ``nn.Embedding`` trong Gluon. Trọng số của tầng
embedding là một ma trận có số hàng là kích thước từ điển
(``input_dim``) và số cột là chiều của mỗi vector từ (``output_dim``).
Ta đặt kích thước từ điển bằng :math:`20` và chiều vector từ là
:math:`4`.
.. code:: python
embed = nn.Embedding(input_dim=20, output_dim=4)
embed.initialize()
embed.weight
.. parsed-literal::
:class: output
Parameter embedding0_weight (shape=(20, 4), dtype=float32)
.. raw:: html
Đầu vào của tầng embedding là chỉ số của từ. Khi ta nhập vào chỉ số
:math:`i` của một từ, tầng embedding sẽ trả về vector từ tương ứng là
hàng thứ :math:`i` của ma trận trọng số. Dưới đây ta nhập vào tầng
embedding một chỉ số có kích thước (:math:`2`, :math:`3`). Vì số chiều
vector từ là 4, ta thu được vector từ kích thước (:math:`2`, :math:`3`,
:math:`4`).
.. code:: python
x = np.array([[1, 2, 3], [4, 5, 6]])
embed(x)
.. parsed-literal::
:class: output
array([[[ 0.01438687, 0.05011239, 0.00628365, 0.04861524],
[-0.01068833, 0.01729892, 0.02042518, -0.01618656],
[-0.00873779, -0.02834515, 0.05484822, -0.06206018]],
[[ 0.06491279, -0.03182812, -0.01631819, -0.00312688],
[ 0.0408415 , 0.04370362, 0.00404529, -0.0028032 ],
[ 0.00952624, -0.01501013, 0.05958354, 0.04705103]]])
.. raw:: html
Phép nhân Minibatch
~~~~~~~~~~~~~~~~~~~
.. raw:: html
| Ta có thể nhân các ma trận trong hai minibatch bằng toán tử nhân
minibatch ``batch_dot``. Giả sử batch đầu tiên chứa :math:`n` ma trận
:math:`\mathbf{X}_1, \ldots, \mathbf{X}_n` có kích thước là
:math:`a\times b`, và batch thứ hai chứa :math:`n` ma trận
:math:`\mathbf{Y}_1, \ldots, \mathbf{Y}_n` có kích thước là
:math:`b\times c`. Đầu ra của toán tử nhân ma trận trên hai batch đầu
vào là :math:`n` ma trận
:math:`\mathbf{X}_1\mathbf{Y}_1, \ldots, \mathbf{X}_n\mathbf{Y}_n` có
kích thước là :math:`a\times c`.
| Do đó, với hai tensor có kích thước là (:math:`n`, :math:`a`,
:math:`b`) và (:math:`n`, :math:`b`, :math:`c`), kích thước đầu ra của
toán tử nhân minibatch là (:math:`n`, :math:`a`, :math:`c`).
.. code:: python
X = np.ones((2, 1, 4))
Y = np.ones((2, 4, 6))
npx.batch_dot(X, Y).shape
.. parsed-literal::
:class: output
(2, 1, 6)
.. raw:: html
Tính toán Truyền xuôi của Mô hình Skip-Gram
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. raw:: html
Ở lượt truyền xuôi, đầu vào của mô hình skip-gram chứa chỉ số ``center``
của từ đích trung tâm và chỉ số ``contexts_and_negatives`` được nối lại
từ chỉ số của từ ngữ cảnh và từ nhiễu. Trong đó, biến ``center`` có kích
thước là (kích thước batch, 1), và biến ``contexts_and_negatives`` có
kích thước là (kích thước batch, ``max_len``). Đầu tiên hai biến này
được biến đổi từ chỉ số từ thành vector từ bởi tầng embedding từ, sau đó
đầu ra có kích thước là (kích thước batch, 1, ``max_len``) thu được bằng
phép nhân minibatch. Mỗi phần tử của đầu ra là tích vô hướng của vector
từ đích trung tâm và vector từ ngữ cảnh hoặc vector từ nhiễu.
.. code:: python
def skip_gram(center, contexts_and_negatives, embed_v, embed_u):
v = embed_v(center)
u = embed_u(contexts_and_negatives)
pred = npx.batch_dot(v, u.swapaxes(1, 2))
return pred
.. raw:: html
Hãy xác nhận kích thước đầu ra là (kích thước batch, 1, ``max_len``).
.. code:: python
skip_gram(np.ones((2, 1)), np.ones((2, 4)), embed, embed).shape
.. parsed-literal::
:class: output
(2, 1, 4)
.. raw:: html
Huấn luyện
----------
.. raw:: html
Trước khi huấn luyện mô hình embedding từ, ta cần định nghĩa hàm mất mát
của mô hình.
.. raw:: html
Hàm Mất mát Entropy chéo Nhị phân
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. raw:: html
Theo định nghĩa hàm mất mát trong phương pháp lấy mẫu âm, ta có thể sử
dụng trực tiếp hàm mất mát entropy chéo nhị phân của Gluon
``SigmoidBinaryCrossEntropyLoss``.
.. code:: python
loss = gluon.loss.SigmoidBinaryCrossEntropyLoss()
.. raw:: html
Lưu ý là ta có thể sử dụng biến mặt nạ để chỉ định một phần giá trị dự
đoán và nhãn được dùng khi tính hàm mất mát trong minibatch: khi mặt nạ
bằng 1, giá trị dự đoán và nhãn của vị trí tương ứng sẽ được dùng trong
phép tính hàm mất mát; khi mặt nạ bằng 0, giá trị dự đoán và nhãn của vị
trí tương ứng sẽ không được dùng trong phép tính hàm mất mát. Như đã đề
cập, các biến mặt nạ có thể được sử dụng nhằm tránh ảnh hưởng của vùng
đệm lên phép tính hàm mất mát.
.. raw:: html
Với hai mẫu giống nhau, mặt nạ khác nhau sẽ dẫn đến giá trị mất mát cũng
khác nhau.
.. code:: python
pred = np.array([[.5]*4]*2)
label = np.array([[1, 0, 1, 0]]*2)
mask = np.array([[1, 1, 1, 1], [1, 1, 0, 0]])
loss(pred, label, mask)
.. parsed-literal::
:class: output
array([0.724077 , 0.3620385])
.. raw:: html
Ta có thể chuẩn hóa mất mát trong từng mẫu do các mẫu có độ dài khác
nhau.
.. code:: python
loss(pred, label, mask) / mask.sum(axis=1) * mask.shape[1]
.. parsed-literal::
:class: output
array([0.724077, 0.724077])
.. raw:: html
Khởi tạo Tham số Mô hình
~~~~~~~~~~~~~~~~~~~~~~~~
.. raw:: html
Ta khai báo tầng embedding lần lượt của từ trung tâm và từ ngữ cảnh, và
đặt siêu tham số số chiều của vector từ ``embed_size`` bằng 100.
.. code:: python
embed_size = 100
net = nn.Sequential()
net.add(nn.Embedding(input_dim=len(vocab), output_dim=embed_size),
nn.Embedding(input_dim=len(vocab), output_dim=embed_size))
.. raw:: html
Huấn luyện
~~~~~~~~~~
.. raw:: html
Hàm huấn luyện được định nghĩa như dưới đây. Do có phần đệm nên phép
tính mất mát có một chút khác biệt so với các hàm huấn luyện trước.
.. code:: python
def train(net, data_iter, lr, num_epochs, device=d2l.try_gpu()):
net.initialize(ctx=device, force_reinit=True)
trainer = gluon.Trainer(net.collect_params(), 'adam',
{'learning_rate': lr})
animator = d2l.Animator(xlabel='epoch', ylabel='loss',
xlim=[0, num_epochs])
for epoch in range(num_epochs):
timer = d2l.Timer()
metric = d2l.Accumulator(2) # Sum of losses, no. of tokens
for i, batch in enumerate(data_iter):
center, context_negative, mask, label = [
data.as_in_ctx(device) for data in batch]
with autograd.record():
pred = skip_gram(center, context_negative, net[0], net[1])
l = (loss(pred.reshape(label.shape), label, mask)
/ mask.sum(axis=1) * mask.shape[1])
l.backward()
trainer.step(batch_size)
metric.add(l.sum(), l.size)
if (i+1) % 50 == 0:
animator.add(epoch+(i+1)/len(data_iter),
(metric[0]/metric[1],))
print(f'loss {metric[0] / metric[1]:.3f}, '
f'{metric[1] / timer.stop():.1f} tokens/sec on {str(device)}')
.. raw:: html
Giờ ta có thể huấn luyện một mô hình skip-gram sử dụng phương pháp lấy
mẫu âm.
.. code:: python
lr, num_epochs = 0.01, 5
train(net, data_iter, lr, num_epochs)
.. parsed-literal::
:class: output
loss 0.331, 24813.8 tokens/sec on gpu(0)
.. figure:: output_word2vec-pretraining_vn_8c89dc_23_1.svg
.. raw:: html
Áp dụng Mô hình Embedding Từ
----------------------------
.. raw:: html
Sau khi huấn luyện mô hình embedding từ, ta có thể biểu diễn sự tương tự
về nghĩa giữa các từ dựa trên độ tương tự cô-sin giữa hai vector từ. Có
thể thấy, khi sử dụng mô hình embedding từ đã được huấn luyện, các từ có
nghĩa gần nhất với từ “chip” hầu hết là những từ có liên quan đến chip
xử lý.
.. code:: python
def get_similar_tokens(query_token, k, embed):
W = embed.weight.data()
x = W[vocab[query_token]]
# Compute the cosine similarity. Add 1e-9 for numerical stability
cos = np.dot(W, x) / np.sqrt(np.sum(W * W, axis=1) * np.sum(x * x) + 1e-9)
topk = npx.topk(cos, k=k+1, ret_typ='indices').asnumpy().astype('int32')
for i in topk[1:]: # Remove the input words
print(f'cosine sim={float(cos[i]):.3f}: {vocab.idx_to_token[i]}')
get_similar_tokens('chip', 3, net[0])
.. parsed-literal::
:class: output
cosine sim=0.596: intel
cosine sim=0.466: computer
cosine sim=0.447: hewlett-packard
Tóm tắt
-------
.. raw:: html
Ta có thể tiền huấn luyện một mô hình skip-gram thông qua phương pháp
lấy mẫu âm.
Bài tập
-------
.. raw:: html
1. Đặt ``sparse_grad=True`` khi tạo một đối tượng ``nn.Embedding``. Việc
này có tăng tốc quá trình huấn luyện không? Hãy tra tài liệu của
MXNet để tìm hiểu ý nghĩa của tham số này.
2. Hãy tìm từ đồng nghĩa cho các từ khác.
3. Điều chỉnh các siêu tham số, quan sát và phân tích kết quả thí
nghiệm.
4. Khi tập dữ liệu lớn, ta thường lấy mẫu các từ ngữ cảnh và các từ
nhiễu cho từ đích trung tâm trong minibatch hiện tại chỉ khi cập nhật
tham số mô hình. Nói cách khác, cùng một từ đích trung tâm có thể có
các từ ngữ cảnh và từ nhiễu khác nhau với mỗi epoch khác nhau. Cách
huấn luyện này có lợi ích gì? Hãy thử lập trình phương pháp huấn
luyện này.
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
- Đỗ Trường Giang
- Phạm Minh Đức
- Lê Khắc Hồng Phúc
- Phạm Hồng Vinh
- 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:
21/07/2020)*