← Назад к вопросам

Как в трансформерах учитывается позиция слов?

1.7 Middle🔥 241 комментариев
#Глубокое обучение#NLP и обработка текста

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Positional Encoding в трансформерах

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

Проблема: почему нужна информация о позиции

RNN читает последовательно

RNN: "Кот ловит мышь"
      ↓
  Обработал "Кот"
      ↓
  Обработал "ловит" (помнит про "Кот")
      ↓
  Обработал "мышь" (помнит про "Кот" и "ловит")

Позиция информация заложена в последовательности обработки.

Трансформер обрабатывает параллельно

Трансформер видит все слова сразу:
"Кот"  "ловит"  "мышь"
  ↓        ↓        ↓
  [обрабатываются ВСЕ одновременно]

Без информации о позиции, модель не поймёт, какое слово идёт первым!

Решение: Positional Encoding

Оригинальный подход в BERT/GPT

Для каждой позиции в предложении создают вектор на основе синусов и косинусов разных частот:

import numpy as np
import math

def positional_encoding(seq_length, d_model):
    """
    seq_length - длина предложения (например, 10)
    d_model - размерность эмбеддинга (например, 512)
    """
    PE = np.zeros((seq_length, d_model))
    
    position = np.arange(0, seq_length).reshape(-1, 1)
    div_term = np.exp(np.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
    
    # Чётные индексы: sin
    PE[:, 0::2] = np.sin(position * div_term)
    
    # Нечётные индексы: cos
    PE[:, 1::2] = np.cos(position * div_term)
    
    return PE

# Пример
PE = positional_encoding(seq_length=10, d_model=512)
print(PE[0])  # Вектор позиции для 1-го слова
print(PE[5])  # Вектор позиции для 6-го слова

Математическая формула

PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

где:
- pos - позиция слова в предложении (0, 1, 2, ...)
- i - размерность эмбеддинга (0, 1, 2, ...)
- d_model - общая размерность (обычно 512)

Как это работает: пример

Предложение: "Я люблю кодировать"

Слово 1 "Я" (позиция 0):
  Эмбеддинг: [0.2, 0.8, -0.1, 0.5, ...]
  + Positional encoding[0]: [0.0, 1.0, 0.0, 0.99, ...]
  = Итоговое представление: [0.2, 1.8, -0.1, 1.49, ...]

Слово 2 "люблю" (позиция 1):
  Эмбеддинг: [0.3, 0.7, 0.2, 0.4, ...]
  + Positional encoding[1]: [0.84, 0.54, 0.99, -0.13, ...]
  = Итоговое представление: [1.14, 1.24, 1.19, 0.27, ...]

Слово 3 "кодировать" (позиция 2):
  Эмбеддинг: [0.6, 0.3, -0.4, 0.8, ...]
  + Positional encoding[2]: [0.91, -0.41, 0.67, -0.98, ...]
  = Итоговое представление: [1.51, -0.11, 0.27, -0.18, ...]

Каждое слово теперь имеет уникальное представление на основе его позиции!

Почему синусы и косинусы?

Свойства, которые нужны

  1. Периодичность разных частот: каждая позиция имеет уникальный паттерн
  2. Масштабируемость: работает для текстов разной длины
  3. Отношения между позициями: модель может выучить относительные расстояния
# Графическое представление
import matplotlib.pyplot as plt

PE = positional_encoding(seq_length=100, d_model=512)

plt.figure(figsize=(12, 6))
plt.imshow(PE[:50, :100], cmap='viridis', aspect='auto')
plt.colorbar()
plt.title('Positional Encodings (первые 50 позиций, первые 100 размерностей)')
plt.xlabel('Размерность эмбеддинга')
plt.ylabel('Позиция слова')
plt.show()

# Видно: волны разных частот создают уникальный паттерн для каждой позиции

Альтернативные подходы

1. Learnable Positional Embeddings (как в T5)

Вместо формулы синус-косинус, используют обычную таблицу векторов, которая обучается вместе с моделью:

import torch.nn as nn

pos_embeddings = nn.Embedding(max_seq_length, d_model)
# max_seq_length = 512 (например)
# Это обычная матрица весов, которая обучается градиентным спуском

Плюсы: более гибко, может выучить оптимальное представление Минусы: требует переобучения для новых длин

2. Relative Positional Bias (как в DeBERTa)

Вместо абсолютной позиции, используют относительное расстояние между словами:

# Вместо PE(pos), используют PE(i - j)
# где i и j - позиции двух слов
# Это более универсально для разных длин текста

3. ALiBi (Attention with Linear Biases, как в BloomBerg)

Добавляют смещение прямо в attention механизм:

# вместо отдельного вектора позиции
# добавляют линейный сдвиг зависящий от расстояния
attention_scores += position_bias_matrix

Практический пример в PyTorch

import torch
import torch.nn as nn
from math import pi

class TransformerWithPositionalEncoding(nn.Module):
    def __init__(self, vocab_size, d_model, max_seq_length):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, d_model)
        self.positional_encoding = self._create_positional_encoding(max_seq_length, d_model)
        
    def _create_positional_encoding(self, max_seq_length, d_model):
        PE = torch.zeros(max_seq_length, d_model)
        position = torch.arange(0, max_seq_length).unsqueeze(1).float()
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * 
                            -(math.log(10000.0) / d_model))
        
        PE[:, 0::2] = torch.sin(position * div_term)
        PE[:, 1::2] = torch.cos(position * div_term)
        
        return PE.unsqueeze(0)  # [1, max_seq_length, d_model]
    
    def forward(self, input_ids):
        seq_length = input_ids.shape[1]
        
        # Эмбеддинги слов
        embeddings = self.embedding(input_ids)  # [batch, seq_len, d_model]
        
        # Добавляем позиции
        embeddings = embeddings + self.positional_encoding[:, :seq_length, :]
        
        return embeddings

Ключевые вывод

  1. Трансформеры обрабатывают параллельно → нужна информация о позиции
  2. Positional encoding добавляется к эмбеддингам → каждое слово становится уникальным
  3. Синусы разных частот → создают уникальный паттерн для каждой позиции
  4. Масштабируется автоматически → работает для текстов разной длины
  5. Существуют альтернативы → learnable embeddings, relative positions, ALiBi
Как в трансформерах учитывается позиция слов? | PrepBro