9.5. Dịch Máy và Tập dữ liệu

Đến nay ta đã thấy cách sử dụng mạng nơ-ron hồi tiếp cho các mô hình ngôn ngữ, mà ở đó ta dự đoán token tiếp theo khi biết tất cả token trước đó. Bây giờ ta sẽ xem xét một ứng dụng khác để dự đoán một chuỗi token thay vì chỉ một token đơn lẻ.

Dịch máy (Machine translation - MT) đề cập đến việc dịch tự động một đoạn văn bản từ ngôn ngữ này sang ngôn ngữ khác. Giải quyết bài toán này với các mạng nơ-ron thường được gọi là dịch máy nơ-ron (neural machine translation - NMT). So với các mô hình ngôn ngữ (Section 8.3), trong đó kho ngữ liệu chỉ chứa một ngôn ngữ duy nhất, bộ dữ liệu dịch máy có ít nhất hai ngôn ngữ, ngôn ngữ nguồn và ngôn ngữ đích. Ngoài ra, mỗi câu trong ngôn ngữ nguồn được ánh xạ tới bản dịch tương ứng trong ngôn ngữ đích. Do đó, cách tiền xử lý dữ liệu dịch máy sẽ khác so với mô hình ngôn ngữ. Phần này được dành riêng để trình bày cách tiền xử lý và nạp một tập dữ liệu như vậy vào các minibatch.

from d2l import mxnet as d2l
from mxnet import np, npx, gluon
import os
npx.set_np()

9.5.1. Đọc và Tiền Xử lý Dữ liệu

Trước tiên ta tải xuống bộ dữ liệu chứa một tập các câu tiếng Anh cùng với các bản dịch tiếng Pháp tương ứng. Có thể thấy mỗi dòng chứa một câu tiếng Anh cùng với bản dịch tiếng Pháp tương ứng, cách nhau bởi một dấu TAB.

# Saved in the d2l package for later use
d2l.DATA_HUB['fra-eng'] = (d2l.DATA_URL + 'fra-eng.zip',
                           '94646ad1522d915e7b0f9296181140edcf86a4f5')

# Saved in the d2l package for later use
def read_data_nmt():
    data_dir = d2l.download_extract('fra-eng')
    with open(os.path.join(data_dir, 'fra.txt'), 'r') as f:
        return f.read()

raw_text = read_data_nmt()
print(raw_text[0:106])
Go. Va !
Hi. Salut !
Run!        Cours !
Run!        Courez !
Who?        Qui ?
Wow!        Ça alors !
Fire!       Au feu !
Help!       À l'aide !

Ta sẽ thực hiện một số bước tiền xử lý trên dữ liệu văn bản thô, bao gồm chuyển đổi tất cả ký tự sang chữ thường, thay thế các ký tự khoảng trắng không ngắt (non-breaking space) UTF-8 bằng dấu cách, thêm dấu cách vào giữa các từ và các dấu câu.

# Saved in the d2l package for later use
def preprocess_nmt(text):
    def no_space(char, prev_char):
        return char in set(',.!') and prev_char != ' '

    text = text.replace('\u202f', ' ').replace('\xa0', ' ').lower()
    out = [' ' + char if i > 0 and no_space(char, text[i-1]) else char
           for i, char in enumerate(text)]
    return ''.join(out)

text = preprocess_nmt(raw_text)
print(text[0:95])
go .        va !
hi .        salut !
run !       cours !
run !       courez !
who?        qui ?
wow !       ça alors !
fire !      au feu !

9.5.2. Token hóa

Khác với việc sử dụng ký tự làm token trong Section 8.3, ở đây một token là một từ hoặc dấu câu. Hàm sau đây sẽ token hóa dữ liệu văn bản để trả về sourcetarget là hai danh sách chứa các danh sách token, với source [i] là câu thứ \(i\) trong ngôn ngữ nguồn và target [i] là câu thứ \(i\) trong ngôn ngữ đích. Để việc huấn luyện sau này nhanh hơn, chúng ta chỉ lấy mẫu num_examples cặp câu đầu tiên.

# Saved in the d2l package for later use
def tokenize_nmt(text, num_examples=None):
    source, target = [], []
    for i, line in enumerate(text.split('\n')):
        if num_examples and i > num_examples:
            break
        parts = line.split('\t')
        if len(parts) == 2:
            source.append(parts[0].split(' '))
            target.append(parts[1].split(' '))
    return source, target

source, target = tokenize_nmt(text)
source[0:3], target[0:3]
([['go', '.'], ['hi', '.'], ['run', '!']],
 [['va', '!'], ['salut', '!'], ['cours', '!']])

Dưới đây là biểu đồ tần suất của số lượng token cho mỗi câu. Có thể thấy, trung bình một câu chứa 5 token và hầu hết các câu có ít hơn 10 token.

d2l.set_figsize((3.5, 2.5))
d2l.plt.hist([[len(l) for l in source], [len(l) for l in target]],
             label=['source', 'target'])
d2l.plt.legend(loc='upper right');
../_images/output_machine-translation-and-dataset_vn_d45ae3_9_0.svg

9.5.3. Bộ Từ vựng

Vì các token trong ngôn ngữ nguồn có thể khác với các token trong ngôn ngữ đích, ta cần xây dựng một bộ từ vựng cho mỗi ngôn ngữ. Do ta đang sử dụng các từ để làm token chứ không dùng ký tự, kích thước bộ từ vựng sẽ lớn hơn đáng kể. Ở đây ta sẽ ánh xạ mọi token xuất hiện ít hơn 3 lần vào token <unk> như trong Section 8.2. Ngoài ra, ta cần các token đặc biệt khác như token đệm <pad>, hay token bắt đầu câu <bos>.

src_vocab = d2l.Vocab(source, min_freq=3,
                      reserved_tokens=['<pad>', '<bos>', '<eos>'])
len(src_vocab)
9140

9.5.4. Nạp Dữ liệu

Trong các mô hình ngôn ngữ, mỗi mẫu là một chuỗi có độ dài num_steps từ kho ngữ liệu, mà có thể là một phân đoạn của một câu hoặc trải dài trên nhiều câu. Trong dịch máy, một mẫu bao gồm một cặp câu nguồn và câu đích. Những câu này có thể có độ dài khác nhau, trong khi đó ta cần các mẫu có cùng độ dài để tạo minibatch.

Một cách giải quyết vấn đề này là nếu một câu dài hơn num_steps, ta sẽ cắt bớt độ dài của nó, ngược lại nếu một câu ngắn hơn num_steps, thì ta sẽ đệm thêm token <pad>. Bằng cách này, ta có thể chuyển bất cứ câu nào về một độ dài cố định.

# Saved in the d2l package for later use
def truncate_pad(line, num_steps, padding_token):
    if len(line) > num_steps:
        return line[:num_steps]  # Trim
    return line + [padding_token] * (num_steps - len(line))  # Pad

truncate_pad(src_vocab[source[0]], 10, src_vocab['<pad>'])
[47, 4, 1, 1, 1, 1, 1, 1, 1, 1]

Bây giờ ta có thể chuyển đổi danh sách các câu thành mảng chỉ số có kích thước (num_example, num_steps). Ta cũng ghi lại độ dài của mỗi câu khi không có token đệm, được gọi là độ dài hợp lệ - valid length. Thông tin này có thể được sử dụng bởi một số mô hình. Ngoài ra, ta sẽ thêm các token đặc biệt “<bos>” và “<eos>” vào các câu đích để mô hình biết thời điểm bắt đầu và kết thúc dự đoán.

# Saved in the d2l package for later use
def build_array(lines, vocab, num_steps, is_source):
    lines = [vocab[l] for l in lines]
    if not is_source:
        lines = [[vocab['<bos>']] + l + [vocab['<eos>']] for l in lines]
    array = np.array([truncate_pad(
        l, num_steps, vocab['<pad>']) for l in lines])
    valid_len = (array != vocab['<pad>']).sum(axis=1)
    return array, valid_len

Sau đó, ta có thể xây dựng các minibatch dựa trên các mảng này.

9.5.5. Kết hợp Tất cả lại

Cuối cùng, ta định nghĩa hàm load_data_nmt để trả về iterator cho dữ liệu cùng với các bộ từ vựng cho ngôn ngữ nguồn và ngôn ngữ đích.

# Saved in the d2l package for later use
def load_data_nmt(batch_size, num_steps, num_examples=1000):
    text = preprocess_nmt(read_data_nmt())
    source, target = tokenize_nmt(text, num_examples)
    src_vocab = d2l.Vocab(source, min_freq=3,
                          reserved_tokens=['<pad>', '<bos>', '<eos>'])
    tgt_vocab = d2l.Vocab(target, min_freq=3,
                          reserved_tokens=['<pad>', '<bos>', '<eos>'])
    src_array, src_valid_len = build_array(
        source, src_vocab, num_steps, True)
    tgt_array, tgt_valid_len = build_array(
        target, tgt_vocab, num_steps, False)
    data_arrays = (src_array, src_valid_len, tgt_array, tgt_valid_len)
    data_iter = d2l.load_array(data_arrays, batch_size)
    return src_vocab, tgt_vocab, data_iter

Hãy thử đọc batch đầu tiên.

src_vocab, tgt_vocab, train_iter = load_data_nmt(batch_size=2, num_steps=8)
for X, X_vlen, Y, Y_vlen in train_iter:
    print('X:', X.astype('int32'))
    print('Valid lengths for X:', X_vlen)
    print('Y:', Y.astype('int32'))
    print('Valid lengths for Y:', Y_vlen)
    break
X: [[12  0  4  1  1  1  1  1]
 [ 0 53  4  1  1  1  1  1]]
Valid lengths for X: [3 3]
Y: [[ 2 10 21  0  4  3  1  1]
 [ 2  0  4  3  1  1  1  1]]
Valid lengths for Y: [6 4]

9.5.6. Tóm tắt

  • Dịch máy (machine translation - MT) là việc dịch tự động một đoạn văn bản từ ngôn ngữ này sang ngôn ngữ khác.
  • Ta đọc, tiền xử lý và token hóa bộ dữ liệu từ cả ngôn ngữ nguồn và ngôn ngữ đích.

9.5.7. Bài tập

Tìm và xử lý một bộ dữ liệu dịch máy.

9.5.8. Thảo luận

9.5.9. 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 Duy Du
  • Nguyễn Văn Quang
  • Phạm Minh Đức
  • Lê Khắc Hồng Phúc
  • Nguyễn Văn Cường
  • Phạm Hồng Vinh