使用pytorch进行中文bert语言模型预训练的例子比较少。在huggingface的Transformers中,有一部分代码支持语言模型预训练(不是很丰富,很多功能都不支持比如wwm)。为了用最少的代码成本完成bert语言模型预训练,本文借鉴了里面的一些现成代码。也尝试分享一下使用pytorch进行语言模型预训练的一些经验。主要有三个常见的中文bert语言模型

  1. bert-base-chinese
  2. roberta-wwm-ext
  3. ernie

1 bert-base-chinese

bert-base-chinese · Hugging Facehuggingface.co


这是最常见的中文bert语言模型,基于中文维基百科相关语料进行预训练。把它作为baseline,在领域内无监督数据进行语言模型预训练很简单。只需要使用官方给的例子就好。

huggingface/transformers

(本文使用的transformers更新到3.0.2)

方法就是


python run_language_modeling.py 
    --output_dir=output 
    --model_type=bert 
    --model_name_or_path=bert-base-chinese 
    --do_train 
    --train_data_file=$TRAIN_FILE 
    --do_eval 
    --eval_data_file=$TEST_FILE 
    --mlm
其中$TRAIN_FILE 代表领域相关中文语料地址。


2 roberta-wwm-ext


ymcui/Chinese-BERT-wwmgithub.com

pytorch 模拟语音 pytorch中文_pytorch 模拟语音


哈工大讯飞联合实验室发布的预训练语言模型。预训练的方式是采用roberta类似的方法,比如动态mask,更多的训练数据等等。在很多任务中,该模型效果要优于bert-base-chinese。

对于中文roberta类的pytorch模型,使用方法如下


import torch
from transformers import BertTokenizer, BertModel
tokenizer = BertTokenizer.from_pretrained("hfl/chinese-roberta-wwm-ext")
roberta = BertModel.from_pretrained("hfl/chinese-roberta-wwm-ext")


切记不可使用官方推荐的


tokenizer = AutoTokenizer.from_pretrained("hfl/chinese-roberta-wwm-ext")
model = AutoModel.from_pretrained("hfl/chinese-roberta-wwm-ext")


因为中文roberta类的配置文件比如vocab.txt,都是采用bert的方法设计的。英文roberta模型读取配置文件的格式默认是vocab.json。对于一些英文roberta模型,倒是可以通过AutoModel自动读取。这就解释了huggingface的模型库的中文roberta示例代码为什么跑不通。https://huggingface.co/models?

如果要基于上面的代码run_language_modeling.py继续预训练roberta。还需要做两个改动。

  • 下载roberta-wwm-ext到本地目录hflroberta,在config.json中修改“model_type”:"roberta"为"model_type":"bert"。
  • 对上面的run_language_modeling.py中的AutoModel和AutoTokenizer都进行替换为BertModel和BertTokenizer。

再运行命令


python run_language_modeling_roberta.py 
    --output_dir=output 
    --model_type=bert 
    --model_name_or_path=hflroberta 
    --do_train 
    --train_data_file=$TRAIN_FILE 
    --do_eval 
    --eval_data_file=$TEST_FILE 
    --mlm


3 ernie

https://github.com/nghuyong/ERNIE-Pytorch)github.com


ernie是百度发布的基于百度知道贴吧等中文语料结合实体预测等任务生成的预训练模型。这个模型的准确率在某些任务上要优于bert-base-chinese和roberta。如果基于ernie1.0模型做领域数据预训练的话只需要一步修改。

下载ernie1.0到本地目录ernie,在config.json中增加字段"model_type":"bert"。


python run_language_modeling.py 
    --output_dir=output 
    --model_type=bert 
    --model_name_or_path=ernie 
    --do_train 
    --train_data_file=$TRAIN_FILE 
    --do_eval 
    --eval_data_file=$TEST_FILE 
    --mlm


最后,huggingface项目中语言模型预训练用mask方式如下。仍是按照15%的数据随机mask然后预测自身。如果要做一些高级操作比如whole word masking或者实体预测,可以自行修改transformers.DataCollatorForLanguageModeling。


def mask_tokens(self, inputs: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
        """
        Prepare masked tokens inputs/labels for masked language modeling: 80% MASK, 10% random, 10% original.
        """

        if self.tokenizer.mask_token is None:
            raise ValueError(
                "This tokenizer does not have a mask token which is necessary for masked language modeling. Remove the --mlm flag if you want to use this tokenizer."
            )

        labels = inputs.clone()
        # We sample a few tokens in each sequence for masked-LM training (with probability args.mlm_probability defaults to 0.15 in Bert/RoBERTa)
        probability_matrix = torch.full(labels.shape, self.mlm_probability)
        special_tokens_mask = [
            self.tokenizer.get_special_tokens_mask(val, already_has_special_tokens=True) for val in labels.tolist()
        ]
        probability_matrix.masked_fill_(torch.tensor(special_tokens_mask, dtype=torch.bool), value=0.0)
        if self.tokenizer._pad_token is not None:
            padding_mask = labels.eq(self.tokenizer.pad_token_id)
            probability_matrix.masked_fill_(padding_mask, value=0.0)
        masked_indices = torch.bernoulli(probability_matrix).bool()
        labels[~masked_indices] = -100  # We only compute loss on masked tokens

        # 80% of the time, we replace masked input tokens with tokenizer.mask_token ([MASK])
        indices_replaced = torch.bernoulli(torch.full(labels.shape, 0.8)).bool() & masked_indices
        inputs[indices_replaced] = self.tokenizer.convert_tokens_to_ids(self.tokenizer.mask_token)

        # 10% of the time, we replace masked input tokens with random word
        indices_random = torch.bernoulli(torch.full(labels.shape, 0.5)).bool() & masked_indices & ~indices_replaced
        random_words = torch.randint(len(self.tokenizer), labels.shape, dtype=torch.long)
        inputs[indices_random] = random_words[indices_random]

        # The rest of the time (10% of the time) we keep the masked input tokens unchanged
        return inputs, labels