Введение
Описывает, как реализовать нейронный машинный перевод (NMT) с использованием обучения от последовательности к последовательности (Seq2Seq).
принцип
Ранее мы реализовали сегментацию китайских слов с помощью модели аннотации последовательности, которая относится к типу Seq2Seq.
На этот раз мы используем Seq2Seq для реализации NMT, Поскольку и входное предложение, и выходное предложение содержат несколько слов, а число не обязательно одно и то же, оно соответствует четвертому случаю на рисунке выше.
Самый простой способ - сначала закодировать все входное предложение в векторное представление фиксированной длины, а затем постепенно декодировать и вывести соответствующее переводное предложение.И кодировщик, и декодер могут быть реализованы с использованием RNN.
Вы можете выбрать LSTM или GRU для типа RNN или рассмотреть возможность использования многоуровневого LSTM, двунаправленного LSTM и других расширений.
Также можно рассмотреть механизм внимания.Для вывода, полученного каждым входом входной последовательности, рассчитывается и взвешивается вес внимания.
- Используйте не только выходные данные последнего шага кодировщика, но и выходные данные каждого шага кодировщика, аналогично небольшому блоку при создании подписи к изображению.
- Каждый раз, когда декодер генерирует данные, он сначала вычисляет соответствующий вес внимания в соответствии с соотношением между текущим состоянием декодера и выходными данными каждого шага кодировщика.
- Выходные данные каждого шага кодировщика взвешиваются и суммируются в соответствии с весом для получения контекста, используемого на текущем шаге.
- Декодер обновляет состояние следующего шага в соответствии с контекстом и выходными данными предыдущего шага, а затем получает выходные данные следующего шага.
При расчете веса внимания в основном используются два типа схем реализации: умножение и сложение.Первая называетсяLuong's multiplicative style
, последний называетсяBahdanau's additive style
данные
Используя китайско-английский параллельный корпус, предоставленный сообществом переводчиков Mavericks с открытым исходным кодом,www.niutrans.com/, после сортировки в обучающем наборе 10W пар данных, в проверочном наборе 1K пар данных и в тестовом наборе 400 пар данных.
выполнить
Здесь мы в основном используем API, предоставляемый TensorFlow, для реализации обучения Seq2Seq, поиска внимания и луча и т. Д., См. Следующие реализации проекта,GitHub.com/tensorflow/…
Код состоит из трех частей: обучение, проверка и вывод.
- Обучение: обучите модель на тренировочном наборе и рассчитайте функцию потерь.
- Валидация: проверьте модель на проверочном наборе и рассчитайте функцию потерь.
- Вывод: примените модель к тестовому набору, не вычисляйте функцию потерь, генерируйте последовательности, используя поиск луча, и оценивайте, используя метрику bleu.
загрузить библиотеку
# -*- coding: utf-8 -*-
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.utils import shuffle
from keras.preprocessing.sequence import pad_sequences
import os
from tqdm import tqdm
import pickle
Загрузите китайско-английский словарь, оставьте наиболее распространенные слова 2W, другие слова начинаются с<unk>
выражать
def load_vocab(path):
with open(path, 'r') as fr:
vocab = fr.readlines()
vocab = [w.strip('\n') for w in vocab]
return vocab
vocab_ch = load_vocab('data/vocab.ch')
vocab_en = load_vocab('data/vocab.en')
print(len(vocab_ch), vocab_ch[:20])
print(len(vocab_en), vocab_en[:20])
word2id_ch = {w: i for i, w in enumerate(vocab_ch)}
id2word_ch = {i: w for i, w in enumerate(vocab_ch)}
word2id_en = {w: i for i, w in enumerate(vocab_en)}
id2word_en = {i: w for i, w in enumerate(vocab_en)}
Загрузите обучающий набор, проверочный набор и данные тестового набора, рассчитайте максимальную длину последовательности, соответствующую данным на китайском и английском языках, и заполните соответствующие данные в соответствии с режимом.
def load_data(path, word2id):
with open(path, 'r') as fr:
lines = fr.readlines()
sentences = [line.strip('\n').split(' ') for line in lines]
sentences = [[word2id['<s>']] + [word2id[w] for w in sentence] + [word2id['</s>']]
for sentence in sentences]
lens = [len(sentence) for sentence in sentences]
maxlen = np.max(lens)
return sentences, lens, maxlen
# train: training, no beam search, calculate loss
# eval: no training, no beam search, calculate loss
# infer: no training, beam search, calculate bleu
mode = 'train'
train_ch, len_train_ch, maxlen_train_ch = load_data('data/train.ch', word2id_ch)
train_en, len_train_en, maxlen_train_en = load_data('data/train.en', word2id_en)
dev_ch, len_dev_ch, maxlen_dev_ch = load_data('data/dev.ch', word2id_ch)
dev_en, len_dev_en, maxlen_dev_en = load_data('data/dev.en', word2id_en)
test_ch, len_test_ch, maxlen_test_ch = load_data('data/test.ch', word2id_ch)
test_en, len_test_en, maxlen_test_en = load_data('data/test.en', word2id_en)
maxlen_ch = np.max([maxlen_train_ch, maxlen_dev_ch, maxlen_test_ch])
maxlen_en = np.max([maxlen_train_en, maxlen_dev_en, maxlen_test_en])
print(maxlen_ch, maxlen_en)
if mode == 'train':
train_ch = pad_sequences(train_ch, maxlen=maxlen_ch, padding='post', value=word2id_ch['</s>'])
train_en = pad_sequences(train_en, maxlen=maxlen_en, padding='post', value=word2id_en['</s>'])
print(train_ch.shape, train_en.shape)
elif mode == 'eval':
dev_ch = pad_sequences(dev_ch, maxlen=maxlen_ch, padding='post', value=word2id_ch['</s>'])
dev_en = pad_sequences(dev_en, maxlen=maxlen_en, padding='post', value=word2id_en['</s>'])
print(dev_ch.shape, dev_en.shape)
elif mode == 'infer':
test_ch = pad_sequences(test_ch, maxlen=maxlen_ch, padding='post', value=word2id_ch['</s>'])
test_en = pad_sequences(test_en, maxlen=maxlen_en, padding='post', value=word2id_en['</s>'])
print(test_ch.shape, test_en.shape)
Определите четыре заполнителя для встраивания ввода
X = tf.placeholder(tf.int32, [None, maxlen_ch])
X_len = tf.placeholder(tf.int32, [None])
Y = tf.placeholder(tf.int32, [None, maxlen_en])
Y_len = tf.placeholder(tf.int32, [None])
Y_in = Y[:, :-1]
Y_out = Y[:, 1:]
k_initializer = tf.contrib.layers.xavier_initializer()
e_initializer = tf.random_uniform_initializer(-1.0, 1.0)
embedding_size = 512
hidden_size = 512
if mode == 'train':
batch_size = 128
else:
batch_size = 16
with tf.variable_scope('embedding_X'):
embeddings_X = tf.get_variable('weights_X', [len(word2id_ch), embedding_size], initializer=e_initializer)
embedded_X = tf.nn.embedding_lookup(embeddings_X, X) # batch_size, seq_len, embedding_size
with tf.variable_scope('embedding_Y'):
embeddings_Y = tf.get_variable('weights_Y', [len(word2id_en), embedding_size], initializer=e_initializer)
embedded_Y = tf.nn.embedding_lookup(embeddings_Y, Y_in) # batch_size, seq_len, embedding_size
Определите часть кодировщика, используйте двунаправленный LSTM
def single_cell(mode=mode):
if mode == 'train':
keep_prob = 0.8
else:
keep_prob = 1.0
cell = tf.nn.rnn_cell.BasicLSTMCell(hidden_size)
cell = tf.nn.rnn_cell.DropoutWrapper(cell, input_keep_prob=keep_prob)
return cell
def multi_cells(num_layers):
cells = []
for i in range(num_layers):
cell = single_cell()
cells.append(cell)
return tf.nn.rnn_cell.MultiRNNCell(cells)
with tf.variable_scope('encoder'):
num_layers = 1
fw_cell = multi_cells(num_layers)
bw_cell = multi_cells(num_layers)
bi_outputs, bi_state = tf.nn.bidirectional_dynamic_rnn(fw_cell, bw_cell, embedded_X, dtype=tf.float32,
sequence_length=X_len)
# fw: batch_size, seq_len, hidden_size
# bw: batch_size, seq_len, hidden_size
print('=' * 100, '\n', bi_outputs)
encoder_outputs = tf.concat(bi_outputs, -1)
print('=' * 100, '\n', encoder_outputs) # batch_size, seq_len, 2 * hidden_size
# 2 tuple(fw & bw), 2 tuple(c & h), batch_size, hidden_size
print('=' * 100, '\n', bi_state)
encoder_state = []
for i in range(num_layers):
encoder_state.append(bi_state[0][i]) # forward
encoder_state.append(bi_state[1][i]) # backward
encoder_state = tuple(encoder_state) # 2 tuple, 2 tuple(c & h), batch_size, hidden_size
print('=' * 100)
for i in range(len(encoder_state)):
print(i, encoder_state[i])
Определите часть декодера, используя два слоя LSTM.
with tf.variable_scope('decoder'):
beam_width = 10
memory = encoder_outputs
if mode == 'infer':
memory = tf.contrib.seq2seq.tile_batch(memory, beam_width)
X_len = tf.contrib.seq2seq.tile_batch(X_len, beam_width)
encoder_state = tf.contrib.seq2seq.tile_batch(encoder_state, beam_width)
bs = batch_size * beam_width
else:
bs = batch_size
attention = tf.contrib.seq2seq.LuongAttention(hidden_size, memory, X_len, scale=True) # multiplicative
# attention = tf.contrib.seq2seq.BahdanauAttention(hidden_size, memory, X_len, normalize=True) # additive
cell = multi_cells(num_layers * 2)
cell = tf.contrib.seq2seq.AttentionWrapper(cell, attention, hidden_size, name='attention')
decoder_initial_state = cell.zero_state(bs, tf.float32).clone(cell_state=encoder_state)
with tf.variable_scope('projected'):
output_layer = tf.layers.Dense(len(word2id_en), use_bias=False, kernel_initializer=k_initializer)
if mode == 'infer':
start = tf.fill([batch_size], word2id_en['<s>'])
decoder = tf.contrib.seq2seq.BeamSearchDecoder(cell, embeddings_Y, start, word2id_en['</s>'],
decoder_initial_state, beam_width, output_layer)
outputs, final_context_state, _ = tf.contrib.seq2seq.dynamic_decode(decoder,
output_time_major=True,
maximum_iterations=2 * tf.reduce_max(X_len))
sample_id = outputs.predicted_ids
else:
helper = tf.contrib.seq2seq.TrainingHelper(embedded_Y, [maxlen_en - 1 for b in range(batch_size)])
decoder = tf.contrib.seq2seq.BasicDecoder(cell, helper, decoder_initial_state, output_layer)
outputs, final_context_state, _ = tf.contrib.seq2seq.dynamic_decode(decoder,
output_time_major=True)
logits = outputs.rnn_output
logits = tf.transpose(logits, (1, 0, 2))
print(logits)
Выберите, следует ли определять функцию потерь и оптимизатор в соответствии с режимом
if mode != 'infer':
with tf.variable_scope('loss'):
loss = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=Y_out, logits=logits)
mask = tf.sequence_mask(Y_len, tf.shape(Y_out)[1], tf.float32)
loss = tf.reduce_sum(loss * mask) / batch_size
if mode == 'train':
learning_rate = tf.Variable(0.0, trainable=False)
params = tf.trainable_variables()
grads, _ = tf.clip_by_global_norm(tf.gradients(loss, params), 5.0)
optimizer = tf.train.GradientDescentOptimizer(learning_rate).apply_gradients(zip(grads, params))
Учебная часть кода, после 20 раундов обучения потери при обучении упали с более чем 200 до 52,19, а недоумение упало до 5,53.
sess = tf.Session()
sess.run(tf.global_variables_initializer())
if mode == 'train':
saver = tf.train.Saver()
OUTPUT_DIR = 'model_diy'
if not os.path.exists(OUTPUT_DIR):
os.mkdir(OUTPUT_DIR)
tf.summary.scalar('loss', loss)
summary = tf.summary.merge_all()
writer = tf.summary.FileWriter(OUTPUT_DIR)
epochs = 20
for e in range(epochs):
total_loss = 0
total_count = 0
start_decay = int(epochs * 2 / 3)
if e <= start_decay:
lr = 1.0
else:
decay = 0.5 ** (int(4 * (e - start_decay) / (epochs - start_decay)))
lr = 1.0 * decay
sess.run(tf.assign(learning_rate, lr))
train_ch, len_train_ch, train_en, len_train_en = shuffle(train_ch, len_train_ch, train_en, len_train_en)
for i in tqdm(range(train_ch.shape[0] // batch_size)):
X_batch = train_ch[i * batch_size: i * batch_size + batch_size]
X_len_batch = len_train_ch[i * batch_size: i * batch_size + batch_size]
Y_batch = train_en[i * batch_size: i * batch_size + batch_size]
Y_len_batch = len_train_en[i * batch_size: i * batch_size + batch_size]
Y_len_batch = [l - 1 for l in Y_len_batch]
feed_dict = {X: X_batch, Y: Y_batch, X_len: X_len_batch, Y_len: Y_len_batch}
_, ls_ = sess.run([optimizer, loss], feed_dict=feed_dict)
total_loss += ls_ * batch_size
total_count += np.sum(Y_len_batch)
if i > 0 and i % 100 == 0:
writer.add_summary(sess.run(summary,
feed_dict=feed_dict),
e * train_ch.shape[0] // batch_size + i)
writer.flush()
print('Epoch %d lr %.3f perplexity %.2f' % (e, lr, np.exp(total_loss / total_count)))
saver.save(sess, os.path.join(OUTPUT_DIR, 'nmt'))
Проверить часть кода, недоумение проверочного набора 11,56
if mode == 'eval':
saver = tf.train.Saver()
OUTPUT_DIR = 'model_diy'
saver.restore(sess, tf.train.latest_checkpoint(OUTPUT_DIR))
total_loss = 0
total_count = 0
for i in tqdm(range(dev_ch.shape[0] // batch_size)):
X_batch = dev_ch[i * batch_size: i * batch_size + batch_size]
X_len_batch = len_dev_ch[i * batch_size: i * batch_size + batch_size]
Y_batch = dev_en[i * batch_size: i * batch_size + batch_size]
Y_len_batch = len_dev_en[i * batch_size: i * batch_size + batch_size]
Y_len_batch = [l - 1 for l in Y_len_batch]
feed_dict = {X: X_batch, Y: Y_batch, X_len: X_len_batch, Y_len: Y_len_batch}
ls_ = sess.run(loss, feed_dict=feed_dict)
total_loss += ls_ * batch_size
total_count += np.sum(Y_len_batch)
print('Dev perplexity %.2f' % np.exp(total_loss / total_count))
Выведите часть кода, синий набор тестов равен 0,2069, а сгенерированный результат перевода на английский язык находится в output_test_diy.
if mode == 'infer':
saver = tf.train.Saver()
OUTPUT_DIR = 'model_diy'
saver.restore(sess, tf.train.latest_checkpoint(OUTPUT_DIR))
def translate(ids):
words = [id2word_en[i] for i in ids]
if words[0] == '<s>':
words = words[1:]
if '</s>' in words:
words = words[:words.index('</s>')]
return ' '.join(words)
fw = open('output_test_diy', 'w')
for i in tqdm(range(test_ch.shape[0] // batch_size)):
X_batch = test_ch[i * batch_size: i * batch_size + batch_size]
X_len_batch = len_test_ch[i * batch_size: i * batch_size + batch_size]
Y_batch = test_en[i * batch_size: i * batch_size + batch_size]
Y_len_batch = len_test_en[i * batch_size: i * batch_size + batch_size]
Y_len_batch = [l - 1 for l in Y_len_batch]
feed_dict = {X: X_batch, Y: Y_batch, X_len: X_len_batch, Y_len: Y_len_batch}
ids = sess.run(sample_id, feed_dict=feed_dict) # seq_len, batch_size, beam_width
ids = np.transpose(ids, (1, 2, 0)) # batch_size, beam_width, seq_len
ids = ids[:, 0, :] # batch_size, seq_len
for j in range(ids.shape[0]):
sentence = translate(ids[j])
fw.write(sentence + '\n')
fw.close()
from nmt.utils.evaluation_utils import evaluate
for metric in ['bleu', 'rouge']:
score = evaluate('data/test.en', 'output_test_diy', metric)
print(metric, score / 100)
встроенное колесо
Следующие проекты предоставляют очень полный интерфейс,GitHub.com/tensorflow/…, вы можете настроить различные модели с помощью простой настройки, поддержка более 70 элементов конфигурации, приведите несколько примеров
-
--num_units
: количество нейронов в скрытом слое RNN. -
--unit_type
: тип RNN, может быть lstm, gru, layer_norm_lstm, nas -
--num_layers
: количество слоев RNN -
--encoder_type
: тип RNN, который может быть uni, bi, gnmt. -
--residual
: использовать ли остаточные соединения -
--attention
: тип внимания, который может быть luong, scaled_luong, bahdanau, normed_bahdanau или пустым, чтобы не использовать механизм внимания
Если вы считаете элементы конфигурации слишком громоздкими, приведенные выше проекты также предоставляют 4 шаблона элементов конфигурации,iwslt15.json
Подходит для небольших наборов данных (IWSLT английский-вьетнамский, 13W), остальные три шаблона подходят для больших наборов данных (WMT немецкий-английский, 4,5M)
Чтобы обучить китайско-английскую модель с использованием вышеуказанных элементов, просто выполните следующие команды: Если вы обучаете англо-китайскую модель, измените значения src и tgt.
python -m nmt.nmt --src=ch --tgt=en --vocab_prefix=data/vocab --train_prefix=data/train --dev_prefix=data/dev --test_prefix=data/test --out_dir=model_nmt --hparams_path=nmt/standard_hparams/iwslt15.json
К результатам обучения относятся следующие
- Последние пять сохраненных моделей
- train_log включает в себя файлы событий, которые можно просмотреть с помощью tensorboard.
- output_dev и output_test соответствуют результатам перевода набора проверки и набора тестов соответственно.
- Best_bleu включает пять версий модели с наивысшим баллом bleu в проверочном наборе.
Блю модели составляет 0,233 на проверочном наборе и 0,224 на тестовом наборе.
Используйте следующую команду, чтобы сделать вывод, напишите текст для перевода в соответствующий файл, а сгенерированный результат перевода на английский язык находится в output_test_nmt.
python -m nmt.nmt --out_dir=model_nmt --inference_input_file=test.ch --inference_output_file=output_test_nmt
поколение куплетов
Используя следующий набор данных,GitHub.com/Я не 14123/ Минато…, включая 70-ваттные парные данные
Обучите модель следующей командой:iwslt15.json
копировать какcouplet.json
, потому что объем данных больше, поэтому целесообразно увеличить количество тренировок, то есть изменитьnum_train_steps
100000
Не беда, если нет набора валидации, его можно заменить на тестовый набор, потому что если необходимые параметры не заполнены, будет сообщено об ошибке
python -m nmt.nmt --src=in --tgt=out --vocab_prefix=couplet/vocab --train_prefix=couplet/train --dev_prefix=couplet/test --test_prefix=couplet/test --out_dir=model_couplet --hparams_path=nmt/standard_hparams/couplet.json
Некоторые примеры результатов в output_test, каждые три предложения — это верхняя ссылка, нижняя ссылка и сгенерированная нижняя ссылка, а количество слов, часть речи и значение слов в основном правильные.
腾 飞 上 铁 , 锐 意 改 革 谋 发 展 , 勇 当 千 里 马
和 谐 南 供 , 安 全 送 电 保 畅 通 , 争 做 领 头 羊
改 革 开 放 , 科 学 发 展 促 繁 荣 , 争 做 领 头 羊
风 弦 未 拨 心 先 乱
夜 幕 已 沉 梦 更 闲
雪 韵 初 融 意 更 浓
彩 屏 如 画 , 望 秀 美 崤 函 , 花 团 锦 簇
短 信 报 春 , 喜 和 谐 社 会 , 物 阜 民 康
妙 笔 生 花 , 书 辉 煌 史 册 , 虎 啸 龙 吟
Если вам нужно сгенерировать нижнюю ссылку на основе невидимой верхней ссылки для вывода, вы можете использовать метод, описанный ранее.
Ссылаться на
- Учебное пособие по нейронному машинному переводу (seq2seq):GitHub.com/tensorflow/…
- Связанная база данных:GitHub.com/Я не 14123/ Минато…