如何从零开始训练BERT模型

article/2025/9/15 14:08:12

我的许多文章都专注于 BERT——这个模型出现并主导了自然语言处理 (NLP) 的世界,标志着语言模型的新时代。

对于那些之前可能没有使用过 Transformer 模型(例如 BERT 是什么)的人,这个过程看起来有点像这样:

  • pip 安装Transformer
  • 初始化一个预训练的 Transformer 模型 — from_pretrained。
  • 在一些数据上测试它。
  • 也许微调模型(再训练一些)。

现在,这是一个很好的方法,但如果我们只这样做,我们就会缺乏对创建我们自己的 Transformer 模型的理解。

而且,如果我们不能创建自己的 Transformer 模型——我们必须依赖于一个适合我们问题的预训练模型,但情况并非总是如此:

因此,在本文中,我们将探讨构建我们自己的 Transformer 模型必须采取的步骤——特别是 BERT 的进一步开发版本,称为 RoBERTa。

概述

这个过程有几个步骤,所以在我们深入研究之前,让我们先总结一下我们需要做什么。 总的来说,有四个关键部分:

  • 获取数据
  • 构建分词器
  • 创建输入管道
  • 训练模型

一旦我们完成了这些部分中的每一个,我们将使用我们构建的标记器和模型 - 并将它们保存起来,以便我们可以像通常使用 from_pretrained 一样的方式使用它们。

获取数据

与任何机器学习项目一样,我们需要数据。 在用于训练 Transformer 模型的数据方面,我们几乎可以使用任何文本数据。

而且,如果我们在互联网上有很多东西——那就是非结构化文本数据。

从互联网上抓取的文本领域中最大的数据集之一是 OSCAR 数据集。

OSCAR 数据集拥有大量不同的语言——从头开始训练最清晰的用例之一是我们可以将 BERT 应用于一些不太常用的语言,例如泰卢固语或纳瓦霍语。

我的语言是英语——但我的女朋友是意大利人,所以她——劳拉,将评估我们讲意大利语的 BERT 模型——FiliBERTo 的结果。

因此,要下载 OSCAR 数据集的意大利语部分,我们将使用 HuggingFace 的数据集库——我们可以使用 pip install datasets 安装它。 然后我们下载 OSCAR_IT :

from datasets import load_dataset
dataset = load_dataset('oscar', 'unshuffled_deduplicated_it')

我们来看看数据集对象。

现在让我们以一种可以在构建分词器时使用的格式存储我们的数据。 我们需要创建一组仅包含数据集中文本特征的纯文本文件,我们将使用换行符 \n 拆分每个样本。

from tqdm.auto import tqdmtext_data = []
file_count = 0for sample in tqdm(dataset['train']):sample = sample['text'].replace('\n', '')text_data.append(sample)if len(text_data) == 10_000:# once we git the 10K mark, save to filewith open(f'../../data/text/oscar_it/text_{file_count}.txt', 'w', encoding='utf-8') as fp:fp.write('\n'.join(text_data))text_data = []file_count += 1
# after saving in 10K chunks, we will have ~2082 leftover samples, we save those now too
with open(f'../../data/text/oscar_it/text_{file_count}.txt', 'w', encoding='utf-8') as fp:fp.write('\n'.join(text_data))

在我们的 data/text/oscar_it 目录中,我们会发现:

构建分词器

接下来是标记器! 在使用转换器时,我们通常会加载一个分词器,连同其各自的转换器模型——分词器是该过程中的关键组件。

在构建我们的分词器时,我们将为它提供我们所有的 OSCAR 数据,指定我们的词汇量大小(分词器中的标记数)和任何特殊标记。

现在,RoBERTa 特殊令牌如下所示:

okenUse
<s>Beginning of sequence (BOS) or classifier (CLS) token
</s>End of sequence (EOS) or seperator (SEP) token
<unk>Unknown token
<pad>Padding token
<mask>Masking token

因此,我们确保将它们包含在标记器的 train 方法调用的 special_tokens 参数中。

from pathlib import Path
paths = [str(x) for x in Path('../../data/text/oscar_it').glob('**/*.txt')]
from tokenizers import ByteLevelBPETokenizer
tokenizer = ByteLevelBPETokenizer()
tokenizer.train(files=paths[:5], vocab_size=30_522, min_frequency=2,special_tokens=['<s>', '<pad>', '</s>', '<unk>', '<mask>'])

我们的分词器现在已经准备好了,我们可以保存它的文件以备后用:

import os
os.mkdir('./filiberto')
tokenizer.save_model('filiberto')

现在我们有两个文件定义了我们的新 FiliBERTo 分词器:

 merges.txt — 执行文本到标记的初始映射vocab.json — 将令牌映射到令牌 ID

有了这些,我们可以继续初始化我们的分词器,以便我们可以像使用任何其他 from_pretrained 分词器一样使用它。

初始化分词器

我们首先使用我们之前构建的两个文件来初始化分词器——使用一个简单的 from_pretrained:

from transformers import RobertaTokenizer# initialize the tokenizer using the tokenizer we initialized and saved to file
tokenizer = RobertaTokenizer.from_pretrained('filiberto', max_len=512)

现在我们的标记器已经准备好了,我们可以尝试用它编码一些文本。 编码时,我们使用与通常使用的两种方法相同的方法,encode 和 encode_batch。

# test our tokenizer on a simple sentence
tokens = tokenizer('ciao, come va?')

从编码对象标记中,我们将提取 input_ids 和 attention_mask 张量以与 FiliBERTo 一起使用。

创建输入管道

我们训练过程的输入管道是整个过程中比较复杂的部分。 它包括我们获取原始 OSCAR 训练数据,对其进行转换,然后将其加载到准备进行训练的 DataLoader 中。

准备数据

我们将从一个示例开始,然后完成准备逻辑。

首先,我们需要打开我们的文件——我们之前保存为 .txt 文件的相同文件。 我们根据换行符 \n 拆分每个,因为这表示单个样本。

with open('../../data/text/oscar_it/text_0.txt', 'r', encoding='utf-8') as fp:lines = fp.read().split('\n')

然后我们使用 encode_batch 方法对我们的数据进行编码。

batch = tokenizer.encode_batch(lines)
len(batch)

现在我们可以继续创建我们的张量——我们将通过掩码语言建模 (MLM) 来训练我们的模型。 所以,我们需要三个张量:

 input_ids — 我们的 token_ids,其中约 15% 的令牌使用掩码令牌 <mask> 进行掩码。attention_mask——一个 1 和 0 的张量,标记“真实”标记/填充标记的位置——用于注意力计算。
labels——我们的 token_ids 没有屏蔽。

如果你不熟悉MLM,我以前的文章已经解释了。

我们的 attention_mask 和标签张量只是从我们的批次中提取的。 但是input_ids 张量需要更多操作,对于这个张量,我们屏蔽了大约 15% 的标记——为它们分配标记 ID 3。

import torchlabels = torch.tensor([x.ids for x in batch])
mask = torch.tensor([x.attention_mask for x in batch])# make copy of labels tensor, this will be input_ids
input_ids = labels.detach().clone()
# create random array of floats with equal dims to input_ids
rand = torch.rand(input_ids.shape)
# mask random 15% where token is not 0 [PAD], 1 [CLS], or 2 [SEP]
mask_arr = (rand < .15) * (input_ids != 0) * (input_ids != 1) * (input_ids != 2)
# loop through each row in input_ids tensor (cannot do in parallel)
for i in range(input_ids.shape[0]):# get indices of mask positions from mask arrayselection = torch.flatten(mask_arr[i].nonzero()).tolist()# mask input_idsinput_ids[i, selection] = 3  # our custom [MASK] token == 3

在最终输出中,我们可以看到编码的 input_ids 张量的一部分。 第一个令牌 ID 是 1 — [CLS] 令牌。 在张量周围,我们有几个 3 个令牌 ID——这些是我们新添加的 [MASK] 令牌。

构建dataloader

接下来,我们定义我们的 Dataset 类——我们用它来将我们的三个编码张量初始化为 PyTorch torch.utils.data.Dataset 对象。

encodings = {'input_ids': input_ids, 'attention_mask': mask, 'labels': labels}
class Dataset(torch.utils.data.Dataset):def __init__(self, encodings):# store encodings internallyself.encodings = encodingsdef __len__(self):# return the number of samplesreturn self.encodings['input_ids'].shape[0]def __getitem__(self, i):# return dictionary of input_ids, attention_mask, and labels for index ireturn {key: tensor[i] for key, tensor in self.encodings.items()}dataset = Dataset(encodings)loader = torch.utils.data.DataLoader(dataset, batch_size=16, shuffle=True)

最后,我们的数据集被加载到 PyTorch DataLoader 对象中——我们使用它在训练期间将数据加载到我们的模型中。

训练模型

我们需要两件东西来训练,我们的 DataLoader 和一个模型。 我们拥有的 DataLoader — 但没有模型。

初始化模型

对于训练,我们需要一个原始的(未预训练的)BERTLMHeadModel。 要创建它,我们首先需要创建一个 RoBERTa 配置对象来描述我们想要用来初始化 FiliBERTo 的参数。

from transformers import RobertaConfigconfig = RobertaConfig(vocab_size=30_522,  # we align this to the tokenizer vocab_sizemax_position_embeddings=514,hidden_size=768,num_attention_heads=12,num_hidden_layers=6,type_vocab_size=1
)

然后,我们使用语言建模 (LM) 头导入并初始化我们的 RoBERTa 模型。

from transformers import RobertaForMaskedLMmodel = RobertaForMaskedLM(config)

训练准备

在进入我们的训练循环之前,我们需要设置一些东西。 首先,我们设置 GPU/CPU 使用率。 然后我们激活我们模型的训练模式——最后,初始化我们的优化器。

device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')
# and move our model over to the selected device
model.to(device)from transformers import AdamW# activate training mode
model.train()
# initialize optimizer
optim = AdamW(model.parameters(), lr=1e-4)

训练

最后——训练时间! 我们按照通常通过 PyTorch 进行培训时的方式进行训练。

epochs = 2for epoch in range(epochs):# setup loop with TQDM and dataloaderloop = tqdm(loader, leave=True)for batch in loop:# initialize calculated gradients (from prev step)optim.zero_grad()# pull all tensor batches required for traininginput_ids = batch['input_ids'].to(device)attention_mask = batch['attention_mask'].to(device)labels = batch['labels'].to(device)# processoutputs = model(input_ids, attention_mask=attention_mask,labels=labels)# extract lossloss = outputs.loss# calculate loss for every parameter that needs grad updateloss.backward()# update parametersoptim.step()# print relevant info to progress barloop.set_description(f'Epoch {epoch}')loop.set_postfix(loss=loss.item())

model.save_pretrained('./filiberto')  # and don't forget to save filiBERTo!

如果我们继续使用 Tensorboard,随着时间的推移,我们会发现我们的损失——它看起来很有希望。

最终测试

现在是进行真正测试的时候了。 我们建立了一个MLM管道——并请劳拉评估结果。

我们首先使用“fill-mask”参数初始化一个管道对象。 然后像这样开始测试我们的模型:

from transformers import pipeline
fill = pipeline('fill-mask', model='filiberto', tokenizer='filiberto')
fill(f'ciao {fill.tokenizer.mask_token} va?')

看样子还不错,接下来是复杂的短语

最后,再来一句更难的话,“cosa sarebbe successo se avessimo scelto un altro giorno?” ——或者“如果我们选择另一天会发生什么?”:

总的来说,看起来我们的模型通过了劳拉的测试——我们现在有一个名为 FiliBERTo 的意大利语模型!

这就是从头开始训练 BERT 模型的演练!

我们已经涵盖了很多方面,从获取和格式化我们的数据——一直到使用语言建模来训练我们的原始 BERT 模型。

作者:James Briggs


http://chatgpt.dhexx.cn/article/JpijegNu.shtml

相关文章

BERT模型详解

Auto-Regressive & Auto-Encoding 在介绍当下最火热的BERT模型之前&#xff0c;我们先来看两个概念&#xff0c;Auto-Regressive和Auto-Encoding。 Auto-Regressive Auto-Regressive如上图所示&#xff0c;其实很像是一个语言模型&#xff0c;遵循的是链式法则&#xff0…

BERT(预训练Transformer模型)

目录 一、前言 二、随机遮挡&#xff0c;进行预测 三、两句话是否原文相邻 四、两者结合起来 五、总结 六、参考链接 一、前言 Bert在18年提出&#xff0c;19年发表&#xff0c;Bert的目的是为了预训练Transformer模型encoder网络&#xff0c;从而大幅提高准确率 Bert …

详细介绍BERT模型

文章目录 BERT简介BERT, OpenAI GPT, 和ELMo之间的区别相关工作BERT的改进 BERT 的详细实现输入/输出表示预训练BERT微调BERT BERT用在下游任务GLUE(一个自然语言任务集合)SQuAD v1.1(QA数据集)SQuAD v2.0SWAG 消融实验预训练任务的影响模型大小的影响基于特征的BERT方法 结论 …

BERT模型—2.BERT模型预训练与微调

文章目录 引言一、预训练语言模型1.为什么要进行预训练&#xff1f;2. BERT预训练-掩码预测任务3.BERT预训练-下句预测任务 二、BERT的文本处理—subword tokenizer1. 为什么要学习subword tokenizer?2. 词表生成与分词 三、BERT embedding四、BERT微调—句子分类任务五、BERT…

图解BERT模型:从零开始构建BERT

转载&#xff1a;https://cloud.tencent.com/developer/article/1389555 本文首先介绍BERT模型要做什么&#xff0c;即&#xff1a;模型的输入、输出分别是什么&#xff0c;以及模型的预训练任务是什么&#xff1b;然后&#xff0c;分析模型的内部结构&#xff0c;图解如何将模…

BERT模型—1.BERT模型架构

文章目录 引言一、Bert模型总览二、注意力机制1.Seq2seq中的注意力操作2.注意力的一般形式&#xff08;三步曲&#xff09;3. transformer中的自注意力机制—Self.Attention4. transformer的多头注意力机制5. scaling6. 模型优化技巧&#xff1a;残差连接 三、BERT其他结构特性…

【科研】BERT模型理论详解

原文&#xff1a;https://cloud.tencent.com/developer/article/1389555 本文首先介绍BERT模型要做什么&#xff0c;即&#xff1a;模型的输入、输出分别是什么&#xff0c;以及模型的预训练任务是什么&#xff1b;然后&#xff0c;分析模型的内部结构&#xff0c;图解如何将模…

BERT模型的详细介绍

1.BERT 的基本原理是什么&#xff1f; BERT 来自 Google 的论文Pre-training of Deep Bidirectional Transformers for Language Understanding&#xff0c;BERT 是“Bidirectional Encoder Representations from Transformers”的首字母缩写&#xff0c;整体是一个自编码语言…

图解BERT模型

1. 模型的输入/输出 BERT模型的全称是&#xff1a;BidirectionalEncoder Representations from Transformer。从名字中可以看出&#xff0c;BERT模型的目标是利用大规模无标注语料训练、获得文本的包含丰富语义信息的Representation&#xff0c;即&#xff1a;文本的语义表示&…

BERT模型

BERT模型 Paper: https://arxiv.org/abs/1810.04805 BERT 全称为Bidirectional Encoder Representation from Transformers&#xff08;来自Transformers的双向编码表示&#xff09;&#xff0c;谷歌发表的发的论文Pre-traning of Deep Bidirectional Transformers for Langu…

BERT模型的结构,特点和实践

BERT模型介绍&#xff0c;特点&#xff0c;资源 一、背景介绍二、BERT与语言模型1.feature-based2.fine-tuning3.BERT的语言模型 三、BERT整体概览1. BERT框架2. BERT模型结构2.1输入部分2.2 BERT的预训练任务任务1&#xff1a;Masked Language Model(MLM)任务2&#xff1a;Nex…

Bert算法:语言模型-BERT详细介绍

本文的目的是向NLP爱好者们详细解析一个著名的语言模型-BERT。 全文将分4个部分由浅入深的依次讲解。1.Bert简介 BERT是2018年10月由Google AI研究院提出的一种预训练模型。 BERT的全称是Bidirectional Encoder Representation from Transformers。BERT在机器阅读理解顶级水平…

Bert 模型学习

一、Bert 模型 BERT 模型的全称是 BidirectionalEncoder Representations from Transformer&#xff0c;基于 Transformer 的双向编码器表示&#xff0c;是一个预训练的语言表征模型&#xff0c;它强调了不再像以往一样采用传统的单向语言模型或者把两个单向语言模型进行浅层拼…

【理论篇】是时候彻底弄懂BERT模型了(收藏)

引言 本文对BERT模型的理论进行了一个非常详尽的解释&#xff0c;相信看完本篇文章后&#xff0c;你对BERT模型的理解会上升&#x1f680;一个层次。 本文是理论篇&#xff0c;下篇是实战篇。 BERT的基本思想 BERT如此成功的一个原因之一是它是基于上下文(context-based)的嵌…

Hive !!!

文章目录 Hive数据处理方法论step1 了解产品/运营的需求step2 数据收集&#xff08;数据准备&#xff09;step3 数据处理&#xff08;数据调研&#xff09;step4 数据分析 Hive背景Hive简介引入原因安装hive注意事项一些代码Hive SQL VS SQL Hive的数据类型数据类型Hive架构 Hi…

Shark简介

简介 Shark是一个新的数据分析系统&#xff0c;在集群上进行查询处理和复杂分析。Shark使用一种新的分布式内存抽象&#xff0c;为SQL查询和复杂分析函数提供了统一的运行引擎&#xff0c;并能够有效的容错。这种新的分布式内存抽象使得shark执行SQL查询的速度比Hive快100倍&a…

Vue3中shallowReactive 与 shallowRef 的用法

shallowReactive 与 shallowRef shallowReactive&#xff1a;只处理对象最外层属性的响应式&#xff08;浅响应式&#xff09;。 shallowRef&#xff1a;只处理基本数据类型的响应式, 不进行对象的响应式处理。 什么时候使用? 如果有一个对象数据&#xff0c;结构比较深, 但…

2018华为软挑--模拟退火+FF解决装箱问题【C++代码】

算法简介&#xff1a; 装箱问题是一个NP完全问题&#xff0c;求解全局最优解有很多种方法&#xff1a;遗传算法、禁忌搜索算法、蚁群算法、模拟退火算法等等&#xff0c;本次使用模拟退火&#xff0c;它的优点是在参数合适的情况下基本上可以100%得到全局最优解&#xff0c;缺点…

2020华为软挑热身赛代码开源-思路大起底(华为软件精英挑战赛编程闯关)

本文首发于个人公众号【两猿社】&#xff0c;后台回复【华为】&#xff0c;获取完整开源代码链接。 昵称&#xff1a;lou_shang_shi_bian_tai 成绩:0.032 社长没有针对硬件做任何优化&#xff0c;热身赛成绩也一般。但有些比赛的trick我想与大家一起分享&#xff0c;希望对继续…

2021华为软挑-成渝复赛复盘

成渝赛区 团队名&#xff1a;newWorld 初赛 rank 22&#xff0c;复赛 rank 22。 github源码&#xff1a;https://github.com/Yin-Freedom/codecraft_2021 赛题介绍 赛题网址&#xff1a;https://competition.huaweicloud.com/advance/1000041380/circumstance 本次赛题来源…