《Transformers for Natural Language Processing》第四章:从零开始预训练RoBERTa模型

在本章中,我们将从零开始构建一个RoBERTa模型。该模型将使用我们在构建BERT模型所需的transformer构建工具包的基本组件。此外,我们将不使用预训练的分词器或模型。本章将按照描述的十五步骤过程来构建RoBERTa模型。

我们将利用在前几章中获得的transformer知识,逐步构建一个可以对被屏蔽的标记进行语言建模的模型。在第2章《开始使用Transformer模型的架构》中,我们介绍了原始Transformer的构建块。在第3章《BERT模型的微调》中,我们对预训练的BERT模型进行了微调。

本章将重点介绍如何使用基于Hugging Face的无缝模块,在Jupyter笔记本中从零开始构建一个预训练的transformer模型。这个模型被命名为KantaiBERT。

KantaiBERT首先加载了为本章创建的伊曼纽尔·康德的书籍合集。您将看到数据是如何获取的,还将了解如何为本笔记本创建自己的数据集。

KantaiBERT会从零开始训练自己的分词器。它将构建合并和词汇文件,在预训练过程中将使用这些文件。

然后,KantaiBERT处理数据集,初始化一个训练器,并训练模型。

最后,KantaiBERT使用训练好的模型执行一个实验性的下游语言建模任务,并使用伊曼纽尔·康德的逻辑来填充一个掩码。

在本章结束时,您将知道如何从零开始构建一个transformer模型。您将具备足够的transformer知识,以应对工业4.0时代使用强大的预训练transformer,如GPT-3引擎,需要超越开发技能来实施的挑战。本章将为您准备好第7章《GPT-3引擎带来超人级transformer的崛起》。

本章涵盖以下主题:

  • RoBERTa和DistilBERT类似的模型
  • 如何从零开始训练分词器
  • 字节级字节对编码
  • 保存训练好的分词器到文件
  • 为预训练过程重新创建分词器
  • 从零开始初始化RoBERTa模型
  • 探索模型的配置
  • 探索模型的8000万参数
  • 为训练器构建数据集
  • 初始化训练器
  • 进行预训练
  • 保存模型
  • 应用模型到掩码语言建模(MLM)的下游任务

我们的第一步将是描述我们即将构建的transformer模型。

训练一个分词器和进行预训练的transformer

在本章中,我们将使用Hugging Face提供的构建块训练一个名为KantaiBERT的transformer模型。我们在第3章《BERT模型的微调》中介绍了我们将使用的模型构建块的理论基础。

我们将在前几章中获得的知识基础上描述KantaiBERT。

KantaiBERT是基于BERT架构的Robustly Optimized BERT Pretraining Approach(RoBERTa)-like模型。

最初的BERT模型为初始的transformer模型带来了创新的特性,正如我们在第3章中所看到的。RoBERTa通过改进预训练过程的机制,提高了transformer在下游任务中的性能。

例如,它不使用WordPiece分词,而是使用字节级别的Byte-Pair Encoding(BPE)。这种方法为各种BERT和BERT-like模型的出现铺平了道路。

在本章中,KantaiBERT与BERT一样,将使用Masked Language Modeling(MLM)进行训练。MLM是一种语言建模技术,它对序列中的一个词进行屏蔽。transformer模型必须训练以预测被屏蔽的词。

KantaiBERT将作为一个小模型进行训练,具有6层、12个头和84,095,008个参数。8400万参数可能看起来很多,但这些参数分布在12个头之间,使其成为一个相对较小的模型。小型模型将使预训练体验更加顺畅,因此每个步骤都可以实时查看,而不必等待几小时才能看到结果。

KantaiBERT是类似DistilBERT的模型,因为它具有相同的6层和12个头的架构。DistilBERT是BERT的精简版本。正如其名称所示,DistilBERT的参数比RoBERTa模型要少。因此,它运行速度更快,但结果稍逊于RoBERTa模型。

我们知道大型模型能够获得卓越的性能。但如果您想在智能手机上运行一个模型怎么办?微型化一直是技术演进的关键。在实施过程中,transformer有时也需要走相同的道路。Hugging Face采用BERT的精炼版本的方法是向前迈出的一大步。使用更少参数进行精炼或者将来采用其他类似方法,这是将预训练的最佳部分变得高效以满足许多下游任务需求的聪明方式。

展示所有可能的架构非常重要,包括在智能手机上运行小型模型。然而,transformer的未来也将是即插即用的API,正如我们将在第7章《GPT-3引擎带来超人级transformer的崛起》中看到的。

KantaiBERT将实现一个字节级别的字节对编码分词器,类似于GPT-2使用的分词器。特殊标记将与RoBERTa使用的标记相同。BERT模型通常使用WordPiece分词。

没有标记类型ID来指示一个标记属于片段的哪一部分。片段将使用分隔标记分隔开来。

KantaiBERT将使用自定义数据集,训练分词器,训练transformer模型,保存模型,并运行一个MLM示例。

让我们开始从零开始构建一个transformer模型。

从零开始构建KantaiBERT

我们将从零开始以15个步骤构建KantaiBERT,并在一个MLM示例上运行它。

打开Google Colaboratory(您需要一个Gmail帐户)。然后上传KantaiBERT.ipynb,该文件位于本章的GitHub目录中。

本节的15个步骤的标题与笔记本单元格的标题类似,这使得它们易于跟踪。

让我们从加载数据集开始。

第1步:加载数据集

现成的数据集提供了一种客观的方式来训练和比较transformer模型。在第5章《使用transformer进行下游自然语言处理任务》中,我们将探索多个数据集。然而,本章的目标是理解transformer的训练过程,使用可以在实时运行而不必等待数小时才能得到结果的笔记本单元格。

我选择使用了伊曼纽尔·康德(1724-1804)的作品,他是启蒙时代的代表性德国哲学家。我的想法是引入类似于人类的逻辑和预训练的推理能力,用于下游推理任务。

Project Gutenberg(www.gutenberg.org)提供了大量可以以文本格式下载的免费电子书。您可以根据您的需要使用其他书籍来创建自定义数据集。

我将伊曼纽尔·康德的以下三本书编译到一个名为kant.txt的文本文件中:

  • 《纯粹理性批判》
  • 《实践理性批判》
  • 《道德形而上学的根本原理》

kant.txt提供了本章中用于训练transformer模型的小型训练数据集。获得的结果仍然是实验性的。对于实际项目,我会添加伊曼纽尔·康德、勒内·笛卡尔、帕斯卡和莱布尼茨等人的完整作品,以获得更全面的数据集。

这个文本文件包含了这些书籍的原始文本内容:

...For it is in reality vain to profess indifference in regard to such inquiries, the object of which cannot be indifferent to humanity.

数据集将在KantaiBERT.ipynb笔记本的第一个单元格中自动从GitHub下载。

您还可以使用Colab的文件管理器加载本章GitHub目录中的kant.txt。在这种情况下,使用curl从GitHub检索数据集:

bash 复制代码
#@title Step 1: Loading the Dataset
#1.Load kant.txt using the Colab file manager
#2.Downloading the file from GitHub
!curl -L https://raw.githubusercontent.com/Denis2054/Transformers-for-NLP-2nd-Edition/master/Chapter04/kant.txt --output "kant.txt"

一旦您加载或下载了数据集,您将在Colab文件管理器窗格中看到它出现:

请注意,Google Colab在重新启动虚拟机时会删除文件。

数据集已被定义和加载。

现在,程序将安装Hugging Face的transformers库。

第2步:安装Hugging Face的transformers库

我们需要安装Hugging Face的transformers和tokenizers库,但在这个Google Colab虚拟机实例中不需要TensorFlow:

yaml 复制代码
#@title Step 2:Installing Hugging Face Transformers
# We won't need TensorFlow here
!pip uninstall -y tensorflow
# Install 'transformers' from master
!pip install git+https://github.com/huggingface/transformers
!pip list | grep -E 'transformers|tokenizers'
# transformers version at notebook update --- 2.9.1
# tokenizers version at notebook update --- 0.7.0

输出显示已安装的版本:

Successfully built transformers
tokenizers               0.7.0          
transformers             2.10.0

Transformer的版本在发展速度相当快。您运行的版本可能会有所不同,显示方式也可能不同。

现在程序将开始训练一个分词器。

第3步:训练一个分词器

在这一部分,程序不使用预训练的分词器。例如,可以使用预训练的GPT-2分词器。但是,在本章的训练过程中,将从头开始训练一个分词器。

使用Hugging Face的ByteLevelBPETokenizer()将使用kant.txt进行训练。BPE分词器将字符串或单词分解为子串或子词。其中有许多好处,但主要有两点:

  1. 分词器可以将单词分解为最小组件,然后将这些小组件合并成统计上有趣的组合。例如,"smaller"和"smallest"可以变成"small," "er,"和"est。"分词器可以进一步划分,例如可以得到"sm"和"all"。无论如何,单词都被分解为子词标记和子词部分的较小单元,如"sm"和"all",而不仅仅是"small"。
  2. 使用WordPiece级别的编码,被分类为未知(unk_token)的字符串块几乎会消失。

在这个模型中,我们将使用以下参数训练分词器:

  • files=paths 是数据集的路径
  • vocab_size=52_000 是分词器模型长度的大小
  • min_frequency=2 是最小频率阈值
  • special_tokens=[] 是特殊标记的列表

在这种情况下,特殊标记列表包括:

  • <s>: 起始标记
  • <pad>: 填充标记
  • </s>: 终止标记
  • <unk>: 未知标记
  • <mask>: 用于语言建模的掩码标记

分词器将被训练以生成合并的子串标记并分析它们的频率。

让我们来看看句子中间的这两个词:

...the tokenizer...

第一步是对字符串进行分词:

'Ġthe', 'Ġtoken', 'izer',

现在,字符串已被分词成带有Ġ(空格)信息的标记。

下一步是用它们的索引替换它们:

程序按预期运行分词器:

ini 复制代码
#@title Step 3: Training a Tokenizer
%%time
from pathlib import Path
from tokenizers import ByteLevelBPETokenizer
paths = [str(x) for x in Path(".").glob("**/*.txt")]
# Initialize a tokenizer
tokenizer = ByteLevelBPETokenizer()
# Customize training
tokenizer.train(files=paths, vocab_size=52_000, min_frequency=2, special_tokens=[
    "<s>",
    "<pad>",
    "</s>",
    "<unk>",
    "<mask>",
])

分词器输出训练所花费的时间:

yaml 复制代码
CPU times: user 14.8 s, sys: 14.2 s, total: 29 s Wall time: 7.72 s

分词器已经训练好并准备好保存了。

第4步:将文件保存到磁盘

分词器在训练时会生成两个文件:

  • merges.txt,包含合并的标记化子串
  • vocab.json,包含标记化子串的索引

程序首先创建KantaiBERT目录,然后保存这两个文件:

lua 复制代码
#@title Step 4: Saving the files to disk
import os
token_dir = '/content/KantaiBERT'
if not os.path.exists(token_dir):
  os.makedirs(token_dir)
tokenizer.save_model('KantaiBERT')

程序输出显示已保存了这两个文件:

css 复制代码
['KantaiBERT/vocab.json', 'KantaiBERT/merges.txt']

这两个文件应该会出现在文件管理器窗格中:

在这个示例中,文件很小。您可以双击它们以查看其内容。merges.txt包含计划的标记化子串:

less 复制代码
#version: 0.2 - Trained by 'huggingface/tokenizers'
Ġ t
h e
Ġ a
o n
i n
Ġ o
Ġt he
r e
i t
Ġo f

vocab.json 包含索引:

css 复制代码
[...,"Ġthink":955,"preme":956,"ĠE":957,"Ġout":958,"Ġdut":959,"aly":960,"Ġexp":961,...]

经过训练的标记化数据集文件已准备好进行处理。

第5步:加载训练好的分词器文件

我们本可以加载预训练的分词器文件。但是,我们训练了自己的分词器,现在准备好加载这些文件:

python 复制代码
#@title Step 5 Loading the Trained Tokenizer Files 
from tokenizers.implementations import ByteLevelBPETokenizer
from tokenizers.processors import BertProcessing
tokenizer = ByteLevelBPETokenizer(
    "./KantaiBERT/vocab.json",
    "./KantaiBERT/merges.txt",
)

分词器可以对一个序列进行编码:

arduino 复制代码
tokenizer.encode("The Critique of Pure Reason.").tokens

"The Critique of Pure Reason" 将变为:

css 复制代码
['The', 'ĠCritique', 'Ġof', 'ĠPure', 'ĠReason', '.']

我们还可以要求查看此序列中的标记数:

arduino 复制代码
tokenizer.encode("The Critique of Pure Reason.")

输出将显示此序列中有6个标记:

ini 复制代码
Encoding(num_tokens=6, attributes=[ids, type_ids, tokens, offsets, attention_mask, special_tokens_mask, overflowing])

分词器现在处理标记,以适应本笔记本中使用的BERT模型变体。后处理器将添加一个起始标记和一个终止标记,例如:

less 复制代码
tokenizer._tokenizer.post_processor = BertProcessing(
    ("</s>", tokenizer.token_to_id("</s>")),
    ("<s>", tokenizer.token_to_id("<s>")),
)
tokenizer.enable_truncation(max_length=512)

让我们对一个经过后处理的序列进行编码:

arduino 复制代码
tokenizer.encode("The Critique of Pure Reason.")

输出显示现在我们有8个标记:

ini 复制代码
Encoding(num_tokens=8, attributes=[ids, type_ids, tokens, offsets, attention_mask, special_tokens_mask, overflowing])

如果我们想查看添加了什么,我们可以要求分词器对后处理的序列进行编码,通过运行以下单元格:

arduino 复制代码
tokenizer.encode("The Critique of Pure Reason.").tokens

输出显示已添加了起始和终止标记,这使得标记的数量增加到了8个,包括起始和终止标记:

css 复制代码
['<s>', 'The', 'ĠCritique', 'Ġof', 'ĠPure', 'ĠReason', '.', '</s>']

训练模型的数据现在准备好了。我们现在将检查运行笔记本的计算机的系统信息。

第6步:检查资源限制:GPU和CUDA

KantaiBERT在图形处理单元(GPU)上以最佳速度运行。

我们首先运行一个命令来查看是否存在NVIDIA GPU卡:

ruby 复制代码
#@title Step 6: Checking Resource Constraints: GPU and NVIDIA 
!nvidia-smi

输出显示了卡的信息和版本:

输出可能会随着每个Google Colab虚拟机配置而有所不同。

现在,我们将检查以确保PyTorch可以看到CUDA:

ini 复制代码
#@title Checking that PyTorch Sees CUDA
import torch
torch.cuda.is_available()

结果应该为True:

True

Compute Unified Device Architecture (CUDA)是由NVIDIA开发的,用于利用其GPU的并行计算能力。

有关NVIDIA GPU和CUDA的更多信息,请参见附录II《Transformer模型的硬件限制》。

现在,我们已经准备好定义模型的配置。

第7步:定义模型的配置

我们将使用与DistilBERT transformer相同数量的层和头来进行RoBERTa类型的预训练。模型的词汇量大小设置为52,000,具有12个注意力头和6个层:

ini 复制代码
#@title Step 7: Defining the configuration of the Model
from transformers import RobertaConfig
config = RobertaConfig(
    vocab_size=52_000,
    max_position_embeddings=514,
    num_attention_heads=12,
    num_hidden_layers=6,
    type_vocab_size=1,
)

我们将在第9步《从头初始化模型》中更详细地探讨配置。

首先,让我们在我们的模型中重新创建分词器。

第8步:在transformers中重新加载分词器

现在,我们已经准备好加载我们训练过的分词器,即使用RobertaTokenizer.from_pretained()加载的预训练分词器:

ini 复制代码
#@title Step 8: Re-creating the Tokenizer in Transformers
from transformers import RobertaTokenizer
tokenizer = RobertaTokenizer.from_pretrained("./KantaiBERT", max_length=512)

既然我们已经加载了我们训练过的分词器,让我们从头开始初始化一个RoBERTa模型。

第9步:从头初始化模

在这一部分,我们将从头初始化一个模型,并检查模型的大小。

程序首先导入了用于语言建模的RoBERTa遮蔽模型:

python 复制代码
#@title Step 9: Initializing a Model From Scratch
from transformers import RobertaForMaskedLM

模型是根据第7步中定义的配置进行初始化的:

arduino 复制代码
model = RobertaForMaskedLM(config=config)

如果我们打印模型,我们可以看到它是一个具有6层和12个注意力头的BERT模型:

scss 复制代码
print(model)

原始Transformer模型的编码器的构建块以不同的维度存在,如输出摘录所示:

ini 复制代码
RobertaForMaskedLM(
  (roberta): RobertaModel(
    (embeddings): RobertaEmbeddings(
      (word_embeddings): Embedding(52000, 768, padding_idx=1)
      (position_embeddings): Embedding(514, 768, padding_idx=1)
      (token_type_embeddings): Embedding(1, 768)
      (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
      (dropout): Dropout(p=0.1, inplace=False)
    )
    (encoder): BertEncoder(
      (layer): ModuleList(
        (0): BertLayer(
          (attention): BertAttention(
            (self): BertSelfAttention(
              (query): Linear(in_features=768, out_features=768, bias=True)
              (key): Linear(in_features=768, out_features=768, bias=True)
              (value): Linear(in_features=768, out_features=768, bias=True)
              (dropout): Dropout(p=0.1, inplace=False)
            )
            (output): BertSelfOutput(
              (dense): Linear(in_features=768, out_features=768, bias=True)
              (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
              (dropout): Dropout(p=0.1, inplace=False)
            )
          )
          (intermediate): BertIntermediate(
            (dense): Linear(in_features=768, out_features=3072, bias=True)
          )
          (output): BertOutput(
            (dense): Linear(in_features=3072, out_features=768, bias=True)
            (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
.../...

花一些时间仔细查看配置的输出细节,然后继续。您将从内部了解模型。

transformer的LEGO®式构建块使其成为有趣的分析对象。例如,您将注意到丢失正则化在所有子层中都存在。

现在,让我们探索参数。

探索参数

模型很小,包含84,095,008个参数。

我们可以检查它的大小:

scss 复制代码
print(model.num_parameters())

输出显示了大约的参数数量,这可能会根据不同的transformer版本而有所不同: 84095008

现在让我们深入研究参数。首先,我们将参数存储在LP中,并计算参数列表的长度:

ini 复制代码
#@title Exploring the Parameters
LP=list(model.parameters())
lp=len(LP)
print(lp)

输出显示大约有108个矩阵和向量,这可能会根据不同的transformer模型而有所不同: 108

现在,让我们显示包含这些矩阵和向量的108个张量:

scss 复制代码
for p in range(0,lp):
  print(LP[p])

输出显示了所有参数,如下所示的部分输出摘录:

ini 复制代码
Parameter containing:
tensor([[-0.0175, -0.0210, -0.0334,  ...,  0.0054, -0.0113,  0.0183],
        [ 0.0020, -0.0354, -0.0221,  ...,  0.0220, -0.0060, -0.0032],
        [ 0.0001, -0.0002,  0.0036,  ..., -0.0265, -0.0057, -0.0352],
        ...,
        [-0.0125, -0.0418,  0.0190,  ..., -0.0069,  0.0175, -0.0308],
        [ 0.0072, -0.0131,  0.0069,  ...,  0.0002, -0.0234,  0.0042],
        [ 0.0008,  0.0281,  0.0168,  ..., -0.0113, -0.0075,  0.0014]],
       requires_grad=True)

花几分钟的时间来查看参数,以了解transformer是如何构建的。

参数的数量是通过取模型中的所有参数并将它们相加来计算的,例如:

  • 词汇表(52,000)x 维度(768)
  • 向量的大小为1 x 768
  • 其他许多维度

您会注意到dmodel = 768。模型中有12个注意力头。因此,每个头的dk维度将是

这再次展示了transformer构建块的优化LEGO®概念。

现在,我们将看到如何计算模型的参数数量,以及如何达到84,095,008这个数字。

如果我们在笔记本中悬停在LP上,我们将看到一些torch张量的形状:

请注意,这些数字可能会根据您使用的transformers模块的版本而有所不同。

我们将进一步计算每个张量的参数数量。首先,程序初始化一个名为np(参数数量)的参数计数器,并遍历参数列表中的元素数量(108):

ini 复制代码
#@title Counting the parameters
np=0
for p in range(0,lp):#number of tensors

这些参数是不同大小的矩阵和向量,例如:

  • 768 x 768
  • 768 x 1
  • 768

我们可以看到一些参数是二维的,而另一些是一维的。

一个简单的方法来确定参数列表LP[p]中的参数p是否具有两个维度是通过执行以下操作:

ini 复制代码
PL2=True
  try:
    L2=len(LP[p][0]) #check if 2D
  except:
    L2=1             #not 2D but 1D
    PL2=False

如果参数具有两个维度,其第二维度将是L2>0和PL2=True(2个维度=True)。如果参数只有一个维度,其第二维度将是L2=1和PL2=False(2个维度=False)。

L1是参数的第一个维度的大小。L3是由以下方式定义的参数的大小:

ini 复制代码
L1=len(LP[p])      
L3=L1*L2

现在,我们可以在循环的每个步骤中累加参数:

makefile 复制代码
np+=L3             # number of parameters per tensor

我们将得到参数的总和,但我们也想确切地看到transformer模型的参数数量是如何计算的:

ini 复制代码
if PL2==True:
    print(p,L1,L2,L3)  # displaying the sizes of the parameters
  if PL2==False:
    print(p,L1,L3)  # displaying the sizes of the parameters
print(np)              # total number of parameters

请注意,如果参数只有一个维度,PL2=False,那么我们只显示第一个维度。

输出是模型中所有张量的参数数量是如何计算的列表,如下摘录所示:

0 52000 768 39936000
1 514 768 394752
2 1 768 768
3 768 768
4 768 768
5 768 768 589824
6 768 768
7 768 768 589824
8 768 768
9 768 768 589824
10 768 768

RoBERTa模型的总参数数量显示在列表的末尾:

84,095,008

参数数量可能会根据所使用的库版本而有所不同。

现在我们确切知道了transformer模型中的参数数量代表什么。花几分钟回顾一下配置的输出、参数的内容和参数的大小。此时,您将对模型的构建块有一个精确的心理表现。

程序现在将构建数据集。

第10步:构建数据集

程序现在将逐行加载数据集,以生成用于批量训练的样本,block_size=128 限制了一个示例的长度:

ini 复制代码
#@title Step 10: Building the Dataset
%%time
from transformers import LineByLineTextDataset
dataset = LineByLineTextDataset(
    tokenizer=tokenizer,
    file_path="./kant.txt",
    block_size=128,
)

输出显示Hugging Face在优化数据处理时间方面投入了大量资源:

yaml 复制代码
CPU times: user 8.48 s, sys: 234 ms, total: 8.71 s
Wall time: 3.88 s

墙时,实际处理器活动的时间,已经经过了优化。

现在,程序将定义一个数据收集器来创建一个用于反向传播的对象。

第11步:定义数据收集器

在初始化训练器之前,我们需要运行一个数据收集器。数据收集器将从数据集中获取样本并将它们汇总成批次。结果是类似字典的对象。

我们通过设置mlm=True来为MLM准备批量样本处理。

我们还将训练mlm_probability=0.15的口罩令牌数量设置为0.15。这将决定在预训练过程中口罩的令牌百分比。

现在,我们使用我们的分词器、激活了MLM,并将口罩令牌的比例设置为0.15来初始化数据收集器:

ini 复制代码
#@title Step 11: Defining a Data Collator
from transformers import DataCollatorForLanguageModeling
data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer, mlm=True, mlm_probability=0.15
)

现在,我们已经准备好初始化训练器。

第12步:初始化训练器

前面的步骤已准备好初始化训练器所需的信息。数据集已经进行了标记和加载。我们的模型已经构建好了。数据收集器已经创建好了。

程序现在可以初始化训练器。出于教育目的,程序会快速训练模型。训练的时代数量被限制为一。由于我们可以共享批次并多进程训练任务,因此GPU非常有用:

ini 复制代码
#@title Step 12: Initializing the Trainer
from transformers import Trainer, TrainingArguments
training_args = TrainingArguments(
    output_dir="./KantaiBERT",
    overwrite_output_dir=True,
    num_train_epochs=1,
    per_device_train_batch_size=64,
    save_steps=10_000,
    save_total_limit=2,
)
trainer = Trainer(
    model=model,
    args=training_args,
    data_collator=data_collator,
    train_dataset=dataset,
)

现在模型已经准备好进行训练。

第13步:预训练模型

一切准备就绪。只需一行代码即可启动训练器:

perl 复制代码
#@title Step 13: Pre-training the Model
%%time
trainer.train()

输出以实时方式显示训练过程,显示损失、学习率、时代和步骤:

yaml 复制代码
Epoch: 100%
1/1 [17:59<00:00, 1079.91s/it]
Iteration: 100%
2672/2672 [17:59<00:00, 2.47it/s]
{"loss": 5.6455852394104005, "learning_rate": 4.06437125748503e-05, "epoch": 0.18712574850299402, "step": 500}
{"loss": 4.940259679794312, "learning_rate": 3.12874251497006e-05, "epoch": 0.37425149700598803, "step": 1000}
{"loss": 4.639936000347137, "learning_rate": 2.1931137724550898e-05, "epoch": 0.561377245508982, "step": 1500}
{"loss": 4.361462069988251, "learning_rate": 1.2574850299401197e-05, "epoch": 0.7485029940119761, "step": 2000}
{"loss": 4.228510192394257, "learning_rate": 3.218562874251497e-06, "epoch": 0.9356287425149701, "step": 2500}
CPU times: user 11min 36s, sys: 6min 25s, total: 18min 2s
Wall time: 17min 59s
TrainOutput(global_step=2672, training_loss=4.7226536670130885)

模型已经训练好了。现在是时候保存我们的工作了。

第14步:将最终模型(+分词器+配置)保存到磁盘

现在我们将保存模型和配置:

ruby 复制代码
#@title Step 14: Saving the Final Model(+tokenizer + config) to disk
trainer.save_model("./KantaiBERT")

点击文件管理器中的"刷新",文件应该会出现:

现在,config.json、pytorh_model.bin 和 training_args.bin 应该会出现在文件管理器中。

merges.txt 和 vocab.json 包含了数据集的预训练标记。

我们已经从头开始构建了一个模型。现在让我们导入pipeline,使用我们的预训练模型和分词器执行语言建模任务。

第15步:使用FillMaskPipeline进行语言建模

现在,我们将导入一个语言建模的填充掩码任务。我们将使用我们训练好的模型和分词器来执行MLM:

ini 复制代码
#@title Step 15: Language Modeling with the FillMaskPipeline
from transformers import pipeline
fill_mask = pipeline(
    "fill-mask",
    model="./KantaiBERT",
    tokenizer="./KantaiBERT"
)

现在,我们可以要求我们的模型思考像伊曼纽尔·康德一样:

scss 复制代码
fill_mask("Human thinking involves human <mask>.")

每次运行后,输出可能会发生变化,因为我们是从零开始使用有限数量的数据进行预训练。但是,本次运行获得的输出很有趣,因为它引入了概念性语言建模:

css 复制代码
[{'score': 0.022831793874502182,  'sequence': '<s> Human thinking involves human reason.</s>',  'token': 393}, {'score': 0.011635891161859035,  'sequence': '<s> Human thinking involves human object.</s>',  'token': 394}, {'score': 0.010641072876751423,  'sequence': '<s> Human thinking involves human priori.</s>',  'token': 575}, {'score': 0.009517930448055267,  'sequence': '<s> Human thinking involves human conception.</s>',  'token': 418}, {'score': 0.00923212617635727,  'sequence': '<s> Human thinking involves human experience.</s>',  'token': 531}]

预测可能会因每次运行和每次Hugging Face更新其模型而有所变化。

然而,以下输出经常出现:

Human thinking involves human reason

这里的目标是看如何训练一个变换器模型。我们可以看到可以进行有趣的类似人类的预测。

这些结果是实验性的,会在训练过程中发生变化。它们会在每次重新训练模型时发生变化。

该模型需要更多来自启蒙时代其他思想家的数据。

然而,这个模型的目标是展示我们可以创建数据集来训练一个特定类型的复杂语言建模任务的变换器。

感谢变换器,我们只是迈入了人工智能的新时代的开端!

下一步

你已经从零开始训练了一个变换器模型。花一些时间想象一下在个人或公司环境中你可以做什么。你可以为特定任务创建一个数据集,并从头开始训练它。利用你的兴趣领域或公司项目,进行有关变换器构建工具的实验!

一旦你制作出喜欢的模型,你可以与Hugging Face社区分享。你的模型将出现在Hugging Face模型页面上:huggingface.co/models

你可以使用此页面上描述的指南,在几个步骤内上传你的模型:huggingface.co/transformer...

你还可以下载Hugging Face社区共享的模型,以获取有关个人和专业项目的新想法。

总结

在这一章中,我们使用Hugging Face提供的构建模块,从零开始构建了KantaiBERT,一个类似RoBERTa的模型变换器。

我们首先加载了一个关于伊曼纽尔·康德(Immanuel Kant)作品的特定主题的自定义数据集。你可以根据自己的目标加载现有数据集或创建自己的数据集。我们看到,使用自定义数据集可以洞察变换器模型的思维方式。然而,这种实验性的方法有其限制。要训练一个用于非教育目的的模型,需要一个更大的数据集。

KantaiBERT项目用于在kant.txt数据集上训练一个分词器。训练后的merges.txt和vocab.json文件被保存。我们使用预训练文件重新创建了一个分词器。KantaiBERT构建了自定义数据集,并定义了一个数据整理器,以处理用于反向传播的训练批次。然后初始化了训练器,并详细探讨了RoBERTa模型的参数。最后,模型进行了训练并保存。

最后,加载了保存的模型,用于下游语言建模任务。目标是使用伊曼纽尔·康德的逻辑来填充掩码。

现在,大门敞开着,等待你在现有或自定义的数据集上进行实验,看看你能获得什么结果。你可以与Hugging Face社区分享你的模型。变换器是数据驱动的。你可以利用这一点来发现使用变换器的新方法。

你现在已经准备好学习如何运行不需要预训练或微调的API的现成变换器引擎。第7章《具有GPT-3引擎的超人类变换器的崛起》将带你进入AI的未来。有了这一章和之前章节的知识,你将做好准备!

在下一章《使用变换器进行下游NLP任务》中,我们将继续准备实施变换器。

相关推荐
Evand J22 分钟前
深度学习的应用综述
深度学习
sp_fyf_20241 小时前
[大语言模型-论文精读] 更大且更可指导的语言模型变得不那么可靠
人工智能·深度学习·神经网络·搜索引擎·语言模型·自然语言处理
肖遥Janic2 小时前
Stable Diffusion绘画 | 插件-Deforum:商业LOGO广告视频
人工智能·ai·ai作画·stable diffusion
我就是全世界3 小时前
一起了解AI的发展历程和AGI的未来展望
人工智能·agi
CV肉饼王3 小时前
基于CNN的水果分类与模型调优实验
深度学习·计算机视觉
colorknight3 小时前
1.2.3 HuggingFists安装说明-MacOS安装
人工智能·低代码·macos·huggingface·数据科学·ai agent
kuan_li_lyg3 小时前
MATLAB - 机械臂手眼标定(眼在手内) - 估计安装在机器人上的移动相机的姿态
开发语言·人工智能·matlab·机器人·ros·机械臂·手眼标定
山川而川-R3 小时前
Windows安装ollama和AnythingLLM
人工智能·python·语言模型·自然语言处理
Kuekua-seu4 小时前
diffusion vs GAN
人工智能·神经网络·生成对抗网络
电子科技圈4 小时前
IAR全面支持国科环宇AS32X系列RISC-V车规MCU
人工智能·嵌入式硬件·mcu·编辑器