如何在已训练BERT模型基础上仅用FP、FN样本完成重训练
仅用FP/FN样本微调已训练BERT分类模型的PyTorch实现方案
你要做的操作属于难例微调(Hard Example Fine-tuning),核心是在保留原有100万数据训练成果的基础上,小幅度调整模型对错分样本的拟合能力,避免过拟合即可,具体操作步骤如下:
1. 前期准备
- 完整加载已训练好的模型权重,不要做任何层的随机初始化,参考代码:
import torch from transformers import BertForSequenceClassification, BertTokenizer # 路径替换为你自己的模型保存路径,初始化逻辑和之前训练时保持完全一致 model = BertForSequenceClassification.from_pretrained("your_trained_model_path") tokenizer = BertTokenizer.from_pretrained("your_trained_model_path") device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device)
- 整理所有FP、FN样本为标准训练格式,每条包含原始文本、真实标签;如果难例样本量少于1000条,可以做少量轻量数据增强(比如同义替换、短文本语序微调),降低过拟合风险。
2. 训练策略设置
这部分是核心,绝对不能用之前全量训练的大学习率,否则会直接冲掉原有训练成果:
- 学习率设置为之前全量训练学习率的1/10 ~ 1/5,比如之前全量训练用的是2e-5,现在就用2e-6 ~ 4e-6即可
- 建议冻住BERT底层的前6~8层Transformer,只微调顶层Transformer和分类头,保留底层通用语义特征能力,避免通用分类能力退化,冻层参考代码:
# 冻住BERT前8层,不更新权重 for name, param in model.bert.named_parameters(): if "layer." in name: layer_num = int(name.split("layer.")[1].split(".")[0]) if layer_num < 8: param.requires_grad = False # 分类头默认开启梯度,不需要额外设置
- 训练轮次控制在3~5轮即可,每轮结束可以用之前的全量验证集跑一次效果,如果出现整体准确率下跌就提前停止训练
- 损失函数沿用你之前分类任务用的交叉熵损失即可,不需要修改
3. 训练流程
和常规PyTorch训练流程一致,仅把训练集替换为整理好的FP/FN样本即可,参考代码片段:
from torch.utils.data import DataLoader, Dataset import torch.nn.functional as F # 自定义数据集类,逻辑和你之前训练时保持一致即可 class HardExampleDataset(Dataset): def __init__(self, texts, labels, tokenizer, max_len=128): self.texts = texts self.labels = labels self.tokenizer = tokenizer self.max_len = max_len def __len__(self): return len(self.texts) def __getitem__(self, idx): text = self.texts[idx] label = self.labels[idx] encoding = self.tokenizer.encode_plus( text, truncation=True, add_special_tokens=True, max_length=self.max_len, return_token_type_ids=False, padding='max_length', return_attention_mask=True, return_tensors='pt', ) return { 'input_ids': encoding['input_ids'].flatten(), 'attention_mask': encoding['attention_mask'].flatten(), 'label': torch.tensor(label, dtype=torch.long) } # 初始化数据集、加载器,batch size可以设小一点,比如8、16均可 dataset = HardExampleDataset(fp_fn_text_list, fp_fn_label_list, tokenizer) dataloader = DataLoader(dataset, batch_size=8, shuffle=True) # 优化器仅更新开启了梯度的参数 optimizer = torch.optim.AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr=2e-6) # 训练循环 model.train() for epoch in range(3): total_loss = 0 for batch in dataloader: input_ids = batch['input_ids'].to(device) attention_mask = batch['attention_mask'].to(device) labels = batch['label'].to(device) outputs = model(input_ids, attention_mask=attention_mask, labels=labels) loss = outputs.loss optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() print(f"Epoch {epoch+1} average loss: {total_loss/len(dataloader)}")
4. 效果校验
训练结束后必须同时做两项校验,避免过拟合:
- 用本次训练用的FP/FN样本测试,确认大部分难例已经可以正确分类
- 用之前全量数据的验证集跑一遍整体准确率、召回率、F1值,确保整体效果没有出现大幅下跌,如果下跌就减少训练轮次或者进一步降低学习率。
额外提示:如果你的FP/FN样本总量少于500条,建议直接只微调分类头,不改动BERT任何Transformer层,过拟合风险会大幅降低。
内容的提问来源于stack exchange,提问作者Patricia
相关产品推荐
相关产品推荐

