写在前面: 本文基于3Blue1Brown老师的几个介绍深度学习的视频,为我个人笔记,加入了些许个人理解与整理,希望能帮助大家了解GPT和深度学习(注意,其中Multilayer Perceptron和预训练部分为我自己补充),若有发现错误,欢迎在评论区讨论补充😋

参考视频:【【官方双语】GPT是什么?直观解释Transformer | 深度学习第5章-哔哩哔哩】 https://b23.tv/n88a39z

小提示: 遇到看不懂的公式可以跳过,并不是每个公式都需要看懂的,如果这篇文章能让你唤起对深度学习AI的兴趣,我建议你去看看上面我推荐的3Blue1Brown老师的视频!💪

让我们开始吧!😊

在举例说明TransformerGPT预训练模型的主要思想之前,我们先来看看什么是GPT?

  • Generative:表示生成式模型,这种模型可以根据输入生成新的数据。
  • Pre-trained:表示预训练,GPT 模型是在大量数据上进行预训练的。预训练阶段模型会学习广泛的语言知识和模式,然后可以在特定任务上进行微调(fine-tuning)。
  • Transformer:表示模型的架构,Transformer 是一种用于处理序列数据(如文本)的神经网络架构。它通过自注意力机制(self-attention mechanism)来捕捉序列中不同位置之间的依赖关系,具有高效的并行处理能力。

我们先简单说说GPT的运行步骤,这样才能更好地理解其中的具体组成部分。下文中,我们同时具体结合ChatGPT-3模型来举例说明。

1.简单说说GPT

我们先来看看GPT的工作原理:首先,输入一段文本,也可以伴随一些音频和图像;然后,预测出文段接下来的内容,并将结果展现为接下来不同文本片段的概率分布;最后依据概率分布取词,追加到文本末尾。接下来再用包括追加内容的所有文本进行新一轮的预测,直到输出停止符。

  • 也就是说,GPT的目标是:接受一段文本,预测下一个词(token)

再来看看GPT模型在使用(不是训练)时的具体运行步骤

  1. 首先把输入切分为很多小片段token。每个token转换为对应一个被称为 嵌入向量(embedding) 的高维列向量,用来代表这个token的意义。组合这些列向量,这样我们就得到一个输入矩阵。比如GPT-3中,每个token的查询就是一个12288维度的向量。

    • 一开始的输入矩阵中的嵌入向量直接来源于模型的“词典”,也就是嵌入矩阵(Embedding matrix),记为WE\boldsymbol{W_E}WE。这时的向量们只有单个单词含义,没有上下文信息。(其实也不是,这个向量还包含了这个token在全文中的位置信息,不过这里先不讨论)GPT-3中,WE\boldsymbol{W_E}WE就是一个12288x50257的矩阵,这就说明了GPT-3的“词典”数量是惊人的50257个token!

    • 输入的token数量是有限制的,称为上下文长度(Context size),它限制了接下来Transformer在预测下一个词时可以结合的文本量。比如GPT-3的上下文长度就是2048token,所以输入矩阵就有2048列。

  2. 接着把输入矩阵放入注意力模块(Attention),让这些向量相互联系,也就是让它们联系上下文,更新自己的值,更新自己的意思。比如:AI“模”型 和 “模”特 的“模”就不是一个意思,它们的向量就会在此时因为上下文改变。

  3. 再把Attention得到的处理后的向量们放入多层感知器模块(Multilayer Perceptron),这时,向量们不再相互联系,而是各种独立并行经历同一处理。类似于对每个向量提出一系列问题,然后根据回答更新向量。

  4. 重复叠加注意力模块和多层感知器模块。

  5. 得到输出矩阵,现在再通过解嵌入矩阵(Unembedding matrix)得出所有可能的token的值,最后再经过softmax得到概率分布。

    • 解嵌入矩阵(Unembedding matrix),记为WU\boldsymbol{W_U}WU。因为要用它左乘输出矩阵处理后得到的融合了大量信息的最后一个列向量,所以它的行数为所有token的数量(每行对应一个token),列数为嵌入维度。
    • 如果我们在softmax中加入一个分母常量TTT,称为温度(temperature),就可以调整输出的随机性。温度越大,最后的结果概率分布就越平均,模型也会越愿意选择概率小的值;如果T=0T=0T=0,那意味着最后的概率是只有一个token为1。GPT-3的API调用限制了T应该小于2,或许也是为了不要让模型生成太过随机的内容吧。

具体来说,GPT有八类参数:

参数类型 符号 位置 作用
1 嵌入矩阵(Embedding matrix) WE\boldsymbol{W_E}WE Input 模型的“词典”,用来初始化每个token对应的嵌入向量E⃗i\vec E_iE i的初始值
2 查询矩阵(Query matrix) WQ\boldsymbol{W_Q}WQ Attention 生成E⃗i\vec E_iE i的查询Q⃗i\vec Q_iQ i,用来询问前面的其他token它们对自己有没有影响
3 键矩阵(Key matrix) WK\boldsymbol{W_K}WK Attention 生成E⃗i\vec E_iE i的回复K⃗i\vec K_iK i,回答后面token的提问,告诉它自己对它的影响有多大
4 值矩阵(Value matrix) WV\boldsymbol{W_V}WV Attention 生成E⃗i\vec E_iE i的值向量V⃗i\vec V_iV i,告诉其他向量自己如果对它有影响,将会是是如何影响它们的
5 输出权重矩阵(Output matrix) WO\boldsymbol{W_O}WO Attention 把多头注意力生成的多个ΔE⃗i(k)\Delta \vec E_i^{(k)}ΔE i(k)合并为ΔE⃗i\Delta \vec E_iΔE i
6 上投影矩阵(Up-projection matrix) Wup\boldsymbol{W_{\text{up}}}Wup MLP E⃗i′\vec E_i'E i从嵌入维度dmodeld_{model}dmodel映射到更高维度dffd_{ff}dff
7 下投影矩阵(Down-projection matrix) Wdown\boldsymbol{W_{\text{down}}}Wdown MLP A⃗i\vec A_iA idffd_{ff}dff映射回dmodeld_{model}dmodel
8 解嵌入矩阵(Unembedding matrix) WU\boldsymbol{W_U}WU Output 把最后的输出向量E⃗ifinal\vec E_i^{final}E ifinal映射回词表空间,生成下一个token的概率分布

2.Transformer

在上面的GPT模型运行过程中,我们可以看到Transformer由两个模块组成,即:注意力模块(Attention)多层感知器模块(Multilayer Perceptron)。这两个模块叠加形成了庞大的Transformer模型,下面分别介绍。

(1)什么是Attention?

目标:训练好的注意力模块可以计算出需要给初始的泛型嵌入加上什么向量,才能把它移动到上下文对应的具体方向上。也就是更新token对应的向量,让它带上更多的上下文信息。

在这里举一个例子:“我买了一个小玩具埃菲尔铁塔。”一开始,这里的“塔”指的可能是任何高大的东西;但我加上“埃菲尔”这个修饰,这个“塔”的向量就会被更新成类似埃菲尔铁塔的东西;再进一步加上“小玩具”这个修饰,向量再次更新,这次“塔”就会远离一开始那些高大的形容了。

单头注意力机制(Single head of attention)

这里用E⃗i\vec E_iE i表示窗口中第i个token的嵌入向量。

  1. 一开始,所有的E⃗i\vec E_iE i都会通过左乘一个查询矩阵WQ\boldsymbol{W_Q}WQ来产生它对应的比较低维的提问向量Q⃗i\vec Q_iQ i,也就是E⃗i\vec E_iE i查询(Query)。这个向量Q⃗i\vec Q_iQ i就相当于一个包含E⃗i\vec E_iE i信息的问题,它可以用来询问其他向量E⃗j\vec E_jE j是否能影响E⃗i\vec E_iE iGPT-3中,这个查询向量的维度只有128,对比嵌入向量的大小,可以说是非常小了。

  2. 在上一步中,我们得到了所有token的查询Q⃗i\vec Q_iQ i,既然有了E⃗i\vec E_iE i的问题,那现在该怎么让其他的E⃗j\vec E_jE j回答呢?这里就要使用键矩阵WQ\boldsymbol{W_Q}WQ,同样还是左乘每一个E⃗i\vec E_iE i,这样我们就得到了每个token对应的低维向量K⃗i\vec K_iK i,也叫键(Key)。键是用来回答查询的,当键与查询的方向相对齐时,就能认为它们匹配GPT-3中,键的维度和查询一样,也是128,这看起来是理所当然的,毕竟它负责回答问题,那当然应该有和问题一样的格式。

  3. 由于查询和键都是向量,所以我们直接计算点Q⃗i\vec Q_iQ iK⃗j\vec K_jK j的点积就行,范围是(-∞, +∞),越大越对齐。如果K⃗j⋅Q⃗i\vec K_j \cdot \vec Q_iK jQ i是个比较大的正数,也就是jjj回答了iii的问题,那称为E⃗j\vec E_jE j注意到了E⃗i\vec E_iE i;当然,如果点积很小(较小或者为负),就证明这两个嵌入不相关。

    通过上面的计算,我们得到了一个n∗nn*nnn的正方形矩阵(假设窗口大小为nnn):

    Q⃗1\vec Q_1Q 1 Q⃗i\vec Q_iQ i Q⃗j\vec Q_jQ j Q⃗n\vec Q_nQ n
    K⃗1\vec K_1K 1
    K⃗i\vec K_iK i
    K⃗j\vec K_jK j K⃗j⋅Q⃗i\vec K_j \cdot \vec Q_iK jQ i
    K⃗n\vec K_nK n

    比如,第(j,i)(j,i)(j,i)个格子就代表jjjiii的问题的回答,也就是E⃗j\vec E_jE j对于E⃗i\vec E_iE i的更新有多大影响。

    不过,这样还不够,我们还需要把上面每列经过softmax归一化处理,这样就能让每列的结果在(0,1)且和为1。处理后,每列就能看作权重,表示左侧的键与顶部查询的相关度。

    现在我们得到了新的矩阵,每一列和是1的权重矩阵,我们称之为注意力模式(Attention Pattern)GPT-3中,这个注意力模式矩阵的大小就是2048x2048了。

    • 这里需要注意的是,这个矩阵左下三角的权重会置为0,因为在生成文本的过程中,我们不希望它“偷看”后面的信息。置为0后,如果i<ji<ji<j,则jjj就不会对iii的生成有影响了,因为jjjiii的后面,iii生成的时候jjj还没出生呢。这种置0的行为称为掩码(masking)
  4. 上面我们得到了token之间相互的影响程度,但是还没有知道怎么影响。接下来就要用到值矩阵(Value matrix)WV\boldsymbol{W_V}WV,同样还是左乘每一个E⃗i\vec E_iE i,这样我们就得到了每个token对应的相同高维度的向量V⃗i\vec V_iV i,也叫值向量(Value vector)。它的意思是,就是说如果jjj影响了iii,那么就要让E⃗i\vec E_iE i加上V⃗j\vec V_jV j

    • 这里比较有意思的是,其实值矩阵WV\boldsymbol{W_V}WV并不是一个嵌入向量维度平方的超大矩阵,它的参数量也不是嵌入向量维度的平方,而是等于键矩阵和查询矩阵的参数量之和。具体做法就是把WV\boldsymbol{W_V}WV拆开成两个矩阵相乘,也就是进行低秩分解。GPT-3中,左边矩阵大小是12288x128,记为Value↑Value_↑Value,右边矩阵大小是128x12288,记为Value↓Value_↓Value。这样,GPT-3的值矩阵参数就减少为了1572864x2。
  5. 现在我们知道了影响程度,也知道了如何影响,接下来就可以求出想要更新E⃗i\vec E_iE i应该加上的ΔE⃗i\Delta \vec E_iΔE i了,直接对注意力模式矩阵每列的权重与对应的所有V⃗j\vec V_jV j加权和即可。

  6. 最后,我们就得到了更新的向量:E⃗i′=E⃗i+ΔE⃗i\vec E_i' = \vec E_i + \Delta \vec E_iE i=E i+ΔE i

注意,上面是拆分后的,实际上实现应该是矩阵乘法,最后得到的是一个矩阵,每列是更新后的向量。GPT-3中,这个矩阵是一个12288x2048的矩阵,和进入Attention时大小一样。

多头注意力机制(Multi-head of attention)

上面已经说明了单头注意力机制,但Transformer内完整的注意力模块则是由多头注意力组成,它大量并行地执行上面的操作,每个头都有不同的查询、键、值矩阵。比如在GPT-3中,每个模块使用96个注意力头。也就是有96个不同的键和查询矩阵,产生96种不同的Attention模式;每个注意力头都有独特的值矩阵,同一个嵌入向量会有96个不同的值向量。

这也就意味者,对于一个E⃗i\vec E_iE i,每个头都会给出一个要加入到该位置的嵌入中的变化量,比如第kkk个头就会生成一个ΔE⃗i(k)\Delta \vec E_i^{(k)}ΔE i(k)

现在,我们得到了来自不同注意力头的多个 ΔE⃗i(k)\Delta \vec E_i^{(k)}ΔE i(k) 向量,每个向量代表了该注意力头对位置 iii 的嵌入向量 E⃗i\vec E_iE i 的更新。那么,如何将这些不同的更新向量合并成一个最终的更新向量 ΔE⃗i\Delta \vec E_iΔE i 呢?为了把这些向量“绑”回一起,我们需要引入一个“粘合剂”——输出权重矩阵 WO\boldsymbol{W_O}WO

具体来说,我们先把 hhh 个头的输出向量拼接成一个大矩阵。假设每个头的输出向量维度是 dkd_kdk,拼接后的大矩阵维度就是 h×dkh \times d_kh×dk。接下来我们就用 WO\boldsymbol{W_O}WO 把这个大矩阵“压扁”,变回我们熟悉的嵌入维度 dmodeld_{model}dmodel

E′=[head1;head2;...;headh]WO\boldsymbol{E'} = [\text{head}_1; \text{head}_2; ...; \text{head}_h] \boldsymbol{W_O}E=[head1;head2;...;headh]WO

其中, WO\boldsymbol{W_O}WO 的维度是 (h×dk)×dmodel(h \times d_k) \times d_{model}(h×dk)×dmodel以GPT-3为例,它有96个注意力头,每个头的输出维度是128,所以拼接后的矩阵维度是12288,而 WO\boldsymbol{W_O}WO 的维度就是12288x12288。这样,通过乘上 WO\boldsymbol{W_O}WO ,我们就把多头的输出“粘”回了原来的嵌入维度。

(2)什么是Multilayer Perceptron?

多层感知器(Multilayer Perceptron,简称MLP) 是一种前馈神经网络,由输入层、隐藏层和输出层组成。在Transformer中,MLP位于注意力模块之后,对每个向量进行独立的非线性变换,增强模型的表达能力。

我们把注意力模块输出的矩阵记为 E′\boldsymbol{E'}E,它的每一列 E⃗i′\vec E_i'E i 就是融合了上下文信息的嵌入向量。可以把MLP想象成一个“向量洗衣机”,经过注意力模块“交流”后的各个向量 Ei′⃗\vec{E'_i}Ei 会被独立地放入MLP进行“清洗提纯”,去除其中不太相关的信息,突出最重要的特征。这个过程和上面的Attention不一样,它不涉及向量间的互相影响。

接下来,这些向量将通过MLP进行变换,MLP由两个线性变换和一个非线性激活函数组成:

  1. 首先,向量 E⃗i′\vec E_i'E i 会与一个矩阵 Wup\boldsymbol{W_{\text{up}}}Wup 相乘,将维度从嵌入维度 dmodeld_{model}dmodel 映射到一个更高的维度 dffd_{ff}dff (ff表示前馈, feedforward),这个过程叫做线性变换投影(projection)

    H⃗i=WupE⃗i′\vec H_i = \boldsymbol{W_{\text{up}}} \vec E_i'H i=WupE i

    在GPT-3中,dmodeld_{model}dmodel 是12288,dffd_{ff}dff 是49152,是原来的4倍。所以 Wup\boldsymbol{W_{\text{up}}}Wup 是一个49152x12288的矩阵。

  2. 接着通过一个非线性激活函数,通常是ReLU (Rectified Linear Unit)

    A⃗i=ReLU(H⃗i)=max⁡(0,H⃗i)\vec A_i = \text{ReLU}(\vec H_i) = \max(0, \vec H_i)A i=ReLU(H i)=max(0H i)

    ReLU可以给向量加入一些非线性因素,这就像在高维空间中对向量进行一些非线性的扭曲变形,让神经网络能够拟合更复杂的函数。

  3. 最后,激活后的向量 A⃗i\vec A_iA i 再与另一个矩阵 Wdown\boldsymbol{W_{\text{down}}}Wdown 相乘,将维度从 dffd_{ff}dff 映射回 dmodeld_{model}dmodel

    E⃗i′′=WdownA⃗i\vec E_i'' = \boldsymbol{W_{\text{down}}} \vec A_iE i′′=WdownA i

    在GPT-3中,Wdown\boldsymbol{W_{\text{down}}}Wdown 是一个12288x49152的矩阵,与 Wup\boldsymbol{W_{\text{up}}}Wup 的形状相反。

MLP使得模型能学习到输入向量的非线性变换,这种变换能让模型提取到更高层次、更抽象的特征表示。有趣的是,尽管MLP的参数量(49152x12288x2=1.2B)占Transformer参数的大部分,但它的计算量相对较小,因为矩阵乘法可以高度并行。事实上,大多数训练时间都花在了Attention的softmax操作上。

最后,我们把MLP的输出 E⃗i′′\vec E_i''E i′′ 与原始嵌入 E⃗i′\vec E_i'E i 相加,就可以得到最终的输出向量:E⃗ifinal=E⃗i′+E⃗i′′\vec E_i^{final} = \vec E_i' + \vec E_i''E ifinal=E i+E i′′。这种残差连接(residual connection) 的结构有助于梯度在深层网络中回流,缓解梯度消失的问题。

多个注意力头MLP堆叠在一起,构成了强大的Transformer模型。这种结构能够让模型很好地捕捉序列中的长距离依赖,学习文本的深层语义表示。

3.预训练(Pre-trained )

有了上面Transformer结构的铺垫,我们最后再来说说GPT的预训练。

GPT模型的预训练过程,就像是在给模型“喂书”。先收集大量的无标签文本数据,比如维基百科、新闻文章、书籍等等。然后把这些文本切分成一个个句子或段落,每次随机抽取一段,输入到GPT模型中。 而GPT要做的任务很简单:*给定输入的文本,预测下一个最可能出现的单词。*比如输入“今天天气真不错,我想出去()”,模型就要预测下一个词最有可能是“散步”“玩”“跑步”……通过不断地对大量文本进行这样的预测训练,模型就慢慢学会了人类语言的规律和模式。

这里我引用一下我的老师上课常常举的例子:预训练阶段可以看作是模型的“小学到高中”,通过海量阅读,模型掌握了基本的语法、语义、常识等知识,为后续的各种具体任务打下了坚实的基础;而之后的微调阶段,则可以看作“大学”,用来精修具体的方向。

GPT采用的是自回归(autoregressive) 的生成式预训练,具体来说:

  1. 将大规模无标签文本语料库进行tokenize,切分成有固定长度的序列,比如GTP-3,它的一个窗口大小是2048token,所以切分成2048块。

  2. 把每个序列输入GPT模型,让模型预测该序列中下一个token。预训练的损失函数一般采用交叉熵损失(cross-entropy loss),模型通过最小化负对数似然损失函数来学习语言的概率分布:

    L(θ)=−∑i=1nlog⁡P(xi∣x<i,θ)L(\theta) = -\sum_{i=1}^{n} \log P(x_i|x_{<i}, \theta)L(θ)=i=1nlogP(xix<iθ)

    其中 xix_ixi 是第i个token, x<ix_{<i}x<i 是i之前的所有token, θ\thetaθ 是模型参数。

  3. 在训练过程中,不断调整模型参数 θ\thetaθ ,让预测的概率分布与真实数据的分布越来越接近。

通过这种自监督学习,GPT可以建模语言的上下文相关性,学会根据前文预测下一个词。预训练让模型拥有了语言的先验知识,具备了一定的语义理解和生成能力。以GPT-3为例,它在高质量的互联网爬取语料(Common Crawl)上训练,训练数据量高达499B个token,训练了大约300B个参数的模型!如此大规模的预训练赋予了GPT-3强大的语言能力,也使得GPT-3的预训练耗时数月,消耗了大量的算力和能源。这也是为什么GPT-3级别的模型通常由大公司训练,而个人很难复现的原因。

预训练完成后,GPT就有了强大的语言理解和生成能力。但它还不能直接应用到具体任务中,因为预训练阶段模型只是尽可能准确地预测下一个词,并没有学习到如何回答问题、写摘要、写代码等任务。

所以,我们还需要对预训练好的GPT进行微调(fine-tuning)。具体来说,就是在下游任务的数据集上,通过给输入文本添加一些特殊标记,让模型学会根据指令去完成特定任务。比如在问答任务中,我们可以把问题和答案用特殊符号隔开,让模型学习到要生成答案。

微调完成后,GPT就可以应用到具体的任务中,如问答、对话、文本生成等。这种 “预训练+微调” 的范式极大地提升了自然语言处理任务的性能,改变了NLP领域的格局。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐