这是机器学习基本概念系列的第二篇。上一篇把模型、特征、训练、推理这些地基性概念过了一遍,这一篇我打算聚焦到AI大模型实战中最容易出问题的几个环节:数据划分、过拟合判断、损失函数与优化器、评估指标、超参数调参。这些概念看起来零散,实际是同一个链条上的零件:数据怎么给、模型怎么学、学了之后怎么衡量、效果不好又怎么调整。把这四个环节串起来,很多报错、训练不收敛、指标虚高的问题,都能当场定位。
1. 数据是地基:训练集、验证集、测试集怎么划才不坑
1.1 三种数据集各自干什么,用错会有什么后果
在机器学习实战里,最容易被轻视但又最致命的就是数据划分。训练集用来更新模型参数,验证集用来做模型选择和超参数调整,测试集用来做最终评估。很多初学者以为验证集和测试集是一回事,其实差别很大。验证集参与调参,所以它的结果已经“被看过”了,不能作为最终能力的证明。测试集只有整个流程快结束时才碰一次,它才是衡量模型真实泛化能力的尺子。
打个比方,训练集是平时做练习,验证集是模拟考,测试集才是最终考试。你可以在模拟考之后修正自己的复习方向,但不能把真题答案背下来去应付正式考试。如果拿测试集反复调参,等于把真题泄露给了自己,最后分数虚高,一到真实场景就崩。
1.2 划分策略与实战比例,不能只看“三七开”
很多教程会告诉你默认按70%训练、15%验证、15%测试来分,但这个比例不是万能公式。当数据量很大的时候,比如有100万条,验证和测试各留几万条就足够了,没必要按百分比留出几十万条。数据量小的时候,比如只有几千条,按比例分会导致验证集太小、波动很大,就可以考虑用K折交叉验证。
对于分类任务,划分时要尽量保持各类别比例一致,也就是分层抽样。如果不做分层,少数类可能全部跑进测试集,模型训练时根本没见过那个类,测试指标会异常低。文本和时间序列数据还要注意另一种情况:不能随机打乱后直接划分。比如新闻文本,同一个事件早期的新闻都可能进了训练集,后面跟进报道成了测试集,如果主题相近但时间上有前后关系,随机切分会让测试集和训练集“串题”。这类数据更适合按时间顺序切分。
实操代码很简单,但细节要对。先划分,再做任何预处理。
from sklearn.model_selection import train_test_split # stratified:分类任务必须带上 train_texts, temp_texts, train_labels, temp_labels = train_test_split( texts, labels, test_size=0.2, stratify=labels, random_state=42 ) val_texts, test_texts, val_labels, test_labels = train_test_split( temp_texts, temp_labels, test_size=0.5, stratify=temp_labels, random_state=42 )先分出20%作为验证和测试,再对这部分一切为二。这里的关键点是random_state固定,保证实验可复现。如果你在调参过程中重置了随机种子,或者换了划分方式,那每次实验都在不同数据上做比较,结果没有意义。
1.3 数据泄露这个大坑,很多人栽在这
最隐蔽的问题不是数据划错了,而是预处理过程中把验证集、测试集的信息带进了训练。我举一个最常见的例子:用全量数据的均值或中位数填充缺失值。填充前你应该只统计训练集的值,再用这个值去填充验证集和测试集。否则测试集的“答案”已经参与过训练时的统计计算,模型在测试集上的表现会被高估。
文本任务里还有一种泄露:数据增强。比如你先把所有数据做了同义词替换、回译,然后再划分训练集和验证集,那么验证集里很可能存在和训练集重复或高度相似的样本。这就像模拟考里混了原题,最后模型在验证集上的分数高,不代表它在新数据上也能这么好。
我自己就吃过这个亏。某个意图分类任务,最开始把整份数据的词频统计当成特征,先拟合了TF-IDF再划分,结果验证集F1到了0.9,实际上线只有0.7。后来改成先拆分数据、再在训练集上fit特征器,验证集指标才和线上表现一致起来。原则很简单:所有基于数据的统计量,都只能在训练集上计算。
2. 过拟合和欠拟合:模型能力的天平往哪偏
2.1 一张图看懂偏差和方差
过拟合和欠拟合,是所有监督学习都会面对的一对矛盾。用打靶来解释最直观:低偏差意味着瞄准点准,高方差的着弹点分散;欠拟合是高偏差,瞄都没瞄对,怎么打都偏;过拟合是高方差,训练时能记住每一个样本的落点,但换个靶位就全乱套。
更严谨地说,模型误差可以被拆成偏差、方差和不可约噪声三部分。偏差衡量模型预测值的平均水平与真实值的差距,方差衡量模型对训练数据变化的敏感度。模型越复杂,偏差通常越低,但方差会升高;模型太简单,方差低但偏差高。你要找的是拐点,而不是某一个极端。
在大模型场景里,很多人以为参数量足够大就不会欠拟合。实际上,如果你用小学习率只训一两个epoch,模型可能压根没学会,训练集和验证集loss都降不下去,这就是欠拟合。这时候还硬调正则化参数,只会雪上加霜。
2.2 大模型微调时过拟合的现场表现
大模型微调时最典型的过拟合,是训练loss低到离谱,但验证loss降到某个点后开始反弹。我见过一个小样本文本分类任务,训练集只有几千条,全参数微调后训练准确率接近100%,验证F1却只有0.6左右。更明显的信号是,模型生成的回答开始“背答案”,比如在特定问法下会原样吐出训练数据里的句子。
造成这种状况的直接原因是:模型参数量远大于任务数据量,它有足够容量去记住训练样本,而不是学习背后的一般规律。微调阶段如果训练轮数太多,或者学习率不合适,很容易在把训练loss压下去的同时伤害泛化能力。
判断过拟合不能只看训练集指标。我在第一次做类似任务时,看到训练集loss一直下降还挺开心,直到验证集指标开始波动才意识到问题。正确做法是同时监控训练集和验证集的loss曲线,如果两者之间的gap持续扩大,就该考虑干预了。
2.3 用正则化、早停和数据增强把模型拉回来
解决过拟合,不是只有“少训练几轮”一个手段。正则化、早停和数据增强要配套用。
L2正则化是深度学习中最常见的,它通过在损失函数里加权重平方和约束,让模型权重别那么大。大模型里的AdamW优化器已经内置了解耦的权重衰减,通常设置weight_decay在0.01到0.1之间,微调时一般一个量级即可。
Dropout是另一个基本功。它随机让一部分神经元失活,让模型不至于过度依赖某些特征。但要注意,大模型原模型大多带Dropout并做了预训练,微调时如果任务数据少,可以考虑稍微提高dropout率来增强鲁棒性。数据增强则根据任务类型选择:文本可以用同义词替换、回译、文本噪声注入;图像就是旋转、裁剪、颜色抖动。注意增强后的数据不能和验证集、测试集产生重复。
早停是成本最低的干预。设定一个patience值,比如连续3个epoch验证集指标没有提升,就停止训练并回滚到最优checkpoint。但别在训练一开始就盯着验证集反复决定是否停止,要在normal训练曲线稳定后再观察。实际组合建议是:LoRA + 小学习率 + 早停 + 适度Dropout,这套组合在大多数中小规模微调任务里都够用。
3. 损失函数与优化器:模型“学习”的刹车和油门
3.1 交叉熵、MSE、困惑度,任务不同损失不同
损失函数告诉优化器“当前预测离目标还有多远”。分类任务最常用交叉熵,它衡量预测概率分布与真实分布之间的差距,梯度更新更稳定,而且和softmax天然搭配。回归任务则常用均方误差,因为要惩罚大误差;如果数据里有很多离群点,MAE(平均绝对误差)更抗噪。
语言模型的核心损失也是交叉熵,不过对象是“下一个token”。预训练时模型对每一个位置预测下一个词的概率,计算平均交叉熵,然后取指数,就得到困惑度(perplexity)。困惑度越低,说明模型对语料中下一个词的预测越自信,但要注意,这是一个内部指标,不代表生成质量一定好。因为模型可能学到的是一套流利但平淡的表达模式。
微调时不要默认沿用预训练的损失。如果任务是句子分类,需要在预训练模型上加分类头,再用交叉熵;如果任务是抽取式问答,损失是Span起始和结束位置的交叉熵之和。选错损失函数,模型很难收敛。
3.2 从SGD到AdamW,优化器怎么选
优化器决定参数沿着梯度怎么走。传统SGD动量为0.9,训练稳定但收敛较慢,需要精心设置学习率。Adam把一阶动量和二阶动量结合,能自适应调节每一步的学习步长,在很多任务上直接可用。
大模型领域更常用的是AdamW,它把权重衰减从梯度更新中剥离出来,避免了L2正则和自适应学习率之间互相干扰。实际使用中,推荐先试AdamW,学习率从3e-5起,beta1=0.9,beta2默认0.999或0.98,epsilon=1e-8。如果是亿级参数量的全参数微调,显存允许的前提下也可以用SGD加warmup,训练曲线会更平滑。
选择优化器不是越新越好。LoRA微调时,我习惯用AdamW;从头训练一个较小的模型时,SGD加momentum也常有意想不到的稳定性。关键是不要在整个实验过程中随意更换优化器,否则很难比较调参效果。
3.3 学习率策略:大模型调参最容易忽略的地方
学习率是超参数里性价比最高的一档。学习率太大,loss震荡甚至发散;太小,模型根本走不动。大模型训练还需要配合调度策略,最常用的是先warmup再衰减。warmup阶段从一个很小的学习率线性增加到目标值,目的是避免训练初期大幅度参数更新带来的不稳定性。之后可以线性衰减到0,也可以走余弦退火路径。
下面是一段常见的调度伪代码,方便理解其结构:
import math max_lr = 3e-5 total_steps = 10000 warmup_steps = 500 def lr_schedule(step): if step < warmup_steps: return max_lr * step / warmup_steps progress = (step - warmup_steps) / (total_steps - warmup_steps) return 0.5 * max_lr * (1 + math.cos(math.pi * progress))实操里要注意:增大batch size时,梯度噪声变小,学习率通常也应该相应调大,而不是保持不动。另外,warmup步数占总训练步数的比例,在小数据微调时可以放到5%到10%,在预训练大型模型时通常控制在1%左右。如果你发现loss前期飞了,优先检查warmup和学习率,而不是怀疑模型结构。
4. 评估指标:模型好不好,得用对尺子量
4.1 分类任务:准确率、精确率、召回率和F1
评估指标选错,比不调参更可怕,因为你会被一个“假好”的分数带偏。准确率是最直观的指标,但它有个大前提:类别分布大致均衡。假如99%是负样本,模型全预测负样本,准确率99%,却没有一点实际价值。
精确率衡量“预测为正的样本里有多少真为正”,召回率衡量“真实为正的样本里有多少被找出来”。F1是两者的调和平均。它们之间的取舍要看业务:垃圾邮件识别更看重精确率,别把正常邮件误杀;疾病筛查更看重召回率,宁可多查一次也别漏诊。
多分类场景还要区分宏平均和微平均。宏平均先算每个类别的指标再取平均,更关心少数类;微平均把样本数汇总后再算,更受多数类影响。对大模型分类任务,我建议两个都看,同时把每一类的召回率单独打出来,才能发现少数类是不是被模型无视了。
4.2 生成任务:困惑度和BLEU在AI大模型里的实际意义
生成任务不能只盯着困惑度看。困惑度衡量的是模型对文本概率分布的拟合程度,但对于大模型生成的开放性内容,低困惑度不一定对应高可读性或高事实正确性。它更多是训练阶段的监控指标,用来判断模型有没有收敛。
BLEU是机器翻译和文本生成里常见的指标,它统计生成文本和参考文本之间的n-gram重叠度。优点是计算快,缺点是几乎不管语序,也不理解语义。同一个意思换一种表达,BLEU可能很低,但人读起来完全没问题。所以在对话生成、摘要生成这类开放任务里,BLEU只能当参考,不能当唯一标准。
更可靠的组合是:困惑度跟踪训练状态,BLEU或ROUGE做自动化对比,再抽样一批样本人工评估。不要只用一个指标判断大模型的好坏。我在实际评估某个摘要模型时,BLEU分数不低,但抽样发现它经常把新闻里最重要的数字丢掉,这就是指标和业务目标脱节。
4.3 指标选择的常见误区与实战建议
第一误区是拿验证集当测试集反复刷。有人会对着验证集调两三天参数,每次看到分数提升就继续改,最后验证集分数很高,一到测试集就崩。正确做法是给验证集设一个“使用预算”,调参阶段只看它,最终只碰一次测试集。
第二误区是只报均分不报方差。模型在随机种子变化时表现会波动,尤其是小数据集。建议固定随机种子后,重复三次实验取平均值和标准差,把所有结果列进表格。否则你看到的提升可能只是随机波动。
第三误区是正负样本定义不固定。若实验代码里“正类”标签因为某一轮改动发生变化,前后指标不可比。所有实验开始前先把评测脚本固化下来,和锁数据划分一样重要。最好把评测代码和训练代码放在同一个版本控制里,任何改动都有记录。
5. 超参数调参与交叉验证:大模型实战的高成本博弈
5.1 哪些超参数值得调,优先级怎么排
超参数调参在传统机器学习里像超市选品,可以慢慢逛,但在大模型训练里更像是限时抢购,一次训练动辄卡上几十卡时。所以优先级特别重要。我的经验是先调学习率,再调训练轮数,然后是batch size,最后才碰正则化和模型结构。
学习率直接决定参数更新步长,它对收敛速度和最终效果的影响最大。训练轮数和学习率相互关联:学习率大,模型可能几轮就过拟合;学习率小,可能要更多轮才能收敛。batch size影响梯度噪声和显存占用,改它时要注意同步调整学习率。正则化和模型结构如果没有明显过拟合问题,不需要一开始就折腾。
另外,微调大模型时先用小数据子集做实验,把学习率和epoch快速扫一遍,再拿完整数据跑最终版本。这个小数据子集要独立抽样,不能从验证集里抽,否则一样是泄露。
5.2 网格搜索、随机搜索和贝叶斯调参的取舍
网格搜索在参数空间里穷举所有组合,适合参数少、每个参数候选值少的情况。一旦扩展到四个维度以上,组合数会爆炸,纯属浪费时间。随机搜索和高斯过程贝叶斯优化可以在高维空间里用更少的尝试次数逼近不错的结果。随机搜索的思路是先确定每个参数的分布,然后随机采样,不同维度之间相互独立,不容易被网格边界误导。
贝叶斯优化则把历史尝试结果保存下来,用一个代理模型预测哪些参数更可能带来高分。实际操作时可以直接用现成的调参工具,定义目标函数,把训练跑完返回验证集指标即可。大模型场景下,我会先用随机搜索覆盖一个大范围,找到有希望的区域后,再用贝叶斯优化做局部精调。
调参不只要看最终分数,还要看loss曲线和收敛行为。有时候一组超参数分数高但训练过程明显震荡,换一个seed可能就不稳定,这样的结果不能直接采用。
5.3 K折交叉验证在大模型场景下的变通做法
传统K折交叉验证把训练数据分成K份,每次取一份当验证集、其余当训练集,循环K次。这样可以减少单次划分带来的随机性,对小样本特别有价值。但在大模型场景,直接做5折全参数微调,训练成本直接乘5,很多人承受不起。
变通做法有三种。第一,用Hold-out单次划分做日常调参,只在最终阶段用一次3折交叉验证做稳定性校验。第二,把K折用在LoRA这种参数高效微调上,因为可训练参数少,单轮训练快很多。第三,如果数据本身就够大,单次划分的验证集足够稳定,没必要强行上交叉验证。
我不建议在每轮调参时都做K折。调参本来就是在探索空间,探索时用快而糙的评估,确定最终方案后再用更严谨的评估确认,这才是大模型实战里的合理策略。我在小样本任务上用过5折,效果确实比单次划分稳定,但前提是每折训练用LoRA、时间还能接受。
6. 我踩过的坑:机器学习基本概念的实战复盘
6.1 一个微调任务从过拟合到收敛的排查过程
之前接手一个文本意图分类任务,样本大概一万条,类别分布不太均衡。第一版全参数微调一个通用预训练模型,跑了10个epoch,训练loss一路降到0.01,但验证集F1只有0.58。当时第一反应是数据太乱,后来把训练集和验证集指标放一起看,才发现是过拟合。
我把优化策略改成四件事:第一,用LoRA替代全参数微调,可训练参数少了一个多量级;第二,把学习率从3e-5降到1e-5,训练轮数限制到6轮;第三,加了Dropout并把早停patience设成2;第四,把原始数据做了回译增强,增强数据只扩充训练集。调整后验证集F1从0.58升到0.79,而且训练loss和验证loss的gap明显收窄。
这个经历说明,遇到指标不理想时别急着换模型。先确认数据划分和预处理是否规范,再看是过拟合还是欠拟合,最后才调整优化策略和超参数。每一步都用实验表格记录,才能看清到底哪一项起了作用。
6.2 关于数据、指标和超参数的几条独家心得
我个人在实际操作中的体会是,整个机器学习项目里,最值得投入时间的是把数据划分、评测脚本和随机种子先固定下来。这件事做得好,后续所有实验都建立在同一套基准上,否则你会在一堆不可复现的结果里反复怀疑人生。
分享一个实用小技巧:在做实验前,把验证集的预测结果和错误样本都dump下来,每次调参后不仅看分数,还能直接检查是哪些样本一直被分错。这个习惯能帮你很快发现数据标注问题、类别噪声和重复样本,比单纯调学习率高效得多。
另外一个建议是在项目文档里维护一张超参实验表。表格至少包含:日期、数据版本、学习率、batch size、轮数、验证指标、测试指标、备注。大模型训练成本高,每一组实验都值得被记录。吃过几次亏后你会发现,这张表才是项目真正积累下来的资产。
最后留一句给后来者:不要迷信大模型自带的能力。它只是把机器学习基础概念放大到了极致,数据泄露、过拟合、指标误读这些老问题,在更大参数下只会以更隐蔽的方式出现。把地基补扎实,AI大模型实战才不会翻车。
这就是我在这条路上横跳几年后最想强调的部分,写出来希望对正在踩坑的人有点用。