07_model_outputs
本节我们详细地探讨 BERT 模型的 outputs.
import torchfrom torch import nnfrom transformers import BertModel, BertTokenizermodel_name = 'bert-base-uncased'
tokenizer = BertTokenizer.from_pretrained(model_name)model = BertModel.from_pretrained(model_name, output_hidden_states=True)1. input
# 表示拼接text = 'After stealing money from the bank vault, the bank robber was seen '\ 'fishing on the Mississippi river bank.'token_input = tokenizer(text, return_tensors='pt')print(token_input)输出为:
{'input_ids': tensor([[ 101, 2044, 11065, 2769, 2013, 1996, 2924, 11632, 1010, 1996, 2924, 27307, 2001, 2464, 5645, 2006, 1996, 5900, 2314, 2924, 1012, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}token_input['input_ids'], token_input['input_ids'].shape输出为:
(tensor([[ 101, 2044, 11065, 2769, 2013, 1996, 2924, 11632, 1010, 1996, 2924, 27307, 2001, 2464, 5645, 2006, 1996, 5900, 2314, 2924, 1012, 102]]), torch.Size([1, 22]))可见 batch_size = 1(我们只有一个句子),序列长度 seq_length = 22(没有 truncate 和 padding).
2. model forward
- forward
- embedding -> encoder -> pooler
model.eval()with torch.no_grad(): outputs = model(**token_input)outputs输出为:
BaseModelOutputWithPoolingAndCrossAttentions(last_hidden_state=tensor([[[-0.4964, -0.1831, -0.5231, ..., -0.1902, 0.3738, 0.3964], [-0.1323, -0.2762, -0.3495, ..., -0.4567, 0.3786, -0.1096], [-0.3626, -0.4002, 0.0676, ..., -0.3207, -0.2709, -0.3004], ..., [ 0.2961, -0.2856, -0.0382, ..., -0.6056, -0.5163, 0.2005], [ 0.4878, -0.0909, -0.2358, ..., -0.0017, -0.5945, -0.2431], [-0.2517, -0.3519, -0.4688, ..., 0.2500, 0.0336, -0.2627]]]), pooler_output=tensor([[-0.6031, -0.3342, -0.7174, 0.3347, 0.5145, -0.1722, 0.4502, 0.2768, -0.3769, -0.9998, -0.3657, 0.7535, 0.9817, -0.0192, 0.7959, -0.3459, -0.1338, -0.3026, 0.1097, 0.5836, 0.5736, 0.9999, 0.1798, 0.1845, 0.2250, 0.9109, -0.5653, 0.8616, 0.8994, 0.7423, -0.2525, 0.0394, -0.9894, -0.1331, -0.7763, -0.9826, 0.2223, -0.6115, 0.1941, 0.0177, -0.7634, 0.2312, 0.9999, -0.7000, 0.4623, -0.2202, -1.0000, 0.1908, -0.8150, 0.6483, 0.5878, 0.8198, 0.1014, 0.3185, 0.3963, -0.3216, -0.1701, 0.0588, -0.1544, -0.4987, -0.5284, 0.1228, -0.4823, -0.7788, 0.6954, 0.0891, -0.0855, -0.1500, 0.0390, -0.0760, 0.6154, 0.2662, -0.0129, -0.7253, 0.1352, 0.2921, -0.5613, 1.0000, 0.1536, -0.9681, 0.7166, 0.2600, 0.4519, 0.5470, -0.2798, -1.0000, 0.3419, -0.2645, -0.9863, 0.1263, 0.5249, -0.2000, 0.5980, 0.4752, -0.2355, -0.4808, -0.3786, -0.7284, -0.0909, 0.0124, -0.0689, -0.2531, -0.1324, -0.2361, 0.1732, -0.3216, -0.0188, 0.2302, -0.3221, 0.4996, 0.4346, -0.1935, 0.2968, -0.9292, 0.5326, -0.3695, -0.9876, -0.4770, -0.9902, 0.6349, -0.1863, -0.2612, 0.9123, -0.1930, 0.3110, 0.0803, -0.7598, -1.0000, 0.0292, -0.0628, -0.1086, -0.2135, -0.9671, -0.9521, 0.3334, 0.8675, 0.2254, 0.9995, -0.2908, 0.9420, 0.0336, -0.4542, 0.4123, -0.4455, 0.4558, -0.4442, -0.0685, 0.3043, 0.0575, 0.1843, -0.6577, -0.3121,... [-0.3626, -0.4002, 0.0676, ..., -0.3207, -0.2709, -0.3004], ..., [ 0.2961, -0.2856, -0.0382, ..., -0.6056, -0.5163, 0.2005], [ 0.4878, -0.0909, -0.2358, ..., -0.0017, -0.5945, -0.2431], [-0.2517, -0.3519, -0.4688, ..., 0.2500, 0.0336, -0.2627]]])), past_key_values=None, attentions=None, cross_attentions=None)这里就是我们把 token_input 丢给 BERT 模型进行前向传播。
其中 **token_input 是把字典展开为关键字参数,相当于:
# 不要执行这段代码
# token_input 是一个 dict,包含:# {# 'input_ids': tensor([[101, 2044, ..., 102]]), # shape: (1, 22)# 'token_type_ids': tensor([[0, 0, ..., 0]]), # shape: (1, 22)# 'attention_mask': tensor([[1, 1, ..., 1]]) # shape: (1, 22)# }## 所以 model(**token_input) 等价于:outputs = model( input_ids=tensor_input_ids, token_type_ids=tensor_token_type_ids, attention_mask=tensor_attention_mask)3. output
len(outputs) == 3outputs[0]last_hidden_state, shape:batch_size*seq_len*hidden_size(1 * 22 * 768)
outputs[1]pooler_output, shape:batch_size*hidden_size(1 * 768)
outputs[2](model.config.output_hidden_states == True)- type: tuple
- one for the output of the embeddings(1), if the model has an embedding layer(12), + one for the output of each layer
- (1 + 12) * (
batch_size*seq_len*hidden_size) = 1 * 22 * 768
- (1 + 12) * (
len(outputs) # 3
type(outputs[2]), len(outputs[2]) # (tuple, 13)outputs[0]
outputs[2][-1]
outputs[0] == outputs[2][-1] # True输出为:
tensor([[[-0.4964, -0.1831, -0.5231, ..., -0.1902, 0.3738, 0.3964], [-0.1323, -0.2762, -0.3495, ..., -0.4567, 0.3786, -0.1096], [-0.3626, -0.4002, 0.0676, ..., -0.3207, -0.2709, -0.3004], ..., [ 0.2961, -0.2856, -0.0382, ..., -0.6056, -0.5163, 0.2005], [ 0.4878, -0.0909, -0.2358, ..., -0.0017, -0.5945, -0.2431], [-0.2517, -0.3519, -0.4688, ..., 0.2500, 0.0336, -0.2627]]])
tensor([[[-0.4964, -0.1831, -0.5231, ..., -0.1902, 0.3738, 0.3964], [-0.1323, -0.2762, -0.3495, ..., -0.4567, 0.3786, -0.1096], [-0.3626, -0.4002, 0.0676, ..., -0.3207, -0.2709, -0.3004], ..., [ 0.2961, -0.2856, -0.0382, ..., -0.6056, -0.5163, 0.2005], [ 0.4878, -0.0909, -0.2358, ..., -0.0017, -0.5945, -0.2431], [-0.2517, -0.3519, -0.4688, ..., 0.2500, 0.0336, -0.2627]]])
tensor([[[True, True, True, ..., True, True, True], [True, True, True, ..., True, True, True], [True, True, True, ..., True, True, True], ..., [True, True, True, ..., True, True, True], [True, True, True, ..., True, True, True], [True, True, True, ..., True, True, True]]])可以看到二者的输出是相等的。因为 outputs[0] 就是最后一层隐藏层的状态,而 outputs[2][-1] 就是所有隐藏层状态的最后一个,二者当然相等。
outputs[1]
model.pooler(outputs[2][-1])
outputs[1] == model.pooler(outputs[2][-1]) # True输出为:
tensor([[-0.6031, -0.3342, -0.7174, 0.3347, 0.5145, -0.1722, 0.4502, 0.2768, -0.3769, -0.9998, -0.3657, 0.7535, 0.9817, -0.0192, 0.7959, -0.3459, -0.1338, -0.3026, 0.1097, 0.5836, 0.5736, 0.9999, 0.1798, 0.1845, 0.2250, 0.9109, -0.5653, 0.8616, 0.8994, 0.7423, -0.2525, 0.0394, -0.9894, -0.1331, -0.7763, -0.9826, 0.2223, -0.6115, 0.1941, 0.0177, -0.7634, 0.2312, 0.9999, -0.7000, 0.4623, -0.2202, -1.0000, 0.1908, -0.8150, 0.6483, 0.5878, 0.8198, 0.1014, 0.3185, 0.3963, -0.3216, -0.1701, 0.0588, -0.1544, -0.4987, -0.5284, 0.1228, -0.4823, -0.7788, 0.6954, 0.0891, -0.0855, -0.1500, 0.0390, -0.0760, 0.6154, 0.2662, -0.0129, -0.7253, 0.1352, 0.2921, -0.5613, 1.0000, 0.1536, -0.9681, 0.7166, 0.2600, 0.4519, 0.5470, -0.2798, -1.0000, 0.3419, -0.2645, -0.9863, 0.1263, 0.5249, -0.2000, 0.5980, 0.4752, -0.2355, -0.4808, -0.3786, -0.7284, -0.0909, 0.0124, -0.0689, -0.2531, -0.1324, -0.2361, 0.1732, -0.3216, -0.0188, 0.2302, -0.3221, 0.4996, 0.4346, -0.1935, 0.2968, -0.9292, 0.5326, -0.3695, -0.9876, -0.4770, -0.9902, 0.6349, -0.1863, -0.2612, 0.9123, -0.1930, 0.3110, 0.0803, -0.7598, -1.0000, 0.0292, -0.0628, -0.1086, -0.2135, -0.9671, -0.9521, 0.3334, 0.8675, 0.2254, 0.9995, -0.2908, 0.9420, 0.0336, -0.4542, 0.4123, -0.4455, 0.4558, -0.4442, -0.0685, 0.3043, 0.0575, 0.1843, -0.6577, -0.3121, -0.1069, -0.7729, -0.2328, 0.9032, -0.4681, -0.5580, 0.3359, -0.1644, -0.1572, 0.6441, 0.2810, 0.2651, 0.1532, 0.4485, -0.5299, 0.2616, -0.7412, -0.0347, 0.2560, -0.2659, -0.6092, -0.9868, -0.2346, 0.4682, 0.9771, 0.5646, 0.2360, 0.4567, -0.2170, 0.1420, -0.9554, 0.9832, -0.0935, 0.2621, -0.7901, 0.5758, -0.7642, -0.5362, 0.6374, -0.3891, -0.6368, -0.0045, -0.2658, -0.1721, -0.7466, 0.4832, -0.3128, -0.2640,... -0.0989, -0.3853, -0.3173, -0.5348, 0.5307, -0.6883, -0.3786, -0.4665, 0.7207, 0.3239, 0.9999, -0.5526, -0.4825, -0.3439, -0.3580, 0.1069, -0.2620, -1.0000, 0.2673, -0.3037, 0.4706, -0.6132, 0.8511, -0.4910, -0.7231, -0.1092, 0.5412, 0.5265, -0.4635, -0.2461, 0.4946, -0.3469, 0.9149, 0.5902, -0.1862, 0.7004, 0.5465, -0.2839, -0.5723, 0.6648]])
tensor([[-0.6031, -0.3342, -0.7174, 0.3347, 0.5145, -0.1722, 0.4502, 0.2768, -0.3769, -0.9998, -0.3657, 0.7535, 0.9817, -0.0192, 0.7959, -0.3459, -0.1338, -0.3026, 0.1097, 0.5836, 0.5736, 0.9999, 0.1798, 0.1845, 0.2250, 0.9109, -0.5653, 0.8616, 0.8994, 0.7423, -0.2525, 0.0394, -0.9894, -0.1331, -0.7763, -0.9826, 0.2223, -0.6115, 0.1941, 0.0177, -0.7634, 0.2312, 0.9999, -0.7000, 0.4623, -0.2202, -1.0000, 0.1908, -0.8150, 0.6483, 0.5878, 0.8198, 0.1014, 0.3185, 0.3963, -0.3216, -0.1701, 0.0588, -0.1544, -0.4987, -0.5284, 0.1228, -0.4823, -0.7788, 0.6954, 0.0891, -0.0855, -0.1500, 0.0390, -0.0760, 0.6154, 0.2662, -0.0129, -0.7253, 0.1352, 0.2921, -0.5613, 1.0000, 0.1536, -0.9681, 0.7166, 0.2600, 0.4519, 0.5470, -0.2798, -1.0000, 0.3419, -0.2645, -0.9863, 0.1263, 0.5249, -0.2000, 0.5980, 0.4752, -0.2355, -0.4808, -0.3786, -0.7284, -0.0909, 0.0124, -0.0689, -0.2531, -0.1324, -0.2361, 0.1732, -0.3216, -0.0188, 0.2302, -0.3221, 0.4996, 0.4346, -0.1935, 0.2968, -0.9292, 0.5326, -0.3695, -0.9876, -0.4770, -0.9902, 0.6349, -0.1863, -0.2612, 0.9123, -0.1930, 0.3110, 0.0803, -0.7598, -1.0000, 0.0292, -0.0628, -0.1086, -0.2135, -0.9671, -0.9521, 0.3334, 0.8675, 0.2254, 0.9995, -0.2908, 0.9420, 0.0336, -0.4542, 0.4123, -0.4455, 0.4558, -0.4442, -0.0685, 0.3043, 0.0575, 0.1843, -0.6577, -0.3121, -0.1069, -0.7729, -0.2328, 0.9032, -0.4681, -0.5580, 0.3359, -0.1644, -0.1572, 0.6441, 0.2810, 0.2651, 0.1532, 0.4485, -0.5299, 0.2616, -0.7412, -0.0347, 0.2560, -0.2659, -0.6092, -0.9868, -0.2346, 0.4682, 0.9771, 0.5646, 0.2360, 0.4567, -0.2170, 0.1420, -0.9554, 0.9832, -0.0935, 0.2621, -0.7901, 0.5758, -0.7642, -0.5362, 0.6374, -0.3891, -0.6368, -0.0045, -0.2658, -0.1721, -0.7466, 0.4832, -0.3128, -0.2640,... -0.0989, -0.3853, -0.3173, -0.5348, 0.5307, -0.6883, -0.3786, -0.4665, 0.7207, 0.3239, 0.9999, -0.5526, -0.4825, -0.3439, -0.3580, 0.1069, -0.2620, -1.0000, 0.2673, -0.3037, 0.4706, -0.6132, 0.8511, -0.4910, -0.7231, -0.1092, 0.5412, 0.5265, -0.4635, -0.2461, 0.4946, -0.3469, 0.9149, 0.5902, -0.1862, 0.7004, 0.5465, -0.2839, -0.5723, 0.6648]])
tensor([[True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True,... True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]])可以看到,outputs[1] 实际上就是 outputs[0] 中 [CLS] 的那个 1 * 768 维的向量,再经过一个 全连接层 + tanh 激活函数得到的东西。
但对于 model.pooler(outputs[2][-1]),outputs[2][-1] 似乎拿出的是最后一个隐藏层的状态,而 outputs[1] 是最后一个隐藏层状态的第一个 token [CLS] 经过激活函数后的输出。那么,model.pooler(outputs[2][-1]) 不应该是对最后一个隐藏层的所有 tokens 都做激活函数处理吗?为什么他会与只取第一个 token 的 outputs[1] 相等?
的确,如果 pooler 真的对全部 tokens 都做激活,那么输出形状应该是 (1, 22, 768),不可能和 (1, 768) 的 outputs[1] 相等。
关键的秘密在于:model.pooler 内部其实只取了第一个 token([CLS]),而不是对所有 tokens 做处理。
我们来看看 HuggingFace 的源码:
class BertPooler(nn.Module): def __init__(self, config): super().__init__() self.dense = nn.Linear(config.hidden_size, config.hidden_size) self.activation = nn.Tanh()
def forward(self, hidden_states): # ⭐ 关键在这里:只取第一个 token ([CLS])! first_token_tensor = hidden_states[:, 0] pooled_output = self.dense(first_token_tensor) pooled_output = self.activation(pooled_output) return pooled_output看到 hidden_states[:, 0] 了吗?Pooler 在 forward 的第一步就做了切片——从 (1, 22, 768) 中只取出 [CLS] 对应的 (1, 768),剩下的 21 个 token 直接丢弃了。
所以整个过程是:
outputs[2][-1] shape: (1, 22, 768) │ │ pooler.forward() 内部: │ hidden_states[:, 0] ← 只取 [CLS] │ ↓ │ shape: (1, 768) │ ↓ │ nn.Linear(768→768) │ ↓ │ nn.Tanh() │ ↓ ▼pooler_output shape: (1, 768) ✅ 与 outputs[1] 一致而 outputs[1] 本质上就是 BERT 模型内部调用了完全相同的这个 model.pooler(outputs[0]) 计算出来的,所以二者必然相等。
总结:Pooler 不是”对所有 tokens 做激活”,而是”只取 [CLS] 的向量,然后做全连接+tanh”。它的设计目的就是为句子级别的分类任务压缩出一个固定长度的句子表示向量。
outputs[2][0]
model.embeddings(token_input['input_ids'], token_input['token_type_ids'])
outputs[2][0] == model.embeddings(token_input['input_ids'], token_input['token_type_ids']) # True输出为:
tensor([[[ 0.1686, -0.2858, -0.3261, ..., -0.0276, 0.0383, 0.1640], [ 0.2329, 0.1390, 0.2979, ..., -0.0655, 0.8885, 0.5109], [ 0.2257, -0.7165, -0.7255, ..., 0.4844, 0.6030, -0.0957], ..., [-0.0374, -0.6155, -1.4419, ..., 0.0793, -0.0811, -0.3802], [-0.0228, 0.4207, -0.3288, ..., 0.4464, 0.5178, 0.5501], [-0.2350, 0.1566, -0.0462, ..., -0.4206, 0.3074, -0.2288]]])
tensor([[[ 0.1686, -0.2858, -0.3261, ..., -0.0276, 0.0383, 0.1640], [ 0.2329, 0.1390, 0.2979, ..., -0.0655, 0.8885, 0.5109], [ 0.2257, -0.7165, -0.7255, ..., 0.4844, 0.6030, -0.0957], ..., [-0.0374, -0.6155, -1.4419, ..., 0.0793, -0.0811, -0.3802], [-0.0228, 0.4207, -0.3288, ..., 0.4464, 0.5178, 0.5501], [-0.2350, 0.1566, -0.0462, ..., -0.4206, 0.3074, -0.2288]]], grad_fn=<NativeLayerNormBackward0>)
tensor([[[True, True, True, ..., True, True, True], [True, True, True, ..., True, True, True], [True, True, True, ..., True, True, True], ..., [True, True, True, ..., True, True, True], [True, True, True, ..., True, True, True], [True, True, True, ..., True, True, True]]])可见,outputs[2][0] 实际上是所有隐藏层状态中的第一个,也就是 embedding 层,它的后面才是 12 个 transformer 层。
自然,embedding 层等于 model.embeddings(token_input['input_ids'], token_input['token_type_ids']). 注意这里的 model.embeddings() 不接受 attention_mask 参数,所以我们只传入 input_ids 和 token_type_ids.
for i in range(len(outputs[2])): print(i, outputs[2][i].shape)输出为:
0 torch.Size([1, 22, 768])1 torch.Size([1, 22, 768])2 torch.Size([1, 22, 768])3 torch.Size([1, 22, 768])4 torch.Size([1, 22, 768])5 torch.Size([1, 22, 768])6 torch.Size([1, 22, 768])7 torch.Size([1, 22, 768])8 torch.Size([1, 22, 768])9 torch.Size([1, 22, 768])10 torch.Size([1, 22, 768])11 torch.Size([1, 22, 768])12 torch.Size([1, 22, 768])这里我们就能很清晰地看到所有隐藏层的形状了。
最后,详细解释如下。
3.1 为什么 len(outputs) == 3
outputs 是 HuggFace 的 BaseModelOutputWithPoolingAndCrossAttentions 对象,它的设计类似一个命名元组,支持两种访问方式:
| 索引 | 属性名 | 内容 | shape |
|---|---|---|---|
outputs[0] | outputs.last_hidden_state | 最后一层编码器输出 | 1 × 22 × 768 |
outputs[1] | outputs.pooler_output | 池化后的 [CLS] 向量 | 1 × 768 |
outputs[2] | outputs.hidden_states | 所有层的 hidden states(元组) | 13个 1×22×768 |
这就像你有一个文件夹:
outputs/ ← len(outputs) = 3(顶层文件夹数)├── last_hidden_state/ ← outputs[0],里面有 1×22×768 个数字├── pooler_output/ ← outputs[1],里面有 1×768 个数字└── hidden_states/ ← outputs[2],里面还有 13 个子文件夹 ├── layer_0 (embedding) ├── layer_1 ├── ... └── layer_12len() 只统计顶层有几个”文件夹”,而不是递归数里面所有的”文件”。 所以返回 3 是对的 ✅
3.2 三个输出分别是什么?
① last_hidden_state(最后一层隐藏状态)
shape: (batch_size=1, seq_len=22, hidden_size=768)含义: 句子中每个 token 经过 BERT 全部 12 层 transformer 编码后的最终向量表示。
- 每一行对应一个 token(
[CLS]→after→stealing→ … →bank→[SEP]) - 每个 token 被编码为一个 768 维的稠密向量
- 这 22 个向量各自包含了上下文信息——比如句子中两个
bank虽然 token 相同,但它们在第 10 行和第 20 行的向量是不一样的,因为上下文不同
例如:token "bank" 出现了 3 次 position 6: "the bank vault" → 银行(金融机构) position 10: "the bank robber" → 银行(金融机构) position 19: "river bank" → 河岸(地理位置) → 这三个 bank 的 768 维向量是不同的!因为 BERT 编码了上下文用途: 这是最通用的特征,可以用来做序列标注(命名实体识别、词性标注)或直接取某个位置的向量做下游任务。
② pooler_output(池化输出)
shape: (batch_size=1, hidden_size=768)含义: 专门为句子级别的分类任务设计的输出。
它的计算方式:
- 先取出
last_hidden_state中第一个 token([CLS])的向量 - 再经过一个 全连接层 + tanh 激活函数
last_hidden_state[0, 0, :] → [CLS] 的 768 维向量 ↓ 全连接层 (768 → 768) ↓ tanh 激活 ↓ pooler_output (768 维)为什么要有 pooler_output?
BERT 在预训练时,[CLS] token 被设计用来聚合整个句子的信息(做下一句预测任务)。所以 pooler_output 是对整个句子的压缩表示。
用途: 文本分类、情感分析、句子对匹配等句子级别的任务。
③ hidden_states(所有隐藏层状态)
type: tuple of 13 tensors每个 tensor shape: (batch_size=1, seq_len=22, hidden_size=768)含义: BERT 每一层的输出都被保存下来了,包括第 0 层(embedding 层)到第 12 层(最后一层 transformer)。
hidden_states[0] → embedding 层的输出(还没经过 transformer)hidden_states[1] → 第 1 层 transformer 的输出hidden_states[2] → 第 2 层 transformer 的输出...hidden_states[12] → 第 12 层 transformer 的输出 = last_hidden_state 🎯为什么需要看每一层?
不同层的编码器学到了不同层次的语言特征:
| 层次 | 学到了什么 | 类比 |
|---|---|---|
| 底层(1-4) | 词法、语法特征 | 像学语言时先认单词、学词性 |
| 中层(5-8) | 句法、语义特征 | 理解短语结构、主谓关系 |
| 高层(9-12) | 上下文语义、抽象概念 | 理解整体意思、指代关系 |
用途: 某些任务中拼接多层的特征比只用最后一层效果更好(比如命名实体识别中拼最后 4 层)。这也是为什么需要设置
output_hidden_states=True。
一张图总结
输入句子: "After stealing money from the bank vault..."
Tokenization ↓ ┌──────────────────┐ │ Embedding 层 │ ← hidden_states[0] └────────┬─────────┘ ↓ ┌──────────────────┐ │ Transformer 1 │ ← hidden_states[1] └────────┬─────────┘ ↓ ... ↓ ┌──────────────────┐ │ Transformer 12 │ ← hidden_states[12] = last_hidden_state └────────┬─────────┘ ↓ 取 [CLS] 位置 + 全连接层 + tanh ↓ pooler_output文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!