BERT中的padding操作
-
- 为什么要Padding?
-
- [1. 批处理(Batching)的需求](#1. 批处理(Batching)的需求)
- [2. BERT的特殊原因](#2. BERT的特殊原因)
- BERT中Padding的具体实现
-
- 三个关键组件
- [配套的Attention Mask](#配套的Attention Mask)
- Padding在BERT中的详细处理流程
-
- [1. 输入阶段](#1. 输入阶段)
- [2. BERT前向传播中的处理](#2. BERT前向传播中的处理)
- [为什么Attention Mask如此重要?](#为什么Attention Mask如此重要?)
- [BERT Padding的特殊考量](#BERT Padding的特殊考量)
-
- [1.[PAD] token的特殊性](#1.[PAD] token的特殊性)
- [2. 位置嵌入的问题](#2. 位置嵌入的问题)
- [3. 实际训练中的技巧](#3. 实际训练中的技巧)
- 总结
BERT的数据必须要经过padding操作。这是深度学习中处理批量数据的一个基本要求,同时BERT有一些特殊原因。
为什么要Padding?
1. 批处理(Batching)的需求
python
# 如果没有padding,一个batch可能是这样:
batch = [
[我, 爱, 吃, 苹果], # 长度4
[今天, 天气, 很好], # 长度3
[自然语言处理, 是, 人工智能, 的, 重要, 分支] # 长度6
]
# ❌ 无法组成张量,长度不一致
2. BERT的特殊原因
python
# BERT需要固定长度的输入
# 因为位置嵌入(position embeddings)是预先定义好的
self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size)
# max_position_embeddings = 512(BERT的最大长度)
# 位置嵌入层只能处理0-511的位置索引
BERT中Padding的具体实现
三个关键组件
python
input_ids = [
[101, 1043, 2345, 102, 0, 0, 0], # 长度7,padding了3个0
[101, 2348, 1043, 2003, 102, 0, 0], # 长度7,padding了2个0
[101, 3829, 2769, 102, 0, 0, 0] # 长度7,padding了3个0
]
# 现在所有句子都长度一致,可以组成 [batch_size, max_len] 的张量
配套的Attention Mask
python
attention_mask = [
[1, 1, 1, 1, 0, 0, 0], # 前4个是真实token,后3个是padding
[1, 1, 1, 1, 1, 0, 0], # 前5个是真实token,后2个是padding
[1, 1, 1, 1, 0, 0, 0] # 前4个是真实token,后3个是padding
]
# 1表示真实token,0表示padding
Padding在BERT中的详细处理流程
1. 输入阶段
python
def prepare_inputs_for_bert(sentences, tokenizer, max_len=512):
"""
将句子转换为BERT输入格式
"""
# 1. Tokenization(分词)
tokens = tokenizer(sentences,
padding='max_length', # 自动padding到max_length
truncation=True, # 超过max_length则截断
max_length=max_len, # BERT最大512
return_tensors='pt') # 返回PyTorch张量
# 返回结果包含:
return {
'input_ids': tokens['input_ids'], # 已经padding好的token IDs
'attention_mask': tokens['attention_mask'], # 对应的mask
'token_type_ids': tokens.get('token_type_ids', None) # 句子类型
}
2. BERT前向传播中的处理
python
class BERTModel(nn.Module):
def forward(self, input_ids, attention_mask=None):
# input_ids: [batch_size, seq_len],已经padding好
# attention_mask: [batch_size, seq_len],标识哪些是padding
# 1. 获取嵌入
embeddings = self.get_embeddings(input_ids)
# 2. 通过Encoder层时,需要传入attention_mask
for layer in self.encoder_layers:
# attention_mask告诉注意力机制忽略padding位置
# 实现方式:在计算注意力分数时,将padding位置的分数设为负无穷
# attention_scores = attention_scores + (1 - attention_mask) * -10000.0
# 这样经过softmax后,padding位置的权重就为0了
embeddings = layer(embeddings, attention_mask)
return embeddings
为什么Attention Mask如此重要?
没有Mask的情况(灾难)
python
# 假设padding位置也参与注意力计算
# 输入: [我, 爱, 吃, 苹果, [PAD], [PAD]]
# 在计算"苹果"的表示时,也会考虑[PAD]的信息
# 这会导致:
# 1. 学习到错误的信息
# 2. [PAD]的表示会影响真实token的表示
# 3. 模型性能严重下降
有Mask的正确情况
python
# 注意力机制的实现
def scaled_dot_product_attention(Q, K, V, mask=None):
# Q, K, V: [batch_size, num_heads, seq_len, d_k]
# mask: [batch_size, 1, 1, seq_len] 或 [batch_size, 1, seq_len, seq_len]
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
if mask is not None:
# 将mask为0的位置设为负无穷大
scores = scores.masked_fill(mask == 0, float('-inf'))
attention_weights = F.softmax(scores, dim=-1)
output = torch.matmul(attention_weights, V)
return output
BERT Padding的特殊考量
1.[PAD] token的特殊性
python
# 在BERT词表中,[PAD]通常被分配ID=0
# 但注意:[PAD]本身也有词嵌入!
# 所以我们需要mask来告诉模型:"这个位置是padding,请忽略"
# 对比其他token:
# [CLS] = 101,有特殊含义
# [SEP] = 102,表示句子结束
# [MASK] = 103,用于预训练
# [UNK] = 100,未知词
# [PAD] = 0,填充
2. 位置嵌入的问题
python
# 即使padding的位置,也会被分配位置嵌入
# 例如:句子长度3,padding到长度5
# position_ids = [0, 1, 2, 3, 4] # 所有位置都有位置编码
# 但通过attention_mask,位置3和4不会被关注
3. 实际训练中的技巧
python
# 动态padding:为了效率,不是都padding到512
batch = [
[101, 1043, 102], # 长度3
[101, 2348, 1043, 102], # 长度4
[101, 3829, 2769, 2793, 102] # 长度5
]
# 动态padding到batch内的最大长度(这里是5),而不是固定的512
padded_batch = [
[101, 1043, 102, 0, 0], # padding 2个
[101, 2348, 1043, 102, 0], # padding 1个
[101, 3829, 2769, 2793, 102] # 不padding
]
# 这样能节省内存和计算量
总结
为什么BERT必须padding:
- 技术原因:批处理需要统一长度的张量
- 架构原因:位置嵌入层有固定的最大长度(512)
- 效率原因:GPU/TPU需要规整的张量进行并行计算
如何正确处理padding:
- 使用
[PAD]token(ID=0)填充句子到相同长度 - 必须配套使用attention_mask,告诉模型哪些位置是padding
- 在注意力计算中,将padding位置的注意力权重设为0
- 在损失计算中,忽略padding位置的输出
关键点:
- Padding本身无害,BERT会给[PAD] token分配嵌入向量
- 没有mask的padding才有害,会让模型学到错误信息
- Attention mask是BERT正确工作的关键,它确保了padding位置不参与计算
这就是为什么你在使用BERT时,总是需要同时提供input_ids和attention_mask!