02_tokenizer_encode_plus_token_type_ids

2133 字
11 分钟
02_tokenizer_encode_plus_token_type_ids

上一节我们通过情感分析的例子初探了 BERT 模型的一次完整推理。这一节,我们来看 BERT 的两个核心预训练任务:MLM 与 NSP. 我们同时也介绍 token_type_ids 的设计来源。

1. 加载模型#

from transformers import BertTokenizer
model_name = 'bert-base-uncased'
tokenizer = BertTokenizer.from_pretrained(model_name)
tokenizer

输出为:

BertTokenizer(name_or_path='bert-base-uncased', vocab_size=30522, model_max_length=512, padding_side='right', truncation_side='right', special_tokens={'unk_token': '[UNK]', 'sep_token': '[SEP]', 'pad_token': '[PAD]', 'cls_token': '[CLS]', 'mask_token': '[MASK]'}, added_tokens_decoder={
0: AddedToken("[PAD]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
100: AddedToken("[UNK]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
101: AddedToken("[CLS]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
102: AddedToken("[SEP]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
103: AddedToken("[MASK]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
})

输出解析:

打印 tokenizer 可以查看 tokenizer 的配置摘要:

字段含义
name_or_path'bert-base-uncased'加载自哪个 checkpoint
vocab_size30522词表大小
model_max_length512模型能接受的最大 token 数
padding_side'right'padding 加在句子右侧
truncation_side'right'超长时从右边截断

special_tokens 特殊标记及其对应的 token ID:

tokenID作用
[UNK]100遇到不认识的字时替换
[SEP]102句子分隔符/结束符
[PAD]0填充符,补齐长度用
[CLS]101分类标记,放在句首
[MASK]103掩码标记,BERT 预训练时遮住词用

added_tokens_decoder 特殊标记的详细注册信息:

每个特殊标记被包装成 AddedToken 对象,属性含义如下:

  • rstrip=False / lstrip=False:在标记前后不自动去除空格
  • single_word=False:不视为独立单词
  • normalized=False:不做归一化(如小写化)
  • special=True:标记为特殊 token(不会被拆分成子词)

2. 认识文本语料#

  • newsgroups_train.DESCR数据集描述,一段长文本,介绍这个数据集的来源、内容、划分方式等。类似你看到的 README 文档。
  • newsgroups_train.data原始文本数据,包含类型、长度和内容示例。
  • newsgroups_train.target标签(数字)
  • newsgroups_train.target_names标签名(文本)
from sklearn.datasets import fetch_20newsgroups
newsgroups_train = fetch_20newsgroups(subset='train')

fetch_20newsgroups 返回一个 Bunch 对象,这是一种在 Python(特别是 Scikit-learn 机器学习库)中广泛使用的容器对象。它的本质是一个字典(dict),但额外允许像访问对象属性一样来获取和修改里面的值。

特性:

  • 字典式访问:你可以像普通字典一样使用键来访问数据,例如:bunch["data"].
  • 属性式访问:你可以像调用对象属性一样直接获取值,例如:bunch.data.
  • 动态扩展:支持随时通过属性赋值的方式添加新键值对(如 bunch.new_key = "value").
from collections import Counter
Counter(newsgroups_train.target)

输出为:

Counter({np.int64(10): 600,
np.int64(15): 599,
np.int64(8): 598,
np.int64(9): 597,
np.int64(11): 595,
np.int64(7): 594,
np.int64(13): 594,
np.int64(14): 593,
np.int64(5): 593,
np.int64(2): 591,
np.int64(12): 591,
np.int64(3): 590,
np.int64(6): 585,
np.int64(1): 584,
np.int64(4): 578,
np.int64(17): 564,
np.int64(16): 546,
np.int64(0): 480,
np.int64(18): 465,
np.int64(19): 377})

我们可以看到数据集中每个类别的样本数量有多少,比如:

np.int64(10): 600 ← 类别10(棒球)有 600 篇新闻,最多
np.int64(15): 599 ← 类别15(基督教)有 599 篇
...
np.int64(19): 377 ← 类别19(宗教杂谈)只有 377 篇,最少

其中:

  • np.int64() :NumPy 的 64 位整数类型。newsgroups_train.target 是 NumPy 数组,它的元素类型是 int64,所以 Counter 统计时 key 保留了这个类型。
    • np.int64(10)(10) 表示具体的类别编号,对应 20 个新闻组中的第 10 个。

一些其他的调用:

type(newsgroups_train.data) # list
len(newsgroups_train.data) # 11314
len(newsgroups_train.target) # 11314
len(newsgroups_train.target_names) # 20
newsgroups_train.target_names

输出为:

list # data 的类型为 list
11314 # 总共 11314 条数据
11314 # 总共 11314 个数字标签
20 # 总共 20 类
['alt.atheism',
'comp.graphics',
'comp.os.ms-windows.misc',
'comp.sys.ibm.pc.hardware',
'comp.sys.mac.hardware',
'comp.windows.x',
'misc.forsale',
'rec.autos',
'rec.motorcycles',
'rec.sport.baseball',
'rec.sport.hockey',
'sci.crypt',
'sci.electronics',
'sci.med',
'sci.space',
'soc.religion.christian',
'talk.politics.guns',
'talk.politics.mideast',
'talk.politics.misc',
'talk.religion.misc'] # 每一类的类别名称

3. tokenizer 补充#

  • input_ids
  • attention_masks
  • encode_plus
  • token_type_ids
test_news = newsgroups_train.data[:3]
len(test_news[2])

输出为:

1981

这个代表的是字节(字母)的数量。

tokenizer(test_news, truncation=True, max_length=32)

输出为:

{'input_ids': [[101, 2013, 1024, 3393, 2099, 2595, 3367, 1030, 11333, 2213, 1012, 8529, 2094, 1012, 3968, 2226, 1006, 2073, 1005, 1055, 2026, 2518, 1007, 3395, 1024, 2054, 2482, 2003, 2023, 999, 1029, 102], [101, 2013, 1024, 3124, 5283, 2080, 1030, 9806, 1012, 1057, 1012, 2899, 1012, 3968, 2226, 1006, 3124, 13970, 2080, 1007, 3395, 1024, 9033, 5119, 8554, 1011, 2345, 2655, 12654, 1024, 2345, 102], [101, 2013, 1024, 1056, 29602, 6856, 1030, 14925, 1012, 14925, 2078, 1012, 19749, 1012, 3968, 2226, 1006, 2726, 1041, 12688, 1007, 3395, 1024, 1052, 2497, 3980, 1012, 1012, 1012, 3029, 1024, 102]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]}

输出解析:

  • input_ids :是文本被 tokenizer 转化后的数字 ID 序列,也就是模型实际接收的”输入信号”(这里我们设置了 truncation=True, max_length=32,所以每个序列都被截断/补齐到了固定长度 32)。
input_ids: [
[101, 2013, 1024, 3393, ..., 102], ← 第1篇新闻,32个token
[101, 2013, 1024, 3124, ..., 102], ← 第2篇新闻,32个token
[101, 2013, 1024, 1056, ..., 102] ← 第3篇新闻,32个token
]
  • token_type_ids :可以看到 token_type_ids 全是 0. 这是因为我们的输入是单句分类任务token_type_ids 是用来区分两个句子的:
    • 单句分类(你的情况):全为 0.
    • 句子对任务(如问答):第一个句子全 0,第二个句子全 1.
  • attention_mask :可以看到 attention_mask 全是 1. 因为这三篇新闻都没有被 padding(没有补 0)。1 表示”这个位置是真实 token,模型要关注它”。
    • 如果 batch 里有不同长度的句子,短的句子补了 [PAD](ID=0),对应的 attention_mask 就会是 0.
tokenizer(test_news[0], test_news[1], truncation=True, padding=True, max_length=32)
tokenizer.decode([101, 2013, 1024, 3393, 2099, 2595, 3367, 1030, 11333, 2213, 1012, 8529, 2094, 1012, 3968, 102, 2013, 1024, 3124, 5283, 2080, 1030, 9806, 1012, 1057, 1012, 2899, 1012, 3968, 2226, 1006, 102])

输出为:

{'input_ids': [101, 2013, 1024, 3393, 2099, 2595, 3367, 1030, 11333, 2213, 1012, 8529, 2094, 1012, 3968, 102, 2013, 1024, 3124, 5283, 2080, 1030, 9806, 1012, 1057, 1012, 2899, 1012, 3968, 2226, 1006, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
# 可以看到第一个句子和第二个句子
'[CLS] from : lerxst @ wam. umd. ed [SEP] from : guykuo @ carson. u. washington. edu ( [SEP]'

这里我们就能看出,对于句子对人物,第一个句子的 token_type_ids 全 0,第二个句子全 1.

关于句子对任务,其一般是用在 NSP(next sentence prediction) 任务中,也就是 BERT 的预训练任务。

顺带一提,BERT 的两大预训练任务分别是 NSP 和 MLM,其中 MLM 任务用的就是 mask.

3.1 MLM(Masked Language Model) 掩码语言模型#

是什么:随机遮住句子中 15% 的词,让 BERT 根据上下文去猜被遮住的是什么词。

输入: 我今天去 [MASK] 了,天气真好
BERT
预测: 我今天去 [公园] 了,天气真好

和笔记的联系:你之前看到的 tokenizer.vocab 中的 [MASK](ID=103)就是为此而生。MLM 让 BERT 学会了双向上下文理解——不仅要看左边的词,还要看右边的词,才能猜出被遮住的是什么。这是 BERT 的核心创新。


3.2 NSP(Next Sentence Prediction)— 下一句预测#

是什么:给 BERT 两个句子,让它判断第二句是不是第一句的”下一句”。

句子A: 我今天去了公园 ← 正例(是下一句)
句子B: 公园里有很多花和树 → 预测: IsNext
句子A: 我今天去了公园 ← 负例(不是下一句)
句子B: 苹果是一种很好吃的水果 → 预测: NotNext

和笔记的联系:这就是你刚在代码中看到的!

tokenizer(test_news[0], test_news[1], ...)

传入了两个句子(text + text_pair),所以输出中的 token_type_ids 变成了:

[0, 0, 0, ..., 0, 1, 1, 1, ..., 1]
~~~~句子A~~~~ [SEP] ~~~~句子B~~~~
  • 全 0 = 第一个句子的 token
  • 全 1 = 第二个句子的 token

这个 token_type_ids 就是 BERT 用来区分两个句子的”标记”,让模型知道哪些 token 属于句子 A、哪些属于句子 B。


3.3 两者与笔记的对应关系总结#

预训练任务笔记中的关联
MLM[MASK] token(ID=103)—— 用来遮住词让模型预测
NSPtoken_type_ids(全 0 / 全 1)—— 用来区分两个句子
[SEP] token(ID=102)—— 分隔两个句子
[CLS] token(ID=101)—— NSP 的分类信号就取自它的输出

BERT 先用 MLM 和 NSP 在大量无标签文本上预训练,然后再在你的情感分析任务上微调(即之前 distilbert-base-uncased-finetuned-sst-2-english 做的事情)。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

02_tokenizer_encode_plus_token_type_ids
https://github.com/chunhuizhang/bilibili_vlogs/tree/master/hugface
作者
HAC
发布于
2026-06-07
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
HAC
观之非易,行且克难
Greetings
欢迎来到我的博客!这里主要分享我的学习笔记与兴趣爱好。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
32
分类
5
标签
13
总字数
79,889
运行时长
0
最后活动
0 天前

文章目录