如何训练BERT来完成掩码语言建模任务

2023年10月20日 由 alex 发表 454 0

介绍


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


像BERT这样的屏蔽语言模型在下游的NLP任务中变得更有用,比如分类和聚类。多亏了诸如hugs Face Transformers之类的库,将这些模型用于下游任务变得更容易访问和管理。同样要感谢开源社区,我们有大量的语言模型可供选择,涵盖了广泛使用的语言和领域。


微调还是从头开始构建?


当将现有的语言模型调整到你的特定用例时,有时我们可以使用现有的模型而无需进一步调优(所谓的微调)。例如,如果你想要一个英语情感/意图检测模型,你可以进入HuggingFace。为你的用例找到合适的模型。


1


然而,对于现实世界中遇到的一些任务,你只能期望这样做。这就需要一种叫做微调的额外技术。首先,你必须选择一个将进行微调的基本模型。在这里,你必须小心选择的模型和目标语言的词汇相似性。


但是,如果你找不到对所需语言进行再训练的合适模型,请考虑从头构建一个。在本文中,我们将为掩码语言模型实现BERT模型。


BERT架构


尽管描述BERT体系结构超出了本教程的范围,但为了清晰起见,让我们对其进行非常狭窄的讨论。BERT,即变压器的双向编码器表示,属于纯编码器变压器系列。它是由谷歌的研究人员于2018年推出的。


论文摘要:


我们引入了一种新的语言表示模型BERT,它代表来自变形金刚的双向编码器表示。与最近的语言表示模型不同,BERT旨在通过在所有层中对左右上下文进行联合条件反射,从未标记的文本中预训练深度双向表示。因此,预训练的BERT模型可以通过一个额外的输出层进行微调,从而为广泛的任务(如问答和语言推理)创建最先进的模型,而无需对特定于任务的架构进行大量修改。


BERT在概念上简单,经验上强大。它在11个自然语言处理任务上获得了新的最先进的结果,包括将GLUE得分提高到80.5%(绝对提高7.7%),将多项准确性提高到86.7%(绝对提高4.6%),将SQuAD v1.1问答测试F1提高到93.2(绝对提高1.5分)和SQuAD v2.0测试F1提高到83.1(绝对提高5.1分)。


在上面,我们可以看到一个有趣的关键字,双向。双向性赋予BERT类似人类的能力。假设你必须像下面这样填写一个空格,


“战争有时可能是必要之恶。但无论多么必要,它总是一个_____,从来没有一个好。”


要猜出单词出现在空白位置,你应该注意几件事:空格前的单词,空格后的单词,以及句子的整体上下文。为了适应这种人性,BERT以同样的方式工作。在训练过程中,我们隐藏了一些单词,并要求BERT尝试预测这些单词。当训练完成后,BERT可以根据单词的前后来预测掩码。要做到这一点,模型应该对输入序列中呈现的单词分配不同的注意力,这可能会显著影响对掩码令牌的预测。


2


正如你在这里看到的,这个模型认为一个适合隐藏位置的词是邪恶的,而第一个句子的邪恶是做出这个预测所必需的。这是一个值得注意的点,意味着模型理解输入序列的上下文。这种上下文感知允许BERT为给定任务生成有意义的句子嵌入。此外,这些嵌入可以用于下游任务,如聚类和分类。BERT说得够多了;让我们从头开始构建一个。


定义BERT模型


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


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


from transformers import BertConfig, BertForMaskedLM
config = 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


训练标记器


在这里,我不会描述标记化是如何工作的。相反,让我们使用hugs Face tokenizers库从头开始训练一个。


这里使用的数据集是Sinhala-400M数据集(在apache-2.0下)。你可以对你拥有的任何数据集遵循相同的方法。


3


正如你可能注意到的,一些僧伽罗语的单词也是用英语输入的。让我们为这些语料库训练一个标记器。


让我们首先导入必要的模块。使用hug Face tokenizers库训练tokenizers的好处是,我们可以使用现有的tokenizers,并根据我们的训练语料库只替换词汇表(并在适用的地方合并)。这意味着标记化步骤,如预标记化和后标记化将被保留。为此,我们可以使用方法train_new_from_iterator BertTokenizer class。


from tokenizers.implementations import ByteLevelBPETokenizer
from tokenizers.processors import BertProcessing
from transformers import AutoTokenizer
from datasets import Dataset
import pandas as pd
#load base tokenizer to train on dataset
tokenizer_base = AutoTokenizer.from_pretrained("bert-base-cased")
# convert pandas dataset to HF dataset
dataset = Dataset.from_pandas(df.rename(columns={"comment":'text'}))
# define iterator
training_corpus = (
    dataset[i : i + 1000]["text"]
    for i in range(0, len(dataset), 1000)
)

#train the new tokenizer for dataset
tokenizer = tokenizer_base.train_new_from_iterator(training_corpus, 5000)
#test trained tokenizer for sample text
text = dataset['text'][123]
print(text)


4


# let's check tokenization process
input_ids = tokenizer(text).input_ids
subword_view = [tokenizer.convert_ids_to_tokens(id) for id in input_ids]
np.array(subword_view)


5


你可以看到像“cricketer”这样的词被分解成cricket和##er,这表明标记器已经经过了充分的训练。然而,尝试不同的词汇量;我的是5000,它相对较小,但适合这个玩具示例。


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


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


定义数据整理器并标记数据集


让我们为传销任务定义一个排序器。在这里,我们将屏蔽15%的代币。不管怎样,我们可以设置不同的掩蔽概率。


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


让我们使用之前创建的标记器对数据集进行标记。我正在使用我的自定义类使用拥抱脸加速取代原来的LineByLineTextDataset。


import torch
from torch.utils.data import Dataset
from accelerate import Accelerator, DistributedType
class 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, TrainingArguments
training_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()


我们可以使用训练器的train()方法来调用它。


trainer.train()


6


经过充分的训练,我们的模型可以用于下游的任务,如零射击分类和聚类。


结论


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

文章来源:https://medium.com/towards-data-science/how-to-train-bert-for-masked-language-modeling-tasks-3ccce07c6fdc
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消