Раскройте потенциал языковых моделей (LLM) с помощью Transformers и TensorFlow. В этом всеобъемлющем учебном пособии вы шаг за шагом познакомитесь с созданием мощных моделей. Получите практические знания, внедрите передовые методы и улучшите свои навыки работы с данными с помощью этого подробного руководства, изучая увлекательный мир LLM.

Этот пост представляет собой введение в Transformer, модель глубокого обучения, представленную в статье Vaswani et al. «Внимание — это все, что вам нужно». Transformer произвел революцию в обработке естественного языка и теперь является основным строительным блоком многих современных моделей.

Пост также включает реализацию TensorFlow Transformer. Он охватывает основные компоненты Transformer, включая механизм самоконтроля, сеть прямой связи и архитектуру кодер-декодер. Реализация использует Keras API в TensorFlow и демонстрирует, как обучить модель на игрушечном наборе данных для машинного перевода.

К концу поста читатели должны хорошо понимать архитектуру Transformer и уметь реализовать ее в TensorFlow. Пост совместим с Google Colaboratory и доступен по этой ссылке:



1. Знакомство с трансформатором

1.1. Генеративно-состязательная сеть против трансформатора:

В предыдущем разделе мы узнали, что такое GAN и как он работает. Теперь давайте разберемся, что такое трансформеры и зачем нам такая штука.

Мы узнали, что GAN — это нейронная сеть, состоящая из двух сетей: генератора и дискриминатора. Генератор пытается создать новые образцы данных, похожие на входные данные, в то время как дискриминатор пытается отличить настоящие образцы данных от поддельных. Две сети обучаются вместе таким образом, что генератор учится создавать более реалистичные выборки, а дискриминатор лучше различает настоящие и поддельные выборки.

С другой стороны, Transformer — это тип архитектуры нейронной сети, который был представлен в области обработки естественного языка (NLP). Он в основном используется для таких задач, как перевод языка, обобщение текста и моделирование языка. Модель Transformer состоит из кодировщика и декодера, которые работают вместе для обработки входных последовательностей и создания выходных последовательностей. Кодер обрабатывает входную последовательность и создает скрытое представление ввода. Затем декодер берет скрытое представление и генерирует выходную последовательность. Transformer использует механизм самоконтроля, который позволяет модели сосредоточиться на разных частях входной последовательности при ее обработке. Кроме того, Transformer использует технику позиционного кодирования для сохранения порядка входной последовательности, что важно для языковых задач.

Хотя обе модели используются для разных задач, они имеют некоторые сходства. И GAN, и Transformer представляют собой модели глубокого обучения, основанные на нейронных сетях и использующие обратное распространение для обучения своих параметров. Кроме того, они оба использовались для создания реалистичных изображений и текста на естественном языке.

Однако ключевое различие между двумя моделями заключается в том, что GAN используется для генеративных задач, а Transformer — для задач, связанных с обработкой естественного языка. GAN генерируют новые выборки, а Transformers преобразуют входные последовательности в выходные последовательности.

1.2. У нас есть RNN и LSTM; зачем нужны трансформаторы?

Хотя RNN и LSTM являются мощными моделями, которые успешно используются во многих задачах обработки естественного языка, у них есть определенные ограничения, которые могут сделать их менее эффективными для определенных задач. Вот несколько причин, по которым Transformers стали важной альтернативой RNN и LSTM:

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

1.2. Компоненты трансформатора

Мы кратко рассказали о компоненте трансформатора; давайте углубимся в это более подробно.

Источник: https://python.plainenglish.io/image-captioning-with-an-end-to-end-transformer-network-8f39e1438cd4

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

  • Кодер. Кодер — это часть архитектуры Transformer, которая обрабатывает входную последовательность и создает скрытое представление последовательности. Входная последовательность сначала преобразуется в последовательность вложений, которые затем подаются в стек идентичных слоев. Каждый уровень в стеке кодировщика состоит из двух подуровней: уровня самоконтроля и уровня прямой связи. Уровень самоконтроля позволяет кодировщику обрабатывать различные части входной последовательности и фиксировать долгосрочные зависимости, в то время как уровень прямой связи применяет нелинейное преобразование к скрытому представлению.
  • Декодер. Декодер — это часть архитектуры Transformer, которая генерирует выходную последовательность на основе скрытого представления, созданного кодировщиком. Как и кодировщик, декодер также состоит из стека идентичных уровней, но каждый уровень имеет три подуровня: уровень самоконтроля, уровень внимания кодер-декодер и уровень прямой связи. Уровень самоконтроля позволяет декодеру обращать внимание на разные части выходной последовательности, в то время как уровень внимания кодер-декодер позволяет декодеру обращать внимание на разные части входной последовательности.
  • Внимание. Внимание — это механизм в нейронных сетях, который позволяет модели выборочно обращать внимание на разные части входных данных при прогнозировании. В архитектуре Transformer внимание используется как в кодере, так и в декодере. Механизм внимания вычисляет взвешенную сумму значений входной последовательности, где веса определяются сходством между запросом и ключами. Механизм внимания позволяет модели сосредоточиться на разных частях входной последовательности в зависимости от поставленной задачи.
  • Механизм самостоятельного внимания. Самостоятельное внимание — это особый тип механизма внимания, который используется в архитектуре Transformer. При самоконтроле входная последовательность преобразуется в последовательность векторов запроса, ключа и значения. Векторы запросов используются для расчета весовых коэффициентов внимания для каждой позиции во входной последовательности на основе сходства между вектором запроса и векторами ключей. Затем векторы значений взвешиваются по весам внимания и суммируются для получения взвешенного представления входной последовательности. Затем это взвешенное представление используется в качестве входных данных для следующего уровня модели. Самостоятельное внимание позволяет модели обращать внимание на различные части входной последовательности и фиксировать долгосрочные зависимости.
  • Позиционное кодирование. Позиционное кодирование — это метод, используемый в архитектуре Transformer для кодирования положения токенов во входной последовательности. Поскольку Transformer не имеет повторяющейся или сверточной структуры, которая может фиксировать порядок входной последовательности, позиционное кодирование добавляется к встраиванию каждого токена, чтобы предоставить модели информацию о положении токена в последовательности. Позиционное кодирование вычисляется с помощью фиксированной функции, учитывающей положение токена в последовательности и размерность встраивания. Затем результат добавляется к внедрению токена, что позволяет модели различать токены, которые появляются в разных позициях во входной последовательности.

Чтобы узнать, как эти компоненты работают в целом, я отсылаю вас к сообщению в блоге Google AI:

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

Источник: Блог Google AI.

Теперь давайте углубимся в код. Я использовал большинство этих кодов на основе официальных руководств от Tensorflow. Я предлагаю пройти это, если вы более опытны в мире машинного обучения. Я немного изменил его, чтобы лучше объяснить коды, чтобы я мог понять и научить!

Учебники можно найти здесь: https://www.tensorflow.org/text/tutorials/transformer

2. Tensorflow-реализация Transformer

2.1. Настройка среды и подготовка данных для обучения

Во-первых, давайте импортируем необходимые библиотеки для построения и обучения модели трансформатора.

!pip install -q -U tensorflow-text tensorflow
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.0/6.0 MB 63.5 MB/s eta 0:00:00
import logging
import time

import numpy as np
import matplotlib.pyplot as plt

import tensorflow_datasets as tfds
import tensorflow as tf

import tensorflow_text

Давайте загрузим набор данных TED Talks для Перевод с португальского на английский с использованием наборов данных TensorFlow (TFDS):

Функция tfds.load() используется для загрузки набора данных. В функцию передаются следующие аргументы:

  • 'ted_hrlr_translate/pt_to_en': Здесь указывается имя загружаемого набора данных, который представляет собой набор данных TED Talks для перевода с португальского на английский язык.
  • with_info=True: Это указывает, что дополнительные метаданные о наборе данных должны быть возвращены вместе с самим набором данных.
  • as_supervised=True: Это указывает, что набор данных должен быть возвращен в виде кортежа пар (ввод, цель), где вход — это предложение на португальском языке, а цель — соответствующий перевод на английский язык.
examples, metadata = tfds.load('ted_hrlr_translate/pt_to_en',
                               with_info=True,
                               as_supervised=True)

train_examples, val_examples = examples['train'], examples['validation']

Давайте распечатаем первую партию примеров из набора данных перевода с португальского на английский, загруженного с помощью наборов данных TensorFlow (TFDS).

Вызов функции train_examples.batch(3).take(1) группирует набор данных в группы по три примера, а затем берет первый пакет. Это означает, что код распечатает первые три примера в наборе данных.

Затем код перебирает примеры в пакете и распечатывает каждый пример на португальском и английском языках. Вызов функции .decode('utf-8') используется для преобразования строк байтов в наборе данных в удобочитаемый текст.

for pt_examples, en_examples in train_examples.batch(3).take(1):
  print('> Examples in Portuguese:')
  for pt in pt_examples.numpy():
    print(pt.decode('utf-8'))
  print()

  print('> Examples in English:')
  for en in en_examples.numpy():
    print(en.decode('utf-8'))
> Examples in Portuguese:
e quando melhoramos a procura , tiramos a única vantagem da impressão , que é a serendipidade .
mas e se estes fatores fossem ativos ?
mas eles não tinham a curiosidade de me testar .
> Examples in English:
and when you improve searchability , you actually take away the one advantage of print , which is serendipity .
but what if it were active ?
but they did n't test for curiosity .

2.2. Настроить токенизатор

Теперь пришло время токенизировать наш текст.

Давайте загрузим и загрузим токенизаторы, используемые для модели перевода с португальского на английский, предоставленной TensorFlow.

Этот учебник следует основному руководству с веб-сайта tensorflow и использует токенизаторы, встроенные в учебник Токенизатор подслов. В этом руководстве два объекта text.BertTokenizer (один для английского и один для португальского) оптимизируются для этого набора данных и экспортируются в формат TensorFlow saved_model.

Функция tf.keras.utils.get_file() используется для загрузки заархивированной версии токенизаторов с веб-сайта TensorFlow. Первый аргумент указывает имя загружаемого файла, а второй аргумент указывает URL-адрес, с которого следует загрузить файл. Аргумент cache_dir указывает каталог, в котором следует кэшировать загруженный файл, а cache_subdir указывает подкаталог, в котором хранится файл. Аргумент извлечения указывает, следует ли извлекать содержимое загруженного zip-файла.

model_name = 'ted_hrlr_translate_pt_en_converter'
tf.keras.utils.get_file(
    f'{model_name}.zip',
    f'https://storage.googleapis.com/download.tensorflow.org/models/{model_name}.zip',
    cache_dir='.', cache_subdir='', extract=True
)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/models/ted_hrlr_translate_pt_en_converter.zip
184801/184801 [==============================] - 0s 1us/step
./ted_hrlr_translate_pt_en_converter.zip

Теперь мы можем использовать функцию tf.saved_model.load() для загрузки токенизаторов из сохраненной модели. Аргумент model_name указывает имя сохраненной модели для загрузки, в данном случае это ted_hrlr_translate_pt_en_converter.

tokenizers = tf.saved_model.load(model_name)

Функция tokenize используется для преобразования группы строк в пакет идентификаторов токенов с дополнением. Перед токенизацией функция разделяет знаки препинания, преобразует все буквы в нижний регистр и нормализует ввод в формат Unicode. Однако, поскольку входные данные уже стандартизированы, эти шаги не видны в коде. Давайте проверим пример до и после токенизатора:

print('> This is a batch of strings:')
for en in en_examples.numpy():
  print(en.decode('utf-8'))
> This is a batch of strings:
and when you improve searchability , you actually take away the one advantage of print , which is serendipity .
but what if it were active ?
but they did n't test for curiosity .
encoded = tokenizers.en.tokenize(en_examples)
print('> This is a padded-batch of token IDs:')
for row in encoded.to_list():
  print(row)
> This is a padded-batch of token IDs:
[2, 72, 117, 79, 1259, 1491, 2362, 13, 79, 150, 184, 311, 71, 103, 2308, 74, 2679, 13, 148, 80, 55, 4840, 1434, 2423, 540, 15, 3]
[2, 87, 90, 107, 76, 129, 1852, 30, 3]
[2, 87, 83, 149, 50, 9, 56, 664, 85, 2512, 15, 3]

Метод detokenize пытается преобразовать идентификаторы токенов в текст, который может быть легко прочитан и понят людьми.

round_trip = tokenizers.en.detokenize(encoded)
print('> This is human-readable text:')
for line in round_trip.numpy():
  print(line.decode('utf-8'))
> This is human-readable text:
and when you improve searchability , you actually take away the one advantage of print , which is serendipity .
but what if it were active ?
but they did n ' t test for curiosity .

Метод нижнего уровня lookup преобразует идентификаторы токенов в текст токена:

print('> This is the text split into tokens:')
tokens = tokenizers.en.lookup(encoded)
tokens
> This is the text split into tokens:
<tf.RaggedTensor [[b'[START]', b'and', b'when', b'you', b'improve', b'search', b'##ability',
  b',', b'you', b'actually', b'take', b'away', b'the', b'one', b'advantage',
  b'of', b'print', b',', b'which', b'is', b's', b'##ere', b'##nd', b'##ip',
  b'##ity', b'.', b'[END]']                                                 ,
 [b'[START]', b'but', b'what', b'if', b'it', b'were', b'active', b'?',
  b'[END]']                                                           ,
 [b'[START]', b'but', b'they', b'did', b'n', b"'", b't', b'test', b'for',
  b'curiosity', b'.', b'[END]']                                          ]>

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

Сначала создается пустой список с именем lengths для хранения длин токенов. Затем для каждой партии из 1024 примеров в обучающем наборе мы можем использовать функции tokenizers.pt.tokenize() и tokenizers.en.tokenize() для токенизации примеров на португальском и английском языках соответственно. Затем функция row_lengths() используется для вычисления количества токенов в каждой строке токенизированных данных, а полученные длины добавляются к списку длин.

После обработки всех пакетов функция np.concatenate() используется для объединения всех длин токенов в один массив numpy с именем all_lengths. Затем этот массив используется для создания гистограммы длин токенов с помощью функции plt.hist().

lengths = []

for pt_examples, en_examples in train_examples.batch(1024):
  pt_tokens = tokenizers.pt.tokenize(pt_examples)
  lengths.append(pt_tokens.row_lengths())
  
  en_tokens = tokenizers.en.tokenize(en_examples)
  lengths.append(en_tokens.row_lengths())
  print('.', end='', flush=True)

all_lengths = np.concatenate(lengths)
plt.hist(all_lengths, np.linspace(0, 500, 101))
plt.ylim(plt.ylim())
avg_length = all_lengths.mean()
plt.plot([avg_length, avg_length], plt.ylim())
max_length = max(all_lengths)
plt.plot([max_length, max_length], plt.ylim())
plt.title(f'Maximum tokens per example: {max_length} and average tokens per example: {avg_length}');

2.3. Настройка конвейера данных

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

Во-первых, предложения на португальском языке размечаются с помощью метода tokenizers.pt.tokenize(), который возвращает неоднородный тензор, представляющий размеченные предложения. Затем код обрезает тензор до максимальной длины синтаксиса MAX_TOKENS using the pt[:, :MAX_TOKENS], которая выбирает первые MAX_TOKENS токенов из каждого предложения. Полученный тензор преобразуется в плотный тензор с заполнением нулями с помощью метода pt.to_tensor().

Английские предложения размечаются и обрезаются аналогичным образом, но с дополнительным шагом. Синтаксис en[:, :(MAX_TOKENS+1)] выбирает первые MAX_TOKENS+1 токены из каждого предложения, включая начальный токен [START] и конечный токен [END]. Тензор en_inputs создается путем выбора всех токенов, кроме последнего, из каждого предложения, в результате чего отбрасывается конечный токен. Тензор en_labels создается путем выбора всех токенов, кроме первого, из каждого предложения, при котором отбрасывается начальный токен.

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

MAX_TOKENS=128
def prepare_batch(pt, en):
    """
    Preprocess a batch of Portuguese and English sentences for training a machine translation model.

    Args:
        pt: A tensor of Portuguese sentences of shape (batch_size,) and dtype tf.string.
        en: A tensor of English sentences of shape (batch_size,) and dtype tf.string.

    Returns:
        A tuple of two tensors representing the input and output sequences for the model, and a tensor of shape
        (batch_size, max_length) representing the ground truth output sequences. The input sequence tensor has shape
        (batch_size, max_length) and dtype tf.int64, and the output sequence tensor has shape (batch_size, max_length)
        and dtype tf.int64.
    """
    pt = tokenizers.pt.tokenize(pt)      # Output is ragged.
    pt = pt[:, :MAX_TOKENS]    # Trim to MAX_TOKENS.
    pt = pt.to_tensor()  # Convert to 0-padded dense Tensor

    en = tokenizers.en.tokenize(en)
    en = en[:, :(MAX_TOKENS+1)]
    en_inputs = en[:, :-1].to_tensor()  # Drop the [END] tokens
    en_labels = en[:, 1:].to_tensor()   # Drop the [START] tokens

    return (pt, en_inputs), en_labels

Теперь давайте возьмем набор данных и преобразуем его в пакеты, готовые для подачи в модель.

Следующая функция перемешивает примеры в наборе данных и объединяет их в пакеты размером BATCH_SIZE. Затем он применяет функцию prepare_batch к каждому пакету, который токенизирует текст и подготавливает входные и выходные последовательности для модели. Наконец, он выполняет предварительную выборку пакетов для повышения производительности во время обучения. Параметр BUFFER_SIZE определяет количество примеров, загружаемых в память для перетасовки. Аргумент tf.data.AUTOTUNE позволяет TensorFlow автоматически настраивать входной конвейер для достижения оптимальной производительности.

BUFFER_SIZE = 20000
BATCH_SIZE = 64
def make_batches(ds):
  """
  This function takes a TensorFlow dataset 'ds' and processes it into batches that are ready to be fed to the model. 

  Parameters:
  ds (tf.data.Dataset): TensorFlow dataset to be processed into batches

  Returns:
  tf.data.Dataset: Processed and batched TensorFlow dataset

  """
  return (
      ds
      .shuffle(BUFFER_SIZE)
      .batch(BATCH_SIZE)
      .map(prepare_batch, tf.data.AUTOTUNE)
      .prefetch(buffer_size=tf.data.AUTOTUNE))

Давайте посмотрим, все ли мы сделали правильно, протестировав набор данных.

# Create training and validation set batches.
train_batches = make_batches(train_examples)
val_batches = make_batches(val_examples)

for (pt, en), en_labels in train_batches.take(1):
  break

print(f'pt.shape: {pt.shape}')
print(f'en_labels.shape: {en_labels.shape}')
pt.shape: (64, 95)
en_labels.shape: (64, 72)

Функция make_batches подготавливает tf.data.Dataset объектов для обучения модели Keras. Ожидается, что модель будет принимать входные данные в виде пар токенизированных португальских и английских последовательностей (pt, en) и прогнозировать английские последовательности, сдвинутые на один токен. Это известно как «принуждение учителя», потому что на каждом временном шаге модель получает истинное значение в качестве входных данных для следующего временного шага независимо от своего предыдущего вывода. Это простой и эффективный способ обучения модели генерации текста, поскольку выходные данные можно вычислять параллельно.

Хотя можно было бы ожидать, что пары input, output будут просто последовательностями Portuguese, English, эта установка добавляет «контекст» к модели, обусловливая ее последовательностью на португальском языке. Можно обучить модель, не обусловливая ее португальской последовательностью, но для этого потребуется написать цикл вывода и передать выходные данные модели обратно на вход. Это медленнее и сложнее в освоении, но может привести к более стабильной модели, поскольку модель должна научиться исправлять свои собственные ошибки во время обучения.

en и en_labels одинаковы, просто смещены на 1:

print(f'en[0][:10]: {en[0][:10]}')
print(f'en_labels[0][:10]: {en_labels[0][:10]}')
en[0][:10]: [   2   96   86  180  369 1127  100  157   15    0]
en_labels[0][:10]: [  96   86  180  369 1127  100  157   15    3    0]

2.4. Определите компоненты

2.4.1. Слой внедрения и позиционного кодирования

Компоненты кодировщика и декодера используют одну и ту же логику для преобразования входных токенов в векторы. Это делается с помощью слоя tf.keras.layers.Embedding, который создает векторное представление для каждого токена во входной последовательности.

Слои внимания в модели не зависят от порядка токенов во входной последовательности, потому что модель не содержит рекуррентных или сверточных слоев, которые по своей сути фиксируют порядок последовательности. Без возможности определить порядок слов модель будет рассматривать входную последовательность как «мешок слов», где порядок токенов не имеет значения. Например, последовательности «как дела», «как дела» и «как дела» будут восприниматься моделью как идентичные.

Чтобы преодолеть эту проблему, модель Transformer добавляет «позиционное кодирование» к векторам встраивания. Позиционное кодирование использует набор синусов и косинусов на разных частотах в последовательности. Каждый токен во входной последовательности имеет уникальное позиционное кодирование, которое фиксирует его позицию в последовательности. Соседние маркеры в последовательности будут иметь аналогичные позиционные кодировки. Включив эту информацию во входное представление, модель может поддерживать последовательный порядок входных токенов и лучше понимать значение предложения.

Формула для расчета позиционного кодирования выглядит следующим образом:

Теперь давайте реализуем это:

Функция positional_encoding генерирует матрицу кодировок позиций для входной последовательности. Целью позиционного кодирования является добавление информации о позиции каждой лексемы в последовательности, чтобы механизм внутреннего внимания в преобразователе мог различать разные позиции лексем.

Функция принимает два аргумента: length, указывающий длину входной последовательности, и depth, указывающий размерность кодирования.

Сначала функция создает две матрицы: positions и depths. position имеет форму (length, 1) и содержит индексы позиций во входной последовательности. depths имеет форму (1, depth/2) и содержит значения от 0 до (depth/2)-1, которые затем нормализуются по depth/2.

Затем функция вычисляет угловые скорости, используя формулу 1 / (10000**depths), которая имеет форму (1, depth/2). Угловые коэффициенты используются для расчета угловых радианов по формуле positions * angle_rates, которая имеет форму (length, depth/2).

Наконец, функция объединяет значения синуса и косинуса угловых радиан вдоль последней оси, чтобы создать матрицу кодирования положения, которая имеет форму (length, depth). Результирующая матрица затем приводится к tf.float32 и возвращается.

def positional_encoding(length, depth):
  """
  Generates a matrix of position encodings for an input sequence.

  Args:
      length: An integer representing the length of the input sequence.
      depth: An integer representing the dimensionality of the encoding.

  Returns:
      A `tf.Tensor` of shape `(length, depth)` representing the position encoding matrix.
  """
  depth = depth/2

  positions = np.arange(length)[:, np.newaxis]     # (seq, 1)
  depths = np.arange(depth)[np.newaxis, :]/depth   # (1, depth)
  
  angle_rates = 1 / (10000**depths)         # (1, depth)
  angle_rads = positions * angle_rates      # (pos, depth)

  pos_encoding = np.concatenate(
      [np.sin(angle_rads), np.cos(angle_rads)],
      axis=-1) 

  return tf.cast(pos_encoding, dtype=tf.float32)

Функция кодирования положения использует ряд синусов и косинусов, которые колеблются с различной частотой в зависимости от того, где они расположены по глубине вектора встраивания. Эти колебания происходят поперек оси положения. Давайте визуализируем это здесь:

#@title
pos_encoding = positional_encoding(length=2048, depth=512)

# Check the shape.
print(pos_encoding.shape)

# Plot the dimensions.
plt.pcolormesh(pos_encoding.numpy().T, cmap='RdBu')
plt.ylabel('Depth')
plt.xlabel('Position')
plt.colorbar()
plt.show()
(2048, 512)

Цель этого графика — визуализировать матрицу позиционного кодирования и увидеть, как она меняется в зависимости от положения и глубины в последовательности. Это также помогает гарантировать, что значения кодирования правильно нормализованы и распределены по матрице.

Давайте визуализируем косинусное сходство между вектором позиционного кодирования с индексом 1000 и всеми другими векторами в матрице позиционного кодирования.

Векторы позиционного кодирования сначала нормализуются с использованием нормализации L2. Затем код вычисляет скалярное произведение между вектором позиционного кодирования с индексом 1000 и всеми другими векторами в матрице, используя функцию einsum. Полученные скалярные произведения отображаются на графике, где ось Y представляет значения косинусного подобия между векторами.

#@title
pos_encoding/=tf.norm(pos_encoding, axis=1, keepdims=True)
p = pos_encoding[1000]
dots = tf.einsum('pd,d -> p', pos_encoding, p)
plt.subplot(2,1,1)
plt.plot(dots)
plt.ylim([0,1])
plt.plot([950, 950, float('nan'), 1050, 1050],
         [0,1,float('nan'),0,1], color='k', label='Zoom')
plt.legend()
plt.subplot(2,1,2)
plt.plot(dots)
plt.xlim([950, 1050])
plt.ylim([0,1])
(0.0, 1.0)

На первом графике показан весь график сходства косинусов, а на втором графике увеличены значения сходства косинусов между индексами 950 и 1050.

Эта визуализация помогает проиллюстрировать, как векторы позиционного кодирования кодируют информацию о положении каждого маркера в последовательности. Значения косинусного подобия являются самыми высокими для векторов, близких друг к другу вдоль оси положения, что указывает на то, что они имеют схожую информацию о положении.

Теперь давайте соберем все воедино и создадим класс PositionEmbedding. Это класс tf.keras.layers.Layer, который объединяет слой внедрения и слой позиционного кодирования для создания слоя, который можно использовать для кодирования входных последовательностей в модели преобразователя.

Класс принимает два аргумента: vocab_size — размер словаря входных последовательностей и d_model — размер векторов встраивания и позиционного кодирования.

В конструкторе он создает слой Embedding, который сопоставляет входные токены с соответствующими векторами встраивания, и матрицу позиционного кодирования формы (max_length, d_model) с помощью функции positional_encoding.

Метод compute_mask этого класса возвращает маску той же формы, что и входной тензор, в слой внедрения.

В методе вызова входной тензор сначала проходит через слой внедрения, а затем масштабируется на квадратный корень из значения d_model. Затем матрица позиционного кодирования добавляется к выходным данным внедрения, соответствующим каждому входному токену. Наконец, закодированная входная последовательность возвращается.

class PositionalEmbedding(tf.keras.layers.Layer):
  """
  This layer combines the input embedding with a positional encoding that helps the Transformer to understand
  the relative position of the tokens in a sequence. It takes an input sequence of tokens and converts it to 
  a sequence of embedding vectors, then adds positional information to it.

  Attributes:
      vocab_size (int): The size of the vocabulary, i.e., the number of unique tokens in the input sequence.
      d_model (int): The number of dimensions in the embedding vector.

  Methods:
      compute_mask(*args, **kwargs): This method computes the mask to be applied to the embeddings.
      call(x): This method performs the computation for the layer.

  """
  def __init__(self, vocab_size, d_model):
    """
    Initializes the PositionalEmbedding layer.

    Args:
        vocab_size (int): The size of the vocabulary, i.e., the number of unique tokens in the input sequence.
        d_model (int): The number of dimensions in the embedding vector.
    """
    super().__init__()
    self.d_model = d_model
    self.embedding = tf.keras.layers.Embedding(vocab_size, d_model, mask_zero=True) 
    self.pos_encoding = positional_encoding(length=2048, depth=d_model)

  def compute_mask(self, *args, **kwargs):
    """
    Computes the mask to be applied to the embeddings.

    Args:
        *args: Variable length argument list.
        **kwargs: Arbitrary keyword arguments.

    Returns:
        Mask to be applied to the embeddings.
    """
    return self.embedding.compute_mask(*args, **kwargs)

  def call(self, x):
    """
    Computes the output of the layer.

    Args:
        x (tf.Tensor): Input sequence of tokens.

    Returns:
        The output sequence of embedding vectors with added positional information.
    """
    length = tf.shape(x)[1]
    x = self.embedding(x)
    # This factor sets the relative scale of the embedding and positonal_encoding.
    x *= tf.math.sqrt(tf.cast(self.d_model, tf.float32))
    x = x + self.pos_encoding[tf.newaxis, :length, :]
    return x

Примечание. Согласно учебнику tensflow, в оригинальной статье, разделы 3.4 и 5.1, используется один токенизатор и весовая матрица как для исходного, так и для целевого языков. В этом руководстве используются два отдельных токенизатора и матрицы весов.

Давайте создадим два экземпляра класса PositionalEmbedding, один для португальского токенизатора и один для английского токенизатора. Мы передаем размер словаря каждого токенизатора и значение для d_model, которое является размерностью вектора внедрения.

Затем мы вызываем эти экземпляры в наших токенизированных предложениях на португальском и английском языках (pt и en) соответственно. Результатом каждого вызова является встроенное представление предложения, где каждый токен представлен в виде вектора с добавленным к нему позиционным кодированием, как описано в классе PositionalEmbedding.

Полученные вложения можно использовать в качестве входных данных для кодера и декодера модели Transformer.

embed_pt = PositionalEmbedding(vocab_size=tokenizers.pt.get_vocab_size(), d_model=512)
embed_en = PositionalEmbedding(vocab_size=tokenizers.en.get_vocab_size(), d_model=512)

pt_emb = embed_pt(pt)
en_emb = embed_en(en)

В Keras маскирование используется для указания временных шагов, которые следует игнорировать во время обработки, например, временных шагов заполнения. Атрибут _keras_mask возвращает логический тензор той же формы, что и en_emb, который указывает, какие временные шаги должны быть замаскированы (True для замаскированных временных шагов, False для немаскированных временных шагов). Если временной шаг замаскирован, это означает, что соответствующие ему значения будут игнорироваться при вычислении».

en_emb._keras_mask
<tf.Tensor: shape=(64, 72), dtype=bool, numpy=
array([[ True,  True,  True, ..., False, False, False],
       [ True,  True,  True, ..., False, False, False],
       [ True,  True,  True, ..., False, False, False],
       ...,
       [ True,  True,  True, ..., False, False, False],
       [ True,  True,  True, ..., False, False, False],
       [ True,  True,  True, ..., False, False, False]])>

2.4.5. Добавить и нормализовать

Блоки «Add & Norm» используются в модели Transformer и помогают в эффективном обучении. Эти блоки состоят из остаточного соединения, которое обеспечивает прямой путь для градиента и гарантирует, что векторы обновляются, а не заменяются слоями внимания, и слоя LayerNormalization, который поддерживает разумный масштаб для выходных данных. Эти блоки разбросаны по всей модели, и вокруг них организован код. Пользовательские классы слоев определены для каждого блока. Слой Add используется в реализации для обеспечения распространения масок Keras, поскольку оператор + этого не делает.

Примечание. В случае остаточного добавления исходные входные данные слоя добавляются к выходным данным этого слоя, создавая «остаточное» соединение, которое позволяет градиенту обходить слой во время обратного распространения. Это помогает предотвратить исчезновение градиентов и позволяет продолжать обновлять веса во время тренировки. В случае модели Transformer остаточные соединения используются в сочетании с нормализацией слоев, чтобы повысить эффективность обучения и сохранить разумный масштаб выходных данных.

2.4.6. Уровень внимания

Модель включает в себя блоки Attention, каждый из которых содержит Layers.MultiHeadAttention, layers.LayerNormalization и layers.Add. Чтобы создать эти уровни внимания, мы сначала определяем базовый класс, который включает эти три компонента, а затем мы создаем определенные подклассы для каждого варианта использования. Хотя это требует написания большего количества кода, такой подход помогает сделать реализацию организованной и простой для понимания.

Класс содержит три слоя: tf.keras.layers.MultiHeadAttention, tf.keras.layers.LayerNormalization и tf.keras.layers.Add.

  • Слой MultiHeadAttention отвечает за вычисление весовых коэффициентов внимания между входной и выходной последовательностями.
  • Слой LayerNormalization нормализует активацию слоя в пакетных и пространственных измерениях.
  • Слой Add добавляет выходные данные слоя MultiHeadAttention к исходной входной последовательности, используя остаточное соединение.

Создав базовый класс с этими слоями, мы можем повторно использовать этот код для создания различных механизмов внимания, наследуя от этого класса и определяя конкретные детали реализации. Это помогает поддерживать порядок и ясность кода.

class BaseAttention(tf.keras.layers.Layer):
  """
  Base Attention layer class that contains a MultiHeadAttention, LayerNormalization and Add layer.

  Attributes:
  -----------
  kwargs: dict
      keyword arguments that will be passed to the MultiHeadAttention layer during initialization.

  Methods:
  --------
  call(inputs, mask=None, training=None):
      Performs a forward pass on the input and returns the output.

  """
  def __init__(self, **kwargs):
    """
    Initializes a new instance of the BaseAttention layer class.

    Parameters:
    -----------
    kwargs: dict
        keyword arguments that will be passed to the MultiHeadAttention layer during initialization.
    """
    super().__init__()
    self.mha = tf.keras.layers.MultiHeadAttention(**kwargs)
    self.layernorm = tf.keras.layers.LayerNormalization()
    self.add = tf.keras.layers.Add()

Как работает внимание?

На уровне внимания есть два входа: последовательность запроса и последовательность контекста. Последовательность запроса — это обрабатываемая последовательность, а контекстная последовательность — это последовательность, на которую обращают внимание. Вывод имеет ту же форму, что и последовательность запроса.

Работа уровня внимания часто сравнивается с работой поиска по словарю, но с нечеткими, дифференцируемыми и векторизованными характеристиками. Как и поиск в словаре, запрос используется для поиска релевантной информации, которая представлена ​​в виде ключей и значений. При поиске запроса в обычном словаре возвращается совпадающий ключ и соответствующее ему значение. Однако в нечетком словаре запрос не обязательно должен точно соответствовать ключу для возвращаемого значения.

Например, если мы искали ключ «species» в словаре {'color': 'blue', 'age': 22, 'type': 'pickup'}, он мог бы вернуть значение «pickup» как наилучшее соответствие запросу.

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

В контексте NLP последовательность запроса может предоставлять вектор запроса в каждом месте, в то время как последовательность контекста служит словарем с вектором ключа и значения в каждом месте. Прежде чем использовать входные векторы, слой layers.MultiHeadAttention включает layers.Dense слоев для проецирования входных векторов.

Итак, теперь давайте воспользуемся этим классом для создания других слоев внимания. Мы создадим:

  • Уровень перекрестного внимания: внимание декодера-кодировщика
  • Слой глобального внимания к себе: самовнимание кодировщика
  • Причинный слой внимания к себе: декодер самовнимания

Источник: [1–2]

2.4.6.1. Уровень перекрестного внимания: внимание декодера-кодировщика

Давайте напишем класс CrossAttention, унаследовав его от класса BaseAttention, который содержит слой внимания с несколькими головками, слой нормализации слоя и слой добавления.

Метод call принимает два входных параметра, x и context. x — это последовательность запросов, которая обрабатывается и выполняет обслуживание, а context — это последовательность context, которая обрабатывается.

Метод call передает x и context слою self.mha (внимание с несколькими головками), который возвращает тензор вывода внимания и тензор оценок внимания. Атрибут self.last_attn_scores устанавливается на тензор оценок внимания для последующего построения графика.

Затем тензор вывода внимания добавляется к исходному тензору x с использованием слоя self.add, а результат нормализуется с использованием слоя self.layernorm. Затем возвращается окончательный результат.

class CrossAttention(BaseAttention):
  """
  A class that implements cross-attention mechanism by inheriting from BaseAttention class.
  Cross-attention is used to process two different sequences and attends to the context sequence while processing the query sequence.
  Inherits:
      BaseAttention: A base class that defines the MultiHeadAttention layer, LayerNormalization, and Add operation.
  Args:
      **kwargs: Arguments to pass to the MultiHeadAttention layer.
  """
  def call(self, x, context):
    """
    The call function that performs the cross-attention operation.

    Args:
        x: The query sequence tensor, shape=(batch_size, seq_len, embedding_dim)
        context: The context sequence tensor, shape=(batch_size, seq_len, embedding_dim)

    Returns:
        The attended output tensor, shape=(batch_size, seq_len, embedding_dim)
    """
    attn_output, attn_scores = self.mha(
        query=x,
        key=context,
        value=context,
        return_attention_scores=True)
   
    # Cache the attention scores for plotting later.
    self.last_attn_scores = attn_scores

    x = self.add([x, attn_output])
    x = self.layernorm(x)

    return x
sample_ca = CrossAttention(num_heads=2, key_dim=512)

print(pt_emb.shape)
print(en_emb.shape)
print(sample_ca(en_emb, pt_emb).shape)
(64, 95, 512)
(64, 72, 512)
(64, 72, 512)

Результатом length является длина последовательности запроса, а не длина последовательности контекста key/value.

2.4.6.2. Слой глобального внимания к себе: самовнимание кодировщика

Этот уровень отвечает за обработку контекстной последовательности и распространение информации по ее длине. Теперь напишем GlobalSelfAttention, наследуя слой baseAttention.

В GlobalSelfAttention есть только один вход x, представляющий собой последовательность векторов, представляющую обрабатываемую последовательность. Этот ввод используется в качестве ввода запроса, ключа и значения для механизма многоголового внимания (MHA). MHA вычисляет средневзвешенное значение значений на основе того, насколько хорошо запрос соответствует ключам, где оценки внимания определяют вес каждого значения.

Другими словами, MHA учится выборочно сосредотачиваться на разных частях входной последовательности, что может помочь модели получить релевантную информацию для конкретной задачи. В GlobalSelfAttention, поскольку входная последовательность используется как для query, так и для key, она фиксирует взаимосвязь между каждой позицией и всеми другими позициями в последовательности.

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

class GlobalSelfAttention(BaseAttention):
  def call(self, x):
    """
    Apply the global self-attention mechanism to the input sequence.

    Args:
        x: A tensor of shape `(batch_size, seq_len, embedding_dim)` 
        representing the input sequence.

    Returns:
        A tensor of the same shape as the input, representing the sequence 
        after being transformed by the self-attention mechanism.
    """
    attn_output = self.mha(
        query=x,
        value=x,
        key=x)
    x = self.add([x, attn_output])
    x = self.layernorm(x)
    return x
sample_gsa = GlobalSelfAttention(num_heads=2, key_dim=512)

print(pt_emb.shape)
print(sample_gsa(pt_emb).shape)
(64, 95, 512)
(64, 95, 512)

Выходной тензор имеет ту же форму, что и входной

2.4.6.3. Причинный слой внимания к себе: декодер самовнимания

Этот слой выполняет ту же работу, что и глобальный уровень самоконтроля для выходной последовательности. Теперь напишем CausalSelfAttention, наследуя слой baseAttention.

Класс CausalSelfAttention — это тип слоя самоконтроля, используемый в нейронных сетях для задач моделирования последовательности, где выходные данные на каждом временном шаге могут зависеть только от предыдущих временных шагов, а не от будущих временных шагов. В таких задачах уровень каузального самоконтроля используется для обеспечения ограничения, согласно которому модель может учитывать только предыдущие временные шаги в процессе декодирования.

Метод call этого класса принимает тензор x в качестве входных данных и применяет к нему механизм каузального внутреннего внимания. В частности, метод использует метод mha (многоголовое внимание) класса BaseAttention с входными параметрами query, key и value, установленными на x. Кроме того, для аргумента use_causal_mask метода mha задано значение True, которое применяет каузальную маску к показателям внимания, чтобы гарантировать, что модель может учитывать только предыдущие временные шаги.

После применения механизма каузального внутреннего внимания метод добавляет выходные данные к исходному входному тензору x и нормализует результат с помощью нормализации слоев. Наконец, в качестве результата метода возвращается нормализованный тензор.

class CausalSelfAttention(BaseAttention):
  """
  Call self attention on the input sequence, ensuring that each position in the 
  output depends only on previous positions (i.e. a causal model).

  Args:
      x: Input sequence tensor of shape `(batch_size, seq_len, embed_dim)`.

  Returns:
      Output sequence tensor of the same shape as the input, after self-attention 
      and residual connection with layer normalization applied.
  """
  def call(self, x):
    attn_output = self.mha(
        query=x,
        value=x,
        key=x,
        use_causal_mask = True)
    x = self.add([x, attn_output])
    x = self.layernorm(x)
    return x
sample_csa = CausalSelfAttention(num_heads=2, key_dim=512)

print(en_emb.shape)
print(sample_csa(en_emb).shape)
(64, 72, 512)
(64, 72, 512)

Вывод для ранних элементов последовательности не зависит от более поздних элементов, поэтому не имеет значения, обрезаете ли вы элементы до или после применения слоя:

out1 = sample_csa(embed_en(en[:, :3])) 
out2 = sample_csa(embed_en(en))[:, :3]

tf.reduce_max(abs(out1 - out2)).numpy()
0.00010704994

в основном разница между до и после обрезки tf.reduce_max(abs(out1 - out2)).numpy() равна нулю!

2.4.7. Сеть прямой связи

Теперь давайте реализуем сеть прямой связи.

Класс FeedForward — это настраиваемый слой в TensorFlow, который реализует нейронную сеть с прямой связью. Он обычно используется в моделях на основе преобразователя, таких как BERT и GPT-2, для обработки представления каждого токена.

Слой принимает на вход тензор x формы (batch_size, seq_len, d_model), где d_model — размер последнего измерения. Он пропускает тензор x через сеть прямой связи, состоящую из двух плотных слоев со скрытыми единицами dff и функцией активации relu. dropout_rate также применяется после первого плотного слоя, чтобы предотвратить переоснащение. Выход сети прямой связи добавляется к исходному входу x через слой Add(). Наконец, выходные данные нормализуются с использованием слоя LayerNormalization().

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

class FeedForward(tf.keras.layers.Layer):
  """
  Implements the feedforward sublayer of the transformer block.

  Parameters:
  -----------
  d_model: int
      The number of expected features in the input and output.
  dff: int
      The number of neurons in the first Dense layer.
  dropout_rate: float, optional (default=0.1)
      The dropout rate to use.

  Attributes:
  -----------
  seq: tf.keras.Sequential
      The sequential model that applies the two Dense layers and Dropout.
  add: tf.keras.layers.Add
      The addition layer that adds the residual connection.
  layer_norm: tf.keras.layers.LayerNormalization
      The normalization layer applied to the output.

  Methods:
  --------
  call(x):
      Computes the feedforward sublayer on the input tensor x and returns the output.

  """
  def __init__(self, d_model, dff, dropout_rate=0.1):
    super().__init__()
    self.seq = tf.keras.Sequential([
      tf.keras.layers.Dense(dff, activation='relu'),
      tf.keras.layers.Dense(d_model),
      tf.keras.layers.Dropout(dropout_rate)
    ])
    self.add = tf.keras.layers.Add()
    self.layer_norm = tf.keras.layers.LayerNormalization()

  def call(self, x):
    """
    Passes the input tensor `x` through a feedforward network consisting of two 
    dense layers with `dff` hidden units and a `relu` activation function. 
    A `dropout_rate` is applied after the first dense layer to prevent overfitting. 
    The output of the feedforward network is added to the original input `x` via the 
    `Add()` layer. Finally, the output is normalized using the `LayerNormalization()` layer.

    Args:
        x (tf.Tensor): Input tensor with shape `(batch_size, seq_len, d_model)`.

    Returns:
        tf.Tensor: Output tensor with shape `(batch_size, seq_len, d_model)`.
    """
    x = self.add([x, self.seq(x)])
    x = self.layer_norm(x) 
    return x

Протестируйте слой; вывод имеет ту же форму, что и ввод:

sample_ffn = FeedForward(512, 2048)

print(en_emb.shape)
print(sample_ffn(en_emb).shape)
print(en_emb.shape)
print(sample_ffn(en_emb).shape)
(64, 72, 512)
(64, 72, 512)

2.4.8. Кодер

Кодер состоит из PositionalEmbedding слоя на входе и стека из EncoderLayer слоев. Где каждый EncoderLayer содержит слой GlobalSelfAttention и FeedForward.

Давайте сначала напишем класс для EncoderLayer и объединим GlobalSelfAttention и FeedForward, а затем используем стек из EncoderLayer и PositionalEmbedding для построения Encoder.

class EncoderLayer(tf.keras.layers.Layer):
  """
  A single layer in the transformer encoder stack.

  Args:
    d_model (int): The dimensionality of the input and output sequences.
    num_heads (int): The number of attention heads to be used in the self-attention sub-layer.
    dff (int): The number of hidden units in the feedforward sub-layer.
    dropout_rate (float): The dropout rate to be applied after the self-attention sub-layer.

  Attributes:
    self_attention (GlobalSelfAttention): A self-attention layer.
    ffn (FeedForward): A feedforward neural network layer.
  """
  def __init__(self,*, d_model, num_heads, dff, dropout_rate=0.1):
    super().__init__()

    self.self_attention = GlobalSelfAttention(
        num_heads=num_heads,
        key_dim=d_model,
        dropout=dropout_rate)

    self.ffn = FeedForward(d_model, dff)

  def call(self, x):
    """
    Applies the forward pass of the encoder layer.

    Args:
      x (tf.Tensor): The input sequence tensor.

    Returns:
      tf.Tensor: The output sequence tensor.
    """
    x = self.self_attention(x)
    x = self.ffn(x)
    return x

Класс EncoderLayer представляет один слой в стеке преобразователя кодировщика. Он состоит из двух подуровней: уровня самоконтроля и уровня нейронной сети с прямой связью.

Функция __init__ инициализирует объект ncoderLayer, создавая его подслои. Слой self_attention является экземпляром класса GlobalSelfAttention, который выполняет само-внимание над входной последовательностью. Параметры num_heads и key_dim определяют количество заголовков внимания и размерность ключей и значений в каждом заголовке соответственно. Параметр dropout_rate определяет скорость отсева, которая будет применяться после подуровня self-attention. Подуровень ffn является экземпляром класса FeedForward, который состоит из двух плотных слоев с активацией ReLU, за которыми следует слой отсева.

Функция вызова вызывается для применения прямого прохода EncoderLayer. Входная последовательность x проходит через подуровень self_attention, за которым следует подуровень ffn, и возвращается результирующая выходная последовательность.

class Encoder(tf.keras.layers.Layer):
  """
  A custom Keras layer that implements the encoder of a transformer-based
  neural network architecture for natural language processing tasks such
  as language translation or text classification.

  Args:
    num_layers (int): The number of layers in the encoder.
    d_model (int): The dimensionality of the output space.
    num_heads (int): The number of attention heads in the multi-head
      self-attention mechanism.
    dff (int): The dimensionality of the fully connected feedforward
      network.
    vocab_size (int): The size of the vocabulary of the input language.
    dropout_rate (float): The dropout rate to use for regularization.

  Attributes:
    d_model (int): The dimensionality of the output space.
    num_layers (int): The number of layers in the encoder.
    pos_embedding (PositionalEmbedding): The layer that learns the position
      embeddings for each token in the input sequence.
    enc_layers (list): A list of `EncoderLayer` instances, one for each
      layer in the encoder architecture.
    dropout (Dropout): The dropout layer for regularization.

  Methods:
    call(x): The forward pass of the encoder layer.

  Returns:
    The output tensor of the encoder layer, which has shape
    `(batch_size, seq_len, d_model)`.
  """
  def __init__(self, *, num_layers, d_model, num_heads,
               dff, vocab_size, dropout_rate=0.1):
    super().__init__()

    self.d_model = d_model
    self.num_layers = num_layers

    self.pos_embedding = PositionalEmbedding(
        vocab_size=vocab_size, d_model=d_model)

    self.enc_layers = [
        EncoderLayer(d_model=d_model,
                     num_heads=num_heads,
                     dff=dff,
                     dropout_rate=dropout_rate)
        for _ in range(num_layers)]
    self.dropout = tf.keras.layers.Dropout(dropout_rate)

  def call(self, x):
    """
    Perform forward pass of the `Encoder` layer.
    
    Args:
    x: tensor of shape (batch_size, sequence_length) representing the input token IDs sequence.

    Returns:
    A tensor of shape (batch_size, sequence_length, d_model) representing the output after applying 
    the self-attention and feed-forward layers to the input sequence.
    """
    # `x` is token-IDs shape: (batch, seq_len)
    x = self.pos_embedding(x)  # Shape `(batch_size, seq_len, d_model)`.
    
    # Add dropout.
    x = self.dropout(x)

    for i in range(self.num_layers):
      x = self.enc_layers[i](x)

    return x  # Shape `(batch_size, seq_len, d_model)`.

Этот код определяет класс Encoder, который используется в архитектуре Transformer для задач обработки естественных языков, таких как языковой перевод и языковое моделирование.

Класс Encoder является подклассом класса tf.keras.layers.Layer, который является базовым классом для реализации новых слоев в Keras.

Метод __init__ инициализирует объект Encoder, определяя параметры модели, такие как d_model (размер выходного пространства), num_heads (количество головок в механизме внимания с несколькими головками), dff (размерность сети прямой связи), vocab_size (размер словаря входных токенов) и dropout_rate (скорость отсева, применяемая к выходным данным слоя).

Атрибут pos_embedding инициализирует слой PositionalEmbedding, который добавляет позиционную информацию к входным токенам, чтобы учитывать их положение в последовательности.

Атрибут enc_layers инициализирует список объектов EncoderLayer, каждый из которых реализует функциональность EncoderLayer. Количество слоев в энкодере определяется параметром num_layers.

Атрибут dropout инициализирует выпадающий слой, чтобы применить выпадение к выходным данным слоя.

Метод call вызывается, когда слой вызывается для входных данных. Он применяет позиционное встраивание к входным токенам, а затем применяет выпадающий слой. Затем он итеративно применяет объект EncoderLayer к выходным данным предыдущего слоя. Конечный результат возвращается в виде тензора формы (batch_size, seq_len, d_model).

Давайте протестируем кодировщик:

# Instantiate the encoder.
sample_encoder = Encoder(num_layers=4,
                         d_model=512,
                         num_heads=8,
                         dff=2048,
                         vocab_size=8500)

sample_encoder_output = sample_encoder(pt, training=False)

# Print the shape.
print(pt.shape)
print(sample_encoder_output.shape)  # Shape `(batch_size, input_seq_len, d_model)`.
(64, 95)
(64, 95, 512)

2.4.9. Декодер

Подобно Encoder, Decoder состоит из PositionalEmbedding и набора DecoderLayer. А стек декодера немного сложнее: каждый DecoderLayer содержит слой CausalSelfAttention, CrossAttention и FeedForward.

Давайте сначала напишем DecoderLayer, затем напишем Encoder.

Давайте определим класс DecoderLayer, который является строительным блоком для декодера на основе преобразователя в модели последовательностей. Класс наследуется от tf.keras.layers.Layer.

В классе есть метод __init__, который инициализирует параметры и подслои слоя. Он принимает следующие аргументы:

  • d_model: количество ожидаемых функций на входе и выходе.
  • num_heads: количество параллельных головок внимания.
  • dff: количество нейронов в подслое прямой связи.
  • dropout_rate: Применяемый коэффициент отсева.

Метод call определяет, как использовать слой в прямом проходе. Он принимает два аргумента: x и context. x — это входные данные для уровня декодера, которые проходят через подуровни каузального само-внимания, перекрестного внимания и прямой связи для получения выходных данных x. context — это выходные данные уровня кодировщика, которые используются в качестве контекста внимания для механизма перекрестного внимания.

Класс содержит следующие подуровни:

  • causal_self_attention: уровень каузального самовнимания, который обращает внимание на входную последовательность каузальным образом, т. е. предсказывает будущие токены на основе предыдущих.
  • cross_attention: Уровень перекрестного внимания, который обращает внимание на контекст вывода кодировщика, чтобы выровнять вывод декодера с входной последовательностью.
  • ffn: подуровень прямой связи, который применяет нелинейное преобразование к выходным данным подуровней внимания.

Метод call также кэширует последние оценки внимания, вычисленные подуровнем cross_attention, которые можно использовать для целей визуализации и отладки.

class DecoderLayer(tf.keras.layers.Layer):
  """
  A single layer of the decoder in a transformer-based architecture.

  Args:
    d_model (int): The number of expected features in the input.
    num_heads (int): The number of attention heads.
    dff (int): The dimensionality of the feedforward network.
    dropout_rate (float): The dropout rate to be applied.

  Attributes:
    causal_self_attention: An instance of the `CausalSelfAttention` layer.
    cross_attention: An instance of the `CrossAttention` layer.
    ffn: An instance of the `FeedForward` layer.
    last_attn_scores: A tensor containing the last attention scores.

  """
  def __init__(self,
               *,
               d_model,
               num_heads,
               dff,
               dropout_rate=0.1):
    super(DecoderLayer, self).__init__()

    self.causal_self_attention = CausalSelfAttention(
        num_heads=num_heads,
        key_dim=d_model,
        dropout=dropout_rate)
    
    self.cross_attention = CrossAttention(
        num_heads=num_heads,
        key_dim=d_model,
        dropout=dropout_rate)

    self.ffn = FeedForward(d_model, dff)

  def call(self, x, context):
    """
    Forward pass of the `DecoderLayer`.

    Args:
      x (tf.Tensor): The input tensor of shape 
      `(batch_size, target_seq_len, d_model)`.
      context (tf.Tensor): The context tensor of shape 
      `(batch_size, input_seq_len, d_model)`.

    Returns:
      The output tensor of the `DecoderLayer` of shape 
      `(batch_size, target_seq_len, d_model)`.

    """
    x = self.causal_self_attention(x=x)
    x = self.cross_attention(x=x, context=context)

    # Cache the last attention scores for plotting later
    self.last_attn_scores = self.cross_attention.last_attn_scores

    x = self.ffn(x)  # Shape `(batch_size, seq_len, d_model)`.
    return x

Теперь давайте поместим это в декодер и напишем класс decoder. Этот класс отвечает за декодирование закодированных входных последовательностей для создания целевых последовательностей в моделях последовательностей. Уровень декодера состоит из нескольких блоков DecoderLayer, каждый из которых содержит механизм самоконтроля, механизм перекрестного внимания и сеть прямой связи. Слой Decoder также включает слои позиционного встраивания и исключения.

class Decoder(tf.keras.layers.Layer):
  """A decoder model for sequence to sequence learning.
  
  This class implements a decoder layer for a transformer-based model used for sequence to sequence learning tasks. The decoder layer takes input embeddings, positional encodings, and attention masks as input, and returns the output of the decoder layer after applying a multi-head self-attention mechanism, followed by a cross-attention mechanism with the output from the encoder layers, and then applying a feed-forward neural network.

  Attributes:
    d_model (int): The number of output dimensions for each layer.
    num_layers (int): The number of layers in the decoder.
    pos_embedding (PositionalEmbedding): The positional embedding layer.
    dropout (Dropout): A dropout layer.
    dec_layers (list): A list of DecoderLayer objects.
    last_attn_scores (ndarray): The attention scores from the last decoder layer.
    
  Methods:
    call(x, context): Implements the forward pass for the decoder layer.
      Args:
        x (ndarray): A tensor of shape (batch_size, target_seq_len), representing the input token IDs.
        context (ndarray): A tensor of shape (batch_size, input_seq_len, d_model), representing the output from the encoder layers.
      Returns:
        ndarray: A tensor of shape (batch_size, target_seq_len, d_model), representing the output from the decoder layers.
  """
  def __init__(self, *, num_layers, d_model, num_heads, dff, vocab_size,
               dropout_rate=0.1):
    super(Decoder, self).__init__()

    self.d_model = d_model
    self.num_layers = num_layers

    self.pos_embedding = PositionalEmbedding(vocab_size=vocab_size,
                                             d_model=d_model)
    self.dropout = tf.keras.layers.Dropout(dropout_rate)
    self.dec_layers = [
        DecoderLayer(d_model=d_model, num_heads=num_heads,
                     dff=dff, dropout_rate=dropout_rate)
        for _ in range(num_layers)]

    self.last_attn_scores = None

  def call(self, x, context):
    """
    Implements the forward pass for the decoder layer.

    Args:
      x (ndarray): A tensor of shape (batch_size, target_seq_len), representing the input token IDs.
      context (ndarray): A tensor of shape (batch_size, input_seq_len, d_model), representing the output from the encoder layers.

    Returns:
      ndarray: A tensor of shape (batch_size, target_seq_len, d_model), representing the output from the decoder layers.
    """
    # `x` is token-IDs shape (batch, target_seq_len)
    x = self.pos_embedding(x)  # (batch_size, target_seq_len, d_model)

    x = self.dropout(x)

    for i in range(self.num_layers):
      x  = self.dec_layers[i](x, context)

    self.last_attn_scores = self.dec_layers[-1].last_attn_scores

    # The shape of x is (batch_size, target_seq_len, d_model).
    return x

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

В конструкторе класс Decoder инициализирует несколько экземпляров уровня, включая слой PositionalEmbedding, который добавляет позиционное кодирование к входным идентификаторам маркеров, слой dropout и стек экземпляров DecoderLayer.

Во время прямого прохода входные идентификаторы токенов сначала проходят через слои позиционного внедрения и исключения. Затем для каждого DecoderLayer входные данные проходят через уровень каузального самоконтроля, за которым следует уровень перекрестного внимания и, наконец, через уровень нейронной сети с прямой связью. Выходные данные последнего DecoderLayer возвращаются как выходные данные Decoder.

Атрибут last_attn_scores экземпляра Decoder содержит оценки внимания из последнего уровня декодера, которые могут быть полезны для визуализации и отладки.

# Instantiate the decoder.
sample_decoder = Decoder(num_layers=4,
                         d_model=512,
                         num_heads=8,
                         dff=2048,
                         vocab_size=8000)

output = sample_decoder(
    x=en,
    context=pt_emb)

# Print the shapes.
print(en.shape)
print(pt_emb.shape)
print(output.shape)
(64, 72)
(64, 95, 512)
(64, 72, 512)
sample_decoder.last_attn_scores.shape  # (batch, heads, target_seq, input_seq)
TensorShape([64, 8, 72, 95])

2.5. Трансформер

Encoder и Decoder являются ключевыми компонентами модели Transformer, но их необходимо объединить, а затем добавить последний слой Dense для вывода вероятностей токенов. Теперь давайте объединим эти два класса и создадим Transformer, расширив tf.keras.Model:

class Transformer(tf.keras.Model):
  """
  A transformer model that consists of an encoder, a decoder and a final dense layer.

  Args:
    num_layers (int): Number of layers in both the encoder and decoder.
    d_model (int): Hidden size of the model.
    num_heads (int): Number of attention heads used in the model.
    dff (int): Size of the feedforward layer in the encoder and decoder.
    input_vocab_size (int): Size of the vocabulary of the input.
    target_vocab_size (int): Size of the vocabulary of the target.
    dropout_rate (float): Dropout rate applied to the output of each sub-layer.

  Attributes:
    encoder (Encoder): An instance of the Encoder class.
    decoder (Decoder): An instance of the Decoder class.
    final_layer (Dense): A Dense layer that converts the final transformer output to output token probabilities.

  Methods:
    call(inputs): Forward pass of the transformer model.

  Returns:
    logits (tf.Tensor): Output tensor of the final dense layer. Shape (batch_size, target_len, target_vocab_size).
  """
  def __init__(self, *, num_layers, d_model, num_heads, dff,
               input_vocab_size, target_vocab_size, dropout_rate=0.1):
    super().__init__()
    self.encoder = Encoder(num_layers=num_layers, d_model=d_model,
                           num_heads=num_heads, dff=dff,
                           vocab_size=input_vocab_size,
                           dropout_rate=dropout_rate)

    self.decoder = Decoder(num_layers=num_layers, d_model=d_model,
                           num_heads=num_heads, dff=dff,
                           vocab_size=target_vocab_size,
                           dropout_rate=dropout_rate)

    self.final_layer = tf.keras.layers.Dense(target_vocab_size)

  def call(self, inputs):
    """
    Forward pass of the transformer model.

    Args:
      inputs (tuple): A tuple of two tensors. The first tensor is the context input tensor of shape (batch_size, context_len).
                      The second tensor is the target input tensor of shape (batch_size, target_len).

    Returns:
      logits (tf.Tensor): Output tensor of the final dense layer. Shape (batch_size, target_len, target_vocab_size).
    """

    # To use a Keras model with `.fit` you must pass all your inputs in the
    # first argument.
    context, x  = inputs

    context = self.encoder(context)  # (batch_size, context_len, d_model)

    x = self.decoder(x, context)  # (batch_size, target_len, d_model)

    # Final linear layer output.
    logits = self.final_layer(x)  # (batch_size, target_len, target_vocab_size)

    try:
      # Drop the keras mask, so it doesn't scale the losses/metrics.
      # b/250038731
      del logits._keras_mask
    except AttributeError:
      pass

    # Return the final output and the attention weights.
    return logits

Класс Transformer — это модель Keras, которая объединяет Encoder и Decoder для реализации архитектуры Transformer.

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

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

final_layer — это плотный слой Keras, который берет выходные данные Decoder и сопоставляет их с последовательностью вероятностей целевых токенов.

Метод call класса Transformer принимает входной тензор inputs, который представляет собой набор из двух тензоров: тензор context, который является входной последовательностью для Encoder, и тензор x, который является целевой последовательностью для Decoder. Метод передает тензор context через Encoder для получения контекстной информации для каждого токена в последовательности, а затем передает тензор x и выходные данные Encoder в Decoder для создания выходной последовательности. Наконец, метод передает выходные данные декодера через final_layer для получения вероятностей целевых токенов. Метод возвращает logits, которые представляют собой вероятности целевых токенов, а также веса внимания.

Чтобы сохранить относительно быстрый и компактный пример, размер слоев, встраивания и внутренняя размерность слоя FeedForward в модели Transformer были уменьшены. В исходной бумаге Transformer использовалась базовая модель с num_layers=6, d_model=512 и dff=2048. Однако количество головок self-attention в этом примере остается прежним, установленным на num_heads=8.

num_layers = 4
d_model = 128
dff = 512
num_heads = 8
dropout_rate = 0.1

Теперь давайте создадим экземпляр модели Transformer:

transformer = Transformer(
    num_layers=num_layers,
    d_model=d_model,
    num_heads=num_heads,
    dff=dff,
    input_vocab_size=tokenizers.pt.get_vocab_size().numpy(),
    target_vocab_size=tokenizers.en.get_vocab_size().numpy(),
    dropout_rate=dropout_rate)

Мы можем протестировать модель, прежде чем перейти к обучающей части:

output = transformer((pt, en))

print(en.shape)
print(pt.shape)
print(output.shape)
(64, 72)
(64, 95)
(64, 72, 7010)

Давайте напечатаем сводку модели, чтобы лучше ее визуализировать.

transformer.summary()
Model: "transformer"
_________________________________________________________________
 Layer (type)                Output Shape              Param #
=================================================================
 encoder_1 (Encoder)         multiple                  3632768

 decoder_1 (Decoder)         multiple                  5647104

 dense_34 (Dense)            multiple                  904290

=================================================================
Total params: 10,184,162
Trainable params: 10,184,162
Non-trainable params: 0
_________________________________________________________________

Общее количество обучаемых параметров в этой модели составляет 10 184 162. Необучаемых параметров нет.

2.6. Обучение

2.6.1. Оптимизатор

Используйте оптимизатор Adam с настраиваемым планировщиком скорости обучения в соответствии с формулой в оригинальной бумаге Transformer.

class CustomSchedule(tf.keras.optimizers.schedules.LearningRateSchedule):
  """
  Custom learning rate schedule that implements the learning rate function
  described in the original Transformer paper. The learning rate is increased
  linearly for the first `warmup_steps` training steps, and then decreased
  proportionally to the inverse square root of the step number.

  Args:
    d_model (int): the dimensionality of the model.
    warmup_steps (int): the number of steps taken to increase the learning rate
      linearly. Default is 4000.
  
  Attributes:
    d_model (float): the dimensionality of the model as a float.
    warmup_steps (int): the number of steps taken to increase the learning rate
      linearly.

  Methods:
    __call__(step): returns the learning rate at the given step.

  Returns:
    The learning rate at the given step.
  """
  def __init__(self, d_model, warmup_steps=4000):
    super().__init__()

    self.d_model = d_model
    self.d_model = tf.cast(self.d_model, tf.float32)

    self.warmup_steps = warmup_steps

  def __call__(self, step):
    """
    Returns the learning rate at the given step.

    Args:
      step (int): the current training step.

    Returns:
      The learning rate at the given step as a float32 tensor.
    """
    step = tf.cast(step, dtype=tf.float32)
    arg1 = tf.math.rsqrt(step)
    arg2 = step * (self.warmup_steps ** -1.5)

    return tf.math.rsqrt(self.d_model) * tf.math.minimum(arg1, arg2)

Класс CustomSchedule является подклассом tf.keras.optimizers.schedules.LearningRateSchedule. Он принимает два аргумента d_model и warmup_steps. d_model представляет размерность модели, которая отлита в float32. warmup_steps — это количество шагов для линейного увеличения скорости обучения перед ее уменьшением.

Метод __call__ принимает один аргумент step, который представляет текущий шаг обучения. Он преобразует шаг в float32 и вычисляет два аргумента arg1 и arg2. arg1 рассчитывается путем извлечения квадратного корня reciprocal из step. arg2 вычисляется путем умножения шага на обратный квадратный корень из warmup_steps, возведенного в степень -1.5.

Наконец, метод возвращает произведение обратного квадратного корня из d_model и минимума из arg1 и arg2. Этот метод используется для определения графика скорости обучения для оптимизатора, используемого в процессе обучения модели Transformer.

Теперь мы можем создать экземпляр оптимизатора (в данном примере это tf.keras.optimizers.Adam):

learning_rate = CustomSchedule(d_model)

optimizer = tf.keras.optimizers.Adam(learning_rate, 
                                     beta_1=0.9, 
                                     beta_2=0.98,
                                     epsilon=1e-9)

Давайте посмотрим, как это выглядит

plt.plot(learning_rate(tf.range(40000, dtype=tf.float32)))
plt.ylabel('Learning Rate')
plt.xlabel('Train Step')

2.6.1. Потеря

Определим функции потерь и точности. Мы используем функцию кросс-энтропийных потерь, используя tf.keras.losses.SparseCategoricalCrossentropy:

def masked_loss(label, pred):
  """
  Calculates the masked sparse categorical cross-entropy loss between the true labels and predicted labels.

  Args:
      label: A tensor of shape (batch_size, seq_length) containing the true labels.
      pred: A tensor of shape (batch_size, seq_length, target_vocab_size) containing the predicted labels.

  Returns:
      A scalar tensor representing the masked loss value.

  """
  mask = label != 0
  loss_object = tf.keras.losses.SparseCategoricalCrossentropy(
    from_logits=True, reduction='none')
  loss = loss_object(label, pred)

  mask = tf.cast(mask, dtype=loss.dtype)
  loss *= mask

  loss = tf.reduce_sum(loss)/tf.reduce_sum(mask)
  return loss

Функция masked_loss вычисляет замаскированную разреженную категориальную кросс-энтропийную потерю между предсказанными значениями и истинными метками. В этой функции входные параметры label и pred являются истинными метками и прогнозируемыми значениями соответственно.

Во-первых, функция создает логическую маску для исключения дополнительных значений (0) из расчета потерь. Затем он определяет объект потерь как SparseCategoricalCrossentropy из tf.keras.losses, который вычисляет кросс-энтропийную потерю между истинной и предсказанной метками.

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

Теперь давайте напишем функцию для вычисления точности:

def masked_accuracy(label, pred):
  """
  Calculates the masked accuracy between the true labels and predicted labels.

  Args:
      label: A tensor of shape (batch_size, seq_length) containing the true labels.
      pred: A tensor of shape (batch_size, seq_length, target_vocab_size) containing the predicted labels.

  Returns:
      A scalar tensor representing the masked accuracy value.

  """
  pred = tf.argmax(pred, axis=2)
  label = tf.cast(label, pred.dtype)
  match = label == pred

  mask = label != 0

  match = match & mask

  match = tf.cast(match, dtype=tf.float32)
  mask = tf.cast(mask, dtype=tf.float32)
  return tf.reduce_sum(match)/tf.reduce_sum(mask)

Функция masked_accuracy вычисляет скрытую точность предсказанных значений с учетом истинных меток. Входными данными функции являются label и pred, которые являются истинными метками и прогнозируемыми значениями соответственно.

Во-первых, функция использует tf.argmax для поиска индекса максимального значения в pred по последнему измерению, которое представляет прогнозируемый класс. Затем истинные метки приводятся к тому же типу данных, что и предсказанные значения pred.

Затем функция создает логическую маску, чтобы исключить из расчета дополнительные значения. Функция использует оператор & для сравнения предсказанных и истинных меток и создания соответствия логической матрицы. Значения в match указывают, совпадают ли предсказанные и истинные метки для не дополненных значений.

Затем матрица совпадения и матрица маски преобразуются в float32 и используются для вычисления средней точности по не дополненным значениям. Функция возвращает сумму совпадений, деленную на сумму масок.

2.6.3. Обучение

Теперь давайте скомпилируем модель и используем model.fit для обучения модели! Я использую графический процессор Colab A100!

transformer.compile(
    loss=masked_loss,
    optimizer=optimizer,
    metrics=[masked_accuracy])
transformer.fit(train_batches,
                epochs=20,
                validation_data=val_batches)
Epoch 1/20
810/810 [==============================] - 156s 150ms/step - loss: 6.6048 - masked_accuracy: 0.1391 - val_loss: 5.0433 - val_masked_accuracy: 0.2476
Epoch 2/20
810/810 [==============================] - 54s 67ms/step - loss: 4.5708 - masked_accuracy: 0.2982 - val_loss: 4.0412 - val_masked_accuracy: 0.3618
Epoch 3/20
810/810 [==============================] - 51s 63ms/step - loss: 3.8159 - masked_accuracy: 0.3807 - val_loss: 3.4748 - val_masked_accuracy: 0.4248
Epoch 4/20
810/810 [==============================] - 51s 62ms/step - loss: 3.2672 - masked_accuracy: 0.4412 - val_loss: 2.9885 - val_masked_accuracy: 0.4846
Epoch 5/20
810/810 [==============================] - 48s 59ms/step - loss: 2.8788 - masked_accuracy: 0.4847 - val_loss: 2.7329 - val_masked_accuracy: 0.5184
Epoch 6/20
810/810 [==============================] - 49s 60ms/step - loss: 2.5759 - masked_accuracy: 0.5214 - val_loss: 2.5032 - val_masked_accuracy: 0.5453
Epoch 7/20
810/810 [==============================] - 48s 59ms/step - loss: 2.3089 - masked_accuracy: 0.5559 - val_loss: 2.3972 - val_masked_accuracy: 0.5612
Epoch 8/20
810/810 [==============================] - 48s 59ms/step - loss: 2.1151 - masked_accuracy: 0.5822 - val_loss: 2.2334 - val_masked_accuracy: 0.5883
Epoch 9/20
810/810 [==============================] - 47s 58ms/step - loss: 1.9663 - masked_accuracy: 0.6028 - val_loss: 2.1774 - val_masked_accuracy: 0.5972
Epoch 10/20
810/810 [==============================] - 47s 57ms/step - loss: 1.8479 - masked_accuracy: 0.6200 - val_loss: 2.1218 - val_masked_accuracy: 0.6046
Epoch 11/20
810/810 [==============================] - 46s 57ms/step - loss: 1.7506 - masked_accuracy: 0.6337 - val_loss: 2.1122 - val_masked_accuracy: 0.6063
Epoch 12/20
810/810 [==============================] - 47s 57ms/step - loss: 1.6672 - masked_accuracy: 0.6466 - val_loss: 2.0984 - val_masked_accuracy: 0.6135
Epoch 13/20
810/810 [==============================] - 46s 57ms/step - loss: 1.5966 - masked_accuracy: 0.6574 - val_loss: 2.0594 - val_masked_accuracy: 0.6167
Epoch 14/20
810/810 [==============================] - 46s 56ms/step - loss: 1.5319 - masked_accuracy: 0.6670 - val_loss: 2.0512 - val_masked_accuracy: 0.6212
Epoch 15/20
810/810 [==============================] - 46s 57ms/step - loss: 1.4739 - masked_accuracy: 0.6758 - val_loss: 2.0489 - val_masked_accuracy: 0.6226
Epoch 16/20
810/810 [==============================] - 47s 57ms/step - loss: 1.4247 - masked_accuracy: 0.6834 - val_loss: 2.0458 - val_masked_accuracy: 0.6264
Epoch 17/20
810/810 [==============================] - 46s 57ms/step - loss: 1.3774 - masked_accuracy: 0.6906 - val_loss: 2.0673 - val_masked_accuracy: 0.6223
Epoch 18/20
810/810 [==============================] - 46s 56ms/step - loss: 1.3376 - masked_accuracy: 0.6972 - val_loss: 2.0647 - val_masked_accuracy: 0.6259
Epoch 19/20
810/810 [==============================] - 46s 57ms/step - loss: 1.2979 - masked_accuracy: 0.7040 - val_loss: 2.0606 - val_masked_accuracy: 0.6307
Epoch 20/20
810/810 [==============================] - 46s 56ms/step - loss: 1.2616 - masked_accuracy: 0.7099 - val_loss: 2.0773 - val_masked_accuracy: 0.6268

2.7. Тестирование

Теперь мы натренировали нашу модель на 20 Эпох, давайте попробуем использовать эту модель для перевода! Для этого напишем класс Translator.

Translator принимает tokenizers и transformer в качестве входных данных в своем конструкторе. В нем есть метод __call__, который берет предложение на португальском языке и переводит его на английский с помощью модели преобразования.

Входное предложение сначала токенизируется с помощью португальского токенизатора и преобразуется в тензор. Вход кодировщика настроен как токенизированное предложение.

Английский токен [START] добавляется к выходным данным для его инициализации. Вывод сохраняется в файле tf.TensorArray.

Для каждого токена на выходе вызывается модель преобразователя с входом энкодера и текущим выходом. Последний токен из измерения seq_len прогнозов выбирается и добавляется к выходным данным. Если последним токеном является токен [END], цикл завершается. Вывод преобразуется в текст с помощью английского токенизатора и возвращается вместе с весами внимания.

class Translator(tf.Module):
  """A translator that uses a transformer model to translate 
  sentences from Portuguese to English.

  Attributes:
      tokenizers (dict): A dictionary of tokenizers for the 
      Portuguese and English languages.
      transformer (tf.keras.Model): A transformer model that can 
      be used for sequence-to-sequence translation.
  """
  def __init__(self, tokenizers, transformer):
    self.tokenizers = tokenizers
    self.transformer = transformer

  def __call__(self, sentence, max_length=MAX_TOKENS):
    """Translates a sentence from Portuguese to English.

    Args:
        sentence (str): The sentence to be translated.
        max_length (int): The maximum number of tokens in the output sentence.

    Returns:
        tuple: A tuple containing the translated text, the tokens of the translated text,
            and the attention weights of the transformer model.
    """
    # The input sentence is Portuguese, hence adding the `[START]` and `[END]` tokens.
    assert isinstance(sentence, tf.Tensor)
    if len(sentence.shape) == 0:
      sentence = sentence[tf.newaxis]

    sentence = self.tokenizers.pt.tokenize(sentence).to_tensor()

    encoder_input = sentence

    # As the output language is English, initialize the output with the
    # English `[START]` token.
    start_end = self.tokenizers.en.tokenize([''])[0]
    start = start_end[0][tf.newaxis]
    end = start_end[1][tf.newaxis]

    # `tf.TensorArray` is required here (instead of a Python list), so that the
    # dynamic-loop can be traced by `tf.function`.
    output_array = tf.TensorArray(dtype=tf.int64, size=0, dynamic_size=True)
    output_array = output_array.write(0, start)

    for i in tf.range(max_length):
      output = tf.transpose(output_array.stack())
      predictions = self.transformer([encoder_input, output], training=False)

      # Select the last token from the `seq_len` dimension.
      predictions = predictions[:, -1:, :]  # Shape `(batch_size, 1, vocab_size)`.

      predicted_id = tf.argmax(predictions, axis=-1)

      # Concatenate the `predicted_id` to the output which is given to the
      # decoder as its input.
      output_array = output_array.write(i+1, predicted_id[0])

      if predicted_id == end:
        break

    output = tf.transpose(output_array.stack())
    # The output shape is `(1, tokens)`.
    text = tokenizers.en.detokenize(output)[0]  # Shape: `()`.

    tokens = tokenizers.en.lookup(output)[0]

    # `tf.function` prevents us from using the attention_weights that were
    # calculated on the last iteration of the loop.
    # So, recalculate them outside the loop.
    self.transformer([encoder_input, output[:,:-1]], training=False)
    attention_weights = self.transformer.decoder.last_attn_scores

    return text, tokens, attention_weights

Создайте экземпляр этого класса Translator и попробуйте несколько раз:

translator = Translator(tokenizers, transformer)

Давайте напишем функцию для перевода предложений для нас:

def print_translation(sentence, tokens, ground_truth):
  """
  The print_translation function takes in three arguments: sentence, tokens, 
  and ground_truth and prints out the input sentence, the predicted translation 
  and the ground truth translation.

  Args:

    sentence: A string that represents the input sentence.
    tokens: A tensor of integers that represents the predicted translation.
    ground_truth: A string that represents the ground truth translation.
  
  Returns:
    This function doesn't return anything, it just prints out the input sentence,
    predicted translation and ground truth translation in a specific format.
  """
  print(f'{"Input:":15s}: {sentence}')
  print(f'{"Prediction":15s}: {tokens.numpy().decode("utf-8")}')
  print(f'{"Ground truth":15s}: {ground_truth}')

Мы также можем написать функцию для возврата внимания. Давайте это напишем!

def plot_attention_head(in_tokens, translated_tokens, attention):
  """
  Plots the attention weights for a single head of the attention mechanism.

  Args:
  - in_tokens: a tensor of shape (in_seq_length,) containing the input tokens.
  - translated_tokens: a tensor of shape (out_seq_length,) containing the translated tokens.
  - attention: a tensor of shape (out_seq_length, in_seq_length) containing the attention weights.

  Returns:
  None.
  """
  # The model didn't generate `<START>` in the output. Skip it.
  translated_tokens = translated_tokens[1:]

  ax = plt.gca()
  ax.matshow(attention)
  ax.set_xticks(range(len(in_tokens)))
  ax.set_yticks(range(len(translated_tokens)))

  labels = [label.decode('utf-8') for label in in_tokens.numpy()]
  ax.set_xticklabels(
      labels, rotation=90)

  labels = [label.decode('utf-8') for label in translated_tokens.numpy()]
  ax.set_yticklabels(labels)
 def plot_attention_weights(sentence, translated_tokens, attention_heads):
  """
  Plots the attention weights for each head of the transformer model.

  Args:
      sentence (str): The input sentence in Portuguese.
      translated_tokens (tf.Tensor): The translated tokens in English.
      attention_heads (list): The attention heads of the transformer model.

  Returns:
      None
  """
  in_tokens = tf.convert_to_tensor([sentence])
  in_tokens = tokenizers.pt.tokenize(in_tokens).to_tensor()
  in_tokens = tokenizers.pt.lookup(in_tokens)[0]

  fig = plt.figure(figsize=(16, 8))

  for h, head in enumerate(attention_heads):
    ax = fig.add_subplot(2, 4, h+1)

    plot_attention_head(in_tokens, translated_tokens, head)

    ax.set_xlabel(f'Head {h+1}')

  plt.tight_layout()
  plt.show()

plot_attention_weights — это функция, которая отображает веса внимания всех головок внимания в механизме внимания с несколькими головками. Он принимает входное предложение, переведенные выходные токены и веса внимания и создает фигуру, в которой каждый подсюжет представляет одну голову внимания.

plot_attention_head — это вспомогательная функция, используемая plot_attention_weights. Он принимает входные токены, выходные токены и матрицу внимания одной головы внимания и строит тепловую карту, где ось X представляет входные токены, а ось Y представляет выходные токены. Он вызывается один раз для каждой головы внимания, чтобы создать подграфики на рисунке plot_attention_weights.

теперь давайте соберем несколько предложений для проверки:

sentence = 'Eu amo programar em Python.'
ground_truth = 'I love programming in Python.'

translated_text, translated_tokens, attention_weights = translator(
    tf.constant(sentence))
print_translation(sentence, translated_text, ground_truth)

plot_attention_weights(sentence, translated_tokens, attention_weights[0])
Input:         : Eu amo programar em Python.
Prediction     : i love to code in python .
Ground truth   : I love programming in Python.

sentence = 'O tempo está ótimo para uma caminhada no parque.'
ground_truth = 'The weather is great for a walk in the park.'

translated_text, translated_tokens, attention_weights = translator(
    tf.constant(sentence))
print_translation(sentence, translated_text, ground_truth)

plot_attention_weights(sentence, translated_tokens, attention_weights[0])

plot_attention_weights(sentence, translated_tokens, attention_weights[0])
Input:         : O tempo está ótimo para uma caminhada no parque.
Prediction     : time is great for a walk into the park .
Ground truth   : The weather is great for a walk in the park.

sentence = 'Eu preciso comprar pão e leite na padaria.'
ground_truth = "I need to buy bread and milk at the bakery."

translated_text, translated_tokens, attention_weights = translator(
    tf.constant(sentence))
print_translation(sentence, translated_text, ground_truth)

plot_attention_weights(sentence, translated_tokens, attention_weights[0])
Input:         : Eu preciso comprar pão e leite na padaria.
Prediction     : i need to buy bread and milk in parrio .
Ground truth   : I need to buy bread and milk at the bakery.

sentence = 'A tecnologia tem transformado profundamente a forma como vivemos e trabalhamos.'
ground_truth = "Technology has profoundly transformed the way we live and work."

translated_text, translated_tokens, attention_weights = translator(
    tf.constant(sentence))
print_translation(sentence, translated_text, ground_truth)

plot_attention_weights(sentence, translated_tokens, attention_weights[0])
Input:         : A tecnologia tem transformado profundamente a forma como vivemos e trabalhamos.
Prediction     : technology has changed deeply the way we live and work .
Ground truth   : Technology has profoundly transformed the way we live and work.

sentence = 'A pandemia de COVID-19 teve um impacto devastador na economia global.'
ground_truth = "The COVID-19 pandemic has had a devastating impact on the global economy."

translated_text, translated_tokens, attention_weights = translator(
    tf.constant(sentence))
print_translation(sentence, translated_text, ground_truth)

plot_attention_weights(sentence, translated_tokens, attention_weights[0])
Input:         : A pandemia de COVID-19 teve um impacto devastador na economia global.
Prediction     : pandemic devi ' s promotes a devastating impact on global economy .
Ground truth   : The COVID-19 pandemic has had a devastating impact on the global economy.

2.8. Экспорт модели

Для этого создадим класс с именем ExportTranslator, затем обернем translator в ExportTranslator:

class ExportTranslator(tf.Module):
  """
  A class for exporting a trained Translator model as a `tf.Module` for inference.

  Args:
  translator (Translator): A trained instance of the Translator class.

  Methods:
  __call__(self, sentence):
      Translates the input sentence to the target language and returns the translation.
  """
  def __init__(self, translator):
    self.translator = translator

  @tf.function(input_signature=[tf.TensorSpec(shape=[], dtype=tf.string)])
  def __call__(self, sentence):
    """
    Translates the input sentence to the target language and returns the translation.

    Args:
    sentence (tf.string): The input sentence to translate.

    Returns:
    A string tensor representing the translated sentence.

    """
    (result,
     tokens,
     attention_weights) = self.translator(sentence, max_length=MAX_TOKENS)

    return result

Класс ExportTranslator принимает объект translator в качестве входных данных и экспортирует его как модуль TensorFlow. У него есть метод __call__, который принимает одно строковое аргумент-предложение и возвращает результат перевода объекта-переводчика для этого входного предложения.

Метод __call__ украшен tf.function и input_signature, которые определяют тип данных и форму входного тензора. Тензор предложения имеет пустую форму и строковый тип данных. Метод __call__ вызывает объект переводчика с входным предложением и аргументом max_length, установленным на MAX_TOKENS, и возвращает результат перевода в виде тензора.

translator = ExportTranslator(translator)

Поскольку модель декодирует прогнозы с использованием tf.argmax, прогнозы являются детерминированными. Исходная модель и модель, загруженная из SavedModel, должны давать идентичные прогнозы:

translator('Eu amo programar em Python.').numpy()
b'i love to code in python .'

Теперь мы можем использовать метод .save для сохранения модели!

tf.saved_model.save(translator, export_dir='translator')

Мы можем перезагрузить модель и проверить прогноз!

reloaded_translator = tf.saved_model.load('translator')
reloaded_translator('Eu amo programar em Python.').numpy()
b'i love to code in python .'

2.9. Эксперименты с моделью

К сожалению, у меня нет доступа к большим вычислительным мощностям, но было бы идеально обучить модель на большее количество эпох. Я протестировал модель с более чем 20 эпохами, поэтому давайте посмотрим на результаты здесь для модели с еще 50 эпохами и сравним результаты!

transformer.fit(train_batches,
                epochs=50,
                validation_data=val_batches)
Epoch 1/50
810/810 [==============================] - 46s 56ms/step - loss: 1.2290 - masked_accuracy: 0.7155 - val_loss: 2.0777 - val_masked_accuracy: 0.6276
Epoch 2/50
810/810 [==============================] - 46s 57ms/step - loss: 1.1964 - masked_accuracy: 0.7213 - val_loss: 2.0961 - val_masked_accuracy: 0.6291
Epoch 3/50
810/810 [==============================] - 47s 57ms/step - loss: 1.1676 - masked_accuracy: 0.7255 - val_loss: 2.0876 - val_masked_accuracy: 0.6302
Epoch 4/50
810/810 [==============================] - 46s 57ms/step - loss: 1.1392 - masked_accuracy: 0.7301 - val_loss: 2.0891 - val_masked_accuracy: 0.6294
Epoch 5/50
810/810 [==============================] - 46s 57ms/step - loss: 1.1140 - masked_accuracy: 0.7346 - val_loss: 2.1056 - val_masked_accuracy: 0.6277
Epoch 6/50
810/810 [==============================] - 46s 57ms/step - loss: 1.0895 - masked_accuracy: 0.7391 - val_loss: 2.1056 - val_masked_accuracy: 0.6292
Epoch 7/50
810/810 [==============================] - 46s 57ms/step - loss: 1.0661 - masked_accuracy: 0.7431 - val_loss: 2.1239 - val_masked_accuracy: 0.6279
Epoch 8/50
810/810 [==============================] - 46s 56ms/step - loss: 1.0442 - masked_accuracy: 0.7472 - val_loss: 2.1320 - val_masked_accuracy: 0.6309
Epoch 9/50
810/810 [==============================] - 46s 56ms/step - loss: 1.0213 - masked_accuracy: 0.7512 - val_loss: 2.1312 - val_masked_accuracy: 0.6303
Epoch 10/50
810/810 [==============================] - 46s 56ms/step - loss: 1.0024 - masked_accuracy: 0.7541 - val_loss: 2.1538 - val_masked_accuracy: 0.6328
Epoch 11/50
810/810 [==============================] - 46s 56ms/step - loss: 0.9836 - masked_accuracy: 0.7580 - val_loss: 2.1624 - val_masked_accuracy: 0.6289
Epoch 12/50
810/810 [==============================] - 47s 57ms/step - loss: 0.9655 - masked_accuracy: 0.7612 - val_loss: 2.1758 - val_masked_accuracy: 0.6298
Epoch 13/50
810/810 [==============================] - 46s 57ms/step - loss: 0.9481 - masked_accuracy: 0.7642 - val_loss: 2.1796 - val_masked_accuracy: 0.6293
Epoch 14/50
810/810 [==============================] - 46s 56ms/step - loss: 0.9306 - masked_accuracy: 0.7677 - val_loss: 2.1919 - val_masked_accuracy: 0.6278
Epoch 15/50
810/810 [==============================] - 46s 56ms/step - loss: 0.9153 - masked_accuracy: 0.7703 - val_loss: 2.2142 - val_masked_accuracy: 0.6281
Epoch 16/50
810/810 [==============================] - 46s 57ms/step - loss: 0.8988 - masked_accuracy: 0.7733 - val_loss: 2.2254 - val_masked_accuracy: 0.6280
Epoch 17/50
810/810 [==============================] - 46s 57ms/step - loss: 0.8835 - masked_accuracy: 0.7761 - val_loss: 2.2310 - val_masked_accuracy: 0.6297
Epoch 18/50
810/810 [==============================] - 46s 57ms/step - loss: 0.8675 - masked_accuracy: 0.7794 - val_loss: 2.2469 - val_masked_accuracy: 0.6300
Epoch 19/50
810/810 [==============================] - 46s 56ms/step - loss: 0.8564 - masked_accuracy: 0.7810 - val_loss: 2.2532 - val_masked_accuracy: 0.6294
Epoch 20/50
810/810 [==============================] - 46s 57ms/step - loss: 0.8416 - masked_accuracy: 0.7840 - val_loss: 2.2837 - val_masked_accuracy: 0.6256
Epoch 21/50
810/810 [==============================] - 46s 57ms/step - loss: 0.8282 - masked_accuracy: 0.7869 - val_loss: 2.2786 - val_masked_accuracy: 0.6276
Epoch 22/50
810/810 [==============================] - 46s 57ms/step - loss: 0.8164 - masked_accuracy: 0.7891 - val_loss: 2.2811 - val_masked_accuracy: 0.6292
Epoch 23/50
810/810 [==============================] - 46s 57ms/step - loss: 0.8056 - masked_accuracy: 0.7912 - val_loss: 2.3101 - val_masked_accuracy: 0.6258
Epoch 24/50
810/810 [==============================] - 46s 56ms/step - loss: 0.7930 - masked_accuracy: 0.7937 - val_loss: 2.3225 - val_masked_accuracy: 0.6280
Epoch 25/50
810/810 [==============================] - 46s 57ms/step - loss: 0.7820 - masked_accuracy: 0.7958 - val_loss: 2.3360 - val_masked_accuracy: 0.6239
Epoch 26/50
810/810 [==============================] - 46s 56ms/step - loss: 0.7728 - masked_accuracy: 0.7979 - val_loss: 2.3388 - val_masked_accuracy: 0.6241
Epoch 27/50
810/810 [==============================] - 46s 57ms/step - loss: 0.7635 - masked_accuracy: 0.7995 - val_loss: 2.3493 - val_masked_accuracy: 0.6256
Epoch 28/50
810/810 [==============================] - 46s 57ms/step - loss: 0.7524 - masked_accuracy: 0.8019 - val_loss: 2.3673 - val_masked_accuracy: 0.6255
Epoch 29/50
810/810 [==============================] - 46s 56ms/step - loss: 0.7413 - masked_accuracy: 0.8039 - val_loss: 2.3591 - val_masked_accuracy: 0.6285
Epoch 30/50
810/810 [==============================] - 46s 56ms/step - loss: 0.7325 - masked_accuracy: 0.8058 - val_loss: 2.3437 - val_masked_accuracy: 0.6295
Epoch 31/50
810/810 [==============================] - 46s 56ms/step - loss: 0.7243 - masked_accuracy: 0.8073 - val_loss: 2.3701 - val_masked_accuracy: 0.6244
Epoch 32/50
810/810 [==============================] - 46s 57ms/step - loss: 0.7145 - masked_accuracy: 0.8095 - val_loss: 2.4013 - val_masked_accuracy: 0.6231
Epoch 33/50
810/810 [==============================] - 46s 56ms/step - loss: 0.7049 - masked_accuracy: 0.8117 - val_loss: 2.3897 - val_masked_accuracy: 0.6269
Epoch 34/50
810/810 [==============================] - 46s 56ms/step - loss: 0.6968 - masked_accuracy: 0.8127 - val_loss: 2.4029 - val_masked_accuracy: 0.6253
Epoch 35/50
810/810 [==============================] - 46s 57ms/step - loss: 0.6897 - masked_accuracy: 0.8144 - val_loss: 2.4204 - val_masked_accuracy: 0.6242
Epoch 36/50
810/810 [==============================] - 45s 56ms/step - loss: 0.6823 - masked_accuracy: 0.8162 - val_loss: 2.4231 - val_masked_accuracy: 0.6225
Epoch 37/50
810/810 [==============================] - 46s 56ms/step - loss: 0.6744 - masked_accuracy: 0.8176 - val_loss: 2.4494 - val_masked_accuracy: 0.6234
Epoch 38/50
810/810 [==============================] - 46s 56ms/step - loss: 0.6673 - masked_accuracy: 0.8193 - val_loss: 2.4518 - val_masked_accuracy: 0.6231
Epoch 39/50
810/810 [==============================] - 46s 56ms/step - loss: 0.6580 - masked_accuracy: 0.8213 - val_loss: 2.4534 - val_masked_accuracy: 0.6244
Epoch 40/50
810/810 [==============================] - 46s 56ms/step - loss: 0.6505 - masked_accuracy: 0.8232 - val_loss: 2.4740 - val_masked_accuracy: 0.6195
Epoch 41/50
810/810 [==============================] - 46s 56ms/step - loss: 0.6450 - masked_accuracy: 0.8242 - val_loss: 2.4826 - val_masked_accuracy: 0.6237
Epoch 42/50
810/810 [==============================] - 46s 56ms/step - loss: 0.6369 - masked_accuracy: 0.8260 - val_loss: 2.4928 - val_masked_accuracy: 0.6223
Epoch 43/50
810/810 [==============================] - 46s 56ms/step - loss: 0.6310 - masked_accuracy: 0.8276 - val_loss: 2.4958 - val_masked_accuracy: 0.6231
Epoch 44/50
810/810 [==============================] - 46s 56ms/step - loss: 0.6248 - masked_accuracy: 0.8286 - val_loss: 2.4985 - val_masked_accuracy: 0.6230
Epoch 45/50
810/810 [==============================] - 46s 56ms/step - loss: 0.6188 - masked_accuracy: 0.8302 - val_loss: 2.5236 - val_masked_accuracy: 0.6210
Epoch 46/50
810/810 [==============================] - 45s 56ms/step - loss: 0.6124 - masked_accuracy: 0.8313 - val_loss: 2.5070 - val_masked_accuracy: 0.6226
Epoch 47/50
810/810 [==============================] - 47s 57ms/step - loss: 0.6070 - masked_accuracy: 0.8325 - val_loss: 2.5257 - val_masked_accuracy: 0.6233
Epoch 48/50
810/810 [==============================] - 46s 57ms/step - loss: 0.6006 - masked_accuracy: 0.8339 - val_loss: 2.5379 - val_masked_accuracy: 0.6211
Epoch 49/50
810/810 [==============================] - 46s 56ms/step - loss: 0.5949 - masked_accuracy: 0.8356 - val_loss: 2.5461 - val_masked_accuracy: 0.6221
Epoch 50/50
810/810 [==============================] - 46s 56ms/step - loss: 0.5890 - masked_accuracy: 0.8368 - val_loss: 2.5491 - val_masked_accuracy: 0.6221
translator = Translator(tokenizers, transformer)
sentence = 'Eu amo programar em Python.'
ground_truth = 'I love programming in Python.'

translated_text, translated_tokens, attention_weights = translator(
    tf.constant(sentence))
print_translation(sentence, translated_text, ground_truth)

plot_attention_weights(sentence, translated_tokens, attention_weights[0])
Input:         : Eu amo programar em Python.
Prediction     : i love programming in python .
Ground truth   : I love programming in Python.

sentence = 'O tempo está ótimo para uma caminhada no parque.'
ground_truth = 'The weather is great for a walk in the park.'

translated_text, translated_tokens, attention_weights = translator(
    tf.constant(sentence))
print_translation(sentence, translated_text, ground_truth)

plot_attention_weights(sentence, translated_tokens, attention_weights[0])

plot_attention_weights(sentence, translated_tokens, attention_weights[0])
Input:         : O tempo está ótimo para uma caminhada no parque.
Prediction     : time is great for a walk in a park .
Ground truth   : The weather is great for a walk in the park.

sentence = 'Eu preciso comprar pão e leite na padaria.'
ground_truth = "I need to buy bread and milk at the bakery."

translated_text, translated_tokens, attention_weights = translator(
    tf.constant(sentence))
print_translation(sentence, translated_text, ground_truth)

plot_attention_weights(sentence, translated_tokens, attention_weights[0])
Input:         : Eu preciso comprar pão e leite na padaria.
Prediction     : i need to buy bread and milk in pricaline .
Ground truth   : I need to buy bread and milk at the bakery.

sentence = 'A tecnologia tem transformado profundamente a forma como vivemos e trabalhamos.'
ground_truth = "Technology has profoundly transformed the way we live and work."

translated_text, translated_tokens, attention_weights = translator(
    tf.constant(sentence))
print_translation(sentence, translated_text, ground_truth)

plot_attention_weights(sentence, translated_tokens, attention_weights[0])
Input:         : A tecnologia tem transformado profundamente a forma como vivemos e trabalhamos.
Prediction     : and technology has been converted deeply to how we live and work .
Ground truth   : Technology has profoundly transformed the way we live and work.

sentence = 'A pandemia de COVID-19 teve um impacto devastador na economia global.'
ground_truth = "The COVID-19 pandemic has had a devastating impact on the global economy."

translated_text, translated_tokens, attention_weights = translator(
    tf.constant(sentence))
print_translation(sentence, translated_text, ground_truth)

plot_attention_weights(sentence, translated_tokens, attention_weights[0])
Input:         : A pandemia de COVID-19 teve um impacto devastador na economia global.
Prediction     : pandemic ' s tidower has been a global impact on global economy .
Ground truth   : The COVID-19 pandemic has had a devastating impact on the global economy.

Давайте экспортируем модель.

translator = ExportTranslator(translator)
tf.saved_model.save(translator, export_dir='translator_70')

Так что, кажется, у нас есть немного лучший перевод!

Последнее примечание перед прощанием: это руководство в значительной степени основано на https://www.tensorflow.org/text/tutorials/transformer. Я попытался сделать объяснение ясным и адаптировать его к моей аудитории, проходящей эту серию курсов! Настоятельно рекомендую пройти основной материал!

Спасибо, что прочитали мой пост, и я надеюсь, что он был полезен для вас. Если вам понравилась статья и вы хотите выразить свою поддержку, рассмотрите следующие действия:
👏 Поддержите историю аплодисментами, чтобы она стала более заметной.
📖 Подпишитесь на меня в Medium, чтобы получить доступ к большему количеству контента в моем профиле.
🛎 Свяжитесь со мной в LinkedIn, чтобы получать обновления.

Рекомендации

[1] Васвани, Ашиш и др. «Внимание — это все, что вам нужно». Достижения в области нейронных систем обработки информации 30 (2017).

[2] https://python.plainenglish.io/image-captioning-with-an-end-to-end-transformer-network-8f39e1438cd4

[3] https://www.tensorflow.org/text/tutorials/transformer

Повышение уровня кодирования

Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:

  • 👏 Хлопайте за историю и подписывайтесь на автора 👉
  • 📰 Смотрите больше контента в публикации Level Up Coding
  • 💰 Бесплатный курс собеседования по программированию ⇒ Просмотреть курс
  • 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"

🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу