В этом посте мы узнаем, как переводить тексты с одного языка на другой, используя нейронный машинный перевод с вниманием, используя python (Keras и энергичное выполнение)
Давайте начнем с теории,
So,
Что такое нейронный машинный перевод?
Нейронная система машинного перевода — это нейронная сеть, которая напрямую моделирует условную вероятность p(y|x) перевода исходного предложения, x1, . . . , xn, к целевому предложению, y1, . . . , мкм3
Он начинает выдавать по одному целевому слову за раз, как показано на рисунке выше. NMT часто представляет собой большую нейронную сеть, обученную сквозным способом и способную хорошо обобщать очень длинные последовательности слов. Это означает, что модели не нужно явно хранить гигантские таблицы фраз и языковые модели, как в случае стандартного МП; следовательно, NMT имеет небольшой объем памяти.
Модель внимания
Модели, основанные на внимании, делятся на две большие категории: глобальные и локальные. Общим для этих двух типов моделей является тот факт, что на каждом временном шаге t в фазе декодирования оба подхода сначала принимают в качестве входных данных скрытое состояние ht на верхнем уровне стековый LSTM. Затем цель состоит в том, чтобы получить вектор контекста ct, который фиксирует соответствующую информацию на стороне источника, чтобы помочь предсказать текущее целевое слово yt. Хотя эти модели различаются тем, как выводится вектор контекста ct, они используют одни и те же последующие шаги.
1 всеобщее внимание
Идея глобальной модели внимания состоит в том, чтобы учитывать все скрытые состояния кодировщика при получении контекстного вектора ct.
2 Модель локального внимания
У глобального внимания есть недостаток, заключающийся в том, что оно должно учитывать все слова на стороне источника для каждого целевого слова, что является дорогостоящим и потенциально может сделать непрактичным перевод более длинных последовательностей, например, абзацев или документов. Чтобы устранить этот недостаток, мы предлагаем механизм локального внимания, который фокусируется только на небольшом подмножестве исходных позиций для целевого слова. Чтобы устранить этот недостаток, мы предлагаем механизм локального внимания, который фокусируется только на небольшом подмножестве исходных позиций для целевого слова.
Предпосылки -
- Python установлен
- Установлены библиотеки Python (Numpy, TensorFlow, SkLearn). (для пошаговой помощи перейдите в раздел Настройка среды этого поста)
Итак, давайте начнем с кода.
1 – Импорт важных библиотек
from __future__ import absolute_import, division, print_function # Import TensorFlow >= 1.10 and enable eager execution import tensorflow as tf tf.enable_eager_execution() import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split import unicodedata import re import numpy as np import os import time print(tf.__version__)#to check the tensorflow version
2- Загрузите и подготовьте набор данных
Мы будем использовать набор языковых данных, предоставленный http://www.manythings.org/anki/. Этот набор данных содержит пары языковых переводов в формате:
Могу я взять эту книгу? ¿Puedo tomar prestado este libro?
Доступны различные языки, но мы будем использовать англо-испанский набор данных. Вы также можете загрузить свою собственную копию. После загрузки набора данных мы предпримем следующие шаги для подготовки данных:
- Добавьте маркеры start и end к каждому предложению.
- Очистите предложения, удалив специальные символы.
- Создайте индекс слова и обратный индекс слова (сопоставление словарей из слова → идентификатор и идентификатор → слово).
- Увеличьте каждое предложение до максимальной длины.
# Download the file path_to_zip = tf.keras.utils.get_file( ‘spa-eng.zip’, origin=’http://download.tensorflow.org/data/spa-eng.zip', extract=True) path_to_file = os.path.dirname(path_to_zip)+”/spa-eng/spa.txt”
3- Некоторая предварительная обработка набора данных
Предварительная обработка включает
- Преобразование файла unicode в ascii
- Создание пробела между словом и следующим за ним знаком препинания
, например: он мальчик. =› Он мальчик. "Ссылка" - Замена всего пробелом, кроме (a-z, A-Z, «.», «?», «!», «,»)
- Добавление начального и конечного токенов к предложению, чтобы модель знала, когда начинать и заканчивать прогнозирование.
- Удаление акцентов
- Очистка предложений
- Возвращает пары слов в формате: [АНГЛИЙСКИЙ, ИСПАНСКИЙ]
- Создание сопоставления слова -> индекса (например, «папа» -> 5) и наоборот. (например, 5 -> «папа») для каждого языка.
(Остальное все написано в комментариях кода для лучшего понимания)
# Converts the unicode file to ascii
def unicode_to_ascii(s): return ‘’.join(c for c in unicodedata.normalize(‘NFD’, s) if unicodedata.category(c) != ‘Mn’) def preprocess_sentence(w): w = unicode_to_ascii(w.lower().strip())# creating a space between a word and the punctuation following it # eg: "he is a boy." => "he is a boy ."
w = re.sub(r”([?.!,¿])”, r” \1 “, w) w = re.sub(r’[“ “]+’, “ “, w)# replacing everything with space except (a-z, A-Z, ".", "?", "!", ",")
w = re.sub(r”[^a-zA-Z?.!,¿]+”, “ “, w) w = w.rstrip().strip()# adding a start and an end token to the sentence # so that the model know when to start and stop predicting.
w = ‘<start> ‘ + w + ‘ <end>’ return w# 1. Remove the accents # 2. Clean the sentences # 3. Return word pairs in the format: [ENGLISH, SPANISH]
def create_dataset(path, num_examples): lines = open(path, encoding='UTF-8').read().strip().split('\n') word_pairs = [[preprocess_sentence(w) for w in l.split('\t')] for l in lines[:num_examples]] return word_pairs# This class creates a word -> index mapping (e.g,. "dad" -> 5) and vice-versa # (e.g., 5 -> "dad") for each language,
class LanguageIndex(): def __init__(self, lang): self.lang = lang self.word2idx = {} self.idx2word = {} self.vocab = set() self.create_index() def create_index(self): for phrase in self.lang: self.vocab.update(phrase.split(' ')) self.vocab = sorted(self.vocab) self.word2idx['<pad>'] = 0 for index, word in enumerate(self.vocab): self.word2idx[word] = index + 1 for word, index in self.word2idx.items(): self.idx2word[index] = word def max_length(tensor): return max(len(t) for t in tensor) def load_dataset(path, num_examples): # creating cleaned input, output pairs pairs = create_dataset(path, num_examples) # index language using the class defined above inp_lang = LanguageIndex(sp for en, sp in pairs) targ_lang = LanguageIndex(en for en, sp in pairs) # Vectorize the input and target languages # Spanish sentences input_tensor = [[inp_lang.word2idx[s] for s in sp.split(' ')] for en, sp in pairs] # English sentences target_tensor = [[targ_lang.word2idx[s] for s in en.split(' ')] for en, sp in pairs] # Calculate max_length of input and output tensor # Here, we'll set those to the longest sentence in the dataset max_length_inp, max_length_tar = max_length(input_tensor), max_length(target_tensor) # Padding the input and output tensor to the maximum length input_tensor = tf.keras.preprocessing.sequence.pad_sequences(input_tensor, maxlen=max_length_inp,padding='post') target_tensor = tf.keras.preprocessing.sequence.pad_sequences(target_tensor,maxlen=max_length_tar,padding='post') return input_tensor, target_tensor, inp_lang, targ_lang, max_length_inp, max_length_tar
4- Получение векторов из функции загрузки набора данных
Попробуйте поэкспериментировать с размером этого набора данных.
num_examples = 30000 input_tensor, target_tensor, inp_lang, targ_lang, max_length_inp, max_length_targ = load_dataset(path_to_file, num_examples)
5- Разделение набора данных на обучающий тест
input_tensor_train, input_tensor_val, target_tensor_train, target_tensor_val = train_test_split(input_tensor, target_tensor, test_size=0.2)
6- Предварительное определение некоторых значений
BUFFER_SIZE = len(input_tensor_train) BATCH_SIZE = 64 N_BATCH = BUFFER_SIZE//BATCH_SIZE embedding_dim = 256 units = 1024 vocab_inp_size = len(inp_lang.word2idx) vocab_tar_size = len(targ_lang.word2idx) dataset = tf.data.Dataset.from_tensor_slices((input_tensor_train, target_tensor_train)).shuffle(BUFFER_SIZE) dataset = dataset.batch(BATCH_SIZE, drop_remainder=True)
7 — Модель кодировщика и декодера
Здесь мы с вниманием реализуем модель кодер-декодер, о которой вы можете прочитать в TensorFlow Учебное пособие по нейронному машинному переводу (seq2seq). На следующей диаграмме показано, что каждому входному слову механизм внимания присваивает вес, который затем используется декодером для предсказания следующего слова в предложении.
Входные данные проходят через модель кодировщика, которая дает нам выходные данные кодировщика формы (batch_size, max_length, hidden_size) и скрытое состояние кодировщика формы (batch_size, hidden_size).
Формы всех векторов на каждом шаге указаны в комментариях в коде:
def gru(units): # If you have a GPU, we recommend using CuDNNGRU(provides a 3x speedup than GRU) # the code automatically does that. if tf.test.is_gpu_available(): return tf.keras.layers.CuDNNGRU(units, return_sequences=True, return_state=True, recurrent_initializer=’glorot_uniform’) else: return tf.keras.layers.GRU(units, return_sequences=True, return_state=True, recurrent_activation=’sigmoid’, recurrent_initializer=’glorot_uniform’) class Encoder(tf.keras.Model): def __init__(self, vocab_size, embedding_dim, enc_units, batch_sz): super(Encoder, self).__init__() self.batch_sz = batch_sz self.enc_units = enc_units self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim) self.gru = gru(self.enc_units) def call(self, x, hidden): x = self.embedding(x) output, state = self.gru(x, initial_state = hidden) return output, state def initialize_hidden_state(self): return tf.zeros((self.batch_sz, self.enc_units)) class Decoder(tf.keras.Model): def __init__(self, vocab_size, embedding_dim, dec_units, batch_sz): super(Decoder, self).__init__() self.batch_sz = batch_sz self.dec_units = dec_units self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim) self.gru = gru(self.dec_units) self.fc = tf.keras.layers.Dense(vocab_size) # used for attention self.W1 = tf.keras.layers.Dense(self.dec_units) self.W2 = tf.keras.layers.Dense(self.dec_units) self.V = tf.keras.layers.Dense(1) def call(self, x, hidden, enc_output): # enc_output shape == (batch_size, max_length, hidden_size) # hidden shape == (batch_size, hidden size) # hidden_with_time_axis shape == (batch_size, 1, hidden size) # we are doing this to perform addition to calculate the score hidden_with_time_axis = tf.expand_dims(hidden, 1) # score shape == (batch_size, max_length, 1) # we get 1 at the last axis because we are applying tanh(FC(EO) + FC(H)) to self.V score = self.V(tf.nn.tanh(self.W1(enc_output) + self.W2(hidden_with_time_axis))) # attention_weights shape == (batch_size, max_length, 1) attention_weights = tf.nn.softmax(score, axis=1) # context_vector shape after sum == (batch_size, hidden_size) context_vector = attention_weights * enc_output context_vector = tf.reduce_sum(context_vector, axis=1) # x shape after passing through embedding == (batch_size, 1, embedding_dim) x = self.embedding(x) # x shape after concatenation == (batch_size, 1, embedding_dim + hidden_size) x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1) # passing the concatenated vector to the GRU output, state = self.gru(x) # output shape == (batch_size * 1, hidden_size) output = tf.reshape(output, (-1, output.shape[2])) # output shape == (batch_size * 1, vocab) x = self.fc(output) return x, state, attention_weights def initialize_hidden_state(self): return tf.zeros((self.batch_sz, self.dec_units)) encoder = Encoder(vocab_inp_size, embedding_dim, units, BATCH_SIZE) decoder = Decoder(vocab_tar_size, embedding_dim, units, BATCH_SIZE)
8- Определение оптимизатора, функции потерь и контрольных точек
optimizer = tf.train.AdamOptimizer() def loss_function(real, pred): mask = 1 — np.equal(real, 0) loss_ = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=real, logits=pred) * mask return tf.reduce_mean(loss_) checkpoint_dir = ‘./training_checkpoints’ checkpoint_prefix = os.path.join(checkpoint_dir, “ckpt”) checkpoint = tf.train.Checkpoint(optimizer=optimizer, encoder=encoder, decoder=decoder)
9- Начнем обучение
- Пропустите ввод через кодировщик, который возвращает выход кодировщика и скрытое состояние кодировщика.
- Выходные данные кодировщика, скрытое состояние кодировщика и входные данные декодера (который является стартовым токеном) передаются декодеру.
- Декодер возвращает прогнозы и скрытое состояние декодера.
- Затем скрытое состояние декодера передается обратно в модель, и прогнозы используются для расчета потерь.
- Используйте принуждение учителя, чтобы выбрать следующий ввод для декодера.
- Принудительное использование учителем – это метод, при котором целевое слово передается в качестве следующего ввода в декодер.
- Последний шаг — рассчитать градиенты и применить их к оптимизатору и обратному распространению.
EPOCHS = 10 for epoch in range(EPOCHS): start = time.time() hidden = encoder.initialize_hidden_state() total_loss = 0 for (batch, (inp, targ)) in enumerate(dataset): loss = 0 with tf.GradientTape() as tape: enc_output, enc_hidden = encoder(inp, hidden) dec_hidden = enc_hidden dec_input = tf.expand_dims([targ_lang.word2idx[‘<start>’]] * BATCH_SIZE, 1) # Teacher forcing — feeding the target as the next input for t in range(1, targ.shape[1]): # passing enc_output to the decoder predictions, dec_hidden, _ = decoder(dec_input, dec_hidden, enc_output) loss += loss_function(targ[:, t], predictions) # using teacher forcing dec_input = tf.expand_dims(targ[:, t], 1) batch_loss = (loss / int(targ.shape[1])) total_loss += batch_loss variables = encoder.variables + decoder.variables gradients = tape.gradient(loss, variables) optimizer.apply_gradients(zip(gradients, variables)) if batch % 100 == 0: print(‘Epoch {} Batch {} Loss {:.4f}’.format(epoch + 1, batch, batch_loss.numpy())) # saving (checkpoint) the model every 2 epochs if (epoch + 1) % 2 == 0: checkpoint.save(file_prefix = checkpoint_prefix) print(‘Epoch {} Loss {:.4f}’.format(epoch + 1, total_loss / N_BATCH)) print(‘Time taken for 1 epoch {} sec\n’.format(time.time() — start))
10- Теперь давайте создадим функцию прогнозирования, которая дает фактический результат
Функция оценки аналогична циклу обучения, за исключением того, что здесь мы не используем принуждение учителя. Входными данными для декодера на каждом временном шаге являются его предыдущие прогнозы, а также скрытое состояние и выходные данные кодировщика.
def evaluate(sentence, encoder, decoder, inp_lang, targ_lang, max_length_inp, max_length_targ): attention_plot = np.zeros((max_length_targ, max_length_inp)) sentence = preprocess_sentence(sentence) inputs = [inp_lang.word2idx[i] for i in sentence.split(‘ ‘)] inputs = tf.keras.preprocessing.sequence.pad_sequences([inputs], maxlen=max_length_inp, padding=’post’) inputs = tf.convert_to_tensor(inputs) result = ‘’ hidden = [tf.zeros((1, units))] enc_out, enc_hidden = encoder(inputs, hidden) dec_hidden = enc_hidden dec_input = tf.expand_dims([targ_lang.word2idx[‘<start>’]], 0) for t in range(max_length_targ): predictions, dec_hidden, attention_weights = decoder(dec_input, dec_hidden, enc_out) # storing the attention weights to plot later on attention_weights = tf.reshape(attention_weights, (-1, )) attention_plot[t] = attention_weights.numpy() predicted_id = tf.argmax(predictions[0]).numpy() result += targ_lang.idx2word[predicted_id] + ‘ ‘ if targ_lang.idx2word[predicted_id] == ‘<end>’: return result, sentence, attention_plot # the predicted ID is fed back into the model dec_input = tf.expand_dims([predicted_id], 0) return result, sentence, attention_plot
11 – Создайте функцию для построения графика веса внимания, а затем создайте связующую функцию для соединения графика и прогнозирования
# function for plotting the attention weights def plot_attention(attention, sentence, predicted_sentence): fig = plt.figure(figsize=(10,10)) ax = fig.add_subplot(1, 1, 1) ax.matshow(attention, cmap=’viridis’) fontdict = {‘fontsize’: 14} ax.set_xticklabels([‘’] + sentence, fontdict=fontdict, rotation=90) ax.set_yticklabels([‘’] + predicted_sentence, fontdict=fontdict) plt.show() def translate(sentence, encoder, decoder, inp_lang, targ_lang, max_length_inp, max_length_targ): result, sentence, attention_plot = evaluate(sentence, encoder, decoder, inp_lang, targ_lang, max_length_inp, max_length_targ) print(‘Input: {}’.format(sentence)) print(‘Predicted translation: {}’.format(result)) attention_plot = attention_plot[:len(result.split(‘ ‘)), :len(sentence.split(‘ ‘))] plot_attention(attention_plot, sentence.split(‘ ‘), result.split(‘ ‘))
12 – Восстановление контрольных точек
# restoring the latest checkpoint in checkpoint_dir checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))
13- ТЕПЕРЬ ПОСЛЕДНЕЕ ЗАДАНИЕ — ПРОГНОЗ!!!
Как видите, мы вводим некоторый текст на испанском языке, и наша модель выводит его английский перевод и график веса внимания.
Научно-исследовательская работа -
Поздравляю! Вы сделали это
пишите комментарии
Получайте удовольствие, продолжайте учиться и всегда продолжайте программировать