bert详解

huggingface中的模型

huggingface中的模型定义的基本结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def _forward_unimplemented(self, *input: Any) -> None:
raise NotImplementedError(f"Module [{type(self).__name__}] is missing the required \"forward\" function")

class Module:
def __init__(self) -> None:
self.basename = "BaseModel"
def __call__(self, *args: Any, **kwds: Any) -> Any:
print(f"now you call __call__,params={args}")
self.forward(args[0])

forward: Callable[..., Any] = _forward_unimplemented


class MyModel(Module):
def __init__(self, name) -> None:
super().__init__()
self.name = name
def forward(self,x):
print(f"now you call forward,param={x}")

model = MyModel("bert")
model([1,2,3,4])
# now you call __call__,params=([1, 2, 3, 4],)
# now you call forward,param=[1, 2, 3, 4]

加载bert模型

1
2
3
4
5
6
7
8
9
10
from typing import Any
from transformers import AutoTokenizer, AutoModel
from typing import Callable
tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-uncased")
print('tokenizer type=',type(tokenizer))
model = AutoModel.from_pretrained("google-bert/bert-base-uncased")

# 加载bert模型推理
inputs = tokenizer("你是个好人", return_tensors='pt') # 返回数据类型可以为pt,也可以为tf
output = model(**inputs) # 打印tensor

bert中分词tokenize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-uncased")  
# huggingface默认下载位置:~\.cache\huggingface\hub,可以配置环境变量export HF_HOME="目标地址"修改
print("词典大小:",tokenizer.vocab_size)
# 词典大小: 30522 对应vocab.txt行数
text = "the game has gone!unaffable I have a new GPU!"
tokens = tokenizer.tokenize(text)
print(f"英文分词, 英文:{text},分词:{tokens}")
# 英文分词, 英文:the game has gone!unaffable I have a new GPU!,分词:['the', 'game', 'has', 'gone', '!', 'una', '##ffa', '##ble', 'i', 'have', 'a', 'new', 'gp', '##u', '!']
text = "我爱北京天安门,吢吣"
tokens = tokenizer.tokenize(text)
print(f"中文分词, 中文:{text},分词:{tokens}")
# 中文分词, 英文:我爱北京天安门,吢吣,分词:['我', '[UNK]', '北', '京', '天', '安', '[UNK]', ',', '[UNK]', '[UNK]']
input_ids = tokenizer.convert_tokens_to_ids(tokens)
print("id-token转换:",input_ids)
# id-token转换: [1855, 100, 1781, 1755, 1811, 1820, 100, 1989, 100, 100]
sen_code = tokenizer.encode_plus("i like you much", "but not him")
print("多句子encode:",sen_code)
# 句子encode: {'input_ids': [101, 1045, 2066, 2017, 2172, 102, 2021, 2025, 2032, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 1, 1, 1, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
print("decode:",tokenizer.decode(sen_code['input_ids']))
# decode: [CLS] i like you much [SEP] but not him [SEP]
inputs = tokenizer("你好", return_tensors="pt")
print(f'tokenizer("你好")={inputs}')
# tokenizer("你好")={'input_ids': tensor([[101, 100, 100, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1]])}
inputs = tokenizer(["你好吗","。不好的。哈哈"], padding=True, truncation=True, max_length=128, return_tensors="pt")
# 多个句子,按最长的进行填充。同时设置最长不超过128
print(f'tokenizer("你好吗。不好的。哈哈")={inputs}')
# tokenizer("你好吗。不好的。哈哈")={'input_ids': tensor([[ 101, 100, 100, 100, 102, 0, 0, 0, 0],
# [ 101, 1636, 1744, 100, 1916, 1636, 100, 100, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 0, 0, 0, 0],
# [1, 1, 1, 1, 1, 1, 1, 1, 1]])}

Bert的tokenizer中有特殊标记(Special Tokens)。它们的含义如下:

  • [PAD]:在batch中对齐序列长度时,用 [PAD]进行填充以使所有序列长度相同。可以通过将其添加到较短的序列末尾来实现对齐。
  • [CLS]:在输入序列的开头添加 [CLS] 标记,以表示该序列的分类结果。
  • [SEP]:用于分隔两个句子,例如在文本分类问题中,将两个句子拼接成一个输入序列时,可以使用 [SEP] 来分隔这两个句子。
  • [UNK]:此标记用于表示未知或词汇外的单词。当一个模型遇到一个它以前没有见过/无法识别的词时,它会用这个标记替换它。

AutoTokenizer到bert tokenizer

对于bert模型,模型文件格式如下:

1
2
3
4
5
6
7
# .cache/huggingface/hub/models--google-bert--bert-base-uncased/snapshots/
.
├── config.json -> ../../blobs/45a2321a7ecfdaaf60a6c1fd7f5463994cc8907d
├── model.safetensors -> ../../blobs/68d45e234eb4a928074dfd868cead0219ab85354cc53d20e772753c6bb9169d3
├── tokenizer_config.json -> ../../blobs/e5c73d8a50df1f56fb5b0b8002d7cf4010afdccb
├── tokenizer.json -> ../../blobs/949a6f013d67eb8a5b4b5b46026217b888021b88
└── vocab.txt -> ../../blobs/fb140275c155a9c7c5a3b3e0e77a9e839594a938

调用tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-uncased") 后,from_pretrained函数首先调用get_tokenizer_config读取tokenizer_config.json文件中的数据,

1
2
3
# tokenizer_config.json
{"do_lower_case": true, "model_max_length": 512}
# 很多自有模型都会提供如tokenizer_class="QWenTokenizer", "auto_map":{"AutoTokenizer":"tokenization_qwen.QWenTokenizer"}等指定tokenizer文件

解析tokenizer_config.jsontokenizer_class,如果不存在,则读取config.json中的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# config.json
{
"architectures": [
"BertForMaskedLM"
],
"attention_probs_dropout_prob": 0.1,
"gradient_checkpointing": false,
"hidden_act": "gelu",
"hidden_dropout_prob": 0.1,
"hidden_size": 768,
"initializer_range": 0.02,
"intermediate_size": 3072,
"layer_norm_eps": 1e-12,
"max_position_embeddings": 512,
"model_type": "bert",
"num_attention_heads": 12,
"num_hidden_layers": 12,
"pad_token_id": 0,
"position_embedding_type": "absolute",
"transformers_version": "4.6.0.dev0",
"type_vocab_size": 2,
"use_cache": true,
"vocab_size": 30522
}

可见,这里的model_type表明了模型类型为berttokenizer_classmodel_type只要存在一个,都可以去内置的Tokenizer中匹配到对应的Tokenizer,然后使用PreTrainedTokenizerBase(PreTrainedTokenizerFast).from_pretrained去创建一个使用词表vocab.txt和分词器tokenizer.json, tokenizer_config.json作为参数构建的Tokenizer。(BertTokenizer等都是继承自PreTrainedTokenizerBase)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
TOKENIZER_MAPPING_NAMES = OrderedDict(
[
(
"albert",
(
"AlbertTokenizer" if is_sentencepiece_available() else None,
"AlbertTokenizerFast" if is_tokenizers_available() else None,
),
),
("align", ("BertTokenizer", "BertTokenizerFast" if is_tokenizers_available() else None)),
("bark", ("BertTokenizer", "BertTokenizerFast" if is_tokenizers_available() else None)),
("bart", ("BartTokenizer", "BartTokenizerFast")),
(
"barthez",
(
"BarthezTokenizer" if is_sentencepiece_available() else None,
"BarthezTokenizerFast" if is_tokenizers_available() else None,
),
),
("bartpho", ("BartphoTokenizer", None)),
("bert", ("BertTokenizer", "BertTokenizerFast" if is_tokenizers_available() else None)),
("bert-generation", ("BertGenerationTokenizer" if is_sentencepiece_available() else None, None)),
("bert-japanese", ("BertJapaneseTokenizer", None)),
("bertweet", ("BertweetTokenizer", None)),
(
"big_bird",
(
"BigBirdTokenizer" if is_sentencepiece_available() else None,
"BigBirdTokenizerFast" if is_tokenizers_available() else None,
),
),
#...
])

注意:
tokenizer.json 文件是 Hugging Face 的 tokenizers 库用来存储预训练的tokenizer的配置和词汇表的文件。这个文件包含了词汇表(vocabulary)以及tokenizer的设置(例如特殊的token,如 [CLS], [SEP], [PAD],以及词汇表的大小,是否使用lower case等等)。
这个文件通常在你使用预训练的模型(如BERT, GPT-2等)进行微调(fine-tuning)时会用到,因为你需要用到和原始预训练模型相同的tokenizer来确保输入的处理方式一致。这个文件在你调用 from_pretrained 方法加载预训练模型时会自动加载。
然而,如果你在构建自己的tokenizer时,你可能不会直接使用到这个文件。你可能会使用一些基础的tokenizer组件(如 BertTokenizer, GPT2Tokenizer 等)和你自己的词汇表来构建tokenizer。在这种情况下,你可能不会直接使用 tokenizer.json 文件,而是使用这些组件和你的词汇表来构建tokenizer。
总的来说,tokenizer.json 文件是一个用来存储预训练tokenizer配置的文件,它在加载预训练模型时会用到,但在构建自定义tokenizer时可能不会直接使用。
在Hugging Face的transformers库中,tokenizer.json文件主要是在使用fast版本的tokenizer时使用的。fast版本的tokenizer是用Rust编写的,性能更好,功能也更丰富。这些tokenizer可以通过tokenizers库单独使用,也可以通过transformers库使用。

1
2
3
tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-uncased")
print('tokenizer type=',type(tokenizer))
# tokenizer type= <class 'transformers.models.bert.tokenization_bert_fast.BertTokenizerFast'>

AutoModel到BertModel

那么,AutoModel是如何找到BertModel的?

  1. 在AutoModel类中,会局部变量model_mapping初始化为MODEL_MAPPING = _LazyAutoMapping(CONFIG_MAPPING_NAMES, MODEL_MAPPING_NAMES)来映射内部所有模型和配置和模型类的映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
CONFIG_MAPPING_NAMES = OrderedDict(
[
# Add configs here
("albert", "AlbertConfig"),
("align", "AlignConfig"),
("altclip", "AltCLIPConfig"),
("audio-spectrogram-transformer", "ASTConfig"),
("autoformer", "AutoformerConfig"),
("bark", "BarkConfig"),
("bart", "BartConfig"),
("beit", "BeitConfig"),
("bert", "BertConfig"),
#......
])
MODEL_MAPPING_NAMES = OrderedDict(
[
# Base model mapping
("albert", "AlbertModel"),
("align", "AlignModel"),
("altclip", "AltCLIPModel"),
("audio-spectrogram-transformer", "ASTModel"),
("autoformer", "AutoformerModel"),
("bark", "BarkModel"),
("bart", "BartModel"),
("beit", "BeitModel"),
("bert", "BertModel"),
#......
])

  1. 首先执行AutoModel.from_pretrained函数,先拉取google-bert/bert-base-uncased中的config.json文件,并使用AutoConfig.from_pretrained构建一个类型为=<class 'transformers.models.bert.configuration_bert.BertConfig'>的对象。原理为从config.json中的model_type为bert来从CONFIG_MAPPING_NAMES加载对应的Config类
  2. 根据构建的BertConfig结构,在model_mapping中找到映射到的具体Model类,即BertModel