Создайте свою собственную модель Transformer с нуля с помощью PyTorch

В этом уроке мы создадим базовую модель Transformer с нуля, используя PyTorch. Модель Transformer, представленная Vaswani et al. в статье «Attention is All You Need» — это архитектура глубокого обучения, предназначенная для последовательных задач, таких как машинный перевод и анализ текста. Она основана на механизмах внутреннего внимания и стала основой для многих современных моделей обработки естественного языка, таких как GPT и BERT.

@qatesting – гайды и уроки по QA Тестированию

Чтобы построить нашу модель, мы выполним следующие шаги:

  1. Импортируем необходимые библиотеки и модули
  2. Определим основные строительные блоки: Multi-Head Attention, Position-wise Feed-Forward Networks, Positional Encoding
  3. Создадим слои кодировщика и декодера
  4. Объединим слои кодировщика и декодера, чтобы создать полную модель
  5. Подготовим образцы данных
  6. Обучим модель

Начнём с импорта необходимых библиотек и модулей:

import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data
import math
import copy

Теперь мы определим основные строительные блоки модели Transformer.

Multi-Head Attention

Создайте свою собственную модель Transformer с нуля с помощью PyTorch

Механизм Multi-Head Attention вычисляет внимание между каждой парой позиций в последовательности. Он состоит из нескольких элементов, которые фиксируют различные аспекты входной последовательности.

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, num_heads):
        super(MultiHeadAttention, self).__init__()
        assert d_model % num_heads == 0, "d_model must be divisible by num_heads"
        
        self.d_model = d_model
        self.num_heads = num_heads
        self.d_k = d_model // num_heads
        
        self.W_q = nn.Linear(d_model, d_model)
        self.W_k = nn.Linear(d_model, d_model)
        self.W_v = nn.Linear(d_model, d_model)
        self.W_o = nn.Linear(d_model, d_model)
        
    def scaled_dot_product_attention(self, Q, K, V, mask=None):
        attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
        if mask is not None:
            attn_scores = attn_scores.masked_fill(mask == 0, -1e9)
        attn_probs = torch.softmax(attn_scores, dim=-1)
        output = torch.matmul(attn_probs, V)
        return output
        
    def split_heads(self, x):
        batch_size, seq_length, d_model = x.size()
        return x.view(batch_size, seq_length, self.num_heads, self.d_k).transpose(1, 2)
        
    def combine_heads(self, x):
        batch_size, _, seq_length, d_k = x.size()
        return x.transpose(1, 2).contiguous().view(batch_size, seq_length, self.d_model)
        
    def forward(self, Q, K, V, mask=None):
        Q = self.split_heads(self.W_q(Q))
        K = self.split_heads(self.W_k(K))
        V = self.split_heads(self.W_v(V))
        
        attn_output = self.scaled_dot_product_attention(Q, K, V, mask)
        output = self.W_o(self.combine_heads(attn_output))
        return output

Код MultiHeadAttention инициализирует модуль входными параметрами и слоями линейного преобразования. Он вычисляет показатели внимания, преобразует входной тензор в несколько элементов и объединяет все их результаты.

Position-wise Feed-Forward Networks

class PositionWiseFeedForward(nn.Module):
    def __init__(self, d_model, d_ff):
        super(PositionWiseFeedForward, self).__init__()
        self.fc1 = nn.Linear(d_model, d_ff)
        self.fc2 = nn.Linear(d_ff, d_model)
        self.relu = nn.ReLU()

    def forward(self, x):
        return self.fc2(self.relu(self.fc1(x)))

Класс PositionWiseFeedForward расширяет nn.Module PyTorch и реализует сеть прямой связи с учётом позиции. Класс инициализируется двумя слоями линейного преобразования и функцией активации ReLU. Прямой метод последовательно применяет эти преобразования и функцию активации для вычисления выходных данных. Этот процесс позволяет модели учитывать положение входных элементов при прогнозировании.

Positional Encoding

Позиционное кодирование используется для ввода информации о положении каждого токена во входную последовательность. Он использует функции синуса и косинуса разных частот для генерации позиционного кодирования:

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, max_seq_length):
        super(PositionalEncoding, self).__init__()
        
        pe = torch.zeros(max_seq_length, d_model)
        position = torch.arange(0, max_seq_length, dtype=torch.float).unsqueeze(1)
        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)
        
        self.register_buffer('pe', pe.unsqueeze(0))
        
    def forward(self, x):
        return x + self.pe[:, :x.size(1)]

Класс PositionalEncoding инициализируется входными параметрами d_model и max_seq_length, создавая тензор для хранения значений позиционного кодирования. Класс вычисляет значения синуса и косинуса для чётных и нечётных индексов соответственно на основе коэффициента масштабирования div_term. Прямой метод вычисляет позиционное кодирование, добавляя сохранённые значения позиционного кодирования к входному тензору, позволяя модели собирать информацию о позиции входной последовательности.

Теперь мы создадим слои кодировщика и декодера.

Слой кодировщика

Создайте свою собственную модель Transformer с нуля с помощью PyTorch

Слой кодировщика состоит из слоя Multi-Head Attention, слоя Position-wise Feed-Forward и двух слоёв Layer Normalization.

class EncoderLayer(nn.Module):
    def __init__(self, d_model, num_heads, d_ff, dropout):
        super(EncoderLayer, self).__init__()
        self.self_attn = MultiHeadAttention(d_model, num_heads)
        self.feed_forward = PositionWiseFeedForward(d_model, d_ff)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, x, mask):
        attn_output = self.self_attn(x, x, x, mask)
        x = self.norm1(x + self.dropout(attn_output))
        ff_output = self.feed_forward(x)
        x = self.norm2(x + self.dropout(ff_output))
        return x

Класс EncoderLayer инициализируется входными параметрами и компонентами, включая модуль MultiHeadAttention, модуль PositionWiseFeedForward, двухуровневые модули нормализации и слой исключения. Прямые методы вычисляют выходные данные уровня кодировщика, применяя собственное внимание, добавляя выходные данные внимания к входному тензору и нормализуя результат. Затем он вычисляет вывод прямой связи по положению, объединяет его с нормализованным выводом собственного внимания и нормализует окончательный результат перед возвратом обработанного тензора.

Слой декодера

Создайте свою собственную модель Transformer с нуля с помощью PyTorch

Слой декодера состоит из двух слоёв Multi-Head Attention, слоя Position-wise Feed-Forward layer и трёх слоев Layer Normalization.

class DecoderLayer(nn.Module):
    def __init__(self, d_model, num_heads, d_ff, dropout):
        super(DecoderLayer, self).__init__()
        self.self_attn = MultiHeadAttention(d_model, num_heads)
        self.cross_attn = MultiHeadAttention(d_model, num_heads)
        self.feed_forward = PositionWiseFeedForward(d_model, d_ff)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.norm3 = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, x, enc_output, src_mask, tgt_mask):
        attn_output = self.self_attn(x, x, x, tgt_mask)
        x = self.norm1(x + self.dropout(attn_output))
        attn_output = self.cross_attn(x, enc_output, enc_output, src_mask)
        x = self.norm2(x + self.dropout(attn_output))
        ff_output = self.feed_forward(x)
        x = self.norm3(x + self.dropout(ff_output))
        return x

DecoderLayer инициализируется входными параметрами и компонентами, такими как модули MultiHeadAttention для маскированного внутреннего и перекрёстного внимания, модуль PositionWiseFeedForward, трехуровневые модули нормализации и слой исключения.

Прямой метод вычисляет выходные данные уровня декодера, выполняя следующие шаги:

  1. Вычисление замаскированного вывода собственного внимания и добавление его к входному тензору с последующим отсевом и нормализацией слоя.
  2. Вычисление вывода перекрёстного внимания между выходами декодера и кодировщика и добавление его к нормализованному замаскированному выводу собственного внимания с последующим отсевом и нормализацией слоя.
  3. Расчёт выходных данных с прямой связью по положению и объединение их с нормализованными выходными данными перекрёстного внимания с последующим отсевом и нормализацией слоев.
  4. Возвращение обработанного тензора.

Эти операции позволяют декодеру генерировать целевые последовательности на основе ввода и вывода кодера.

Теперь давайте объединим оба слоя, чтобы создать полную модель Transformer.

Модель Transformer

Создайте свою собственную модель Transformer с нуля с помощью PyTorch

Объединяем всё вместе:

class Transformer(nn.Module):
    def __init__(self, src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout):
        super(Transformer, self).__init__()
        self.encoder_embedding = nn.Embedding(src_vocab_size, d_model)
        self.decoder_embedding = nn.Embedding(tgt_vocab_size, d_model)
        self.positional_encoding = PositionalEncoding(d_model, max_seq_length)

        self.encoder_layers = nn.ModuleList([EncoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])
        self.decoder_layers = nn.ModuleList([DecoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])

        self.fc = nn.Linear(d_model, tgt_vocab_size)
        self.dropout = nn.Dropout(dropout)

    def generate_mask(self, src, tgt):
        src_mask = (src != 0).unsqueeze(1).unsqueeze(2)
        tgt_mask = (tgt != 0).unsqueeze(1).unsqueeze(3)
        seq_length = tgt.size(1)
        nopeak_mask = (1 - torch.triu(torch.ones(1, seq_length, seq_length), diagonal=1)).bool()
        tgt_mask = tgt_mask & nopeak_mask
        return src_mask, tgt_mask

    def forward(self, src, tgt):
        src_mask, tgt_mask = self.generate_mask(src, tgt)
        src_embedded = self.dropout(self.positional_encoding(self.encoder_embedding(src)))
        tgt_embedded = self.dropout(self.positional_encoding(self.decoder_embedding(tgt)))

        enc_output = src_embedded
        for enc_layer in self.encoder_layers:
            enc_output = enc_layer(enc_output, src_mask)

        dec_output = tgt_embedded
        for dec_layer in self.decoder_layers:
            dec_output = dec_layer(dec_output, enc_output, src_mask, tgt_mask)

        output = self.fc(dec_output)
        return output

Класс Transformer объединяет ранее определённые модули для создания полной модели Transformer. Во время инициализации модуль Transformer устанавливает входные параметры и инициализирует различные компоненты, в том числе встраивающие слои для исходной и целевой последовательностей, модуль PositionalEncoding, модули EncoderLayer и DecoderLayer для создания сложенных слоев, линейный слой для проецирования выходных данных декодера и слой исключения.

Метод generate_mask создаёт двоичные маски для исходной и целевой последовательностей, чтобы игнорировать маркеры заполнения и не допускать, чтобы декодер обращался к будущим маркерам. Прямой метод вычисляет выходные данные модели Transformer с помощью следующих шагов:

  1. Генерация исходной и целевой маски с помощью метода generate_mask.
  2. Вычисление исходного и целевого встраивания, а также применение позиционного кодирования и отсев.
  3. Обработка исходной последовательности через слои кодировщика, обновив тензор enc_output.
  4. Обработка целевой последовательности через слои декодера, используя enc_output и маски, и обновление тензора dec_output.
  5. Применение слоя линейной проекции к выходным данным декодера, получив выходные логиты.

Эти шаги позволяют модели Transformer обрабатывать входные последовательности и генерировать выходные последовательности на основе объединенной функциональности её компонентов.

Подготовка выборочных данных

В этом примере мы создадим маленький набор данных для демонстрационных целей. На практике вы должны использовать больший набор данных, предварительно обработать текст и создать сопоставление словарей для исходного и целевого языков.

src_vocab_size = 5000
tgt_vocab_size = 5000
d_model = 512
num_heads = 8
num_layers = 6
d_ff = 2048
max_seq_length = 100
dropout = 0.1

transformer = Transformer(src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout)

# Generate random sample data
src_data = torch.randint(1, src_vocab_size, (64, max_seq_length))  # (batch_size, seq_length)
tgt_data = torch.randint(1, tgt_vocab_size, (64, max_seq_length))  # (batch_size, seq_length)

Обучение модели

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

criterion = nn.CrossEntropyLoss(ignore_index=0)
optimizer = optim.Adam(transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)

transformer.train()

for epoch in range(100):
    optimizer.zero_grad()
    output = transformer(src_data, tgt_data[:, :-1])
    loss = criterion(output.contiguous().view(-1, tgt_vocab_size), tgt_data[:, 1:].contiguous().view(-1))
    loss.backward()
    optimizer.step()
    print(f"Epoch: {epoch+1}, Loss: {loss.item()}")

Мы можем использовать этот способ для создания простой модели Transformer с нуля в Pytorch. Все большие языковые модели используют эти блоки кодировщика или декодера Transformer для обучения. Следовательно, понимание сети, с которой всё началось, чрезвычайно важно. Надеюсь, эта статья поможет всем, кто хочет глубже погрузиться в LLM.

Рекомендации

Attention is all you need

А. Васвани , Н. Шазир , Н. Пармар , Дж. Ушкорейт , Л. Джонс , А. Гомес , {. Кайзер , И. Полосухин . Достижения в области нейронных систем обработки информации, стр. 5998–6008. 2017 )

+1
0
+1
3
+1
0
+1
0
+1
0

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *