Self-attention understood
So, what a great feeling. Self-attention foi compreendido.
Considere aqui o texto que considerei para explicar no notebook.
Para compreender, tem dois blocos de código. O modo de estabelecer a relação entre os tokens sem self-attention e o modo de estabelecer a relação entre os tokens com self-attention.
So, o primeiro bloco, que não tem self-attention seria o seguinte.
import torch
import torch.nn as nn
import torch.nn.functional as F
torch.manual_seed(1337);
B, T, C = 4, 8, 32 # batch, time, channels.
x = torch.randn(B, T, C)
tril = torch.tril(torch.ones(T, T))
wei = torch.zeros((T, T))
wei = wei.masked_fill(tril == 0, float('-inf'))
wei = F.softmax(wei, dim=-1)
So, texto explicando seria o seguinte.
O tril seria um modo de conseguir obter uma estrutura que ajude a chegar naquela regra de que o token não pode considerar um token futuro. Então para a primeira linha apenas o token em si; para o segundo token, pode ser ele mesmo e o anterior.
O wei seria a construção em si desses tokens e suas relações. Ele começa como uma matriz de T por T, que seriam os números de tokens, com zero, sem nenhuma informação.
Depois, aplica-se o tril como máscara considerando a condição de considerar um valor de menos infinito para quando o tril está com 0. Assim, chega-se na questão de apenas considerar o próprio token e os tokens passados.
O softmax depois seria aplicado ao wei para se obter números em intervalo que possa codificar melhor a relação dos tokens. No caso do resultado com o softmax já se chega em uma relação de média entre os tokens, como se a relação entre o token mesmo e os tokens passados tivesse a mesma importância.
Texto agora falando do self-attention.
O self-attention entra aqui. A relação entre os tokens pode não ser a mesma. Então essa média pode não representar a relação entre os tokens. Um token, para um dado token que se está considerando, um token do passado, pode ser melhor para o token que se está considerando do que um outro token do passado.
O self-attention realiza isso.
O Karpathy explica que o self-attention realiza isso com a emissão de dois sinais por token, a consulta e a chave.
O sinal de consulta significa o quê o token está procurando.
O sinal de chave signfica o quê o token possui.
O jeito que se obtém então afinidades entre os tokens seria com dot product entre os vetores de query e key.
So, para um exemplo. A consulta de um token dot product as chaves dos outros tokens se torna os pesos.
O x seria algo particular, então se tem uma boa afinidade, o que será mostrado seria o value, que seria outro sinal do self-attention mechanism.
Great, fim do texto, compreendi assim self-attention mechanism.
O código seria o seguinte.
# let's see a single Head perform self-attention.
head_size = 16
key = nn.Linear(C, head_size, bias=False)
query = nn.Linear(C, head_size, bias=False)
k = key(x) # (B, T, 16).
q = query(x) # (B, T, 16).
wei = q @ k.transpose(-2, -1) # (B, T, 16) @ (B, 16, T) results (B, T, T).
tril = torch.tril(torch.ones(T, T))
wei = wei.masked_fill(tril == 0, float('-inf'))
wei = F.softmax(wei, dim=1)
result = wei @ x
result.shape
Truly special de ter compreendido. Foco.
Sabe, fui assistindo o Karpathy e pausava, explicava no notebook com as minhas próprias palavras. Quando não entendi uma parte específica, discuti com o Claude, mas sabia a pergunta que queria realizar. That is important. To understand what you need to know. You ask to have a response. Clear question. Saber a pergunta a ser realizada.
Truly special.
Really great learning.