DataBrick-大模型基础笔记-全-

DataBrick 大模型基础笔记(全)

002:Transformer架构介绍 🧠

在本模块中,我们将学习Transformer架构。这是当今几乎所有大语言模型所采用的核心神经网络架构。如果你在本课程中只想关注一个核心内容,那么Transformer无疑是最佳选择。

概述

Transformer架构于2017年通过论文《Attention Is All You Need》首次发布。自此之后,大多数大语言模型都是其某种变体,包括OpenAI的GPT(生成式预训练Transformer)系列模型。这些模型在当前基于聊天的语言模型浪潮中产生了巨大影响。

Transformer的独特地位

在Transformer出现之前,深度学习模型设计领域经历了快速的实验阶段,各种不同的层、函数和模块的组合层出不穷,堪称一次“寒武纪大爆发”。

然而,在Transformer之后,至少在自然语言处理领域,许多模型设计都开始遵循相同的基本构建模块。研究重点更多地转向了不同的训练技术、数据生成方法等方面,而底层架构本身并未发生根本性的巨大改变。

架构的核心优势

Transformer架构之所以强大,是因为它允许模型学习其输入中不同方面之间的多种交互关系。此外,该架构本身可以进行堆叠,形成不同的深度,从而获得不同质量的模型。

尽管如今存在一些变体,旨在使某些操作更快或成本更低,但其基本构建模块是相同的。因此,深入理解这一架构至关重要。这不仅能帮助我们解读模型的行为,也能让我们更好地评估针对该架构的新改进。

总结

本节课我们一起学习了Transformer架构的起源、其在当前大语言模型中的核心地位,以及其稳定且强大的设计特点。理解这些基本构建模块是深入探索现代语言模型世界的第一步。

003:Transformer概览 🧠

在本模块中,我们将深入探讨Transformer模型。我们将拆解一个Transformer模型,了解其构成部分。我们将学习构成GPT、BERT或其他任何基于Transformer模型的不同构建模块。

课程概述

在本节课中,我们将学习Transformer模型的核心组件。我们将了解构成各种Transformer架构(包括编码器模型、解码器模型以及编码器-解码器组合模型)的所有不同构建模块。我们还将更好地理解注意力机制是什么、如何工作以及为何它如此重要,并解锁了如今大型语言模型的能力。最后,我们将把这些大型语言模型应用于不同类型的NLP任务,以观察其卓越性能。

背景与重要性

回顾过去,您学习本课程的主要原因之一,很可能是在过去6到12个月里,您开始使用了一项特定的技术,最可能是ChatGPT。2022年,OpenAI向世界发布了ChatGPT,它成为了人类历史上采用速度最快的技术。

这标志着我们正在进入人工智能的新黄金时代。2023年,我们在AI大型语言模型领域见证了想法、概念和创新的爆炸式增长,它们持续以其所能实现的功能令我们惊叹。

ChatGPT及其同类产品代表了人类与技术互动的一种新方式。因为它们基于自然语言处理,我们能够以比其他类型技术更自然的方式与它们交流。同时,它们广泛的技能和深厚的技术知识也使我们能够在日常工作中表现得更好。

我们实际上以前也见过类似的创新。对于那些熟悉过去十年左右深度学习领域的人来说,可能已经注意到我们在2010年代初、2012年左右经历过类似的狂热时刻。当时,计算机视觉领域因卷积神经网络的创新而震动。这项创新就是卷积层,它使我们能够查看图像的不同空间区域,以理解图像内部的内容。

正如您在此处看到的图像所示,这意味着我们可以与旧技术竞争并完全超越它们。在ImageNet测试中,卷积神经网络轻松主导了竞争,并且自2012年左右以来,每个模型都基于卷积神经网络,使结果趋于饱和。

自然语言处理领域一直在等待这种发展,我们在2018年左右得到了它。解锁大型语言模型能力的关键创新是一种称为注意力机制的东西。

核心创新:注意力机制

注意力,顾名思义,允许计算机(在此处指Transformer)精确地看到一个单词在特定序列中如何与其他单词相关联。它为序列中每个单词对其他单词的重要性进行评分。

对我们人类来说,这似乎是一个显而易见的概念,是我们早年就发展出来的能力。但对于自然语言处理来说,这是一个至关重要的部分,它解锁了以前无法实现的能力。

然而,虽然注意力是我们掌握自然语言处理能力的一大进步,但它实际上只是构建我们今天看到的Transformer和模型所需的几个部分之一。

后续内容预告

在下一节中,我们将深入探讨所谓的Transformer模块,并了解从输入标记到下一个选定单词的输出,所有这些部分如何通过注意力、神经网络和其他组件连接在一起,使我们能够攻克NLP任务。

总结

本节课我们一起学习了Transformer模型的概览及其重要性。我们了解到,注意力机制是解锁现代大型语言模型能力的关键创新,它允许模型理解序列中单词之间的关系。我们还回顾了计算机视觉中卷积神经网络的类似突破,并预告了接下来将深入探讨Transformer的具体构建模块。

004:1.3 Transformer块 🧱

在本节课中,我们将要学习Transformer模型的核心组成部分——Transformer块。我们将了解输入序列如何通过这个块被处理和丰富,以及它如何为最终的预测任务做准备。


输入与目标

Transformer与大多数语言模型一样,用于尝试预测下一个词。

Transformer实现这一点的方式与大多数其他语言模型不同。

但它们仍然基于相同的基本原理工作。

它们会接收一个词元序列,然后对该序列进行处理。

改变该序列中的信息,使得该序列可用于预测词汇表中的下一个词或词元。

在Transformer中,我们获取输入词元并将其转换为词嵌入向量,从而得到一个由不同词嵌入向量组成的向量序列。

然后,我们经过一系列丰富阶段,使得这些向量被转换,并为每个向量构建越来越多的上下文和更多信息,以便当我们最终将其提供给softmax分类层或预测层时,它有大量信息可供使用。


Transformer块概览

上一节我们介绍了Transformer的基本目标,本节中我们来看看实现这一目标的核心结构——Transformer块。

如果我们观察Transformer块,其中词元被丰富然后传递到下一个序列,我们可以在示意图中看到实际发生的过程。

如果我们考虑一个仅包含一个Transformer块的Transformer。

该过程大致如下:我们获取一个输入词元序列。

将它们转换为词向量,从而得到一系列词向量。

然后,我们会添加额外信息,例如位置编码(我们稍后会讨论)。

这提供了序列中每个词元相对于其他词元的位置信息。

然后我们将其传入Transformer块。

Transformer块的目标是尽可能用丰富的上下文信息来丰富该序列中的每个词元。

它通过注意力机制和神经网络变换来实现这一点。

然后我们通过添加残差连接和归一化序列中的向量来进一步处理。

接着,这些向量在我们的Transformer输出端,通过一个线性和softmax的组合,来尝试预测下一个词元或对序列进行分类。

大多数Transformer会有数百个Transformer块,但过程完全相同。

在Transformer块的末端,序列现在与我们最初的自然语言输入几乎没有相似之处,但仍保持相同的大小和格式,并被传递到下一个Transformer块。


逐步解析处理阶段

让我们逐步看看这个准备、丰富和预测阶段是如何工作的。

注意力机制 👁️

首先,我们有至关重要的注意力机制。

注意力机制的作用是衡量序列中每个词相对于其他词的重要性和相关性。

当我们从一个块到另一个块,在传统的Transformer架构中向上移动时,这个概念会略有延伸,我们可能有几十个甚至几百个Transformer块。

因此,在第一个块之后,你并不是真正地在比较一个词与另一个词,但你仍然在关注每个词元开始时所在的完全相同的序列和位置。

它们被之前的块赋予了额外的上下文信息,我们仍然在观察不同的块如何以不同的方式传递序列。

通过这样做,通过添加越来越多的Transformer块层,我们能够丰富这些向量,并更深入地观察序列内部是如何相互作用的。

典型的序列长度如今是数千,甚至数万的数量级。

因此,有大量信息需要处理,单个Transformer块无法捕获例如多段落上下文中的所有信息。

此外,注意力是一个线性操作(我们稍后会看到它的具体形式),到目前为止它并没有为模型添加任何深度学习成分。

事实上,Transformer几乎不被视为深度学习模型。

然而,大型语言模型中的大部分参数被每个注意力块中使用的前馈神经网络所占据。

前馈神经网络 🧠

现在,Transformer中的前馈神经网络的工作方式可能与你的预期略有不同。

如前所述,提供给Transformer的词元被转换为词嵌入,它们具有特定的维度,假设是100维。

这意味着我们将得到一个向量序列,每个向量长度为100维。

在Transformer中被称为“位置式前馈神经网络”的网络,其输入宽度将是100个神经元。

这意味着我们逐个将每个词元传递给神经网络。

该神经网络的权重和结构在每次应用于序列中的每个向量时都是相同的。

因此,这个“位置式”(意味着它处理每个位置)前馈神经网络被应用于每个词元,以便将其转换为正确的格式,然后提供给Transformer中的下一个块,或在Transformer末端提供给输出块。

这允许进行非线性变换,并且使得Transformer本身能够在我们从最初理解(例如,名词和动词在Transformer块较低层次上如何相关)一直到理解上下文的情感(在Transformer块末端)的过程中,建立起不同层次的复杂性和理解。

我们将在后续的笔记本中操作Transformer时看到更多这方面的内容。

残差连接与层归一化 ⚖️

Transformer块架构中另一个非常重要的部分是残差连接和层归一化。

特别是残差连接非常重要,因为它们允许梯度在反向传播期间自由地反向流动。

它们还确保输入序列的信号在这些向量变得越来越丰富的过程中不会丢失。

因此,我们有一条不间断的路径,让输入序列的原始结构能够一直穿过Transformer。

层归一化也至关重要,因为Transformer通常需要很长时间来训练。

因此,确保训练的稳定性是层归一化允许我们做到的事情。


整体输入与输出

现在让我们谈谈整个Transformer本身的输入和输出。

让我们从输入开始。如前所述,我们从一系列自然语言词元开始,这些词元被转换为词嵌入。

然后,为了确保我们保留序列中这些词元的顺序,我们还会向词嵌入附加一种位置编码。

位置编码有多种不同类型,我们将在本模块和模块3的笔记本中探讨其中一些。

一旦我们的词元被丰富为带有位置编码的词嵌入,我们就把它们传入Transformer块。

然后,Transformer块致力于为序列中的向量添加不同类型的丰富性、复杂性,以及(希望是)理解。

然后将其传递给Transformer的输出端。

在Transformer的输出端,我们有我们的词汇表和一个线性神经网络,该网络使用softmax函数来选择下一个要生成的词元(基于我们在Transformer块中构建的序列向量),或者根据我们为特定应用开发的某种分类方案对其进行分类。


不同的Transformer应用方式

在本节中描述的Transformer块有多种不同的使用方式,在下一节中,我们将讨论其中一些不同的方法。

以下是几种主要类型:

  • 编码器模型:我们实际上不生成任何新词元。
  • 解码器模型:我们只专注于生成下一个词元。
  • 编码器-解码器模型:我们输入一个序列,并根据任务输出一个完全不同的序列。

总结

本节课中我们一起学习了Transformer的核心处理单元——Transformer块。我们了解了输入序列如何通过词嵌入和位置编码进行准备,然后通过注意力机制和前馈神经网络在块内被丰富。我们还探讨了残差连接和层归一化在稳定训练和传递信息中的关键作用。最后,我们概述了Transformer的整体输入输出流程,并预告了基于Transformer块的不同模型架构。

005:Transformer架构

在本节课中,我们将学习Transformer模型的不同架构类型。到目前为止,我们对Transformer可以采用的架构类型讨论得比较宽泛,主要聚焦于Transformer块是什么以及它们由什么组成。然而,通过组织这些Transformer块的方式,我们可以构建出许多不同类型的Transformer。

在本节中,我们将探讨Transformer常见的不同架构类型,以及它们各自的应用场景和所需的创新。

如果我们看一下当前Transformer家族树的状态,会发现它相当庞大且复杂。但我们可以将其分为三个不同的类别:左侧是仅编码器模型,我们稍后会解释用Transformer进行“编码”的含义;最右侧是仅解码器模型,你会看到一些熟悉的名字,如Claude或GPT;中间则是编码器-解码器模型。仔细观察,你会发现中间部分有一个非常熟悉的字母“G”代表谷歌,谷歌在编码器-解码器模型领域占据主导地位,这有很好的原因。

😊 在名为《Attention is All You Need》的原始Transformer论文中,谷歌的研究人员提出了一种基于编码器-解码器方法的架构。原因是他们想进行英语和德语之间的机器翻译。目标是输入一个英语词元序列,最终输出一个翻译后的德语序列。

他们实现这一目标的方式是采用一系列编码器块(即我们目前所见的常规Transformer块),输入英语词元,按照上一节介绍的方式进行转换和准备。然后,在Transformer块的末端,经过这些块处理后产生的不同序列向量输出,实际上被用于解码器部分的注意力机制中,称为交叉注意力

其工作原理是:模型首先查看解码器部分已生成的单词,当需要交叉注意力时,它会比较其Transformer块中间位置的单词,并查看来自编码器端的交叉注意力向量。我们稍后会详细讨论注意力机制如何结合这些不同类型的向量,但你可以这样理解:首先,编码器接收英语并将其转换为某种丰富的向量表示;然后,它利用这些丰富的向量,学习德语单词与待翻译英语单词之间的关系。

因此,编码器-解码器模型通常将一种语言任务转换为另一种语言任务。这可以是翻译或转换,也可能是介于两者之间的任务,例如将英语或某种自然语言输入转换为代码语言,或将一种编程语言转换为另一种编程语言,或者进行文本摘要。编码器-解码器模型有多种不同的应用场景,它们都基于交叉注意力的概念。我们将在后续详细讨论注意力机制时,深入探讨交叉注意力是什么以及如何使用它。

本质上,编码器为解码器提供了额外的信号源,使其能够完成给定的任务,并且在反向传播过程中,解码器学会依赖来自编码器的信号来实现其目标。

Transformer架构家族的下一部分是仅编码器模型。在原始Transformer发布几年后,谷歌也提出了第二种Transformer架构,即BERT

BERT引入了几项创新:一是片段嵌入,你可以输入一个句子,用[SEP]分隔符隔开,然后输入第二个序列或句子,BERT能够将两个句子一起进行比较。BERT的训练方式也不同,它会故意遮蔽句子中的不同单词。它还允许你进行下一句预测,即判断它看到的第二个句子是否紧跟在第一个句子之后,并给出真或假的判断。

BERT非常擅长微调,至今仍在许多自然语言处理任务的最新技术中占据主导地位。BERT非常适用于问答、命名实体识别等更传统的自然语言处理任务。它至今仍在使用,并且比我们通常在新闻中看到的某些大型模型要轻量得多。

说到这些模型,基于Transformer架构产生的第三种架构被称为仅解码器模型。其中最流行和广为人知的版本是GPT。GPT(生成式预训练Transformer)顾名思义,是一种生成新单词的Transformer。你可能听说过“生成式AI”这个流行词,GPT正是这个词流行的原因。

仅解码器模型的全部目标,是尝试基于当前正在处理的序列来预测下一个单词。在GPT中,它会接收所有正在处理和丰富的向量,并使用Transformer块末端的分类Softmax层来尝试预测下一个词元或单词。

我们已经看到了大量基于这些GPT或解码器模型的应用,你可能熟悉ChatGPT,还有Claude、Llama、MPT等等。本课程将重点介绍GPT,但仅编码器模型和编码器-解码器模型同样非常有价值,值得花时间熟悉。

现在我们已经回顾了Transformer是什么以及如何构建它们,同样重要的是要考虑一些我们将反复听到的重要变量。

让我们从输入方面开始:

  • 词表大小:Transformer训练时使用的词元数量,这定义了它的“词汇”范围,使其能够组合词元来创建新词。
  • 嵌入维度或模型大小:这是Transformer中一个非常重要的变量,通常与模型将占用的空间大小有关。我们稍后会讨论参数数量,但决定模型参数数量的最重要变量之一就是嵌入维度或模型大小。这些是我们使用的词嵌入的维度。Transformer块内部的许多矩阵和神经网络的大小都与模型大小或嵌入维度直接相关。
  • 序列长度或上下文长度:这对Transformer所需的计算量至关重要,虽然与参数数量关系较小。我们已经看到上下文长度从原始GPT模型的512,一路发展到像Claude这样的新模型的数十万。

如果我们查看内部变量,还需要关注:

  • 注意力头数量:我们将在下一节详细讨论注意力,多头注意力中的头数量也是一个需要跟踪的重要部分。
  • 中间或内部前馈网络大小:这与位置级前馈神经网络的中间或内部隐藏层有关。这些位置级前馈神经网络约占Transformer中所有学习参数的66%。
  • 层数:这同样至关重要,因为它决定了Transformer拥有的Transformer块的数量。

当我们讨论训练时:

  • 批次大小:对于所有这些模型,批次大小也非常重要。事实上,虽然Transformer本质上是深度学习实体,但你会注意到许多方面相当不同。对于这类模型,看到周期数仅为1批次大小仅为1或2的情况并不少见。
  • 训练词元数量:此外,Transformer训练所用的词元数量达到了数百万、数十亿甚至数万亿。这是我们在之前的深度学习中未曾见过的,但正是这一点使得大语言模型变得“大”。

总结
本节课中,我们一起学习了Transformer的三种主要架构类型:编码器-解码器模型(如原始Transformer,用于翻译等任务)、仅编码器模型(如BERT,擅长分类和问答)以及仅解码器模型(如GPT,用于文本生成)。我们还了解了构建和描述Transformer模型时需要关注的关键变量,包括词表大小、嵌入维度、上下文长度、注意力头数量、层数以及训练相关的批次大小和训练词元数量。理解这些架构和变量是掌握大语言模型工作原理的基础。

006:模块_1-Transformer-1.5_注意力机制 🧠

在本节课中,我们将要学习Transformer模型的核心组件之一:注意力机制。理解注意力是构建和训练我们自己的基础Transformer模型的关键一步。

概述

上一节我们介绍了Transformer模型的一些重要变量。本节中,我们来看看注意力机制的具体工作原理。注意力机制是Transformer中最重要但也可能较为复杂的组件之一,特别是如果你之前没有接触过类似概念的话。

注意力机制详解

首先,让我们思考如何处理我们正在处理的向量。这个向量代表我们当前正在查看的标记。我们假设现在处于模型的第一层,这样我们可以直接将输入的词嵌入向量与注意力机制中将要讨论的向量关联起来。

注意力机制由三个向量族构成:查询向量、键向量和值向量。实际上,我们只有一个查询向量,它与序列中我们当前正在查看的标记相关。我们将拥有多个键向量,它们来自序列中的所有向量。我们还将拥有一系列值向量。

我们将使用一个矩阵乘法,用词向量(或者你喜欢的话,称为“丰富后的嵌入向量”)乘以这个查询矩阵,来得到我们的查询向量。所有的矩阵——查询矩阵、键矩阵和值矩阵——都由在反向传播过程中学习的权重构成。

注意力背后的思想是,我们使用一个单一的查询向量,并与我们从序列中生成的所有其他键向量进行“对话”。我们实际上是在问:“键向量,你与查询向量(也就是我)有多相关?” 我们为序列中的所有标记并行执行这个计算。

因此,每次我们进行注意力计算时,我们都聚焦于我们的查询向量,并将查询向量“广播”给所有的键。这里的“对话”指的是,我们正在评估这个键向量对于查询向量有多相似、多重要。

注意力计算步骤

以下是计算注意力的具体步骤。

第一步,我们获取输入向量(如果在第一层,就是带有位置信息的词嵌入向量),并创建三种新类型的向量:查询向量、键向量和值向量。如前所述,查询向量仅由当前标记构建。

第二步,我们使用缩放点积,将查询向量与所有的键向量相乘。这为我们提供了注意力分数。我们将为当前查询向量与每个键向量的每一对组合得到一个注意力分数,最终得到一个注意力分数向量。这个向量的长度与查询向量相同,也与我们从标记得到的词嵌入长度相同。这也是我们构建的模型的维度(即模型大小)非常重要的另一个原因。这些向量的大小就是模型的维度。

第三步,查询向量乘以键向量得到注意力权重,这些权重被缩放到0到1之间。然后我们进行一种特殊类型的乘法。对于向量中的每个位置,注意力权重会乘以值向量在对应索引处的值。从索引0到嵌入大小,我们将索引0处的注意力权重分数与索引0处的值向量分数进行简单的缩放点积,并对每个索引都执行此操作。

这最终为我们提供了一个完整的输出向量,代表了该特定标记在整个序列上的注意力分数。

注意力机制的直观理解

让我们再花点时间思考一下,因为注意力机制可能有些复杂。

实际上,你可以将其视为一种文件柜和查找系统。我们有一个来自当前标记的查询,然后我们翻阅文件(这些是键向量),查看每个不同的文件(键向量)拥有我们所需信息(即值向量)的程度如何。

一旦我们精确计算出每个键向量应该为查询向量贡献多少(这就是注意力权重),我们就把它们全部组合起来,从而得到一个完整的图景。这就是我们的输出向量,它完整地描绘了我们应该对序列中其他每个标记给予多少关注。

因此,“注意力”这个概念就来源于此:输出向量每个部分的值,告诉我们相对于当前关注的标记,我们应该对序列中其他每个标记给予多少注意力。

总结

本节课中,我们一起学习了Transformer的注意力机制。我们了解了它如何通过查询、键和值三个向量族,计算出一个标记与序列中所有其他标记的相关性,并生成一个加权的输出向量。这构成了模型理解上下文关系的基础。

在下一节中,我们将结合注意力机制和前馈神经网络,探讨如何实际构建我们自己的基础模型,了解训练这些模型所需的、超越注意力本身的更大架构。

007:模块1-Transformer-1.6_基础模型 🧱

在本节课中,我们将要学习如何将Transformer的各个构建模块组合起来,构建一个有用的大型语言模型。我们将探讨构建、训练模型所需的完整流程,包括数据、算力和训练程序等关键组成部分。

概述

上一节我们介绍了Transformer的各个核心组件。本节中,我们来看看如何将这些组件整合起来,从头开始构建和训练一个真正有用的大型语言模型。

我们已经了解了构建Transformer大型语言模型所需的所有不同基础模块。你可能会想知道,我们如何构建一个真正有用的模型?如何训练和构建它?以及我们需要哪些部分才能让一切正常运行?在本节中,我们将介绍所有不同的组成部分,包括数据、算力和训练流程。如果你想从头开始训练自己的大型语言模型,就需要遵循这些步骤。

基础模型与微调

现在需要记住一点,在本课程和更广泛的文献中,你可能会听到“基础模型”或“基座模型”这两个术语交替使用。它们指的是从随机初始化的权重开始训练,仅用于预测下一个词的大型语言模型

你有时会发现基础模型的行为方式可能与你期望的不同。这是因为当我们训练一个基础模型时,它从根本上是在理解语言的语法和潜在的语义。例如,你问一个基础模型“法国的首都是哪里?”,它可能不会给出你期望的答案,而是反问“德国的首都是哪里?”。这是因为在训练数据集中,它更常见到的是问题列表,而非问答对。

我们稍后会讨论不同类型的训练集。但请记住,对于特定任务的性能,你几乎总是需要对模型进行微调。这只需要少得多的训练数据,并且通常是大多数人的推荐做法:他们会采用一个经过预训练的基础模型,然后在其基础上进行微调。

然而,对于那些有足够勇气开始训练自己基础模型的人,让我们看看你需要经历的一些不同选项。

构建基础模型的考量因素

以下是构建基础模型时需要考虑的关键因素:

  • 模型架构:需要考虑是选择解码器编码器还是两者的组合架构。
  • 目标任务:需要考虑你希望微调后的模型执行何种任务。这将影响你对模型结构以及数据类型的一些决策。
  • 模型规模:需要考虑你希望模型有多大,希望它对语言的表征有多丰富,这涉及到嵌入维度块的数量等。
  • 数据数据的类型、可用性以及数据整理将是你需要克服的最困难的事情之一。
  • 算力资源:实际上,获取算力资源,包括训练模型所分配的时间可用的硬件,是至关重要的。如今这并非理所当然,因为GPU相当紧缺,尤其是训练基础模型所需的那些。

架构选择与数据准备

让我们思考一下不同类型的架构。我们已经见过谷歌在《Attention Is All You Need》论文中提出的编码器-解码器模型,以及其后出现的不同代际模型,如BERTGPTT5

根据你想要的任务类型,可以进行选择:

  • 如果是分类任务,你可能会选择像BERT这样的模型。
  • 如果是生成任务,你可能会想要像GPT这样的模型。
  • 如果是翻译任务,你可能会想要像T5这样的编码器-解码器模型。

你还需要考虑层的数量以及你能处理的上下文长度

最重要的是,数据是你必须努力争取的。有一些公开可用的数据集,例如著名的Pile数据集,它是不同公开文本资源的组合。然而,如果你在相同的数据上训练别人已经训练过的相同模型,很可能不会获得太多优势,直接下载该模型的权重会更好。

你至少可以从像Pile这样的数据集开始,以很好地理解(至少在这种情况下)英语乃至一般语言。然后,你可能拥有自己专有的或精心策划的、更具体的数据集,可能包括转录文本、数字化文本、代码示例以及其他你认为对训练此基础模型有价值的来源。

模型训练

一旦你准备好了所有的数据、架构和算力,那么你可以松一口气了,因为现在你又回到了训练一个常规的深度学习模型。

大型语言模型的训练方式或多或少与其他深度学习模型类似,除了它们规模巨大,需要数周甚至数月的时间才能训练到一个合理的状态,并且通常需要数百个GPU来完成。然而,它们通常依赖于相当典型的损失函数(如交叉熵)和优化器(如AdamW),尽管社区每天都在研究和开发新的优化器。

对齐问题与后续步骤

现在你的模型已经训练完成,你可能会想接下来该做什么?很可能,当你开始与你的大型语言模型交互时,你会发现它存在对齐问题

如果你不熟悉大型语言模型中的对齐问题,其本质可以归结为几个方面:

  • 准确性:模型是否准确?
  • 行为:模型的行为是否符合我们的期望?它是否有毒?是否表现出负面偏见或任何会削弱我们期望性能的偏见?
  • 幻觉:当我们要求它尽可能符合事实时,它是否会编造情境和例子?或者,也许你希望它尽可能有创意,而它在这方面也不擅长。

无论如何,对齐问题仍然是一个持续研究的领域,人们正在探索不同类型的工具和程序。

在构建好基础模型之后,你真正想做的是研究微调方法。在我们完成模块2之后,我们将讨论参数高效微调模型,这是一个新的发展领域,可以让你以非常高效的计算方式在不同任务上微调模型。

GPT发展简史

现在,让我们稍微换个方向,在探索这项吸引了所有使用者想象力的非凡技术时,谈谈GPT从第一代到第四代的发展历史。

总结

本节课中我们一起学习了构建大型语言基础模型的完整流程。我们从基础模型与微调的区别讲起,探讨了构建模型时在架构、目标任务、规模、数据和算力等方面的关键考量。接着,我们了解了如何根据任务选择架构、准备数据,并回顾了模型训练的基本过程。最后,我们指出了训练后模型可能面临的对齐问题,并简要介绍了后续的微调方向以及GPT系列模型的发展历程。

008:Transformer-1.7 生成式预训练 Transformer (GPT) 🧠

在本节中,我们将聚焦于一个特定且广为人知的Transformer家族——OpenAI的GPT(生成式预训练Transformer)。我们将回顾GPT从第一代到第四代的发展历程,探讨其架构演变、数据变化以及规模扩大的原因,并理解ChatGPT如何在此基础上构建。


GPT的起源与发展

上一节我们介绍了Transformer的不同架构类型。本节中,我们来看看其中最著名的生成式模型系列——GPT。

ChatGPT于2022年11月发布,它是GPT-3或GPT-3.5的微调版本。作为首批基于大语言模型的聊天机器人之一,ChatGPT获得了广泛赞誉和使用,成为了历史上普及最快的技术之一。其本质是一个基于解码器的Transformer模型应用。

生成式预训练Transformer(GPT)家族是在2018年Google发布“Attention Is All You Need”论文后,被研究和发布给更广泛社区的模型系列。

  • GPT-1 审视了原始Transformer的编码器-解码器架构,但决定只专注于解码器模型,即架构的右侧部分。
  • 后续的GPT-2、3和4家族基本依赖于相同的架构,只是规模变得更大,并在更多数据上进行了训练。
  • 虽然存在一些重要的创新和巧妙的改动使得模型扩展成为可能,但核心的基础设施和架构与我们讨论过的内容一致。

以下是GPT系列的关键参数演变:

模型 发布时间 核心数据源 参数量 关键特点
GPT-1 2018 Book Corpus 1.17 亿 12个Transformer块,解码器架构起点
GPT-2 2019 WebText 最大15亿 词嵌入维度增至1024,Transformer块增至48个
GPT-3 2020 WebText2 1750亿 Transformer块与模型嵌入尺寸再次翻倍,擅长少样本/零样本学习
GPT-4 2023 未公开 传闻约1万亿(混合专家模型) 具体细节未公开,性能与能力大幅提升

关于GPT-4,OpenAI尚未公布其结构信息。有推测认为它采用了混合专家(Mixture of Experts) 方法,由多个较小的模型(如2200亿参数)组合而成。我们将在模块3中详细探讨混合专家技术。


深入GPT架构:为何模型越来越大?

我们观察到GPT-1、2、3、4的规模持续增长。这主要是因为层数(Transformer块的数量) 不断增加。虽然模型维度(如词嵌入大小)也在增大,但其缩放速度不如层数显著。

增加更多层数的原因在于,这能让注意力机制与位置前馈神经网络协同工作,使模型能够“看到”文本中越来越多层面的信息。

我们可以类比卷积神经网络(CNN):

  • 浅层:关注边缘等基础特征。在Transformer中,早期的注意力层可能关注词序、词性、基本句子结构。
  • 中层:关注复合特征。在Transformer中,模型开始关注文本中不同短语之间的意义和关系,而不仅仅是单个句子内部。
  • 深层:关注高级、抽象的概念。在Transformer中,最后的注意力层可以关注语篇结构、情感和复杂的长期依赖关系。

随着当今模型上下文长度不断增加,也需要更多的层数来关注和理解更长的文本序列中丰富的信息。因此,GPT-4参数量可能达到万亿级别,这源于层数、模型维度和注意力头数的共同增长。

模型总参数量受多个因素影响,可用以下公式概念化理解:
总参数量 ≈ (层数 × 每层参数量) + 词嵌入参数量
其中,每层参数量主要由注意力矩阵前馈神经网络的权重构成。


GPT的训练与数据演进

现在,让我们更具体地谈谈GPT的训练,特别是其数据演变。

以下是各代GPT使用的关键数据集:

  1. GPT-1:Book Corpus

    • 包含约7000本未出版书籍,涵盖多种体裁。
    • 约有8亿单词,主题和风格多样。
    • 如果你运行GPT-1,会发现其输出带有一定的“文学风格”,但整体能力有限。
  2. GPT-2:WebText

    • 首次在大语言模型中使用公开爬取的网络数据集。
    • 数据量远超Book Corpus,涵盖了人们在互联网上讨论的广泛内容。
    • 数据规模达45TB,这要求团队进行大量去重过滤低质量网页的工作。数据清洗至今仍是构建高质量模型的关键挑战。
  3. GPT-3:WebText2

    • 比WebText更大、更多样化。
    • 即使参数量相同,更好的数据也使得GPT-3在各项任务上的性能显著超越GPT-2。

展望未来,随着大语言模型的发展,我们需要寻找新的数据来源,例如视频转录文本或其他合成数据源。


模块总结与架构选择

至此,我们已经了解了GPT从第一代到第三代的发展历程及所需的各项创新与改变。现在,让我们整体回顾一下在Transformer架构学习上的收获。

在本模块中,我们深入探讨了Transformer及其构成各种大语言模型的基本构建块。

我们学习了:

  • Transformer块如何结合注意力机制位置前馈神经网络来丰富输入向量的信息。
  • Transformer可以构建成编码器模型(如BERT)、解码器模型(如GPT)或编码器-解码器模型(如T5)。
  • GPT从一代发展到四代,需要在架构上做出细微调整,并在数据方面进行巨大变革。

需要记住的是,虽然我们希望将这些模型作为基础或基石模型进行训练,但若想针对手头的具体任务发挥其最大效能,通常需要进行某种形式的微调,以达到最先进的性能。

不同的Transformer架构各有优劣。选择哪种模型取决于你的具体任务和可用资源。

以下是主要架构类型的简要对比:

  • 编码器模型(如BERT)
    • 优点:擅长理解任务(如文本分类、情感分析、命名实体识别)。通常计算效率较高。
    • 缺点:不直接用于生成文本。
  • 解码器模型(如GPT)
    • 优点:擅长生成连贯的文本(如对话、创作、代码生成)。
    • 缺点:在需要深度理解输入文本的任务上可能不如编码器模型。
  • 编码器-解码器模型(如T5)
    • 优点:适用于需要同时理解和生成的任务(如翻译、摘要、问答)。
    • 缺点:架构相对复杂,训练和推理成本可能更高。

如果你的目标是情感分析或需要严格控制数据,且计算资源有限,那么使用BERT或T5可能比直接调用庞大的GPT-4/5更合适。建议你花时间研究不同框架的优缺点,以选择最适合自己需求的模型。


本节课中,我们一起学习了:

  1. GPT(生成式预训练Transformer)系列模型的发展历史与关键参数。
  2. GPT模型规模(尤其是层数)增大的原因及其对模型理解能力的影响。
  3. 各代GPT训练所使用的核心数据集及其演进过程。
  4. 不同类型的Transformer架构(编码器、解码器、编码器-解码器)的优缺点及适用场景。

现在,让我们进入实践环节,在Notebook中探索如何从零开始构建我们自己的微型Transformer模型。🚀

009:模块 1-Transformer-1.8 笔记本 📓

在本课程的第一个笔记本中,我们将深入学习如何构建自己的Transformer模型。我们将从输入文本如何从普通英文格式化为词嵌入开始,然后添加位置编码。接着,我们将构建一个简单的单层解码器Transformer块,并逐步构建一个能够解码文本的多层Transformer。最后,我们将查看Hugging Face上预训练的GPT2模型,并学习如何用它生成一些结果。

让我们开始吧。首先,确保集群正在运行,然后点击命令单元格运行课堂设置。请确保在本课程的所有笔记本中都首先运行此设置,以确保所有配置正确。

在运行设置的同时,让我们看看将要导入的一些库。我们将使用PyTorch及其神经网络功能,还会用到math、time、numpy以及几个绘图库。设置完成后,现在导入我们的Python库。

从文本到词嵌入 🧱

首先,我们来看如何将自然语言编码成Transformer所需的正确格式。

让我们从一个简单的句子开始:the quick brown fox jumps over the lazy dog

第一步是将句子分割成单个单词。我们这里假设每个单词代表一个唯一的标记。实际上,大多数标记化方法(如字节对编码)使用子词作为单个标记,但这里使用单个单词,所有学习原理是相同的。

我们将分割每个单词,并将其存储在word_to_id字典中。在打印之前,我们还需要将文本转换为每个单词的索引。这将构建我们Transformer输入的上下文向量。

我们将使用PyTorch构建一个张量,并利用word_to_id字典,首先遍历所有创建的单词,将ID存储到input_ids中。

现在,我们将使用PyTorch的神经网络嵌入函数创建词嵌入,并稍后设置嵌入大小。我们将通过一个简单的函数返回嵌入。要了解更多信息,请查看PyTorch的嵌入函数。

现在设置一些变量:嵌入大小(模型的维度)设为16。然后,使用第9行定义的get_word_embeddings函数创建所有词嵌入。接着打印这些词嵌入。

运行此单元格,查看所有输出内容。

首先,输出的是句子分割后的对象,每个单词对应一个索引。然后可以看到我们设置的PyTorch张量,它标记了每个单词(不一定按顺序)。实际上,这是为我们定义的顺序。接着,可以看到张量按照我们需要的顺序排列,例如索引8对应大写的“the”,下一个索引3对应“quick”,依此类推,构建出之前的句子。

下一个输出是词嵌入张量。由于我们将词嵌入维度设为16,这意味着句子中的每个标记或单词都有16个标量变量。例如,这里高亮的第一个向量就是对应单词“the”的词嵌入。可以看到,对于句子中的每个单词,我们都有一个对应的词嵌入向量。

添加位置编码 🧭

现在我们有了词嵌入,接下来定义位置编码。我们将使用《Attention Is All You Need》论文中提出的正弦位置嵌入函数。

我们将创建一个位置编码向量,其长度等于我们将要找到的最大序列长度。我们还需要为此特定项设置所有其他组件,包括一个除数项(指数除以模型维度)。然后定义位置编码向量,首先将其初始化为零,然后为每一项添加正弦和余弦版本。

为了更好地理解,我们可以绘制位置编码的作用。Transformer架构本身不像LSTM或RNN那样知道输入标记的顺序,位置编码的作用就是区分每个标记,让模型对标记的相对位置有感知(不一定是绝对位置)。

我们可以绘制位置编码的热图。在输出中,Y轴是标记,X轴是位置编码向量。可以看到,对于标记0,我们得到特定的位置编码向量分布,并且可以看到序列中每个标记的数值如何变化。这些变化为注意力和神经网络提供了理解标记在序列中位置的信息。这个位置编码信号告诉Transformer的不同部分,它正在查看的标记在序列中的位置。

通过阅读此处提供的信息,可以了解更多关于位置编码图的信息及其生成方式。计算序列中位置p、给定嵌入空间维度i的位置编码值的方法如下(记住我们将其设为16,因此最终得到16个不同的位置编码变量)。

组合词嵌入与位置编码 ➕

下一步是将我们创建的词嵌入与刚刚展示的位置编码结合起来。这将得到最终的嵌入向量,以便我们开始将其输入到Transformer块中。

现在,在将嵌入向量输入注意力机制之前,我们得到了其最终状态。在下一步中,我们将从头开始构建解码器,构建一个单层Transformer(仅用于演示目的,性能不会特别好)。然而,通过从头构建,你将能很好地了解实际构建一个Transformer需要什么。

构建单层解码器块 🔨

让我们使用PyTorch构建一个解码器块。我们将定义解码器块,它是Transformer的内部组件。Transformer还需要处理解码器块的输入和输出,但让我们从构建解码器块开始,这在讲座中也见过。

如今,在PyTorch中定义神经网络Transformer有一种相当标准的形式。我们将从定义__init__开始,它接受一些输入变量:模型维度、注意力头数量、神经网络隐藏层维度大小以及是否使用dropout及其比率。

对于解码器块,我们将使用来自PyTorch库的多头注意力,并为其提供模型维度、头数和dropout值。我们还将有一个归一化层、一个dropout层,然后在这里设置两个全连接神经网络层。这将是我们的位置前馈神经网络,输入是模型维度,然后是一个内部隐藏层维度大小(通常是模型维度的四倍左右)。接着是第二个线性全连接层,将维度降低回模型维度。然后是我们的归一化层和最终的dropout。这就是我们定义解码器块初始化的方式。

下一步是定义forward方法,即我们如何获取模型并进行推理。forward方法将接收两个输入:x(输入张量,即所有词嵌入的向量或序列)和target_mask(我们稍后会讨论掩码的作用)。

查看forward函数,首先调用自注意力,传入三个x的副本以创建查询、键和值向量,并设置注意力掩码。然后,将注意力的输出加入dropout,并将结果存储到向量的新版本中。接着,将向量通过归一化层,然后将归一化的输出传递给我们多层感知机的第一个线性层。再将该输出作为第二个多层感知机层的输入。这给出了位置前馈神经网络的输出。我们再添加一个dropout,最后对x进行归一化并返回x

这段代码代表了Transformer单层(即单个解码器块)中发生的一切。如果是编码器-解码器架构,还需要考虑交叉注意力,但为简单起见,这是解码器模型中解码器块的工作方式。

构建位置编码器类 🏗️

现在,让我们继续构建完整Transformer模型所需的另一个组件:使用位置编码器。我们之前看到了如何进行位置编码,但这次我们要将其转换为一个函数和类,以便在Transformer神经网络中向前推进时使用。

我们将大致采用之前的做法,但将其格式化为Transformer可用的形式。我们将创建一个dropout类,并拥有一个位置编码函数(这确实是类本身的名称)。我们将创建位置编码(初始化为零),然后创建之前见过的除数项。接着,我们创建两个维度的向量:正弦和余弦值,这些值将赋予我们的位置编码向量。然后,通过转置我们刚刚定义的函数来返回位置编码。

forward前向传播中,它非常简单,只需接收输入x,然后加上我们在第5到50行之间定义的位置编码。

构建完整Transformer解码器 🧩

现在我们有了位置编码器设置和解码器块设置,可以构建完整的Transformer了。我们将首先创建一个嵌入层,添加位置编码,然后将编码后的序列发送到Transformer解码器块。接着,将解码器块的输出传递到带有softmax的最终层,以分类要输出的下一个标记。

让我们看看Transformer解码器类。我们将接收多个输入:词汇表(softmax函数需要)、模型维度、注意力头数量、位置前馈神经网络的隐藏层维度大小以及模型的dropout比率。

在这里,我们可以看到嵌入层的构建、位置编码器、Transformer块,然后是输出线性层和softmax层。在forward函数中,我们接收x(标记化的输入),它通过嵌入层,也通过编码器(即添加到之前的嵌入层)。然后创建目标掩码。接着将目标掩码和编码序列x传递给Transformer块。获取输出并通过线性层,再将该输出通过softmax,然后返回它认为合适的词汇表中任何标记的输出。

理解掩码的作用 🎭

现在让我们谈谈掩码,因为解码器中几乎其他所有内容我们都讨论过了。

需要记住的是,对于解码器,它不允许看到序列中它之后的任何标记。回想一下,解码器Transformer的目标是预测序列中的下一个标记。对于编码器模型(如BERT),注意力机制可以查看标记在序列中的左右两侧,这使其对序列中包含的内容有很好的理解。然而,对于解码器,根据定义,它不知道下一个单词是什么,它必须生成它。因此,掩码的目标是确保在注意力机制中,未来的信息被屏蔽掉。

让我们看看具体如何操作。回想一下在注意力中,我们使用softmax函数。我们查看softmax计算的相关分数。如果我们创建一个像这里看到的掩码,其中掩码值几乎是无穷大,那么当我们对其应用softmax时,会得到0值。因此,对于当前查看的值之后序列中的任何标记,都不会给予任何注意力。

如果你想了解更多关于不同掩码以及Transformer如何使用掩码进行不同处理的信息,请查看附加资源中提供的一些链接。

测试单层Transformer 🧪

现在我们有了掩码、解码器、嵌入和编码,让我们制作第一个完整的Transformer。我们将定义词汇表大小为1000,使用维度512,仅使用单个注意力头(不拆分为多头),为位置前馈神经网络使用2倍的隐藏层维度大小,dropout比率为10%,使用10层,上下文长度为50,批处理大小为1。

创建Transformer解码器。创建一个随机化的输入张量(实际上只是使用一个没有真实词汇表背后的词汇表大小),稍后我们将进行调整,看看是否能将其连接到真实的词汇表。我们将基本上只是将输入张量的信号发送到模型的输出。

运行此单元格。现在,我们得到了输出张量c,它包含了预测的单词索引。可以看到我们有一个大小为50的torch张量。这在测试我们可以通过Transformer发送信号的意义上是有用的。然而,你可能注意到我们这里有一个层数的变量,但实际上并没有将其传递给我们的Transformer解码器。这是因为我们创建的是单层解码器。在下一节中,我们将构建一个多层模型,这将允许我们使用这个层数变量。

在此之前,让我们看看这个模型有多少参数。可以看到有300万个参数。

再看看我们从softmax层刚刚创建的输出。在X轴上,我们有1000个标记的单词索引。许多解码器的目标是选择它认为下一个出现概率最高的标记。你可以看到这里在400和100左右有几个选项,这将是下一个标记的良好候选(无论在这个假设情况下是什么)。但也可以看到,许多其他标记也有大约70%的概率。因此,可以说这个模型具有较高的困惑度,它可能对应该选择哪个标记有一定的感知,但不够好。在一个困惑度低的模型中,你会看到一个非常平坦的概率分布,在一两个好的下一个标记处有一个峰值。有时,你可能甚至故意不选择最明显的下一个单词,这就是温度参数的价值,你有时会在与大型语言模型交互时看到这个选项,这意味着通过选择不一定是最明显的下一个标记,赋予它一定的可变性或创造性。

构建多层Transformer解码器 🏢

为了引入多层概念,我们实际上可以重用已有的许多代码。在这个新的类定义中,可以看到之前的词汇表大小,以及一个新变量:层数。

要构建多层Transformer,我们将遵循相同的步骤:首先从PyTorch的嵌入开始,然后是之前构建的位置编码器。现在,我们只有一个解码器块列表,我们将根据层数进行复制。所有解码器块在架构上最初是相同的。其思想是,在训练期间,在反向传播过程中,每个Transformer块(每个解码器块)中注意力头和神经网络的权重都会不同,因为它能越来越好地预测正确的下一个单词。

在通过所有不同的解码器块后,输出被送入线性层,然后使用softmax选择最佳的下一个标记。

我们的forward方法看起来大致相同。我们有嵌入层,接收x,通过嵌入层,再通过位置编码器。然后进入一个完整的循环,获取目标掩码(在选择标记时也需要更新)。然后将x通过Transformer块。一旦我们连续完成此操作,并且x通过所有块变得越来越丰富,我们便将丰富的向量或向量序列x输入线性层,最后通过softmax输出。

运行此单元格以定义新的多层Transformer解码器,然后构建一个新的。我们将设置词汇表大小为10000,模型维度更大,坚持使用单头,定义隐藏层维度为模型维度的四倍,保持10%的dropout,选择10层,上下文长度为100。

像之前一样,创建一个随机化的输入张量,代表一系列词向量。然后使用刚刚构建的类实例化模型,并计算该模型实际拥有的参数数量。我们还将再次查看概率分布,看看这个模型在选择下一个单词方面表现如何。

这将需要一点时间来运行,因为它现在必须构建一个更大的神经网络。可以看到,现在有5亿个可训练参数,而不是几百万。我们可以通过改变词汇表大小、模型维度、层数等来改变这个数字。但请注意,一旦超过10亿左右,计算资源将开始抱怨,因为会遇到内存不足错误。

查看这个特定模型的分布,可以看到它的困惑度实际上低得多,并且对要选择的下一个标记相当确定。这些都是随机变量、随机参数,所以不要对此解读太多。然而,这只是如何为词汇表获得不同输出分布的一个例子。

看看模型本身,看看PyTorch将其存储为什么。可以看到嵌入层和带有dropout的位置编码器。然后是我们所有解码器的模块列表,从解码器块0一直到9(因为我们有10个解码器块,从0开始)。接着是线性层,输入为2048(模型维度),输出为我们词汇表的大小,最后是我们的softmax函数作为最终部分。

连接真实词汇表 📚

我们已经用随机向量做了很多工作,现在让我们更进一步,为模型添加一个真实的词汇表。

我们将拥有一个真实的词汇表,因此不会将该值设为一个数字,而是计算词汇表中不同单词的数量。模型维度设得更低,仅为100。保持一些值相同,这次使用更少的层,上下文长度也更小。

现在,我们将通过放入一些真实的词汇来增加一些真实感,添加一些英语中的分词和简单单词。我们将根据这个词汇表创建一个字典。这将允许我们获取一个句子并将其转换为该词汇表的索引。它还允许我们实际选择其中一个单词并将其作为真实输出生成。

像上次一样创建模型。创建一个输入序列,它只是词汇表中的一堆单词。然后根据这个序列创建输入张量,可以看到我们实际上是根据之前定义的word_to_id函数创建索引。

然后,我们将使用这个序列作为起点生成一系列单词。我们将运行一个循环。这就是ChatGPT或其他类似模型的工作方式:你从一个起始序列开始,运行一个完整的循环,然后它会预测下一个单词。它会将刚刚预测的单词添加到生成的序列中,然后重复这个过程。

让我们看看是否能让这个模型“说话”。现在可以看到它一个接一个地给我们单词,就像在和我们说话一样。你可能能看出这没有任何意义,很多单词都是无意义的,这是因为我们实际上还没有训练这个模型来了解任何关于语言的知识。事实上,我们给它的序列也是完全无意义的。然而,我们正在逐步构建模型的复杂性,以便你能确切地看到这些模型的行为方式。

使用预训练模型:GPT-2 🤖

假设我们能够创建一个大型词汇表,收集了所有需要的计算资源,并通过反向传播使用PyTorch训练了我们的模型。我们可以利用前人已经完成的一些工作,即利用预训练的模型权重。

我们将从Hugging Face获取Transformers库并下载GPT-2。运行此单元格以下载代码,其中包括GPT-2的权重和分词器。这需要一点时间运行。

现在有了这个,我们可以定义一个提示。这类似于之前的序列,但由于我们现在处理的是实际的语言和训练过的模型,我们倾向于将其更多地称为提示。

我们将说:“这是一个关于大型语言模型的讲座。我已经思考过了。”然后让模型决定接下来要说什么。

具体操作如下:我们将基于刚刚下载的分词器创建输入,这是小版本的分词器,因为我们使用的是最小的GPT-2版本。我们还需要注意力掩码,和之前看到的一样。我们还将有一些特殊的ID用于序列结束标记,这将告诉它何时停止生成额外的单词。这是基于在训练期间学习到的训练数据中这些序列结束的位置。

我们将把Transformer的输出添加到提示的末尾。现在,我们将通过完整的循环来获取模型的输出。输出将是一系列ID,然后使用分词器解码输出。接着,将生成的单词附加到我们开始的提示中。

让我们看看GPT-2会说什么。可以看到,这是我们开始的内容,而GPT-2补充的内容在语法和意义上似乎相当合理。它做得相当不错,但也要考虑到GPT-2是一个非常小的模型(特别是这个版本),因此它的理解、领会和复制更精细人类语言的能力可能有所欠缺,但我觉得在这种情况下它做得很好。

然而,我们可以更进一步。之前我们看到可以改变层数、注意力头数等变量,OpenAI在发布GPT-2时也这样做了,他们发布了不同大小的版本,其中最大的被称为GPT-2 Extra Large。让我们下载那个模型,看看它在相同提示下是否表现更好。

由于这是一个更大的模型,下载需要更长一点时间。现在,我们可以开始看看GPT-2 Extra Large能用我们为GPT-2 Small开始的提示做什么。

同样,我们将获取起始提示并对其进行标记化。然后将此提示输入模型,一旦获得模型的输出(它只会生成下一个标记),我们需要将该标记添加回提示中,然后再将其输入模型,这样它就会不断自我构建。

运行此单元格,看看GPT-2 Extra Large会说什么。可以看到,它输出的文本实际上更细致,流动更自然。这只是如何采用这些不同模型并应用这种类似语音行为的一个例子。这也是ChatGPT、Claude、Bard等所有基于聊天的工具正在使用的底层机制。

希望这个实验笔记本对你来说令人兴奋。在实验笔记本中,你将处理类似的内容,但在那种情况下,你将使用编码器模型来更深入地了解编码器类模型的工作原理。


本节课总结 🎯

在本节课中,我们一起学习了Transformer模型的核心构建块。我们从文本的标记化和词嵌入开始,理解了位置编码的必要性和实现方式。然后,我们逐步构建了单层Transformer解码器块,并扩展为多层解码器模型。我们探讨了掩码在解码器中的关键作用,并最终将理论付诸实践,连接了真实词汇表,并使用了Hugging Face的预训练GPT-2模型来生成文本。通过这个过程,我们深入了解了现代大型语言模型的基础架构和工作原理。

010:介绍 🎯

在本节课中,我们将要学习高效微调。微调是机器学习中一项强大的技术,它能让我们利用预训练好的基础模型,通过少量新数据来更新模型权重,从而使其适应特定任务或领域。

概述

上一节我们介绍了基础模型的概念。本节中,我们来看看如何让这些通用模型变得“专精”。微调正是实现这一目标的核心过程。它允许我们以一个在大规模数据上预训练好的模型为起点,通过我们自己的数据对其进行调整,最终得到一个为我们的具体应用量身定制的高质量模型。

什么是微调?

微调是指获取一个预训练好的深度模型,向其输入新数据,并根据这些(通常数量较少的)新数据更新其权重的过程。对于大语言模型而言,这项技术尤其强大。因为从头训练一个优秀的基础语言模型成本极高,所以业界普遍的做法是先在大量数据(如网络文本)上进行预训练,得到一个具备良好语言理解能力和通用知识的模型。

然而,这样的通用模型并未针对任何特定领域进行优化。通过微调,我们可以:

  • 向模型注入它之前未掌握的新信息(例如公司内部的机密数据)。
  • 将其定制为执行特定任务(如情感分类、翻译或信息提取)。

微调的重要性

作为使用语言模型的机器学习工程师,微调将是你的主要工作之一。在许多领域,如果你想获得最高质量的模型,微调几乎是必经之路。即使是对参数量较少的小模型(如 Llama)进行微调,其效果也可能优于未经调整的、更强大的通用模型(如 GPT-4)。这是因为微调让模型有机会吸收特定领域的知识。

例如,加州大学伯克利分校的 Gorilla 项目通过微调,训练模型学习使用各种软件工具和API。他们发现,经过少量微调后,模型能产生质量高得多的响应。这只是一个例子,在大多数领域,如果你希望模型表现更好,微调通常是最有效、甚至唯一能将其性能推向极致的主要工具。

高效微调技术

我们将讨论微调的一般原理、使其良好工作所需的条件,以及它的工作机制。此外,我们还将重点介绍一些令人兴奋的参数高效微调新技术。

这些技术的核心思想是:并非同时或同等地更新模型中的所有参数。其优势在于:

  1. 试错成本极低:你可以快速尝试。
  2. 有效的初步验证:即使你计划后续进行更昂贵的全参数微调,高效微调也能让你快速投入一些数据,验证模型在特定任务上是否有通过微调获得提升的潜力。

总结

本节课中,我们一起学习了微调的基本概念及其在定制大语言模型中的关键作用。微调是将通用基础模型转化为领域专家模型的核心手段。我们还了解到,参数高效微调技术能让我们以极低的成本开始实验和验证。在接下来的章节中,我们将深入探讨如何具体实施微调以及这些高效技术的细节。

011:高效微调概览 🧠

在本模块中,我们将学习什么是微调、为何需要微调,以及如何以参数高效的方式进行微调。我们还将探讨数据准备的最佳实践,因为数据是所有大语言模型的基础。

什么是微调?

在上一模块中,我们学习了Transformer的内部工作原理以及几乎渗透于所有语言模型架构的注意力机制。你可能还记得,大语言模型之所以“大”,既因为它训练于海量数据,也因为它拥有海量的可训练参数。这自然使得许多个人和组织难以从头开始训练一个大语言模型。因此,我们转向微调,这是一种只更新模型参数子集的方法。

在深度学习领域,微调是“迁移学习”的一种形式。迁移学习指的是我们将一个通用的预训练模型应用于一个新的、但相关的任务。一个常见的类比是体育运动:如果你会打网球,你可能更容易学会打排球,因为两者都需要力量。

微调属于迁移学习的范畴,它简单意味着我们对模型进行进一步的训练。有些人可能会区分微调和迁移学习,认为当我们改变或修改基础模型的架构(例如解冻基础模型的顶部几层并训练这些层,同时训练新添加的层)时,才称为微调。但实际上,这种区分并不必要,包括吴恩达和Andrej Karpathy在内的许多人通常都互换使用这两个术语。

你可以简单地将微调理解为:对基础模型进行更多训练,或者在更多或不同的数据上训练基础模型。

我们讨论的预训练模型也常被称为基础模型或基座模型,尤其是在大语言模型的语境下。一般来说,利用这些基础模型有三种方式:

  • 直接使用:按原样使用预训练模型,例如T5、GPT-4、BLOOM、GPT等。
  • 作为特征提取器:使用基础模型的输出(如嵌入向量)作为特征,输入到另一个机器学习模型中以生成预测。这是BERT嵌入的一个非常常见的用例。
  • 微调:这是我们本模块的重点。我们可能只更新基础模型的顶部几层,或更新所有层,或在模型使用前添加一些层,以生成我们期望的预测。

我们将看一个此类中最新的语言模型示例,一个名为GOAT的模型。顾名思义,它是另一个农场动物名称。它是Llama的微调版本,专门用于执行算术运算。我们将在稍后回到GOAT模型。

为何需要微调?

我们希望在特定下游任务上获得更好的性能。我们可以针对特定任务的数据微调一个预训练语言模型,使其适应特定的风格和词汇。这也有助于我们实现监管合规。

但这个想法并不新鲜。事实上,早在2018年,Jeremy Howard和Sebastian Ruder就发表了一篇论文,介绍了可用于任何NLP任务的微调技术,其中之一就是通过逐步解冻层来为目标任务微调分类器层。

简而言之,微调意味着我们更新模型的权重或参数。通常,当我们更新更多层时,会获得更好的模型性能。典型情况下,当我们进行全参数微调(即更新所有模型权重)时,我们会为每个任务生成一个独立的模型。在右侧的图片中,你可以看到我们可以在不同的数据集上微调BERT,例如SQuAD(问答数据集)、命名实体识别数据、MultiNLI(多体裁自然语言推理数据集)。每个微调过程都会给我们一个新模型,因此在部署时,我们为每个任务服务一个模型。

但这意味着我们需要处理同一个基础模型的许多副本,这在当今模型远大于这个1.1亿参数的BERT模型时尤其不受欢迎。虽然如今磁盘空间很便宜,但像Llama这样专为对话设计的模型家族,单个模型就占用近500GB的磁盘空间。

因此,在部署时我们需要考虑的另一个问题是:我们是否有那么多针对特定任务的输入请求?其次,全参数微调可能产生一个不良后果,称为灾难性遗忘,即我们之前训练或利用的基础模型已经忘记了如何执行其他预训练任务。

全参数微调成本高昂,如何避免?

有两种方法:一种是小样本学习,另一种是参数高效微调

在深入探讨参数高效微调(我们常缩写为PEFT)之前,我们先简要了解一下什么是小样本学习。

小样本学习是指我们在文本提示中提供新任务的几个示例。我们通常在不想进行微调,或者缺乏带标签的训练数据,但可以轻松写出几个示例供大语言模型学习时使用这种方法。

这种以文本形式写出指令的过程称为提示,我们迭代地编写不同的提示以找到传递给大语言模型的最佳提示,这个过程称为提示工程。因此,开发提示的过程也可以称为提示设计。对于本模块尤其重要的是,我们将提示工程称为硬提示调优离散提示调优。所以请记住,每当我提到硬提示调优或离散提示调优时,我实际上是在谈论小样本学习。

我们应该区分小样本学习和微调的主要一点是:小样本学习不更新任何模型权重,而微调更新模型权重。小样本学习也常被称为上下文学习,因为我们为大语言模型提供了一些上下文供其学习或在生成输出过程中利用。

这种小样本学习的优点是:不需要带标签的训练数据;我们不需要为每个新任务创建模型的副本,这可以极大地简化我们的模型部署过程;可能最棒的优势是,我们传入的文本提示可以非常容易理解,因为这些是我们人类精心设计并传递给大语言模型的输入文本。

但其缺点是:尽管我们获得了能够解释文本提示的优势,但一切都是手动的且劳动密集;提示也可能高度特定于模型,这意味着当你换用不同的模型时,可能需要完全重新开发提示;我们还经常遇到上下文长度限制。如果要添加更多示例,那么留给指令的空间就会减少。但如果使用具有更高或更大上下文窗口的模型,那么在服务时也会带来更高的延迟。最近也有研究论文表明,更长的上下文窗口可能不是未来大语言模型的解决方案,因为大语言模型也倾向于忘记我们提供的上下文中间部分。

最后,这确实是我们转向微调的原因:即使有小样本学习,模型性能可能仍然不尽如人意。

微调优于小样本学习的示例

这里有一个微调优于小样本学习的例子,即我们的GOAT模型(70亿参数)。它的基础模型是Llama,并在100万个合成数据示例上进行了训练。我们发现,在算术任务上,其准确率优于此处的小样本学习模型Palm(5400亿参数)。它在算术任务上也优于GPT-4。事实上,GPT-4在所有算术任务上表现都不太好。我们还看到,这个GOAT模型可以在一个算术基准测试上达到最先进的结果。

这是一个属于监督指令微调领域的模型,使用一种名为LoRA的PEFT技术进行训练,我们将在后面几节深入探讨这种技术。

在继续之前,我想指出关于GOAT的两个重要事项:首先,它是一个指令微调模型;其次,它可以在服务时执行多个任务。第一个任务是加法,第二个任务是减法(但混合了一些自然语言),第三个任务是乘法,第四个任务是除法。你可以看到,在服务时,不需要为这里的每个数学任务生成一个模型,我们使用一个模型来处理我们提供给大语言模型的所有算术任务。

现在,让我们看看其他几个流行的指令微调多任务大语言模型的例子:

  • FLAN:代表“微调语言网络”。其基础模型是一个1370亿参数的模型,并在超过60个具有不同任务类型的NLP数据集上进行了微调。不同FLAN风格模型的例子包括T5-FLAN、FLAN-T5或FLAN-PaLM。
  • Dolly:其基础模型是Pythia(120亿参数模型),并在15,000对提示和响应上进行了微调。

微调模型的目标

现在你已经看到了几个微调模型的例子,让我们回顾一下我们希望通过这些微调模型实现的目标:

  1. 高效训练:通常,全参数微调对许多组织来说在计算上是难以承受的,尽管这能带来最佳的模型性能。因此,对于预算较小的组织,折衷方案是进行一些微调,但不是全参数微调。
  2. 高效的服务和存储:我们希望在部署时,这个大语言模型能够进行多任务服务,而不是为每个任务服务一个模型。

因此,我们将在下一节进入参数高效微调的领域,并介绍一些最流行的技术。

总结

本节课中,我们一起学习了微调的基本概念。我们了解到微调是迁移学习的一种,用于在特定任务上提升预训练大语言模型的性能,但全参数微调成本高昂且可能导致灾难性遗忘。作为替代,小样本学习不更新权重但性能可能有限且依赖人工设计提示。因此,参数高效微调成为了平衡性能与成本的关键技术,我们将在后续章节详细探讨。

012:高效微调-2.3 PEFT与软提示 🧠

在本节中,我们将深入探讨参数高效微调(PEFT)的概念,并重点介绍其“加法”类别中的一种核心方法——软提示(Soft Prompt)。我们将了解如何通过添加可训练的“虚拟令牌”来微调模型,而无需更新庞大的基础模型参数。

概述:什么是参数高效微调(PEFT)?

参数高效微调旨在以最小的参数量更新来适应模型到特定任务。它通常缩写为PEFT。参数效率涵盖多个方面,包括存储内存计算开销以及最终的模型性能

PEFT主要分为三类:加法(Additive)、选择性(Selective)和重参数化(Reparameterization)。在本课程中,我们将聚焦于第一类和第三类,即加法重参数化方法,因为选择性方法的研究表明其效果通常不如前两者。PEFT是一个活跃的研究领域。

本质上,这些方法作用于Transformer核心模块。有些方法专门针对负责信息传递的查询(Query)、键(Key)、值(Value)权重矩阵进行操作。

从硬提示到软提示

上一节我们介绍了手动编写文本提示,这被称为硬提示离散提示。本节内容的核心是如何去除提示工程中的人工环节,转而使用软提示

简单来说,添加软提示意味着我们在输入中添加虚拟令牌

请看下图,我们有文本输入(通常称为文本嵌入),例如“These chips are tasty”。软提示意味着我们现在添加的是虚拟令牌。目前,你只需知道这些虚拟令牌是任务特定的。因此,当你听到“软提示”时,应立刻将其等同于“虚拟令牌”。

软提示的维度与我们的输入嵌入向量相同。在微调过程中,我们将这些可训练的虚拟提示(或称虚拟令牌、软提示)与输入嵌入向量进行拼接。我们称此为提示微调而非模型微调,因为我们只更新粉红色部分所示的提示权重。

深入理解虚拟令牌

硬提示工程面临的挑战在于其高度依赖人工、费力且容易出错。在软提示方法中,我们不再依赖人类去精心设计完美提示,而是让模型通过微调自行寻找最佳提示。

我们首先随机初始化一个由随机数构成的嵌入向量。这个向量的维度与输入嵌入相同。由于这些随机初始化的嵌入向量是完全随机的,它们不属于任何词汇表,我们不知道它们对应什么文本。

在右侧的图表中,你可以看到真实的单词输入令牌可以在嵌入空间中被可视化,并且我们知道它代表什么词。但当我们观察虚拟令牌在嵌入空间中的位置时,我们知道它占据了某个位置,却不知道它对应什么文本。这有点像比特币:我们知道它像货币一样运作,但我们无法像触摸现金一样触摸它,甚至不知道它长什么样,但它确实存在并起作用。

在一些研究中,人们也尝试用离散提示来初始化这些虚拟令牌。这意味着我们为模型提供一些最简单的离散提示作为起点,然后模型在训练过程中可以自由更新这些嵌入向量。例如,我的离散提示可以简单到是“classify this sentence”或“translate this sentence”这三个词。然后,这些离散提示会在模型微调过程中被自由更新。这种用离散提示初始化的方法也被称为知情初始化

但有趣的是,相关论文发现,随机初始化(将提示设置为随机数)的效果几乎与提供简单的文本提示输入(即知情初始化)一样好。在后续的实践环节中,我们将尝试这两种初始化方式,以加深对随机初始化和知情初始化的理解。

提示微调 vs. 全参数微调

现在,让我们通过对比提示微调和全参数微调的场景,来具体看看提示微调涉及什么。我们继续使用情感分类的场景。现在,我们不止有一个输入,而是有多个输入,因此我们定义任务批次大小为4,因为我们总共有四个情感需要分类。

请注意,虚拟令牌本质上只是随机数,因此不对应任何特定的词汇或文本。

在全参数微调中,我们基于损失通过反向传播更新模型权重,并且整个基础模型的权重是解锁未冻结的,以便在反向传播过程中更新所有权重。

相比之下,在提示微调中,如上图所示,基础模型的权重被完全冻结。因此,当模型进行前向传播和反向传播时,我们只更新此处的虚拟令牌权重。这些虚拟令牌或软提示基本上是通过反向传播学习到的,并被调整以吸收我们提供的任何数量标签提示的信号,梯度更新仅应用于这些虚拟嵌入向量。

回顾一下,当我们考虑手动提示工程或编写离散提示时(就像在少样本学习中,我们提供一些示例作为上下文传递给LLM),我们是在具有固定嵌入的令牌的文本空间中搜索。而在提示微调中,我们是在嵌入空间中搜索,以找到LLM应该接受的最佳提示表示。这一切最大的好处是,模型能自动学习提示的最优表示,从而消除了人工设计离散提示的繁琐工作。

多任务处理与模型规模的影响

到目前为止,我们的例子只包含了情感分类这一个任务。但如果我们有多个任务,包括问答、翻译等,该怎么办?这不是问题,因为我们可以将每个任务视为一个提示。对于每个任务,我们会指定一个完全不同的软提示。这样,在部署时,我们不需要换入换出基础模型或基础模型,我们只需要换入换出学习到的虚拟令牌。

另一个好处是,模型现在可以处理一个更大的混合任务批次。这里的任务批次可以包含多个请求,你现在可以看到它不仅包含情感分类,还可以接受问答请求或翻译请求。一个批次中捕获的各种任务就是我们这里所说的混合任务批次。

研究人员发现,对于更大的模型(特别是参数规模超过110亿的模型),提示微调的性能与全参数微调相当。SuperGLUE分数是一个基准测试,包含各种任务,如回答布尔问题或阅读理解问题,分数越高越好。我们可以看到,当模型较小时,软提示调优效果不佳,这直观上是合理的,因为较小的模型学习能力有限。但当模型变大时,我们可以看到全参数微调与提示微调的性能实际上非常相似。

此外,提示长度对于大模型的性能也没有太大影响。什么是提示长度?你可以在这个例子中看到,当我们初始化虚拟提示时,这里只有两个嵌入向量,所以提示长度为2。大模型即使在提示长度为1时也表现良好,事实上,随着我们增加提示长度,模型性能似乎存在收益递减。提示长度为100(此处绿色线)似乎是最佳点,但也要注意SuperGLUE分数的置信区间也相当宽,因此软提示调优存在性能不稳定的问题。

提示微调的优点与缺点

让我们回顾一下提示微调的优点:

  • 与少样本学习需要人工进行提示工程不同,我们不受可传递给模型上下文的示例数量的限制。
  • 我们消除了手动设计最佳提示的挑战,可以利用反向传播让模型帮助我们找到任务特定虚拟提示的最佳嵌入表示。
  • 我们不需要拥有同一模型的多个副本,可以实现多任务服务。
  • 最后,它对领域偏移具有弹性。这指的是,由于我们冻结或锁定了基础模型的权重,提示微调防止了模型修改其对语言的一般理解,从而降低了模型在微调数据上过拟合的能力。相比之下,我们学习到的软提示参数数量要少得多,因此在推理时对任务内部的变化更具泛化能力。

当然,正如我们之前看到的,提示微调也有一些缺点:

  • 你可能一直在想,我们怎么知道这些虚拟令牌是什么?答案是,我们并不确切知道。解释它们的最佳尝试是使用一些余弦距离或其他距离度量来找到最近的邻居,从而估计或猜测它们可能代表什么词。因此,与离散提示相比,它的可解释性要差得多。
  • 第二个缺点是我们刚才看到的,提示微调可能存在性能不稳定的问题。

在软提示类别下,还有另一种非常相似的方法叫做前缀微调。前缀微调与提示微调非常相似,它也允许任务特定的提示,其中每个前缀代表不同的任务。唯一的区别是,这些前缀层被添加到每个Transformer块中,而不仅仅是输入嵌入层。

这结束了我们对PEFT加法类别(即软提示)的讨论。在接下来的课程中,我们将讨论重参数化方法,特别是LoRA。

总结

本节课我们一起学习了参数高效微调中的软提示方法。我们了解到,软提示通过添加可学习的虚拟令牌来替代人工设计的文本提示,从而在微调时只需更新少量参数,而保持基础模型权重冻结。这种方法特别适合大模型和多任务场景,虽然存在可解释性较差和性能可能不稳定的缺点,但它极大地提升了微调的效率并降低了资源需求。下一节,我们将探索另一种高效的微调技术——LoRA。

013:高效微调-2.4 重参数化-LoRA 🧩

在本节中,我们将讨论当前参数高效微调(PET)中最流行的技术之一:LoRA。LoRA是一种重参数化方法,它利用低秩表示来最小化可训练参数的数量。我们将涵盖少量线性代数知识,以剖析低秩表示的含义。

概述

我们将学习LoRA(Low-Rank Adaptation)的核心思想:通过将权重更新矩阵分解为两个低秩矩阵,来大幅减少微调时需要更新的参数数量,同时保持甚至提升模型性能。

线性代数基础回顾:矩阵的秩

在深入LoRA之前,让我们简要回顾一下线性代数的基础知识,以理解什么是矩阵的秩。

指的是矩阵中线性无关列的最大数量。当你有一个满秩矩阵时,意味着该矩阵没有任何冗余的行或列,这些行或列可以基于其他列的组合来表达。

请看下面的例子:

矩阵:
[1, 2, 3]
[3, 6, 9]

由于第二列和第三列可以通过第一列乘以一个常数得到,它们不是线性无关的;因此该矩阵的列秩为1。对于行也是如此,因为第二行可以通过第一行乘以3得到。

本质上,我们试图确保矩阵中的信息表示没有任何冗余。

LoRA的工作原理

上一节我们回顾了矩阵秩的概念,本节中我们来看看LoRA如何利用这一概念。

在微调或任何一般的模型训练场景中,我们通过前向和反向传播来更新模型权重。LoRA背后的思想是,我们可以将权重更新矩阵(ΔW)分解为两个低秩矩阵(WA 和 WB)。

你可能会问,这有什么不同?在回答这个问题之前,我们先通过一个例子看看它是如何减少参数量的。

假设 ΔW 的维度是 100 x 100(即10,000个参数)。我们可以将其分解为两个更小的矩阵:

  • WA 的维度是 100 x 2(200个参数)
  • WB 的维度是 2 x 100(200个参数)

当我们把这两个矩阵相乘(WB * WA)时,结果仍然是 100 x 100 的矩阵,与 ΔW 的形状相同。这一点非常重要,因为我们可以将这个结果与原始的预训练权重相加,然后传递给后续的网络层。

这种分解方法极大地减少了参数数量。这里参数总数是 100*2 + 2*100 = 400。而原始的 ΔW 矩阵有 10,000 个参数。这带来了 96% 的可训练参数减少。

启发LoRA的观察是:在微调过程中,注意力权重矩阵变化的秩通常低于实际的权重更新矩阵ΔW的满秩。因此,我们可以冻结预训练权重,只更新这两个低秩权重矩阵(即图中的 WA 和 WB)。

研究发现,LoRA的性能可以匹配全量微调,有时甚至优于微调,而所需参数仅为原始GPT-3参数的 0.02%

如何确定秩的大小?

很自然地,下一个问题是:我们如何确定这些矩阵的秩(即上面例子中的“2”)?

我们可以将其视为一个需要搜索的超参数。一般来说,对于GPT-3,不同的秩大小会产生大致相似的验证准确率。直观地说,太小的秩可能无法适用于所有任务或数据集,尤其是在下游任务与基础模型的原始训练任务差异很大的情况下。

研究人员也尝试过更新权重矩阵的不同组合进行分解,但没有得出明确的趋势性结论。

LoRA的优势与特点

以下是LoRA方法的主要优势:

  • 参数高效:与提示词微调类似,LoRA锁定或冻结了模型的大部分权重。
  • 模型共享:你可以共享或重用同一个基础模型。
  • 训练高效:由于不需要计算大部分梯度或优化器状态,提高了训练效率。
  • 无额外推理延迟:因为我们可以将 WA 和 WB 与原始权重合并(W_final = W_pretrained + WB * WA),所以在推理时没有额外延迟。
  • 可组合性:它也可以与其他参数高效微调方法结合使用。然而,Hugging Face现有的PEFT库尚不允许并发使用多种PEFT方法。

在后续我们将探索的实验笔记本中,你将需要应用LoRA作为一种微调技术。

LoRA的局限性

现在,让我们谈谈LoRA的一些局限性。

尽管理论上我们可以在推理时直接替换不同的权重更新矩阵,但在处理包含多个任务的混合批次时,如何操作并不直接明了。如果我们想在推理时动态选择使用哪一组矩阵A和B,就会产生额外的服务延迟。

当然,也存在其他开放的研究问题,例如:

  • 为什么我们只分解 ΔW?可以分解原始权重矩阵W吗?
  • 我们能进一步减少参数数量吗?

事实上,已经有一种名为 IA3 的新PEFT技术,在LoRA的基础上改进,可以进一步减少可训练参数的数量。

总结

本节课中,我们一起学习了LoRA(低秩自适应)技术。我们了解到,LoRA通过将全量权重更新矩阵分解为两个低秩矩阵的乘积,实现了用极少的可训练参数(通常不到原模型的1%)对大型模型进行高效微调。这种方法在保持模型性能的同时,显著降低了计算和存储开销,是当前参数高效微调领域的核心技术之一。

在下一节中,我们将总体回顾适用于大多数PEFT方法的通用局限性。

014:PEFT的局限性 🧐

在本节中,我们将探讨参数高效微调(PEFT)技术,如提示调优或LoRA,虽然前景广阔,但并非完美无缺。我们将从模型性能和计算限制两个角度,系统地分析PEFT方法存在的普遍局限性。

模型性能的局限性

上一节我们介绍了PEFT的各种技术,本节中我们来看看它们在性能方面的挑战。尽管PEFT在许多情况下可以媲美全参数微调的效果,但它存在一些固有的性能不稳定性问题。

  • 性能稳定性不足:PEFT技术很难持续稳定地超越全参数微调的性能,因为它们对超参数的变化非常敏感。
  • 应用依据不明确:目前,对于为何选择PEFT、以及为何将适配器仅应用于注意力权重矩阵等决策,缺乏清晰的理论依据。
  • 全参数微调仍有价值:我们或许不应完全放弃全参数微调。当前PEFT的研究焦点很大程度上集中在存储优化上,旨在减少同一基础模型多个副本的存储占用。

然而,存储只是问题的一部分。今年六月,一组研究人员发布了一篇论文,提出了一种名为LoOMO的新型优化器。该优化器能将训练所需的内存占用降低至原始需求的11%,这为全参数微调的高效化提供了新的思路。

计算与成本的局限性

了解了性能方面的挑战后,我们再来看看PEFT在计算效率和成本方面的限制。

以下是PEFT在计算层面存在的主要问题:

  • 推理效率未必提升:PEFT并不总能提升模型服务或推理阶段的效率。
  • 基础模型存储成本未减:PEFT无法免除或降低存储单个大型基础模型副本的成本。
  • 训练时间复杂度未变:对于PEFT,训练过程同样需要进行完整的前向传播反向传播,其训练的时间复杂度并未降低。

总结

本节课中我们一起学习了参数高效微调(PEFT)技术的局限性。我们了解到,PEFT虽然在节省存储空间方面优势显著,但在模型性能的稳定性、超参数敏感性、以及计算效率(尤其是训练时间成本和基础模型存储成本)方面仍存在挑战。这些局限性提醒我们,需要根据具体任务和资源条件,在PEFT与全参数微调之间做出权衡选择。

在下一节,也是本模块的最后一节,我们将学习为模型微调准备数据的一些最佳实践。

015:数据准备最佳实践 📊

在本节中,我们将总结数据准备的最佳实践,这是进行任何高质量微调的前提。数据准备通常是任何机器学习项目中最具挑战性的部分,涉及如何收集数据以及如何妥善准备数据。

概述

本节将探讨高质量训练数据的重要性、所需数据量的考量、获取更多数据的方法(如合成数据),以及数据格式化和质量验证的最佳实践。我们还将回顾本模块的核心内容。

高质量数据的重要性

希望无需赘言,更好的模型源于更好的训练数据。许多当前备受关注的高性能大语言模型都使用了精心策划的数据集。

  • MPT系列和Alma系列:使用了改进版的Common Crawl数据集,称为C4(Colossal Clean Crawled Corpus)。
  • GPT-Neo和GPT-J:这些是GPT-3的开源替代品,在名为The Pile的数据集上训练,该数据集由22个多样化的高质量数据集组成。GPT-J在零样本任务上与GPT-3表现相当,而GPT-Neo在情感分类任务上优于GPT-3的ADA变体。
  • Bloomberg GPT:彭博社策划了自己的英文金融文档数据集,涵盖超过40年的数据,并用公共数据进行了增强。结果非常显著,该模型在金融任务上超越了现有模型。

数据量的考量

你可能会想,这些模型都利用了数十亿规模的标记数据,我是否有足够的数据?研究显示,有时仅需数百个高质量的标注示例即可取得良好效果(例如,基于650亿参数的LLaMA模型的案例研究)。

然而,当你希望模型覆盖多样化的用例时,就需要确保数据能覆盖这些用例的多样性。OpenAI建议至少准备数百个用例样本。通常,数据集规模翻倍能带来模型性能的线性提升。

获取更多数据:合成数据

如何获取更多数据?合成数据可能是一个途径。以下是几种方法:

  • 同义词替换或重写:用同义词替换原文中的词语,或对句子进行重述。
  • 词语删除:删除一些词语,例如副词。
  • 词语位置交换:交换句子中词语的位置。
  • 引入噪声:在数据集中故意引入拼写错误。

数据准备最佳实践

上一部分我们讨论了数据的重要性,接下来看看准备数据时的具体注意事项。

数据质量与多样性

若要微调出最佳的指令遵循模型,需遵循以下原则:期望的用例越多样,所需的数据样本量就越大,但所有样本都必须是高质量的。

数据格式化

OpenAI发布了更多关于格式化的建议。一般而言:

  • 无需提供非常详细的指令。
  • 只需在提示和补全中提供你的内容。
  • 可以使用不同的分隔符来告知模型提示的结束和补全的开始,但分隔符不应出现在其他地方。
  • 一些研究人员在微调模型前,会手动编译高质量的提示。

数据质量验证

准备数据绝非易事,我们需要:

  • 手动验证数据质量。
  • 移除任何不想要的数据,包括攻击性、有害内容,或任何私人、机密信息。

关于使用LLM输出作为训练数据

需要指出的是,使用大语言模型的输出作为训练数据并不总是最佳答案。下游模型或模仿模型倾向于学习风格而非你传递给模型的内容。这与另一项新研究的结果一致,即模型的知识主要是在预训练阶段学习的。

因此,如果你想微调模型,请确保选择一个在任务或数据集上与你的下游任务最相似的基础模型。

总结

本节课中,我们一起学习了数据准备的核心要点。我们回顾了微调虽然通常能带来最佳结果,但可能受限于计算资源。如果不可行,参数高效微调是很好的替代方案,它能大幅减少可训练参数的数量。

我们探讨了三种主要方法:

  1. 提示词调优:属于软提示或添加式方法,能自动学习虚拟提示,无需手动进行提示工程。
  2. LoRA:一种重参数化方法,可将权重增量矩阵分解为低秩矩阵。
  3. 数据准备:微调数据的质量和多样性至关重要,更多高质量数据通常能提升模型性能。

现在,让我们转向一些代码,看看如何在演示笔记本中应用提示词调优,以及如何在实验笔记本中应用LoRA。

016:笔记本教程 🧠

在本教程中,我们将学习如何使用Hugging Face开发的PEFT库对大型语言模型进行高效的参数微调。我们将以BloomZ模型为例,通过Prompt Tuning技术,在“励志英文名言”数据集上进行微调,并比较不同初始化方法的效果。最后,我们还会学习如何将微调好的模型分享到Hugging Face Hub。


概述

本节我们将使用Hugging Face的PEFT库进行高效微调。PEFT支持多种微调方法,例如LoRA和Prompt Tuning。在本演示中,我们将重点使用Prompt Tuning方法,对一个名为BloomZ的自回归语言模型进行微调,目标是让它能够生成励志的英文名言。


准备工作

首先,我们需要下载PEFT库和课程相关的设置文件。

# 安装PEFT库
!pip install peft
# 下载课程设置(假设有相关脚本)
# !wget [课程设置文件URL]

完成上述步骤后,我们可以开始加载模型和分词器。


加载模型与分词器

我们将使用Hugging Face的AutoClass来自动获取预训练模型和分词器。这非常方便,只需指定模型名称即可。

from transformers import AutoTokenizer, AutoModelForCausalLM

# 指定预训练模型名称
model_name = "bigscience/bloomz-560m"
# 加载分词器
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 加载因果语言模型(自回归模型)
model = AutoModelForCausalLM.from_pretrained(model_name)

我们使用的模型是BloomZ,它是一个在多语言数据集上训练的模型,涵盖46种自然语言和编程语言。


微调前的基础模型表现

在开始微调之前,我们先看看基础模型在给定输入下的表现。我们提供一个句子开头,让模型生成后续文本。

input_text = "Two things are infinite"
inputs = tokenizer(input_text, return_tensors="pt")
outputs = model.generate(**inputs, max_length=50)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

输出可能是:“The number of people and the number.” 这个回答不算差,但我们的目标是让模型生成更励志、更具哲理的名言。


准备微调数据集

我们将使用一个名为“English quotes”的数据集进行微调。这个数据集包含了许多名人的名言。

# 假设我们已经加载了数据集
# dataset = load_dataset("english_quotes")
# 为了演示速度,我们只使用50条示例进行微调
train_dataset = dataset["train"].select(range(50))

以下是数据集的示例结构:

  • quote: "The only limit to our realization of tomorrow is our doubts of today."
  • author: "Franklin D. Roosevelt"

我们的目标是让模型学会生成具有类似励志或哲理风格的文本。


应用Prompt Tuning进行微调

Prompt Tuning允许我们使用随机初始化文本初始化的软提示。本节我们将先尝试随机初始化。

随机初始化Prompt Tuning

首先,我们配置Prompt Tuning,指定软提示的长度(虚拟令牌数)和任务类型。

from peft import PromptTuningConfig, get_peft_model

# 配置Prompt Tuning
peft_config = PromptTuningConfig(
    task_type="CAUSAL_LM",  # 因果语言建模
    num_virtual_tokens=20,  # 软提示长度
    prompt_tuning_init="RANDOM"  # 随机初始化
)
# 将PEFT配置应用到模型
peft_model = get_peft_model(model, peft_config)
# 打印可训练参数比例
peft_model.print_trainable_parameters()

应用PEFT后,可训练参数的数量会大幅减少(通常小于0.01%),这就是高效微调的优势。


配置训练参数

接下来,我们使用Hugging Face的Trainer类来定义训练配置。

from transformers import TrainingArguments, Trainer

# 定义训练参数
training_args = TrainingArguments(
    output_dir="./output",  # 输出目录
    per_device_train_batch_size=4,  # 批次大小
    learning_rate=3e-2,  # 学习率(比全参数微调高)
    num_train_epochs=5,  # 训练轮数
    logging_steps=10,
    save_steps=100
)

请注意,由于我们只微调一小部分参数,因此需要使用更高的学习率来提高效果。


设置训练器并开始训练

现在,我们将所有组件传递给Trainer类并开始训练。

from transformers import DataCollatorForLanguageModeling

# 数据整理器,用于批处理
data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,
    mlm=False  # 我们使用因果语言建模,而非掩码语言建模
)

# 创建训练器
trainer = Trainer(
    model=peft_model,
    args=training_args,
    train_dataset=train_dataset,
    data_collator=data_collator
)

# 开始训练
trainer.train()

训练过程大约需要10分钟。在Databricks上,这会自动触发一个MLflow运行,用于跟踪超参数、指标和产物。


保存并测试微调后的模型

训练完成后,我们保存模型并测试其生成效果。

# 保存模型
save_path = "./my_peft_model"
trainer.model.save_pretrained(save_path)

# 加载微调后的模型进行测试
from peft import PeftModel
loaded_model = PeftModel.from_pretrained(model, save_path)

# 生成文本
input_text = "Two things are infinite"
inputs = tokenizer(input_text, return_tensors="pt")
outputs = loaded_model.generate(**inputs, max_length=50)
print("微调后输出:", tokenizer.decode(outputs[0], skip_special_tokens=True))

输出可能是:“time and space.” 这比之前的回答更接近哲理风格。请注意,由于训练数据有限,结果可能有所不同。


文本初始化Prompt Tuning

现在,我们尝试使用文本初始化的Prompt Tuning。唯一的区别在于我们需要提供一个初始文本提示。

# 配置文本初始化的Prompt Tuning
peft_config_text = PromptTuningConfig(
    task_type="CAUSAL_LM",
    num_virtual_tokens=20,
    prompt_tuning_init="TEXT",  # 文本初始化
    prompt_tuning_init_text="Generate inspirational quotes"  # 初始文本
)
peft_model_text = get_peft_model(model, peft_config_text)

# 使用相同的训练参数和数据集进行训练
trainer_text = Trainer(
    model=peft_model_text,
    args=training_args,
    train_dataset=train_dataset,
    data_collator=data_collator
)
trainer_text.train()

训练完成后,我们同样测试其输出。

# 测试文本初始化模型
outputs_text = peft_model_text.generate(**inputs, max_length=50)
print("文本初始化输出:", tokenizer.decode(outputs_text[0], skip_special_tokens=True))

输出可能是:“the number of people that you can count.” 这个回答也还不错。


随机初始化 vs. 文本初始化

通过比较,我们发现两种初始化方法在单一样例上的表现差异不大。实际上,根据相关研究,随机初始化通常能达到与文本初始化相近的性能,且省去了设计初始文本的麻烦。因此,在实践中,随机初始化往往是更受欢迎的选择。

注意:评估生成文本的质量是主观的。为了更客观地比较,我们需要在更多的测试样例上进行评估。


将模型分享到Hugging Face Hub(可选)

如果您对微调后的模型感到满意,可以将其分享到Hugging Face Hub,供社区使用。

步骤1:创建Hugging Face账户并获取访问令牌

  1. 访问Hugging Face官网并注册账户。
  2. 登录后,点击右上角头像,进入“Settings”。
  3. 在“Access Tokens”选项卡中,创建一个新的令牌。

步骤2:在Databricks中安全地使用令牌

您可以使用Databricks Secrets来安全地存储令牌,或者通过Hugging Face的交互式登录方式。

from huggingface_hub import notebook_login
notebook_login()  # 这会提示您输入令牌

步骤3:推送模型到Hub

from huggingface_hub import HfApi

# 设置您的用户名和模型ID
username = "your_hf_username"
model_id = f"{username}/my-bloomz-peft-model"

# 推送模型
peft_model.push_to_hub(model_id, use_auth_token=True)

推送成功后,您可以在Hugging Face个人主页的“Models”部分看到它。

步骤4:从Hub加载模型

其他人现在可以像加载任何Hugging Face模型一样加载您的模型。

from peft import PeftModel
shared_model = PeftModel.from_pretrained(model, model_id)

总结

在本教程中,我们一起学习了:

  1. PEFT库的基本使用:如何利用它大幅减少微调时的可训练参数量。
  2. Prompt Tuning技术:实践了随机初始化和文本初始化两种方式。
  3. 模型微调流程:从加载模型、准备数据、配置训练到保存测试的完整步骤。
  4. 结果比较:观察到在有限数据和训练下,两种初始化方法表现相近。
  5. 模型分享:如何将微调好的模型上传到Hugging Face Hub与社区共享。

通过本教程,您已经掌握了使用PEFT进行高效微调的基本技能。接下来,您可以尝试使用更大的数据集、不同的模型或PEFT的其他方法(如LoRA)来进一步提升模型性能。

017:部署与硬件-3.1 介绍 🚀

在本节课程中,我们将学习如何优化已训练好的语言模型,以提升其部署效率、减小模型体积并加快推理速度。我们将探讨一系列关键技术,从模型压缩到架构优化,再到组合多个模型的策略,旨在帮助您在有限的延迟预算或成本约束下,实现尽可能高的模型性能。


深度学习神经网络的一个显著优势在于,当你获得一个在特定任务上表现良好的权重网络后,存在多种技术可以在保持质量基本不变的前提下,优化其计算成本。这些技术本质上是近似计算的不同方式,例如降低数值精度,或采用其他模型压缩方法以获得相似的结果。这一领域已有多年的大量研究工作,涌现出许多可应用于几乎所有模型的技术,以提升其在推理时的速度和减小体积,从而实现更快、更廉价的推理。在本节中,我们将涵盖该领域的一些关键技术。

上一节我们介绍了模型训练后的优化技术,本节中我们来看看如何从模型架构本身入手进行优化。

此外,还有一些新兴思路旨在改进模型架构本身,甚至在训练阶段就进行设计,以使模型更利于推理和低成本使用。我们将介绍其中几种方法。无论您是从头开始预训练模型,还是使用已集成了这些技术的现有模型,这些方法都适用。

最后,我们将探讨一些我称之为“元优化”的策略。这些策略涉及组合使用多个现有模型,以在许多情况下实现更低的成本和延迟。我们将讨论两种技术:

以下是两种关键的元优化策略:

  • 模型级联:其核心思想是将较简单的输入发送给成本较低的模型处理,而将更复杂的输入发送给成本更高的模型。如果实施得当,这种方法可以降低应用程序处理大量请求的金钱成本和延迟,同时对于高难度的复杂问题,仍能通过高性能模型获得高质量结果。我们将介绍一项来自我博士生名为“Frugal GD”的研究,它实践了部分此类思想,并会展示如何自行尝试,以判断其是否适用于您关心的场景。
  • 专家混合:这是一种新兴的通用模型设计方法,其模型本质上是多个子模型的组合,通过在请求处理过程中动态路由至不同的“专家”,以获得更高质量的结果。

本节内容在两种情况下尤为重要:

  1. 交互式LLM应用:如果您开发任何面向用户的交互式LLM应用,那么延迟很可能是一个关键考量。用户对延迟的容忍度存在一个上限,超过这个上限,他们会认为应用响应过慢而不愿使用。因此,您必须设法在有限的延迟预算内,进行适量的计算,以获得尽可能高质量的回答。大多数面向用户的应用都需要关注这一点。
  2. 高流量应用:LLM应用的范围很广,从每小时仅处理几个请求(例如供高级商业用户使用,他们可能不介意支付较高费用)的应用,到每秒可能运行数千次的应用(例如广告投放系统,此时部署的美元成本至关重要)。如果您处于后一种情况,作为一名机器学习工程师,主要任务之一就是设法在保持质量尽可能高的前提下,从模型中榨取最佳性能并实现最低成本。

在本节中,我们将涵盖广泛的技术,这些技术在上述两种情况下都能提供帮助,并且对于任何类型的LLM生产部署都至关重要。本节课中我们一起学习了模型部署优化的核心目标、主要技术分类(包括训练后优化、架构优化和元优化策略),以及为何延迟和成本是生产部署中的关键考量因素。接下来的章节将深入探讨具体的技术细节。

018:部署与硬件概览 🚀

在本模块中,我们将深入探讨如何充分利用可用的计算和时间资源。在模块2中,Cheang Yin向我们展示了一些可用的参数高效微调工具。现在,我们需要关注这些工具的原因之一是,大型语言模型确实非常庞大。

具体来说,它们庞大到单个消费级甚至有时企业级的硬件设备都无法处理。这意味着我们必须协调多个硬件设备。这增加了复杂性、成本和限制。

在本模块中,我们将深入探讨一些可以缓解这些问题的方法。到本模块结束时,您将能够做出不同的设计选择,不仅针对如何微调您的大型语言模型,还包括如果您正在进行预训练,如何创建基础模型。


上一节我们介绍了本模块的学习目标,本节中我们来看看我们将要探讨的具体技术方向。

以下是本模块将涵盖的两个主要技术方向:

  • 专家混合方法:我们将研究专家混合方法,以及它如何创建一个系统,让我们能够利用多个LLM,同时节省训练和推理成本。
  • 量化技术:我们还将探讨量化如何允许我们利用已经训练好的大型语言模型,并将其转换为更小的版本,而不会在性能上牺牲太多。


现在,让我们来谈谈当前这些变得异常庞大的大型语言模型所面临的问题。

问题的核心在于内存。我们发现,这些拥有数千亿参数的大型语言模型往往无法装入消费级或企业级GPU。

从我们的大型语言模型中可以看到,随着模型规模的增长,它们的性能往往会更好,包括输出准确性更高、能更好地与我们需要的任务对齐,并且解决不同类型任务的能力范围更广。

然而,这是有代价的,特别是速度方面。我们在模块1中已经看到,GPT-2超大模型需要更长的时间来生成输出。对于内存占用,如果您是大型语言模型的开发者,那么“内存不足”错误将是您非常熟悉的。此外还有可更新性,如果有新数据进来,我们需要继续训练模型,模型越大,这一点就越难做到。

因此,我们面临一个选择:是必须选择一个至少速度快但质量不高的小模型,还是尝试使用一个非常大的模型,并尽可能多地投入计算资源以利用其高质量?

或者,我们能否两者兼得?在本模块中,我们将重新审视Transformer内部的一些组件,看看是否能进行一些改进,帮助缓解其中一些问题。


本节课中我们一起学习了本模块的概览,明确了大型语言模型在部署时面临的核心挑战——内存限制,并介绍了后续将深入探讨的两种关键技术:专家混合方法与量化技术,旨在实现性能与效率的平衡。

019:部署与硬件-3.3 提高学习效率 🚀

在本节中,我们将探讨如何提升大型语言模型(LLM)的学习与推理效率。核心在于理解并优化注意力机制,这是驱动现代LLM的关键技术,但也带来了计算和上下文长度扩展的挑战。

注意力机制:成就与挑战

上一节我们介绍了模型部署的基础,本节中我们来看看如何优化其核心计算。注意力机制是解锁当今大型语言模型能力的关键技术之一。这个出色的工具使LLM能够以我们之前难以想象的方式解读文本。然而,它也带来了一些我们必须面对并设法解决的问题。

上下文长度的困境

如果我们思考如何与大型语言模型交互,就必须讨论上下文长度。这本质上是我们输入到提示中的信息量,LLM将利用这些信息来解读被询问的内容或正在进行的对话类型。和我们人类一样,更大的上下文对LLM也更有利。但是,增加LLM的上下文长度并不像想象中那么简单。

从理论上讲,注意力机制的操作没有上下文长度限制。涉及查询、键和值向量的运算跨越整个序列长度,这个序列长度可以无限,且不影响模型参数的数量,因为参数是基于每个词嵌入向量的维度确定的。

然而,当我们增加上下文长度并对其应用注意力机制时:

  • 计算输入值的开销大致呈线性增长。
  • 执行位置前馈神经网络计算的开销也呈线性增长。
  • 但计算注意力分数本身的开销呈二次方增长,因为我们需要处理一个 N × N 的矩阵。

更严重的问题是,如果我们用某个长度(例如1000)训练模型,然后在两倍、三倍甚至十倍于此的长度上进行推理,模型的性能会显著下降。一个可能的原因是我们在为注意力机制提供词元位置关系信息时使用的位置编码。我们使用正弦和余弦函数进行位置编码,以提供词元间的相对位置感。然而,当训练长度和测试长度差异很大时,这种编码似乎无法让神经网络或注意力机制很好地理解位置差异。困惑度分数(用于衡量模型预测下一个词元的能力)会随着上下文长度的改变而越来越差。

解决方案:ALiBi位置编码

我们可以尝试通过改进注意力机制本身来解决这个问题。一项名为ALiBi的创新采用了一种方法:对查询向量和键向量的点积结果施加一个线性偏置。具体来说,与当前查询词元距离为 k 步的词元,其注意力分数会被减去一个系数 m * k。其中,系数 m 是一个几何序列。

公式调整后的注意力分数 = 查询·键 - m * |位置差|

这意味着,你可以在相对较短的上下文长度上训练模型,然后在推理时将其扩展到几乎任意大小。这使得最大上下文长度从大约4000,一路提升到32,000、64,000,在某些情况下甚至超过100,000。这意味着我们可以在上下文中加入更多信息,包括文档、代码库和聊天历史,从而从LLM中获得更好的性能。

计算资源的新挑战

然而,基于我们之前的讨论,你可能会意识到,进行更长的推理所需的计算资源现在成了问题。这不再仅仅是模型参数加载到内存中的问题,而是生成这些注意力权重本身成为了我们必须处理的难题。

幸运的是,这个领域充满了许多聪明人,我们已经提出了许多解决方案。其中,最受广泛关注的是Flash Attention。它利用了线性代数中的一个原理:我们实际上根本不需要具体化这些庞大的矩阵,可以进行一种“免矩阵”操作。因为我们知道一个向量的哪个索引需要与另一个向量的哪个索引交互,所以我们可以逐个处理这些单独的变量。

这种方法的重要性在于我们需要考虑计算注意力时使用的硬件。在GPU中计算注意力分数时,我们实际上在与一种叫做SRAM(静态随机存取存储器)的硬件交互。这类似于CPU上的高速缓存,它是一种非常快但容量很小的内存,非常靠近计算单元。对于LLM,当我们尝试加载完整的注意力矩阵时,SRAM会迅速过载。如果我们从不具体化注意力矩阵,就可以持续将单个变量发送到SRAM并进行排列,从而无需访问速度较慢的内存,避免了因矩阵过大无法放入SRAM而导致的性能损失。使用Flash Attention及其后续变体,我们在计算这些因更长上下文而产生的注意力时,看到了数量级的速度提升。

注意力机制的进一步优化

进一步审视注意力机制,我们可以思考其他改进方法。在第一模块中,我们讨论了注意力,但没有讨论多头注意力。本质上,多头注意力将键和值矩阵的整个注意力计算分割成多个“头”。这意味着我们将一个查询发送到多个不同的矩阵,但它们的总大小与使用单个矩阵时相同。

我们将此分割成多头的原因在于,它允许这些不同版本的键、查询和值专注于语言的不同部分。通过分割成多个头,可能一个头关注名词,一个头关注介词,另一个头关注其他词性。虽然这能通过得到更详细、更丰富的最终注意力分数来产生更准确的结果,但由于需要分多步进行计算,速度较慢。

对此的一些改进包括多查询注意力,即我们创建查询向量的多个副本,但只输入到一个键和值向量中。然而,这种方法的问题在于,虽然比多头注意力快得多,但往往无法捕捉我们在多头注意力情况下所需的所有细微差别差异。

此问题的折中方案是分组查询注意力,这也是Llama 2等大型语言模型所采用的方式。在这种机制中,我们的注意力机制有多个不同的“头”,但我们向它们发送几个不同的查询向量。请注意,这些查询向量来自同一个词元,但使用了略有不同的投影操作,从而为键向量提供了不同版本的查询向量以供审视。这使我们能够兼顾多头注意力的多焦点优势和多查询注意力的部分速度提升。分组查询注意力是迄今为止我们在改进注意力操作方面看到的最新创新之一。随着该领域的发展,我们将看到更多创新,现在我们正开始寻找不同的方法来改进Transformer架构,使其从2018年的初始版本不断成熟、精炼。

总结

本节课中,我们一起学习了提升LLM效率的关键方法。我们了解到,扩展上下文长度会带来注意力计算的二次方增长和位置编码外推的挑战。通过采用ALiBi位置编码,我们实现了训练与推理上下文长度的解耦。为了应对长上下文带来的巨大计算开销,Flash Attention利用硬件特性,通过避免具体化大型注意力矩阵,显著提升了计算速度。最后,我们还探讨了从标准多头注意力到多查询注意力,再到分组查询注意力的演进,后者在模型表现力和计算效率之间取得了更好的平衡。现在,我们已经了解了这些不同的算法改进,在下一节中,我们将探讨如何更好地利用现有的硬件和存储能力。

021:3.5 多LLM推理 🧠

在本节中,我们将探讨如何利用多个大型语言模型来提升性能或降低成本。我们将介绍两种核心方法:专家混合模型和LLM级联,它们分别适用于模型训练和推理优化的场景。


假设你已经完成了所有可能的优化,或者你拥有足够的计算能力来处理一些现成的多参数大语言模型,但你仍有更多数据需要训练。此时你该如何继续?又或者,你虽然能访问多个大语言模型,但推理预算有限。本节将讨论如何在推理和训练中,通过多种不同的方式和风格来使用多个大语言模型。

专家混合模型

上一节我们讨论了单一模型的优化,本节我们来看看如何组合多个模型。专家混合模型的核心思想是,我们可以利用多个经过专门任务训练的小型系统。这在机器学习和深度学习领域相当常见,集成方法就是如此运作的。

专家混合模型的不同之处在于,输入会被发送到一个称为“路由器”的组件。路由器经过训练,学习如何将不同的输入分配给不同类型的“专家”。这不一定非得是大语言模型,它可以应用于不同类型的机器学习和深度学习任务,但在此我们将专注于大语言模型背景下的专家混合模型。

当我们思考大语言模型中的参数分布时,超过三分之二的参数存在于位置前馈神经网络中。这些网络存在于每个Transformer块中,用于在向量经过注意力机制后对其进行额外的丰富处理。

由谷歌研究人员提出的Switch Transformer,利用了这样一个事实:通过在训练过程中使用并训练不同的前馈神经网络,我们可以提出一种方法,在同一时间训练多个这样的前馈神经网络“专家”。

这种方法在参数成本方面对我们有帮助,因为我们可以拥有多个(例如,1000亿参数)的前馈网络,但一次只训练其中一个。这意味着在训练过程中,通过每个批次中的不同样本,路由器会学习将信号发送给哪个专家。它可能将信号发送给几个专家,然后对输出进行某种聚合;也可能只发送给一个专家。这就是我们如何将多个千亿参数模型组合在一起,形成一个大型集成模型的方式。通过这种方式,我们可以轻松地从多个千亿参数的Transformer模型扩展到万亿乃至数万亿参数的模型。

关于专家混合模型方法如何工作的研究仍在继续,但Switch Transformer已经展示了出色的成果。这些方法确实需要大量的计算资源,随着我们更深入地探索专家混合模型领域,迄今为止看到的所有优化技术都将派上用场。

LLM级联与Frugal GPT

但是,假设我们主要关心的不是训练,而是推理。例如,你有一个固定的成本预算,并且只能通过某些API与大语言模型交互。在这种情况下,你可能会考虑像LLM级联这样的方法。

在2023年发布的Frugal GPT论文中,研究人员提出了一种方法:他们首先将提示传递给性能最低的模型,然后查看该模型对其自身表现结果的评估。当我们从模型输出一个特定的词元时,我们可以获取该结果的“困惑度”,这让我们了解大语言模型对其刚刚选择的内容有多大的把握。

如果当前这个低质量模型不确定或具有较高的困惑度值,那么我们将跳过它的输出,转而使用下一个更复杂的模型。这种复杂性和自检的级联效应意味着,Frugal GPT能够在保持更高准确性的同时,使用更少的成本。

像LLM级联和Frugal GPT这样的方法,仅仅是研究和工业用例新探索领域的开始。在这个领域中,我们将充分利用现有的大量大语言模型。

关于使用LLM的最佳实践还有更多内容,我们将在本模块的最后一节重点讨论。


总结

本节课中,我们一起学习了两种利用多个大型语言模型的核心策略。专家混合模型通过一个路由器动态分配任务给不同的“专家”子模型,主要用于构建参数量巨大的高效训练模型,其核心是训练一个能选择专家的路由器。而LLM级联(如Frugal GPT)则是一种推理优化策略,它让提示依次通过从简到繁的模型,并根据困惑度等置信度指标决定是否“升级”到更强大的模型,从而在控制成本的同时保证输出质量。这两种方法展示了如何通过组合模型来突破单一模型在规模或成本上的限制。

022:3.6 当前最佳实践 🚀

在本节中,我们将整合前面所学的优化与改进技术,讨论当前进入大语言模型领域(无论是训练还是推理)时可用的最佳实践选项。

上一节我们介绍了多种针对训练和推理的优化技术,本节中我们来看看如何将它们组合应用,形成一套当前推荐的最佳实践方案。

训练最佳实践

如果你计划从头开始训练一个大语言模型,以下是推荐的关键实践:

  • 使用ALiBi位置编码:这允许模型处理远超其训练时所见长度的上下文,即拥有非常大的上下文长度
  • 利用Flash Attention:在计算注意力时,此技术能避免GPU的SRAM内存过载,从而支持使用更长的上下文,并提升计算效率。
  • 采用分组查询注意力:这可以节省注意力机制所需的计算资源和参数量,公式可表示为将传统的多头注意力中的键值对进行分组共享。
  • 考虑混合专家模型:如果你的目标是实现真正大规模的语言模型,混合专家方法是一个值得探索的方向,其核心思想是y = sum( GatingNetwork(x)_i * ExpertNetwork_i(x) ),其中路由网络为每个输入动态选择少数专家。

推理与微调最佳实践

如果你的重点在于微调现有模型并进行推理,则应关注以下工具和方法:

  • 使用LoRA或其量化版本:LoRA是一种高效的参数微调方法,它通过注入低秩适配器来更新模型,而非全参数微调,代码示意为W_updated = W + BA,其中B和A是低秩矩阵。
  • 探索FrugalGPT与LLM级联:如果你关心如何在既定推理预算下最小化成本,可以研究FrugalGPT和LLM级联方法。其核心思想是先用小型、低成本模型处理简单请求,仅对困难请求调用更强大但更昂贵的模型。

硬件选择参考

社区提供了一些每位LLM开发者都应了解的经验数据。以下是关于GPU内存需求的通用指南:

模型参数量与GPU显存需求的大致关系是:所需显存(GB) ≈ 参数量(十亿) × 2。

例如,一个70亿参数的模型,推理时大约需要14GB的GPU显存。请注意,训练模型需要更多的显存

基于当前常见的GPU型号,我们可以得到以下大致的模型容量匹配参考:

  • V100 (16GB/32GB):适合约50亿至200亿参数的模型。
  • A10G (24GB):适合约100亿至200亿参数的模型。
  • A100 (40GB/80GB):适合约200亿至400亿参数的模型。

当然,随着NVIDIA、AMD等厂商技术的不断进步,我们将持续获得更强大的硬件(如H100及后续型号)。但需注意,这些尖端硬件通常价格更高,且获取难度较大。

总结

本节课中我们一起学习了LLM部署与硬件模块的当前最佳实践。我们了解到,由于LLM的发展速度超过了当前计算能力的增长,因此必须专注于优化和寻找解决方案。

我们回顾了如何改进原始的注意力机制以支持更长的上下文,并更高效地利用硬件。通常,拥有更长上下文的LLM在任务上表现更好。

在存储这些代表大语言模型的数字时,量化被证明是一个强大的工具,尽管我们需要权衡其带来的性能损失与成本节约。

我们还看到了如何在推理时组合使用大语言模型,例如通过混合专家模型和结合FrugalGPT的LLM级联方法,以尝试在控制成本的同时,获得所需的结果和性能。

接下来,让我们进入实践环节,在Notebook中我们将学习如何量化不同的模型,并有机会构建你自己的混合专家模型。

023:部署与硬件-3.7 量化技术 📉

在本节课程中,我们将学习量化技术。量化是一种将高精度数字转换为低精度表示的方法,旨在节省模型在训练和推理时的存储空间与计算资源。我们将从单个数值的量化开始,逐步深入到函数和神经网络,最终理解量化如何应用于大型语言模型。

概述

量化通过降低数值表示的精度来减少模型的内存占用和计算需求。本节将介绍量化的基本原理、实现方法及其在深度学习中的应用。

单个数值的量化

首先,我们来看如何对一个单独的数值进行量化。量化过程将浮点数转换为整数,同时记录缩放因子以便后续恢复。

我们定义两个函数:一个用于量化,另一个用于反量化。量化函数将输入值映射到离散的整数区间,反量化函数则尝试恢复原始值。

以下是量化函数的定义:

def quantize(value, bits):
    # 确保输入值在[-1, 1]范围内
    assert -1 <= value <= 1, "Value must be between -1 and 1"
    # 计算量化值
    quantized_value = round(value * (2**(bits - 1) - 1))
    return quantized_value

反量化函数的定义如下:

def unquantize(quantized_value, bits):
    # 将量化值恢复为浮点数
    unquantized_value = quantized_value / (2**(bits - 1) - 1)
    return unquantized_value

现在,我们测试4位和8位量化对数值0.5的处理效果:

value = 0.5
bits_4 = 4
bits_8 = 8

quantized_4 = quantize(value, bits_4)
unquantized_4 = unquantize(quantized_4, bits_4)

quantized_8 = quantize(value, bits_8)
unquantized_8 = unquantize(quantized_8, bits_8)

print(f"4-bit量化: 量化值={quantized_4}, 反量化值={unquantized_4}")
print(f"8-bit量化: 量化值={quantized_8}, 反量化值={unquantized_8}")

运行上述代码后,我们可以看到4位量化引入了较大的误差,而8位量化的误差较小。这是因为8位量化提供了更高的精度。

函数的量化

上一节我们介绍了单个数值的量化,本节中我们来看看如何对整个函数进行量化。量化函数实际上是在离散点上对连续函数进行采样。

我们以正弦函数为例,生成其量化版本,并计算平均误差。

以下是量化正弦函数的步骤:

  1. 生成从-1到1的100个点。
  2. 计算每个点的正弦值。
  3. 对正弦值进行4位和8位量化。
  4. 计算量化后的平均误差。
import numpy as np

# 生成输入点
x = np.linspace(-1, 1, 100)
y = np.sin(x)

# 量化函数值
y_quantized_4 = np.array([quantize(val, 4) for val in y])
y_quantized_8 = np.array([quantize(val, 8) for val in y])

# 反量化函数值
y_unquantized_4 = np.array([unquantize(val, 4) for val in y_quantized_4])
y_unquantized_8 = np.array([unquantize(val, 8) for val in y_quantized_8])

# 计算平均误差
error_4 = np.mean(np.abs(y - y_unquantized_4))
error_8 = np.mean(np.abs(y - y_unquantized_8))

print(f"4-bit量化平均误差: {error_4}")
print(f"8-bit量化平均误差: {error_8}")

运行上述代码后,我们可以看到4位量化的平均误差较大,而8位量化的误差非常小。这表明8位量化在函数表示上具有更高的保真度。

神经网络的量化

上一节我们介绍了函数的量化,本节中我们来看看如何将量化技术应用于神经网络。量化神经网络可以显著减少模型的大小和计算需求。

我们使用PyTorch定义一个简单的神经网络,并在训练后对其进行量化。以下是神经网络的架构定义:

import torch
import torch.nn as nn

class SimpleNet(nn.Module):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.quant = torch.quantization.QuantStub()
        self.fc1 = nn.Linear(28*28, 128)
        self.fc2 = nn.Linear(128, 10)
        self.dequant = torch.quantization.DeQuantStub()

    def forward(self, x):
        x = self.quant(x)
        x = torch.relu(self.fc1(x))
        x = self.fc2(x)
        x = self.dequant(x)
        return x

接下来,我们训练神经网络并在训练完成后对其进行量化:

# 训练神经网络
model = SimpleNet()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# 训练过程(简化)
for epoch in range(5):
    for data, target in train_loader:
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()

# 量化模型
model.eval()
model.qconfig = torch.quantization.get_default_qconfig('fbgemm')
torch.quantization.prepare(model, inplace=True)
torch.quantization.convert(model, inplace=True)

# 保存模型
torch.save(model.state_dict(), 'quantized_model.pth')

量化后,我们可以比较原始模型和量化模型的大小:

import os

original_size = os.path.getsize('original_model.pth')
quantized_size = os.path.getsize('quantized_model.pth')

print(f"原始模型大小: {original_size} 字节")
print(f"量化模型大小: {quantized_size} 字节")
print(f"量化后模型大小减少: {(original_size - quantized_size) / original_size * 100:.2f}%")

运行上述代码后,我们可以看到量化模型的大小显著减少,通常可以减少到原始模型的25%左右。

总结

在本节课中,我们一起学习了量化技术的基本原理和应用。我们从单个数值的量化开始,逐步扩展到函数和神经网络的量化。量化通过降低数值表示的精度来减少模型的内存占用和计算需求,尤其在大型语言模型中具有重要应用。尽管量化会引入一定的误差,但通过合理选择量化位数(如8位),可以在保持较高精度的同时实现显著的空间节省。希望本节内容能帮助你更好地理解量化技术,并在实际项目中应用它。

024:3.8 MosaicML 关于从零开始训练LLM的客座讲座 🚀

在本节课中,我们将学习如何从零开始训练大型语言模型,重点介绍MosaicML(现为Databricks的一部分)在构建开源模型MPT-7B和MPT-30B过程中所涉及的基础设施与科学原理。课程将分为计算与编排、训练运行时以及MPT模型本身三个主要部分。

计算与编排 💻

上一节我们介绍了课程概述,本节中我们来看看训练LLM所需的核心基础设施:计算与编排。

训练LLM最需要了解的一点是它们需要大量的计算资源。这里的“计算”指的是实际训练模型所需的数学和浮点运算。为了在人类友好的时间尺度(例如几周而非几年)内完成所有这些运算,我们必须将工作并行化到成百上千个GPU上。这正是其独特之处:我们需要专门的工具来在大型GPU集群上启动这些大规模训练任务,管理计算资源,与团队成员共享,在发生故障时自动恢复运行等。

在MosaicML,我们决定构建一个专门为机器学习工程师解决这些问题的产品,称之为MosaicML Cloud。它是一个编排和调度层,可以部署在任何GPU计算集群之上。它的特别之处在于,它解决了机器学习工程师在尝试大规模训练模型时面临的所有不同问题。这意味着支持多节点训练、故障时恢复运行、支持用于数据流和检查点的对象存储,以及集成如MLflow和Weights & Biases等实验跟踪器。当然,最重要的是它的高性能,我们已针对所有不同的云提供商进行了优化。

在实践中,用户可以通过MosaicML Cloud提交一个多节点训练任务。用户提交一个YAML文件,其中包含几个关键部分:一个Docker镜像、一个用于检出GitHub仓库的集成(即源代码),然后就是一系列训练运行命令。神奇之处在于,你可以将此任务提交到你账户中的任何一个集群,并且可以无缝地扩展GPU数量,无论是1个、8个还是256个,一切都能正常工作。

多节点编排使得在大型集群上启动这些运行变得容易,但性能如何呢?我们发现,云中构建的许多计算集群实际上拥有非常好的节点间网络。这意味着不同机器间GPU的带宽相对较高。在正确的软件和编排工具下,你可以在大型集群上启动任务,并实现近乎线性的扩展。这意味着你的任务可以更快完成。例如,在此图中,我们使用16倍的GPU数量获得了14.4倍的加速。这基本上意味着你可以更快地完成任务,而总成本增加不多。毕竟,如果你的任务规模扩大了10倍,但只用了1/10的时间,那么实际成本大致相同。

在MosaicML早期,我们专注于多节点扩展,以此作为在不增加客户支付价格的前提下,更快地向客户交付模型的一种方式。

扩展可能是LLM训练中最令人兴奋的部分,但在进行科学研究时也有很多细节需要注意,其中之一就是确定性。我们在技术栈的许多组件中早期就关注确定性,以便能够精确地进行科学研究。基本上,任何时候你想尝试新方法或不同架构,你都需要确保你的运行没有太多噪声,或者你测量的评估指标也没有噪声。

在左侧,你可以看到我们构建的一项技术:流式数据集。它使我们能够将数据从对象存储流式传输到大型GPU集群。无论你使用1个、2个还是64个GPU,你得到的损失曲线基本上是确定性的,样本的顺序也是确定性的。你可以看到在没有此功能时(顶部),得到的曲线大致相同但不完全一致;而在底部,你得到了几乎完全确定性的结果。这也意味着你可以在不同数量的GPU上恢复运行。如果你的集群发生故障,或者你需要与队友共享一些计算资源,你可以在不同节点/GPU上停止并恢复任务,并且它会像之前一样继续运行。

在右侧,我们的训练器或实际训练模型循环也面临类似情况。其中一个挑战是我们使用微批次技术来避免内存不足错误。这意味着当我们有一批总数据需要处理时,我们首先将其分割成更小的微批次,对每个微批次进行前向和后向传播,然后在所有微批次上累积梯度。同样,为了确保我们的训练运行对内存容量和你选择的微批次大小不敏感,我们基本上确保了我们的微批次引擎是确定性的。因此,你可以看到,无论我们使用大小为1、2还是直到7的微批次,我们得到的损失曲线在误差范围内几乎完全相同。

编排栈的最后一部分是恢复。这实际上是一个许多人直到真正开始大规模训练LLM时才会知道的挑战:这些GPU集群经常发生硬件故障。这些硬件故障的范围从网络错误到实际的硬件故障(需要移除整台机器),问题在于它们发生得相当频繁,大约每100到1000天一次。例如,在这个案例中,我们在512个A100 GPU上进行训练,基本上每两天就会发生一次故障(这是MPT-7B的训练运行)。传统上,研究人员必须像照看婴儿一样照看这些训练运行,等待出现问题,然后可能在凌晨2点醒来,停止运行并恢复它。通过我们的平台,我们的目标是基本消除这种情况。我们的编排工具会自动检测这些故障,无缝地停止运行,检测任何硬件故障,隔离那些损坏的机器,然后自动恢复运行。对于MPT-7B,我们实际上能够在没有任何人工干预的情况下完成整个训练运行,这要归功于我们的编排工具。

训练运行时 ⚙️

上一节我们介绍了计算与编排,本节中我们来看看训练运行时。这是实际的软件库,其中许多我们已经开源,使得高效训练LLM变得容易。

以下是训练运行时的核心组件:

  • MosaicML Streaming:第一个组件实际上与模型无关,而是与数据相关。从一开始,我们就专注于帮助客户使用他们自己的私有数据训练私有模型。但为了以安全的方式做到这一点,我们需要能够将他们的数据从对象存储流式传输到计算集群。我们构建了一个名为MosaicML Streaming的开源库来实现这一点。它使你能够使用任何对象存储提供商(如S3、GCS、OCI或R2),并将数据流式传输到计算集群,而无需等待整个数据下载。它具有非常高的性能和良好的随机打乱功能,并且关键的是,一旦运行完成,数据就会被删除,因此它仅用于训练目的而临时使用,我们从不将其存储在计算集群上。另一个好处是,它基本上让你可以将计算位置与数据存储位置分开。例如,你可以将数据从AWS流式传输到Oracle Cloud中的计算集群,我们经常这样做。这基本上意味着你在计算提供商方面拥有很大的议价能力。

  • Composer:下一个组件是我们的开源训练库Composer。Composer是一个构建在PyTorch之上的库,使得训练从扩散模型到LLM再到图像模型都变得容易。它处理了所有细节,如混合精度训练、分布式训练、对象存储检查点等,使你能够在云中大规模训练这些模型。它还有一个非常简洁的回调系统,因此你可以根据需要自定义它。你可以编写在训练循环不同阶段(如前向传播之后或优化器步骤之前)发生的算法,注册这些算法,并基本上根据你的运行需求进行定制。在MosaicML内部,我们所有的训练运行都使用Composer,它让一切变得非常顺利,并且适用于扩散模型和LLM。

  • 完全分片数据并行:针对大型语言模型,我想讨论的一种技术是完全分片数据并行。这基本上是一种将模型参数拆分到集群中所有GPU上的方法,以避免内存不足。我们训练的一些大型模型有300亿或更多参数,因此在训练时它们永远无法装入单个GPU。相反,我们使用PyTorch团队构建的FSDP,将这些参数拆分到整个集群中,然后仅在需要时才在前向传播过程中从集群中收集它们。这样做的一个好处是它对模型架构非常灵活。我们可以训练任何Hugging Face风格的LLM、我们的MPT架构,我们也用它来训练Stable Diffusion架构。这基本上意味着我们不必担心张量并行或流水线并行,我们只需在所有模型类型上使用这一种FSDP策略。

下图展示了FSDP的实际工作原理:在前向传播过程中,我们从集群中收集权重到每个GPU上,仅执行该层的前向传播,然后丢弃该层的权重,接着对第2层、第3层等重复此过程,直到网络末端。反向传播过程是相反的:当我们需要最后一层(第N层)时,我们收集所有权重并执行反向传播,然后再次丢弃。梯度和优化器状态同样被分片,因此训练期间使用的整个模型状态现在被拆分到整个集群中。这实际上意味着,现在你的参数总数只需要适应集群内存,而不是每个GPU的内存。这就是它与数据并行(DP)的不同之处。

  • LLM Foundry:最后我想提的是,所有这些组件单独使用都很有用,但将它们整合到一个统一的LLM平台是我们今年上半年的重点。我们有一个名为LLM Foundry的开源库。这基本上是一个完整的工具包,用于准备你的预训练数据、实际执行训练运行、之后对模型进行微调、在自定义评估数据集上评估它们,甚至为推理准备你的模型。我们构建的这个系统的神奇之处在于,我们研究团队使用的所有东西都是外部化的。因此,MPT-7B和30B都是用LLM Foundry构建的,这样我们的客户和社区成员就可以使用完全相同的工具来构建他们的LLM。

MPT模型本身 🧠

上一节我们介绍了训练运行时,本节中我们来看看MPT模型本身,从数据开始。

当我们着手训练MPT-7B和30B时,我们选择了大约1万亿token的预算,然后用来自网络的各种不同数据源填充它。你可以看到我们使用了不同来源的英文网络数据(如Common Crawl抓取数据)、来自The Stack和GitHub的代码数据、来自arXiv和Stack Exchange的科学论文等等。

处理这些非常庞大的数据集时,必须对它们进行清理。过滤和去重这些数据空间至关重要,特别是当你有多个重叠的网络抓取数据时。选择数据比例也很重要。当我们预训练这些模型时,我们通常有一个目标通用用例,我们期望这些基础模型能被不同客户针对不同任务进行微调。但我们仍然需要做出一些选择,例如放入多少英文数据与多少代码数据。使用我们极其数据化的库,可以在运行时轻松选择这些比例。对于MPT,我们选择实际上增加了代码的比例,因此与一些类似规模的开源模型相比,MPT模型在编码方面表现得特别好。我们还发现分词器有很大影响。在特定领域(如数学、算术编码,其中空格和制表符非常重要)以及外语中,为这些情况构建的分词器将极大地提高性能。

在模型架构方面,对于MPT系列,我们选择保持相当保守。我们基本上从与GPT-3系列相同的架构和模型开始,这是一个仅解码器的Transformer。我们做的主要改进是在性能方面使用了Flash Attention,这是一个针对注意力操作进行了大量优化的内核,在数学上等效,但提高了速度并减少了内存使用量。我们做的另一个重大改变是移除了位置嵌入(传统上对序列中可以拥有的token数量设置了硬性限制),而是使用了一种称为ALiBi的相对位置编码。

ALiBi使我们能够做的是,实际上在注意力掩码上添加一个偏置。这样在运行时,如果我们愿意,可以将这个偏置张量扩展到更长的序列长度,并且如果你希望,可以非常容易地对基础模型进行微调以获得更长的上下文长度。正如稍后将讨论的,我们为MPT-7B的一个名为StoryWriter的模型做到了这一点。

研究团队如何实际做出这些决策呢?这都归结为扩展定律。我们通过小型测试运行(例如1亿到10亿参数的小模型)来测试许多不同的架构选择。我们喜欢绘制我们获得的质量与使用的计算量之间的关系图,并可以从中得出扩展趋势。基于这个趋势,我们可以看到一种架构是否在每个规模上都提高了性能,这就是我们所看到的帕累托改进,然后这将给我们信心将该架构扩展到最终规模(如70亿和300亿参数)。我们发现这些扩展定律对于预训练非常有帮助。

那么微调呢?对于每个我们构建的MPT模型(7B和30B),我们也构建了几个不同的微调变体。这些变体以经过长时间预训练的基础模型为起点,然后在一个小型、精选的数据集上继续训练。这个精选数据集要么具有特定的输入-输出风格,要么有一个前缀,使其更好地与你想要的内容对齐。我们为30B和7B专注于两个变体:指令跟随变体(有助于短格式任务,如将指令转换为JSON或从列表中选择最佳项目)和多轮聊天微调模型(基本上用于聊天用例,如ChatGPT)。对于MPT-7B,我们还针对一个独特的情况:StoryWriter。我们以基础模型为基础,在大量具有非常长上下文的书籍上对其进行微调。正如我之前提到的,我们使用ALiBi的架构选择使得这变得非常容易。结果,我们能够构建一个可以吸收整本书(如《了不起的盖茨比》)然后继续撰写下一章或后记的模型。这些大多是演示,但它展示了我们客户可能实现的艺术效果。因此,如果他们需要长上下文的特定任务,我们可以帮助他们构建这些模型。

重要的是要知道,微调比预训练更快、更便宜,以至于我之前谈到的某些扩展定律在这里不太相关。在微调领域,我们实际上可以以完整的token预算尝试所有想法,并基本上选择最好的一个。我们还发现,更大的模型(如30B相对于7B)更容易微调,并且可能实际上需要更少的样本来做到这一点。

基于这一点,以下是MPT-7B及其微调模型的一些实际训练细节。你可以看到基础模型的训练成本大约为20万美元,但微调模型要便宜得多:指令模型仅需37美元,聊天模型几百美元,StoryWriter几千美元。因此,总的来说,为客户提供强大的基础模型,使他们为自己构建自定义微调模型变得更容易、更具成本效益。

最后一部分是评估。评估是一个非常开放的问题,正如你将在其他模块中听到的,没有一种方法可以做到。我们不断改进我们的技术,并随着时间的推移变得更好。过去,在我们的第一篇MPT-7B博客文章中评估MPT与其他模型时,我们基本上查看了我们的模型在单个任务(学术任务)上的零样本和少样本性能,这些任务收集自之前的论文(如GPT-3论文),并以此方式了解我们与竞争对手的对比情况。然而,由于我们的基础模型被用于大量不同的任务,我们发现这个系统有点低效。相反,我们现在实际上在大量任务上聚合性能。你可以在底部看到我们的新模型评估技术“Gauntlet”。在这里,跨越六个不同的维度(如编程、世界知识),我们实际上在每个维度内有20到30或50个任务,我们在这些任务上平均性能,然后我们实际上可以在这个雷达图中看到每个模型代际如何比上一代改进。例如,我们可以看到MPT-30B在每一个维度上都优于7B。

未来,我们认为模型评估将比这走得更远。我们一直在探索一些技术,我们将相同的提示发送给两到三个不同的模型,并要求人类实际对他们更喜欢哪一个进行排名。这可以帮助我们开发模型之间的正面竞争或ELO评分。主要的启示是,随着LLM变得更强大,开始在我们简单的基准测试上达到饱和,我们必须想出更复杂的基准测试和更多涉及人类的评估指标。

总结 📝

本节课中我们一起学习了构建像MPT这样的模型需要大量的基础设施和科学研究。在MosaicML和Databricks,我们致力于帮助更多客户掌握这些技术,使他们不必担心所有具有挑战性的基础设施和多节点训练细节。他们只需提供数据,并专注于构建适合他们的模型。

025:多模态语言模型介绍 👁️🗨️

在本节课中,我们将要学习多模态语言模型。我们将超越纯文本数据,探索如何让模型接收并处理不同类型的数据(如图像、音频),并将它们结合起来以产生有用的输出。

概述

多模态语言模型是当前语言模型和深度学习最具潜力的领域之一。其核心在于能够处理世界上以各种格式存在的数据,并从中提取有意义的洞察。想象一下,在医疗领域,模型可以同时分析核磁共振图像和文本报告;在企业中,模型可以处理传感器数据流;或者,模型能够理解视频内容。Transformer架构的通用性为此提供了可能,其输入“令牌”可以代表广泛的信息类型,而注意力机制则能处理这些多样化的输入序列。

上一节我们介绍了Transformer架构在文本处理上的应用,本节中我们来看看如何将其扩展至多模态领域。

多模态的应用潜力

以下是多模态AI的一些关键应用领域:

  • 视觉数据:例如医疗影像(MRI),模型可结合图像与文本来进行分析。
  • 结构化数据:企业内部的各类表格和数据库信息。
  • 传感器数据:例如飞机上所有传感器的读数,模型可以处理这些时间序列数据。
  • 音视频数据:处理音频流和视频内容。

Transformer架构的通用性是其支持多模态的关键。其输入令牌(input_tokens)可以灵活地表示文本、图像块或音频片段等多种信息。注意力机制(attention(query, key, value))则允许模型在这些不同类型的令牌之间建立关联,从而学习跨模态的联合表征。

本模块学习内容

在本模块中,我们将探讨当前常见的多模态AI类型:

  1. 视觉Transformer:学习如何处理图像输入。
  2. 图文联合Transformer:通过实践练习,构建一个能同时处理图像和文本的模型。
  3. 其他数据类型:简要了解如何处理音频等其他模态的数据。
  4. 训练与微调技术:探讨针对多模态数据最有效的模型训练和优化方法。
  5. 架构演进:最后,我们将讨论一些新兴的、旨在替代或增强经典Transformer架构的技术,这些技术可能以更少的计算或数据需求实现相当甚至更好的性能。

能够无缝创建处理文本及其他模态数据的应用程序,并可靠地执行复杂任务,这无疑是AI未来最令人兴奋的方向之一。

总结

本节课中我们一起学习了多模态语言模型的基本概念与巨大潜力。我们了解了Transformer架构如何通过其通用的令牌表示和注意力机制,为处理图像、音频、传感器数据等多种信息提供了基础。从下一节开始,我们将首先深入探讨视觉Transformer的具体原理与实践。

026:4.2 模块概览 🧠

在本模块中,我们将一起探索多模态语言模型的广阔领域。多模态模型能够处理文本、图像、音频等多种类型的信息,这为人工智能应用开启了无限可能。我们将了解其工作原理、核心模型以及未来的发展趋势。


从文本到多模态:Transformer的扩展

上一模块我们深入探讨了基于Transformer的纯文本语言模型。本节中,我们来看看如何将Transformer架构的强大能力扩展到文本以外的领域。

Transformer架构之所以通用和灵活,关键在于其注意力机制。它最初设计用于处理文本序列,但其核心思想——计算输入元素之间的相关性——同样适用于其他类型的数据。关键在于如何将非文本数据(如图像、音频)转化为模型能够理解的“语言”,即数值向量序列。

以下是实现多模态输入的核心步骤:

  1. 模态编码:使用专门的编码器(如卷积神经网络处理图像,音频特征提取器处理声音)将不同模态的原始数据转换为特征向量序列。
  2. 序列化与投影:将这些特征向量序列投影到与文本词向量相同的语义空间,形成统一的“令牌”序列。
  3. 联合处理:将处理后的多模态令牌序列与文本令牌序列拼接,一同输入标准的Transformer模型进行处理。

通过这种方式,一个原本为文本设计的Transformer模型,就能“理解”并综合处理来自图像、音频和文本的联合信息。


多模态模型的应用与能力

多模态模型非常实用,它们更加用户友好和灵活,几乎能模拟人类感知信息的方式。以下是一些令人印象深刻的应用实例:

  • 视频理解:可以构建视频问答应用,让模型描述视频内容并回答后续问题。
  • 图像生成与理解:例如,DALL-E可以根据文本描述生成图像,而CLIP可以判断图像与文本描述的匹配程度。
  • 复杂任务处理:可以使用MiniGPT-4来解释难以理解的网络梗图,甚至根据描述生成包含特定笑话的网站代码。

与大型语言模型类似,多模态语言模型也展现出思维链推理能力。

我们可以提供多模态信息作为上下文。例如,提供一系列视频帧,要求模型解释帧与帧之间发生了什么;或者提供饼干和苏打水的照片,询问它们有何共同属性。

更令人印象深刻的是,多模态语言模型能够同时处理多种模态的输入。我们可以同时传入一张图片和一段录音,并向语言模型提出相关问题。

此外,多模态语言模型还可以作为智能体,调用其他工具或模型来完成复杂任务。


本模块学习路线图

虽然许多多模态模型基于Transformer架构,但我们目前所知的Transformer主要处理文本。那么,它是如何适应多模态输入的呢?我们将在下一个视频中揭晓答案。

在本模块的后续内容中,我们将:

  1. 纵览多模态语言模型的广阔前景。
  2. 深入理解Transformer架构如何灵活地接受非文本输入。
  3. 讨论当前多模态语言模型的局限性,以及可能替代Transformer和注意力机制的其他架构。
  4. 最后,通过探讨多模态应用的广泛可能性来总结本模块。

总结

本节课中,我们一起学习了多模态语言模型的基本概念和巨大潜力。我们了解到,通过扩展Transformer架构,AI模型可以像人类一样综合处理文本、图像和音频信息,从而实现视频问答、图像生成、复杂推理等丰富应用。这标志着人工智能向更通用、更贴近人类感知世界的方式迈出了关键一步。在接下来的课程中,我们将深入其技术细节。

027:文本以外的 Transformer 🎯

在本节中,我们将探讨 Transformer 架构如何超越文本处理,应用于图像、音频等多种模态。我们将了解其核心原理、具体实现方式以及面临的挑战。


上一节我们见识了一系列令人印象深刻且多样化的多模态语言模型应用。它们都有一个共同点:在底层都使用了某种 Transformer 架构的变体。


Transformer 的普适性

Transformer 架构具有惊人的通用性,它是一个通用的序列处理工具。事实证明,我们可以将许多事物视为序列,包括图像、音频文件、乐谱、视频帧,甚至一系列游戏动作或蛋白质结构。具体来说,交叉注意力机制可以帮助连接不同模态,无论是图像、音频、文本还是神经时间序列。实际上,这正是文本生成图像模型 Stable Diffusion 所使用的技术。你可以看到右侧的图片,我们可以要求 Stable Diffusion 模型根据文本生成图像,这正是利用交叉注意力在文本和图像之间建立桥梁。


计算机视觉中的 Transformer 🖼️

首先,我们来看看如何将 Transformer 用于计算机视觉。

自2021年以来,如何将 Transformer 用于计算机视觉已得到深入研究。在此之前,计算机视觉领域的事实标准架构是卷积神经网络。本幻灯片中列出的模型代表了计算机视觉领域的里程碑。我不会逐一介绍这里列出的每个模型,而是将重点放在 Vision Transformer 上。这是首个用于计算机视觉的 Transformer,其在计算效率和准确性方面比卷积神经网络高出近四倍。此后,一系列研究随之展开,将 Transformer 应用于计算机视觉。我们稍后也会在计算机视觉和 Transformer 的背景下回顾零样本和少样本学习。

但首先,我们需要理解如何将图像表示为数字。


图像的数值表示

当我们购买新手机或新相机时,我们可能关心的一个细节是相机分辨率,即照片中的像素数量,通常以百万像素为单位。我们可以将图像分割成许多微小的像素,每个像素都包含主要颜色构成的信息,即图像中蓝色、绿色和红色的含量。你可以看到中心图像中,每个像素都有一个十六进制代码和 RGB 级别,这通常是我们传递给神经网络的数据。像素值的范围可以从 0 到 256。

事实证明,彩色图像可以表示为张量。在文本处理中,我们通常以矩阵形式表示文本嵌入,这是一个二维张量。对于彩色图像,它们是三维张量。第三维是代表红、绿、蓝的通道数。灰度图像也可以是三维张量,但由于所有三个通道共享相同的值,我们可以将它们表示为二维张量。这就是为什么在大规模图像处理模型中我们经常使用灰度图像,因为它们所需的空间要少得多。


像素处理的初步想法及其局限

处理像素的最初直觉是简单地将它们变成一个序列。我们可以在自回归上下文或像 BERT 那样的掩码建模上下文中使用像素序列。但不是预测下一个词元,我们使用自注意力来预测下一个像素。

但这存在两个限制:

  1. 我们失去了像素之间的垂直空间关系。这并不难理解,因为当我们从左到右将像素展平为单个序列时,我们不再知道这个灰色像素是否直接在橄榄绿色像素的上方。
  2. 这种使用自注意力的方法会产生非常高的复杂度,为 O(n²),因为我们需要计算每个像素相对于所有其他像素的复杂度。即使在像 256x256 这样的较低分辨率图像中,从左到右我们也会有超过 65k 次计算。但我们还需要从上到下复制这个过程。因此,对于我们的 256x256 图像,单个注意力层将需要进行 10⁹ 次计算。你可以想象,当图像分辨率高于 256x256 时,我们需要多少更多的计算资源。因此,这种方法不可行。

Vision Transformer 的工作原理

于是,Vision Transformer 应运而生。让我们先来了解相关术语。

Vision Transformer 将输入图像表示为一系列图像块。在 NLP 中,我们将这些单独的块称为词或子词词元。对于 Vision Transformer,它将图像分割成 16x16 的块,但在这张图片中,我只将我的猫分割成 4x4 块。因此,从左到右,你可以想象有 16 个块,每个块内有多个像素。这就是为什么 ViT 论文的标题是“一张图片值 16x16 个词”。

以下是 Vision Transformer 的工作步骤:

  1. 构建图像块:Vision Transformer 将一张大图像分割成一系列块,就像你在这里看到的一样。每个块的维度是:通道数(3)x 像素数 x 像素数。
  2. 线性投影:构建图像块后,ViT 使用一个线性投影层将图像块映射到一个 D 维向量。这些 D 维向量输出称为块嵌入。就像相似的词应该出现在相似的嵌入空间中一样,相似的图像块也应该被映射到相似的块嵌入空间。
  3. 添加位置嵌入:但如果你回想起本课程的第一个模块,我们了解到 Transformer 没有任何默认的排序机制。我们需要让模型能够以某种方式知道或推断出块的位置或顺序。因此,ViT 添加了位置嵌入。添加位置嵌入后,块嵌入就完整了。
  4. 添加分类标记:现在,这里我们参考了 BERT 中学到的东西。在 BERT 中,引入 Transformer 的一个特性是使用了 [CLS] 标记,它代表分类标记。这个标记是一个特殊标记,因为它实际上不代表一个实际的词元,它始于空白状态,因此 Transformer 将被迫学习将整个序列的通用表示编码到该嵌入中。ViT 也使用相同的逻辑,添加了这个 [CLS] 标记,也称为可学习嵌入。因此,这个可学习嵌入的输出将用作分类器的输入,以便分类器以后能够学会做出准确的预测。
  5. 输入 Transformer 编码器:如你所见,我们现在将整个序列作为输入传递给一个标准的 Transformer 编码器。
  6. 预训练与分类:然后,我们使用来自 ImageNet 数据集的图像标签对模型进行预训练,这正是 ViT 所使用的。在最后,最后一个 Transformer 块的输出通过一个分类头,然后给我们一个图像类别预测。这就是 ViT 的工作原理。

Vision Transformer 的性能与影响

我们发现,ViT 仅在更大的数据集上才优于 ResNets(另一种流行的基于卷积神经网络的计算机视觉网络)。因此你可以看到,仅对于 ImageNet,当图像数据集较小时,ViT 表现更差,但在更大的数据上,ViT 优于 ResNet。不过需要注意的是,ViT 的训练比 ResNet 在计算上高效得多,事实上,它的训练速度比 ResNet 快四倍。

在 ViT 研究之后,出现了许多其他利用注意力机制进行计算机视觉的后续研究。其中之一叫做 Swin Transformer,下一个叫做 MLP-Mixer。需要说明的是,MLP-Mixer 实际上既不是 Transformer 也不是 CNN,但它基于这篇论文的发现启发了许多同期论文和后续研究。所以如果你对 MLP-Mixer 在做什么感兴趣,一定要看看他们的论文。

我还想提一下,Vision Transformer 是一种进化,不一定是革命。根据密歇根大学这位计算机科学教授 Justin Johnson 的说法,我们基本上可以使用卷积神经网络解决相同的问题,但使用注意力的主要好处可能归结为速度,因为矩阵乘法比计算卷积对硬件更友好。因此,具有相同浮点运算次数的 ViT 比卷积网络训练和运行速度更快。


音频处理中的 Transformer 🎵

现在,让我们来看看音频。音频可以表示为频率和时间的函数。

同样的想法也适用于音频,我们可以为每个固定长度的音频帧创建嵌入向量。因此你可以看到,当我从左到右移动时,这告诉我音频的长度,每一列将代表每个音频帧的长度,这可以是每三秒、每六秒、每一分钟等等。既然现在我们可以简单地将音频表示为序列,那么在嵌入向量中,我们也可以利用 Transformer 架构。

但你会发现,音频通常比文本长度长得多。因此,你在本幻灯片上看到的内容,90% 甚至更高比例应该看起来很熟悉,因为它基本上是原始的编码器-解码器架构。但它有一个额外的部分,即在输入层之后添加了卷积神经网络层,以减少音频输入的维度。

这是一个 Speech Transformer。这篇论文的作者还尝试使用可选模块,如添加 ResNet 或长短期记忆网络,在将输入传递给 Transformer 编码器块之前进一步处理输入。


音频多模态模型的挑战

与计算机视觉相比,音频领域的多模态进展要少得多。本幻灯片上显示的模型都是 Transformer,但在 2019 年之前的很长一段时间里,音频处理模型并未使用 Transformer 架构。事实上,你在这里看到的大多数模型只专注于文本到语音、语音到文本或语音到语音。这或许并不奇怪,因为仅处理音频数据并提取所有可用信息已经足够困难了,比如情感、声学、语调、语速、如何识别谁在说话等等。

唯一似乎结合了多模态的模型是 Meta 去年刚刚发布的 Data2Vec 模型。生产此类模型的另一个主要挑战是,与仅获取文本数据或图像数据相比,获取高质量的多模态数据要困难得多。


总结

本节课中,我们一起学习了 Transformer 架构如何突破文本的界限,应用于图像和音频等多种模态。我们深入探讨了 Vision Transformer 如何将图像分割成块并进行处理,以及音频如何被表示为序列并输入 Transformer。同时,我们也认识到,尽管潜力巨大,但为多模态模型获取高质量的训练数据仍然是一个主要挑战。这正是我们下一节将要讨论的内容:多模态模型的训练数据到底是什么样子的。

028:多模态LM的训练数据 📊

在本节中,我们将探讨多模态大型语言模型(MLLM)训练数据的关键方面。我们将了解这类数据的构成、收集的挑战性,以及一个重要的开源数据集示例。

与文本-图像数据相比,文本-音频或文本-视频数据的收集要困难得多。事实上,许多研究人员必须从头开始手动整理这些数据。

以下是几个数据收集和标注方式的例子。

数据标注示例

在第二个图像示例中,我们看到标注人员必须逐帧提供详细的视频描述。描述遵循“首先,接下来,然后,最后,总体”的结构。

另一组研究人员使用了名为 FAMOS(缩写为farmers)的框架来描述他们在图片中看到的场景。他们同样使用这个框架,将场景描述分为结构化描述和密集描述。

数据也可以组织成 JSON 格式或表格格式。左侧的JSON文件展示了基于特定图像ID,人类与GPT模型之间的对话交换。右侧,我们看到类似的数据,但以表格形式组织。

高质量数据的重要性

你可能会问,能否让模型为我生成数据示例?答案当然是肯定的。但需要注意的是,你需要先做一些基础工作,提供高质量的例子。

在左侧图像中,研究人员首先通过编写预标题(pre-caption)来全面描述特定图像或场景,其次标注图像中的对象。在右侧图像中,研究人员还为单张图像提供了多个可以正确应用的标题示例。你还可以看到标注人员编写的另一段对话交换。

由此可见,在训练出优秀模型之前,标注人员必须投入如此细致的努力。

重要的开源数据集

目前最好的开源图像-文本数据集可能是 Laion-5B。许多图像-文本旗舰模型都是在专有数据集上训练的,而Laion-5B是第一个为研究目的发布的大规模开源数据集。它包含 58.5亿 个经过CLIP过滤的图像-文本对,其中 23亿 对是英文的,22亿 对是其他语言的。

然而,有一个非常重要的免责声明:该数据集中的图像大多受版权保护,Laion不声称对这些图像拥有任何所有权。

总结与展望

希望现在你能明白,高质量的数据整理,尤其是对于多模态用例,可能非常耗时且并非易事,而这正是产出高质量模型的关键。

你脑海中可能浮现的下一个问题是:如果我们没有那么多数据,或者没有那么多资源来收集优质数据,但仍然想利用这些多模态模型,该怎么办?这就是小样本学习的用武之地。我们将在下一个视频中探讨。

029:小样本学习 🎯

在本节中,我们将探讨多模态大语言模型中的小样本学习技术。正如在纯语言任务中,小样本学习变得越来越流行和重要,关于如何为多模态语言模型执行小样本学习的研究也正在兴起。

我们将首先关注计算机视觉领域。我们知道,收集多模态数据非常困难,在这种情况下,仅使用少量标注样本会非常有帮助。因此,本节我们将先看一个零样本学习的例子——CLIP模型,然后介绍一个最令人兴奋的多模态小样本学习模型——Flamingo,它于2022年底发布。

CLIP:对比语言-图像预训练 🖼️📝

CLIP代表“对比语言-图像配对”。其核心思想是从海量的自然语言数据语料库中学习视觉表示。

CLIP使用一个简单的对比损失来训练一个图像编码器和一个文本编码器。其目标是:给定一组图像和文本,预测哪些文本-图像对实际出现在训练数据中。

因此,对比预训练涉及最大化这个N×N矩阵对角线上编码的余弦相似度,因为它们才是真实的图像-文本对。这是一个相当简单的预训练任务,使得CLIP在测试时能在零样本设置下表现良好。

在第二张图中,可以看到CLIP模型通过最大化单词“狗”与视觉信息之间的相似度,正确预测了狗的标题。

我们发现,CLIP在各种设置下都能表现得更好。在这张图中,我们有ImageNet数据集,还有其他可能更真实的、描绘不同场景中香蕉的图像。无论香蕉在图像中显得模糊不清,还是只是一张草图,或者存在故意对抗性的干扰使模型更难识别那是香蕉,CLIP在这些非ImageNet数据集上都表现良好。

但CLIP的一个主要限制是:虽然我们可以预测标题的概率,以确定哪个文本最可能与图像相关联,但它无法生成文本

Flamingo:多模态小样本生成模型 🦩

现在,让我们转向另一个具有小样本能力的多模态模型——由DeepMind发布的Flamingo。Flamingo模型是一个视觉语言模型家族,能够接收交织的视觉数据和文本作为输入,并生成自由形式的文本作为输出。

其第二个亮点是它使用了一个感知器重采样器。这个感知器重采样器接收来自视觉编码器的空间、时间特征,并输出一组固定大小的视觉标记。

然后,这些视觉标记将通过新初始化的交叉注意力层来调节冻结的语言模型,这些交叉注意力层被交织在预训练的语言模型层之间。因此,这些层将为语言模型提供一种方法,将视觉信息整合到下一个标记的预测任务中。

让我们更详细地了解一下。请记住,这些作者的首要目标是利用预训练的语言模型,这样他们就不必花费更多时间或计算资源从头开始训练一个大型语言模型。具体来说,他们使用了一个名为Chinchilla的模型,它也是由DeepMind引入的。

这使得Flamingo模型具备了强大的生成语言能力,并能访问大量的预训练语言知识。视觉模型的作用是从给定的图像和视频中提取丰富的语义空间特征。

Flamingo的第二个目标是和谐地桥接视觉和语言模型。为此,作者冻结了这些模型的权重,并通过两个可学习的架构将它们连接起来。

Flamingo模型的一个重要方面是,它能够对文本y的似然进行建模,这些文本y与一系列先前的图像或视频以及先前的文本标记交织在一起。

因此,这种架构可以实现广泛的任务,包括开放式任务(如视觉问答或图像描述)和封闭式任务(如分类)。

让我们再仔细看看感知器重采样器。感知器重采样器模块将从视觉编码器输出的可变大小的时空视觉特征映射为固定数量的输出标记。

这里看到的键和值只是时空视觉特征的拼接,而查询则包含一组学习到的潜在向量。

在重采样器的另一端是固定数量的输出标记,在这个例子中我们看到有五个。

性能与数据的重要性 📊

当仅给出四个任务示例时,Flamingo击败了所有先前的小样本学习方法。事实上,它甚至超越了16个最先进微调模型中的6个。

促成这一成功的一个重要因素是,Flamingo的研究人员策划了三个高质量的数据集。这再次印证了前面模块传达的信息:数据确实对模型输出质量有很大影响

以下是Flamingo输出的一些精选示例:

  • 图像理解与推理:当给Flamingo模型一个输入提示时,它可以推断出物体是什么,并围绕这些物体进行一些推理。
  • 遵循格式的响应:它可以接收图像和文本,然后我们可以提出查询,模型会遵循之前看到的响应格式,生成类似的响应。
  • 视频问答:这里看到的第三个例子是一系列视频帧,我们可以向Flamingo询问关于这些视频帧的问题。

音频领域的小样本学习 🎵

既然我们已经讨论了计算机视觉领域的小样本学习多模态模型,那么音频领域呢?

同样,音频领域最常被引用的零样本例子是OpenAI的Whisper模型。不出所料,它使用了编码器-解码器Transformer架构,同时也使用卷积神经网络来降低输入音频的维度。

在Whisper的例子中,输入音频被分割成30秒的帧。研究人员将Whisper与其他在LibriSpeech(包含1000小时的英语朗读语音)上微调过的模型进行比较,发现Whisper的平均词错误率要低得多,甚至在零样本设置下可以媲美人类的鲁棒性。

总结与展望 🔮

在本节中,我们一起学习了多模态大语言模型中的小样本学习技术。我们介绍了CLIP模型,它通过对比学习实现了强大的零样本图像分类能力,但无法生成文本。接着,我们深入探讨了Flamingo模型,它通过创新的感知器重采样器和交叉注意力机制,桥接了视觉与语言模型,实现了卓越的小样本多模态理解和生成能力。最后,我们简要提及了音频领域的Whisper模型,展示了零样本学习在语音识别上的成功应用。

尽管我们在多模态语言模型中看到了所有这些显著的进步,但我们当然仍有许多尚未解决的挑战。我们将在下一节讨论这些挑战,并看看可能取代当前无处不在的注意力机制的新兴架构。

030:4.6 挑战与替代架构 🧩

在本节中,我们将探讨多模态语言模型所面临的挑战,并了解一些可能超越当前主流架构的替代方案。

概述

上一节我们探讨了多模态语言模型的潜力。然而,我们尚未完全解决所有问题。多模态语言模型并非对传统语言模型的局限性免疫,事实上,它们继承了许多相同的风险。

多模态语言模型的挑战

以下是多模态语言模型面临的主要挑战。

幻觉问题

模型可能完全虚构图像中不存在的内容。例如,当被问及手机屏幕上显示什么信息时,Flamingo模型输出“来自朋友的短信”,而图像中并无此内容。

继承自LLM的局限性

其他语言模型的局限性,如提示敏感性、上下文长度限制和推理计算成本,在多模态语言模型的少样本学习案例中同样存在。

偏见与毒性

模型可能继承并放大数据中的偏见。例如,一个亚洲女性要求模型让她的照片“更专业”,结果模型却让她的肤色“更白”。值得注意的是,该公司使用的模型是在目前最好的开源文本-图像数据集Laion上对Stable Diffusion模型进行微调的。因此,这个问题很可能并非该公司独有。

版权问题

与语言模型类似,多模态模型也面临版权问题。模型训练所使用的数据(如Reddit数据集)的版权归属和付费问题已被提出讨论。

缺乏常识

当要求模型根据文本生成图像时,输出结果可能完全不合逻辑。语言模型也存在类似问题,例如,当要求GPT-3补全关于“蔓越莓汁”和“重感冒”的提示时,它可能生成“你应该喝它,但你会死”这样不合常理的句子。

潜在的改进方向与替代架构

那么,我们接下来可以构建什么来改进现有模型呢?许多挑战可能很难解决甚至永远不会消失,但除了注意力机制,我们还可以考虑其他替代架构。

注意力机制,特别是自注意力,一直是NLP乃至计算机视觉领域的焦点。但看起来,确实存在其他现有或新兴的有前景的竞争架构。此处不会深入所有架构的细节,目标是让大家注意到它们,因为未来谁会成为主流尚未可知。

可能成为应用核心的部分:RLHF

无论模型变得多好,构建可靠的生产级模型的最佳方式或许始终是让人参与其中。这就是RLHF

RLHF代表基于人类反馈的强化学习
其流程通常如下:

  1. 人类反馈用于训练一个奖励模型。
  2. 这个奖励模型通常是另一个语言模型,用于输出人类偏好的标签、排名或分数。
  3. 同时,使用KL散度损失来确保微调后的模型不会与原始预训练模型偏离太远。
  4. 奖励模型编码人类偏好,并为模型输出分配质量标签。
  5. 最后,近端策略优化算法会根据奖励信号更新预训练的大型语言模型。

这是一个非常庞大的主题,鼓励大家自行深入阅读。

可能的下一代架构

现在,让我们谈谈可能的下一代架构。

  1. Hyena Hierarchy
    它使用卷积神经网络而非Transformer或注意力机制。研究人员发现,它在语言任务上是一个相当不错的少样本模型,并且其性能也能与视觉Transformer匹配。或许CNN正在卷土重来。

  2. Retentive Networks
    这种架构近期在NLP通讯中获得了相当多的关注。作者提出了一种注意力的新变体——保留机制
    这种保留机制可以连接循环和注意力。其特别之处在于,它能够在不牺牲模型性能的前提下实现更高的计算效率。这通常是一个难以破解的挑战:如何在不使模型过慢的情况下保持良好的性能,反之亦然。

总结

本节课中,我们一起学习了多模态语言模型面临的一系列挑战,包括幻觉、偏见、版权和常识缺失等问题。同时,我们也展望了未来的改进方向,重点介绍了基于人类反馈的强化学习这一重要方法,并了解了Hyena HierarchyRetentive Networks这两种有潜力的替代架构。最后,我们将在下一个视频中探讨多模态语言模型领域涌现的各种新应用。

031:多模态语言模型 - 4.7 新兴应用 🚀

在本节中,我们将探讨多模态语言模型领域令人兴奋的新兴应用。这些应用展示了AI技术如何突破传统文本处理的界限,进入图像、视频、音频乃至机器人领域。


目睹此前难以想象的AI应用不断涌现,确实是一个非常激动人心的时刻。整理这一部分内容也充满了乐趣,因为这个领域的发展速度令人惊叹。希望接下来展示的这些前沿应用实例能给你带来启发,并激励你持续学习。或许,下一个具有新闻价值的语言模型应用就将由你创造。

新兴应用概览 🌟

以下是当前多模态语言模型领域一些引人注目的新兴应用方向。

  • 文本生成3D对象:例如 Dream Diffusion,可以根据文本描述生成3D物体模型。
  • 文本生成视频:Meta 发布的 Make-A-Video 应用,能够从文本生成视频内容。
  • 语言模型与机器人结合:例如 Google 的 PaLM-E,它将大型语言模型(PaLM)与机器人应用相结合。
  • 代码生成:使用大型语言模型自动生成代码。例如,用AI代码解决“用N片制作披萨所需的最少分钟数”这类问题。
  • 多语言模型进展:在多语言模型,尤其是覆盖低资源语言(如Bactrian-X)的模型上,终于看到了一些进展。
  • 音频应用:例如 Textless NLP,它可以直接从原始音频生成语音,无需任何文本转录。这对于解决低资源语言训练数据不足的挑战具有重要意义。
  • Transformer在生物研究中的应用:Transformer架构开始在生物学研究中掀起波澜。因为蛋白质序列可以被视为一种序列,很容易输入到Transformer架构中进行处理。
  • 未来家庭机器人:或许十年后,每个家庭都可能拥有一个能为我们做一切事情、陪伴我们的机器人,包括玩游戏、聊天、搭积木等。

本模块回顾与展望 🔄

上一节我们介绍了多模态模型的具体技术,本节中我们看到了它们广阔的应用前景。现在,让我们对本模块内容做一个快速回顾。

我们看到,多模态语言模型在研究领域正真正获得发展动力。Transformer是一种通用的序列处理架构,能够接受非文本序列(如音频和图像)作为输入。然而,它们也继承了我们所使用的预训练大型语言模型的一些局限性。

尽管Transformer架构已经存在并似乎无处不在,但它可能并非最终的架构。越来越多激动人心的多模态语言模型应用即将到来。


实践环节预告 💻

在接下来的演示笔记本中,我们将学习如何构建自己的图像描述生成模型。而在实验笔记本中,你将亲手实践如何进行零样本视频分类。

我们笔记本中见。

032:从零构建图像描述模型 🖼️📝

在本教程中,我们将学习如何构建一个基础的图像描述模型。我们将选取一个基于Transformer的视觉模型和一个基于Transformer的文本模型,并将它们连接起来。之后,我们会将这个“拼接”模型与一个预训练好的图像描述模型进行性能对比。

概述

我们将使用SBU Captions数据集,它包含约100万对图像URL和英文描述。我们的目标是构建一个模型,能够理解图像内容并生成对应的文字描述。整个过程包括数据准备、模型构建、训练和评估。

数据准备与处理

首先,我们需要定义数据处理函数。这个函数需要能够同时处理图像和文本数据。

以下是数据处理的关键步骤:

  1. 获取图像:对于数据集中的每个图像URL,我们将发送请求获取图像像素数据。
  2. 处理图像:使用视觉模型的特征提取器将图像转换为模型可理解的格式。
  3. 处理文本:使用文本模型的分词器将描述文本处理成单词或子词标记。

我们将使用现成的分词器和特征提取器。具体来说,文本部分使用GPT-2的分词器,图像部分使用Vision Transformer(ViT)的特征提取器。

# 示例:初始化分词器和特征提取器
from transformers import GPT2Tokenizer, ViTFeatureExtractor

tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
feature_extractor = ViTFeatureExtractor.from_pretrained("google/vit-base-patch16-224")

为了加速后续的微调过程,我们只使用数据集中2000个样本。

构建视觉-编码器-解码器模型

现在,我们将视觉编码器(ViT)和文本解码器(GPT-2)连接起来,形成一个视觉编码器-解码器架构。

from transformers import VisionEncoderDecoderModel

model = VisionEncoderDecoderModel.from_encoder_decoder_pretrained(
    "google/vit-base-patch16-224", # 编码器:ViT
    "gpt2" # 解码器:GPT-2
)

注意:在加载GPT-2解码器时,你可能会看到“某些权重未初始化”的警告。这是因为预训练权重并不包含适配下游任务(如图像描述)的所有必要参数。因此,Hugging Face建议我们在下游任务上进一步微调整个模型,以获得最佳性能。

模型配置与训练

在开始训练前,我们需要定义一些模型配置,例如如何处理填充标记、序列结束标记、词汇表大小、生成文本时的束搜索数量以及最大输出长度。

接下来,我们定义训练参数,如批次大小和训练轮数。我们将使用Hugging Face的TrainingArgumentsTrainer类来简化训练步骤。

from transformers import TrainingArguments, Trainer

training_args = TrainingArguments(
    output_dir="./image-caption-model",
    num_train_epochs=3,
    per_device_train_batch_size=8,
    save_steps=10_000,
    save_total_limit=2,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    data_collator=data_collator,
)
trainer.train()

训练过程大约需要一分半钟。你可以通过MLflow链接监控训练日志。

模型评估与问题分析

训练完成后,我们使用测试集中的一张图像来评估模型。图像显示的是一个有商贩和顾客的户外集市。

我们让模型为这张图生成描述:

生成描述:“从悬崖上俯瞰海滩的景色。”

这个描述与图像内容完全不符。这主要归因于两个原因:

  1. 解码器权重问题:正如之前警告所示,GPT-2解码器的权重并未针对图像描述任务进行充分初始化或微调。最佳实践是先在文本描述数据上单独微调解码器,再将其接入编码器-解码器架构。
  2. 训练不足:我们只使用了少量数据进行了短期训练。增加训练轮数、提供更多训练样本以及调整模型超参数,都可能改善性能。

这个实验表明,简单地拼接两个强大的预训练模型并快速微调,可能无法直接达到理想效果。

使用预训练图像描述模型

为了获得更好的效果,我们可以直接使用专门为图像描述任务预训练的模型。接下来,我们使用一个名为BLIP的先进模型。

BLIP模型的全称是“Bootstrapping Language-Image Pre-training”,其突出特点是不仅有一个生成描述的生成器,还有一个过滤器来筛除不相关的描述。

我们使用BLIP模型进行两种描述生成:

  • 条件图像描述:在输入图像时,同时提供一个前缀文本(如“a photo of”)来引导模型。
  • 无条件图像描述:直接输入图像,不提供任何文本前缀。
from transformers import BlipProcessor, BlipForConditionalGeneration

processor = BlipProcessor.from_pretrained("Salesforce/blip-image-captioning-base")
model = BlipForConditionalGeneration.from_pretrained("Salesforce/blip-image-captioning-base")

# 条件描述
text = "a photo of"
inputs = processor(images=image, text=text, return_tensors="pt")
out = model.generate(**inputs)
caption = processor.decode(out[0], skip_special_tokens=True)
print(f"条件描述: {caption}")

# 无条件描述
inputs = processor(images=image, return_tensors="pt")
out = model.generate(**inputs)
caption = processor.decode(out[0], skip_special_tokens=True)
print(f"无条件描述: {caption}")

对于同一张集市图片,BLIP模型生成的结果如下:

  • 条件描述输出:“a photo of a mall with people walking around.”
  • 无条件描述输出:“a large indoor shopping mall.”

这两个描述都准确地捕捉了图像的核心内容(尽管对“室内/室外”的判断略有不同)。这也说明了评估图像描述模型的挑战性:对于同一张图像,可能存在多个同样正确的描述。

总结

本节课中我们一起学习了图像描述模型的基础构建流程。

  1. 我们首先了解了如何将视觉Transformer编码器和文本Transformer解码器拼接成一个多模态模型。
  2. 接着,我们实现了数据预处理、模型构建和训练流程,并观察到一个快速拼接微调的模型可能表现不佳。
  3. 最后,我们使用了专门的预训练模型BLIP,它展示了显著更好的性能,并介绍了条件与无条件图像描述两种生成方式。

核心结论是:对于复杂的多模态任务(如图像描述),使用针对该任务设计和预训练的模型(如BLIP),通常比简单拼接通用模型并快速微调要有效得多。当然,如果我们对自建模型进行更充分、更精细的微调,也有可能提升其性能。在接下来的实验中,你将有机会在零样本视频分类任务上亲自实践。

posted @ 2026-03-26 01:40  绝不原创的飞龙  阅读(1)  评论(0)    收藏  举报