【Transformers 实战——特征提取】从 Tokenizer 编码到 BERT 四类返回值详解与 NER 选型避坑

文章目录

  • 1、`my_tokenizer.encode_plus(...)`
  • 2、代码
  • [3、`my_model(...)` 返回值:`last_hidden_state` 和 `pooler_output`](#3、my_model(...) 返回值:last_hidden_statepooler_output)
  • [4、`my_model(...)` 返回值:`hidden_states` 和 `attentions`](#4、my_model(...) 返回值:hidden_statesattentions)
  • [5、`my_model(...)` 返回值总结:两种](#5、my_model(...) 返回值总结:两种)

模型的加载及其细节,可以看这篇文章:一文吃透 Transformers 模型加载:from_pretrained 参数大全与推理全流程解析

分词器的加载及其细节,可以看这篇文章:一文吃透 HuggingFace Tokenizer:API 参数详解、encode 避坑、模型推理全流程实战

1、my_tokenizer.encode_plus(...)

简单来说,encode_plustransformers 库中一个功能强大的底层方法 。它主要用于对单条 文本(或一个文本对)进行完整编码,并返回一个包含 input_idsattention_mask 等丰富信息的 BatchEncoding 对象 。尽管它曾经是主要接口,但现在官方更推荐使用高层封装的 __call__ 方法。

encode_plusmy_tokenizer(...) 以及 my_tokenizer.encode(...) 的核心区别在于调用层级和信息完整度

方法 调用层级 输入支持 返回值 推荐程度
encode() 便捷方法 单条 / 批量 input_ids 列表(或张量) 仅用于快速测试
encode_plus() 底层方法 单条(或文本对) 信息完整的 BatchEncoding 对象 阅读旧代码时需了解
batch_encode_plus() 底层方法 批量文本 信息完整的 BatchEncoding 对象 批量编码的底层实现
__call__() 官方推荐 单条 / 批量 信息完整的 BatchEncoding 对象 日常开发首选

📌 三者关系详解

  • encode() :一个"轻量级"便捷方法,只返回纯净的 input_ids 序列 。它不提供 attention_masktoken_type_ids 等关键辅助信息,因此极易触发模型关于缺失 attention_mask 的警告。通常只适合在快速查看 token ID 或调试时使用。
  • encode_plus() :专注于单条文本 编码的"基本工作单元",返回一个包含完整编码信息的 BatchEncoding 对象。它是 __call__ 方法处理单条输入时的底层实现。
  • batch_encode_plus() :与 encode_plus 对应,专门用于批量文本的高效编码,同样是底层方法。
  • __call__() :这是当前官方推荐 的统一入口。它内部会根据输入类型智能分发:
    • 传入单条文本 → 调用 encode_plus()
    • 传入文本列表 → 调用 batch_encode_plus()
      因此,使用 __call__() 方法可以同时获得代码的简洁性和批量处理的高效性,并始终返回可直接传入模型的 BatchEncoding 对象。

🧬 深入理解 encode_plus

函数签名与关键参数

encode_plus 提供了对编码过程的精细控制,其参数与你熟悉的 __call__ 方法高度一致,因为后者正是对其的封装。

python 复制代码
def encode_plus(
    text,                          # 核心:要编码的文本,或文本对 (text, text_pair)
    text_pair=None,                # 可选:第二个文本,用于句对任务
    add_special_tokens=True,       # 是否添加 [CLS], [SEP] 等特殊标记
    padding=False,                 # 填充策略
    truncation=False,              # 截断策略
    max_length=None,               # 最大长度限制
    return_tensors=None,           # 返回张量类型 ('pt', 'tf', 'np', None)
    return_attention_mask=None,    # 是否返回注意力掩码(默认遵循模型设置)
    return_token_type_ids=True,    # 是否返回分段ID(BERT系模型需要)
    return_offsets_mapping=False,  # 是否返回 token 到原字符的偏移映射
    return_special_tokens_mask=False, # 是否返回特殊 token 掩码
    return_length=False,           # 是否返回去除填充后的真实长度
    **kwargs                       # 其他参数
)

返回值:BatchEncoding 对象

encode() 只返回简单列表不同,encode_plus 返回的是一个功能丰富的 BatchEncoding 对象 (行为类似字典),你可以直接将其解包(**)后传递给模型。

  • input_ids :模型能直接"消化"的数字 ID 序列,是最核心的输出。
  • attention_mask强烈建议使用 ,它告诉模型哪些位置是真实的词(1),哪些是填充的(0)。
  • token_type_ids :用于区分句子对(如问答、相似度任务),单句任务中全为 0
  • 可选信息 :如 offsets(token 到原字符的映射,NER 任务必备)、special_tokens_masklength 等,为更复杂的任务提供额外元数据。

💎 总结:日常开发如何选择?

场景需求 推荐方法 理由
模型推理或训练 my_tokenizer(texts, ...) 官方推荐 ,代码简洁高效,自动处理单条/批量,返回完整 BatchEncoding
快速查看 token ID,无需掩码 my_tokenizer.encode(text) 简单快捷,直接返回 ID 列表,适合临时调试。
理解底层机制或阅读旧版代码 my_tokenizer.encode_plus(...) 有助于深入理解 Tokenizer 内部原理,但新项目开发中不推荐直接使用。

因此,在日常使用中,熟练掌握 my_tokenizer(...)__call__ 方法就完全足够了,它体现了当前 Transformers 库的最佳实践。

2、代码

python 复制代码
import torch
from transformers import AutoTokenizer, AutoModel


# 1. 加载分词器
my_tokenizer = AutoTokenizer.from_pretrained(r'./model/bert-base-chinese')
# print(my_tokenizer)
# BertTokenizerFast(name_or_path='./model/bert-base-chinese', vocab_size=21128, model_max_length=1000000000000000019884624838656, is_fast=True, padding_side='right', truncation_side='right', special_tokens={'unk_token': '[UNK]', 'sep_token': '[SEP]', 'pad_token': '[PAD]', 'cls_token': '[CLS]', 'mask_token': '[MASK]'}, clean_up_tokenization_spaces=False, 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),
# }
# )

# 2. 加载模型
my_model = AutoModel.from_pretrained(r'./model/bert-base-chinese')

# 3. 准备样本
message = ['我们各自攀登', '然后山顶见']

# 4. 把句子转成张量
message_id = my_tokenizer(
    text=message,
    return_tensors='pt',
    padding=True,          # `True` (补齐到 batch 中最长)
    truncation=True,
    max_length=15
)
# print(message_id)
# 101-CLS(开头), 102-SEP(结尾)
# {
#     'input_ids': tensor([[101, 2769, 812,  1392, 5632, 3102, 4633, 102],
#                          [101, 4197, 1400, 2255, 7553, 6224, 102,   0 ]]),   # 自动处理了batch, my_tokenizer.encode_plus(..)只能处理单样本或文本对
#     'token_type_ids': tensor([[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, 0]])
# }

# 5. 开启模型推理模型 & 不计算梯度, 这两个可以同时开启, 也可以只开启 my_model.eval(), 推荐同时开启
my_model.eval()
with torch.no_grad():
    # 6. 得到模型输出结果
    output = my_model(**message_id)
print(output.last_hidden_state.shape)   # torch.Size([2, 8, 768])
print(output.pooler_output.shape)       # torch.Size([2, 768])
print(output)
# BaseModelOutputWithPoolingAndCrossAttentions(
#   last_hidden_state=tensor([
#                             [[ 0.5498,  0.7501,  0.0460,  ...,  0.6016, -0.1392, -0.2972],
#                              [ 0.1664, -0.0419, -0.0726,  ..., -0.5484, -0.5869, -0.2282],
#                              ...,
#                              [ 1.0726,  0.3592, -0.3137,  ...,  0.8689, -0.3638, -0.4375],
#                              [ 0.2997,  0.3714,  0.1938,  ...,  0.2208, -0.0583, -0.2907]],
#
#                             [[-0.8766,  0.8066, -0.2332,  ...,  0.4074,  0.5240, -0.3291],
#                              [-0.4696, -0.0328,  0.9041,  ..., -0.9132, -0.2767, -0.1639],
#                              ...,
#                              [-0.7512,  0.6317,  0.0726,  ...,  0.3382,  0.0803, -0.0023],
#                              [-0.4700,  0.5480, -0.0080,  ..., -0.1451, -0.0982, -0.5543]]
#                            ]),
#   pooler_output=tensor([
#                         [ 0.9995,  1.0000,  0.7246,  ..., -0.9914, -0.9976,  0.8039],
#                         [ 0.9994,  1.0000,  0.9479,  ..., -0.9971, -0.9897,  0.8022]
#                        ]),
#   hidden_states=None,
#   past_key_values=None,
#   attentions=None,
#   cross_attentions=None
# )

3、my_model(...) 返回值:last_hidden_statepooler_output

📝 BERT 输出详解

python 复制代码
# BaseModelOutputWithPoolingAndCrossAttentions(
#   last_hidden_state=tensor([
#                             [[ 0.5498,  0.7501,  0.0460,  ...,  0.6016, -0.1392, -0.2972],
#                              [ 0.1664, -0.0419, -0.0726,  ..., -0.5484, -0.5869, -0.2282],
#                              ...,
#                              [ 1.0726,  0.3592, -0.3137,  ...,  0.8689, -0.3638, -0.4375],
#                              [ 0.2997,  0.3714,  0.1938,  ...,  0.2208, -0.0583, -0.2907]],
#
#                             [[-0.8766,  0.8066, -0.2332,  ...,  0.4074,  0.5240, -0.3291],
#                              [-0.4696, -0.0328,  0.9041,  ..., -0.9132, -0.2767, -0.1639],
#                              ...,
#                              [-0.7512,  0.6317,  0.0726,  ...,  0.3382,  0.0803, -0.0023],
#                              [-0.4700,  0.5480, -0.0080,  ..., -0.1451, -0.0982, -0.5543]]
#                            ]),
#   pooler_output=tensor([
#                         [ 0.9995,  1.0000,  0.7246,  ..., -0.9914, -0.9976,  0.8039],
#                         [ 0.9994,  1.0000,  0.9479,  ..., -0.9971, -0.9897,  0.8022]
#                        ]),
#   hidden_states=None,
#   past_key_values=None,
#   attentions=None,
#   cross_attentions=None
# )

简单来说,output 对象就像是模型处理完文本后给你的"体检报告"。对于 AutoModel(基础模型)来说,这份报告主要包含两个核心部分:详细的逐词报告 (last_hidden_state) 和 整句的总结报告 (pooler_output)。

🧩 1. last_hidden_state:逐词的特征表示

这是模型最后一层 Transformer 输出的原始结果,它保留了文本的序列结构。

  • 含义

    它代表了输入序列中每一个 token (包括 [CLS]、汉字、标点、[SEP] 以及填充的 [PAD])在经过 BERT 模型深层处理后得到的上下文向量表示

    • 在这个向量中,每个词的含义都融合了整句话的语境(因为 BERT 是双向的)。
  • 形状解读:torch.Size([2, 8, 768])

    这三个数字分别代表 (Batch_Size, Sequence_Length, Hidden_Size)

    • 2 (Batch Size) :你输入了两句话(['我们各自攀登', '然后山顶见']),所以模型一次性处理了 2 个样本。
    • 8 (Sequence Length) :这是分词并填充后的长度。
      • 第一句 '我们各自攀登' (6个字) + [CLS] + [SEP] = 8个token。
      • 第二句 '然后山顶见' (5个字) + [CLS] + [SEP] = 7个token。
      • 因为开启了 padding=True,第二句被补齐到了 8 的长度(最后一个是 [PAD])。所以长度是 8。
    • 768 (Hidden Size) :这是 bert-base 模型的隐藏层维度。每一个 token 都被映射成了一个 768 维的向量,用来描述这个词在当前语境下的特征。

形象理解:这就像是一个 Excel 表格,有 2 行(2句话),每行有 8 列(8个词),每个格子里装着 768 个数字(这个词的详细特征)。

📌 2. pooler_output:整句的聚合表示

这是专门为了句子级别的任务(如文本分类、语义相似度)准备的输出。

  • 含义

    它是对整个句子语义的总结。具体来说,它的计算过程分为两步:

    1. 提取 :取出 last_hidden_state第一个 token(即 [CLS])的向量
    2. 变换 :将这个向量通过一个**全连接层(Linear)**和一个 Tanh 激活函数进行非线性变换。
    • 这个全连接层的权重是在 BERT 预训练阶段(Next Sentence Prediction 任务)学到的。
  • 形状解读:torch.Size([2, 768])

    这两个数字代表 (Batch_Size, Hidden_Size)

    • 2 (Batch Size):对应 2 个句子,每个句子得到一个总结向量。
    • 768 (Hidden Size):维度保持不变,依然是 768 维。

形象理解 :如果说 last_hidden_state 是整篇文章的逐字详解,那么 pooler_output 就是这篇文章经过"提炼"后的"中心思想"。

📊 总结对比表

特性 last_hidden_state pooler_output
核心含义 逐词表示:每个词在语境中的具体含义 整句表示 :整个句子的语义总结(基于 [CLS] + 全连接层)
形状 [2, 8, 768] (3维) [2, 768] (2维)
适用任务 序列标注:如命名实体识别、分词、机器翻译 句子分类:如情感分析、文本匹配
对应位置 对应输入序列的每一个位置 仅对应 [CLS] 位置(经过额外处理)

💡 进阶补充(避坑指南)

pooler_output 真的最好吗?

虽然 pooler_output 设计初衷是用于分类,但在很多现代微调任务中,大家发现直接使用 last_hidden_state 中的 [CLS] 向量(即 last_hidden_state[:, 0, :]),或者对整个序列进行平均池化(Mean Pooling) ,效果往往比 pooler_output 更好。因为 pooler_output 经过了 Tanh 激活,可能会丢失一些信息。

我们接着上面关于 pooler_output 的话题,来具体说说"平均池化(Mean Pooling)"在这里是什么意思。

🎯 一句话概括

平均池化 就是把 BERT 输出的所有 Token 的向量 ,在序列长度这个维度上求平均值,得到一个单一的、固定长度的向量来表示整个句子。


🔍 为什么要做平均池化?

先回顾一下 BERT 的输出 last_hidden_state

  • 形状是 (batch_size, seq_len, hidden_size)
  • 它是一个矩阵 ,每一行对应输入文本中一个 Token(比如 [CLS][SEP])的 768 维向量。

但我们做分类或句子相似度任务时,通常需要一个向量来表示整句话,而不是一堆向量。

常见的提取句子向量的方法有四种:

方法 操作 优缺点
1. 直接取 [CLS] last_hidden_state[:, 0, :] 简单,但 [CLS] 在预训练时主要用于 NSP 任务,未必能完美总结整句信息。
2. 用 pooler_output [CLS] 后过全连接层 + Tanh 专为分类设计,但 Tanh 可能导致信息压缩或饱和。
3. 平均池化 last_hidden_stateseq_len 维度求平均 让每个词都贡献意见,能捕捉更全面的语义,实践中常优于前两者。
4. 最大池化 last_hidden_stateseq_len 维度求最大值 只保留每一维最显著的特征,但容易忽略整体语境,较少单独使用。

📐 平均池化的具体操作(画图式讲解)

假设一句话有 4 个 Token(包含 [CLS][SEP]),每个 Token 是一个 768 维向量。

可视化矩阵(数字随便写的,仅示意):

复制代码
                   维度 1  维度 2  ... 维度 768
[CLS]   →         [ 0.1,   0.2,  ...,  0.5 ]
我      →         [ 0.8,   0.1,  ...,  0.3 ]
爱      →         [ 0.6,   0.9,  ...,  0.2 ]
你      →         [ 0.3,   0.4,  ...,  0.7 ]
[SEP]   →         [ 0.0,   0.0,  ...,  0.1 ]

平均池化就是:

  1. 沿着"列"的方向(即跨过所有 Token),对每一列的所有数值求平均。
  2. 结果是一个新的 768 维向量,这个向量就是整句话的表示。

代码实现(PyTorch):

python 复制代码
# last_hidden_state.shape = (batch_size, seq_len, hidden_size)
sentence_embedding = last_hidden_state.mean(dim=1)   # 在序列长度维度 dim=1 上求平均
# sentence_embedding.shape = (batch_size, hidden_size)

就这么简单!.mean(dim=1) 就是平均池化。

⚠️ 实际编码避坑指南:别忘了排除 [PAD]

上面直接 .mean(dim=1) 有一个隐患:如果句子长短不一,我们通常会用 [PAD] 把短句子补长。[PAD] 的向量是全 0 ,如果不加处理直接平均,会稀释掉真实语义(句子越长,有效信息占比越低)。

正确的平均池化写法 (利用 attention_mask):

python 复制代码
# attention_mask.shape = (batch_size, seq_len),有效位置为 1,填充位置为 0
mask = attention_mask.unsqueeze(-1).float()   # 扩展维度以广播
sum_embeddings = (last_hidden_state * mask).sum(dim=1)
sum_mask = mask.sum(dim=1).clamp(min=1e-9)    # 防止除以 0
sentence_embedding = sum_embeddings / sum_mask

这样只有真正的词参与了平均,[PAD] 被完美忽略。


🌰 举个例子感受一下

文本:"今天天气真好"

分词后:[CLS] 今 天 天 气 真 好 [SEP]

  • [CLS] 向量 :只听了第一个词 [CLS] 的"总结发言"。
  • 平均池化 :让 [CLS][SEP] 每个词都发言,然后取大家的平均意见 。这样 天气 等关键信息都直接参与了最终向量的构建,不会被 [CLS] 一个词"代表"了。

✅ 总结

平均池化就是把 BERT 输出的所有 Token 向量加起来取平均 ,得到一个融合了整句信息的句子向量。因为不引入额外参数、不经过 Tanh 压缩,在很多微调任务中表现比 pooler_output 更稳定、效果更好。实际代码中务必结合 attention_mask,只对真实 Token 求平均。

预训练模型的 pooler_output 是经过全连接层(Linear)处理得到的,这是一种普遍的标准实现。不过,想知道特定模型的确切情况,最可靠的方法是"查看配置"或"检查模型"。

💎 总结

  • 默认情况 :对于大多数标准的BERT模型pooler_output 就是 [CLS] 向量经过一个 全连接层 + Tanh激活函数 后的输出。
  • 如何确认
    • 最可靠 :检查 config.json 文件中的 use_poolerpooler_type 参数。
    • 最直接 :打印模型对象,查看 pooler 属性是否存在及其内部结构。
    • 最规范:查阅模型官方文档或Hugging Face Hub上的模型卡片。

通过这三种方法,你就能准确判断任意预训练模型 pooler_output 的具体实现了。

4、my_model(...) 返回值:hidden_statesattentions

📝 深入 BERT 内部机制

这部分确实是 BERT 的 "黑盒" 内部机制。为了让你彻底搞懂 hidden_statesattentions,我们需要把 BERT 想象成一个由 12 层 Transformer 堆叠而成的"千层饼"

默认情况下,模型只给你看最后的结果(last_hidden_state),而把中间的过程都扔掉了。如果你开启了这两个选项,就像是给模型做了一次"全身体检",能看到每一层的内部变化。

以下是详细的解释:

🧠 1. hidden_states:模型的"思考过程"

BERT(Base版)有 12 层 Transformer 编码器。

  • 默认情况 :模型只返回最后一层 (第 12 层)的输出,也就是我们常说的 last_hidden_state
  • 开启后 :你会得到一个包含 13 个元素 的元组(Tuple)。

为什么是 13 个?

  1. 第 0 个元素:Embedding 层的输出(即词向量刚查表出来,还没进 Transformer 之前的状态)。
  2. 第 1~12 个元素:分别是第 1 层到第 12 层 Transformer 的输出。

形状是什么?

每一个元素的形状都是 [Batch_Size, Sequence_Length, 768]

  • 比如你输入 2 句话,每句 8 个词,那么 hidden_states 就是一个包含 13 个 [2, 8, 768] 张量的元组。

有什么用?

  • 研究模型学到了什么:浅层通常关注语法(如词性),深层关注语义(如指代关系)。
  • 提取更强特征 :有些任务(如语义相似度)会将最后 4 层的 hidden_states 加权求和,效果比单用最后一层更好。

👁️ 2. attentions:模型的"注意力焦点"

这是 Transformer 最核心的机制------自注意力(Self-Attention)

  • 默认情况:不返回。
  • 开启后 :你会得到一个包含 12 个元素 的元组(对应 12 层)。

它代表什么意思?

它展示了在每一层中,每一个词"关注"了句子中的哪些其他词

比如句子是"我把苹果吃了",当模型处理"吃"这个词时,注意力机制会告诉我们要关注"我"(主语)和"苹果"(宾语)。

形状是什么?

每一个元素的形状是 [Batch_Size, Num_Heads, Sequence_Length, Sequence_Length]

  • Batch_Size:2。
  • Num_Heads:12(BERT Base 有 12 个注意力头,意味着每一层有 12 种不同的关注视角)。
  • Sequence_Length, Sequence_Length :比如 8x8。这是一个矩阵,表示第 i 个词对第 j 个词的注意力权重(0到1之间的概率值)

有什么用?

  • 可解释性分析:你可以把注意力矩阵画成热力图。比如你想看模型是怎么做"指代消解"的(比如把"它"和"苹果"联系起来),看注意力图一目了然。

🛠️ 3. 如何开启?(代码实操)

开启这两个功能通常有三种方法,方法二在实际写脚本时最常用。

方法一:在加载模型时配置(最正式)

通过 BertConfig 在初始化时就告诉模型:"我要保留中间状态"。

python 复制代码
from transformers import BertModel, BertConfig

config = BertConfig.from_pretrained('./model/bert-base-chinese')
config.output_hidden_states = True
config.output_attentions = True

model = BertModel.from_pretrained('./model/bert-base-chinese', config=config)

方法二:直接传参(最常用)

from_pretrained 加载模型时,直接覆盖默认配置。

python 复制代码
# 推荐这种写法,简单直接
model = BertModel.from_pretrained(
    './model/bert-base-chinese', 
    output_hidden_states=True, 
    output_attentions=True
)


# 下面这个也是可以的
my_model = AutoModel.from_pretrained(
    r'./model/bert-base-chinese', 
    output_hidden_states=True, 
    output_attentions=True
)

方法三:在推理时指定(仅当前有效)

python 复制代码
# 这种方式适合只想偶尔调试一次的情况
outputs = model(**message_id, output_hidden_states=True, output_attentions=True)

⚠️ 4. 注意事项

  • 内存爆炸 :开启这两个选项会显著增加内存占用。因为原本模型只传一个张量,现在要传 13 个隐藏状态和 12 个注意力矩阵。如果是大批量训练(Batch Size 大),很容易显存溢出(OOM)。
  • 速度变慢:返回这些数据需要时间,推理速度会略微下降。

总结:

  • last_hidden_state:是最终答案。
  • hidden_states:是草稿纸上的演算过程(每一层的变化)。
  • attentions:是解题时的视线轨迹(关注点在哪里)。

5、my_model(...) 返回值总结:两种

📝 Hugging Face 模型的两种返回类型与 NER 选型

这两个返回值之所以长得完全不一样,根本原因在于你加载的**模型类型(Model Class)**不同。简单来说,就是你向模型"要"的东西不一样,它给你的"包裹"自然也不同。

在 Hugging Face 的 Transformers 库中,模型主要分为两大类,正好对应你看到的这两种输出:

  1. 基础模型(Base Model)
  • 代表模型BertModelRobertaModel,以及万能类 AutoModel (名字里没有 For... 的)。
  • 对应你看到的输出BaseModelOutputWithPoolingAndCrossAttentions

这类模型只负责把输入的文本转换成高质量的向量(特征提取器),它不包含针对具体任务(如分类、NER)的"任务头(Task Head)"。

  • last_hidden_state :这是最核心的输出。形状通常是 [batch_size, seq_len, hidden_size](比如 BERT 就是 768 维)。它包含了句子中每一个 Token 经过 Transformer 编码后的上下文语义表示。做 NER(命名实体识别)或序列标注时,我们就是拿这个输出接一个 Linear 层和 CRF。
  • pooler_output :形状通常是 [batch_size, hidden_size]。它是把 last_hidden_state 中第一个 Token(也就是 [CLS])的向量拿出来,再经过一个额外的全连接层(Linear)和 Tanh 激活函数处理后的结果。它代表的是整个句子的语义特征,通常用于文本分类、句子相似度匹配等句子级别的任务。
  1. 下游任务模型(Task-Specific Models)
  • 代表模型BertForSequenceClassificationAutoModelForSequenceClassification 等(名字里带 For... 的)。
  • 对应你看到的输出SequenceClassifierOutput

这类模型在基础模型(Base Model)的上面,已经帮你搭好了一个专门用于特定任务的"头(Head)"。比如 ForSequenceClassification 就是在 BERT 后面接了一个分类器。

  • logits :这是最核心的输出。它是经过模型顶部的分类层计算后,Softmax 激活函数之前 的原始预测分数。比如你的输出是 tensor([[-1.34, ..., 3.65], ...]),这代表当前 batch 里的两句话,在 5 个分类标签上的预测得分(得分最高的那个就是模型预测的类别)。
  • loss :如果你在调用模型时传入了 labels(真实标签),它会自动帮你计算交叉熵损失并返回;如果没传 labels,这里就是 None

💡 核心区别与 NER 任务的选择

为了让你更直观地理解,我为你整理了一个对比表格:

特性 基础模型 (BertModel / AutoModel) 任务模型 (BertForSequenceClassification)
返回对象 BaseModelOutputWithPooling... SequenceClassifierOutput
核心输出 last_hidden_state (每个字的特征) logits (最终的分类预测分)
包含任务头 ❌ 无 (只有 Transformer 底座) ✅ 有 (底座 + 顶部的分类层)
适用场景 需要自定义下游结构 (如 NER + CRF) 标准的文本分类、情感分析

如果你要做的是 NER(命名实体识别),并且要自己接一个 CRF 层。这意味着你需要拿到每一个字(Token)的深层语义特征,然后自己传给 CRF 去计算发射分数。

因此,你应该使用的是 第一种返回值的模型 ,也就是基础模型(如 BertModelAutoModel)。

  • 你需要提取它的 last_hidden_state(形状 [batch_size, seq_len, 768])。
  • 然后通过一个你自己定义的 nn.Linear(768, num_tags) 线性层,把它转换成 CRF 需要的发射分数(Emissions)。
  • 避坑指南 :在做 NER 时,千万不要用 pooler_output !因为它只是 [CLS] 这个特殊标记的向量,丢失了句子中其他所有字的信息。

如果你用了第二种模型(ForSequenceClassification),它直接给你返回了分类的 logits,不仅丢失了每个 Token 的细粒度信息,而且它的输出维度是分类标签数,完全无法满足 NER 任务的需求。

相关推荐
宝贝儿好7 小时前
【LLM】第三章:BERT讲解+情感分析案例
人工智能·深度学习·神经网络·算法·自然语言处理·bert
星马梦缘3 天前
机器学习与模式识别 第十七章 Transformers & LLMs 考点压缩
人工智能·机器学习·transformer·tokenizer·bpe
chen_zn9522 天前
Hugging Face 无法访问的解决方案
huggingface
旅僧23 天前
Bert理论讲解
人工智能·深度学习·bert
王小王-12325 天前
基于深度学习的景区口碑情感分析可视化系统
人工智能·深度学习·bert·情感分析·关键词提取·主题分析·景区评论分析
me8321 个月前
【AI面试】小白理解大模型:仅编码器(BERT类)、仅解码器(GPT类)和完整的编码器-解码器架构各有什么优缺点?
人工智能·gpt·ai·bert
kishu_iOS&AI1 个月前
LLM —— 基础知识(Bert&GPT&T5)浅析
人工智能·gpt·bert
星川皆无恙1 个月前
基于BERT+LSTM+CRF与知识图谱的医疗智能问答系统实战:Neo4j图数据库+实体识别+意图分析完整项目
数据库·人工智能·深度学习·bert·lstm·知识图谱·neo4j
毕竟是shy哥1 个月前
基于提示引导适配器的实体级对齐遥感图文检索
人工智能·学习·bert·transformer