如何高效为已训练BERT序列分类模型新增分类类别?
问题描述
我正在执行基于商品描述的品牌分类任务,数据集包含description和brand两个字段。完成初始训练后,希望在不重新训练整个模型的前提下为模型新增分类类别以节省计算资源。尝试采用BERT序列分类模型结合PEFT(参数高效微调)方案,但修改分类器层为model.classifier = nn.Linear(model.bert.config.hidden_size, 100)后,重新训练适配器时触发错误:
RuntimeError: shape '[-1, 43]' is invalid for input of size 660
当前使用的完整代码如下:
from transformers import AutoModelForSequenceClassification, AdamW, get_scheduler from peft import LoraConfig import torch from torch import nn from tqdm import tqdm import numpy as np model = AutoModelForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=len(set(custom_dataset2.labels))) lora_config = LoraConfig( task_type=peft.TaskType.SEQ_CLS, # 任务类型:序列分类 r=4, # 低秩适配矩阵的秩 lora_alpha=32, # 低秩矩阵的缩放因子 lora_dropout=0.1, # 低秩矩阵的 dropout 概率 ) optimizer = AdamW(model.parameters(), lr=0.00001) num_epochs = 30 total_steps = num_epochs * len(data_loader2) lr_scheduler = get_scheduler( "linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=total_steps ) device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") model.to(device) model.train() progress_bar = tqdm(range(total_steps)) for epoch in range(num_epochs): losses = [] correct_predictions = 0 for i, batch in enumerate(data_loader2): input_ids = batch["input_ids"].to(device) attention_mask = batch["attention_mask"].to(device) labels = batch["labels"].to(device) outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels) logits = outputs.logits predictions = logits.argmax(dim=1) loss = outputs.loss losses.append(loss.item()) optimizer.zero_grad() loss.backward() optimizer.step() progress_bar.update(1) print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{total_steps}], Loss: {loss.item():.4f}') correct_predictions += torch.sum(predictions == labels) train_acc = correct_predictions.double() / len(data_loader1.dataset) train_loss = np.mean(losses) print(f"Epoch {epoch+1}/{num_epochs}, Train accuracy: {train_acc:.4f}, Train loss: {train_loss:.4f}") # 需要解决的代码部分: model.classifier = nn.Linear(model.bert.config.hidden_size, 100)
错误原因
- 未启用PEFT机制:代码仅定义了
LoraConfig,但未通过get_peft_model将适配器绑定到BERT模型,导致模型仍是全参数训练状态,没有实现参数高效微调。 - 分类器维度不匹配:直接替换分类器后,模型输出维度(100类)与损失计算依赖的标签维度(原43类)不匹配,触发形状错误。
- 训练逻辑错误:
correct_predictions仅累加最后一个batch的正确数,且混淆了data_loader1和data_loader2的数据集长度统计。
解决方案
步骤1:正确完成初始PEFT训练
初始训练时必须用get_peft_model包装BERT模型,仅训练适配器参数:
from peft import get_peft_model, LoraConfig, TaskType # 初始化BERT分类模型(初始类别数根据数据集设置) model = AutoModelForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=初始类别数) # 配置Lora适配器 lora_config = LoraConfig( task_type=TaskType.SEQ_CLS, r=4, lora_alpha=32, lora_dropout=0.1, target_modules=["query", "value"] # 指定BERT中注入Lora的注意力模块 ) # 绑定适配器到模型,启用PEFT model = get_peft_model(model, lora_config) # 查看可训练参数(仅适配器参数会被标记为可训练) model.print_trainable_parameters()
步骤2:加载PEFT模型并替换分类器
新增类别时,先加载已训练的PEFT模型,再替换分类器层并配置可训练参数:
from peft import PeftModel # 加载已训练的PEFT模型 peft_model_path = "你的PEFT模型保存路径" base_model = AutoModelForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=初始类别数) model = PeftModel.from_pretrained(base_model, peft_model_path) # 替换分类器为新类别数(比如100类) new_num_labels = 100 model.classifier = nn.Linear(model.config.hidden_size, new_num_labels).to(device) # 设置参数可训练状态:冻结BERT主体,仅训练适配器和新分类器 for name, param in model.named_parameters(): if "classifier" in name or "lora" in name: param.requires_grad = True else: param.requires_grad = False
步骤3:修正训练流程适配新类别
调整训练循环逻辑,确保损失计算与新分类器维度匹配:
# 仅优化可训练参数 optimizer = AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-4) num_epochs = 10 # new_data_loader是包含新旧类别的数据集加载器 total_steps = num_epochs * len(new_data_loader) lr_scheduler = get_scheduler( "linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=total_steps ) model.train() progress_bar = tqdm(range(total_steps)) for epoch in range(num_epochs): losses = [] correct_predictions = 0 total_samples = 0 for batch in new_data_loader: input_ids = batch["input_ids"].to(device) attention_mask = batch["attention_mask"].to(device) labels = batch["labels"].to(device) total_samples += labels.size(0) outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels) loss = outputs.loss losses.append(loss.item()) # 统计当前batch的正确预测数 predictions = outputs.logits.argmax(dim=1) correct_predictions += torch.sum(predictions == labels).item() optimizer.zero_grad() loss.backward() optimizer.step() progress_bar.update(1) # 计算 epoch 层面的准确率和损失 train_acc = correct_predictions / total_samples train_loss = np.mean(losses) print(f"Epoch {epoch+1}/{num_epochs}, Train accuracy: {train_acc:.4f}, Train loss: {train_loss:.4f}")
关键注意事项
- 新增类别时,数据集必须包含新旧类别的标注样本,否则模型无法学习新类别的特征。
- 可根据需求选择:仅训练新分类器(冻结适配器),或同时微调适配器和分类器(保留两者的
requires_grad=True)。 - 替换分类器后,默认的Kaiming初始化即可满足需求,无需额外调整。
内容的提问来源于stack exchange,提问作者CaroLife
相关产品推荐
相关产品推荐

