Transformers TokenClassification输出解析及AutoConfig原理探究(以中文NER为例)
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。 -
第 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 个类别的线性层。
- 形状含义总结图
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 序列顺序一致)。
- 数值特征解释
- 数值范围:有正有负(如
9.39、-2.56)。 - 物理意义:这些数值不是概率,它们没有经过 Softmax 归一化,因此所有类别的分数之和不为 1,也不能直接解释为置信度。
- 决策逻辑:数值越大,表示模型越倾向于认为该 Token 属于该类别。
- 如何转化为最终预测结果?
后续处理流程通常是这样的(虽然代码中没有写,但这是 NER 推理的必备后处理):
-
取 Argmax:对每个 Token 的 32 个分数取最大值所在的索引。
predictions = torch.argmax(outputs.logits, dim=-1) # Shape: [1, 29] -
映射回标签名:通过
my_model.config.id2label将索引0~31转换为具体的字符串标签(如'B-book','I-address','O')。predicted_labels = [my_model.config.id2label[idx.item()] for idx in predictions[0]] -
忽略特殊符号:通常我们会忽略
[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
}
关键字段深度解析:
model_type(核心灵魂):- 例如
"roberta"。这是AutoConfig能够“自动”的关键。库内部有一张映射表,读到这个字段后,它就知道该实例化RobertaConfig类,而不是BertConfig。
- 例如
architectures:- 告诉库这个模型的具体用途类名(如
RobertaForTokenClassification),例如,读取到"RobertaForTokenClassification",它就知道应该实例化RobertaConfig类来加载这些配置
- 告诉库这个模型的具体用途类名(如
id2label/label2id:- 这就是 “为什么 config 里有标签”。这里明确存储了数字 ID 和类别名称的对应关系。
- 这是微调任务的“字典”。它存储了数字 ID 和人类可读标签(如
"B-book")的对应关系。推理时我们全靠它把预测结果翻译成人话。
- 结构参数:
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_size或num_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 属性,用来保存构建该模型时所用的全部配置信息。
逻辑链条如下:
-
from_pretrained加载过程
当你调用AutoModelForTokenClassification.from_pretrained()时,库内部实际上做了三件事:- 第一步:用
AutoConfig.from_pretrained()读取config.json,获得配置对象config。 - 第二步:根据
config中的架构参数,构建模型类(如RobertaForTokenClassification),并将下载的预训练权重加载进去。 - 第三步:将第一步得到的
config对象挂载到模型的config属性上。
- 第一步:用
-
因此
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_size、num_hidden_layers)→ 可以直接.出来。 - 非标准/自定义参数(如你这里的
pooler_fc_size、pooler_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
# }
下面这些操作的输出结果只是示例效果,不是和上面这个配置 一 一 对应:
- 查看结构参数
print(my_model.config.hidden_size) # 768
print(my_model.config.num_hidden_layers) # 12
print(my_model.config.model_type) # 'roberta'
- 获取标签映射(NER 推理必备)
# 直接通过模型访问 id2label
label_name = my_model.config.id2label[0] # 大概率是 'O'
print(label_name)
# 也可以获取整个字典
id2label = my_model.config.id2label
- 动态修改配置(需谨慎)
# 修改 dropout(不影响已加载的权重,仅改变模型 forward 时的行为)
my_model.config.hidden_dropout_prob = 0.0
# 如果修改了标签数量,需要同步调整分类头权重维度(一般不推荐直接改,除非重新初始化)
# my_model.config.num_labels = 10 # 仅当你也重新初始化分类头时才有效
注意:修改 my_model.config 中的结构参数(如 hidden_size、num_attention_heads)不会自动改变模型层本身的维度,此时模型计算图与配置会出现不一致,极易报错。除非你非常清楚自己在做什么,否则仅建议修改 运行时参数(如 dropout、标签映射等)。
例外:num_labels 虽然也属于结构参数,但如果你在修改它之后,显式地用新的分类头替换原有 classifier 层,再重新训练,这种操作在迁移学习中是允许的。
- 保存模型时会自动带上最新的 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_labels或id2label解析结果,直接取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_size、my_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')
# ]
更多推荐


所有评论(0)