Transformador | Codificação da posição de detecção de alvo DETR explicação detalhada do código position_encoding

Este artigo descreve principalmente o position_encoding no documento DETR. Para uma análise detalhada do documento DETR, consulte

 Documentos | 2020-Facebook-DETR: Detecção de alvo de ponta a ponta usando Transformers => tradução e compreensão (atualizado continuamente)_Summer|여름이다's blog-CSDN blog_dert target detection

Ao contrário do RNN, o Transformer pode aceitar e processar palavras de acordo com a ordem de posição, portanto, para obter as informações de posição das palavras, as informações de posição são adicionadas ao vetor de incorporação de cada palavra, que é chamado de codificação de posição. Dois métodos de codificação são fornecidos no DETR, um é a codificação senoidal (PositionEmbeddingSine) e o outro é a codificação apreensível (PositionEmbeddingLearned).O padrão é a codificação senoidal.

 Conforme mostrado na figura, antes que o vetor de incorporação usado como entrada seja usado como entrada do transformador, o valor da codificação de posição é adicionado e o processo de adição do valor de codificação de posição antes do vetor de incorporação é usado como entrada do codificador é o seguinte:

 O Transformer usa as duas funções a seguir para criar um valor com informações posicionais:

 O transformador adiciona os valores das funções seno e cosseno ao vetor de incorporação, além de informações sobre a ordem das palavras. A adição do vetor de incorporação e do código de posição é completada pela adição da matriz de sentença e da matriz de código de posição.As duas matrizes são vetores formados pela agregação da incorporação.

 - pos: indica a posição do vetor de incorporação na frase de entrada

- i : é o índice da dimensão no vetor de incorporação

- modelo d: é um hiperparâmetro do transformador e é a dimensão de saída de todas as camadas. Na foto acima é 4, no jornal "Atenção é tudo que você precisa" é 512.

De acordo com a expressão acima, o valor da função seno é usado se o índice de cada dimensão no vetor de incorporação for par , e a função cosseno é usada se o índice for ímpar .

Visualização codificada de posição

import numpy as np
import matplotlib.pylab as plt


def getPositionEncoding(seq_len, d, n=10000):
    P = np.zeros(((seq_len, d)), dtype=float)
    for k in range(seq_len):
        for i in np.arange(int(d/2)):
            denominator = np.power(n, 2*i/d)
            P[k, 2*i] = np.sin((k/denominator))
            P[k, 2*i+1] = np.cos(k/denominator)
    return P

P = getPositionEncoding(seq_len=100, d=512, n=10000)


print(P)


cat = plt.matshow(P)
plt.gcf().colorbar(cat)
plt.show()

resultado

1. Codificação senoidal

Retire a máscara e inverta a máscara. Como o método de codificação é uma codificação bidimensional, acumulamos as linhas e colunas separadamente como a codificação de cada dimensão e a normalizamos para convertê-la em um ângulo. Ao mesmo tempo, assumimos que cada dimensão da codificação consiste em um vetor de 128 dimensões. Em seguida, codificamos de acordo com o seguinte método de codificação de seno, encontramos o cosseno para números ímpares e encontramos o seno para números pares. Após a codificação, as dimensões de x_emding e y_emding são batch*h*w*128.

O Transformer usa as duas funções a seguir para criar um valor com informações posicionais:

As dimensões da codificação de posição PE e o vetor de palavras precisam ser consistentes antes de serem adicionados posteriormente. onde pos é a posição de entrada da palavra, i é a dimensão,

import torch

# 1d绝对sin_cos编码
def create_1d_absolute_sin_cos_embedding(pos_len, dim):
    assert dim % 2 == 0, "wrong dimension!"
    position_emb = torch.zeros(pos_len, dim, dtype=torch.float)
    # i矩阵
    i_matrix = torch.arange(dim//2, dtype=torch.float)
    i_matrix /= dim / 2
    i_matrix = torch.pow(10000, i_matrix)
    i_matrix = 1 / i_matrix
    i_matrix = i_matrix.to(torch.long)
    # pos矩阵
    pos_vec = torch.arange(pos_len).to(torch.long)
    # 矩阵相乘,pos变成列向量,i_matrix变成行向量
    out = pos_vec[:, None] @ i_matrix[None, :]
    # 奇/偶数列
    emb_cos = torch.cos(out)
    emb_sin = torch.sin(out)
    # 赋值
    position_emb[:, 0::2] = emb_sin
    position_emb[:, 1::2] = emb_cos
    return position_emb


if __name__ == '__main__':
    print(create_1d_absolute_sin_cos_embedding(4, 4))

- tocha.arange(): converte para vetor unidimensional

- arch.pow() : implementa operação de expoente elemento a elemento entre tensor e escalar, ou operação de expoente elemento a elemento entre tensores difundíveis.

- arch.long: cast tensor para tipo long

2. position_encoding.py na detecção de alvo DETR

A Incorporação Posicional no DETR é um valor fixo.O código da Incorporação Posicional é o seguinte:De acordo com as características do mapa de recursos bidimensional, o DETR implementa seu próprio método de codificação de posição bidimensional.

# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
"""
Various positional encodings for the transformer.
"""
import math
import torch
from torch import nn

from util.misc import NestedTensor


class PositionEmbeddingSine(nn.Module):
    """
    This is a more standard version of the position embedding, very similar to the one
    used by the Attention is all you need paper, generalized to work on images.
    """
    def __init__(self, num_pos_feats=64, temperature=10000, normalize=False, scale=None):
        super().__init__()
        self.num_pos_feats = num_pos_feats
        self.temperature = temperature
        self.normalize = normalize
        if scale is not None and normalize is False:
            raise ValueError("normalize should be True if scale is passed")
        if scale is None:
            scale = 2 * math.pi
        self.scale = scale

    def forward(self, tensor_list: NestedTensor):
        x = tensor_list.tensors
        mask = tensor_list.mask
        assert mask is not None
        not_mask = ~mask
        y_embed = not_mask.cumsum(1, dtype=torch.float32)
        x_embed = not_mask.cumsum(2, dtype=torch.float32)
        if self.normalize:
            eps = 1e-6
            y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scale
            x_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale

        dim_t = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device)
        dim_t = self.temperature ** (2 * (dim_t // 2) / self.num_pos_feats)

        pos_x = x_embed[:, :, :, None] / dim_t
        pos_y = y_embed[:, :, :, None] / dim_t
        pos_x = torch.stack((pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4).flatten(3)
        pos_y = torch.stack((pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4).flatten(3)
        pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)
        return pos


class PositionEmbeddingLearned(nn.Module):
    """
    Absolute pos embedding, learned.
    """
    def __init__(self, num_pos_feats=256):
        super().__init__()
        self.row_embed = nn.Embedding(50, num_pos_feats)
        self.col_embed = nn.Embedding(50, num_pos_feats)
        self.reset_parameters()

    def reset_parameters(self):
        nn.init.uniform_(self.row_embed.weight)
        nn.init.uniform_(self.col_embed.weight)

    def forward(self, tensor_list: NestedTensor):
        x = tensor_list.tensors
        h, w = x.shape[-2:]
        i = torch.arange(w, device=x.device)
        j = torch.arange(h, device=x.device)
        x_emb = self.col_embed(i)
        y_emb = self.row_embed(j)
        pos = torch.cat([
            x_emb.unsqueeze(0).repeat(h, 1, 1),
            y_emb.unsqueeze(1).repeat(1, w, 1),
        ], dim=-1).permute(2, 0, 1).unsqueeze(0).repeat(x.shape[0], 1, 1, 1)
        return pos


def build_position_encoding(args):
    N_steps = args.hidden_dim // 2
    if args.position_embedding in ('v2', 'sine'):
        # TODO find a better way of exposing other arguments
        position_embedding = PositionEmbeddingSine(N_steps, normalize=True)
    elif args.position_embedding in ('v3', 'learned'):
        position_embedding = PositionEmbeddingLearned(N_steps)
    else:
        raise ValueError(f"not supported {args.position_embedding}")

    return position_embedding

 Para fazer com que a rede perceba as informações de localização de diferentes entradas, a maneira mais intuitiva é atribuir o valor 1 ao primeiro recurso e o valor 2 ao segundo recurso, mas esse método de atribuição não é amigável para entradas maiores, então algumas pessoas propõem usar A função seno controla o valor entre −1 e 1, mas a função seno é periódica, o que pode causar o mesmo valor em posições diferentes. Portanto, o autor estende a função seno para um vetor de dimensão d, e canais diferentes têm comprimentos de onda diferentes, como na fórmula acima.

Em outras palavras, pos é a posição do vetor de palavras na sequência e i é o índice do canal. Comparando o código, pode-se ver que o DETR calcula um código de posição para as direções x e y do mapa de recursos bidimensional, e o comprimento do código de posição de cada dimensão é num_pos_feats (esse valor é na verdade metade de hidden_dim), para x ou y, calcule o seno da posição ímpar, calcule o cosseno da posição par e, em seguida, concatene pos_x e pos_y para obter uma matriz NHWD e, em seguida, passe permute (0,3,1,2), a forma se torna NDHW, onde D é igual a hidden_dim. Este hidden_dim é a dimensão do vetor de entrada do Transformer.Em termos de implementação, deve ser igual à dimensão do mapa de recursos de saída do backbone CNN . Portanto, a forma do código pos e do recurso de saída CNN é exatamente a mesma.

 O código de detalhe nele

- math.pi : É uma função integrada na biblioteca de funções matemáticas em python, que expressa principalmente pi.

#1:math.pi
import math
print(math.pi)
scale = 2 * math.pi
print(scale)

resultado:

 - NestedTensor tensor aninhado

Os tensores aninhados são muito semelhantes aos tensores regulares, exceto pela forma:

  • Para tensores regulares, cada dimensão tem um tamanho

  • Para tensores aninhados, nem todas as dimensões têm tamanhos regulares; algumas delas são irregulares

Os tensores aninhados são uma solução natural para representar dados sequenciais entre domínios:

  • No NLP, as sentenças podem ter comprimento variável, então um lote de sentenças forma um tensor aninhado

  • No CV, as imagens podem ter formas variáveis, portanto, um lote de imagens forma um tensor aninhado

Exemplo: tensores aninhados no transformador

#Transformers中使用的多头注意力组件
import math
import torch
import torch.nn.functional as F

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
#嵌套张量初始化
nt = torch.nested_tensor([torch.randn((2, 6)), torch.randn((3, 6))], device=device)
print(nt)
#通过将每个底层张量填充为相同的形状,嵌套张量可以转换为常规张量。
pt = torch.nested.to_padded_tensor(nt, padding=0.0)
print(pt)
"""
Args参数:
    query: query of shape 形状的查询 (N, L_t, E_q)
    key: key of shape key的形状 (N, L_s, E_k)
    value: value of shape value的形状(N, L_s, E_v)
    nheads: number of heads in multi-head attention多头注意力中的头数
    W_q: Weight for query input projection of shape (E_total, E_q)形状的查询输入投影的权重 
    W_k: Weight for key input projection of shape (E_total, E_k)形状的关键输入投影的权重
    W_v: Weight for value input projection of shape (E_total, E_v)形状的价值输入投影的权重
    W_out: Weight for output projection of shape (E_out, E_total)形状的输出投影的权重
    b_q (optional): Bias for query input projection of shape E_total. Default: None    b_q(可选)。形状E_total的查询输入投影的偏置。默认值。无
    b_k (optional): Bias for key input projection of shape E_total. Default: None       b_k (可选): 形状E_total的关键输入投影的偏置。默认值。无
    b_v (optional): Bias for value input projection of shape E_total. Default: None     b_v (可选): 形状E_total的值输入投影的偏置。默认值。无
    b_out (optional): Bias for output projection of shape E_out. Default: None         b_out(可选)。形状E_out的输出投影的偏置。默认值。无
    dropout_p: dropout probability. Default: 0.0   dropout_p: dropout概率。默认值:0.0
    where:
        N is the batch size  N是批次大小
        L_t is the target sequence length (jagged)   L_t是目标序列的长度(锯齿状)
        L_s is the source sequence length (jagged)  L_s是源序列的长度(锯齿状)
        E_q is the embedding size for query     E_q是查询的嵌入大小
        E_k is the embedding size for key       E_k是键的嵌入大小
        E_v is the embedding size for value     E_v是值的嵌入大小
        E_total is the embedding size for all heads combined          E_total is the embedding size for all heads  
        E_out is the output embedding size     E_out是输出嵌入的大小
Returns返回:
    attn_output: Output of shape (N, L_t, E_out)   attn_output: 形状的输出(N, L_t, E_out)
"""
def mha_nested(query, key, value, nheads,
W_q, W_k, W_v, W_out,
b_q=None, b_k=None, b_v=None, b_out=None,
dropout_p=0.0):
    N = query.size(0)
    E_total = W_q.size(0)
    assert E_total % nheads == 0, "Embedding dim is not divisible by nheads"#嵌入的dim不能被nheads分割,必须是8的倍数
    E_head = E_total // nheads

    # apply input projection
    # (N, L_t, E_q) -> (N, L_t, E_total)
    query = F.linear(query, W_q, b_q)
    # (N, L_s, E_k) -> (N, L_s, E_total)
    key = F.linear(key, W_k, b_k)
    # (N, L_s, E_v) -> (N, L_s, E_total)
    value = F.linear(value, W_v, b_v)

    # reshape query, key, value to separate by head
    # (N, L_t, E_total) -> (N, L_t, nheads, E_head) -> (N, nheads, L_t, E_head)
    query = query.reshape(-1, -1, nheads, E_head).transpose(1, 2)
    # (N, L_s, E_total) -> (N, L_s, nheads, E_head) -> (N, nheads, L_s, E_head)
    key = key.reshape(-1, -1, nheads, E_head).transpose(1, 2)
    # (N, L_s, E_total) -> (N, L_s, nheads, E_head) -> (N, nheads, L_s, E_head)
    value = value.reshape(-1, -1, nheads, E_head).transpose(1, 2)

    # query matmul key^T
    # (N, nheads, L_t, E_head) x (N, nheads, L_s, E_head)^T -> (N, nheads, L_t, L_s)
    keyT = key.transpose(-1, -2)
    attn_weights = torch.matmul(query, keyT)

    # scale down
    attn_weights = attn_weights * (1.0 / math.sqrt(E_head))

    # softmax
    attn_weights = F.softmax(attn_weights, dim=-1)

    # dropout
    if dropout_p > 0.0:
        attn_weights = F.dropout(attn_weights, p=dropout_p)

    # attention_weights matmul value
    # (N, nheads, L_t, L_s) x (N, nheads, L_s, E_head) -> (N, nheads, L_t, E_head)
    attn_output = torch.matmul(attn_weights, value)

    # merge heads
    # (N, nheads, L_t, E_head) -> (N, L_t, nheads, E_head) -> (N, L_t, E_total)
    attn_output = attn_output.transpose(1, 2).reshape(N, -1, E_total)

    # apply output projection
    # (N, L_t, E_total) -> (N, L_t, E_out)
    attn_output = F.linear(attn_output, W_out, b_out)

    return attn_output

 - mask é um array de máscaras de posição.Para uma imagem que não passou zero_pad, sua máscara é um array de todos os 0s.

referências

【1】2017-Attention Is All You Need- https://proceedings.neurips.cc/paper/2017/file/3f5ee243547dee91fbd053c1c4a845aa-Paper.pdf

【2】1) Transformer - Introdução ao processamento de linguagem natural usando aprendizado profundo

[3]  Compreendendo o DETR bzdww

Acho que você gosta

Origin blog.csdn.net/weixin_44649780/article/details/127162890
Recomendado
Clasificación