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