《如何为遮蔽语言建模任务训练BERT》

如何训练BERT来应对语言遮蔽任务

使用Python和Transformers库从零开始构建用于MLM任务的语言模型的实践指南

介绍

近年来,大型语言模型(LLM)引起了机器学习社区的关注。在LLM出现之前,我们对各种语言建模技术进行了关键的研究阶段,包括掩码语言建模、因果语言建模和序列到序列语言建模。

从上述列表中,像BERT这样的掩码语言模型在下游自然语言处理任务(如分类和聚类)中变得更加可用。得益于Hugging Face Transformers等库,将这些模型调整为下游任务变得更加容易和可管理。同样感谢开源社区,我们有很多可供选择的语言模型,涵盖广泛使用的语言和领域。

微调还是从零开始构建?

当将现有语言模型调整为特定用例时,有时我们可以在不进一步调整的情况下使用现有模型(所谓的微调)。例如,如果您想要一个用于英语情感/意图检测的模型,您可以在HuggingFace.co上找到适合您用例的合适模型。

然而,您只能对现实世界中遇到的某些任务有此期望。这就是我们需要另一种称为微调的附加技术的地方。首先,您必须选择要进行微调的基础模型。在这里,您必须对所选模型和目标语言的词汇相似性要小心。

然而,如果找不到适用于所需语言的合适模型,考虑从零开始构建一个。在本教程中,我们将实现用于掩码语言模型的BERT模型。

BERT架构

尽管描述BERT架构超出了本教程的范围,但为了清晰起见,让我们对它进行狭义的介绍。BERT是双向编码器自变换器(Bidirectional Encoder Representations from Transformers)的缩写,属于仅编码器变换器家族。它由Google的研究人员于2018年推出。

论文摘要:

我们介绍了一种称为BERT的新的语言表示模型,它代表双向编码器自变换器(BERT)的含义。与最近的语言表示模型不同,BERT被设计为通过在所有层中联合条件地对左右上下文进行预训练,从未标记的文本中获得深度双向表示。因此,预训练的BERT模型只需一个附加输出层就可以进行微调,创建用于各种任务的最先进模型,例如问题回答和语言推理,无需进行重大的特定任务架构修改。BERT在概念上简单而实证强大。它在包括将GLUE分数推到80.5%(绝对改进7.7%),MultiNLI准确率提高到86.7%(绝对改进4.6%),SQuAD v1.1问题回答测试F1提高到93.2(绝对改进1.5点),以及SQuAD v2.0测试F1提高到83.1(绝对改进5.1点)在内的十一个自然语言处理任务中获得了最新的最佳结果。论文:https://arxiv.org/abs/1810.04805

在上面的内容中,我们可以看到一个有趣的关键字,即双向。双向性使BERT具有人类的能力。假设您必须填写一个空白,如下所示:

“战争有时可能是一种必要的邪恶。但是无论多么必要,它永远不是一种善。”

要猜测填入空白位置的词语,您应该了解一些事情:空白之前的词语,空白之后的词语以及整个句子的上下文。BERT以与此类人类本性相同的方式工作。在训练过程中,我们隐藏一些词语并要求BERT尝试预测它们。当训练完成时,BERT可以基于它们之前和之后的词语预测掩码标记。为此,模型应在输入序列中分配不同的关注,这可能会对预测掩码标记产生重大影响。

通过 https://huggingface.co/spaces/exbert-project/exbert 作者提供的图片

就像你在这里看到的,模型将隐藏位置的合适单词“邪恶”视为必要的,以使该预测成立。这是一个值得注意的观点,并暗示模型理解输入序列的上下文。这种上下文意识允许BERT为给定任务生成有意义的句子嵌入。此外,这些嵌入可以在聚类和分类等下游任务中使用。关于BERT的介绍就到此为止,让我们从零开始构建一个。

定义BERT模型

我们通常有BERT(基础)和BERT(大型)两个版本。每个版本都有64个维度的头部。大型变体包含24个编码器层,而基础变体只有12个。然而,我们并不限于这些配置。令人惊讶的是,我们可以完全控制使用Hugging Face Transformers库定义模型的方式。我们所要做的就是使用BertConfig类定义所需的模型配置。

我选择了6个头部和384个总模型维度,以符合原始实现。这样,每个头部都有64个维度,与原始实现类似。让我们初始化我们的BERT模型。

from transformers import BertConfig, BertForMaskedLMconfig = BertConfig(    hidden_size = 384,    vocab_size= tokenizer.vocab_size,    num_hidden_layers = 6,    num_attention_heads = 6,    intermediate_size = 1024,    max_position_embeddings = 256)model = BertForMaskedLM(config=config)print(model.num_parameters()) #10457864

训练分词器

在这里,我不打算描述分词的内部工作原理。相反,让我们使用Hugging Face tokenizers库从头开始训练一个分词器。请注意,原始的BERT实现中使用的是WordPiece分词器,这是另一种基于子词的分词方法。您可以使用下面这个精简的HuggingFace资源,了解更多关于这种分词的信息。

WordPiece分词 – Hugging Face NLP课程

我们通过开源和开放式科学,正在推动和普及人工智能。

huggingface.co

这里使用的数据集是Sinhala-400M数据集(基于apache-2.0协议)。您可以使用任何数据集来进行相同的操作。

正如您可能注意到的,有些僧伽罗语的单词也用英语打出来了。让我们为这些语料库训练一个分词器。

首先,让我们导入必要的模块。使用Hugging Face Tokenizers库训练分词器的好处是,我们可以使用现有的分词器,并根据我们的训练语料库替换词汇表(在适用的情况下进行合并)。这意味着分词的步骤,如预分词和后处理分词等将被保留。为此,我们可以使用方法train_new_from_iterator BertTokenizer类。

from tokenizers.implementations import ByteLevelBPETokenizerfrom tokenizers.processors import BertProcessingfrom transformers import AutoTokenizerfrom datasets import Datasetimport pandas as pd#load base tokenizer to train on datasettokenizer_base = AutoTokenizer.from_pretrained("bert-base-cased")# convert pandas dataset to HF datasetdataset = Dataset.from_pandas(df.rename(columns={"comment":'text'}))# define iteratortraining_corpus = (    dataset[i : i + 1000]["text"]    for i in range(0, len(dataset), 1000))#train the new tokenizer for datasettokenizer = tokenizer_base.train_new_from_iterator(training_corpus, 5000)#test trained tokenizer for sample texttext = dataset['text'][123]print(text)

# 我们来检查词元化过程输入_id = tokenizer(text).输入标记subword_view = [tokenizer.convert_ids_to_tokens(id) for id in input_ids]np.array(subword_view)

你可以看到像 ‘cricketer’ 这样的词被分解为 cricket 和 ##er,这意味着词元化器经过了充分的训练。然而,尝试使用不同的词表大小;我使用的是5000,相对较小但对于这个演示例子来说是合适的。

最后,我们可以将训练好的词元化器保存到我们的目录中。

tokenizer.save_pretrained("tokenizer/sinhala-wordpiece-yt-comments")

定义数据合并器和词元化数据集。

让我们为MLM任务定义一个数据合并器。在这里,我们将遮盖15%的词元。无论如何,我们可以设置不同的遮盖概率。

from transformers import DataCollatorForLanguageModelingdata_collator = DataCollatorForLanguageModeling(    tokenizer=tokenizer, mlm=True, mlm_probability=0.15)

使用先前创建的词元化器对数据集进行词元化。我使用自定义类替换了原始的LineByLineTextDataset并利用了Hugging Face accelerate 加速库。

import torchfrom torch.utils.data import Datasetfrom accelerate import Accelerator, DistributedTypeclass LineByLineTextDataset(Dataset):    def __init__(self, tokenizer, raw_datasets, max_length: int):        self.padding = "max_length"        self.text_column_name = 'text'        self.max_length = max_length        self.accelerator = Accelerator(gradient_accumulation_steps=1)        self.tokenizer = tokenizer        with self.accelerator.main_process_first():            self.tokenized_datasets = raw_datasets.map(                self.tokenize_function,                batched=True,                num_proc=4,                remove_columns=[self.text_column_name],                desc="Running tokenizer on dataset line_by_line",            )            self.tokenized_datasets.set_format('torch',columns=['input_ids'],dtype=torch.long)                def tokenize_function(self,examples):        examples[self.text_column_name] = [            line for line in examples[self.text_column_name] if len(line[0]) > 0 and not line[0].isspace()        ]        return self.tokenizer(            examples[self.text_column_name],            padding=self.padding,            truncation=True,            max_length=self.max_length,            return_special_tokens_mask=True,        )    def __len__(self):        return len(self.tokenized_datasets)    def __getitem__(self, i):        return self.tokenized_datasets[i]

让我们对数据集进行词元化。

tokenized_dataset_train = LineByLineTextDataset(    tokenizer= tokenizer,    raw_datasets = dataset,    max_length=256,)

好的,让我们编写我们的训练循环。

from transformers import Trainer, TrainingArgumentstraining_args = TrainingArguments(    output_dir="./model",    overwrite_output_dir=True,    push_to_hub=True,    hub_model_id="Ransaka/sinhala-bert-yt-comments",    num_train_epochs=2,    per_device_train_batch_size=32,    save_steps=5_000,    logging_steps = 1000,    save_total_limit=2,    use_mps_device = True, # disable this if you're running non-mac env    hub_private_repo = False, # please set true if you want to save model privetly    save_safetensors= True,    learning_rate = 1e-4,    report_to='wandb')trainer = Trainer(    model=model,    args=training_args,    data_collator=data_collator,    train_dataset=tokenized_dataset_train)trainer.train()

我们可以使用 trainer 的 train() 方法来调用训练过程。

trainer.train()

经过充分的训练后,我们的模型可以用于下游任务,如零样本分类和聚类。您可以在此 Hugging Face 空间上找到使用示例以获得更多详细信息。

锁爱关键颜 \- 由Ransaka提供的Hugging Face空间

发现由社区制作的惊人机器学习应用程序

huggingface.co

结论

受限于资源,预训练模型可能只能识别特定的语言模式,但对特定用例仍然有帮助。强烈建议在可能的情况下进行微调。

本文中的所有图片(除非另有说明)均由作者提供。

参考资料

  1. An Explorable BERT — https://huggingface.co/spaces/exbert项目/exbert
  2. BERT论文 — https://arxiv.org/abs/1810.04805
  3. 数据集 — https://huggingface.co/datasets/Ransaka/Sinhala-400M