HuggingFace BERT源码详解:基本模型组件实现

©PaperWeekly 原创 · 作者 | 李泺秋

学校 | 浙江大学硕士生

研究方向 | 自然语言处理、知识图谱

本文记录一下对 HuggingFace 开源的 Transformers 项目代码的理解。

众所周知,BERT 模型自 2018 年问世起就各种屠榜,开启了 NLP 领域预训练+微调的范式。到现在,BERT 的相关衍生模型层出不穷(XL-Net、RoBERTa、ALBERT、ELECTRA、ERNIE 等),要理解它们可以先从 BERT 这个始祖入手。

HuggingFace 是一家总部位于纽约的聊天机器人初创服务商,很早就捕捉到 BERT 大潮流的信号并着手实现基于 pytorch 的 BERT 模型。这一项目最初名为 pytorch-pretrained-bert,在复现了原始效果的同时,提供了易用的方法以方便在这一强大模型的基础上进行各种玩耍和研究。

随着使用人数的增加,这一项目也发展成为一个较大的开源社区,合并了各种预训练语言模型以及增加了 Tensorflow 的实现,并且在 2019 年下半年改名为 Transformers。截止写文章时(2021 年 3 月 30 日)这一项目已经拥有 43k+ 的star,可以说 Transformers 已经成为事实上的 NLP 基本工具。

详见:transformers [1] 

本文基于 Transformers 版本 4.4.2(2021 年 3 月 19 日发布)项目中,pytorch 版的 BERT 相关代码,从代码结构、具体实现与原理,以及使用的角度进行分析,包含以下内容:

1. BERT Tokenization分词模型(BertTokenizer)

2. BERT Model本体模型(BertModel)

2.1 BertEmbeddings

2.2 BertEncoder

2.2.1 BertLayer

2.2.1.1 BertAttention

2.2.1.1.1 BertSelfAttention

2.2.1.1.2 BertSelfOutput

2.2.1.2 BertIntermediate

2.2.1.3 BertOutput

2.2.3 BertPooler

3. BERT-based Models应用模型(请看下篇)

3.1 BertForPreTraining

3.2 BertForSequenceClassification

3.3 BertForMultiChoice

3.4 BertForTokenClassification

3.5 BertForQuestionAnswering

4. BERT训练与优化(请看下篇)

4.1 Pre-Training

4.2 Fine-Tuning

4.2.1 AdamW

4.2.2 Warmup

Tokenization(BertTokenizer)

和 BERT 有关的 Tokenizer 主要写在/models/bert/tokenization_bert.py/models/bert/tokenization_bert_fast.py 中。

这两份代码分别对应基本的BertTokenizer,以及不进行 token 到 index 映射的BertTokenizerFast,这里主要讲解第一个。

class BertTokenizer(PreTrainedTokenizer):
    """
    Construct a BERT tokenizer. Based on WordPiece.

    This tokenizer inherits from :class:`~transformers.PreTrainedTokenizer` which contains most of the main methods.
    Users should refer to this superclass for more information regarding those methods.
    ...
    """

BertTokenizer 是基于BasicTokenizerWordPieceTokenizer 的分词器:

  • BasicTokenizer负责处理的第一步——按标点、空格等分割句子,并处理是否统一小写,以及清理非法字符。

    • 对于中文字符,通过预处理(加空格)来按字分割;

    • 同时可以通过never_split指定对某些词不进行分割;

    • 这一步是可选的(默认执行)。

  • WordPieceTokenizer在词的基础上,进一步将词分解为子词(subword) 。

    • subword 介于 char 和 word 之间,既在一定程度保留了词的含义,又能够照顾到英文中单复数、时态导致的词表爆炸和未登录词的 OOV(Out-Of-Vocabulary)问题,将词根与时态词缀等分割出来,从而减小词表,也降低了训练难度;

    • 例如,tokenizer 这个词就可以拆解为“token”和“##izer”两部分,注意后面一个词的“##”表示接在前一个词后面。

BertTokenizer 有以下常用方法:

  • from_pretrained:从包含词表文件(vocab.txt)的目录中初始化一个分词器;

  • tokenize:将文本(词或者句子)分解为子词列表;

  • convert_tokens_to_ids:将子词列表转化为子词对应下标的列表;

  • convert_ids_to_tokens :与上一个相反;

  • convert_tokens_to_string:将 subword 列表按“##”拼接回词或者句子;

  • encode:对于单个句子输入,分解词并加入特殊词形成“[CLS], x, [SEP]”的结构并转换为词表对应下标的列表;对于两个句子输入(多个句子只取前两个),分解词并加入特殊词形成“[CLS], x1, [SEP], x2, [SEP]”的结构并转换为下标列表;

  • decode:可以将 encode 方法的输出变为完整句子。

以及,类自身的方法:

>>> from transformers import BertTokenizer
>>> bt = BertTokenizer.from_pretrained('./bert-base-uncased/')
>>> bt('I like natural language progressing!')
{'input_ids': [101, 1045, 2066, 3019, 2653, 27673, 999, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1]}

Model(BertModel)

和 BERT 模型有关的代码主要写在/models/bert/modeling_bert.py中,这一份代码有一千多行,包含 BERT 模型的基本结构和基于它的微调模型等。

下面从 BERT 模型本体入手分析:

class BertModel(BertPreTrainedModel):
    """

    The model can behave as an encoder (with only self-attention) as well as a decoder, in which case a layer of
    cross-attention is added between the self-attention layers, following the architecture described in `Attention is
    all you need <https://arxiv.org/abs/1706.03762>`__ by Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit,
    Llion Jones, Aidan N. Gomez, Lukasz Kaiser and Illia Polosukhin.

    To behave as an decoder the model needs to be initialized with the :obj:`is_decoder` argument of the configuration
    set to :obj:`True`. To be used in a Seq2Seq model, the model needs to initialized with both :obj:`is_decoder`
    argument and :obj:`add_cross_attention` set to :obj:`True`; an :obj:`encoder_hidden_states` is then expected as an
    input to the forward pass.
    """ 

BertModel 主要为 transformer encoder 结构,包含三个部分:

  1. embeddings,即BertEmbeddings类的实体,对应词嵌入;

  2. encoder,即BertEncoder类的实体;

  3. pooler,即BertPooler类的实体,这一部分是可选的。

补充:注意 BertModel 也可以配置为 Decoder,不过下文中不包含对这一部分的讨论。

下面将介绍 BertModel 的前向传播过程中各个参数的含义以及返回值:

    def forward(
        self,
        input_ids=None,
        attention_mask=None,
        token_type_ids=None,
        position_ids=None,
        head_mask=None,
        inputs_embeds=None,
        encoder_hidden_states=None,
        encoder_attention_mask=None,
        past_key_values=None,
        use_cache=None,
        output_attentions=None,
        output_hidden_states=None,
        return_dict=None,
    ): ...
  • input_ids:经过 tokenizer 分词后的 subword 对应的下标列表;

  • attention_mask:在 self-attention 过程中,这一块 mask 用于标记 subword 所处句子和 padding 的区别,将 padding 部分填充为 0;

  • token_type_ids:标记 subword 当前所处句子(第一句/第二句/ padding);

  • 7
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值