ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

【Bug已解决】Fine tuning a BERT Model as a chatbot giving error while training 解决方案

【Bug已解决】Fine tuning a BERT Model as a chatbot giving error while training 解决方案 【Bug已解决】Fine tuning a BERT Model as a chatbot giving error while training 解决方案一、现象长什么样很多人第一次想做自己的聊天机器人时会本能地选 BERT因为它名气大、教程多。于是把一问一答的语料直接喂给BertForSequenceClassification运行trainer.train()之后控制台立刻抛出类似下面的错误RuntimeError: size mismatch for classifier.weight: copying a param with shape torch.Size([5, 768]) from checkpoint, the shape in current model is torch.Size([2, 768]).或者另一种更隐蔽的写法——把 BERT 当成生成式模型用BertLMHeadModel喂入整句对话并期望它续写回复结果得到IndexError: index 103 out of bounds for dimension 1 with size 103还有一些人会遇到ValueError: Expected input batch_shape (8, 64) but got (8, 64, 768)无论报的是哪一种本质上都指向同一件事BERT 的模型结构和聊天机器人这件事之间存在根本性的错配而不是某个参数写错了那么简单。二、背景BERT 是 2018 年提出的encoder-only仅编码器预训练模型它的预训练任务是 MLMMasked Language Model把句子里某些词盖住让模型猜和 NSP下一句预测。这决定了三件关键事实BERT 没有自回归生成能力。它读入一整段文本输出每个 token 的上下文表示而不是一个接一个地往外吐词。BertForSequenceClassification的头部是一个线性层classifier: Linear(hidden_size, num_labels)它把整句压缩成一个固定维度的向量再分类。BertLMHeadModel虽然能填空但只在[MASK]位置上产生预测且不会像 GPT 那样根据前面生成的词继续生成下一个词。所谓聊天机器人在工程上有两种主流做法检索 / 分类式用户说一句话模型判断意图或匹配最相似的问答对再从库里取出对应回复。这是 BERT 真正擅长的事。生成式模型逐 token 生成回复。这应该是 GPT、LLaMA、Qwen 等 decoder-only 模型的地盘。把生成式聊天硬塞进 BERT就会出现第一节里的各种尺寸、下标错误。三、根因先把那个最典型的size mismatch讲清楚。下面是一段会引发错误的真实写法from transformers import BertForSequenceClassification, Trainer, TrainingArguments # 语料里有 5 种意图但下面这行没有传 num_labels model BertForSequenceClassification.from_pretrained(bert-base-uncased) # 此时模型默认 num_labels2classifier 权重形状是 [2, 768] training_args TrainingArguments(output_dir./bot, per_device_train_batch_size8) trainer Trainer(modelmodel, argstraining_args, train_datasetdataset) trainer.train() # 报错classifier.weight 形状对不上根因链条如下Hugging Face 的from_pretrained在加载BertForSequenceClassification时如果没有显式指定num_labels会采用配置文件里的默认值通常是 2。当你的dataset里labels字段取到 0~45 类Trainer内部会用标签的最大值推断类别数于是前向传播期望classifier输出维度是 5。但加载的权重是 2 维PyTorch 在拷贝/计算时发现[2, 768]与期望的[5, 768]不一致抛size mismatch。至于BertLMHeadModel那种IndexError根因是MLM 头只在被 mask 的位置计算 loss你却把整句含[CLS]、[SEP]都当成要预测的目标位置索引越界。还有一个常被忽略的根因任务建模本身选错了模型。用 encoder-only 的 BERT 去生成回复属于架构层面的不匹配补参数只能让程序不崩却产不出能聊天的模型。四、最小可运行复现下面用最小代码稳定复现size mismatch。不需要 GPUCPU 即可。from transformers import BertTokenizerFast, BertForSequenceClassification import torch tokenizer BertTokenizerFast.from_pretrained(bert-base-uncased) texts [hello, bye, thanks, help, ok] # 假设 5 类但标签用了 0~4 labels torch.tensor([0, 1, 2, 3, 4]) enc tokenizer(texts, return_tensorspt, paddingTrue, truncationTrue) enc[labels] labels # 关键点不传 num_labels默认 2 model BertForSequenceClassification.from_pretrained(bert-base-uncased) # 因为预训练权重是 2 类这里强行 forward 会触发形状问题 out model(**enc) print(out.logits.shape) # 期望 [5, 2]但你的标签暗示应有 5 类当Trainer用这套配置训练时它会在某一步对classifier做形状相关的操作例如带动量的优化器状态、或加载你中途存的检查点从而暴露size mismatch。即使logits.shape这次打印出来是[5, 2]一旦你保存检查点再用num_labels5加载就会立刻对不上。五、解决方案第一层最小直接修复最便宜的修复让num_labels和实际类别数一致。在加载模型时显式声明from transformers import BertForSequenceClassification NUM_INTENTS 5 model BertForSequenceClassification.from_pretrained( bert-base-uncased, num_labelsNUM_INTENTS, )这样classifier会被随机初始化成[5, 768]与你的标签范围匹配训练可以正常启动。如果你的聊天其实是想做问答匹配给定用户问题从候选回复里挑最像的那条可以直接用BertForSequenceClassification做句子对分类或者做向量召回from transformers import BertModel import torch.nn.functional as F base BertModel.from_pretrained(bert-base-uncased) def encode(text): enc tokenizer(text, return_tensorspt, paddingTrue, truncationTrue) with torch.no_grad(): v base(**enc).pooler_output # [1, 768] return F.normalize(v, dim-1) query encode(怎么退款) candidates [encode(请在订单页点击申请退款), encode(今天天气不错)] sims [F.cosine_similarity(query, c) for c in candidates] best candidates[sims.index(max(sims))] print(最佳回复:, 请在订单页点击申请退款)这一层只解决程序能跑但还没解决它到底是不是个好聊天机器人。六、解决方案第二层结构化改进真正稳妥的做法是把聊天机器人这件事拆成清晰的两阶段并用一个配置对象作为唯一真相来源避免num_labels和数字散落在代码各处导致再次对不上。from dataclasses import dataclass, field from typing import List dataclass class BertChatbotTrainPolicy: model_name: str bert-base-uncased intents: List[str] field(default_factorylist) # 意图名列表唯一真相来源 max_length: int 64 batch_size: int 16 epochs: int 3 learning_rate: float 2e-5 property def num_labels(self) - int: # 类别数永远由 intents 推导杜绝手滑写错 return len(self.intents) def build_model(self): from transformers import BertForSequenceClassification if self.num_labels 2: raise ValueError(意图数必须 2单类无法训练分类头) return BertForSequenceClassification.from_pretrained( self.model_name, num_labelsself.num_labels, ) def tokenize(self, texts): from transformers import BertTokenizerFast tok BertTokenizerFast.from_pretrained(self.model_name) return tok( texts, return_tensorspt, paddingTrue, truncationTrue, max_lengthself.max_length, )使用方式变成policy BertChatbotTrainPolicy( intents[greeting, refund, thanks, help, other], ) model policy.build_model() # num_labels 自动 5 print(policy.num_labels) # 5和 intents 永远一致如果业务确实需要生成式回复第二层应该直接换模型而不是在 BERT 上硬改from transformers import AutoModelForCausalLM, AutoTokenizer # 生成式聊天请用 decoder-only 模型 gen_tok AutoTokenizer.from_pretrained(microsoft/DialoGPT-small) gen_model AutoModelForCausalLM.from_pretrained(microsoft/DialoGPT-small)把决策固化成一条规则意图识别 / 语义匹配用 BERT开放生成用 GPT 类模型。这条规则写进BertChatbotTrainPolicy的注释里团队就不会再选错架构。七、解决方案第三层断言 / CI 守护把类别数与标签范围一致输入是二维 batch做成自动化断言让错误在 CI 阶段而不是训练到一半才暴露。import torch import pytest from transformers import BertForSequenceClassification, BertTokenizerFast pytest.fixture def policy(): from dataclasses import dataclass dataclass class P: model_name: str bert-base-uncased intents: list None def num_labels(self): return len(self.intents) def build(self): return BertForSequenceClassification.from_pretrained( self.model_name, num_labelsself.num_labels()) p P(intents[a, b, c, d, e]) return p def test_num_labels_matches_intents(policy): assert policy.num_labels() 5 def test_classifier_dim_matches_labels(policy): model policy.build() # 头部的输出维度必须等于类别数 assert model.classifier.out_features policy.num_labels() def test_forward_accepts_2d_batch(policy): tok BertTokenizerFast.from_pretrained(policy.model_name) enc tok([hi, bye], return_tensorspt, paddingTrue, truncationTrue) enc[labels] torch.tensor([0, 1]) out policy.build()(**enc) # logits 必须是 [batch, num_labels]二维 assert out.logits.dim() 2 assert out.logits.shape[1] policy.num_labels() def test_reject_single_class(): with pytest.raises(ValueError): BertForSequenceClassification.from_pretrained( bert-base-uncased, num_labels1)把这套 pytest 挂到 CI下次有人把intents改少或漏传num_labels会在提交阶段直接红掉。八、排查清单遇到 Fine tuning a BERT Model as a chatbot giving error while training 这类问题按下面顺序查看报错里有没有size mismatch for classifier有 →num_labels和实际类别数对不上按第五节修复。确认dataset的labels最大值 1 是否等于你给的num_labels标签从 0 开始计5 类就必须是 0~4。不要对 BERT 用BertLMHeadModel做开放生成它只在[MASK]位置算 loss做聊天会IndexError。想做聊天先问自己是要意图分类 / 问答匹配用 BERT还是要自由生成用 GPT/LLaMA 类 decoder-only 模型。保存和加载检查点用同一套num_labels否则第二次加载会再次size mismatch。pooler_output才是句向量做召回匹配时用model(**enc).pooler_output别直接用last_hidden_state未池化就算余弦。小数据集别训太久BERT 能力强几十条文案训 3 个 epoch 就够过拟合反而答非所问。九、小结BERT 训练报错as a chatbot giving error while training绝大多数情况下不是环境问题而是用错了模型范式BERT 是 encoder-only擅长分类与匹配不擅长逐词生成。最常见的size mismatch直接来自num_labels没和实际意图数对齐——显式声明即可解决但更本质的修复是重新定义任务把聊天拆成意图识别 回复检索或向量召回生成式需求直接换 decoder-only 模型。用BertChatbotTrainPolicy这样的配置对象把num_labels收敛为单一来源再配 pytest 守护就能让这类错配在写代码阶段而不是训练中途被拦下来。
返回列表