在深度学习领域,BERT作为强大的预训练模型广泛应用于各种自然语言处理任务。而BERT的核心输入预处理工具之一——分词器(Tokenizer),在模型的成功应用中扮演了至关重要的角色。本文将带你深入解析BERT分词器的工作原理、参数配置以及如何与模型结合完成推理任务,帮助你快速上手并理解这一关键组件。
一、BERT分词器的作用
在BERT模型中,分词器的作用是将自然语言文本转换为模型可以理解的输入格式。这一过程包括以下步骤:
1. 分词(Tokenization):
• 将原始文本拆分为子词单元(subwords tokens)。
• BERT采用的是WordPiece分词算法,可以有效处理未登录词(OOV)。
2. 添加特殊标记(Special Tokens):
• 添加 [CLS] 标记表示句子开头,用于分类任务。
• 添加 [SEP] 标记表示句子结束,用于分隔两个句子或句子结束。
3. 生成Token IDs:
• 将分词后的结果映射为词表中的唯一整数ID。
4. 生成其他特征:
• Attention Mask:区分实际文本和填充部分。
• Segment IDs:区分第一句和第二句(用于句对任务)。
二、分词器的使用流程
我们以具体代码为例,逐步展示如何加载分词器并完成文本预处理。
1. 初始化分词器
from transformers import BertTokenizer
# 加载预训练的BERT分词器
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
• from_pretrained 方法会加载指定路径或预训练模型的分词器配置和词表。
• 如果使用的是本地BERT模型,可以传入模型路径。
conf = Config() # 初始化配置对象
tokenizer = BertTokenizer.from_pretrained(conf.bert_path) # 从指定路径加载 BERT 分词器
2. 分词与预处理
使用分词器对文本进行处理:
inputs = tokenizer(
"睡一觉醒睡不着咋搞的?", # 输入文本
max_length=60, # 指定最大长度为 60
padding="max_length", # 填充到固定长度
truncation=True, # 超过最大长度时进行截断
return_tensors="pt" # 返回 PyTorch 张量
)
print(inputs)
输出结果:
{
# 分词后的 Token ID 序列
'input_ids': tensor([[101, 1234, 5678, ..., 102, 0, 0]]),
# 注意力掩码,区分有效文本和填充部分
'attention_mask': tensor([[1, 1, 1, ..., 1, 0, 0]])
}
3. 参数解析
1. max_length:
- 默认值为 None,即不限制序列长度。
- 如果指定,会将序列长度固定为 max_length,不足填充,超过截断。
2. padding:
- 默认值为 False,不进行填充。
- 常用值:
- "max_length":填充到 max_length 的长度。
- "longest":填充到当前批次中最长序列的长度。
3. truncation:
- 默认值为 False,不进行截断。
- 如果设置为 True,超过 max_length 的部分会被截断。
4. return_tensors:
- 默认值为 None,返回Python列表格式。
- 可选值:
- "pt":返回PyTorch张量。
- "tf":返回TensorFlow张量。
- "np":返回NumPy数组。
三、分词器与模型结合
预处理完成后,生成的 inputs 可直接输入到BERT模型中:
from transformers import BertModel
# 加载预训练的BERT模型
model = BertModel.from_pretrained("bert-base-chinese")
# 推理
outputs = model(**inputs)
# 输出结果
print(outputs)
输出包括:
1. last_hidden_state:每个输入token的隐藏状态表示。
2. pooler_output:句子级别的特征表示(对应于 [CLS] 的输出)。
四、完整代码示例
以下是一个完整的BERT分词器与推理任务的代码示例:
from transformers import BertTokenizer, BertModel
# 加载分词器和模型
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
model = BertModel.from_pretrained("bert-base-chinese")
# 文本预处理
inputs = tokenizer(
"睡一觉醒睡不着咋搞的?",
max_length=60,
padding="max_length",
truncation=True,
return_tensors="pt"
)
# 模型推理
outputs = model(**inputs)
# 查看结果
print("Input IDs:", inputs["input_ids"])
print("Attention Mask:", inputs["attention_mask"])
print("Last Hidden State Shape:", outputs.last_hidden_state.shape)
print("Pooler Output Shape:", outputs.pooler_output.shape)
结果输出:
五、BertModel模型推理的outputs
# 模型推理
outputs = model(**inputs)
在使用 BertModel 进行推理后,outputs 是一个包含多个关键输出的对象,通常为 BaseModelOutputWithPoolingAndCrossAttentions 类型。以下是具体的输出内容以及其含义:
outputs 的内容完整输出结构
假设:
• batch_size=1(一个句子)
• sequence_length=60(句子长度)
• hidden_size=768(BERT-base 的默认隐藏层大小)
outputs 结构:
BaseModelOutputWithPoolingAndCrossAttentions(
last_hidden_state=tensor of shape (1, 60, 768), # 每个 token 的表示
pooler_output=tensor of shape (1, 768), # [CLS] 的表示
hidden_states=None, # 如果指定输出所有层,可以获取各层隐藏状态
attentions=None # 如果启用,包含注意力权重
)
代码示例与解释
from transformers import BertTokenizer, BertModel
# 加载分词器和模型
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
model = BertModel.from_pretrained("bert-base-chinese")
# 文本预处理
inputs = tokenizer(
"睡一觉醒睡不着咋搞的?",
max_length=60,
padding="max_length",
truncation=True,
return_tensors="pt"
)
# 推理
outputs = model(**inputs)
# 输出结果
print("Last Hidden State Shape:", outputs.last_hidden_state.shape)
print("Pooler Output Shape:", outputs.pooler_output.shape)
输出示例:
对于 BertModel,推理输出包含两个主要部分:
# 1个句子,60个token,每个token的768维向量
Last Hidden State Shape: torch.Size([1, 60, 768])
# 1个句子,每个句子的768维向量
Pooler Output Shape: torch.Size([1, 768])
用途与场景
1. last_hidden_state(Token-wise 表示)
- 使用场景:
- 序列标注任务(如 NER、词性标注):需要每个 token 的上下文信息。
- 注意:这个结果包含了 [CLS] 和 [SEP] 标记的向量。
2. pooler_output(句子级别表示)
- 使用场景:
- 文本分类任务:直接使用 [CLS] 的输出特征进行分类。
- 句子相似度任务:比较两个句子的句子级别特征向量。
注意事项
1. 可选输出(hidden_states 和 attentions)
- 在模型初始化时,可以设置参数 output_hidden_states=True 和 output_attentions=True,获取更丰富的中间输出。
- hidden_states:包含每一层的隐层输出(共 12 层或 24 层)。
- attentions:包含每一层的注意力权重矩阵。
示例:
model = BertModel.from_pretrained("bert-base-chinese", output_hidden_states=True, output_attentions=True)
outputs = model(**inputs)
print(outputs.hidden_states) # 获取每层的输出
print(outputs.attentions) # 获取每层的注意力权重
2. 特定任务模型可能有额外输出
- 如果使用的是 BertForSequenceClassification 等下游任务模型,输出会直接包含分类或其他任务的结果。
outputs 的核心内容总结:
• last_hidden_state:每个 token 的上下文表示,适合序列标注任务。
• pooler_output:句子级别的特征表示,适合分类或句子比较任务。
通过理解 outputs 的内容和结构,可以根据任务需求灵活提取需要的特征,应用于各种自然语言处理任务。
3. 案例:使用bert 进行分类:
• 使用 pooler_output(对应 [CLS] 的隐藏状态)作为全连接层的输入:
logits = classifier(pooler_output)
• classifier 是一个全连接层,输出的维度等于分类标签的数量(比如情感分类是 positive 和 negative,则输出维度为 2)。
• 使用 softmax 函数获取概率分布,选择概率最大的类别作为分类结果。
这就是我们的分类输出
六、注意事项与常见问题
1. 分词器与模型必须匹配:
• 分词器的词表和分词方式需要与对应的BERT模型一致,否则可能导致索引错误。
2. 文本长度限制:
• BERT的最大输入长度通常为512,超过此限制会报错。
3. 动态填充与静态填充:
• 动态填充适合批处理推理,节省计算资源。
• 静态填充适合固定长度任务。
七、总结
BERT分词器是预训练模型中不可或缺的一环,其主要作用是将自然语言文本转化为模型可处理的输入格式。在实际应用中,合理配置分词器的参数,并结合模型的需求进行预处理,能够显著提升模型的推理效率与性能。
你在使用BERT分词器时是否遇到过问题?欢迎在评论区交流你的经验与心得!