LLM极简科普

编者按:本文内容来自Datawhale《AI第一课》,目标是向普通大众传播AI相关知识。本文是第一稿,太过于偏技术,因此需要重新修改打磨。不过从有编程背景读者的角度看我觉得内容尚可,特记录在此。同时也是便于后面对比最终内容和最初内容的差别,提升自己科普内容创作方面的技巧。

简介

本节主要介绍LLM(Large Language Model,大语言模型)的基础科普。大纲如下:

  • 计算机如何识别文字:Token化+词嵌入(Tokenize+Embedding)
  • 大模型如何学习(训练):下个词预测(Next Token Prediction,NTP)
  • 大模型如何理解文本:多层多头注意力(Multi-Layer+Multi-Head Self-Attention,MHA)
  • 大模型如何处理任务:上下文学习或情境学习(In-Context Learning)
  • 大模型如何生成回复:推理(Inference)

本文涉及到上面提到的重要概念时,会以中文表述,括号内的是对应的英文表达。

计算机如何识别文字

我们知道计算机底层是二进制的,只认识0和1,但是文字是高度抽象的,这中间需要进行映射。具体的做法就是将文本先分词(Token化),变成一个一个Token后,再把Token映射到一个固定的ID,然后再根据ID找到一个固定维度维度的向量。

Token化可以理解成分词过程,向量是一维的一串小数。举个例子:

1
2
3
sentence = Datawhale是一个开源学习组织。
# Token化
tokens = Datawhale 是 一个 开源 学习 组织 。

变成ID则需要一个词表,词表每一行就是一个Token,对应的序号就是ID。比如我们的词表(以词为例)是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1 ABC
2 Datawhale
3 钢琴
4 吉他
5 开源
6 是
7 我
8 你
9 学习
10 一个
11 组织
12 。
……
下面很多词
……

词表一般都是几万甚至十几万的大小。有了词表,我们就知道前面例子中每个Token用什么数字来表示了。大家数一下ID,结果是:

1
tokens = 2 6 10 5 9 11 12

其实,这时候计算机已经能”认识“文字了,不过这种方法在深度学习中不太适合,所以还需要将其映射成一组小数。具体多少个小数都是事先指定的,一般和模型的维度一致,我们假设维度为512。再假设词表大小为32000,那就需要一个32000×512的二维数组,每一个词表ID对应这个二维数组的一行,这一行512个小数就表示该Token的表示,也叫做”词嵌入(Embedding)“。如下所示。

1
2
3
4
5
6
7
8
[0.29664, 0.34321, 0.24217, 0.75958, 0.62795, 0.22623, 0.6034, '...']
[0.37428, 0.2981, 0.29431, 0.84877, 0.99636, 0.16284, 0.0576, '...']
[0.31602, 0.32578, 0.43302, 0.66272, 0.48802, 0.19358, 0.22016, '...']
[0.22739, 0.70895, 0.41965, 0.6072, 0.18054, 0.36004, 0.30305, '...']
[0.90415, 0.83478, 0.44862, 0.46151, 0.40612, 0.36267, 0.99368, '...']
[0.65705, 0.34451, 0.49258, 0.40018, 0.07113, 0.21101, 0.66925, '...']
[0.07244, 0.5279, 0.43557, 0.42674, 0.65442, 0.0662, 0.75656, '...']
......................................................................

如果是前面的例子,Datawhale这个Token对应的表示就是[0.37428, 0.2981, 0.29431, 0.84877, 0.99636, 0.16284, 0.0576, '...']

LLM如何学习(训练)

模型是什么?简单来说它就是一个规则,比如y=2x+1,对这个简单的模型来说,参数就是x的系数(等于2),x是输入,y是输出。

我们可以把大语言模型也当做一个函数:f(x),它的输入是一串文字,输出也是一串文字。训练的核心就是预测下一个Token。训练数据对应的x和y如下所示:

1
2
3
4
5
6
7
8
x          ==>    f(x)   ==>       y
<s> ==> f(x) ==> Datawhale
Datawhale ==> f(x) ==> 是
是 ==> f(x) ==> 一个
一个 ==> f(x) ==> 开源
开源 ==> f(x) ==> 组织
组织 ==> f(x) ==> 。
。 ==> f(x) ==> </s>

通过预测下一个词(Next Token Prediction,NTP),模型f(x)就可以学习到语言内部的复杂关系。当然大模型的这个f(x)主要体现在大,大意味着模型的参数非常多,换句话说计算过程比较多。不过基本构成无非就是线性和非线性两种运算。

LLM如何理解文本

LLM的之所以能理解文本,关键是其中的多层多头自注意力设计。当模型接收到一段文本的输入后,多头自注意力模块会关注到文本的不同特征和属性,而多层的设计则会让不同的层有不同的分工,一般来说越是前面的层,理解的内容就越底层和抽象,越后面的层则相对越具体。我们来看个简单的例子,如下所示。

比如我们问模型:”Datawhale是一个开源学习组织。我想入门人工智能,Datawhale有哪些资料可以提供?“,然后看它在不同层,以及不同注意力头都在”注意“什么。

##LLM如何处理任务

刚刚我们其实是以对话的形式和LLM进行交互。但其实LLM还有一项突破性的功能——上下文学习(In-Context Learning)能力。简单来说,它可以根据你输入的文本,理解文本的意思,并对文本作出反馈。这也就意味着,只要我们的文本表述的足够清楚,LLM理论上可以完成各种任务。接下来我们就来试试。

文本里面常见的任务可以分为两类:理解和生成。常见的理解任务包括:情感分析、实体识别、意图理解等;常见的生成任务包括:文本摘要、文本续写改写、翻译等。我们直接动手尝试,方便起见,我们把每类任务合并掉让模型一次完成。

1
2
3
4
5
# 理解任务
prompt = """给你一段文本,你需要进行情感分析(判断文本情感倾向为正向、中性或负向),实体识别(找出其中的实体,并注明类型,最好先输出类型,再给出对应的实体),意图理解(判断文本意图,输出词即可,不要输出句子)。

文本:我想学人工智能,Datawhale有什么相关内容吗?我不了解这个组织,你怎么评价?
"""

结果如下所示(来自文心一言):

1
2
3
4
5
6
7
8
# 生成任务
prompt = """给你一段文本,你需要将其总结成一句话的摘要,同时你需要将这句摘要改成鲁迅风格的,我还需要你输出这句摘要的英文版本。

文本:
Datawhale是一个专注于AI领域的开源组织,致力于构建一个纯粹的学习圈子,帮助学习者更好地成长。我们专注于机器学习,深度学习,编程和数学等AI领域内容的产出与学习。
Datawhale发展于2018年12月6日。团队成员规模也在不断扩大,有来自双非院校的优秀同学,也有来自上交、武大、清华等名校的小伙伴,同时也有来自微软、字节、百度等企业的工程师。
我们将一群有能力有想法的理想青年们集结在一起,共同改善学习的模式。在这里,我们没有标签。如果你是小白,我们将为你提供学习和改变的机会,如果你是优秀的学习者,我们将向你提供提升和发展的平台。
"""

结果如下所示(来自文心一言):

LLM如何生成回复

刚刚我们已经看到模型可以处理各种任务了,这说明LLM它理解了我们给它的输入,而且它同时也知道应该输出什么。那LLM又是怎么输出的呢?

我们前面已经知道模型是通过预测下一个Token来进行学习的,推理生成时那肯定也是一个Token接着一个Token出来的。也就是说,每次只能输出一个Token,我们现在要探索的是它怎么输出这个Token的。

当给模型输入一段文本后,它会首先输出一个Token,这个Token是什么,取决于输入那段文本后模型的输出。那输出是什么呢,其实是一个在整个词表上的概率分布。以32000大小的词表为例,当我们输入给模型任意一段文本,它都会输出一个32000大小的输出,然后我们会对其进行一些处理,最后得到一个32000大小的概率分布(所有数字加起来等于1),然后就可以根据概率大小选择Token了。

得到第一个Token后,我们把这个Token放在输入文本后面作为新的输入,继续重复就可以源源不断地输出Token了。那什么时候停止呢?一般遇到结束标记(训练数据里的</s>)时停止,也可以指定生成的最大长度,到了最大长度时强行停止。

我们来看个具体的例子,还是以刚刚的输入为例。

1
2
3
query = "简单介绍一下Datawhale。"
input_ids = tokenizer(query) # (1, 19)
output = model(input_ids) # (1, 19, 32000)

第一步的输出如上,我们的输入是19个Token,输出就是每个Token对应一个32000大小的输出,这里我们只取最后一个Token的结果。

注意,虽然我们取的是最后一个,但其实最后一个的结果生成时是使用了前面所有Token的信息的。

接下来就是把这个32000大小的向量变成归一化(就是所有值加起来等于1)的概率。

1
2
3
# 取到最后一个32000
logits = output[:,-1,:]
probs = logits2probs(logits)

logits2probs 方法有很多,我们这里用最简单的——取概率最大的Token,得到下一个Token的ID,然后循环执行这个过程,就可以不断输出回复内容了。整个过程如下所示:

我们只生成了20个Token(每个用空格分开)。可以看到,模型接着我们的问题开始输出。

这就是模型如何生成回复的过程,它理解了我们给的输入,然后据此一个Token一个Token输出它基于输入的回复内容。

总结

LLM接受一段文本的输入,通过对文本理解后,针对性地输出回复内容。这个过程中,LLM的生成能力令我们惊艳,但更惊艳的其实是理解能力,不管我们给它什么任务,它看起来都能准确理解到我们的意图。这正是LLM革命性的表现——涌现——当模型规模达到一定程度后,模型能力突然得到极大提升。另外,涌现还有另一层意思,即多个简单的单元组合成功能复杂的系统的现象。而这正是深度学习和LLM的形态。