【Day 2】词嵌入与BERT的输入

如何从实战层面认识BERT

BERT的研究与应用可以分为不同的层次。你可以钻研BERT的模型技术细节,了解它为什麽这麽有效,甚至可以发现其中有问题的设计来加以改进,例如RoBERTa就是FB的工程师在BERT之上的改进成果。你也可以去探讨BERT不同层的嵌入之间的差别,来探讨模型如何「学习」到语言文本的知识。

但这些关於BERT的研究都在基础层面,并无法很有效地应用於我们的平常使用中。因为我们和FB、Google的工程师之间有一个巨大差别:缺乏硬体资源,没法重新预训练一个完整的BERT模型。所以,当我们在比赛、作业、研究等情境中使用BERT这类预训练模型时,更关注的是如何在既有的预训练模型基础上更好地应用它们,让它们与Data结合发挥最大效果。

那麽,认识的第一步就是先了解BERT的输入与输出,这时,不妨先将BERT当作一个魔法黑盒子。其中的内部运作我会在接下来几天说明。

嵌入Embedding:从符号到向量

词嵌入(Word Embedding)已经在NLP领域中被讨论许久了,而这也是理解BERT的一个很好的切入点。

文字是符号,文本是符号的排列,语义就是被编码进这些符号与符号的排列中。我们认识符号,也能认识符号在不同排列之中的不同意义,是因为我们的大脑对於语言符号会形成一定的「理解」,将其转化为我们脑中可以让神经元计算、沟通的存在形式。但是计算机没有这样的转化系统,计算机只能理解数字,无法辨识符号。

所以词嵌入做的事情就是将文字符号转化为高维度的向量。之所以高维度,是因为语义很复杂,需要较高维度才能充分表现其义。如果你学过特徵抽取,那麽也可以用类似的方式来理解,词嵌入就是对文本序列中的每一个词进行特徵抽取。抽取出来的一串特徵值组成了向量。

在BERT模型发明之前,词嵌入主要由Word2Vec来完成。但Word2Vec的缺点是无法分辨脉络(Context),也就是说,在Word2Vec所转换的词嵌入结果中,一个词在任何语句中的表示向量都是一样的。例如:

自然真美好,我想生活在绿色环境中。
顺其自然吧,没什麽好难过的。
矿泉水是最自然的饮用品。

三句话中的「自然」的语义都不一样,第一个「自然」指的是自然环境(而且还特指绿色环境),第二个「自然」是指不干预、自由发展,第三个「自然」则是指非人工加工。词本身没有变化,变的是在词语上下的文本脉络。而BERT正可以弥补这个缺失,让词语根据上下文转换成符合脉络的嵌入。因此,BERT的输入必定是一个完整的文本序列。而输出则是序列中每个词语对应的多维向量。以下来详细讨论。

BERT的输入

https://ithelp.ithome.com.tw/upload/images/20210905/20127672biWv2mIAdN.jpg

上图是BERT的输入的说明,两道分隔线将图片分成三个部分,最上层是作为文本序列的输入(预处理後的原始资料),第二层是实际BERT模型需要的三个输入,第三层则是在Pytorch、Transformers模组中的三个输入。我们实际用Python进行模型微调的时候只要输入最下方第三层的三个输入即可。

让我们从上往下进行讲解:

文本的预处理

可以注意到,文本原文应为「my dog is cute」和「he likes playing」两个句子,但在这边预处理後多了许多被[]包裹着的token。它们别代表的意思是:

  • [CLS]:Special Classification Embedding,用於每个序列之首,Fine-tuning时用於聚集所有分类资讯(BertPooler,一个全连接层将整句的资讯用第一个token表示),代表整个序列的语义。
  • [SEP]:有两个句子的文本会被串接成一个输入序列,并在两句之间插入这个 token 以做区隔。
  • [PAD]:占位符,将长度不一的输入序列补齐方便做 batch 运算。

除了上述三个之外,BERT还有以下两个特殊token:

  • [UNK]:没出现在 BERT 字典里的字会被这个 token 取代。
  • [MASK]:未知遮罩,仅在预训练阶段的克漏字任务中会用到。

BERT模型所需的三个输入

BERT模型尚无法读取预处理後的文本,它仍然需要被更进一步转换成BERT所需的格式,也就是三个不同类型的嵌入。你可能会问,词嵌入不是我们的输出吗?怎麽在输入阶段就需要嵌入?别慌,在BERT预训练阶段,这些嵌入是随机初始化并随着模型的训练而得到改善的,在微调阶段,这些嵌入已经被包含在了模型内部的,你不需要实际输入它们。所以你也可以将他们看作模型本身的一部分,知道它们是高维向量即可。

以下我简单做一个说明:

  • Token Embeddings:也就是词嵌入。只是BERT模型的输入的Token embeddings是最浅层的词嵌入,只能代表词语的浅层特徵(例如字符长度之类的),也没有包含上下文脉络,因为还没有经过与序列中其他token的运算。
  • Segment Embeddings:分段嵌入。只有两种不同的向量,用於在输入是两个句子时分辨 token 是属於哪个句子。第一句子的每一个词对应相同的Segment Embedding,第二句对应第二种Segment embedding。
  • Position Embeddings:表示序列位置的嵌入。因为BERT是同时输入做平行计算,而非一步步按照序列进行输入,所以无法自然得知序列的前後顺序,需要一个位置嵌入来补足。

用Transformers微调所使用的三个输入

这部分才是我们微调下游任务时实际所需的三个输入。Transformers是目前使用预训练语言模型最流行的一个框架,也是一个python模组,它依赖pytorch。你可以在这里进行更多了解,本系列之後的文章也会介绍。

因为上面已经提到,Token Embeddings、Segment Embeddings、Position Embeddings是已经包含在BERT模型中的嵌入,在实际应用过程中,我们只要能捞取到对应的Embeddings即可,那麽怎麽捞取呢?这些嵌入是以字典的形式进行储存,例如对於Token Embeddings,每一个不同的词(Token)对应固定ID的嵌入。所以我们只要把词语转换为对应的数字id即可。而BERT模型在释出时也会提供相应的字典,让你可以进行自动对应。

这部分的三个输入(下方括号内为此输入在Transformers中的变量名称)分别有:

  • tokens_tensor(input_ids):每个token的索引值,用於对应Token Embeddings。可以用BERT模型提供的vocab.txt查到。
  • segments_tensor(token_type_ids):对应Segment Embeddings,识别句子界限,第一句中的每个词给0,第二句中每个词给1。
  • masks_tensor(attention_mask):排除占位符(<pad>)用,界定自注意力机制范围,为1则是有意义的文本序列内容,进入後续运算,<pad>对应0,不进入运算。

你可能会好奇,为什麽没有Position Embeddings的对应输入呢?很简单,因为序列的index本身就包含在输入资料内,模型可以替你进行处理。所以虽然Position Embeddings很重要,但是不用自己输入一个[0,1,2,3,4⋯⋯]的序列。而这部分多的masks_tensor则是为了符合实际训练的需要,为了进行Batch运算,同一批次输入的序列长度必须一致,所以需要占位符号,但运算过程中又要排除占位符。、

以上就是BERT模型输入部分的介绍。其实在Transformers中,这个过程可以被简化为如下两行,但了解其背後原理仍是必要的。

tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
inputs = tokenizer("my dog is cute","he likes playing")

<<:  D5 - 如何用 Google Apps Script 搭配 HTML 客制 Google 表单的回应信件?

>>:  [Day4]PHP变数命名规则

Deserialization

JSON serialization/deserialization 应该是不少 Android a...

【面试】coding interview

另一系列悲剧..不小心按到上一页.. 感觉这篇还少了点什麽? 如果平常只用过 Leetcode,建...

[Day7] Vite 出小蜜蜂~Shoot 射击系统!

Day7 Shoot 是时候帮我们的 LaserCannon 装上子弹了! Input 首先,当玩家...

Day 01 HTML<常用标签>

标题标签** <h1> - <h6> (一级标题 - 六级标题) 文字粗体...

如何与使用者对话

How to Talk to Users: Startup Ideas, Building Prod...