公司动态
孟加拉语少样本NLP:iPET迭代提示微调与彩票假设剪枝实践
做孟加拉语BengaliNLP的时候很多人都卡在同一个问题上标注数据太少。孟加拉语全球使用人口超过 2.5 亿但公开可用的高质量标注数据却远远比不上英语和中文少样本场景下的文本分类、情感分析、命名实体识别都很难直接套用常规 BERT 微调流程。最近我在梳理低资源语言模型方案时看到 BnBERT-iPET 这一思路——把迭代提示微调iPET和彩票假设剪枝Lottery Ticket Pruning结合起来用少量标注数据完成孟加拉语模型的稀疏训练和推理。本文就围绕这个方案做一次系统拆解从概念、原理、实验流程到代码思路和常见坑点给大家一份能直接参考的实操笔记。1. 背景孟加拉语少样本语言建模的难点1.1 孟加拉语 NLP 为什么难做孟加拉语属于印欧语系印度-雅利安语支词形变化丰富存在大量复合动词、叠词和方言变体而且字符集是孟加拉文Bangla script和拉丁语系的 Tokenizer 兼容性天然比较差。这就带来两个直接问题英语预训练模型直接迁移到孟加拉语时词表覆盖率和语义对齐效果往往不理想。孟加拉语的开源标注数据规模小很多任务只有几百条样本深度学习模型很容易过拟合。从工程角度看低资源语言模型还需要考虑推理成本和存储成本。模型参数动辄上亿移动端和业务系统里很难直接部署。因此少样本学习Few-shot Learning和模型稀疏化Sparsity这两个方向在孟加拉语场景里就显得特别实用。1.2 少样本场景的常规解法少样本训练常见思路有三种解决思路代表方法优点缺点数据增强回译、同义词替换简单易懂可能改变语义孟加拉语增强工具少提示学习Prompt-tuning、PET能用小模型完成任务依赖模板设计标注数据少时效果不稳迭代增强训练iPET自动扩充训练集错误累积风险高需要置信度过滤模型压缩剪枝、蒸馏降低推理成本过度剪枝会导致精度明显下降BnBERT-iPET 的核心思路就是用 iPET 框架把少量标注数据滚动扩成较高质量的伪标注数据集再用 Lottery Ticket Pruning 把模型压成稀疏结构两者叠加之后既缓解了数据不足又降低了训练和推理的资源消耗。1.3 为什么选 BERT 架构BERT 在跨语言任务上表现稳定HuggingFace 也提供了多种孟加拉语预训练模型。常规流程是用孟加拉语 BERT 做 MLM 继续预训练或直接做下游任务微调。相比从头训练 Transformer使用预训练 BERT 权重可以显著减少对标注数据的依赖这也是 BnBERT-iPET 把 BERT 作为基础模型的原因。2. 核心概念拆解iPET 与 Lottery Ticket Pruning2.1 iPET 是什么iPET 的全称是 Iterative Prompt-tuning for Few-shot Learning是 PETPattern-Exploiting Training的迭代增强版本。PET 通过设计“模式”Pattern和“语言表达”Verbalizer把文本分类任务伪装成完形填空或文本蕴含任务用少量样本微调模型。iPET 在此基础上引入了迭代蒸馏用少量标注数据训练多个 Teacher 模型。Teacher 模型对无标注数据进行预测。根据置信度筛选伪标注样本扩充训练集。用扩充后的数据训练新的 Student 模型。重复迭代直到性能收敛。这个流程很像蒸馏和自训练的变体区别在于 iPET 特别强调“多 Teacher 集成 置信度过滤”从而减少伪标注中的噪声。2.2 Lottery Ticket Hypothesis 是什么彩票假设Lottery Ticket Hypothesis由 Frankle 和 Carbin 在 2019 年提出。核心观点是随机初始化的稠密网络中存在一个“中奖子网络”winning ticket单独训练这个子网络可以达到甚至超过原始网络的精度。寻找中奖彩票的标准流程随机初始化网络记录初始权重。完整训练网络得到收敛权重。按权重绝对值大小剪掉一定比例参数。将剩余网络的权重重置为初始值而不是保留训练后的值。重复训练和剪枝得到稀疏子网络。这个机制对低资源任务很友好因为最终部署时只需要保留稀疏子网络存储和计算量都大幅下降。2.3 BnBERT-iPET 的组合动机把 iPET 和 Lottery Ticket Pruning 组合起来逻辑上是互补的iPET 解决“数据不够”的问题让模型在少样本条件下尽可能学到稳定特征。Lottery Ticket Pruning 解决“模型太大、容易过拟合、部署成本高”的问题。稀疏化本身也是一种正则化在少样本场景下反而可能提升泛化性能。因此BnBERT-iPET 对应的实验流程大致是先选一个孟加拉语 BERT 模型用 iPET 流程迭代生成伪标注数据训练出 Teacher/Student 模型同时用彩票剪枝寻找稀疏子网络最后评估稀疏模型在小样本测试集上的效果。3. 环境准备与实验配置3.1 硬件与软件环境下面是一份可供参考的实验环境。由于深度学习框架迭代很快建议以实际安装为准操作系统Ubuntu 20.04 / 22.04Windows 也可以运行但命令行参数要适当调整。Python3.8 或 3.9。GPU建议至少 12GB 显存如 Tesla T4、RTX 3060 及以上显存不足时可以减少 batch size。深度学习框架PyTorch 1.12 以上。HuggingFace Transformers4.20 以上。Datasets、Accelerate、scikit-learn、tqdm。依赖安装示例pip install torch transformers datasets accelerate scikit-learn tqdm3.2 孟加拉语预训练模型选择可以选择 HuggingFace 上的孟加拉语 BERT 模型例如bert-base-multilingual-cased多语言 BERT覆盖孟加拉语但词表偏大。专门的孟加拉语 BERT 模型部分第三方模型在孟加拉语任务上效果更好但需要根据模型卡信息确认数据构成和许可协议。注意不同模型 Tokenizer 对孟加拉语字符的处理方式不同实验前最好在小样本上跑通前向传播确认输入 ID 和 attention mask 是正常的。from transformers import AutoTokenizer, AutoModelForSequenceClassification model_name bert-base-multilingual-cased tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForSequenceClassification.from_pretrained(model_name, num_labels2) print(tokenizer.tokenize(আমি বাংলায় ভালোবাসি))如果 Tokenizer 输出的不是孟加拉字符就需要换成更合适的专用模型。3.3 项目结构建议建议把实验代码按模块分离方便调试和复现bnbert_ipet/ ├── config.py ├── data/ │ ├── train.csv │ ├── unlabeled.csv │ └── test.csv ├── models/ │ └── bert_classifier.py ├── ipet/ │ ├── teacher.py │ ├── student.py │ └── filter.py ├── prune/ │ └── lottery.py ├── train.py ├── prune_train.py └── evaluate.py这样拆分的目的是让数据加载、Teacher 训练、伪标注过滤、剪枝训练彼此解耦。在少样本实验中一个小配置错误就可能导致整个流程白跑清晰的模块边界能帮你快速定位问题。4. 方案设计与技术拆解4.1 整体工作流BnBERT-iPET 的完整工作流可以分成六个阶段数据准备把少量标注数据划分成支撑集和验证集。初始化 Teacher 模型用少量标注数据微调多个 Teacher。iPET 迭代增强Teacher 对无标注数据预测经过置信度过滤后合并进训练集。Student 模型训练用增强后的数据训练 Student。彩票剪枝搜索记录初始权重训练、剪枝、重置、再训练。稀疏模型评估评估剪枝后子网络在真实测试集上的精度与稀疏度。4.2 数据划分与样本量设计少样本实验里支撑集通常每类只保留 10 到 100 条样本。例如二分类任务可以每类 50 条共 100 条训练数据无标注数据可以准备几千到几万条用于 iPET 扩充。孟加拉语数据本身稀缺如果没有现成无标注语料也可以用大规模孟加拉语爬虫文本但要注意清洗噪声。伪代码表示数据划分逻辑def split_few_shot(df, label_col, shots_per_class, seed42): grouped df.groupby(label_col) support [] remaining [] for label, group in grouped: g group.sample(frac1, random_stateseed) support.append(g.head(shots_per_class)) remaining.append(g.tail(-shots_per_class)) support_df pd.concat(support) unlabeled_df pd.concat(remaining) return support_df, unlabeled_df这里的unlabeled_df在完整场景中应替换为真正的无标注语料但逻辑上是相同的。4.3 Teacher 模型集成策略iPET 通常训练 3 到 5 个 Teacher每个 Teacher 使用不同的随机种子或不同的数据子集。这样做是为了让伪标注具备多样性避免单一模型偏差被放大。Teacher 数量不是越多越好原因有三个每个 Teacher 都是一次完整微调成本线性增长。Teacher 之间差异太小时集成提升有限。孟加拉语任务标注数据太少时多个 Teacher 容易收敛到相似状态。经验上3 个 Teacher 性价比最高。如果 GPU 资源充足可以加到 5 个。4.4 置信度过滤策略伪标注质量直接决定 Student 模型效果。过滤策略通常看两个指标预测概率概率越高样本越可靠。Teacher 投票一致性多个 Teacher 结果一致的样本更值得保留。可以组合使用def filter_pseudo_labels(predictions, min_prob0.7, min_votes2): filtered [] for preds in predictions: probs np.mean(preds[probs], axis0) votes preds[labels] max_prob np.max(probs) # 一致性多数 Teacher 输出相同标签 consistent votes.count(votes[0]) min_votes if max_prob min_prob and consistent: filtered.append({ text: preds[text], label: votes[0], confidence: max_prob }) return filtered这里需要注意置信度阈值不是越高越好。阈值太高会导致扩充样本太少iPET 迭代失去意义阈值太低则会把噪声引入训练集。建议先观察伪标注置信度分布再决定阈值。5. 核心代码与流程演示5.1 数据加载与 Tokenizer 封装下面的代码演示如何用 HuggingFace Datasets 加载 CSV 数据并进行 Tokenizer 处理。import pandas as pd from datasets import Dataset from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(model_name) def encode_batch(batch): return tokenizer( batch[text], truncationTrue, paddingmax_length, max_length128 ) def load_data(path): df pd.read_csv(path) dataset Dataset.from_pandas(df) dataset dataset.map(encode_batch, batchedTrue) dataset.set_format( typetorch, columns[input_ids, attention_mask, label] ) return dataset这段代码把文本转成 BERT 需要的input_ids和attention_mask。max_length128在大部分文本分类任务上够用但如果孟加拉语样本句子很长可以适当调大。5.2 训练一个 Teacher 模型下面是一个最小可运行的训练函数包含早停逻辑的占位。实际使用时建议配合accelerate做多卡加速。import torch from torch.utils.data import DataLoader from transformers import AdamW, get_linear_schedule_with_warmup def train_teacher(model, train_dataset, valid_dataset, epochs3, lr2e-5, batch_size8): train_loader DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue) valid_loader DataLoader(valid_dataset, batch_sizebatch_size) optimizer AdamW(model.parameters(), lrlr) total_steps len(train_loader) * epochs scheduler get_linear_schedule_with_warmup( optimizer, num_warmup_stepsint(0.1 * total_steps), num_training_stepstotal_steps ) for epoch in range(epochs): model.train() total_loss 0 for batch in train_loader: batch {k: v.to(device) for k, v in batch.items()} outputs model(**batch) loss outputs.loss loss.backward() optimizer.step() scheduler.step() optimizer.zero_grad() total_loss loss.item() print(fepoch {epoch 1} loss: {total_loss / len(train_loader):.4f}) return model需要说明这里的device建议通过torch.device(cuda if torch.cuda.is_available() else cpu)获取。5.3 iPET 迭代增强流程iPET 的迭代逻辑如下def ipet_iteration(student_model, teacher_models, unlabeled_texts, max_new_samples200): all_preds [] for teacher in teacher_models: teacher.eval() preds predict_with_model(teacher, unlabeled_texts) all_preds.append(preds) pseudo_samples filter_pseudo_labels(all_preds, min_prob0.7, min_votes2) new_samples sorted(pseudo_samples, keylambda x: x[confidence], reverseTrue) new_samples new_samples[:max_new_samples] return new_samples这里的predict_with_model函数会返回每个样本的预测概率与标签数组。过滤后的样本与原始标注数据合并作为下一轮 Student 的训练数据。整个 iPET 迭代需要设置一个最大轮数比如 3 到 5 轮。每一轮结束后在验证集上评估 Student 模型如果指标不再提升就停止迭代避免过度拟合伪标注噪声。5.4 Lottery Ticket Pruning 剪枝实现这里的关键是记录初始权重并在每轮剪枝后恢复初始权重。以一个简化版本为例import copy def save_init_weights(model): 记录初始权重用于剪枝后 rewind init_state {} for name, param in model.named_parameters(): init_state[name] param.detach().clone() return init_state def create_mask(model, sparsity_ratio): 按参数绝对值大小生成二元掩码 mask {} for name, param in model.named_parameters(): if param.dim() 2: # 不对 bias 和 LayerNorm 参数剪枝 mask[name] torch.ones_like(param) continue num_params param.numel() k int(num_params * (1 - sparsity_ratio)) flat param.detach().abs().flatten() threshold torch.topk(flat, k).values[-1] mask[name] (param.detach().abs() threshold).float() return mask def apply_mask(model, mask): 把 mask 注册到梯度更新中 for name, param in model.named_parameters(): if name in mask: param.data.mul_(mask[name])训练时需要让被 mask 的参数不产生梯度def train_with_mask(model, mask, dataloader, optimizer, epochs): for epoch in range(epochs): for batch in dataloader: batch {k: v.to(device) for k, v in batch.items()} outputs model(**batch) loss outputs.loss loss.backward() for name, param in model.named_parameters(): if name in mask: param.grad.mul_(mask[name]) optimizer.step() optimizer.zero_grad()这里每次步进前都乘 mask目的是让被剪枝权重保持为 0。标准的 Lottery Ticket 流程还需要在剪枝后把剩余权重重置为初始值def rewind_to_init(model, init_state, mask): with torch.no_grad(): for name, param in model.named_parameters(): if name in mask: param.copy_(init_state[name] * mask[name])需要提醒的是Transformer 的 LayerNorm 参数和 bias 通常不参与剪枝否则会导致训练不稳定。上面的create_mask里已经做了维度过滤这是工程上比较稳妥的做法。5.5 Student 训练与剪枝搜索结合完整流程是把 iPET 增强后的数据集当作训练集先做一次标准训练作为基线然后做迭代剪枝。简化组合代码如下init_state save_init_weights(student_model) mask create_mask(student_model, sparsity_ratio0.5) for round_idx in range(5): # 剪枝并 rewind 到初始权重 apply_mask(student_model, mask) rewind_to_init(student_model, init_state, mask) # 训练稀疏模型 train_with_mask(student_model, mask, train_loader, optimizer, epochs2) # 评估并决定是否进一步增加稀疏度 eval_acc evaluate(student_model, valid_loader) print(fround {round_idx} acc: {eval_acc:.4f})实际实验中稀疏度通常从 0.1 开始逐步提升到 0.5、0.7、0.9观察精度下降曲线。如果 90% 稀疏度下精度下降在可接受范围内那这个稀疏子网络就可以进入部署流程。6. 常见问题与排查清单6.1 常见问题表格问题现象常见原因解决思路iPET 迭代后验证集效果下降伪标注噪声过大调高置信度阈值、增加 Teacher 数量、每轮后早停伪标注几乎全部是某一类Teacher 偏差严重或数据不平衡检查标注数据分布给低置信度样本做类别均衡剪枝后精度骤降初始稀疏度过高或 LayerNorm/bias 被误剪降低稀疏度、禁止剪 LayerNorm 和 bias、尝试 rewinding 到早期 checkpoint训练 loss 为 NaN学习率过大或网络权重初始化异常降低学习率、添加梯度裁剪、检查输入文本是否为空tokenizer输出空列表模型词表不包含孟加拉字符换用支持孟加拉语的专用模型显存不足batch size 过大减小 batch size、开启 gradient checkpointing 或使用混合精度6.2 排查步骤建议如果 iPET 效果不理想按以下顺序排查先在小验证集上检查 Teacher 单模型精度确认基础模型没有选错。输出部分伪标注样本人工检查标签是否正确。统计伪标注类别分布确认没有全部集中到某个类别。对比 Student 在原始训练集和增强训练集上的 loss判断是否过拟合噪声。如果问题依旧降低迭代轮数或阈值。如果剪枝效果不理想先确认 mask 是否正确应用到了梯度上。检查参数初始权重保存的时机必须在第一次训练前保存。尝试把 rewind 位置从“初始化”改成“训练早期第 1 个 epoch 后的状态”这对应 Lottery Ticket 的 late rewinding 技巧。评估时不要只报告精度还要记录稀疏度和实际 FLOPs。6.3 一个容易踩的坑Teacher 和 Student 共用验证集很多人在做 iPET 时会把无标注数据的一小部分拿去当验证集结果过滤伪标注时看到了一部分和验证集重叠的文本导致评估分数虚高。你应该保证Teacher、Student 的验证集必须来自人工标注数据不能包含任何参与伪标注的无标注样本。7. 工程实践与最佳实践7.1 数据管理标注数据单独存放禁止把伪标注数据直接覆盖原始标注文件。用版本号管理扩充数据集方便复现实验。记录每次 iPET 迭代的过滤阈值、Teacher 数量、伪标注样本数。7.2 模型训练配置固定随机种子包括 Python、NumPy、PyTorch 和 HuggingFace 的随机源。使用transformers的TrainingArguments时设置evaluation_strategyepoch和load_best_model_at_endTrue。使用混合精度训练可以显著减少显存占用孟加拉语长文本数据尤其受益。7.3 剪枝工程细节在完整训练之前先做 2 到 3 轮 warmup再保存初始权重这种 late rewinding 比严格 reset 到随机初始化更稳定。不要对 embedding 矩阵做高比例剪枝因为孟加拉语词表覆盖本来就有限过度剪枝会把词向量变成零向量。每轮剪枝后记录验证集精度绘制“稀疏度-精度”曲线用于选择部署阈值。生产环境部署时可以把 mask 和稀疏权重导出成 ONNX用稀疏矩阵格式存储进一步减少模型体积。7.4 安全与合规孟加拉语语料可能包含网络爬虫文本使用前要做隐私和敏感内容过滤。如果业务涉及用户数据确保实验数据已脱敏符合数据使用授权要求。不要在未授权环境下使用生产数据库数据做实验。7.5 评价指标选择少样本分类任务不要只看准确率建议同时报告Macro F1类别不平衡下更稳定。混淆矩阵观察具体类别错误模式。置信度校准曲线评估模型是否过度自信。稀疏模型相对稠密模型的内存节省比例和推理加速比。如果最终目标是部署还要关注剪枝后的模型在推理框架里的实际加速效果而不只是参数数量。8. 总结与下一步学习路线这篇文章围绕 BnBERT-iPET 的思路拆解了孟加拉语少样本语言建模的两个关键环节iPET 迭代增强和 Lottery Ticket 剪枝。从概念上讲iPET 解决的是“没有足够标注数据”的问题彩票剪枝解决的是“模型太冗余、部署成本高”的问题两者结合后在低资源语言场景里可以用更少的样本和更小的模型完成文本分类等任务。如果你准备动手实践建议按下面路线推进先在英语或中文公开数据集上复现 iPET 流程确保对 Teacher 集成和置信度过滤逻辑熟悉。再换成孟加拉语数据集先用bert-base-multilingual-cased跑通流程再尝试专用孟加拉语模型。加入彩票剪枝从 50% 稀疏度开始逐步增大比例找到精度和稀疏度的平衡点。有条件的话把模型导出成 ONNX 或使用 HuggingFace Optimum 做量化推理对比加速效果。在真实项目中最值得重视的风险是伪标注错误累积和过度剪枝导致的精度退化。建议在每次 iPET 迭代后都做强校验用一个小型人工标注验证集做闸门只有验证集效果不降才继续迭代。如果你正在做孟加拉语或其他低资源语言的 NLP 任务可以把这篇文章当作一条路线参考结合自己的数据情况调整细节。遇到具体问题欢迎在评论区交流。