前段时间,AI大神Karpathy上线的AI大课,已经收获了全网15万次播放量。

当时还有网友表示,这2小时课程的含金量,相当于大学4年。

图片

就在这几天,Karpathy又萌生了一个新的想法:

那便是,将2小时13分钟的「从头开始构建GPT分词器」的视频,转换为一本书的章节(或者博客文章)形式,专门讨论「分词」。

具体步骤如下:

- 为视频添加字幕或解说文字。

- 将视频切割成若干带有配套图片和文字的段落。

- 利用大语言模型的提示工程技术,逐段进行翻译。

- 将结果输出为网页形式,其中包含指向原始视频各部分的链接。

更广泛地说,这样的工作流程可以应用于任何视频输入,自动生成各种教程的「配套指南」,使其格式更加便于阅读、浏览和搜索。

这听起来是可行的,但也颇具挑战。

他在GitHub项目minbpe下,写了一个例子来阐述自己的想象。

地址:https://github.com/karpathy/minbpe/blob/master/lecture.md

Karpathy表示,这是自己手动完成的任务,即观看视频并将其翻译成markdown格式的文章。

「我只看了大约4分钟的视频(即完成了3%),而这已经用了大约30分钟来写,所以如果能自动完成这样的工作就太好了」。

接下来,就是上课时间了!

「LLM分词」课程文字版

大家好,今天我们将探讨LLM中的「分词」问题。

遗憾的是,「分词」是目前最领先的大模型中,一个相对复杂和棘手的组成部分,但我们有必要对其进行详细了解。

因为LLM的许多缺陷可能归咎于神经网络,或其他看似神秘的因素,而这些缺陷实际上都可以追溯到「分词」。

字符级分词

那么,什么是分词呢?

事实上,在之前的视频《让我们从零开始构建 GPT》中,我已经介绍过分词,但那只是一个非常简单的字符级版本。

如果你去Google colab查看那个视频,你会发现我们从训练数据(莎士比亚)开始,它只是Python中的一个大字符串:

First Citizen: Before we proceed any further, hear me speak.

All: Speak, speak.

First Citizen: You are all resolved rather to die than to famish?

All: Resolved. resolved.

First Citizen: First, you know Caius Marcius is chief enemy to the people.

All: We know't, we know't.

但是,我们如何将字符串输入LLM呢?

我们可以看到,我们首先要为整个训练集中的所有可能字符,构建一个词汇表:

# here are all the unique characters that occur in this text
chars = sorted(list(set(text)))
vocab_size = len(chars)
print(''.join(chars))
print(vocab_size)


# !$&',-.3:;?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
# 65

然后根据上面的词汇表,创建用于在单个字符和整数之间进行转换的查找表。此查找表只是一个Python字典:

stoi = { ch:i for i,ch in enumerate(chars) }
itos = { i:ch for i,ch in enumerate(chars) }
# encoder: take a string, output a list of integers
encode = lambda s: [stoi[c] for c in s]
# decoder: take a list of integers, output a string
decode = lambda l: ''.join([itos[i] for i in l])


print(encode("hii there"))
print(decode(encode("hii there")))


# [46, 47, 47, 1, 58, 46, 43, 56, 43]
# hii there

一旦我们将一个字符串转换成一个整数序列,我们就会看到每个整数,都被用作可训练参数的二维嵌入的索引。

因为我们的词汇表大小为 vocab_size=65 ,所以该嵌入表也将有65行:

class BigramLanguageModel(nn.Module):


def __init__(self, vocab_size):
        super().__init__()
        self.token_embedding_table = nn.Embedding(vocab_size, n_embd)


def forward(self, idx, targets=None):
        tok_emb = self.token_embedding_table(idx) # (B,T,C)

在这里,整数从嵌入表中「提取」出一行,这一行就是代表该分词的向量。然后,该向量将作为相应时间步长的输入输入到Transformer。

使用BPE算法进行「字符块」分词

对于「字符级」语言模型的天真设置来说,这一切都很好。

但在实践中,在最先进的语言模型中,人们使用更复杂的方案来构建这些表征词汇。

具体地说,这些方案不是在字符级别上工作,而是在「字符块」级别上工作。构建这些块词汇表的方式是使用字节对编码(BPE)等算法,我们将在下面详细介绍该算法。

暂时回顾一下这种方法的历史发展,将字节级BPE算法用于语言模型分词的论文,是2019年OpenAI发表的GPT-2论文Language Models are Unsupervised Multitask Learners。

图片

论文地址:https://d4mucfpksywv.cloudfront.net/better-language-models/language_models_are_unsupervised_multitask_learners.pdf

向下翻到第2.2节「输入表示」,在那里他们描述并激励这个算法。在这一节的末尾,你会看到他们说:

词汇量扩大到50257个。我们还将上下文大小从512增加到1024个token,并使用512更大batchsize。

回想一下,在Transformer的注意力层中,每个token都与序列中之前的有限token列表相关联。

本文指出,GPT-2模型的上下文长度从GPT-1的512个token,增加到1024个token。

换句话说,token是 LLM 输入端的基本「原子」。

图片

「分词」是将Python中的原始字符串,转换为token列表的过程,反之亦然。

还有一个流行的例子可以证明这种抽象的普遍性,如果你也去Llama 2的论文中搜索「token」,你将得到63个匹配结果。

比如,该论文声称他们在2万亿个token上进行了训练,等等。

图片

论文地址:https://baitexiaoyuan.oss-cn-zhangjiakou.aliyuncs.com/itnew/sjwsqf1tsw1.pdf style="text-align: justify;">浅谈分词的复杂性

在我们深入探讨实现的细节之前,让我们简要地说明一下,需要详细了解「分词」过程的必要性。

分词是LLM中许多许多怪异问题的核心,我建议你不要忽略它。

很多看似神经网络架构的问题,实际上都与分词有关。这里只是几个例子:

- 为什么LLM不会拼写单词?——分词 

- 为什么LLM不能执行超简单的字符串处理任务,比如反转字符串?——分词 

- 为什么LLM在非英语语言(比如日语)任务中更差?——分词 

- 为什么LLM不擅长简单的算术?——分词 

- 为什么GPT-2在用Python编码时遇到了更多的问题?——分词 

- 为什么我的LLM在看到字符串<|endoftext|>时突然停止?——分词 

- 我收到的关于「trailing whitespace」的奇怪警告是什么?——分词 

- 如果我问LLM关于「SolidGoldMagikarp」的问题,为什么它会崩溃?——分词 

- 为什么我应该使用带有LLM的YAML而不是JSON?——分词 

- 为什么LLM不是真正的端到端语言建模?——分词

图片

我们将在视频的末尾,再回到这些问题上。

分词的可视化预览

接下来,让我们加载这个分词WebApp。

图片

地址:https://tiktokenizer.vercel.app/

这个Web应用程序的优点是,分词在网络浏览器中实时运行,允许你轻松地在输入端输入一些文本字符串,并在右侧看到分词结果。

在顶部,你可以看到我们当前正在使用 gpt2 分词器,并且可以看到,这个示例中粘贴的字符串目前正在分词为 300个token。

在这里,它们用颜色明确显示出来:

图片

比如,字符串「Tokenization」编码到token30642,其后是token是1634。

token「is」(注意,这是三个字符,包括前面的空格,这很重要!)是318。

注意使用空格,因为它在字符串中是绝对存在的,必须与所有其他字符一起分词。但为了清晰可见,在可视化时通常会省略。

你可以在应用程序底部打开和关闭它的可视化功能。同样,token「at」是379,「the」是262,依此类推。

接下来,我们有一个简单的算术例子。

在这里,我们看到,分词器对数字的分解可能不一致。比如,数字127是由3个字符组成的token,但数字677是因为有2个token:6(同样,请注意前面的空格)和77。

我们依靠LLM来解释这种任意性。

它必须在其参数内部和训练过程中,了解这两个token(6和77实际上组合成了数字677)。

同样,我们可以看到,如果LLM想要预测这个总和的结果是数字804,它必须在两个时间步长内输出:

首先,它必须发出token「8」,然后是token「04」。

请注意,所有这些拆分看起来都是完全任意的。在下面的例子中,我们可以看到1275是「12」,然后「75」,6773实际上是三个token「6」、「77」、「3」,而8041是「8」、「041」。

(未完待续...)

(TODO:若想继续文字版的内容,除非我们想出如何从视频中自动生成)

图片

网友在线,出谋划策

网友表示,太好了,实际上我更喜欢阅读这些帖子,而不是看视频,更容易把握自己的节奏。

图片

还有网友为Karpathy出谋划策:

「感觉很棘手,但使用LangChain可能是可行的。我在想是否可以使用whisper转录,产生有清晰章节的高级大纲,然后对这些章节块并行处理,在整体提纲的上下文中,专注于各自章节块的具体内容(也为每个并行处理的章节生成配图)。然后再通过LLM把所有生成的参考标记,汇编到文章末尾」。

图片

有人为此还写了一个pipeline,而且很快便会开源。


点赞(30) 打赏

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部