В этом посте мы узнаем, как переводить тексты с одного языка на другой, используя нейронный машинный перевод с вниманием, используя python (Keras и энергичное выполнение)

Давайте начнем с теории,

So,

Что такое нейронный машинный перевод?

Нейронная система машинного перевода — это нейронная сеть, которая напрямую моделирует условную вероятность p(y|x) перевода исходного предложения, x1, . . . , xn, к целевому предложению, y1, . . . , мкм3

Он начинает выдавать по одному целевому слову за раз, как показано на рисунке выше. NMT часто представляет собой большую нейронную сеть, обученную сквозным способом и способную хорошо обобщать очень длинные последовательности слов. Это означает, что модели не нужно явно хранить гигантские таблицы фраз и языковые модели, как в случае стандартного МП; следовательно, NMT имеет небольшой объем памяти.

Модель внимания

Модели, основанные на внимании, делятся на две большие категории: глобальные и локальные. Общим для этих двух типов моделей является тот факт, что на каждом временном шаге t в фазе декодирования оба подхода сначала принимают в качестве входных данных скрытое состояние ht на верхнем уровне стековый LSTM. Затем цель состоит в том, чтобы получить вектор контекста ct, который фиксирует соответствующую информацию на стороне источника, чтобы помочь предсказать текущее целевое слово yt. Хотя эти модели различаются тем, как выводится вектор контекста ct, они используют одни и те же последующие шаги.

1 всеобщее внимание

Идея глобальной модели внимания состоит в том, чтобы учитывать все скрытые состояния кодировщика при получении контекстного вектора ct.

2 Модель локального внимания

У глобального внимания есть недостаток, заключающийся в том, что оно должно учитывать все слова на стороне источника для каждого целевого слова, что является дорогостоящим и потенциально может сделать непрактичным перевод более длинных последовательностей, например, абзацев или документов. Чтобы устранить этот недостаток, мы предлагаем механизм локального внимания, который фокусируется только на небольшом подмножестве исходных позиций для целевого слова. Чтобы устранить этот недостаток, мы предлагаем механизм локального внимания, который фокусируется только на небольшом подмножестве исходных позиций для целевого слова.

Предпосылки -

Итак, давайте начнем с кода.

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?

Доступны различные языки, но мы будем использовать англо-испанский набор данных. Вы также можете загрузить свою собственную копию. После загрузки набора данных мы предпримем следующие шаги для подготовки данных:

  1. Добавьте маркеры start и end к каждому предложению.
  2. Очистите предложения, удалив специальные символы.
  3. Создайте индекс слова и обратный индекс слова (сопоставление словарей из слова → идентификатор и идентификатор → слово).
  4. Увеличьте каждое предложение до максимальной длины.
# 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- Некоторая предварительная обработка набора данных

Предварительная обработка включает

  1. Преобразование файла unicode в ascii
  2. Создание пробела между словом и следующим за ним знаком препинания
    , например: он мальчик. =› Он мальчик. "Ссылка"
  3. Замена всего пробелом, кроме (a-z, A-Z, «.», «?», «!», «,»)
  4. Добавление начального и конечного токенов к предложению, чтобы модель знала, когда начинать и заканчивать прогнозирование.
  5. Удаление акцентов
  6. Очистка предложений
  7. Возвращает пары слов в формате: [АНГЛИЙСКИЙ, ИСПАНСКИЙ]
  8. Создание сопоставления слова -> индекса (например, «папа» -> 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- Начнем обучение

  1. Пропустите ввод через кодировщик, который возвращает выход кодировщика и скрытое состояние кодировщика.
  2. Выходные данные кодировщика, скрытое состояние кодировщика и входные данные декодера (который является стартовым токеном) передаются декодеру.
  3. Декодер возвращает прогнозы и скрытое состояние декодера.
  4. Затем скрытое состояние декодера передается обратно в модель, и прогнозы используются для расчета потерь.
  5. Используйте принуждение учителя, чтобы выбрать следующий ввод для декодера.
  6. Принудительное использование учителем – это метод, при котором целевое слово передается в качестве следующего ввода в декодер.
  7. Последний шаг — рассчитать градиенты и применить их к оптимизатору и обратному распространению.
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- ТЕПЕРЬ ПОСЛЕДНЕЕ ЗАДАНИЕ — ПРОГНОЗ!!!

Как видите, мы вводим некоторый текст на испанском языке, и наша модель выводит его английский перевод и график веса внимания.

Научно-исследовательская работа -

Эффективные подходы к нейронному машинному переводу на основе внимания Минх-Танг Луонг, Хье Фам, Кристофер Д. Мэннинг

Поздравляю! Вы сделали это

пишите комментарии

Получайте удовольствие, продолжайте учиться и всегда продолжайте программировать

Мой LinkedIn и Twitter