Как в трансформерах учитывается позиция слов?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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, ...]
Каждое слово теперь имеет уникальное представление на основе его позиции!
Почему синусы и косинусы?
Свойства, которые нужны
- Периодичность разных частот: каждая позиция имеет уникальный паттерн
- Масштабируемость: работает для текстов разной длины
- Отношения между позициями: модель может выучить относительные расстояния
# Графическое представление
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
Ключевые вывод
- Трансформеры обрабатывают параллельно → нужна информация о позиции
- Positional encoding добавляется к эмбеддингам → каждое слово становится уникальным
- Синусы разных частот → создают уникальный паттерн для каждой позиции
- Масштабируется автоматически → работает для текстов разной длины
- Существуют альтернативы → learnable embeddings, relative positions, ALiBi