Desta vez, vou ser afiado e curto. Em 10 minutos, indicarei as pequenas modificações da arquitetura do transformador para classificação da imagem.
Como é um artigo de acompanhamento, fique à vontade para aconselhar meus artigos anteriores sobre Transformador e atenção Se você não se sente tão confortável com os termos.
Agora, senhoras e senhores, você pode começar seus relógios!
Os transformadores não têm os vieses indutivos das redes neurais convolucionais (CNNs), como invariância de tradução e uma restrição localmente restrita campo receptivo. Você provavelmente ouviu isso antes.
Mas o que isso realmente significa?
Bem, invariância significa que você pode reconhecer uma entidade (ou seja, objeto) em uma imagem, mesmo quando sua aparência ou posição varia. Tradução Na visão computacional implica que cada pixel de imagem foi movido por uma quantidade fixa em uma direção específica.
Além disso, lembre -se de que a convolução é um operador local linear. Vemos apenas os valores do vizinho, conforme indicado pelo kernel.
Por outro lado, o transformador é por design invariante de permutação. A má notícia é que ele não pode processar dados estruturados em grade. Precisamos de sequências! Para esse fim, converteremos um sinal não sequencial espacial em uma sequência!
Vamos ver como.
Como o transformador de visão funciona em poucas palavras
A arquitetura total é chamada Vision Transformer (Vit em abreviação). Vamos examiná -lo passo a passo.
-
Divida uma imagem em patches
-
Achate os remendos
-
Produzir incorporações lineares de menor dimensão a partir dos remendos achatados
-
Adicione incorporações posicionais
-
Alimente a sequência como uma entrada para um codificador de transformador padrão
-
Pré -“o modelo com rótulos de imagem (totalmente supervisionada em um enorme conjunto de dados)
-
Finetune no conjunto de dados a jusante para classificação de imagem
Os patches de imagem são basicamente os tokens de sequência (como palavras). De fato, o bloco do codificador é idêntico ao transformador original proposto por Vaswani et al. (2017) como temos extensivamente descrito:
O bem conhecido bloco de transformadores. Imagem de Alexey Dosovitskiy et al 2020. Fonte:Uma imagem vale 16×16 palavras: transformadores para reconhecimento de imagem em escala
A única coisa que muda é o número desses blocos. Para esse fim, e para provar ainda que, com mais dados, eles podem treinar variantes maiores de VIT, foram propostos 3 modelos:
Alexey Dosovitskiy et al 2020. Fonte:Uma imagem vale 16×16 palavras: transformadores para reconhecimento de imagem em escala
Cabeças se referem Atenção de várias cabeçasenquanto o tamanho do MLP se refere ao módulo azul na figura. O MLP significa perceptron de várias camadas, mas na verdade é um monte de camadas de transformação lineares.
Tamanho oculto é o tamanho de incorporação, que é mantido fixado ao longo das camadas. Por que mantê -lo fixo? Para que possamos usar resíduos curtos pular conexões.
Caso você tenha perdido, existe não decodificador no jogo. Apenas uma camada linear extra para a classificação final chamada MLP Head.
Mas isso é suficiente?
Sim e não. Na verdade, precisamos de uma quantidade enorme de dados e, como resultado, recursos computacionais.
Detalhes importantes
Especificamente, se o VIT for treinado em conjuntos de dados com mais de 14m (pelo menos: P), ele pode se aproximar ou vencer os CNNs de última geração.
Caso contrário, é melhor você ficar com resinações ou EFABIFITYNETS.
O VIT é pré-treinado no grande conjunto de dados e depois ajustado para pequenos. A única modificação é descartar a cabeça de previsão (cabeça do MLP) e anexar um novo Camada linear, onde k é o número de classes do pequeno conjunto de dados.
Achei interessante que os autores afirmem que é melhor ajustar resoluções mais altas do que o pré-treinamento.
Para ajustar as resoluções mais altas, 2D interpolação das incorporações de posição pré-treinadas são realizadas. O motivo é que eles modelam incorporações posicionais com camadas lineares treináveis. Dito isto, a parte principal de engenharia deste artigo tem tudo a ver com alimentar uma imagem no transformador.
Representando uma imagem como uma sequência de patches
Eu também estava super curioso como você pode remodelar elegantemente a imagem em patches. Para uma imagem de entrada e tamanho do patch queremos criar patches de imagem denotados como onde . é o comprimento da sequência semelhante às palavras de uma frase.
Se você não percebeu o patch de imagem, ou seja, (16,16,3) é achatado para 16x16x3. Espero que agora o título faça sentido;)
Vou usar o Einops Biblioteca que funciona acima de Pytorch. Você pode instalá -lo via PIP:
$ pip install einops
E então algum código compacto pytorch:
from einops import rearrange
p = patch_size
x_p = rearrange(img, 'b c (h p1) (w p2) -> b (h w) (p1 p2 c)', p1 = p, p2 = p)
Em suma, cada símbolo ou cada parêntese indica uma dimensão. Para mais informações sobre operações de Einsum, consulte nosso blogpost nas operações de Einsum.
Observe que os patches de imagem são sempre quadrados para simplificar.
E quanto a ir de patch para incorporação? É apenas uma camada de transformação linear que leva uma sequência de elementos e saídas .
patch_dim = (patch_size**2) * channels
patch_to_embedding = nn.Linear(patch_dim, dim)
Você pode ver o que está faltando?
Aposto que você faz! Precisamos fornecer algum tipo de ordem.
Incorporações posicionais
Embora muitos esquemas de incorporação posicional tenham sido aplicados, nenhuma diferença significativa foi encontrada. Provavelmente, isso se deve ao fato de o codificador transformador operar em um nível de patch. O aprendizado de incorporações que capturam as relações de ordem entre patches (informações espaciais) não é tão crucial. É relativamente mais fácil entender as relações entre os remendos de P X P do que de uma altura da imagem completa x largura.
Intuitivamente, você pode imaginar resolver um quebra -cabeça de 100 peças (patches) em comparação com 5000 peças (pixels).
Portanto, após a projeção linear de baixa dimensão treinável A incorporação de posição é adicionada às representações do patch. É interessante ver como são essas incorporações de posição após o treinamento:
Alexey Dosovitskiy et al 2020. Fonte:Uma imagem vale 16×16 palavras: transformadores para reconhecimento de imagem em escala
Primeiro, há algum tipo de estrutura 2D. Segundo, os padrões entre linhas (e colunas) têm representações semelhantes. Para altas resoluções, foi utilizada uma estrutura sinusoidal.
Principais descobertas
Nos primeiros dias do Conv, costumávamos visualizar as primeiras camadas.
Por que?
Porque acreditamos que redes bem treinadas geralmente mostram bom e suave filtros.
Esquerda: AlexNet Fileters Visualização. Fonte:Curso de Standford CS231N Certo: Vit Aprendido Filtros. Fonte:Uma imagem vale 16×16 palavras: transformadores para reconhecimento de imagem em escala
Peguei emprestado a imagem do curso de Stanford CS231N: Redes neurais convolucionais para reconhecimento visual.
Como afirmou perfeitamente no CS231N:
“Observe que os pesos da primeira camada são muito agradáveis e suaves, indicando uma rede bem convergida. Os recursos de cor/escala de cinza estão agrupados porque o Alexnet contém dois fluxos separados de processamentoE uma aparente consequência dessa arquitetura é que um fluxo desenvolve recursos de alta frequência em escala de cinza e os outros recursos de cor de baixa frequência. ” ~ Curso Stanford CS231: Visualizando o que os Convnets aprendem
Para tais visualizações PCA é usado. Dessa forma, o autor mostrou que as representações antecipadas da camada podem Compartilhe recursos semelhantes.
Próxima pergunta, por favor.
Quão longe as interações não locais aprendidas estão?
Resposta curta: Para o tamanho do patch P, máximo p*P, que no nosso caso é 128, mesmo da 1ª camada!
Não precisamos de convocação sucessiva. camadas para chegar a pixels de 128 anos. Com convoluções sem dilatação, o campo receptivo é aumentado linearmente. Usando a auto-atimento, temos interação entre representações de pixels na 1ª camada e pares de representações na 2ª camada e assim por diante.
Direita: Imagem gerada usando Fomoro você tem um computador Esquerda: imagem por Alexey Dosovitskiy et al 2020
Com base no diagrama à esquerda do VIT, pode -se argumentar que:
-
De fato, existem cabeças que já atendem a todo o patch nas camadas iniciais.
-
Pode -se justificar o ganho de desempenho com base nas interações de pixel de acesso antecipado. Parece mais crítico para as primeiras camadas terem acesso a todo o patch (informações globais). Em outras palavras, as cabeças que pertencem à parte superior esquerda da imagem podem ser o principal motivo do desempenho superior.
-
Curiosamente, a distância de atenção aumenta com a profundidade da rede semelhante ao campo receptivo das operações locais.
-
Também há cabeças de atenção com distâncias de atenção consistentemente pequenas nas camadas baixas. À direita, uma camada de 24 camadas com convoluções 3×3 padrão tem um campo receptivo de menos de 50. Precisávamos de aproximadamente 50 convivências, para atender a um campo receptivo ~ 100, sem dilatação ou agrupamento de camadas.
-
Para aplicar essa idéia de cabeças de atenção altamente localizadas, os autores experimentaram modelos híbridos que aplicam uma resnet antes do transformador. Eles encontraram cabeças menos altamente localizadas, como esperado. Juntamente com a visualização do filtro, sugere que pode servir a uma função semelhante à das camadas convolucionais iniciais nos CNNs.
Distância da atenção e visualização
No entanto, acho essencial entender como eles mediram a distância média de atenção. É análogo ao campo receptivo, mas não exatamente o mesmo.
A distância da atenção foi calculada como A distância média entre o pixel de consulta e o restante do patchmultiplicado pelo peso da atenção. Eles usaram 128 imagens de exemplo e calculou a média de seus resultados.
Um exemplo: se um pixel estiver a 20 pixels de distância e o peso da atenção é 0,5, a distância é 10.
Finalmente, o modelo atende a imagens de regiões semanticamente relevantes para a classificação, conforme ilustrado abaixo:
Alexey Dosovitskiy et al 2020. Fonte:Uma imagem vale 16×16 palavras: transformadores para reconhecimento de imagem em escala
Implementação
Confira o nosso repositório Encontrar módulos de auto-ataque para a visão de computação. Dada uma implementação da baunilha Codificador do transformadorVit parece tão simples assim:
import torch
import torch.nn as nn
from einops import rearrange
from self_attention_cv import TransformerEncoder
class ViT(nn.Module):
def __init__(self, *,
img_dim,
in_channels=3,
patch_dim=16,
num_classes=10,
dim=512,
blocks=6,
heads=4,
dim_linear_block=1024,
dim_head=None,
dropout=0, transformer=None, classification=True):
"""
Args:
img_dim: the spatial image size
in_channels: number of img channels
patch_dim: desired patch dim
num_classes: classification task classes
dim: the linear layer's dim to project the patches for MHSA
blocks: number of transformer blocks
heads: number of heads
dim_linear_block: inner dim of the transformer linear block
dim_head: dim head in case you want to define it. defaults to dim/heads
dropout: for pos emb and transformer
transformer: in case you want to provide another transformer implementation
classification: creates an extra CLS token
"""
super().__init__()
assert img_dim % patch_dim == 0, f'patch size {patch_dim} not divisible'
self.p = patch_dim
self.classification = classification
tokens = (img_dim // patch_dim) ** 2
self.token_dim = in_channels * (patch_dim ** 2)
self.dim = dim
self.dim_head = (int(dim / heads)) if dim_head is None else dim_head
self.project_patches = nn.Linear(self.token_dim, dim)
self.emb_dropout = nn.Dropout(dropout)
if self.classification:
self.cls_token = nn.Parameter(torch.randn(1, 1, dim))
self.pos_emb1D = nn.Parameter(torch.randn(tokens + 1, dim))
self.mlp_head = nn.Linear(dim, num_classes)
else:
self.pos_emb1D = nn.Parameter(torch.randn(tokens, dim))
if transformer is None:
self.transformer = TransformerEncoder(dim, blocks=blocks, heads=heads,
dim_head=self.dim_head,
dim_linear_block=dim_linear_block,
dropout=dropout)
else:
self.transformer = transformer
def expand_cls_to_batch(self, batch):
"""
Args:
batch: batch size
Returns: cls token expanded to the batch size
"""
return self.cls_token.expand((batch, -1, -1))
def forward(self, img, mask=None):
batch_size = img.shape(0)
img_patches = rearrange(
img, 'b c (patch_x x) (patch_y y) -> b (x y) (patch_x patch_y c)',
patch_x=self.p, patch_y=self.p)
img_patches = self.project_patches(img_patches)
if self.classification:
img_patches = torch.cat(
(self.expand_cls_to_batch(batch_size), img_patches), dim=1)
patch_embeddings = self.emb_dropout(img_patches + self.pos_emb1D)
y = self.transformer(patch_embeddings, mask)
if self.classification:
return self.mlp_head(y(:, 0, :))
else:
return y
Conclusão
A parte principal da engenharia deste trabalho é a formulação de um problema de classificação de imagem como um problema seqüencial usando patches de imagem como tokens e processando -o por um transformador. Isso parece bom e simples, mas precisa de dados enormes. Infelizmente, o Google possui o conjunto de dados pré -treinado para que os resultados não sejam reproduzíveis. E mesmo que fossem, você precisaria ter poder de computação suficiente.
* Divulgação: Observe que alguns dos links acima podem ser links de afiliados e, sem nenhum custo adicional, ganharemos uma comissão se você decidir fazer uma compra depois de clicar.