微调BERT模型作为聊天机器人时训练报错求助
问题:微调BERT生成角色风格回复时训练报错
我尝试微调BERT模型,让它根据输入语句生成类角色风格的回复,但训练时总是出现异常错误。其中source_texts是上下文语句列表,target_texts是对应回复语句列表,代码如下:
from transformers import AutoModel, AutoTokenizer model = AutoModel.from_pretrained("bert-base-cased").to(device) tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") input_ids = [] output_ids = [] for i in range (0 , len(source_text)): input_ids.append(tokenizer.encode(source_texts[i], return_tensors="pt")) output_ids.append(tokenizer.encode(target_texts[i], return_tensors="pt")) import torch device = torch.device("cuda") from transformers import BertForMaskedLM, AdamW model = BertForMaskedLM.from_pretrained("bert-base-cased") optimizer = AdamW(model.parameters(), lr=1e-5) loss_fn = torch.nn.CrossEntropyLoss() def train(input_id, output_id): input_id = input_id.to(device) output_id = output_id.to(device) model.zero_grad() logits, _ = model(input_id, labels=output_id) # Compute the loss loss = loss_fn(logits.view(-1, logits.size(-1)), output_id.view(-1)) loss.backward() optimizer.step() return loss.item() for epoch in range(50): # Train the model on the training dataset train_loss = 0.0 for input_sequences, output_sequences in zip(input_ids, output_ids): input_sequences = input_sequences.to(device) output_sequences = output_sequences.to(device) train_loss += train(input_sequences, output_sequences)
报错信息:ValueError: too many values to unpack (expected 2)
错误原因及修复方案
1. 核心错误:模型返回值处理错误
BertForMaskedLM的forward方法返回的是**MaskedLMOutput对象**,而非可解构的元组,因此logits, _ = model(...)会触发解包错误。正确获取logits的方式是访问对象的logits属性:
outputs = model(input_id, labels=output_id) logits = outputs.logits # 同时可直接调用outputs.loss获取预计算的损失,无需手动定义CrossEntropyLoss
2. 其他关键问题修复
- 变量名错误:循环中使用
len(source_text),但实际变量为source_texts,会触发NameError,需改为len(source_texts)。 - 设备定义顺序错误:在定义
device前就执行了model.to(device),会触发NameError,需将import torch和device定义移至代码最开头。 - 重复加载模型:先加载
AutoModel后又重新加载BertForMaskedLM,前者无实际作用,直接删除即可。 - 数据未做对齐:每个样本的
input_ids和output_ids长度不一致,训练时会报错,需用tokenizer.pad统一长度,或在encode时指定padding='max_length'和truncation=True。 - 模型选型偏差:
BertForMaskedLM是掩码语言模型,适用于完形填空类任务,不适合直接做上下文到回复的生成任务。若要实现对话生成,更推荐使用Seq2Seq模型(如BartForConditionalGeneration、T5ForConditionalGeneration)或专门的对话模型(如BlenderBot)。若坚持用BERT,需将任务改造为掩码预测形式(拼接上下文与回复,掩码回复部分token训练)。
修复后代码示例(改用Seq2Seq模型T5)
针对对话生成任务,推荐使用T5模型,示例代码如下:
import torch from transformers import T5Tokenizer, T5ForConditionalGeneration, AdamW # 初始化设备 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 加载模型与分词器 model_name = "t5-small" tokenizer = T5Tokenizer.from_pretrained(model_name) model = T5ForConditionalGeneration.from_pretrained(model_name).to(device) # 示例数据集 source_texts = ["你好,今天天气不错", "最近有什么好看的电影?"] target_texts = ["是啊,适合出门散步", "推荐你看《流浪地球2》,特效很棒"] # 数据预处理:添加任务前缀(T5要求),统一长度 inputs = tokenizer( ["generate response: " + text for text in source_texts], return_tensors="pt", padding=True, truncation=True, max_length=512 ).to(device) labels = tokenizer( target_texts, return_tensors="pt", padding=True, truncation=True, max_length=512 ).to(device) # 优化器 optimizer = AdamW(model.parameters(), lr=1e-5) # 训练函数 def train_epoch(): model.train() total_loss = 0.0 optimizer.zero_grad() outputs = model(**inputs, labels=labels["input_ids"]) loss = outputs.loss loss.backward() optimizer.step() total_loss += loss.item() return total_loss / len(source_texts) # 训练循环 for epoch in range(50): epoch_loss = train_epoch() print(f"Epoch {epoch+1}, Loss: {epoch_loss:.4f}")
坚持用BERT的掩码训练方案
若一定要基于BERT实现,可将上下文与回复拼接后掩码回复部分token,示例代码如下:
import torch from transformers import BertTokenizer, BertForMaskedLM, AdamW device = torch.device("cuda" if torch.cuda.is_available() else "cpu") tokenizer = BertTokenizer.from_pretrained("bert-base-cased") model = BertForMaskedLM.from_pretrained("bert-base-cased").to(device) # 示例数据集 source_texts = ["Hello, how are you?", "What's your favorite hobby?"] target_texts = ["I'm fine, thanks!", "I like reading books."] # 预处理:拼接文本并生成掩码标签 input_ids = [] mask_labels = [] for src, tgt in zip(source_texts, target_texts): # 拼接上下文与回复 combined = tokenizer.encode_plus(src, tgt, return_tensors="pt") input_id = combined["input_ids"].squeeze() # 定位回复部分起始位置(第一个[SEP]之后) sep_indices = (input_id == tokenizer.sep_token_id).nonzero().squeeze() tgt_start_idx = sep_indices[0] + 1 # 生成掩码标签:回复部分保留原token,其余设为-100(CrossEntropyLoss会忽略) mask_label = torch.full_like(input_id, -100) mask_label[tgt_start_idx:] = input_id[tgt_start_idx:] # 随机掩码回复部分token mask_prob = 0.15 mask_indices = torch.rand(len(input_id)) < mask_prob mask_indices &= (input_id != tokenizer.cls_token_id) & (input_id != tokenizer.sep_token_id) input_id[mask_indices] = tokenizer.mask_token_id input_ids.append(input_id) mask_labels.append(mask_label) # 统一padding长度 max_len = max(len(x) for x in input_ids) input_ids = torch.stack([torch.nn.functional.pad(x, (0, max_len - len(x)), value=tokenizer.pad_token_id) for x in input_ids]).to(device) mask_labels = torch.stack([torch.nn.functional.pad(x, (0, max_len - len(x)), value=-100) for x in mask_labels]).to(device) optimizer = AdamW(model.parameters(), lr=1e-5) # 训练函数 def train_epoch(): model.train() total_loss = 0.0 optimizer.zero_grad() outputs = model(input_ids, labels=mask_labels) loss = outputs.loss loss.backward() optimizer.step() total_loss += loss.item() return total_loss / len(source_texts) # 训练循环 for epoch in range(50): epoch_loss = train_epoch() print(f"Epoch {epoch+1}, Loss: {epoch_loss:.4f}")
内容的提问来源于stack exchange,提问作者Prarabdha Srivastava
相关产品推荐
相关产品推荐

