Позиционное кодирование

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

Основная идея

Позиционное кодирование добавляет к эмбеддингам токенов дополнительный вектор, который несет информацию о позиции токена в последовательности. Если обозначить эмбеддинг токена как ( x_i ), а позиционное кодирование как ( p_i ), итоговый вход в модель вычисляется как:

[ z_i = x_i + p_i]

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

Формулы синусоидального позиционного кодирования

Синусоидальное кодирование является наиболее часто используемым способом. Для позиции ( pos ) и размерности ( d ) создаются компоненты:

[ _{(pos, 2i)} = ()]

[ _{(pos, 2i+1)} = ()]

где ( i ) — индекс подкомпоненты в эмбеддинге. Эта конструкция позволяет сети легко обучаться обобщению на более длинные последовательности, чем те, что встречались при обучении.

Реализация в TensorFlow.js

В TensorFlow.js позиционное кодирование можно реализовать с помощью операций над тензорами. Для синусоидального варианта создается матрица размером [sequenceLength, embeddingDim].

import * as tf from '@tensorflow/tfjs';

function getPositionalEncoding(sequenceLength, embeddingDim) {
  const pos = tf.range(0, sequenceLength, 1, 'float32').expandDims(1);
  const i = tf.range(0, embeddingDim, 1, 'float32').expandDims(0);
  const angleRates = tf.div(pos, tf.pow(10000, tf.div(i, embeddingDim)));
  const sinTerms = tf.sin(angleRates.slice([0,0],[sequenceLength, embeddingDim/2]));
  const cosTerms = tf.cos(angleRates.slice([0,embeddingDim/2],[sequenceLength, embeddingDim/2]));
  return tf.concat([sinTerms, cosTerms], 1);
}

Пояснения к коду:

  • tf.range создает последовательность индексов позиций и размерностей.
  • expandDims формирует корректные размеры для последующих операций.
  • tf.div и tf.pow реализуют деление на степени (10000^{2i/d}) для каждой компоненты.
  • tf.slice разделяет индексы на синусоидальные и косинусоидальные части.
  • tf.concat объединяет их в окончательный тензор позиционного кодирования.

Добавление к эмбеддингам токенов

Эмбеддинги токенов обычно получаются через слой tf.layers.embedding. После вычисления позиционного кодирования, итоговый тензор складывается с эмбеддингами:

const tokenEmbeddings = embeddingLayer.apply(tokenIds); // [batchSize, seqLen, embeddingDim]
const positionalEncoding = getPositionalEncoding(seqLen, embeddingDim); // [seqLen, embeddingDim]
const inputEmbeddings = tokenEmbeddings.add(positionalEncoding); // Broadcasting по batchSize

TensorFlow.js автоматически распространяет позиционное кодирование по всем элементам батча благодаря broadcasting.

Варианты позиционного кодирования

  1. Обучаемое позиционное кодирование Вместо фиксированных синусов и косинусов создаются обучаемые тензоры tf.variable, которые оптимизируются вместе с моделью. Это может давать лучшие результаты на конкретных задачах.

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

Особенности использования

  • Длина последовательности, на которую рассчитано позиционное кодирование, должна совпадать с максимальной длиной входа модели. Для динамических последовательностей часто используют маскирование.
  • Позиционное кодирование добавляется к эмбеддингам до передачи в слои внимания (tf.layers.multiHeadAttention).
  • Эффективность синусоидального кодирования проявляется при генерализации на более длинные последовательности, чем те, что встречались на обучении.

Производительность

Для больших последовательностей стоит избегать циклов и использовать векторизированные операции TensorFlow.js. Тензорные операции позволяют GPU и WebGL ускорять вычисления, что особенно важно для браузерных реализаций трансформеров.

Применение в задачах

  • NLP: машинный перевод, суммаризация текста, классификация.
  • Временные ряды: прогнозирование финансовых и сенсорных данных.
  • Генерация данных: аудио и музыка, где порядок временных отсчетов критичен.

Позиционное кодирование является ключевым элементом архитектуры трансформеров в TensorFlow.js, обеспечивая моделям понимание структуры последовательностей без утраты параллельной обработки.