Должен ли я чередовать синус и косинус в синусоидальном позиционном кодировании?

Вопрос или проблема

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

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]

вставьте описание изображения сюда

Ответ или решение

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

Сравнение двух подходов

  1. Первый подход: SinusoidalPosEmb

    • В этом случае код вычисляет позиционное кодирование для каждого вектора входных данных, используя sin и cos функции в юнити (однородных) множителях, где результирующие значения соединяются (конкатенируются) вдоль последнего измерения. То есть, для половины размерности (half_dim) получаются значения синуса и косинуса, которые потом объединяются в одно кодирование.
    emb = torch.cat((emb.sin(), emb.cos()), dim=-1)

    Этот метод создает выход, в котором для каждой позиции применяется синус и косинус изменения через переменную x и затем объединяются в вектор.

  2. Второй подход: TransformerPositionalEmbedding

    • Здесь используются отдельные индексы для синусов и косинусов. Позиционное кодирование создается за счет вычисления синуса для четных индексов и косинуса для нечетных индексов. Это определяет, что каждая из стран кодирования будет занимать свою "группу" в векторе.
    self.pe_matrix[:, 0::2] = torch.sin(timesteps * div_term)
    self.pe_matrix[:, 1::2] = torch.cos(timesteps * div_term)

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

Правильность подходов

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

  • Читаемость и интерпретируемость: Второй подход с раздельным использованием синусов и косинусов может быть более понятным для разработчиков и позволяет четко видеть, как синусы и косинусы распределяются между позициями.

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

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

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...