.. _sec_machine_translation:
Tradução Automática e o Conjunto de Dados
=========================================
Usamos RNNs para projetar modelos de linguagem, que são essenciais para
o processamento de linguagem natural. Outro benchmark emblemático é a
*tradução automática*, um domínio de problema central para modelos de
*transdução de sequência* que transformam sequências de entrada em
sequências de saída. Desempenhando um papel crucial em várias aplicações
modernas de IA, modelos de transdução de sequência formarão o foco do
restante deste capítulo e :numref:`chap_attention`. Para este fim,
esta seção apresenta o problema da tradução automática e seu conjunto de
dados que será usado posteriormente.
*Tradução automática* refere-se ao tradução automática de uma sequência
de um idioma para outro. Na verdade, este campo pode remontar a 1940
logo depois que os computadores digitais foram inventados, especialmente
considerando o uso de computadores para decifrar códigos de linguagem na
Segunda Guerra Mundial. Por décadas, abordagens estatísticas tinha sido
dominante neste campo
:cite:`Brown.Cocke.Della-Pietra.ea.1988, Brown.Cocke.Della-Pietra.ea.1990`
antes da ascensão de aprendizagem ponta a ponta usando redes neurais. O
último é frequentemente chamado *tradução automática neural* para se
distinguir de *tradução automática de estatística* que envolve análise
estatística em componentes como o modelo de tradução e o modelo de
linguagem.
Enfatizando o aprendizado de ponta a ponta, este livro se concentrará em
métodos de tradução automática neural. Diferente do nosso problema de
modelo de linguagem in :numref:`sec_language_model` cujo corpus está
em um único idioma, conjuntos de dados de tradução automática são
compostos por pares de sequências de texto que estão em o idioma de
origem e o idioma de destino, respectivamente. Desse modo, em vez de
reutilizar a rotina de pré-processamento para modelagem de linguagem,
precisamos de uma maneira diferente de pré-processar conjuntos de dados
de tradução automática. Na sequência, nós mostramos como carregar os
dados pré-processados em minibatches para treinamento.
.. raw:: html
.. raw:: html
.. code:: python
import os
from mxnet import np, npx
from d2l import mxnet as d2l
npx.set_np()
.. raw:: html
.. raw:: html
.. code:: python
import os
import torch
from d2l import torch as d2l
.. raw:: html
.. raw:: html
.. code:: python
import os
import tensorflow as tf
from d2l import tensorflow as d2l
.. raw:: html
.. raw:: html
Download e Pré-processamento do Conjunto de Dados
-------------------------------------------------
Começar com, baixamos um conjunto de dados inglês-francês que consiste
em `pares de frases bilíngues do Projeto
Tatoeba `__. Cada linha no conjunto de
dados é um par delimitado por tabulação de uma sequência de texto em
inglês e a sequência de texto traduzida em francês. Observe que cada
sequência de texto pode ser apenas uma frase ou um parágrafo de várias
frases. Neste problema de tradução automática onde o inglês é traduzido
para o francês, Inglês é o *idioma de origem* e o francês é o *idioma de
destino*.
.. raw:: html
.. raw:: html
.. code:: python
#@save
d2l.DATA_HUB['fra-eng'] = (d2l.DATA_URL + 'fra-eng.zip',
'94646ad1522d915e7b0f9296181140edcf86a4f5')
#@save
def read_data_nmt():
"""Load the English-French dataset."""
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[:75])
.. parsed-literal::
:class: output
Downloading ../data/fra-eng.zip from http://d2l-data.s3-accelerate.amazonaws.com/fra-eng.zip...
Go. Va !
Hi. Salut !
Run! Cours !
Run! Courez !
Who? Qui ?
Wow! Ça alors !
.. raw:: html
.. raw:: html
.. code:: python
#@save
d2l.DATA_HUB['fra-eng'] = (d2l.DATA_URL + 'fra-eng.zip',
'94646ad1522d915e7b0f9296181140edcf86a4f5')
#@save
def read_data_nmt():
"""Load the English-French dataset."""
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[:75])
.. parsed-literal::
:class: output
Downloading ../data/fra-eng.zip from http://d2l-data.s3-accelerate.amazonaws.com/fra-eng.zip...
Go. Va !
Hi. Salut !
Run! Cours !
Run! Courez !
Who? Qui ?
Wow! Ça alors !
.. raw:: html
.. raw:: html
.. code:: python
#@save
d2l.DATA_HUB['fra-eng'] = (d2l.DATA_URL + 'fra-eng.zip',
'94646ad1522d915e7b0f9296181140edcf86a4f5')
#@save
def read_data_nmt():
"""Load the English-French dataset."""
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[:75])
.. parsed-literal::
:class: output
Downloading ../data/fra-eng.zip from http://d2l-data.s3-accelerate.amazonaws.com/fra-eng.zip...
Go. Va !
Hi. Salut !
Run! Cours !
Run! Courez !
Who? Qui ?
Wow! Ça alors !
.. raw:: html
.. raw:: html
Depois de baixar o conjunto de dados, continuamos com várias etapas de
pré-processamento para os dados de texto brutos. Por exemplo,
substituímos o espaço ininterrupto por espaço, converter letras
maiúsculas em minúsculas, e insira espaço entre palavras e sinais de
pontuação.
.. raw:: html
.. raw:: html
.. code:: python
#@save
def preprocess_nmt(text):
"""Preprocess the English-French dataset."""
def no_space(char, prev_char):
return char in set(',.!?') and prev_char != ' '
# Replace non-breaking space with space, and convert uppercase letters to
# lowercase ones
text = text.replace('\u202f', ' ').replace('\xa0', ' ').lower()
# Insert space between words and punctuation marks
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[:80])
.. parsed-literal::
:class: output
go . va !
hi . salut !
run ! cours !
run ! courez !
who ? qui ?
wow ! ça alors !
.. raw:: html
.. raw:: html
.. code:: python
#@save
def preprocess_nmt(text):
"""Preprocess the English-French dataset."""
def no_space(char, prev_char):
return char in set(',.!?') and prev_char != ' '
# Replace non-breaking space with space, and convert uppercase letters to
# lowercase ones
text = text.replace('\u202f', ' ').replace('\xa0', ' ').lower()
# Insert space between words and punctuation marks
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[:80])
.. parsed-literal::
:class: output
go . va !
hi . salut !
run ! cours !
run ! courez !
who ? qui ?
wow ! ça alors !
.. raw:: html
.. raw:: html
.. code:: python
#@save
def preprocess_nmt(text):
"""Preprocess the English-French dataset."""
def no_space(char, prev_char):
return char in set(',.!?') and prev_char != ' '
# Replace non-breaking space with space, and convert uppercase letters to
# lowercase ones
text = text.replace('\u202f', ' ').replace('\xa0', ' ').lower()
# Insert space between words and punctuation marks
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[:80])
.. parsed-literal::
:class: output
go . va !
hi . salut !
run ! cours !
run ! courez !
who ? qui ?
wow ! ça alors !
.. raw:: html
.. raw:: html
Tokenização
-----------
Diferente da tokenização em nível de personagem
in:numref:\ ``sec_language_model``, para tradução automática preferimos
tokenização em nível de palavra aqui (modelos de última geração podem
usar técnicas de tokenização mais avançadas). A seguinte função
``tokenize_nmt`` tokeniza os primeiros pares de sequência de texto
``num_examples``, Onde cada token é uma palavra ou um sinal de
pontuação. Esta função retorna duas listas de listas de tokens:
``source`` e\ ``target``. Especificamente, ``source [i]`` é uma lista de
tokens do :math:`i^\mathrm{th}` sequência de texto no idioma de origem
(inglês aqui) e ``target [i]`` é a do idioma de destino (francês aqui).
.. raw:: html
.. raw:: html
.. code:: python
#@save
def tokenize_nmt(text, num_examples=None):
"""Tokenize the English-French dataset."""
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[:6], target[:6]
.. parsed-literal::
:class: output
([['go', '.'],
['hi', '.'],
['run', '!'],
['run', '!'],
['who', '?'],
['wow', '!']],
[['va', '!'],
['salut', '!'],
['cours', '!'],
['courez', '!'],
['qui', '?'],
['ça', 'alors', '!']])
.. raw:: html
.. raw:: html
.. code:: python
#@save
def tokenize_nmt(text, num_examples=None):
"""Tokenize the English-French dataset."""
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[:6], target[:6]
.. parsed-literal::
:class: output
([['go', '.'],
['hi', '.'],
['run', '!'],
['run', '!'],
['who', '?'],
['wow', '!']],
[['va', '!'],
['salut', '!'],
['cours', '!'],
['courez', '!'],
['qui', '?'],
['ça', 'alors', '!']])
.. raw:: html
.. raw:: html
.. code:: python
#@save
def tokenize_nmt(text, num_examples=None):
"""Tokenize the English-French dataset."""
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[:6], target[:6]
.. parsed-literal::
:class: output
([['go', '.'],
['hi', '.'],
['run', '!'],
['run', '!'],
['who', '?'],
['wow', '!']],
[['va', '!'],
['salut', '!'],
['cours', '!'],
['courez', '!'],
['qui', '?'],
['ça', 'alors', '!']])
.. raw:: html
.. raw:: html
Deixe-nos representar graficamente o histograma do número de tokens por
sequência de texto. Neste conjunto de dados inglês-francês simples, a
maioria das sequências de texto tem menos de 20 tokens.
.. raw:: html
.. raw:: html
.. code:: python
d2l.set_figsize()
_, _, patches = d2l.plt.hist(
[[len(l) for l in source], [len(l) for l in target]],
label=['source', 'target'])
for patch in patches[1].patches:
patch.set_hatch('/')
d2l.plt.legend(loc='upper right');
.. figure:: output_machine-translation-and-dataset_887557_51_0.svg
.. raw:: html
.. raw:: html
.. code:: python
d2l.set_figsize()
_, _, patches = d2l.plt.hist(
[[len(l) for l in source], [len(l) for l in target]],
label=['source', 'target'])
for patch in patches[1].patches:
patch.set_hatch('/')
d2l.plt.legend(loc='upper right');
.. figure:: output_machine-translation-and-dataset_887557_54_0.svg
.. raw:: html
.. raw:: html
.. code:: python
d2l.set_figsize()
_, _, patches = d2l.plt.hist(
[[len(l) for l in source], [len(l) for l in target]],
label=['source', 'target'])
for patch in patches[1].patches:
patch.set_hatch('/')
d2l.plt.legend(loc='upper right');
.. figure:: output_machine-translation-and-dataset_887557_57_0.svg
.. raw:: html
.. raw:: html
Vocabulário
-----------
Uma vez que o conjunto de dados da tradução automática consiste em pares
de línguas, podemos construir dois vocabulários para tanto o idioma de
origem quanto o idioma de destino separadamente. Com tokenização em
nível de palavra, o tamanho do vocabulário será significativamente maior
do que usando tokenização em nível de caractere. Para aliviar isso, aqui
tratamos tokens infrequentes que aparecem menos de 2 vezes como o mesmo
token desconhecido (“”). Além disso, especificamos tokens especiais
adicionais como para sequências de preenchimento (“”) com o mesmo
comprimento em minibatches, e para marcar o início (“”) ou o fim
(“”) das sequências. Esses tokens especiais são comumente usados em
tarefas de processamento de linguagem natural.
.. raw:: html
.. raw:: html
.. code:: python
src_vocab = d2l.Vocab(source, min_freq=2,
reserved_tokens=['
', '', ''])
len(src_vocab)
.. parsed-literal::
:class: output
10012
.. raw:: html
.. raw:: html
.. code:: python
src_vocab = d2l.Vocab(source, min_freq=2,
reserved_tokens=['
', '', ''])
len(src_vocab)
.. parsed-literal::
:class: output
10012
.. raw:: html
.. raw:: html
.. code:: python
src_vocab = d2l.Vocab(source, min_freq=2,
reserved_tokens=['
', '', ''])
len(src_vocab)
.. parsed-literal::
:class: output
10012
.. raw:: html
.. raw:: html
.. _subsec_mt_data_loading:
Carregando o Conjunto de Dados
------------------------------
Lembre-se de que na modelagem de linguagem cada exemplo de sequência, ou
um segmento de uma frase ou uma extensão de várias frases, tem um
comprimento fixo. Isso foi especificado pelo ``num_steps`` (número de
etapas de tempo ou tokens) argumento em :numref:`sec_language_model`.
Na tradução automática, cada exemplo é um par de sequências de texto de
origem e destino, onde cada sequência de texto pode ter comprimentos
diferentes.
Para eficiência computacional, ainda podemos processar um minibatch de
sequências de texto ao mesmo tempo por *truncamento* e *preenchimento*.
Suponha que cada sequência no mesmo minibatch deve ter o mesmo
comprimento ``num_steps``. Se uma sequência de texto tiver menos de
tokens ``num_steps``, continuaremos acrescentando a seção especial
“” símbolo ao final até que seu comprimento alcance ``num_steps``.
Por outro lado, vamos truncar a sequência de texto pegando apenas seus
primeiros tokens ``num_steps`` e descartando o restante. Desta maneira,
cada sequência de texto terá o mesmo comprimento para ser carregado em
minibatches do mesmo formato.
A seguinte função ``truncate_pad`` trunca ou preenche sequências de
texto conforme descrito anteriormente.
.. raw:: html
.. raw:: html
.. code:: python
#@save
def truncate_pad(line, num_steps, padding_token):
"""Truncate or pad sequences."""
if len(line) > num_steps:
return line[:num_steps] # Truncate
return line + [padding_token] * (num_steps - len(line)) # Pad
truncate_pad(src_vocab[source[0]], 10, src_vocab['
'])
.. parsed-literal::
:class: output
[47, 4, 1, 1, 1, 1, 1, 1, 1, 1]
.. raw:: html
.. raw:: html
.. code:: python
#@save
def truncate_pad(line, num_steps, padding_token):
"""Truncate or pad sequences."""
if len(line) > num_steps:
return line[:num_steps] # Truncate
return line + [padding_token] * (num_steps - len(line)) # Pad
truncate_pad(src_vocab[source[0]], 10, src_vocab['
'])
.. parsed-literal::
:class: output
[47, 4, 1, 1, 1, 1, 1, 1, 1, 1]
.. raw:: html
.. raw:: html
.. code:: python
#@save
def truncate_pad(line, num_steps, padding_token):
"""Truncate or pad sequences."""
if len(line) > num_steps:
return line[:num_steps] # Truncate
return line + [padding_token] * (num_steps - len(line)) # Pad
truncate_pad(src_vocab[source[0]], 10, src_vocab['
'])
.. parsed-literal::
:class: output
[47, 4, 1, 1, 1, 1, 1, 1, 1, 1]
.. raw:: html
.. raw:: html
Agora definimos uma função para transformar sequências de texto em
minibatches para treinamento. Anexamos o especial “” símbolo ao
final de cada sequência para indicar o fim da sequência. Quando um
modelo está prevendo de gerar um token de sequência após o token, a
geração do “” símbolo pode sugerir que a sequência de saída está
completa. Além do mais, nós também gravamos o comprimento de cada
sequência de texto, excluindo os tokens de preenchimento. Esta
informação será necessária por alguns modelos que nós cobriremos mais
tarde.
.. raw:: html
.. raw:: html
.. code:: python
#@save
def build_array_nmt(lines, vocab, num_steps):
"""Transform text sequences of machine translation into minibatches."""
lines = [vocab[l] for l in lines]
lines = [l + [vocab['
']] for l in lines]
array = np.array([truncate_pad(
l, num_steps, vocab['']) for l in lines])
valid_len = (array != vocab['']).astype(np.int32).sum(1)
return array, valid_len
.. raw:: html
.. raw:: html
.. code:: python
#@save
def build_array_nmt(lines, vocab, num_steps):
"""Transform text sequences of machine translation into minibatches."""
lines = [vocab[l] for l in lines]
lines = [l + [vocab['
']] for l in lines]
array = torch.tensor([truncate_pad(
l, num_steps, vocab['']) for l in lines])
valid_len = (array != vocab['']).type(torch.int32).sum(1)
return array, valid_len
.. raw:: html
.. raw:: html
.. code:: python
#@save
def build_array_nmt(lines, vocab, num_steps):
"""Transform text sequences of machine translation into minibatches."""
lines = [vocab[l] for l in lines]
lines = [l + [vocab['
']] for l in lines]
array = tf.constant([truncate_pad(
l, num_steps, vocab['']) for l in lines])
valid_len = tf.reduce_sum(
tf.cast(array != vocab[''], tf.int32), 1)
return array, valid_len
.. raw:: html
.. raw:: html
Juntando todas as coisas
------------------------
Finalmente, definimos a função ``load_data_nmt`` para retornar o
iterador de dados, junto com os vocabulários do idioma de origem e do
idioma de destino.
.. code:: python
#@save
def load_data_nmt(batch_size, num_steps, num_examples=600):
"""Return the iterator and the vocabularies of the translation dataset."""
text = preprocess_nmt(read_data_nmt())
source, target = tokenize_nmt(text, num_examples)
src_vocab = d2l.Vocab(source, min_freq=2,
reserved_tokens=['', '', ''])
tgt_vocab = d2l.Vocab(target, min_freq=2,
reserved_tokens=['', '', ''])
src_array, src_valid_len = build_array_nmt(source, src_vocab, num_steps)
tgt_array, tgt_valid_len = build_array_nmt(target, tgt_vocab, num_steps)
data_arrays = (src_array, src_valid_len, tgt_array, tgt_valid_len)
data_iter = d2l.load_array(data_arrays, batch_size)
return data_iter, src_vocab, tgt_vocab
Vamos ler o primeiro minibatch do conjunto de dados inglês-francês.
.. raw:: html
.. raw:: html
.. code:: python
train_iter, src_vocab, tgt_vocab = load_data_nmt(batch_size=2, num_steps=8)
for X, X_valid_len, Y, Y_valid_len in train_iter:
print('X:', X.astype(np.int32))
print('valid lengths for X:', X_valid_len)
print('Y:', Y.astype(np.int32))
print('valid lengths for Y:', Y_valid_len)
break
.. parsed-literal::
:class: output
X: [[67 8 4 3 1 1 1 1]
[ 7 84 4 3 1 1 1 1]]
valid lengths for X: [4 4]
Y: [[120 28 5 3 1 1 1 1]
[ 12 16 4 3 1 1 1 1]]
valid lengths for Y: [4 4]
.. raw:: html
.. raw:: html
.. code:: python
train_iter, src_vocab, tgt_vocab = load_data_nmt(batch_size=2, num_steps=8)
for X, X_valid_len, Y, Y_valid_len in train_iter:
print('X:', X.type(torch.int32))
print('valid lengths for X:', X_valid_len)
print('Y:', Y.type(torch.int32))
print('valid lengths for Y:', Y_valid_len)
break
.. parsed-literal::
:class: output
X: tensor([[ 6, 18, 153, 4, 3, 1, 1, 1],
[166, 15, 5, 3, 1, 1, 1, 1]], dtype=torch.int32)
valid lengths for X: tensor([5, 4])
Y: tensor([[ 6, 7, 60, 4, 3, 1, 1, 1],
[ 0, 50, 174, 5, 3, 1, 1, 1]], dtype=torch.int32)
valid lengths for Y: tensor([5, 5])
.. raw:: html
.. raw:: html
.. code:: python
train_iter, src_vocab, tgt_vocab = load_data_nmt(batch_size=2, num_steps=8)
for X, X_valid_len, Y, Y_valid_len in train_iter:
print('X:', tf.cast(X, tf.int32))
print('valid lengths for X:', X_valid_len)
print('Y:', tf.cast(Y, tf.int32))
print('valid lengths for Y:', Y_valid_len)
break
.. parsed-literal::
:class: output
X: tf.Tensor(
[[ 9 28 4 3 1 1 1 1]
[69 17 9 11 3 1 1 1]], shape=(2, 8), dtype=int32)
valid lengths for X: tf.Tensor([4 5], shape=(2,), dtype=int32)
Y: tf.Tensor(
[[ 73 5 3 1 1 1 1 1]
[ 90 172 9 3 1 1 1 1]], shape=(2, 8), dtype=int32)
valid lengths for Y: tf.Tensor([3 4], shape=(2,), dtype=int32)
.. raw:: html
.. raw:: html
Sumário
-------
- Tradução automática refere-se à tradução automática de uma sequência
de um idioma para outro.
- Usando tokenização em nível de palavra, o tamanho do vocabulário será
significativamente maior do que usando tokenização em nível de
caractere. Para aliviar isso, podemos tratar tokens raros como o
mesmo token desconhecido.
- Podemos truncar e preencher sequências de texto para que todas tenham
o mesmo comprimento para serem carregadas em minibatches.
Exercícios
----------
1. Tente valores diferentes do argumento ``num_examples`` na
função\ ``load_data_nmt``. Como isso afeta os tamanhos do vocabulário
do idioma de origem e do idioma de destino?
2. O texto em alguns idiomas, como chinês e japonês, não tem indicadores
de limite de palavras (por exemplo, espaço). A tokenização em nível
de palavra ainda é uma boa ideia para esses casos? Por que ou por que
não?
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
.. raw:: html