.. _sec_vision-transformer: Трансформеры для компьютерного зрения ===================================== Архитектура трансформера изначально была предложена для обучения задачам «последовательность-в-последовательность» с акцентом на машинный перевод. Впоследствии трансформеры стали моделями выбора для различных задач обработки естественного языка :cite:`Radford.Narasimhan.Salimans.ea.2018,Radford.Wu.Child.ea.2019,brown2020language,Devlin.Chang.Lee.ea.2018,raffel2020exploring`. Однако в области компьютерного зрения доминирующей архитектурой оставалась свёрточная нейронная сеть (CNN) :numref:`chap_modern_cnn`. Естественным образом исследователи начали задаваться вопросом: возможно ли добиться лучших результатов, адаптировав модели трансформеров для работы с изображениями. Этот вопрос вызвал огромный интерес в сообществе компьютерного зрения. Недавно :cite:t:`ramachandran2019stand` предложили схему замены свёртки на самовнимание. Однако использование ими специализированных паттернов в механизме внимания затрудняет масштабирование моделей на аппаратных ускорителях. Затем :cite:t:`cordonnier2020relationship` теоретически доказали, что самовнимание может научиться вести себя аналогично свёртке. Эмпирически :math:`2 \times 2` использовали фрагменты изображений в качестве входных данных, но малый размер фрагментов ограничивает применимость модели только изображениями низкого разрешения. Без жёстких ограничений на размер патчей *трансформеры для зрения* (ViT) извлекают патчи из изображений и передают их в кодировщик трансформера для получения глобального представления, которое затем преобразуется для классификации :cite:`Dosovitskiy.Beyer.Kolesnikov.ea.2021`. Примечательно, что трансформеры демонстрируют лучшую масштабируемость по сравнению с CNN: при обучении более крупных моделей на более крупных наборах данных трансформеры для зрения значительно превосходят ResNet. Подобно тому, как это произошло в области проектирования архитектур сетей для обработки естественного языка, трансформеры также стали переломным моментом в компьютерном зрении. .. raw:: latex \diilbookstyleinputcell .. code:: python import torch from torch import nn from d2l import torch as d2l Модель ------ :numref:`fig_vit` изображает архитектуру модели трансформеров для зрения. Эта архитектура состоит из стема, который разбивает изображения на патчи, тела на основе многослойного кодировщика трансформера и головы, которая преобразует глобальное представление в выходную метку. .. _fig_vit: .. figure:: ../img/vit.svg Архитектура Vision Transformer. В этом примере изображение разбивается на девять патчей. Специальный токен «» и девять сплющенных патчей изображения преобразуются с помощью эмбеддинга патчей и :math:`\mathit{n}` блоков кодировщика трансформера в десять представлений соответственно. Представление «» далее преобразуется в выходную метку. Рассмотрим входное изображение высотой :math:`h`, шириной :math:`w` и с :math:`c` каналами. Если задать высоту и ширину патча равными :math:`p`, изображение разбивается на последовательность из :math:`m = hw/p^2` патчей, каждый из которых сворачивается в вектор длины :math:`cp^2`. Таким образом, патчи изображения можно обрабатывать аналогично токенам в текстовых последовательностях с помощью кодировщиков трансформера. Специальный токен «» (class) и :math:`m` сплюснутых патчей изображения линейно проецируются в последовательность из :math:`m+1` векторов, к которым прибавляются обучаемые позиционные эмбеддинги. Многоуровневый кодировщик трансформера преобразует :math:`m+1` входных векторов в такое же количество выходных векторных представлений той же длины. Он работает точно так же, как исходный кодировщик трансформера в :numref:`fig_transformer`, за исключением положения нормализации. Поскольку токен «» обращает внимание на все патчи изображения посредством самовнимания (см. :numref:`fig_cnn-rnn-self-attention`), его представление на выходе кодировщика трансформера будет дополнительно преобразовано в выходную метку. Встраивание патчей ------------------ Для реализации визуального трансформера начнем с эмбеддинга патчей в :numref:`fig_vit`. Разбиение изображения на патчи и линейная проекция этих сплющенных патчей можно упростить до одной операции свёртки, при этом размер ядра и шаг устанавливаются равными размеру патча. .. raw:: latex \diilbookstyleinputcell .. code:: python class PatchEmbedding(nn.Module): def __init__(self, img_size=96, patch_size=16, num_hiddens=512): super().__init__() def _make_tuple(x): if not isinstance(x, (list, tuple)): return (x, x) return x img_size, patch_size = _make_tuple(img_size), _make_tuple(patch_size) self.num_patches = (img_size[0] // patch_size[0]) * ( img_size[1] // patch_size[1]) self.conv = nn.LazyConv2d(num_hiddens, kernel_size=patch_size, stride=patch_size) def forward(self, X): # Output shape: (batch size, no. of patches, no. of channels) return self.conv(X).flatten(2).transpose(1, 2) В следующем примере, где изображения с высотой и шириной ``img_size`` подаются на вход, эмбеддинг патчей выдает ``(img_size//patch_size)**2`` патчей, которые линейно проецируются в векторы длины ``num_hiddens``. .. raw:: latex \diilbookstyleinputcell .. code:: python img_size, patch_size, num_hiddens, batch_size = 96, 16, 512, 4 patch_emb = PatchEmbedding(img_size, patch_size, num_hiddens) X = torch.zeros(batch_size, 3, img_size, img_size) d2l.check_shape(patch_emb(X), (batch_size, (img_size//patch_size)**2, num_hiddens)) .. _subsec_vit-encoder: Кодировщик Vision Transformer ----------------------------- Многослойный перцептрон (MLP) кодировщика Vision Transformer немного отличается от позиционно-независимой полносвязной сети (FFN) оригинального кодировщика трансформера (см. :numref:`subsec_positionwise-ffn`). Во-первых, в качестве функции активации здесь используется гауссовская линейная единица (GELU), которую можно рассматривать как более плавный аналог ReLU :cite:`Hendrycks.Gimpel.2016`. Во-вторых, для регуляризации к выходу каждого полносвязного слоя в MLP применяется дропаут. .. raw:: latex \diilbookstyleinputcell .. code:: python class ViTMLP(nn.Module): def __init__(self, mlp_num_hiddens, mlp_num_outputs, dropout=0.5): super().__init__() self.dense1 = nn.LazyLinear(mlp_num_hiddens) self.gelu = nn.GELU() self.dropout1 = nn.Dropout(dropout) self.dense2 = nn.LazyLinear(mlp_num_outputs) self.dropout2 = nn.Dropout(dropout) def forward(self, x): return self.dropout2(self.dense2(self.dropout1(self.gelu( self.dense1(x))))) Реализация блока кодировщика Vision Transformer следует архитектуре с предварительной нормализацией, описанной в :numref:`fig_vit`, где нормализация применяется непосредственно *перед* механизмом многоголовного внимания или блоком MLP. В отличие от пост-нормализации («add & norm» в :numref:`fig_transformer`), при которой нормализация выполняется сразу *после* остаточных связей, предварительная нормализация обеспечивает более эффективное или быстрое обучение трансформеров :cite:`baevski2018adaptive,wang2019learning,xiong2020layer`. .. raw:: latex \diilbookstyleinputcell .. code:: python class ViTBlock(nn.Module): def __init__(self, num_hiddens, norm_shape, mlp_num_hiddens, num_heads, dropout, use_bias=False): super().__init__() self.ln1 = nn.LayerNorm(norm_shape) self.attention = d2l.MultiHeadAttention(num_hiddens, num_heads, dropout, use_bias) self.ln2 = nn.LayerNorm(norm_shape) self.mlp = ViTMLP(mlp_num_hiddens, num_hiddens, dropout) def forward(self, X, valid_lens=None): X = X + self.attention(*([self.ln1(X)] * 3), valid_lens) return X + self.mlp(self.ln2(X)) Как и в :numref:`subsec_transformer-encoder`, блок кодировщика трансформера для зрения не изменяет форму своих входных данных. .. raw:: latex \diilbookstyleinputcell .. code:: python X = torch.ones((2, 100, 24)) encoder_blk = ViTBlock(24, 24, 48, 8, 0.5) encoder_blk.eval() d2l.check_shape(encoder_blk(X), X.shape) Собираем всё вместе ------------------- Прямой проход в трансформерах для зрения, приведённый ниже, прост. Сначала входные изображения подаются в объект ``PatchEmbedding``, вывод которого конкатенируется с эмбеддингом токена «». Затем они суммируются с обучаемыми позиционными эмбеддингами перед применением дропаута. После этого вывод подается в кодировщик трансформера, который состоит из ``num_blks`` экземпляров класса ``ViTBlock``. Наконец, представление токена «» проецируется головкой сети. .. raw:: latex \diilbookstyleinputcell .. code:: python class ViT(d2l.Classifier): """Vision Transformer.""" def __init__(self, img_size, patch_size, num_hiddens, mlp_num_hiddens, num_heads, num_blks, emb_dropout, blk_dropout, lr=0.1, use_bias=False, num_classes=10): super().__init__() self.save_hyperparameters() self.patch_embedding = PatchEmbedding( img_size, patch_size, num_hiddens) self.cls_token = nn.Parameter(torch.zeros(1, 1, num_hiddens)) num_steps = self.patch_embedding.num_patches + 1 # Add the cls token # Positional embeddings are learnable self.pos_embedding = nn.Parameter( torch.randn(1, num_steps, num_hiddens)) self.dropout = nn.Dropout(emb_dropout) self.blks = nn.Sequential() for i in range(num_blks): self.blks.add_module(f"{i}", ViTBlock( num_hiddens, num_hiddens, mlp_num_hiddens, num_heads, blk_dropout, use_bias)) self.head = nn.Sequential(nn.LayerNorm(num_hiddens), nn.Linear(num_hiddens, num_classes)) def forward(self, X): X = self.patch_embedding(X) X = torch.cat((self.cls_token.expand(X.shape[0], -1, -1), X), 1) X = self.dropout(X + self.pos_embedding) for blk in self.blks: X = blk(X) return self.head(X[:, 0]) Обучение -------- Обучение визуального трансформера на наборе данных Fashion-MNIST аналогично обучению свёрточных нейронных сетей, описанному в :numref:`chap_modern_cnn`. .. raw:: latex \diilbookstyleinputcell .. code:: python img_size, patch_size = 96, 16 num_hiddens, mlp_num_hiddens, num_heads, num_blks = 512, 2048, 8, 2 emb_dropout, blk_dropout, lr = 0.1, 0.1, 0.1 model = ViT(img_size, patch_size, num_hiddens, mlp_num_hiddens, num_heads, num_blks, emb_dropout, blk_dropout, lr) trainer = d2l.Trainer(max_epochs=10, num_gpus=1) data = d2l.FashionMNIST(batch_size=128, resize=(img_size, img_size)) trainer.fit(model, data) Итоги и обсуждение ------------------ Вы могли заметить, что для небольших наборов данных, таких как Fashion-MNIST, реализованный нами Vision Transformer не превосходит ResNet в :numref:`sec_resnet`. Аналогичные наблюдения можно сделать даже на наборе данных ImageNet (1,2 миллиона изображений). Это связано с тем, что трансформеры *не обладают* такими полезными свойствами свёрток, как инвариантность к сдвигу и локальность (:numref:`sec_why-conv`). Однако ситуация меняется при обучении более крупных моделей на больших наборах данных (например, 300 миллионов изображений), где Vision Transformer значительно превосходит ResNet в классификации изображений, демонстрируя внутреннее преимущество трансформеров в масштабируемости :cite:`Dosovitskiy.Beyer.Kolesnikov.ea.2021`. Появление Vision Transformer изменило ландшафт проектирования сетей для моделирования изображений. Вскоре было показано, что они эффективны на наборе данных ImageNet при использовании стратегий обучения, требующих меньшего объёма данных, как в DeiT :cite:`touvron2021training`. Однако квадратичная сложность самовнимания (:numref:`sec_self-attention-and-positional-encoding`) делает архитектуру трансформера менее подходящей для изображений высокого разрешения. В стремлении создать универсальную базовую сеть для компьютерного зрения, Swin Transformer решил проблему квадратичной вычислительной сложности относительно размера изображения (:numref:`subsec_cnn-rnn-self-attention`) и восстановил приоритеты, подобные свёрточным, расширив применимость трансформеров на ряд задач компьютерного зрения за пределами классификации изображений, достигнув наивысших результатов :cite:`liu2021swin`. Упражнения ---------- 1. Как значение ``img_size`` влияет на время обучения? 2. Вместо проецирования представления токена ```` на выход, как бы вы спроецировали усреднённые представления патчей? Реализуйте это изменение и посмотрите, как оно повлияет на точность. 3. Можете ли вы изменить гиперпараметры, чтобы повысить точность трансформера для зрения? `Обсуждения `__