从初中物理到大模型原理(三)

从初中物理到大模型原理(三)

附上前两篇的链接:
从初中物理到大模型原理(一)
从初中物理到大模型原理(二)

附上后一篇的链接:
从初中物理到大模型原理(四)

当我们在说注意力机制的时候,我们到底在说什么?注意力机制其实就是衡量距离,只不过在大模型中这个衡量很抽象,很复杂。

一、词向量

我们还是先从上一篇最后的那个例子开始说。直角坐标系中,我们取两个点 x 1 = ( 1 , 0 ) x_1=(1,0) x1=(1,0) x 2 = ( 1 , 1 ) x_2=(1,1) x2=(1,1) 。再从原点出发,得到了两个向量 x 1 = ( 1 , 0 ) \mathbf{x_1} = (1,0) x1=(1,0) x 2 = ( 1 , 1 ) \mathbf{x_2} = (1,1) x2=(1,1),用向量的点积 x 1 ⋅ x 2 \mathbf{x_1}·\mathbf{x_2} x1x2 来衡量这两个向量的距离,数学上用 x 1 ⋅ x 2 = x 1 x 2 + y 1 y 2 \mathbf{x_1}·\mathbf{x_2}=x_1x_2 + y_1y_2 x1x2=x1x2+y1y2 来计算。可以这样算,有几个关键的信息:

  1. 直角坐标系:这个是基础,所有的点都必须在这个直角坐标系中,才可以算;
  2. 维度:直角坐标系是二维的只有 x , y x,y x,y,相对的 x 1 = ( 1 , 0 ) x_1=(1,0) x1=(1,0) 表示在 x x x 轴上的投影是 1 1 1,在 y y y 轴上的投影是 0 0 0 ,如果是三维的,那么计算方式就变成了 x 1 ⋅ x 2 = x 1 x 2 + y 1 y 2 + z 1 z 2 \mathbf{x_1}·\mathbf{x_2}=x_1x_2 + y_1y_2 + z_1z_2 x1x2=x1x2+y1y2+z1z2,以此类推;

有了这两个前提,我们来看看大模型的计算过程:假设我们有一个768维的空间(维度越高,可以携带的信息就越多,GPT-2是768维),那么我们需要把上一篇文章里面 x = ( 2 , 28 , 32 , ⋅ ⋅ ⋅ , 99 ) \mathbf{x} = (2,28,32,···,99) x=(2,28,32⋅⋅⋅99)的这个向量映射到这个768维的空间中(简单一些,我们后面用只包含四个token的 x = ( 2 , 28 , 32 , 99 ) \mathbf{x} = (2,28,32,99) x=(2,28,32,99)来表示)。上一篇我们假设我们的词库总共有100个词,那么每个词都需要映射到这个768维的空间中,映射后在每一维上的值,就是在这个维度上的投影,假设 2 2 2映射出来以后的 ( 0.003 , 0.023 , 0.045 , 0.06 , 0.07 , ⋅ ⋅ ⋅ , 0.037 ) (0.003,0.023,0.045,0.06,0.07,···,0.037) (0.003,0.023,0.045,0.06,0.07,⋅⋅⋅,0.037) 这是一个 1 ∗ 768 1*768 1768 的矩阵,也叫 词向量。那么上面的 x = ( 2 , 28 , 32 , 99 ) \mathbf{x} = (2,28,32,99) x=(2,28,32,99) 就可以映射成一个 4 ∗ 768 4*768 4768 的矩阵,假设长这个样子 X = ( 0.003 , 0.023 , 0.045 , 0.06 , 0.07 , ⋅ ⋅ ⋅ , 0.037 0.001 , 0.323 , 0.745 , 0.16 , 0.27 , ⋅ ⋅ ⋅ , 0.347 0.053 , 0.083 , 0.095 , 0.63 , 0.75 , ⋅ ⋅ ⋅ , 0.264 0.006 , 0.213 , 0.415 , 0.06 , 0.57 , ⋅ ⋅ ⋅ , 0.537 ) \mathbf{X} = \begin{pmatrix} 0.003,0.023,0.045,0.06,0.07,···,0.037\\ 0.001,0.323,0.745,0.16,0.27,···,0.347\\ 0.053,0.083,0.095,0.63,0.75,···,0.264 \\ 0.006,0.213,0.415,0.06,0.57,···,0.537\end{pmatrix} X= 0.003,0.023,0.045,0.06,0.07,⋅⋅⋅,0.0370.001,0.323,0.745,0.16,0.27,⋅⋅⋅,0.3470.053,0.083,0.095,0.63,0.75,⋅⋅⋅,0.2640.006,0.213,0.415,0.06,0.57,⋅⋅⋅,0.537 至此,我们就完成了从字到token再到词向量的转变。我们用代码表示一下,很简单,为了方便理解,我会在关键的代码后面,直接加上输出:

import torch
from torch import nn
import tiktoken

# gpt2 提供的从字到token的转换器,也叫分词器
tokenizer = tiktoken.get_encoding("gpt2")
start_context = "大模型原"
# 转为 token id
encoded = tokenizer.encode(start_context)
# 下面这一行是 encoded 的值
# 不是四个值的原因,我就不在这里讲了,不是重点,感兴趣的可以去看一下分词器的原理
# encoded: [32014, 162, 101, 94, 161, 252, 233, 43889, 253]
# 现在 encoded 还不是矩阵,只是个数组
# 转为 1 * token.size 的 矩阵
encoded_tensor = torch.tensor(encoded).unsqueeze(0)
#encoded_tensor: tensor([[32014, 162, 101, 94, 161, 252, 233, 43889, 253]])
#encoded_tensor.shape: torch.Size([1, 9])
#这样就得到了一个 1*9 的矩阵
#在我们的假设里,共有100个词,GPT2是 50257个
tok_emb = nn.Embedding(50257, 768)
#由矩阵转换为词向量
tok_embeds = tok_emb(encoded_tensor)
print(tok_embeds)
#这个输出是肯定打不全的啦
tensor([[[-0.7558,  0.2214, -0.0669,  ..., -0.0675,  1.5860, -1.7477],
         [-1.5326,  0.6995,  2.1200,  ..., -0.3058,  1.6716,  0.4993],
         [ 0.1497, -0.6573,  0.9820,  ..., -0.6105, -0.2578, -2.4229],
         ...,
         [ 0.4232,  0.5039,  0.3852,  ..., -0.2627, -0.6688,  1.4953],
         [ 2.0909,  1.6228, -1.3269,  ...,  0.5683,  0.4071,  0.9331],
         [ 1.4512,  0.8749,  0.3835,  ...,  0.8473, -2.2383, -0.4602]]],
       grad_fn=<EmbeddingBackward0>)
#我们看一下最后这个词向量的形状
print(tok_embeds.shape)
torch.Size([1, 9, 768])
#我们现在可以把结果简单的理解成 1 个 9*768 的矩阵

好,我们现在已经得到了 9个词向量,共同组成了一个矩阵 tok_embeds,那么怎么计算呢?

二、注意力机制

我想了很久,怎么通俗易懂的讲注意力机制。直到上周末,我表妹马上大学毕业了。询问我计算机相关的事情时,我才恍然大悟。原来是这个名字翻译的不好,如果翻译成集中注意力,或者集中注意力机制,真的就很好理解了。

举个例子,大学的时候,假设有两门选修的课程《西夏文字研究》,《欧洲中世纪教会史》,每节课都有12个课时,老师每天都在讲课。但是你根本不感兴趣,因为你想选的是《影视鉴赏》和《基础摄影技术》。然后《西夏文字研究》开始考试了,侥幸考的刚好及格60分,我们假设每个课时讲的东西都考到了,而且只能得5分,剩下的咱也不会做。然而《欧洲中世纪教会史》老师比较有经验,知道大家都是为了学分才选的这个,所以在第12课时的最后半小时的时候,说了一句话:给大家画一下重点啊,第5课时XXXXXX,第7课时XXXXXXXX,最后大家都考了80分。

这就是注意力机制(虽然我觉得叫集中注意力更合适,但是还是沿用注意力机制吧)。

好,接下来我们开始抽象。

最开始各有12个输入,我们称为12个key;然后《欧洲中世纪教会史》有一个划重点,我们称为query,最后得到的输出就是《欧洲中世纪教会史》80分,《西夏文字研究》60分。80分的原因是因为我们把复习的时间更多给到了第5课时和第7课时,就是说这两个的权重比其他的课时变大了。

在这里插入图片描述
接下来,用数学公式表达一下以上的过程。首先,我们有一个词向量 k = ( k 1 , k 2 , k 3 , ⋅ ⋅ ⋅ , k 12 ) \mathbf{k} = (k_1,k_2,k_3,···,k_{12}) k=(k1,k2,k3,⋅⋅⋅,k12)然后我们有另外一个 key 对应值的词向量 v = ( v 1 , v 2 , v 3 , ⋅ ⋅ ⋅ , v 12 ) \mathbf{v} = (v_1,v_2,v_3,···,v_{12}) v=(v1,v2,v3,⋅⋅⋅,v12)有一个查询的向量,帮助我们集中注意力 q \mathbf{q} q。接下来最后的得分用一个函数来表示 80 = ∑ i = 1 12 α ( q , k i ) v i 80= \sum_{i=1}^{12} \alpha(\mathbf{q},\mathbf{k}_i)\mathbf{v}_i 80=i=112α(q,ki)vi这个函数称为注意力汇聚函数(集中注意力), α \alpha α 称为注意力评分函数(我觉得应该翻译成注意力得分函数)。我们知道作用到 v \mathbf{v} v 上的是权重,所以我们需要知道 q \mathbf{q} q k \mathbf{k} k 计算以后的每一个 k i k_i ki 的占比,这里使用了 s o f t m a x softmax softmax 函数 α ( q , k i ) = s o f t m a x ( a ( q , k i ) ) = e x p ( a ( q , k i ) ) ∑ j = 1 m e x p ( a ( q , k i ) ) \alpha(\mathbf{q},\mathbf{k}_i) = softmax(a(\mathbf{q},\mathbf{k}_i))=\frac{exp(a(\mathbf{q},\mathbf{k}_i))}{ \sum_{j=1}^mexp(a(\mathbf{q},\mathbf{k}_i))} α(q,ki)=softmax(a(q,ki))=j=1mexp(a(q,ki))exp(a(q,ki))这里用指数函数的意义在于永不为负,用自然底数的意义在于导数好算。

三、大模型的自注力机制

接下来我们说一下,大模型是怎么用的。
对于我们输入每一个token来说,这个点原本就在词库对应的高维空间中。为了方便,我换成英文再重新输出一个

start_context_en = "Hello, I am"
en_tokenid = tokenizer.encode(start_context_en)
en_tokenid_tensor = torch.tensor(encoded).unsqueeze(0)
en_embeds = tok_emb(en_tokenid_tensor)
# en_embeds 的输出为:4个是应为标点符号也占用了一位
tensor([[[-0.5124,  0.2813,  0.2190,  ...,  0.9741,  0.3482,  0.2245],
         [-1.2808,  0.2001, -2.3235,  ..., -1.0598,  0.4959,  0.5178],
         [-1.8275,  0.1890, -1.5606,  ...,  2.0024,  0.5615,  0.8081],
         [ 0.4713,  1.1638, -0.4603,  ...,  1.3759, -0.4100,  1.5074]]],
       grad_fn=<EmbeddingBackward0>)

第一行代表"hello",第二行代表",“,第三行代表"I”,第四行代表"am"。这四个token本身是50257中的四个,所以我们把每一个token作为 q \mathbf{q} q ,再把整个整个上下文向量作为 k \mathbf{k} k ,这样我们就得到了按照每一个token计算后的权重。我们用代码实现一下

attn_scores = torch.bmm(en_embeds, en_embeds.transpose(1, 2)) 
print(attn_scores)
tensor([[[752.2334,  44.0126,  49.4829, -20.0411],
         [ 44.0126, 769.1793,  33.2120,   1.2057],
         [ 49.4829,  33.2120, 794.4288,  -4.4434],
         [-20.0411,   1.2057,  -4.4434, 841.0673]]], grad_fn=<BmmBackward0>)
         
attn_weights = torch.softmax(attn_scores, dim=-1)
print(attn_weights)
tensor([[[1., 0., 0., 0.],
         [0., 1., 0., 0.],
         [0., 0., 1., 0.],
         [0., 0., 0., 1.]]], grad_fn=<SoftmaxBackward0>)

通俗一点理解自注力机制就是没有人帮你划重点。
这个全是1的得分显然是不符合常识的,真正大模型在使用的时候,是把输入 X \mathbf{X} X 经过三个不同的线性层,变为 W q \mathbf{W_q} Wq W k \mathbf{W_k} Wk W v \mathbf{W_v} Wv 然后再计算。线性层的参数是可学习的。简单用代码表示一下

# en_embeds 是 1 * 4 * 768 的矩阵 我们这里不做升降维 
W_query = nn.Linear(768, 768, bias=False)
W_key = nn.Linear(768, 768, bias=False)
W_value = nn.Linear(768, 768, bias=False)
query = W_query(en_embeds)
key = W_key(en_embeds)
value = W_value(en_embeds)
attn_scores = query @ key.transpose(1, 2)

attn_weights = torch.softmax(attn_scores, dim=-1)
print(attn_weights)
tensor([[[9.9966e-01, 6.5504e-08, 2.5137e-06, 3.3316e-04],
         [9.5424e-12, 1.9095e-04, 9.9981e-01, 3.4956e-10],
         [6.4441e-01, 2.2519e-05, 2.5931e-03, 3.5298e-01],
         [1.0413e-12, 4.4447e-04, 7.3512e-06, 9.9955e-01]]],
       grad_fn=<SoftmaxBackward0>)
       
context_vec = (attn_weights @ value)
print(context_vec)
tensor([[[ 0.4057,  0.1294,  0.5982,  ...,  0.2427, -0.3708, -0.4979],
         [-0.6604, -0.7601,  0.4312,  ..., -0.6503,  0.3912,  0.2349],
         [ 0.1802,  0.0558,  0.1966,  ...,  0.7259, -0.4829, -0.5026],
         [-0.2259, -0.0727, -0.5388,  ...,  1.6182, -0.6942, -0.5163]]],
       grad_fn=<UnsafeViewBackward0>)

上面的自注力计算完成后,还有很多的其他层,然后层层递进。最后才是一个输出。
下一篇我们完整的实现一个大模型。

think twice code once

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值