有温度的数字科技

有温度的数字科技

【Andrej Karpathy】Deep Dive into LLMs like ChatGPT

image.png

# 大家好

其实我早就想做这期视频了。这是一期面向普通观众的综合介绍,主要讲解像ChatGPT这类大型语言模型(Large Language Models,简称LLM)。我希望通过这期视频,能让大家形成一套认知框架,去理解这类工具究竟是什么。

显然,它在某些方面既神奇又出色——有些任务能完成得非常好,但有些任务则表现欠佳,而且它还存在不少需要留意的“潜在问题”。那么,这个文本输入框的背后究竟是什么?你可以在里面输入任何内容,按下回车就能得到回复,但我们到底该输入什么?生成的这些文字从何而来?它的工作原理是怎样的?我们究竟在和什么“对话”?

这些问题我都会在这期视频中解答。我们会完整梳理这类工具的构建流程,但所有内容都会尽量做到通俗易懂,让普通观众也能理解。首先,我们来看看如何构建一个像ChatGPT这样的模型,过程中我还会聊到这类工具在认知心理学层面带来的一些影响。

好,我们先来拆解ChatGPT的构建过程。它主要分为多个依次进行的阶段,第一个阶段叫做**预训练阶段**(Pre-training Stage),而预训练阶段的第一步,就是从互联网上下载并处理数据。 要想大致了解这个过程,我建议大家看看这个网址(注:原文未提供具体URL,此处保留原意)。有一家叫Hugging Face的公司,他们收集、创建并整理了一个名为“Fine Web”的数据集,还在一篇博客文章(原文“block post”应为“blog post”的拼写误差,此处修正)中详细介绍了构建该数据集的方法。而像OpenAI、Anthropic、谷歌这类主流大型语言模型提供商,在内部都会有类似Fine Web的等效数据集。 简单来说,我们做这件事的核心目标是什么?就是从互联网的公开来源中获取海量文本数据。我们需要的是**数量庞大、质量极高且种类多样**的文档——因为我们希望模型能蕴含丰富的知识。要实现这一点其实相当复杂,从他们的介绍就能看出来,这需要多个阶段的精细处理才能做好。

不过现在,我想先提一个细节:以Fine Web数据集为例(它很能代表工业级应用中的数据集水平),最终它所占的存储空间其实只有约44太字节(Terabyte,简称TB)。如今,1太字节的U盘很容易买到,甚至这个数据集用一块硬盘几乎就能装下。所以说到底,这其实不算特别庞大的数据量——尽管互联网本身无比巨大,但我们只提取文本数据,而且会进行严格的筛选,所以像这个例子里,最终就只留下了约44太字节的数据。

接下来,我们来看看这些数据具体是什么样的,以及数据处理的部分阶段是怎样的。这类数据收集工作,很多时候都是从Common Crawl的数据开始的,最终的数据集里,大部分数据也来自这里。Common Crawl是一个机构,从2007年起就一直在对互联网进行“全网抓取”。以2024年的数据为例(原文“common CW”应为“Common Crawl”的简写,此处修正),Common Crawl已经索引了27亿个网页。他们有大量的“爬虫”程序(Crawlers)在互联网上运行,基本流程是:从几个“种子网页”(Seed Web Pages)开始,追踪页面上的所有链接,不断跳转、不断索引信息,长此以往,就积累了海量的互联网数据。所以,这通常是很多数据收集工作的起点。

不过,Common Crawl(原文“common C”为“Common Crawl”的简写,此处补充完整名称)的数据非常“原始”,需要通过多种不同方式进行筛选。这里(演示中)有一张图表,他们在图表里简要说明了这些阶段会进行的处理流程。首先第一步是“URL筛选(URL Filtering)”,具体指的是存在一份“黑名单”,里面列出了我们不希望从中获取数据的URL(统一资源定位符,即网址)或域名。通常这类被屏蔽的内容包括恶意软件网站、垃圾邮件网站、营销推广网站、种族主义网站、成人网站等。因此,在这个阶段,这类性质的网站会被直接剔除,避免它们进入我们的数据集

第二步是“文本提取(Text Extraction)”。需要注意的是,爬虫抓取并保存的是这些网页的原始HTML(超文本标记语言)代码。比如我现在打开“检查”模式(指浏览器的开发者工具),看到的就是网页原始HTML的样子——你会发现里面全是类似列表标签这类标记代码,还包含CSS(层叠样式表)等内容。这些代码几乎相当于网页的“计算机指令”,但我们真正需要的只是网页中的**纯文本内容**,而非导航栏等无关元素。因此,要从网页中精准提取出有价值的文本内容,需要经过大量的筛选、处理,以及运用相关的启发式算法(原文“heris”应为“heuristics”的拼写误差,此处修正并补充中文释义)。

接下来的阶段是“语言筛选(Language Filtering)”。以Fine Web数据集为例,他们会使用语言分类器判断每个网页的语言,然后仅保留英语内容占比超过65%的网页(此处为示例)。由此可见,“数据集中应包含多少比例的不同语言”是各公司可自主决定的设计选项。举个例子,如果我们筛选掉所有西班牙语内容,那么之后训练出的模型很可能就不擅长处理西班牙语——因为它从未接触过足够多的西班牙语数据。因此,不同公司对模型“多语言能力”的重视程度和投入力度可以有所不同。比如Fine Web数据集就侧重英语,若后续用它训练语言模型,该模型的英语表现会很出色,但在其他语言上的表现可能就欠佳。

语言筛选之后,还会进行其他几项筛选步骤,比如“去重(Deduplication,原文“D duplication”应为“Deduplication”的拼写误差,此处修正)”等,最后一步通常是“个人身份信息(PII,Personally Identifiable Information)移除”。例如地址、社保号码这类可识别个人身份的信息,我们会通过技术手段检测出来,并将包含这些信息的网页也从数据集中剔除。

数据处理的阶段还有很多,这里就不逐一展开细节了,但这确实是预训练前处理中相当关键且繁琐的环节。最终,我们就能得到像Fine Web这样的数据集。点击查看数据集详情,就能看到它最终呈现的样子,而且任何人都可以在Hugging Face(原文“huging phase”为“Hugging Face”的拼写误差,此处修正,为知名AI开源社区及公司名称)的官网上下载该数据集。以下是最终进入训练集的文本示例,比如这篇是关于2012年龙卷风的报道。

嗯,比如有篇报道讲的是2012年和2020年发生的龙卷风事件,以及当时的具体情况。下一篇内容则是关于“你知道吗?你的身体里有两个小小的、像9伏电池那么大的黄色肾上腺”——显然,这是一篇有点特别的医学科普文章。

简单来说,大家可以把这些文本理解为:从互联网网页中,通过多种筛选方式提取出的纯文本内容。现在我们已经拥有了海量文本——足足40太字节(TB),而这也成为了当前阶段下一步工作的起点。

我想让大家直观理解一下我们现在的进展,所以我从数据集中抽取了前200个网页(要知道,整个数据集里的网页数量可多了去了),然后把这些网页的所有文本都提取出来拼接在一起。最终得到的就是这样一份内容——纯粹的原始文本,也就是直接从互联网上获取的原始文本。即便只是这200个网页,文本量也非常庞大。我可以继续“缩小视图”,能看到这些文本就像一张巨大的“文本织锦”,其中蕴含着各种各样的语言模式。

而我们现在要做的,就是用这些数据来训练神经网络,让神经网络能够“内化”这些文本的语言规律,并建立起对文本逻辑流向的模型。简单讲,我们面前摆着这海量的文本,接下来要做的就是训练出能“模仿”其语言逻辑的神经网络。

不过,在将文本输入神经网络之前,我们得先确定如何“表示”这些文本,以及如何将其“输入”到模型中。当前这类神经网络技术有个特点:它们需要的是**一维的符号序列**,而且这些符号必须来自一个有限的集合。因此,我们首先要明确“符号是什么”,然后再将数据转换成由这些符号组成的一维序列。

现在我们看到的文本,本身就是一维序列——从这里开始,延伸到那里,再接着往下延续,以此类推。尽管在显示器上,它呈现出的是二维排版,但文本的逻辑顺序是“从左到右、从上到下”的,所以本质上是一维文本序列。

当然,在计算机中,这些文本会有其底层的表示形式。如果我们对文本进行所谓的“UTF-8编码”(UTF-8是一种常用的字符编码格式,用于在计算机中存储和传输文本),就能得到这些文本在计算机中对应的原始二进制数据(即比特流),它看起来就是这个样子(此处对应演示中的二进制数据展示)。

举个例子,最左边这一段,就是由8个比特(bit,计算机中最小的数据单位,取值只有0和1)组成的。从某种意义上来说,这就是我们要找的“符号序列”——它只有0和1两种可能的符号,并且是一长串这样的符号组合。但实际情况是,在神经网络中,序列长度是一种非常有限且宝贵的资源,我们并不希望用“仅两种符号”来生成极长的序列。相反,我们希望在“符号数量(也就是我们所说的‘词汇表’大小)”和“最终序列长度”之间找到一个平衡:不想要只有2种符号的极长序列,而是希望用更多的符号来缩短序列长度。

那么,有一种简单的压缩方法(或者说缩短序列长度的方法),就是将连续的若干个比特组合在一起。比如,把8个比特组成一个“字节(Byte,原文“bite”为“Byte”的拼写误差,此处修正,字节是计算机中常用的数据存储单位)”。因为每个比特只有0或1两种状态,8个比特组合起来,总共只有256种可能的组合方式。因此,我们可以把原本的比特序列转换成“字节序列”——这样一来,序列长度会缩短到原来的1/8,但符号数量变成了256种(每个字节的取值范围是0到255)。

这些比特(bit)的状态只有两种:开(1)或关(0)。如果我们将8个比特组成一组,就会发现这8个比特的“开/关”组合方式总共只有256种。因此,我们可以把原本的比特序列转换成**字节(Byte)序列**——这样一来,序列长度会缩短到原来的1/8,但此时我们的符号集合就包含了256种可能的符号(每个字节的取值范围是0到255)。

在这里,我非常建议大家不要把这些数字看作“数值”,而要将其理解为“唯一标识符(Unique ID)”或“独特符号”。或许换个更形象的方式理解会更容易:如果我们把每个字节都替换成一个独特的表情符号(Emoji),得到的序列就会类似这样(此处对应演示中的表情符号序列展示)。也就是说,我们可以把字节序列看作是由256种不同表情符号组成的序列。

但实际情况是,在最先进(state-of-the-art)的语言模型工业应用中,我们还需要进一步优化——要继续缩短序列长度(因为序列长度在模型中是极其宝贵的资源),而代价则是增加词汇表中的符号数量。实现这一目标的方法,是运行一种名为“字节对编码(Byte Pair Encoding,简称BPE,原文“Bite pair encoding”为“Byte Pair Encoding”的拼写误差,此处修正)”的算法。

这种算法的工作原理是:寻找数据中频繁出现的连续字节(或符号)对。例如,我们发现“116”后面紧跟“32”这个字节对出现的频率非常高,那么我们就会将这个字节对组合成一个**新符号**——给它分配一个新的ID(比如256),然后把所有“116+32”的组合都替换成这个新符号。我们可以根据需要,反复迭代运行这个算法:每生成一个新符号,序列长度就会相应缩短,而词汇表的符号数量则会增加。

在实际应用中,词汇表大小设置为约10万个符号时,模型效果会比较理想。比如GPT-4(生成式预训练Transformer 4)就使用了100277个符号(原文“100, 277”为“100277”的输入误差,此处修正)。而将原始文本转换成这些符号(我们称之为“标记(Token)”)的过程,就叫做“分词(Tokenization)”。接下来,我们来看看GPT-4是如何进行分词的——包括从文本到标记的转换、从标记回退到文本的过程,以及实际的分词效果。我常用一个名为“tiktokenizer”的网站(注:即OpenAI官方分词工具tiktoken的在线演示平台,原文“tick tokenizer”为“tiktokenizer”的拼写误差,此处修正)来查看标记的表示形式。进入网站后,在下拉菜单中选择“cl100k_base”——这是GPT-4基础模型使用的分词器。在左侧输入文本,网站就会显示该文本对应的分词结果。比如输入“hello world”(“hello”后接空格和“world”),结果显示它恰好被分成2个标记:第一个标记是“hello”,对应的ID是15339;第二个标记是“ world”(带空格的“world”),对应的ID是1917。

再举个例子,如果我调整输入形式,比如把“hello”拆分成“h”和“ello”,得到的依然是2个标记,但此时会是“h”这个标记,后面跟着不含“h”的“ello world”标记;如果我在“hello”和“world”之间加两个空格,分词结果又会不同——这里会出现一个新的标记,ID是220。大家可以自己在这个网站上尝试不同输入,观察分词结果的变化。另外需要注意的是,分词是**区分大小写**的:如果把“h”改成大写“H”(即“Hello”),对应的标记就会不同;要是输入“Hello World”(两个首字母均大写),实际上会被分成3个标记,而不是原来的2个。所以(处理极长的序列)在计算上的成本会非常高,因此我们通常会确定一个合适的长度——比如8000、4000或16000个标记,超过这个长度的部分就会被截断。  

在这个示例中,为了让演示更清晰,我会选取前4个标记来构建“标记窗口”。这4个标记对应的文本片段是“in”“ ”(空格)“a”“single”,它们各自有对应的标记ID(如演示中所示)。我们现在要做的核心任务,就是预测这个序列的下一个标记是什么——没错,下一个标记的ID是3962(对应特定文本片段)。  在这里,我们把这4个标记称为“上下文(Context)”,它们会作为输入数据传入神经网络,也就是神经网络的输入内容。稍后我会详细讲解神经网络的内部结构,而目前最重要的是先理解神经网络的**输入和输出**:输入是长度可变的标记序列,长度范围可以从0到我们设定的最大值(比如8000个标记)。  你可以自己尝试操作,直观感受这些标记(Token)的运作方式。实际上,我们在视频后面还会回过头来再讲分词(Tokenization),现在我只是想先给大家展示这个网站,并且让大家明白:说到底,文本在模型眼中就是这样的存在。比如我拿这里的一行文本举例,这就是GPT - 4(原文“GT4”应为“GPT - 4”的拼写误差,此处修正,指生成式预训练Transformer 4模型)看到它的样子——这段文本会被转换成一个长度为62的标记序列,屏幕上显示的就是这个序列,同时也能看到文本片段和这些符号(即标记)之间的对应关系。再强调一次,(GPT - 4的)词汇表中共有100277个可能的符号(原文“100, 27777”为“100277”的输入误差,此处修正),而文本最终都会变成由这些符号组成的一维序列。所以,关于分词我们之后还会再讨论,目前就先了解到这里。

好的,现在我已经把数据集中的这段文本序列,通过分词器重新转换成了标记序列,转换后的结果就是大家现在看到的这样。举个例子,回到Fine Web数据集(前文提及的高质量文本数据集),官方提到它不仅占用44太字节(TB)的存储空间(原文“terab of dis space”应为“terabytes of disk space”的拼写误差,此处修正),还对应着该数据集中约15万亿个标记的序列。屏幕上这些只是这个数据集中最开始的几个、几十个或者几千个标记(具体数量不多),但大家要记住,整个数据集足足有15万亿个标记。而且请大家再次注意:这些标记都代表着小小的文本片段,它们就像是序列的“原子”;标记对应的这些数字本身没有实际意义,它们仅仅是**唯一标识符(Unique IDs)** 而已。接下来就到了最有意思的部分——**神经网络训练**。训练这些神经网络时,大部分计算密集型的核心工作都集中在这一步。在这个步骤中,我们要做的是对标记在序列中相互跟随的统计关系进行建模。具体做法是,从数据中随机选取一段段“标记窗口”(即连续的标记序列)。窗口的长度可以变化,最短可以是0个标记,最长则由我们设定一个上限。比如在实际应用中,常见的标记窗口长度可能是8000个标记。

理论上,我们可以使用任意长度的标记窗口,但处理极长的窗口序列(原文“window sequ”应为“window sequences”的简写,此处修正)在计算上的成本会非常高,因此我们通常会确定一个合适的长度(比如8000、4000或16000个标记),超过这个长度的部分就会被截断。

神经网络的输出,是对“下一个标记是什么”的预测。由于我们的词汇表包含100277个可能的标记,神经网络会恰好输出100277个数字,每个数字分别对应一个标记作为“下一个标记”的概率。简单来说,它就是在“猜测”下一个标记可能是什么。

刚开始的时候,神经网络的参数是随机初始化的(后面会解释这意味着什么),相当于一种“随机转换”,所以训练初期,这些预测概率也会比较随机。我这里只举了3个例子,但要知道实际上会有10万个这样的概率数字(此处“10万个”为近似表述,与前文“100277个”一致)。比如,神经网络可能会认为“ space Direction”(带空格的“Direction”)这个标记的概率是4%,标记ID为11799的标记概率是2%,而标记ID为3962(对应“post”这个文本片段)的概率是3%。

当然,因为这个窗口是从我们的数据集中抽取的,我们知道真正的下一个标记是什么。我们知道答案——这个答案就是“标签(Label)”,我们明确序列的下一个标记就应该是3962。

现在,我们有了一套用于更新神经网络的数学流程,也有了调整它的方法。之后我会稍微详细解释其中细节,但核心逻辑很简单:我们知道当前正确标记(3962)的概率只有3%,我们希望提高这个概率,同时降低其他所有标记的概率。通过数学计算,我们能确定如何调整和更新神经网络,让正确答案的概率略微升高。

举个例子,如果现在对神经网络进行一次更新,那么下次再将这4个标记的序列输入模型时,神经网络的参数会发生细微调整——它可能会预测“post”(对应标记3962)的概率变为4%,某个错误标记(比如“case”)的概率变为1%,“Direction”的概率变为2%,诸如此类。也就是说,我们有办法“微调”神经网络(通过细微更新),让它对序列中“下一个正确标记”给出更高的概率。需要记住的是,这个过程不仅针对“输入4个标记、预测1个标记”的这一组数据,而是会同时应用到整个数据集中的所有标记上。在实际操作中,我们会抽取多个这样的标记窗口,组成“批次(Batches)”;然后针对每个窗口中的每个标记,调整神经网络参数,让正确标记的概率略微提高。所有这些操作都会在大规模的标记批次中并行进行——这就是神经网络的训练过程:通过一系列迭代更新,让模型的预测结果与训练集中的实际统计规律相匹配,使其概率分布符合数据中标记相互跟随的统计模式。

接下来,我们简单了解一下神经网络的内部结构,让大家对其内部构成有个概念。 ## 神经网络的内部结构

正如之前提到的,我们的输入是标记序列。在这个例子中,输入是4个标记,但实际输入长度可以在0到设定的最大值(比如8000个标记)之间变化。理论上,输入标记数量可以无限多,但处理无限长的序列会耗费极高的计算成本,因此我们会将序列截断到特定长度,这个长度就是该模型的“最大上下文长度(Maximum Context Length)”。这些输入(用X表示)会和神经网络的“参数(Parameters)”或“权重(Weights)”一起,参与一个复杂的数学运算。我这里只展示了6个示例参数及其取值,但实际上,现代神经网络的参数数量能达到数十亿个。训练开始前,这些参数的取值是完全随机设定的。

你可能会想到,既然参数是随机的,神经网络的预测结果也会是随机的——没错,训练初期的预测完全是随机的。但通过“反复更新网络”这一过程(我们称之为“训练神经网络”),参数的取值会被逐步调整,最终让神经网络的输出结果与训练集中观察到的模式一致。

可以把这些参数想象成DJ调音台上的旋钮:当你转动这些旋钮时,对于任何可能的标记序列输入,都会得到不同的预测结果。而“训练神经网络”,本质上就是找到一组参数设置,使其能与训练集的统计规律相匹配。

为了让大家对这个“复杂的数学运算”有个概念,我来举个例子。现代神经网络的运算表达式规模极大,可能包含数万亿项,但我先给大家看一个简化示例,它大概是这样的(对应演示中的表达式)。我举这些例子是为了说明,这类运算其实没那么“可怕”:我们有输入X(比如这里的两个示例输入X₁、X₂),它们会与网络的权重(W₀、W₁、W₂、W₃等)相结合,而这种“结合”其实就是简单的数学操作,比如乘法、加法、指数运算、除法等等。而“神经网络架构研究”的核心课题,就是设计出高效的数学表达式——这些表达式需要具备诸多实用特性,比如“表达能力强”(能捕捉复杂模式)、“可优化”(便于调整参数)、“可并行”(适合大规模计算,原文“paralyzable”应为“parallelizable”的拼写误差,此处修正)等。但说到底,这些表达式本身并不复杂,本质上就是通过输入与参数的组合来生成预测结果;而我们要做的,就是优化这个神经网络的参数,让预测结果与训练集的实际情况一致。

接下来,我想给大家看一个实际工业级神经网络的示例。推荐大家访问一个网站(注:原文未提供具体网址),上面有这类网络的直观可视化演示。你在这个网站上会看到,工业场景中使用的神经网络具有一种特殊结构,这种网络被称为“Transformer(Transformer架构)”。以其中一个示例为例,这个Transformer模型大约有8.5万个参数(原文“8 5,000”应为“85,000”的输入格式误差,此处修正)。可视化界面的顶部,我们输入标记序列,信息会在神经网络中逐步流转,最终到达输出端——这里的“logit(对数几率)”和“softmax(softmax函数)”,本质上就是对“下一个标记是什么”的预测结果。整个过程包含一系列转换步骤,在这个数学运算中生成的所有中间值,都在为“预测下一个标记”服务。

举个具体流程:首先,标记会被转换成一种名为“分布式表示(Distributed Representation)”的形式——也就是说,在神经网络内部,每个可能的标记都会对应一个向量(Vector)来代表它。完成“标记嵌入(Token Embedding)”后,这些向量会按照可视化图中的路径流转,而图中的每一步操作都是独立且简单的数学运算,比如“层归一化(Layer Norms)”、“矩阵乘法(Matrix Multiplications)”、“Softmax运算”等等。例如,信息会先经过Transformer的“注意力块(Attention Block)”,再流转到“多层感知机块(Multi-Layer Perceptron Block)”,依此类推。图中的这些数字,就是运算过程中产生的中间值。你可以粗略地把这些中间值想象成“人工神经元的激活率”,但我要提醒大家:**不要把它们和生物神经元过度类比**。因为这些“人工神经元”极其简单,和你大脑中的生物神经元完全不是一个量级——生物神经元是具备记忆等功能的复杂动态系统,而人工神经元没有任何记忆能力,只是从输入到输出的固定数学转换,是“无状态”的。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

«    2025年11月    »
12
3456789
10111213141516
17181920212223
24252627282930
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
搜索
最新留言
    文章归档
    网站收藏
    友情链接

    Powered By Z-BlogPHP 1.7.4

    Copyright yck12.com .AllRights Reserved.