如何用AutoModelForSequenceClassification进行NER微调?解决张量形状错误
解决方案:用AutoModelForSequenceClassification适配NER任务
一、先解决输入序列长度报错
你遇到的716 vs 512尺寸不匹配,是因为输入文本长度超过了BERT默认的512最大上下文窗口限制,处理方式有两种:
- 截断/分段长文本:直接将超过512的文本截断至最大长度;或者分成多个重叠/不重叠的片段(比如每段取512个token,相邻段重叠100个token),确保所有输入序列长度≤512;
- 修改模型配置(谨慎使用):如果模型允许,修改
max_position_embeddings参数并重新初始化位置嵌入,但这种方式会破坏预训练的位置编码效果,仅在必要时尝试。
二、适配SequenceClassification模型到NER任务
AutoModelForSequenceClassification默认只输出[CLS] token的预测结果,而NER需要逐token的分类输出,需从模型结构和前向逻辑两方面改造:
1. 替换分类头
加载模型后,把原本针对[CLS]的序列分类头,改成支持逐token预测的线性层:
from transformers import AutoModelForSequenceClassification, AutoConfig import torch.nn as nn # 加载目标模型和配置 model_name = "你的预训练模型名称" config = AutoConfig.from_pretrained(model_name) # 修改配置为你的NER标签数量 config.num_labels = len(你的NER标签列表) # 加载模型(用新配置覆盖原分类头设置) model = AutoModelForSequenceClassification.from_pretrained(model_name, config=config) # 替换分类层:原classifier是单维度输出,改为适配所有token的线性层 del model.classifier model.classifier = nn.Linear(config.hidden_size, config.num_labels)
2. 重写前向传播逻辑
让模型返回所有token的logits,而非仅[CLS]的结果,并适配NER的损失计算:
def custom_forward(input_ids, attention_mask=None, token_type_ids=None, labels=None): # 获取BERT主体的所有token隐藏状态 bert_outputs = model.bert(input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids) last_hidden_state = bert_outputs.last_hidden_state # 形状:(batch_size, seq_len, hidden_size) # 生成逐token的预测logits logits = model.classifier(last_hidden_state) # 形状:(batch_size, seq_len, num_labels) loss = None if labels is not None: # 用CrossEntropyLoss计算损失,忽略padding部分的标签(设为-100) loss_fct = nn.CrossEntropyLoss(ignore_index=-100) loss = loss_fct(logits.view(-1, config.num_labels), labels.view(-1)) return (loss, logits) if loss is not None else logits # 替换模型默认的forward方法 model.forward = custom_forward
3. 数据处理适配
确保标签序列和输入序列长度对齐,且padding部分的标签设为-100(让损失函数忽略):
import torch from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained(model_name) def preprocess_ner_data(examples): tokenized_inputs = tokenizer( examples["text"], truncation=True, max_length=512, padding="max_length", return_tensors="pt" ) labels = [] for i, label in enumerate(examples["labels"]): word_ids = tokenized_inputs.word_ids(batch_index=i) label_ids = [] prev_word_idx = None for word_idx in word_ids: if word_idx is None: # padding部分标签设为-100 label_ids.append(-100) elif word_idx != prev_word_idx: # 每个词的第一个token用原标签 label_ids.append(label[word_idx]) else: # 子词处理:IOB格式下可继承父词的I-标签,或设为-100,根据任务调整 label_ids.append(label[word_idx] if label[word_idx].startswith("I-") else -100) prev_word_idx = word_idx labels.append(label_ids) tokenized_inputs["labels"] = torch.tensor(labels) return tokenized_inputs
三、训练注意事项
- 损失计算必须忽略padding标签,否则会引入无效损失影响训练;
- 由于该预训练模型原本是针对序列分类任务的,微调NER时可能需要更多训练数据和epochs,抵消预训练任务的偏差;
- 验证阶段需将子词的预测结果合并回原词,比如取每个词的第一个token的预测作为该词的最终标签。
内容的提问来源于stack exchange,提问作者Bsonmez
相关产品推荐
相关产品推荐

