Intro

Attention Mechanism (Self-Attention)

# Attention is cool -> [1, 3, 4]
batch_size = 1
seq_len = 3
d_k = 4

Q = torch.randn(batch_size, seq_len, d_k)
K = torch.randn(batch_size, seq_len, d_k)
V = torch.randn(batch_size, seq_len, d_k)
sent = ["Attention", "is", "cool"] 
# Assume our tokenizer just a 3 word dictionary
tokenizer = {"Attention": 0, "is": 1, "cool": 2}
tokens = torch.tensor([tokenizer[word] for word in sent]).unsqueeze(0)  # change to tensor and add batch dim 1 at index 0
embedder = nn.Embedding(num_embeddings=len(tokenizer), embedding_dim=4)
embedded_sent = embedder(tokens)
# Linear layers to transform Q,K,V into inputs to attention
d_k = 4
Q_linear = nn.Linear(d_k, d_k)
K_linear = nn.Linear(d_k, d_k)
V_linear = nn.Linear(d_k, d_k) 

Q = Q_linear(embedded_sent) # [1, 3, 4]
K = K_linear(embedded_sent) # [1, 3, 4]
V = V_linear(embedded_sent) # [1, 3, 4]
def attention(Q, K, V):

    d_k = Q.size(-1) # last dimension of Q (shape of hidden layer/embedding)

    attn_scores = torch.matmul(Q, K.transpose(-1, -2)) / math.sqrt(d_k) # this is the weights part

    attn_probs = attn_scores.softmax(dim=-1) # want to take softmax across embedding dim

    return torch.matmul(attn_probs, V) # final attention function output
>>> attn_scores = 
tensor([[[-0.4478, -0.0182, -0.4006],
         [-0.2950, -0.0614, -0.5863],
         [-0.3634,  0.0023, -0.6501]]], grad_fn=<DivBackward0>)
>>> attn_probs = 
tensor([[[0.2789, 0.4286, 0.2924],
         [0.3322, 0.4196, 0.2482],
         [0.3133, 0.4516, 0.2352]]], grad_fn=<SoftmaxBackward0>)
>>> attn = 
tensor([[[-0.3229, -0.7989, -0.8596,  0.2613],
         [-0.3294, -0.8030, -0.8922,  0.2746],
         [-0.3343, -0.7966, -0.8509,  0.2528]]], grad_fn=<UnsafeViewBackward0>)

Multi-Headed Attention

alt text

MultiHead(Q,K,V)=Concat(head1,...headh)WOMultiHead(Q, K, V) = Concat(head_{1}, ... head_{h})W^{O}
headi=Attention(QWiQ,KWiK,VWiV)head_{i} = Attention(QW_{i}^{Q}, KW_{i}^{K}, VW_{i}^{V})

def clones(module, N):
    "Produce N identical layers."
    return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])

class MultiHeadedAttention(nn.Module):
    def __init__(self, num_heads, d_model):
        super().__init__()
        self.num_heads = num_heads
        self.d_k = d_model // num_heads
        self.projections = clones(nn.Linear(d_model, d_model), 4)

    def forward(self, query, key, value):
        nbatches = query.size(0)

        # Do all the linear projections in batch from dim: d_model -> d_model -> (num_heads, d_k)
        query, key, value = [
            projection(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)
            for projection, x in zip(self.projections, (query, key, value))
        ]

        # Apply attention on all the projected vectors in batch.
        x, self.attn = attention(query, key, value, mask=mask, dropout=self.dropout)

        # Concat and return to dim: d_model
        x = x.transpose(1, 2).contiguous().view(nbatches, -1, self.h * self.d_k)
        del query
        del key
        del value
        return self.linears[-1](x)

Conclusion