生成式深度学习(第二版)-译文-第九章-Transformers (I)

章节目标:

  • 了解GPT的起源,一种用于文本生成的强大解码器Transformer。
  • 从概念上了解注意力机制是如何模拟人类的注意力: 对句子中某些词比其他关注更多。
  • 从第一性原理出发深入了解注意力机制是如何工作的,包括 queries,keys 以及 values 是如何创建和操作的。
  • 知道因果掩膜在文本生成任务上的重要性。
  • 理解注意力头(attention heads) 如何聚集到多头注意力层( multihead attention layer)。
  • 了解多头注意力层 如何构成 Transformer Block的一部分,另外还包括 层归一化 (layer normalization) 和 跳跃连接 (skip connections)。
  • 构建位置编码(Positional Encoding)以捕获每个token 或者 词token嵌入的位置。
  • 利用Keras 构建一个GPT模型,来生成酒评中的文字。
  • 分析GPT模型的输出,包括审视注意力分数 (attention scores) 来查看模型在关注哪里。
  • 了解不同类型的Transformers,包括每种Transformer可以解决的任务类型示例,以及最知名的经典实现之描述。
  • 理解不同的 编码器-解码器 架构是如何工作的,例如谷歌的 T5 模型。
  • 探索 OpenAI ChatGPT的训练过程。

在第五章中,我们已经看到如何在文本数据上通过循环神经网络(RNNs), 如 LSTMs 和 GRUs,来构建生成式模型。这些自回归模型以一次一个token的方式来处理序列数据,持续更新一个捕获输出当前隐表示的隐向量。RNN可以通过在隐向量上应用一个密集连接层和一个softmax激活来预测序列中的下一个单词。在2017年之前,这种方式被认为是生成文本最复杂的方式,直到一篇论文的横空出世改变了文本生成的版图。

引言

谷歌大脑这篇名为《Attention Is All You Need》的论文,因让注意力的概念流行而闻名世界 — 注意力机制目前在大部分经典的文本生成模型中占据统治地位。

作者展示了如何为序列建模构建一种名为"Transformers"的强大神经网络,它无需复杂的循环 或 卷积结构,而仅仅之需依赖注意力机制。这一方法克服了 RNN模型的一个关键缺陷,即RNN模型由于一次处理序列的一个token而难以并行化。Transformers 则是高度并行化的,这让它们可以在大量的数据集上进行训练。

在本章中,我们将要深入了解现代文本生成模型如何利用Transformer架构在文本生成挑战上达到SOTA性能。尤其的,我们将探索一种名为 *生成式预训练Transformer (Generative Pre-trained Transformer, GPT) * 的自回归模型,该模型是 OpenAI GPT-4模型的动力所在,被普遍认为是当前文本生成的经典之作。

GPT

OpenAI在2018年六月在一篇名为《Improving Language Understanding by Generative Pre-Training》的论文中首度提出GPT,几乎刚好是原始Transformer论文出现一整年之后。

在这篇论文中,作者展示了Transformer架构在巨量文本数据上如何训练,以预测序列中的下一个词,并进一步在特定的下游任务上进行精调。

GPT的预训练过程包括在一大波文本语料(BookCorpus,4.5GB文本,来自不同流派的7000本未初版书籍)进行模型训练。在预训练过程中,模型被训练用以在给定前序词句的基础上预测序列的下一个词。这一过程被称为 语言建模 (language modeling), 一般用于教会模型理解自然语言的结构和模式。

预训练之后,GPT模型可以针对特定任务精调,只需要提供给它一个小的,特定任务的数据集。精调包括调整模型的参数以更好拟合手头任务。例如,模型可以面向分类,相似度评分,问答等任务精调。

随着OpenAI后续模型如 GPT-2,GPT-3,GPT-3.5 以及GPT-4 的不断发布,GPT架构得到持续改进和拓展。这些模型在更大的数据集上训练,有更大的容量,因此他们可以生成更复杂、更相干的文本。GPT模型被学界和工业界广泛采用,并在自然语言处理任务上贡献良多。

在本章中,我们将构建原始GPT模型的变种,在略少的数据上训练,但是仍然利用了相同的组件和底层原则。

运行本示例代码
本示例代码可以在Jupyter Notebook的下列路径找到: “notebooks/09_transformer/01_gpt/gpt.ipynb”。该代码修改自Keras官网上 Apoorv Nandan贡献的优秀GPT tutorial。

Win Reviews 数据集

我们将要使用Kaggle上可供下载的 Wine Reviews 数据集。它包含 13000条酒评,也包含其它的元数据例如描述和价格。

你可以通过运行本书所附代码目录下的 Kaggle 数据集下载器直接下载本数据集,如样例 9-1 所示。这将把酒评和对应的元数据本地存储到 /data 目录下。

bash scripts/download_kaggle_data.sh zynicide wine-reviews

这里的数据准备步骤与第五章中为LSTM准备数据输入的步骤完全相同,因此我们在这里不再重复细节。如图 9-1所示,这些步骤如下:
生成式深度学习(第二版)-译文-第九章-Transformers (I)_第1张图片

  1. 加载数据,对于每种酒构建一个文本字符串描述列表。
  2. 对标点符号用空格补齐,使得每个标点符号作为一个单独的词来处理。
  3. 将字符串传入 TextVectorization 层,用以tokenizes 数据,并通过补齐/裁剪将每个string处理到固定长度。
  4. 创建一个训练集,输入是tokenized文本字符串,输出则是对相同字符串移位一个token后的预测。

注意力

理解GPT工作机制的第一步,是理解注意力机制是如何工作的。注意力机制是让Transformer架构独一无二,有别于语言建模循环方法的关键。当我们对于注意力有了坚实的理解,我们就将看到它是如何在Transformer架构(例如GPT)中起作用的。

当你书写时,你对于语句中下一个单词的选择是受你已经写下的其它单词影响的。例如,假定你以如下的句子开头:

The pink elephant tried to get into the car but it was too

很显然,下一个词应该是与 big 同义的什么词。我们怎么知道这点的?

句子中的某些其他词对于帮助我们做决定是重要的。例如,是大象而非树懒的事实,意味着我们倾向于 big 而非 slow。如果大象进入的是游泳池而非一辆车,我们可以选择 scared 而非 big。最后,getting into 的动作意味着尺寸是个问题 — 如果大象在尝试挤压车,我们可能选择fast最后最后一个单词,其中it 这里指代的是车。

句子中其他的单词压根不重要。例如,大象是粉红色的事实对我们选择最后一个单词没什么影响。同样的,句子中的小词语 (the, but, it, etc.) 给出了句子的语法形式,但是对于决定要求的形容词并不重要。

换句话说,我们对句子中的某些词要更加注意,并很大程度上忽略其它。如果我们的模型也能做同样的事情,是不是很棒?

Transformer中的注意力机制 (也称为 注意力头) 正是为此而设计的。为了有效抽取有用信息而而不被无关细节遮蔽,它可以决定输入中的什么部位值得拉取信息。这使得它足以适配各种情况,因为它可以决定推理时自己到底从哪里查找信息。

相反,循环层试图构建一个通用的隐状态,该状态足以在每个时间步捕获输入的全部表示。这种方式的一个弱点在于,如我们刚刚所见,融合进隐变量的很多词语跟手头的任务并不一定直接相关 (例如,预测下一个词语)。注意力头则不会遭受这一问题,因为它们根据上下文从邻近的词语中挑选并选择如何融合信息。

Queries,Keys,Values

那么,注意力头是如何决定它从哪里查找信息呢?在我们深入细节之前。让我们用 pink elephant 样例从高层次探索一下它是如何工作的。

假设,我们想要预测单词 too 后面紧跟着是什么。为了完成这一任务,其它前面的单词加进来表达它们的观点,但是它们的贡献是根据它们各自在预测 too 后面单词的专业度来加权的。例如,单词 elephant 可能非常自信的贡献更可能是一个跟 sizeloudness 关联的词,而单词 was 则没有太多信息可以提供用以缩小可能性。

换句话说,我们可以把一个注意力头看做一种信息检索系统,其中一个query (“What word follows too?”) 被投放进入一个 key/value store (句子中的其他词语),结果输出是 values 的加权和,权重由 query 和 每个 key 的共振来度量。

现在,我们将进一步从细节上看看这一过程 (如图9-2),我们再次参考我们的 pink elephant 句子。
生成式深度学习(第二版)-译文-第九章-Transformers (I)_第2张图片
Query (Q) 可以被认为是手头当前任务的一个表示 (例如,“What word follows too?”)。在这个例子中,它是从单词 too 的嵌入得到,把它传入一个权重矩阵 W Q W_Q WQ 以把向量的维度从 d e d_e de 改变为 d k d_k dk

Key向量 (K) 则是句子中每个单词的表示 — 你可以把它们看做每个单词可以帮助的预测类别的描述。它们是通过与 query 类似的方式获取,即将每个嵌入传入一个权重矩阵 W K W_K WK以把每个向量的维度从 d e d_e de 改变为 d k d_k dk。注意,keys 和 query 都具有相同的长度 (d_k)。

在注意力头里,每个key都使用向量对点积( Q K T QK^T QKT)来与 query 进行比较。这是为何 keys 和 query 必须要有相同的长度。对于一对特定的 key/query 来说,其内积值越大,则key 和 query的共振越多,进而它就对该注意力头的输出有更多的贡献。结果向量用 d k \sqrt{d_k} dk 进行尺度放缩以保持向量和的方差稳定(大致等于1),我们用一个 softmax 来确保贡献之和为 1。这是一个 注意力权重(attention weights) 向量。

Value向量(V) 也是句子中每个单词的表示 — 你可以把它看作每个单词的非加权表示。它们通过把每个嵌入传入一个权重矩阵 W V W_V WV来获得,并把向量的维度从 d e d_e de 改变为 d v d_v dv。注意 value 向量不一定与 key 和 query 有相同的长度 (但是通常为了简单起见,是一样的)。

对于一个给定的Q, K, V, value向量 与注意力权重相乘以得到注意力 (attention),如下公式9-1所示。

A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k ) V Attention(Q, K, V) = softmax(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dk QKT)V

为了从注意力头获得最终的输出向量,注意力 求和得到一个长度为 d v d_v dv 的向量。这个上下文向量 针对预测too后单词的任务捕获了句中单词的融合意见。

多头注意力

我们没有任何理由仅仅只用一个注意力头!在Keras中,我们构建一个 MultiHeadAttention 层,它可以连接多个注意力头的输出,允许每一个注意力头学习一种独特的注意力机制,使得该层作为一个整体能够学到更复杂的关系。

我们把连接的输出传入一个最终的权重矩阵 W O W_O WO来把向量投影到想要的输出维度,在我们的例子中,与query的输入维度相同 ( d e d_e de),因此这些层可以序列在彼此之上堆叠。

图9-3 展示了一个 MultiHeadAttention 层的输出是如何构成的。在Keras里,我们简单按照样例9-2的代码创建这样一个层。
生成式深度学习(第二版)-译文-第九章-Transformers (I)_第3张图片

# 在Keras中创建一个 MultiHeadAttention 层
layers.MultiHeadAttention(
    num_heads = 4,  # 该多头注意力层有4个头
    key_dim = 128,  # keys (以及 query)是128维向量
    value_dim = 64, # values (以及每个头的输出)是64维向量
    output_shape = 256  # 输出向量维度为256
    )

迄今为止,我们假定我们的注意力头之query输入是单个向量。但是,在训练中,为高效起见,我们理想情况下希望注意力层可以在输入的每个单词上仅操作一遍,以预测每个后续词都是什么。换句话说,我们希望我们的GPT模型可以并行处理一组 query 向量 (也即,一个矩阵)。

你可以在想,我们可以把向量batch起来形成一个矩阵,并让线性代数来处理剩下的事情。这是正确的,但是我们需要额外的一步 — 我们需要在 query/key 点积上应用一个掩膜,来避免来自未来单词的信息泄露。如图9-4所示,这通常被称为 因果掩膜 (causal masking)。
生成式深度学习(第二版)-译文-第九章-Transformers (I)_第4张图片
没有这个掩膜,我们的GPT模型就可以完美的猜测句子中的下一个词,因为它已经使用了该词本身得到的key作为一个特征!如下样例9-3,我们给出了创建因果掩膜的代码,结果的 numpy 数组 (转置以匹配框图) 如下图9-5所示。

# 样例9-3 因果掩膜函数
def causal_attention_mask(batch_size, n_dest, n_src, dtype):
    i = tf.range(n_dest)[:, None]
    j = tf.range(n_src)
    m = i >= j - n_src + n_dest
    mask = tf.cast(m, dtype)
    mask = tf.reshape(mask, [1, n_dest, n_src])
    mult = tf.concat(
        [tf.expand_dims(batch_size, -1), tf.constant([1, 1], dtype=tf.int32)], 0
    )
    return tf.tile(mask, mult)

np.transpose(causal_attention_mask(1, 10, 10, dtype = tf.int32)[0])

生成式深度学习(第二版)-译文-第九章-Transformers (I)_第5张图片

小贴士
因果掩膜只在解码器Transformers (例如GPT)中是必须的,其任务是给定前序tokens 序列产生tokens。在训练过程中掩膜掉未来的tokens因此是必须的。
其他类型的Transformers (如,编码器Transformers) 不一定需要因果掩膜,因为他们不是训练来预测下一个token的。例如,谷歌的BERT预测给定句子中被掩膜掉的单词,因此BERT需要使用问题单词前后的上下文。
在本章的最后,我们将进一步从细节上探索不同类型的Transformers。

现在,关于多头注意力机制的解释到此为止,它几乎在所有的Transformers中出现。它非凡的地方在于: 这样一个重要的层,其科学系的参数却不外乎每个注意力头对应的三个密集连接权重矩阵( W Q , W K , W V W_Q, W_K, W_V WQ,WK,WV), 以及一个额外的矩阵 W O W_O WO以reshape输出。在一个多头注意力层中,并没有卷积或者循环机制!

接下来,我们需要回退一步,看看多头注意力层如何形成一个更大component (通常称为 Transformer Block ) 的一部分。

请参考下一篇博文

你可能感兴趣的:(深度学习,人工智能,AIGC)