1 理论理解
1.1 注意力机制(Attention)
注意力机制很像人类看图片的逻辑,当我们看一张图片的时候,我们并没有看图片的全部内容,而是将注意力集中在了图片的焦点上。用上了注意力机制,信息(如下面的图片)就不在单纯是信息本身,而是附加了焦点。

1.2 注意力机制的实现
运用注意力机制处理信息就是将信息和关注的维度点乘,或者说将信息投影到关注的维度。比如一件商品有质量,价格,颜色,外形,生产商,发货地点等信息,但是你特别关注商品的价格,那么所有商品的所有信息投影到你的需求上,基本只有价格影响你买还是不买。

1.3 自注意力机制
自注意力机制是为了找到信息内部联系的手段。比如随便一句话:
美国国务卿布林肯将于2月5日至6日访问中国,这是他担任国务卿之后首次访华,也是拜登政府上台两年多来,访华级别最高的美国官员。
他指的是美国国务卿布林肯,与句子中的其他信息关联就不大。为了找到这种联系,引入了自注意力机制。做法就是将信息映射到不同的维度,然后投影后的信息做点乘(投影)。
做个形象化的比喻,你在广东被人叫靓仔,在广西被人叫叼毛,本质上你还是你,包含你的肉体和灵魂。只是你在不同的空间,时间,以及叫法不同。为了找出这种本质,先把你扔到广东被人叫靓仔,然后把你扔到广西被人叫叼毛,然后把两个空间的你做点乘。
1.4 多头自注意力机制
我们都说人是复杂的,很难用好人,或者坏人来描述。所以要从不同的方面去解构,而且不能只看你一个人怎么看你,还要看很多人怎么看你。类比到多头自注意力机制就是通过多个人观察你,每个人观察的角度不同,关注的点也不同,然后汇总,得到一个比较完整的你。
1.5 因果自注意力
因为GPT是生成模型,是利用前面的信息生成后面的信息的,也就是后面的信息开始是不知道的。但是通过已有的信息我们可以推测未知的信息。
2 代码分析
class CausalSelfAttention(nn.Module):
"""
A vanilla multi-head masked self-attention layer with a projection at the end.
It is possible to use torch.nn.MultiheadAttention here but I am including an
explicit implementation here to show that there is nothing too scary here.
"""
def __init__(self, config):
super().__init__()
assert config.n_embd % config.n_head == 0
# key, query, value projections for all heads, but in a batch
self.c_attn = nn.Linear(config.n_embd, 3 * config.n_embd)
# output projection
self.c_proj = nn.Linear(config.n_embd, config.n_embd)
# regularization
self.attn_dropout = nn.Dropout(config.attn_pdrop)
self.resid_dropout = nn.Dropout(config.resid_pdrop)
# causal mask to ensure that attention is only applied to the left in the input sequence
self.register_buffer("bias", torch.tril(torch.ones(config.block_size, config.block_size))
.view(1, 1, config.block_size, config.block_size))
self.n_head = config.n_head
self.n_embd = config.n_embd
def forward(self, x):
B, T, C = x.size() # batch size, sequence length, embedding dimensionality (n_embd)
# calculate query, key, values for all heads in batch and move head forward to be the batch dim
#同一个信息映射到不同的空间
q, k ,v = self.c_attn(x).split(self.n_embd, dim=2)
k = k.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
q = q.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
v = v.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
# causal self-attention; Self-attend: (B, nh, T, hs) x (B, nh, hs, T) -> (B, nh, T, T)
att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))
att = att.masked_fill(self.bias[:,:,:T,:T] == 0, float('-inf'))
att = F.softmax(att, dim=-1)
att = self.attn_dropout(att)
y = att @ v # (B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs)
y = y.transpose(1, 2).contiguous().view(B, T, C) # re-assemble all head outputs side by side
# output projection
y = self.resid_dropout(self.c_proj(y))
return y