[Перевод] Модели Sequence-to-Sequence Ч.2

Всем привет!

Вторая часть перевода, который мы разместили пару недель назад, в рамках подготовки к старту второго потока курса «Data scientist». Впереди ещё один интересный материал и открытый урок.

А пока поехали дальше в дебри моделей.

Модель нейронного перевода

В то время как ядро sequence-to-sequence модели создается функциями из tensorflow/tensorflow/python/ops/seq2seq.py, остается еще пара трюков, использующихся в нашей модели перевода в models/tutorials/rnn/translate/seq2seq_model.py, о которых стоит упомянуть.

3zs9fym7zcpeqweylxlqhknznko.png

Сэмплированный softmax и проекция вывода

Как уже говорилось выше, мы хотим использовать сэмплированный softmax для работы с большим словарем на выходе. Для декодирования из него, придется отслеживать проекцию вывода. И сэмплированные softmax потери, и проекции вывода создаются следующим кодом в seq2seq_model.py.

if num_samples > 0 and num_samples < self.target_vocab_size:
  w_t = tf.get_variable("proj_w", [self.target_vocab_size, size], dtype=dtype)
  w = tf.transpose(w_t)
  b = tf.get_variable("proj_b", [self.target_vocab_size], dtype=dtype)
  output_projection = (w, b)

  def sampled_loss(labels, inputs):
    labels = tf.reshape(labels, [-1, 1])
    # We need to compute the sampled_softmax_loss using 32bit floats to
    # avoid numerical instabilities.
    local_w_t = tf.cast(w_t, tf.float32)
    local_b = tf.cast(b, tf.float32)
    local_inputs = tf.cast(inputs, tf.float32)
    return tf.cast(
        tf.nn.sampled_softmax_loss(
            weights=local_w_t,
            biases=local_b,
            labels=labels,
            inputs=local_inputs,
            num_sampled=num_samples,
            num_classes=self.target_vocab_size),
        dtype)


Во-первых, обратите внимание, что мы создаем сэмплированный softmax, только если количество образцов (512 по умолчанию) меньше, чем целевой размер словаря. Для словарей размером менее 512 лучше использовать стандартную softmax потерю.

Затем, создаем проекцию вывода. Это пара, состоящая из матрицы весов и вектора смещения. При использовании, rnn клетка возвращает векторы формы количества обучающих образцов на size, а не количества обучающих образцов на target_vocab_size. Для восстановления логитов нужно домножить его на матрицу весов и добавить смещение, что и происходит в строчках 124–126 в seq2seq_model.py.

if output_projection is not None:
  for b in xrange(len(buckets)):
    self.outputs[b] = [tf.matmul(output, output_projection[0]) +
                       output_projection[1] for ...]


Bucketing и padding

В дополнении к сэмплированному softmax, наша модель перевода также использует группировку (bucketing) — метод, позволяющий эффективно управлять предложениями разной длины. Для начала, объясним проблему. При переводе с английского на французский, имеем английские предложения разной длины L1 на входе и французские предложения разной длины L2 на выходе. Поскольку английское предложение передается как encoder_inputs, а французское предложение выводится как decoder_inputs (с префиксом символа GO), необходимо создать seq2seq модель для каждой пары (L1, L2+1) длин английского и французского предложений. В итоге получим огромный граф, состоящий из множества похожих подграфов. С другой стороны, мы можем «набить» (pad) каждое предложение специальными PAD символами. И тогда нам понадобится только одна seq2seq модель для «набитых» длин. Но такая модель будет неэффективна на коротких предложениях — придётся кодировать и декодировать множество бесполезных PAD символов.

В качестве компромисса между созданием графа для каждой пары длин и набиванием до единой длины, мы используем определенное число групп (buckets) и набьем каждое предложение до длины группы выше. В translate.py мы используем следующие группы по умолчанию.

buckets = [(5, 10), (10, 15), (20, 25), (40, 50)]

Таким образом, если на вход поступает английское предложение с 3 токенами, а соответствующее ему французское предложение на выходе содержит 6 токенов, то они поступят в первую группу и будут набиты до длины 5 на входе кодера и длины 10 на входе декодера. А если в английском предложении 8 токенов, а в соответствующем французском 18, то они не попадут в группу (10, 15) и будут перенесены в группу (20, 25), то есть английское предложение увеличится до 20 токенов, а французское до 25.

Помните, что при создании входных данных декодера мы добавляем специальный символ GO в ввод. Это происходит в функции get_batch() в seq2seq_model.py, которая также переворачивает английское предложение. Переворот входных данных помог достичь улучшения результатов нейронной модели перевода у Sutskever et al., 2014 (pdf). Чтобы окончательно разобраться, представим, что на входе есть предложение «I go.», разбитое на токены ["I", "go", "."], а на выходе — предложение «Je vais.», разбитое на токены ["Je", "vais", "."]. Они будут добавлены к группе (5, 10), с представление ввода кодера [PAD PAD "." "go" "I"] и ввода декодера [GO "Je" "vais" "." EOS PAD PAD PAD PAD PAD].

Запустим это

Чтобы обучить описанную выше модели, понадобится большой англо-французский корпус. Для тренировки мы будем использовать 10^9 франко-английский корпус с сайта WMT»15, а в качестве рабочей выборки тестовые новости с того же сайта. Оба датасета будут загружены в train_dir, когда будет запущена следующая команда.

python translate.py
  --data_dir [your_data_directory] --train_dir [checkpoints_directory]
  --en_vocab_size=40000 --fr_vocab_size=40000


Вам понадобится 18 гб места на жестком диске и несколько часов на подготовку тренировочного корпуса. Корпус распаковывается, создаются файлы словаря в data_dir,, а после этого корпус токенизируется и конвертируется в целочисленные идентификаторы. Обратите внимание на параметры, отвечающие за размер словаря. В примере выше, все слова за пределами 40 тысяч наиболее часто использующихся будут сконвертированы в токен UNK, обозначающий неизвестное слово. Таким образом, при изменении размеров словаря бинарник заново переформирует корпус по токен-id. После подготовки данных начинается обучение.

Значения, заданные в translate, по умолчанию очень высокие. Большие модели, обучающиеся в течение длительного времени, показывают хорошие результаты, но это может занять слишком много времени или слишком много памяти GPU. Вы можете задать тренировку модели меньших размеров, как в примере ниже.

python translate.py
  --data_dir [your_data_directory] --train_dir [checkpoints_directory]
  --size=256 --num_layers=2 --steps_per_checkpoint=50


Команда выше обучит модель с двумя слоями (по умолчанию их 3), в каждом из которых 256 юнитов (по умолчанию — 1024), с чекпоинтом на каждых 50 шагах (по умолчанию — 200). Поэкспериментируйте с этими параметрами, чтобы понять, модель какого размера подходит для вашего графического процессора.

Во время тренировки, каждый шаг steps_per_checkpoint бинарник будет выдавать статистику прошедших шагов. С параметрами по умолчанию (3 слоя размера 1024) первое сообщение выглядит следующим образом:

global step 200 learning rate 0.5000 step-time 1.39 perplexity 1720.62
  eval: bucket 0 perplexity 184.97
  eval: bucket 1 perplexity 248.81
  eval: bucket 2 perplexity 341.64
  eval: bucket 3 perplexity 469.04
global step 400 learning rate 0.5000 step-time 1.38 perplexity 379.89
  eval: bucket 0 perplexity 151.32
  eval: bucket 1 perplexity 190.36
  eval: bucket 2 perplexity 227.46
  eval: bucket 3 perplexity 238.66


Заметим, что каждый шаг занимает чуть меньше 1.4 секунды, перплексия обучающей выборки и перплексии рабочей выборки в каждой группе. Спустя примерно 30 тысяч шагов, мы видим, как перплексии коротких предложений (группы 0 и 1) становятся однозначными. Тренировочный корпус содержит около 22 миллионов предложений, одна итерация (один прогон тренировочных данных) занимает около 340 тысяч шагов с количеством обучающих образцов в размере 64. На этом этапе модель можно применять для перевода английских предложений на французский, используя опцию --decode.

python translate.py --decode
  --data_dir [your_data_directory] --train_dir [checkpoints_directory]

Reading model parameters from /tmp/translate.ckpt-340000
>  Who is the president of the United States?
 Qui est le président des États-Unis ?


Что дальше?

Пример выше показывает, как самостоятельно создать свой англо-французский переводчик end-to-end. Запустите его и посмотрите как работает модель. Качество приемлемое, но идеальную модель перевода не получить с дефолтными параметрами. Вот несколько вещей, которые можно улучшить.

Во-первых, мы используем примитивную токенизацию, базовую функицю basic_tokenizer в data_utils. Токенизатор получше можно найти на сайте WMT'15. Если использовать его и словарь больших размеров, можно добиться улучшения переводов.

Кроме того, дефолтные параметры модели перевода настроены не идеально. Можно попробовать изменить скорость обучения, затухание, инициализацию весов модели. Также можно заменить стандартный GradientDescentOptimizer в seq2seq_model.py на что-то более продвинутое, например, AdagradOptimizer. Пробуйте и следите за улучшением результата!

И наконец, модель, представленная выше, может использоваться не только для перевода, но и для любой другой sequence-to-sequence задачи. Даже если вы хотите превратить последовательность в дерево, например, сгенерировать дерево разбора, эта модель может выдать state-of-the-art результаты, как показано у Vinyals & Kaiser et al., 2014 (pdf). Так вы можете создать не только переводчик, но и парсер, чат-бот или любую другую программу, которую захотите. Экспериментируйте!

Вот и всё!

Ждём ваши комментарии и вопросы тут или приглашаем задать их преподавателю на открытом уроке.

© Habrahabr.ru