1、模型输出结果解析

import torch
from transformers import AutoTokenizer, AutoModelForTokenClassification


# 1. 加载分词器
my_tokenizer = AutoTokenizer.from_pretrained(r'./model/roberta-base-finetuned-cluener2020-chinese')

# 2. 加载模型
my_model = AutoModelForTokenClassification.from_pretrained(r'./model/roberta-base-finetuned-cluener2020-chinese')

# 3. 准备样本
text = '停留在甲板等日出,关于美人鱼的纪录,从此后被塞入了瓶盖'

# 4. 把样本转换成张量
# inputs.input_ids 在前后加入了 [CLS]-101、[SEP]-102, 而不仅仅是原始文本
inputs = my_tokenizer(
    text=text,
    padding=True,
    return_tensors='pt'
)
print(inputs)
# {
#     'input_ids': tensor([[101,   977, 4522, 1762, 4508, 3352, 5023, 3189, 1139, 8024, 1068,  754,
#                           5401,  782, 7824, 4638, 5279, 2497, 8024,  794, 3634, 1400, 6158, 1853,
#                           1057,  749, 4486, 4667, 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, 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, 1, 1,
#                                1, 1, 1, 1, 1]])
# }

# 为了后面用 '原始token 类型' 放在一起展示, 所以这里把 inputs.input_ids 解码成对应的 token
# my_tokenizer.convert_ids_to_tokens(...) 在《自动模型方式完成多种NLP任务》里的《完型填空任务》中的《`my_tokenizer.decode(...)` - API》有解释
# my_tokenizer.convert_ids_to_tokens(...) 里面只能是 单个 ID 或 ID 列表
text_token = my_tokenizer.convert_ids_to_tokens(inputs.input_ids[0])
print(text_token)
# ['[CLS]', '停', '留', '在', '甲', '板', '等', '日', '出', ',', '关', '于', '美', '人',
# '鱼', '的', '纪', '录', ',', '从', '此', '后', '被', '塞', '入', '了', '瓶', '盖', '[SEP]']

# 5. 开启模型推理模型 & 不计算梯度, 这两个可以同时开启, 也可以只开启 my_model.eval(), 推荐同时开启
my_model.eval()
with torch.no_grad():
    outputs = my_model(**inputs)
print(outputs.logits.shape)    # torch.Size([1, 29, 32])
print(outputs)
# TokenClassifierOutput(
#     loss=None,
#     logits=tensor([[[9.3945e+00,   4.1166e-01,  1.2500e+00, -1.5394e-01, -2.9729e-01,
#                      -1.2726e-01, -8.9371e-03,  2.3292e-01, -5.5381e-01, -4.2870e-01,
#                      -2.5781e-01, -8.8203e-01, -1.1683e-01, -8.4785e-02,  1.1912e-01,
#                      -6.1745e-01,  3.8417e-01,  5.7206e-02, -5.7706e-01, -8.8238e-02,
#                       9.3051e-01, -1.4076e+00, -1.9109e+00, -2.0576e+00, -2.4949e+00,
#                      -1.9235e+00, -1.6251e+00, -1.8709e+00, -1.9851e+00, -2.1019e+00,
#                      -2.5674e+00, -2.2312e+00],
#                     ...
#                      [1.1723e+01,  4.9509e-02, -1.2653e-01, -5.9781e-01, -4.2287e-01,
#                       1.1590e-01, -2.9862e-01, -5.6368e-02, -5.3732e-01, -1.6400e-01,
#                      -8.2875e-01, -1.1824e+00, -3.0672e-01, -3.1450e-01,  1.7780e-02,
#                       2.1668e-02, -2.7108e-01, -6.0661e-01, -7.7470e-01, -2.5914e-01,
#                      -1.9377e-02, -1.4555e+00, -1.8572e+00, -2.2463e+00, -2.5508e+00,
#                      -2.1479e+00, -1.7642e+00, -2.3704e+00, -2.0913e+00, -2.1438e+00,
#                      -2.4708e+00, -2.2453e+00]]]),
#     hidden_states=None,
#     attentions=None
# )

一、输出结果 outputs 整体结构详解

当你打印 outputs 时,看到的是一个 TokenClassifierOutput 对象,它包含四个主要字段:

TokenClassifierOutput(
    loss=None,
    logits=tensor([[[ ... ]]]),
    hidden_states=None,
    attentions=None
)
字段名 当前值 含义与解释
loss None 分类损失。因为这里只进行了推理(预测),没有传入 labels 参数与真实答案计算对比,所以损失值为空。如果在训练模式下传入标签,这里会返回一个标量张量。
logits 一个三维张量 核心输出:原始预测分数。这是模型最后一层分类头(线性层)的输出,尚未经过 Softmax 归一化。每个 Token 在每个可能的类别上都有一个原始得分。
hidden_states None 隐藏层状态。只有当模型初始化或调用时设置 output_hidden_states=True,这里才会返回编码器各层的输出张量,用于分析模型内部特征。
attentions None 注意力权重。只有当 output_attentions=True 时才会返回,用于可视化注意力矩阵。

二、重点解析:outputs.logits.shape 为什么是 torch.Size([1, 29, 32])

这是问题的核心。要理解这个形状,我们需要对应回输入数据的结构和模型的任务定义。

  1. 维度拆解
  • 第 1 维 1:批次大小

    代码中只输入了一句话(一个样本),且未使用批处理,所以批次大小为 1

  • 第 2 维 29:序列长度

    观察代码打印结果:

    text_token = ['[CLS]', '停', '留', '在', '甲', '板', '等', '日', '出', ',', 
                  '关', '于', '美', '人', '鱼', '的', '纪', '录', ',', '从', 
                  '此', '后', '被', '塞', '入', '了', '瓶', '盖', '[SEP]']
    

    列表长度恰好是 29。模型对输入文本中的每一个 Token(包括特殊的 [CLS][SEP])都会生成一个对应的预测向量。

  • 第 3 维 32:标签类别数

    这是该预训练模型在 CLUENER2020 数据集上微调时所使用的实体标签总数。CLUENER 包含 10 种实体类型(如人名、地址、游戏、书籍等),在 NER 任务中通常采用 BIO 标注法

    • B-XX:某实体的开始 Token
    • I-XX:某实体的内部 Token
    • O:非实体 Token

    理论上的最小标签数量为 10 × 2 (B/I) + 1 (O) = 21。但实际训练时,标签集可能还包含一些额外类别(例如为 [PAD] 准备的忽略标签、或数据预处理时引入的其他辅助标签),因此你加载的 roberta-base-finetuned-cluener2020-chinese 模型配置中 num_labels = 32。这意味着模型最后的分类头是一个将 768 维隐藏向量映射到 32 个类别的线性层。

  1. 形状含义总结图
torch.Size([1, 29, 32])
   	        │   │   │
   		    │   │   └── 每个 Token 对应 32 个标签的“原始得分”
   		    │   └────── 输入文本包含 29 个 Token(含首尾特殊符号)
             └────────────── 当前批次只有 1 句话

三、outputs.logits 里面的元素是什么意思?

让我们聚焦在打印出来的那个庞大的浮点数矩阵上。以第一个 Token [CLS] 对应的行(长度为 32 的向量)为例:

[9.3945e+00, 4.1166e-01, 1.2500e+00, -1.5394e-01, ..., -2.5674e+00, -2.2312e+00]

核心定义:

logits[i][j] 表示i 个 Token 被预测为第 j 种标签类别的原始分数(其中 i 从 0 开始,与 token 序列顺序一致)。

  1. 数值特征解释
  • 数值范围:有正有负(如 9.39-2.56)。
  • 物理意义:这些数值不是概率,它们没有经过 Softmax 归一化,因此所有类别的分数之和不为 1,也不能直接解释为置信度
  • 决策逻辑数值越大,表示模型越倾向于认为该 Token 属于该类别
  1. 如何转化为最终预测结果?

后续处理流程通常是这样的(虽然代码中没有写,但这是 NER 推理的必备后处理):

  1. 取 Argmax:对每个 Token 的 32 个分数取最大值所在的索引

    predictions = torch.argmax(outputs.logits, dim=-1)  # Shape: [1, 29]
    
  2. 映射回标签名:通过 my_model.config.id2label 将索引 0~31 转换为具体的字符串标签(如 'B-book', 'I-address', 'O')。

    predicted_labels = [my_model.config.id2label[idx.item()] for idx in predictions[0]]
    
  3. 忽略特殊符号:通常我们会忽略 [CLS][SEP] 的预测结果,只关注中间真实字符的标签。实际使用时,会切片去掉首尾的预测。


四、补充:为什么输出中包含 [CLS][SEP] 的分数?

虽然在实际的实体识别中 [CLS][SEP] 不包含实体,但 AutoModelForTokenClassification 在计算 Loss 和 Logits 时默认对所有 Token 输出

  • 训练阶段计算 Loss 时,HuggingFace 内部会将这两个位置对应的标签设为 -100(忽略索引),从而不产生梯度贡献。
  • 推理阶段,为了保持张量形状的规整以及计算图的统一,模型依然计算了它们的分数,但我们在后续解析结果时应当手动剔除这两个位置的预测。

五、一点实用提示

如果想直接得到每个 Token 对应的最高概率标签,可以使用 pipeline 简化流程:

from transformers import pipeline

ner_pipeline = pipeline("ner", model=my_model, tokenizer=my_tokenizer)
results = ner_pipeline(text)
print(results)

这会自动完成 token 对齐、分数 softmax 以及特殊符号过滤,返回结构化的实体列表。


2、AutoConfig.from_pretrained(...) 读取配置

📘 深入理解 AutoConfig.from_pretrained(…)

这是一个非常棒的问题!AutoConfig.from_pretrained() 是 Hugging Face transformers 库中一个“轻量级”但功能强大的工具。

简单来说,它的作用就是只读取模型的“说明书”,而不下载或加载沉重的“大脑”(权重)。这使得我们可以用极少的内存(KB级别)和极快的速度查看模型的所有架构细节,而无需占用显存。

下面我为你详细拆解它的内部运作机制:

🔍 它会去找路径里的什么文件?

当你运行这行代码时:

my_config = AutoConfig.from_pretrained('./model/roberta-base-finetuned-cluener2020-chinese')

程序会去你指定的文件夹里寻找 config.json 这个文件。

  • 如果找不到: 会报错 (OSError: Can't load config for...)。
  • 如果是从 Hub 下载: 它会先检查本地缓存有没有这个文件,没有的话就从 Hugging Face 官网下载这个 config.json

📝 配置文件格式与核心字段解析

这个 config.json 本质上就是一个标准的 JSON 文本文件。它不仅包含模型的骨架信息,还包含了任务特定的元数据。

一个典型的 NER 模型 config.json 内容如下:

{
  "_name_or_path": "hfl/chinese-roberta-wwm-ext",
  "architectures": [
    "RobertaForTokenClassification"
  ],
  "attention_probs_dropout_prob": 0.1,
  "hidden_act": "gelu",
  "hidden_size": 768,
  "id2label": {
    "0": "O",
    "1": "B-book",
    "2": "I-book"
  },
  "label2id": {
    "O": 0,
    "B-book": 1,
    "I-book": 2
  },
  "initializer_range": 0.02,
  "intermediate_size": 3072,
  "max_position_embeddings": 512,
  "model_type": "roberta",       # 模型类型
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  "pad_token_id": 0,
  "type_vocab_size": 2,
  "vocab_size": 21128
}

关键字段深度解析:

  1. model_type (核心灵魂):
    • 例如 "roberta"。这是 AutoConfig 能够“自动”的关键。库内部有一张映射表,读到这个字段后,它就知道该实例化 RobertaConfig 类,而不是 BertConfig
  2. architectures:
    • 告诉库这个模型的具体用途类名(如 RobertaForTokenClassification),例如,读取到 "RobertaForTokenClassification",它就知道应该实例化 RobertaConfig 类来加载这些配置
  3. id2label / label2id:
    • 这就是 “为什么 config 里有标签”。这里明确存储了数字 ID 和类别名称的对应关系。
    • 这是微调任务的“字典”。它存储了数字 ID 和人类可读标签(如 "B-book")的对应关系。推理时我们全靠它把预测结果翻译成人话。
  4. 结构参数:
    • hidden_size (768), num_hidden_layers (12层) 等。这些定义了模型的“体型”。
    • hidden_size: 隐藏层维度(768)。
    • num_hidden_layers: Transformer 的层数(12层)。
    • num_attention_heads: 注意力头的数量(12个)。
    • vocab_size: 词表大小。

💻 怎么使用 my_config?

my_config 是一个 Python 对象(例如 RobertaConfig 的实例),你可以像访问属性一样访问它。

1. 查看模型参数(最常用)

print(my_config.hidden_size)       # 输出: 768
print(my_config.num_hidden_layers) # 输出: 12
print(my_config.model_type)        # 输出: roberta

2. 获取标签映射(推理必备)

# 获取 id -> 标签 的字典
labels = my_config.id2label
print(labels[1])  # 输出: B-book

# 获取 标签 -> id 的字典
ids = my_config.label2id
print(ids["O"])   # 输出: 0

3. 从零构建模型(不加载预训练权重)
如果你想从头开始训练一个模型(随机初始化),或者修改架构后创建模型,可以用 from_config

from transformers import AutoModel

# 根据 config 的定义,创建一个全新的、未训练的模型(权重是随机的)
random_model = AutoModel.from_config(my_config) 

这在科研实验或自定义架构设计中非常有用。

❓ my_config 是不是只能使用配置文件里的配置?

绝对不是! my_config 是一个普通的 Python 对象,它是完全可修改的。

1. 动态修改与保存

你可以在代码中随意更改它的属性,甚至可以保存下来供以后使用:

# 1. 加载默认配置
my_config = AutoConfig.from_pretrained('./model/...')

# 2. 修改配置
my_config.hidden_dropout_prob = 0.0  # 关闭 Dropout
my_config.num_labels = 10            # 修改分类数量

# 3. 保存修改后的配置到新目录
my_config.save_pretrained("./my_custom_config/")

⚠️ 重要注意事项:

  • 运行时参数 vs 结构参数: 修改 dropout 这种参数,可以直接作用于通过 from_pretrained 加载的预训练模型。但如果修改 hidden_sizenum_hidden_layers结构参数,则无法兼容已下载的预训练权重(因为维度对不上了)。此时必须使用 from_config 重新初始化一个随机权重的模型。

2. 添加自定义属性

你甚至可以往里面塞自己的东西(虽然不建议滥用):

my_config.my_custom_variable = "Hello World"
print(my_config.my_custom_variable) 

📌 总结

特性 描述
核心文件 仅读取 config.json
内存占用 极低(KB 级别),不加载几 GB 的权重文件。
自动化原理 依赖 JSON 中的 model_type 字段自动匹配类。
主要用途 1. 检查模型架构;
2. 获取 id2label 等元数据;
3. 从零构建或修改模型结构。
灵活性 加载后可读写修改,支持 save_pretrained 保存。

所以,当你只需要知道“这个模型长什么样”或者“它的标签是什么”时,使用 AutoConfig.from_pretrained() 是最快、最经济的方法。


3、通过 my_model.config 使用配置

📘 深入理解 my_model.config:模型内的配置对象

你已经理解了 AutoConfig 是独立的“说明书”,现在我们来探讨 当模型本身已经加载时,如何通过 my_model 来使用配置,以及它与单独加载 my_config 的本质区别。


一、为什么 my_model 里会有 config 属性?它存的不是模型架构吗?

一句话回答:
my_model 既包含模型架构的计算图,也内嵌了一个 config 属性,用来保存构建该模型时所用的全部配置信息。

逻辑链条如下:

  1. from_pretrained 加载过程
    当你调用 AutoModelForTokenClassification.from_pretrained() 时,库内部实际上做了三件事:

    • 第一步:用 AutoConfig.from_pretrained() 读取 config.json,获得配置对象 config
    • 第二步:根据 config 中的架构参数,构建模型类(如 RobertaForTokenClassification),并将下载的预训练权重加载进去。
    • 第三步:将第一步得到的 config 对象挂载到模型的 config 属性上。
  2. 因此 my_model.config 与单独加载的 my_config 本质上是同一份数据
    模型对象只是“随身携带”了这份配置,方便你在推理、微调时随时查阅。

类比理解:

  • config.json 是一张建筑蓝图
  • my_model 是一栋已经建好的大楼,大楼内部墙上挂着一幅裱好的蓝图副本my_model.config)。
  • 你既可以从档案室单独调取蓝图(AutoConfig),也可以直接抬头看墙上的副本(model.config)——内容完全一致。

二、如何通过 my_model 来使用 config

🤝 继承体系的设计 (PreTrainedModel)

transformers 的源码中,所有的模型类(如 RobertaForTokenClassification)都继承自一个基类叫 PreTrainedModel
这个基类的 __init__ 方法强制要求传入一个 config 对象。

# 伪代码示意
class PreTrainedModel(nn.Module):
    def __init__(self, config, *inputs, **kwargs):
        super().__init__()
        self.config = config  # <--- 看这里!它直接把 config 存为了自己的属性
        # ...后续根据 config 搭建网络...

所以,任何继承自它的模型,天生就带有一个 .config 属性。这是为了方便模型在内部调用参数(比如定义层的大小时直接读 self.config.hidden_size)。

因为 config 是模型对象的普通属性,使用方式与单独加载的配置对象完全相同

config.json 配置文件示例如下:

{
  "architectures": [
    "BertForMaskedLM"
  ],
  "attention_probs_dropout_prob": 0.1,
  "directionality": "bidi",
  "hidden_act": "gelu",
  "hidden_dropout_prob": 0.1,
  "hidden_size": 768,
  "initializer_range": 0.02,
  "intermediate_size": 3072,
  "layer_norm_eps": 1e-12,
  "max_position_embeddings": 512,
  "model_type": "bert",
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  "pad_token_id": 0,
  "pooler_fc_size": 768,
  "pooler_num_attention_heads": 12,
  "pooler_num_fc_layers": 3,
  "pooler_size_per_head": 128,
  "pooler_type": "first_token_transform",
  "type_vocab_size": 2,
  "vocab_size": 21128
}

只要配置文件里有的属性,都可以通过 my_model.config.xxx 取到。

Hugging Face 的 BertConfig 类在加载 JSON 时,会自动将文件中所有的键值对注册为对象的属性

  • 标准 BERT 参数(如 hidden_sizenum_hidden_layers)→ 可以直接 . 出来。
  • 非标准/自定义参数(如你这里的 pooler_fc_sizepooler_type)→ 也会作为额外属性挂在 config 上,一样可以用 config.pooler_fc_size 访问。

所以只要 xxx 是 JSON 里真实存在的键,my_model.config.xxx 就不会报错。

from transformers import BertModel


# 加载模型
my_model = BertModel.from_pretrained(r'./model/bert-base-chinese')
print(my_model.config)
# BertConfig {
#   "_attn_implementation_autoset": true,
#   "architectures": [
#     "BertForMaskedLM"
#   ],
#   "attention_probs_dropout_prob": 0.1,
#   "classifier_dropout": null,
#   "directionality": "bidi",
#   "hidden_act": "gelu",
#   "hidden_dropout_prob": 0.1,
#   "hidden_size": 768,
#   "initializer_range": 0.02,
#   "intermediate_size": 3072,
#   "layer_norm_eps": 1e-12,
#   "max_position_embeddings": 512,
#   "model_type": "bert",
#   "num_attention_heads": 12,
#   "num_hidden_layers": 12,
#   "pad_token_id": 0,
#   "pooler_fc_size": 768,
#   "pooler_num_attention_heads": 12,
#   "pooler_num_fc_layers": 3,
#   "pooler_size_per_head": 128,
#   "pooler_type": "first_token_transform",
#   "position_embedding_type": "absolute",
#   "torch_dtype": "float32",
#   "transformers_version": "4.51.3",
#   "type_vocab_size": 2,
#   "use_cache": true,
#   "vocab_size": 21128
# }

下面这些操作的输出结果只是示例效果,不是和上面这个配置 一 一 对应:

  1. 查看结构参数
print(my_model.config.hidden_size)        # 768
print(my_model.config.num_hidden_layers)  # 12
print(my_model.config.model_type)         # 'roberta'
  1. 获取标签映射(NER 推理必备)
# 直接通过模型访问 id2label
label_name = my_model.config.id2label[0]    # 大概率是 'O'
print(label_name)

# 也可以获取整个字典
id2label = my_model.config.id2label
  1. 动态修改配置(需谨慎)
# 修改 dropout(不影响已加载的权重,仅改变模型 forward 时的行为)
my_model.config.hidden_dropout_prob = 0.0

# 如果修改了标签数量,需要同步调整分类头权重维度(一般不推荐直接改,除非重新初始化)
# my_model.config.num_labels = 10   # 仅当你也重新初始化分类头时才有效

注意:修改 my_model.config 中的结构参数(如 hidden_sizenum_attention_heads)不会自动改变模型层本身的维度,此时模型计算图与配置会出现不一致,极易报错。除非你非常清楚自己在做什么,否则仅建议修改 运行时参数(如 dropout、标签映射等)。
例外num_labels 虽然也属于结构参数,但如果你在修改它之后,显式地用新的分类头替换原有 classifier 层,再重新训练,这种操作在迁移学习中是允许的。

  1. 保存模型时会自动带上最新的 config

当你调用 my_model.save_pretrained("./new_model") 时,库会自动将 my_model.config 的内容写入 config.json 文件,并一同保存模型权重。因此,你在此前对 config 的任何修改都会随之固化下来。


三、通过 my_model.config 与单独 my_config 使用配置的区别

对比维度 my_model.config AutoConfig.from_pretrained(...)
获取方式 模型加载后直接作为属性访问 需显式调用 from_pretrained 单独加载
内存占用 不额外占用,直接复用已加载的配置对象 会新建一个配置对象,额外占用极小内存(KB级)
代码简洁性 无需额外导入 AutoConfig,一句搞定 需要单独一行加载,略显冗余
配置与模型一致性 绝对一致(因为它就是构建模型用的那份配置) 理论上一致,但如果你手动修改过模型内的 config 而未保存,则单独加载的 my_config 可能过时
修改后对模型的影响 直接生效(运行时参数)或需手动调整模型层(结构参数) 修改后不影响任何已存在的模型实例,仅代表一份独立的配置蓝图
适用场景 已加载模型时,查询或修改配置的首选方式 未加载模型时,只想快速查看配置信息(省显存、省时间)

四、推荐使用哪种方式?

✅ 推荐原则:模型在手,直接用 model.config

  • 理由一:省事。 模型已经加载到内存了,何必再开一个副本?
  • 理由二:保证一致性。 推理过程中你很可能需要根据 num_labelsid2label 解析结果,直接取 model.config 确保使用的是当前模型“真正认可”的配置。
  • 理由三:符合库设计惯例。 Hugging Face 官方示例和文档中,凡是已经加载模型的地方,几乎都使用 model.config 来获取配置。

✅ 何时使用 AutoConfig.from_pretrained

  • 仅当你需要“只看配置,不加载模型”时。例如:
    • 你有一堆本地模型文件夹,想快速遍历查看它们的标签数量、隐藏层大小,但不想把几个G的权重都塞进显存。
    • 你在写一个脚本,需要根据配置自动生成模型结构代码,但不需要实际推理。

五、完整示例:两种方式的实际应用

场景:加载模型后,获取标签映射并解析预测结果

from transformers import AutoTokenizer, AutoModelForTokenClassification
import torch

model_path = './model/roberta-base-finetuned-cluener2020-chinese'
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForTokenClassification.from_pretrained(model_path)

# 直接使用 model.config 获取标签映射
id2label = model.config.id2label

text = "我在北京故宫看到了红楼梦"
inputs = tokenizer(text, return_tensors="pt")
with torch.no_grad():
    outputs = model(**inputs)

predictions = torch.argmax(outputs.logits, dim=-1)[0]

# 注意:inputs.input_ids 包含 [CLS] 和 [SEP],这里展示全部 token 的预测(含特殊符号)
for token_id, pred_id in zip(inputs.input_ids[0], predictions):
    token = tokenizer.decode(token_id)
    label = id2label[pred_id.item()]
    print(f"{token:6} -> {label}")

# 实际使用时通常会剔除首尾特殊符号,只关注真实字符:
# for token_id, pred_id in zip(inputs.input_ids[0][1:-1], predictions[1:-1]):
#     ...

场景:只想查看配置,不想加载模型(节省资源)

from transformers import AutoConfig

config = AutoConfig.from_pretrained(model_path)
print(f"隐藏层维度:{config.hidden_size}")
print(f"标签数量:{config.num_labels}")
print(f"标签映射:{config.id2label}")

六、小结

问题 答案
my_model.config 是什么? 模型对象内嵌的配置属性,保存了构建该模型所用的全部参数和元数据。
怎么用? 像普通对象一样访问属性:my_model.config.hidden_sizemy_model.config.id2label[0]
与单独加载 AutoConfig 的区别? 内容相同,但 model.config 不额外占用内存,且与模型状态绝对一致。单独加载的配置对象修改后不影响已存在的模型实例。
推荐使用哪种? 只要模型已加载,优先使用 model.config;仅当不想加载模型时才用 AutoConfig

现在你已经完全掌握了 Hugging Face 配置系统的两种访问方式及其最佳实践。这种理解对后续调试模型、自定义训练流程都至关重要。


4、最终代码

import torch
from transformers import AutoTokenizer, AutoModelForTokenClassification
from transformers import AutoConfig    # 单独加载配置


# 1. 加载分词器
my_tokenizer = AutoTokenizer.from_pretrained(r'./model/roberta-base-finetuned-cluener2020-chinese')

# 2. 加载模型
my_model = AutoModelForTokenClassification.from_pretrained(r'./model/roberta-base-finetuned-cluener2020-chinese')
print(my_model)

# 3. 准备样本
# text = '停留在甲板等日出,关于美人鱼的纪录,从此后被塞入了瓶盖'    # 这句话里一个 ner 都找不出来
text = '我在北京故宫博物院看到了《红楼梦》这本书'                 # 用这句话就可以

# 4. 把样本转换成张量
# inputs.input_ids 在前后加入了 [CLS]-101、[SEP]-102, 而不仅仅是原始文本
inputs = my_tokenizer(
    text=text,
    padding=True,
    return_tensors='pt'
)
print(inputs)
# {
#     'input_ids': tensor([[101,   977, 4522, 1762, 4508, 3352, 5023, 3189, 1139, 8024, 1068,  754,
#                           5401,  782, 7824, 4638, 5279, 2497, 8024,  794, 3634, 1400, 6158, 1853,
#                           1057,  749, 4486, 4667, 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, 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, 1, 1,
#                                1, 1, 1, 1, 1]])
# }

# 为了后面用 '原始token 类型' 放在一起展示, 所以这里把 inputs.input_ids 解码成对应的 token
# my_tokenizer.convert_ids_to_tokens(...) 在《自动模型方式完成多种NLP任务》里的《完型填空任务》中的《`my_tokenizer.decode(...)` - API》有解释
# my_tokenizer.convert_ids_to_tokens(...) 里面只能是 单个 ID 或 ID 列表
text_token = my_tokenizer.convert_ids_to_tokens(inputs.input_ids[0])
print(text_token)
# ['[CLS]', '停', '留', '在', '甲', '板', '等', '日', '出', ',', '关', '于', '美', '人',
# '鱼', '的', '纪', '录', ',', '从', '此', '后', '被', '塞', '入', '了', '瓶', '盖', '[SEP]']

# 5. 开启模型推理模型 & 不计算梯度, 这两个可以同时开启, 也可以只开启 my_model.eval(), 推荐同时开启
my_model.eval()
with torch.no_grad():
    outputs = my_model(**inputs)
print(outputs.logits.shape)    # torch.Size([1, 29, 32])
print(outputs)
# outputs.logits 里每个logit里最高分对应的下标就是预测的类别
# TokenClassifierOutput(
#     loss=None,
#     logits=tensor([[[9.3945e+00,   4.1166e-01,  1.2500e+00, -1.5394e-01, -2.9729e-01,
#                      -1.2726e-01, -8.9371e-03,  2.3292e-01, -5.5381e-01, -4.2870e-01,
#                      -2.5781e-01, -8.8203e-01, -1.1683e-01, -8.4785e-02,  1.1912e-01,
#                      -6.1745e-01,  3.8417e-01,  5.7206e-02, -5.7706e-01, -8.8238e-02,
#                       9.3051e-01, -1.4076e+00, -1.9109e+00, -2.0576e+00, -2.4949e+00,
#                      -1.9235e+00, -1.6251e+00, -1.8709e+00, -1.9851e+00, -2.1019e+00,
#                      -2.5674e+00, -2.2312e+00],
#                     ...
#                      [1.1723e+01,  4.9509e-02, -1.2653e-01, -5.9781e-01, -4.2287e-01,
#                       1.1590e-01, -2.9862e-01, -5.6368e-02, -5.3732e-01, -1.6400e-01,
#                      -8.2875e-01, -1.1824e+00, -3.0672e-01, -3.1450e-01,  1.7780e-02,
#                       2.1668e-02, -2.7108e-01, -6.0661e-01, -7.7470e-01, -2.5914e-01,
#                      -1.9377e-02, -1.4555e+00, -1.8572e+00, -2.2463e+00, -2.5508e+00,
#                      -2.1479e+00, -1.7642e+00, -2.3704e+00, -2.0913e+00, -2.1438e+00,
#                      -2.4708e+00, -2.2453e+00]]]),
#     hidden_states=None,
#     attentions=None
# )

id2label = my_model.config.id2label    # 模型里本身就有配置,
ans = []
for token, logit in zip(text_token, outputs.logits[0]):
    # print(token)           # [CLS]
    # print(logit.shape)     # torch.Size([32])

    # 由于 text_token 和 outputs.logits 都包含特殊字符 [CLS]、[SEP]
    idx = torch.argmax(logit, dim=-1).item()    # 类别对应的 id
    ans.append((token, id2label[idx]))
print(ans)
# [
#     ('[CLS]', 'O'), ('我', 'O'), ('在', 'O'), ('北', 'B-address'), ('京', 'I-address'),
#     ('故', 'I-organization'), ('宫', 'I-organization'), ('博', 'I-scene'), ('物', 'I-scene'),
#     ('院', 'I-scene'), ('看', 'O'), ('到', 'O'), ('了', 'O'), ('《', 'B-book'), ('红', 'I-book'),
#     ('楼', 'I-book'), ('梦', 'I-book'), ('》', 'I-book'), ('这', 'O'), ('本', 'O'), ('书', 'O'), ('[SEP]', 'O')
# ]


Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐