Вопрос или проблема
Я пытаюсь реализовать синусоидальное позиционное кодирование. Я нашел два решения, которые дают разные кодировки. Мне интересно, является ли одно из них неправильным или оба правильные. Я демонстрирую визуальные изображения полученных кодировок для обоих вариантов. Спасибо 🙂
class SinusoidalPosEmb(nn.Module):
def __init__(self, dim):
super().__init__()
self.dim = dim
def forward(self, x):
device = x.device
half_dim = self.dim // 2
emb = math.log(10000) / (half_dim - 1)
emb = torch.exp(torch.arange(half_dim, device=device) * -emb)
emb = x[:, None] * emb[None, :]
emb = torch.cat((emb.sin(), emb.cos()), dim=-1)
return emb
2)
class TransformerPositionalEmbedding(nn.Module):
"""
Из статьи "Attention Is All You Need", раздел 3.5
"""
def __init__(self, dimension, max_timesteps=1000):
super(TransformerPositionalEmbedding, self).__init__()
assert dimension % 2 == 0, "Размер кодирования должен быть четным"
self.dimension = dimension
self.pe_matrix = torch.zeros(max_timesteps, dimension)
# Собираем все четные размеры по вектору кодирования
even_indices = torch.arange(0, self.dimension, 2)
# Вычисляем термин, используя логарифмические преобразования для более быстрых расчетов
# (https://stackoverflow.com/questions/17891595/pow-vs-exp-performance)
log_term = torch.log(torch.tensor(10000.0)) / self.dimension
div_term = torch.exp(even_indices * -log_term)
# Предварительно вычисляем матрицу позиционного кодирования на основе четных/нечетных временных шагов
timesteps = torch.arange(max_timesteps).unsqueeze(1)
self.pe_matrix[:, 0::2] = torch.sin(timesteps * div_term)
self.pe_matrix[:, 1::2] = torch.cos(timesteps * div_term)
def forward(self, timestep):
# [bs, d_model]
return self.pe_matrix[timestep]
Ответ или решение
Вопрос о том, следует ли интерлевировать синусы и косинусы в синусоидальном позиционном кодировании, является предметом обсуждения в сообществах, занимающихся обработкой естественного языка и моделированием последовательностей, таких как архитектуры трансформеров. Рассмотрим два представленных примера кода, чтобы более детально разобраться в вопросе.
Сравнение двух подходов
-
Первый подход: SinusoidalPosEmb
- В этом случае код вычисляет позиционное кодирование для каждого вектора входных данных, используя
sin
иcos
функции в юнити (однородных) множителях, где результирующие значения соединяются (конкатенируются) вдоль последнего измерения. То есть, для половины размерности (half_dim) получаются значения синуса и косинуса, которые потом объединяются в одно кодирование.
emb = torch.cat((emb.sin(), emb.cos()), dim=-1)
Этот метод создает выход, в котором для каждой позиции применяется синус и косинус изменения через переменную
x
и затем объединяются в вектор. - В этом случае код вычисляет позиционное кодирование для каждого вектора входных данных, используя
-
Второй подход: TransformerPositionalEmbedding
- Здесь используются отдельные индексы для синусов и косинусов. Позиционное кодирование создается за счет вычисления синуса для четных индексов и косинуса для нечетных индексов. Это определяет, что каждая из стран кодирования будет занимать свою "группу" в векторе.
self.pe_matrix[:, 0::2] = torch.sin(timesteps * div_term) self.pe_matrix[:, 1::2] = torch.cos(timesteps * div_term)
Это упрощает интерпретацию кодирования и, возможно, лучше наносит структуры данных на низком уровне для продвинутых моделей.
Правильность подходов
Оба подхода принципиально корректны и могут быть использованы в зависимости от вашей специфической задачи, однако между ними стоит учитывать следующее:
-
Читаемость и интерпретируемость: Второй подход с раздельным использованием синусов и косинусов может быть более понятным для разработчиков и позволяет четко видеть, как синусы и косинусы распределяются между позициями.
-
Производительность: Первый подход создает дополнительные вычисления и может быть менее эффективным в плане производительности, поскольку добавляет шаг конкатенации. Однако, если размерность
dim
небольшая, различия могут быть незначительными. -
Тактические нюансы: С точки зрения архитектуры трансформеров, некоторые улучшения могут быть вычислены если будут учитывать четные и нечетные позиции отдельно. Это может привести к быстрому кодированию и более высокому качеству при обработке данных.
Заключение
В целом, выбор между интерлевированием синуса и косинуса в синусоидальном позиционном кодировании зависит от вашей специфической задачи и предпочтений. Оба подхода работают, и оба имеют свои плюсы и минусы. Однако, учитывая выбранные вами архитектуры и как они обрабатывают данные, вы можете применить тот или иной способ основываясь на ваших требованиях к модельному качеству, производительности и читаемости кода.