作为TensorFlow系列的一部分,本文将重点介绍如何对BERT和Transformer进行编码。这些例子包括:
IMDB文件:使用预先训练的TF Hub-BERT模型和AdamW进行情感分析 在例子中,我们使用一个预训练的TensorFlow Hub模型来表示BERT和AdamW优化器。因为大部分工作都是由TF-Hub模型完成的,所以我们将对这个示例进行简单的解释。 首先,我们下载并准备IMDB文件。 接下来,我们准备数据集。 我们将创建一个包含预训练BERT预处理层和预训练BERT处理层的模型,然后,我们添加一个分类头,其中包含一个dropout和一个dense层。 接下来,我们实例化一个新模型,用AdamW优化器进行训练。安装后,我们使用它来评估测试数据。 导出模型 代码的最后一部分导出并重新加载SavedModel。 GLUE/MRPC BERT 微调 在本例中,我们为“glue/mrpc”数据集微调了一个BERT模型。该数据集标记两个句子是否语义等价。 在这个例子中,我们还使用了一个预先训练好的模型。其配置、词汇表和checkpoint存储在远程存储器gs_folder_bert中。下面是gs_folder_bert的目录列表。 gs_folder_bert包含在BERT中的预训练阶段训练的BERT模型。我们将对MRPC的BERT分类器进行微调。 在第25行中,我们将数据集加载到“glue”中。它是一个Python字典,包含加载后的训练、测试和验证数据. 接下来,我们将创建一个适应训练数据词汇表的标记器。我们将使用此标记器将文本转换为整数序列-每个标记一个整数标记索引。 每个样本包含两个句子。我们添加一个[CLS]标记(一个分类标记)来指示样本的开始,并在每个句子的结尾添加一个[SEP]标记。准备好的数据将一次输入一个BERT 模型。 在下面的bert_encode方法中,它从字典glue_dict中提取句子1和2,准备它们,并将它们编码成整数序列。最后,我们返回三个数据结构:
给定一个文本输入,input_type_id相当于BERT文件中的段嵌入。在下面的示例中,前六个token属于句子1,并在input_type_ID中标记为0。其余的是1。 接下来,我们调用bert_encode对训练、验证和测试进行编码。 我们一次对整个数据集中的样本进行编码。因此,glue_train包含所有3688个样本,input_word_id的形状为(3668103),(样本数,序列长度)。 训练数据集中最长的样本有103个token。所有的训练样本都用0填充到这个最长的序列中。这允许将样本训练为张量。填充由to_tensor()完成。下面是此方法的工作原理示例。 input_mask表示token i是持有填充的0还是现在持有。应忽略填充标记并输出默认值,例如0,mask故意创建为单独的张量,以便可以传递到后续层,接下来,我们将创建一个BERT模型。配置存储在远程目录gs_folder_bert中。以下是配置文件的内容。 作为参考,这是创建的BERT模型(Transformer的编码)的模型摘要。 然后,我们使用该模型测试了10个训练样本作为乱序检查。 这是创建的最终模型。 该模型输出两个类(类相等和类不相等)的logit。因为这是一个二进制类,所以只需一个logit后跟一个sigmoid函数就可以完成。然而,使用两个logit是很常见的。选择哪一个并不重要。以下是10个样本的输出,每个样本有两个logit输出。 但是模型只是随机初始化的。接下来,我们将使用远程目录中的checkpoint来恢复它。我们将使用AdamW优化器再次对模型进行3个阶段的训练。 我们使用的AdamW优化器将有一个自定义的预热期,然后逐渐衰减。学习率如下所示: 作为最后一项任务,我们使用新样本测试模型。作为演示,我们再次保存并恢复模型。 TRecord 在现实数据集中,内存不够大,无法容纳所有样本。相反,在需要时从文件中读取数据。但是,为了更快地读取和处理文件,我们可以先将数据保存为TF设计的二进制TRecord格式。在下面的代码中,我们将示例保存为TFRecord文件,并从中创建数据集。 作为高级用户的参考,下面的代码对数据加载具有较低级别的控制。 这将是创建相应数据集的代码: TF Hub TF Hub还直接提供预先训练的TF Hub BERT模型。下面是创建BERT编码器的代码,不带分类头。然后,我们可以添加我们自己的分类器头。 或者我们可以直接使用分类器_模型得到一个BERT分类器。 Transformer语言翻译 下图是 Transformer 的一般架构。 它在深度学习中很复杂但很重要。 假设你对 Transformer 有基本的了解。 如果你看不懂,则应先阅读这篇文章(稍后添加连接)。 在本例中,我们使用Transformer将葡萄牙语翻译成英语。 数据集准备 首先,我们下载用于将葡萄牙语翻译成英语的数据集文件。 我们还分别为英语和葡萄牙语样本准备了两个分词器。 分词器的词汇量有限。 如果单词不在词汇表中,分词器会将其分解为可识别的子词,并将每个子词或词分词为一个整数(标记索引)。 这是“Transformer is awesome.”的整数序列 接下来,我们添加每个样本的开始和结束标记。 然后,我们创建数据集。 但作为演示,我们丢弃token长度超过 40 的样本。在本示例中,将缩小许多配置以加快训练速度。。 Word Embedding + Position Embedding Transformer 使用学习嵌入将token索引转换为向量表示。 为了改进模型,词的位置也被嵌入(添加)到词嵌入中。 为了将标量位置 pos 转换为 128 维向量,我们分别对向量中的偶数和奇数元素使用下面的 sin 和 cosine 函数。 (注意,论文使用了 512 维向量)。 这是 512 维向量中前 50 个位置值的可视化。 这些值根据右侧的颜色条着色。 如图所示,向量的早期元素中的位置值被更频繁地回收。 Padding Padding通常用于将输入序列扩展到特定的固定长度。 对于应该忽略的输入,mask值为 1,否则为 0。并且此mask可以传递给其他层以帮助它们生成输出。 此外,与时间序列模型不同,Transformer 在训练期间同时进行所有预测。 但在推理中,它仍然一次预测一个词/子词。 在训练期间,为了避免注意力模块窥视未来的序列,我们创建了一个mask来掩盖这些信息。 Scaled Dot-Product Attention 接下来,我们创建一个 Scaled Dot-Product Attention。 等式是 左图是模型设计。 这是代码。 Multi-head attention 接下来,我们使用scaled dot-product attention实现多头注意力。 但是,我们不会创建 8 个缩放点积注意力的实例。 相反,下面的代码将正确地 reshape q、k、v,这样所有 8 个头都可以通过scaled dot-product attention作为单个实体进行处理。 位置前馈网络 接下来,我们在每个token位置实现一个相同且可共享的位置前馈网络。 编码器层 每个编码器层将如下所示: 但是下面的代码在两个归一化层之前都有一个 dropout 层。 解码层 解码器层如下图所示。 它有 2 个多头注意力模块。 第二个使用编码器的输出来生成密钥 K 和值 V。 这是代码,它再次在层归一化层之前添加了一个 dropout 层。 编码器 接下来,我们堆叠编码器层以创建编码器。 我们将词嵌入结果与位置嵌入一起添加为编码器的输入。 该代码还在编码器之前添加了一个 dropout。 解码器 现在,我们准备堆叠解码器层来创建解码器。 Transformer 最后,我们将编码器和解码器放在一起,创建了一个 Transformer。 这个编码器和解码器分别有四个堆叠的编码器层和四个解码器层。 每个单词都由 Transformer 编码器编码为 128 维向量。 注意模块每个使用 8 个头。 我们使用 dropout 为0.1。 Training 训练将使用 Adam 优化器和一个自定义调度器来计算学习率。 其余步骤看起来与许多其他 DL 代码相似。 所以我会快速略过。 以下是损失函数和性能指标。 接下来,我们将创建 Transformer、CheckpointManager 和一个函数,用于根据输入为编码器和解码器创建不同的mask。 这是训练步骤。 Transformer 不是顺序模型。 有了源句和目标句,我们可以在一个时间步内预测整个输出序列。 我们只需要确保对于每个解码器的标记位置,注意力模块都创建了正确的掩码,这样它就无法看到未来的输入序列。 和训练循环。 验证 训练后,我们使用 Transformer 进行翻译。 在推理中,我们一次只预测一个词/子词。 我们需要到目前为止预测的单词来预测下一个单词。 所以让我们用下面的输入句子来追踪它。 input: este é um problema que temos que resolver prediction: so this is a problem that we have to solve the global challenges 分词器将输入句子转换为整数标记索引序列,然后我们将开始和结束标记添加到该序列中。这是 Transformer 编码器的输入。 Transformer 解码器的第一个输入将是英文开始标记。现在,我们调用 Transformer 进行预测。转换器将为分词器中使用的词汇表中的每个单词预测一个分数。我们将使用 argmax 从这些预测中选择下一个最有可能的词。返回值是下一个词/子词的标记索引。 现在我们将这个整数附加到我们到目前为止预测的单词上。预测序列现在包含 <s> 和“so”的整数序列。在下一个时间步中,我们将此序列作为新的输入提供给解码器。解码器将有两个输出,一个用于每个解码器的输入token。我们选择最后一个词的预测并再次使用 argmax。这次我们选择“这个”这个词。我们将其附加到预测序列中,现在是 <s>、“so”、“this”。 我们在下一个时间步中使用这个序列作为解码器。 这次解码器将有三个输出。 同样,我们使用最后一个位置的预测来选择我们的下一个单词。 我们继续迭代,直到预测到结束标记</s>。 这是对输入进行预测的代码。 这就是我们所说的将葡萄牙语句子翻译成英语的方式。 到此结束 |