最近做了几个大模型微调的项目,从BERT到GPT-2,从文本分类到文本生成,踩了无数的坑。
最开始以为,大模型微调就是"调包侠",用Hugging Face的Transformers库,加载预训练模型,喂数据,训练,完事。但真正做起来才发现,坑太多了:数据格式不对、模型不收敛、显存溢出、生成效果差、评估指标看不懂……
有好几次,为了调一个bug,熬到凌晨两三点。本文分享这些踩坑经历,包括数据处理、模型选择、训练技巧、性能优化、评估方法等方面的问题和解决方案。希望能帮你少熬夜,少踩坑。
一、数据处理的坑
数据是微调的基础,数据处理不好,后面全白搭。
坑一:数据格式不对
最开始,我用自己的CSV数据训练,结果模型不收敛。
查了半天,发现是数据格式的问题。Hugging Face的数据集,要求特定的格式:
- 文本分类:
text和label字段 - 文本生成:
text字段,或者input_ids和labels - 问答:
question、context、answers字段
我用的CSV,字段名是content和category,和预期的不一样。虽然代码里做了映射,但tokenizer的时候出了问题。
解决方法:
- 用
datasets库加载数据,统一格式 - 仔细看文档,确认每个任务需要的字段名
- 打印几条数据,确认格式正确
坑二:数据清洗不彻底
我的训练数据是从网上爬的,里面有很多脏数据:
- HTML标签没有去掉
- 有重复数据
- 有乱码和特殊字符
- 标签有错误
这些脏数据,导致模型学不到真正的规律,效果很差。
解决方法:
- 用BeautifulSoup去掉HTML标签
- 用dedupe或简单的哈希去重
- 过滤掉乱码和异常字符
- 人工抽检一部分数据,修正标签
- 数据清洗后,再训练,效果提升明显
坑三:数据分布不均
文本分类任务,我的数据分布很不均匀:有的类别有几万条,有的类别只有几百条。
模型训练后,对多数类的效果很好,对少数类的效果很差,几乎全预测成多数类。
解决方法:
- 过采样:少数类重复采样
- 欠采样:多数类随机采样
- 类别权重:损失函数中给少数类更高的权重
- 数据增强:对少数类做同义词替换、回译等增强
我最后用了类别权重 + 数据增强,少数类的F1提升了20%。
坑四:训练集和测试集泄漏
有一次,我发现模型在测试集上的效果特别好,但在真实数据上效果很差。
查了半天,发现是训练集和测试集有重叠。我在划分数据集的时候,用了随机划分,但数据中有很多相似的文本,被分到了不同的集合里。
解决方法:
- 按某个唯一标识划分(比如用户ID、时间),而不是随机划分
- 去重后再划分
- 检查训练集和测试集的相似度
二、模型选择的坑
坑五:模型太大,显存不够
最开始,我想用GPT-2 XL做微调,结果一运行就OOM(显存溢出)。
我的GPU是RTX 3090,24G显存。GPT-2 XL有1.5B参数,光是加载模型就要6G(fp32),加上梯度和优化器状态,24G根本不够。
解决方法:
- 用更小的模型:GPT-2(124M)、GPT-2 Medium(355M)
- 用混合精度训练(fp16),显存减半
- 用梯度累积,减小batch size
- 用LoRA或Adapter,只训练少量参数
我最后用了GPT-2 Medium + fp16 + 梯度累积,24G显存刚好够用。
坑六:模型和任务不匹配
有一次,我用BERT做文本生成,效果很差。
后来才明白,BERT是双向编码器,适合理解类任务(分类、命名实体识别、问答),不适合生成类任务。生成类任务要用自回归模型(GPT-2、T5等)。
解决方法:
- 理解任务类型:理解类用BERT/RoBERTa,生成类用GPT-2/T5
- 理解模型架构:Encoder-only、Decoder-only、Encoder-Decoder
- 看模型的论文和文档,确认适用场景
坑七:预训练模型和领域不匹配
我用通用的BERT做医疗领域的文本分类,效果一般。
因为通用BERT是在通用语料上预训练的,对医疗领域的术语和表达不熟悉。
解决方法:
- 用领域预训练模型:医疗领域用BioBERT、PubMedBERT,法律领域用Legal-BERT
- 继续预训练:在领域语料上继续预训练,然后再微调
- 领域自适应微调:先用领域无标注数据做继续预训练,再用标注数据微调
我最后用了PubMedBERT,医疗文本分类的F1提升了8%。
三、训练技巧的坑
坑八:学习率太大,模型不收敛
最开始微调,我用了预训练时的学习率(1e-4),结果损失不下降,反而上升。
后来才知道,微调的学习率要比预训练小很多。因为预训练模型已经有了很好的参数,微调只需要稍微调整,学习率太大会破坏预训练的知识。
解决方法:
- 微调的学习率一般用2e-5到5e-5
- 用学习率预热(warmup),前几步学习率从小到大
- 用学习率衰减,训练过程中学习率逐渐减小
- 不同层用不同的学习率,底层小,顶层大
我最后用了3e-5的学习率,warmup比例10%,线性衰减,模型正常收敛了。
坑九:batch size太小,效果差
因为显存不够,我把batch size设成了2,结果模型效果很差,训练也不稳定。
batch size太小,梯度估计不准确,训练波动大。尤其是有Batch Normalization的模型,batch size太小会影响BN的统计量。
解决方法:
- 用梯度累积:模拟大batch size。比如实际batch size=2,累积8步,等效batch size=16
- 用Layer Normalization代替Batch Normalization(Transformer模型一般用LN)
- 调整学习率,batch size小的时候学习率也要小一些
坑十:训练轮数太多,过拟合
有一次,我训练了20个epoch,发现训练集损失一直在降,但验证集损失先降后升。这是过拟合了。
大模型微调,一般不需要太多epoch。因为模型已经预训练过了,微调只是适配下游任务,1-5个epoch就够了。
解决方法:
- 用早停(Early Stopping):验证集损失连续N步不下降就停止
- 保存最佳模型:只保存验证集效果最好的模型
- 用dropout和weight decay正则化
- 一般微调2-3个epoch就够了
坑十一:冻结层不对
最开始,我冻结了BERT的所有层,只训练分类头,结果效果不好。
后来全部解冻,效果好了很多,但训练慢了,也容易过拟合。
解决方法:
- 数据少的时候:冻结大部分层,只训练顶层和分类头
- 数据多的时候:全部解冻,用小学习率微调
- 分层学习率:底层学习率小,顶层学习率大
- 用LoRA/Adapter:只训练少量参数,效果接近全参数微调
四、性能优化的坑
坑十二:训练速度慢
最开始,训练一个epoch要十几个小时,太慢了。
排查后发现,数据处理是瓶颈。我在CPU上做tokenize,速度很慢。
解决方法:
- 用
datasets库的map函数,多进程处理数据 - 预处理后缓存到磁盘,避免重复处理
- 用GPU做tokenize(如果支持)
- 用DataLoader的
num_workers,多进程加载数据 - 用混合精度训练(fp16),计算更快
优化后,一个epoch从十几个小时降到了两个小时。
坑十三:显存占用越来越大
训练过程中,显存占用越来越大,最后OOM。
查了半天,发现是梯度累积的时候,没有正确清零梯度。还有一个原因是,我在循环里累积了tensor,没有释放。
解决方法:
- 每步结束后调用
optimizer.zero_grad() - 不要在循环里累积tensor,用
.item()取标量 - 用
torch.cuda.empty_cache()清理缓存 - 检查是否有内存泄漏
坑十四:多GPU训练问题
我尝试用多GPU训练,结果遇到了很多问题:
- 数据并行(DataParallel)速度提升不明显
- 分布式训练(DistributedDataParallel)配置复杂
- 多GPU下的学习率和batch size调整
解决方法:
- 用Accelerate库,简化多GPU训练
- 学习率和batch size要按GPU数量线性缩放
- 用DDP而不是DP,DDP更快更稳定
- 先在单GPU上调通,再上多GPU
五、文本生成的坑
文本生成比文本分类坑更多。
坑十五:生成的文本重复
用GPT-2微调后,生成的文本经常重复,同一句话反复说。
这是自回归模型的常见问题,尤其是在小数据集上微调的时候。
解决方法:
- 用
norepeatngram_size参数,禁止重复的n-gram - 用
repetition_penalty参数,惩罚重复的token - 增加训练数据,数据越多重复越少
- 用更好的解码策略:beam search、top-k、top-p
坑十六:生成的文本不通顺
生成的文本,语法错误多,不通顺。
原因可能是:
- 训练数据质量差
- 训练轮数不够
- 模型太小
- 解码策略不对
解决方法:
- 提高训练数据质量
- 增加训练轮数,但不要过拟合
- 用更大的模型
- 调整解码参数:temperature、topk、topp
坑十七:生成的文本和输入不相关
做条件生成的时候,生成的文本和输入不相关,模型在"自由发挥"。
原因可能是:
- 训练数据的输入输出对应关系不好
- 模型没有学会条件生成
- 解码时没有正确传入prompt
解决方法:
- 检查训练数据,确保输入输出对应
- 用Encoder-Decoder模型(T5)做条件生成,比GPT-2更合适
- 解码时正确构造prompt,用特殊符号分隔输入和输出
六、评估方法的坑
坑十八:只看准确率
文本分类任务,我最开始只看准确率,结果发现准确率90%,但模型根本没用。
因为数据分布不均,模型全预测成多数类,准确率就有90%。但少数类完全识别不出来。
解决方法:
- 看混淆矩阵,了解每个类别的表现
- 看精确率、召回率、F1,尤其是少数类的
- 看宏平均(macro-F1),而不是微平均(micro-F1)
- 根据业务需求,选择合适的评估指标
坑十九:生成文本的评估
文本生成的评估,比分类难多了。
最开始我用BLEU分数,但发现BLEU高的文本,人读起来不一定好。BLEU低的文本,可能反而更通顺。
解决方法:
- 人工评估:找人读生成的文本,打分
- 用多个指标:BLEU、ROUGE、METEOR、BERTScore
- 做人工对比实验:把模型生成的和人工写的混在一起,让人判断
- 关注实际使用效果,而不只是指标
坑二十:测试集和真实场景不一致
模型在测试集上效果很好,但上线后效果很差。
原因是测试集和真实场景的数据分布不一样。测试集是干净的、规范的,真实数据是嘈杂的、多样的。
解决方法:
- 测试集要尽量模拟真实场景
- 用真实数据做A/B测试
- 上线后持续监控,收集bad case,迭代优化
七、部署的坑
坑二十一:模型太大,部署困难
微调后的模型,几百MB甚至几个GB,部署的时候加载慢,占用内存大。
解决方法:
- 模型量化:INT8量化,模型体积减小75%
- 模型蒸馏:用大模型教小模型
- 用ONNX或TorchScript优化推理
- 用专门的推理框架:ONNX Runtime、TensorRT、FasterTransformer
坑二十二:推理速度慢
模型上线后,推理速度慢,用户等待时间长。
解决方法:
- 用更小的模型
- 用批量推理,一次处理多个请求
- 用GPU推理,比CPU快很多
- 用KV Cache优化生成速度
- 限制生成长度,避免生成太长
八、我的经验总结
踩了这么多坑,总结一些经验。
1. 先跑通baseline
不要一开始就追求最优效果。先用最简单的方式跑通baseline,确认数据、模型、训练流程都没问题,再慢慢优化。
2. 小数据先验证
先用一小部分数据验证流程,确认没问题了,再用全量数据训练。避免全量数据训练了几个小时,最后发现数据格式错了。
3. 记录实验
每次实验都记录:用了什么模型、什么参数、什么数据、效果如何。用WandB或TensorBoard记录实验,方便对比和复现。
4. 多看文档和教程
Hugging Face的文档很详细,遇到问题先查文档。GitHub的issue里也有很多解决方案。不要自己瞎琢磨,先看看别人有没有遇到过同样的问题。
5. 不要迷信大模型
大模型不是万能的。有些任务,小模型就够了,甚至传统机器学习方法效果更好。根据任务选择合适的模型,不要盲目追求大。
九、写在最后
大模型微调,看起来简单,做起来坑很多。
从数据处理到模型选择,从训练技巧到性能优化,从评估方法到部署上线,每一步都可能出问题。但踩坑的过程,也是学习的过程。每解决一个问题,对大模型的理解就深了一层。
2022年了,大模型越来越火,微调大模型成为很多人的必备技能。但不要被"调包侠"的表象迷惑,真正做好微调,需要理解数据、理解模型、理解训练过程。
最后,用一句话总结:"大模型微调的坑,90%来自数据和细节。把数据处理好,把细节做到位,效果自然不会差。"
愿大家的大模型微调之路,少踩坑,少熬夜,都能训出好模型。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录