2021SC@SDUSC
代码分析
Hugging Face pytorch版本-bert模型代码
BertTokenizer类 Tokenization分词,是NLP任务中的首要工作。对于文本这种非结构化的数据,需要将其分割为最小的且具有完整语义的单位——词,来进行研究。接下来看一下bert的分词器——BertTokenizer。 bert的分词器主要是基于WordPiece算法。WordPiece算法是一种subword模型,它在构造词表时划分粒度介于词与字符之间,比如looked可以会被划分为look和ed两个词。这样就避免了传统构造此表方法中,比如looking、looked这种语义相同而时态不同的词的大量出现,造成词表过大的问题。
class BertTokenizer(PreTrainedTokenizer):
r"""
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.
WordPiece算法是如何构建词表的呢?
-
获得足够大的语料库。 -
定义所需的子词词汇量。 -
将单词拆分为字符序列。 -
用文本中的所有字符初始化词汇表。 -
根据词汇建立语言模型。 -
通过将当前词汇表中的两个单元组合以将词汇表增加一个来生成新的子词单元。 从所有可能性中选择新的子词单位,这会在添加到模型时最大程度地增加训练数据的可能性。 -
重复第5步,直到达到子词词汇量(在第2步中定义),或者似然性增加降至某个阈值以下。 https://zhuanlan.zhihu.com/p/355338876
在第6步合并子词的过程中,会计算相邻两个子词的相关性,每次将相关性最高,也就是经常一起出现的子词合并。
WordPiece许多方面与BPE相似,不同之处在于它基于似然而不是下一个最高频率对形成一个新的子字,这个似然值表示来两个子字的相关性,WordPiece会合并相关性最高的两个子字。 https://zhuanlan.zhihu.com/p/191648421
一些参数: do_lower_case是否将输入转换为小写; do_basic_tokenize是否在WordPiece前做基本分词
def __init__(
self,
vocab_file,
do_lower_case=True,
do_basic_tokenize=True,
never_split=None,
unk_token="[UNK]",
sep_token="[SEP]",
pad_token="[PAD]",
cls_token="[CLS]",
mask_token="[MASK]",
tokenize_chinese_chars=True,
strip_accents=None,
**kwargs
):
super().__init__(
do_lower_case=do_lower_case,
do_basic_tokenize=do_basic_tokenize,
never_split=never_split,
unk_token=unk_token,
sep_token=sep_token,
pad_token=pad_token,
cls_token=cls_token,
mask_token=mask_token,
tokenize_chinese_chars=tokenize_chinese_chars,
strip_accents=strip_accents,
**kwargs,
)
self.vocab = load_vocab(vocab_file)
self.ids_to_tokens = collections.OrderedDict([(ids, tok) for tok, ids in self.vocab.items()])
self.do_basic_tokenize = do_basic_tokenize
if do_basic_tokenize:
self.basic_tokenizer = BasicTokenizer(
do_lower_case=do_lower_case,
never_split=never_split,
tokenize_chinese_chars=tokenize_chinese_chars,
strip_accents=strip_accents,
)
self.wordpiece_tokenizer = WordpieceTokenizer(vocab=self.vocab, unk_token=self.unk_token)
load_vocab方法读入词文件读入每一行,构造了一个有序词典——collections.OrderedDict()可以按照元素的插入顺序进行排序。而且如果有键重复插入的情况,则对值进行覆盖,并按照首次插入顺序进行保存。
def load_vocab(vocab_file):
"""Loads a vocabulary file into a dictionary."""
vocab = collections.OrderedDict()
index = 0
with open(vocab_file, "r", encoding="utf-8") as reader:
while True:
token = reader.readline()
if not token:
break
token = token.strip()
vocab[token] = index
index += 1
return vocab
class WordpieceTokenizer(object):
"""Runs WordPiece tokenization."""
def __init__(self, vocab, unk_token, max_input_chars_per_word=100):
self.vocab = vocab
self.unk_token = unk_token
self.max_input_chars_per_word = max_input_chars_per_word
def tokenize(self, text):
output_tokens = []
for token in whitespace_tokenize(text):
chars = list(token)
if len(chars) > self.max_input_chars_per_word:
output_tokens.append(self.unk_token)
continue
is_bad = False
start = 0
sub_tokens = []
while start < len(chars):
end = len(chars)
cur_substr = None
while start < end:
substr = "".join(chars[start:end])
if start > 0:
substr = "##" + substr
if substr in self.vocab:
cur_substr = substr
break
end -= 1
if cur_substr is None:
is_bad = True
break
sub_tokens.append(cur_substr)
start = end
if is_bad:
output_tokens.append(self.unk_token)
else:
output_tokens.extend(sub_tokens)
return output_tokens
|