从零构建大模型

从零构建大模型

Posted by MZ on June 16, 2025

从零构建大模型

1.理解大语言模型

什么是大语言模型

大语言模型是一种用于理解、生成和响应类似人类语言文本的神经网络。这类模型属于深度神经网络,通过大规模文本数据训练而成。通过输入大量的文本数据,训练资料可能涵盖了网络上大部分公开的文本。

大语言模型的“”主要体现在两个方面:一是训练样本数据大,二是训练模型的参数规模大,数百亿甚至数千亿的参数,这些参数都是可调整的,在训练中慢慢被优化,以预测文本的下一个词。

大语言模型采用了TransFormer架构,该架构允许预测文本生成时有选择地关注输入文本的不同部分。

人工智能是一个囊括机器学习、深度学习等众多分支的领域,大语言模型只是人工智能的分支之一,具体关系如图所示。

构建大语言模型包含哪些阶段

大语言模型的构建通常包括预训练(pre-training)和微调(fine-tuning)两个阶段。“预训练”中的“预”表明它是模型训练的初始阶段,此时模型会在大规模、多样化的数据集上进行训练,以形成全面的语言理解能力。以预训练模型为基础,微调阶段会在规模较小的特定任务或领域数据集上对模型进行针对性训练,以进一步提升其特定能力。下图展示了由预训练和微调组成的两阶段训练方法。

预训练是大语言模型的第一个训练阶段,预训练后的大语言模型通常称为基础模型,这个模型能够完成文本补全任务,即根据用户的前半句话将句子补全。

通过在无标注数据集上训练获得预训练的大语言模型后,我们可以在带标注的数据集上进一步训练这个模型,这一步称为微调。

微调大语言模型最流行的两种方法是指令微调和分类任务微调。在指令微调(instruction fine-tuning)中,标注数据集由“指令−答案”对(比如翻译任务中的“原文−正确翻译文本”)组成。在分类任务微调(classification fine-tuning)中,标注数据集由文本及其类别标签(比如已被标记为“垃圾邮件”或“非垃圾邮件”的电子邮件文本)组成。

Transformer架构介绍

从零开始构建大语言模型的步骤

下面介绍如何分三个阶段构建大模型:

在第一阶段,我们将学习数据预处理的基本流程,并着手实现大语言模型的核心组件——注意力机制

在第二阶段,我们将学习如何编写代码并预训练一个能够生成新文本的类 GPT 大语言模型。同时,我们还将探讨评估大语言模型的基础知识,这对于开发高效的自然语言处理系统至关重要。

最后,在第三阶段,我们将对一个预训练后的大语言模型进行微调,使其能够执行回答查询、文本分类等任务——这是许多真实应用程序和研究中常见的需求。

2.处理文本数据

本章主要将大模型的输入数据,如何将文本等信息转换成大模型能够识别的输入数据,下图就是从文本到输入数据——向量的简单过程:一段文本–>文本分词–>对分词进行ID映射,每个词对应一个数字ID–>创建词元嵌入向量–>编码词的位置向量添加到嵌入向量中–>输入到大模型中。

深度学习模型一般不能直接处理视频、音频、文字等原始格式数据,需要将其转换成向量才能被处理。这也是本章的核心思路——把文本转换成向量

为什么深度学习模型可以预测出正确的文本?向量起到的作用是什么?

深度学习模型之所以可以预测出正确的文本,主要是因为文本转换成向量的表示,向量与向量之间就有了距离,距离远近就代表相关度高低。比如把词向量做一个二维空间的可视化。

词向量的维度可以从一维到数千维不等。更高的维度有助于捕捉到更细微的关系,但这通常以牺牲计算效率为代价。

具体实现方式比较简单,除了向量之外都属于一些常规算法,比如分词——正则划分;词元与ID映射——set集合(该数据结构直接帮你不重复的映射所有词元);重点需要说明的是嵌入向量和位置向量。

2.1嵌入向量

嵌入向量是可以用作大模型输入的一种数据表示形式,它是一个1*N的矩阵向量,是从嵌入层的权重矩阵中提取的一行。嵌入层的权重矩阵是一个随机初始化的M行N列矩阵(在人工智能领域中,大多数矩阵都是随机初始化,然后训练才有了权重信息等),这是大模型开始的第一个可训练的矩阵,后面还有很多不同功能但同样需要训练的矩阵。

从词元ID到嵌入向量的表示很简单,就像是数组与其下标的关系一样,ID就像下标,嵌入层的权重矩阵就是数组。

举个例子:比如有10个词元ID,那么一般会有10*N的嵌入层权重矩阵(N越大,掌握细节越多,训练效果可能越好,但训练代价也越大),每一个ID对应一行,这一行就是该词元ID的嵌入向量。

现在根据词元 ID 创建了嵌入向量。接下来,我们将对这些嵌入向量进行细微的调整,以编码词元在文本中的位置信息。

2.2位置嵌入向量

理论上,词元嵌入非常适合作为大语言模型的输入。然而,大语言模型存在一个小缺陷——它们的自注意力机制(参见第 3 章)无法感知词元在序列中的位置或顺序。

位置嵌入旨在提升大语言模型对词元顺序及其相互关系的理解能力,从而实现更准确、更具上下文感知力的预测。

3.注意力机制

本章将探讨大语言模型架构中的一个核心部分——注意力机制。本章将实现 4 种注意力机制的变体,如图 所示。这些变体中的每一个都是在前一个的基础上逐步建立的,最终目的是实现一种紧凑、高效的多头注意力机制。

3.1没有可训练权重的简单自注意力机制

自注意力机制的目标是为每一个输入元素计算一个上下文向量,这个向量结合了所有的输入元素信息。比如下图就是计算第二个输入元素的上下文向量,即$Z^{(2)}$。

想要计算出上下文向量,首先必须要知道该输入元素对所有输入元素的注意力权重,即哪个输入元素该该元素重要,哪个不重要,上图中的$\alpha_{xx}$。

如何计算呢?这里直接用该元素与其他元素的点积即可。如下图所示。

然后为了稳定性,将这些权重进行归一化处理,最后将各个元素与其对应的权重相乘再相加就得到了想要的元素的上下文向量,如下所示。

上述过程完成了输入序列中的一个元素的上下文向量计算,里面提到的只是一个元素的注意力权重,接下来要计算所有输入元素的注意力权重,如下图所示。

有了注意力权重,就能计算出所有的上下文向量。最后总结一下计算的过程:

可以看到本节的注意力权重直接用输入元素与其他元素的点积获取的,属于静态不可训练的,接下来就是要改变注意力权重的表达方式,使其可以在训练中动态调整。

3.2实现带可训练权重的自注意力机制

去前一节相比,最明显的区别就是引入了在模型训练期间可以更新的权重矩阵。上一节的权重计算是直接把两个输入元素相乘得到点积,两个元素都是固定值,无法训练;这一次引入权重矩阵(查询矩阵、键矩阵、值矩阵)后,先把输入元素和这些矩阵相乘,得到三个对应的向量,分别是查询向量、键向量、值向量;然后利用这些向量之间进行点积求和获取权重。这样中间就得到了一个可以训练的权重矩阵。如下图所示,$W_Q、W_K、W_V$是三个可训练的权重矩阵,初始化时是随机生成的,后面通过训练调整,$q^{(x)}、k^{(x)}、v^{(x)}$是通过第x项的输入向量与权重矩阵相乘得到的。

然后利用第x项的查询向量去其他项的键向量之间的点积计算得到未缩放的注意力分数,然后通过将注意力分数除以键向量的嵌入维度的平方根来进行缩放(取平方根在数学上等同于以 0.5 为指数进行幂运算)。

为什么要进行缩放?

为了避免梯度过小,当嵌入维度很大时,点积计算出的值会非常大,从而导致反向传播算法的梯度很小,梯度小了,模型训练的速度就会很慢。所以要进行缩放。

最后求和得到第x项的上下文向量。

3.3利用掩码隐藏一些信息

本小节思路比较简单,就是掩码遮住一些目前不想要的信息,然后对其他重新归一化。

3.3.1利用因果注意力隐藏未来词汇

因果注意力机制是限制模型在处理任何给定词元时,只能基于序列中的先前和当前输入来计算注意力分数,而标准的自注意力机制可以一次性访问整个输入序列。即:

只要生成一个对角线以上都是0的掩码矩阵,然后与得到的注意力权重矩阵相乘,再归一化即可。

3.3.2利用 dropout 掩码额外的注意力权重

为了防止过拟合,dropout通过随机忽略一些隐藏层单元来有效地“丢弃”它们。一些包括 GPT 在内的模型通常会在两个特定时间点使用注意力机制中的 dropout:一是计算注意力权重之后,二是将这些权重应用于值向量之后。我们将在计算注意力权重之后应用 dropout 掩码,因为这是实践中更常见的做法。

目前我们完成了利用掩码扩展的注意力机制,下一节会扩展到多头注意力。

3.4多头注意力机制

实现多头注意力需要构建多个自注意力机制的实例,每个实例都有其独立的权重,然后将这些输出进行合成。如下图所示。

多头注意力的主要思想是多次(并行)运行注意力机制,每次使用学到的不同的线性投影——这些投影是通过将输入数据(比如注意力机制中的查询向量、键向量和值向量)乘以权重矩阵得到的。

我们还可以更简洁一些,通过堆叠了多个单头注意力层,并将其合并成一个多头注意力层,如图所示,只是简化,并未进行其他实质性操作。

本章从头实现了GPT模型中最重要的部分——自注意力机制。下面一章会实现其他部分,不过相比自注意力机制来说,其他部分有些简单,很好理解。

4.从头实现GPT模型进行文本生成

本章在第三章的基础上完整的实现一个GPT模型核心结构(即Transformer 块,GPT模型是有 Transformer 块*N来组成的),这个模型可以对输入文本进行预测生成,但由于未经过训练,所以生成的文本并不满足人类的愿望。不过本章重点是GPT模型除自注意力机制外的其他子模块。

4.1构建一个大语言模型

本节主要汇总一下本章都做了哪些内容,然后建立一个空壳子,等待后续章节填充。Transformer 块剩余的一些子模块包括:层归一化、GELU激活函数、前馈神经网络以及快捷连接等。

4.2层归一化

什么是归一化?

归一化就是将数据的均值调整为0,方差调整为1。

为什么要进行归一化

为了防止梯度爆炸和梯度消失,梯度是链式的,如果每一层很大,相乘之后数值更大,就会梯度爆炸,而如果每一层很小比1小很多,相乘之后就无限趋近0,所以归一化的好处就是防止梯度的爆炸和消失。

层归一化与批归一化

批次归一化是所有样本的某个特定特征进行归一化,层归一化是单个样本的所有特征进行归一化。就好像你有一堆学生(样本),他们有各科成绩(特征),那么你对所有学生单科比如语文成绩进行归一化就类似与批次归一化,而如果你对单个学生的每一科成绩进行归一化就是层归一化。

4.3实现具有 GELU 激活函数的前馈神经网络

激活函数目的是为了阻止线性变换,通常我们的输入到输出是线性的,那么即使很多层之后也是一个更大的线性函数,无法表达更复杂的函数,加入激活函数就是为了使其更复杂,可以学习到更多。

GELU与ReLU区别是它更平滑,可以更好的被优化和训练。

GELU 激活函数可以通过多种方式实现,其精确的定义为$GELU(x)=x\Phi(x)$,其中$\Phi(x)$是标准高斯分布的累积分布函数。然而,在实际操作中,通常我们会使用一种计算量较小的近似实现(原始的 GPT-2 模型也是使用这种通过曲线拟合得到的近似方法进行训练的): \(GELU(x)\approx0.5\times x \times (1+tanh[\sqrt{\frac 2\pi} \times (x+0.044715 \times x^3)])\)

从上图可以看出GELU确实比ReLU更平滑

实现了GELU激活函数之后,可以加入两个线性层组成一个小型前馈神经网络,如下图所示。

虽然该模块的输入和输出维度保持一致,但它通过第一个线性层将嵌入维度扩展到了更高的维度,如图所示。扩展之后,应用非线性 GELU 激活函数,然后通过第二个线性变换将维度缩回原始大小。这种设计允许 模型探索更丰富的表示空间。

4.4快捷连接

讨论一下快捷连接(也称为“跳跃连接”或“残差连接”)的概念。快捷连接最初用于计算机视觉中的深度网络(特别是残差网络),目的是缓解梯度消失问题。梯度消失问题指的是在训练过程中,梯度在反向传播时逐渐变小,导致早期网络层难以有效训练。

如图所示,快捷连接通过跳过一个或多个层,为梯度在网络中的流动提供了一条可替代且更短的路径。这是通过将一层的输出添加到后续层的输出中实现的。这也是为什么这种连接被称为跳跃连接。在反向传播训练中,它们在维持梯度流动方面扮演着至关重要的角色。

4.5连接 Transformer 块中的注意力层和线性层

Transformer 块的核心思想是,自注意力机制在多头注意力块中用于识别和分析输入序列中元素之间的关系。相比之下,前馈神经网络则在每个位置上对数据进行单独的修改。这种组合不仅提供了对输入更细致的理解和处理,而且提升了模型处理复杂数据模式的整体能力。

4.6实现GPT模型

将输入、Transformer块组合在一起构成一个完成的GPT模型,模型里所有参数都是随机初始化的,需要通过训练来让这个GPT模型成长起来。下图是整个GPT模型的概览,包含了2、3、4章学习的所有内容。

4.7生成文本

通过2、3、4章成功实现了一个完整的GPT模型,但GPT 模型如何将这些输出张量转化为生成的文本?GPT 模型将输出张量转化为生成文本的过程涉及多个步骤,如图所示。这些步骤包括解码输出张量、根据概率分布选择词元,以及将这些词元转换为人类可读的文本。

但由于模型未经过训练,生成了无意义的内容,不是连贯的文本,下一章我们开始学习模型的训练,争取能够让模型输出人类能够理解的文本。

5.在无标签数据上进行预训练