3.2 目标函数 (Objective Functions) 本节摘要:目标函数是模型的"北极星",通过 objective 参数指定,决定了模型在训练时到底要把什么量最小化。它和 metric 不是一回事:objective 是真正被优化的目标,metric 只是训练日志里观察用的仪表。本节按回归、二分类、多分类、排序四类任务逐一展开,重点对比 L1 与 L2、Huber 与 Fair、交叉熵与 Focal Loss、Softmax 多分类与 One-vs-All 之间的取舍,说明当数据有异常值、类别不平衡或需要预测分位数时,该把默认目标函数换成什么。 本节导航 阅读完本节,你应当能够: 说清 objective 与 metric 的本质区别,避免把"观察指标"误当成"优化目标"。
本节摘要:目标函数是模型的"北极星",通过 objective 参数指定,决定了模型在训练时到底要把什么量最小化。它和 metric 不是一回事:objective 是真正被优化的目标,metric 只是训练日志里观察用的仪表。本节按回归、二分类、多分类、排序四类任务逐一展开,重点对比 L1 与 L2、Huber 与 Fair、交叉熵与 Focal Loss、Softmax 多分类与 One-vs-All 之间的取舍,说明当数据有异常值、类别不平衡或需要预测分位数时,该把默认目标函数换成什么。
阅读完本节,你应当能够:
在梯度提升里,每一轮训练都做同一件事:计算当前预测与真实值之间的"差距",再训练一棵新树去补这个差距。这里"差距"怎么算,就是目标函数说了算。它决定了模型的优化方向,也决定了最终学出来的模型对什么更宽容、对什么更敏感。
有个误区值得一开始就点破:目标函数和评估指标不是一回事。objective 是训练时真正被梯度优化的量,metric 只是你贴在日志上看的仪表。你可以一边用 binary 作为目标函数、一边用 auc 作为监控指标,两者完全可以不同。但反过来,如果你只盯着 metric 换着看,却把 objective 填错,那模型等于朝着错误的方向狂奔,仪表再好看也是白搭。
好在大多数时候,objective 不用你从头设计,LightGBM 内置了一套覆盖四大任务的现成目标函数。你真正要做的判断,往往只集中在一个问题上:我的数据有什么"脾气",默认的那个目标函数还吃不吃得消。
回归默认的 objective 是 regression_l2,也就是均方误差。它算的是"预测值与真实值之差的平方",光滑可导、优化方便,所以成了默认。但平方这个动作会放大误差:真实值 100,预测 110,误差 10,平方后是 100;预测 130,误差 30,平方后变成 900。误差越大,被放得越狠。于是只要数据里混进几个离谱的异常值,模型就会为了迁就这几个点而整体跑偏。
这正是 L1 存在的理由。regression_l1 算的是误差的绝对值,不平方,异常值不会被几何级放大,模型更稳。代价是它在零点处不可导,收敛偶尔会慢一点,而且 L1 优化出来的结果更贴近中位数而非均值。一句话总结:数据干净、追求均值意义上的最优,选 L2;数据里有脏点、希望模型不被带偏,选 L1。
Huber 则想做"中间派":误差小的时候按平方算,享受 L2 的光滑;误差大的时候转成线性,享受 L1 的稳健。中间那个转折点由 huber_delta 参数控制,默认 1.0。Fair 是 Huber 的"远亲",思路类似——误差小时近似平方、误差大时近似线性,靠 fair_c 参数调节稳健程度。这俩适合"既担心异常值、又不想完全放弃小误差下的精度"的场景,属于比 L1 更精致的折中。
再往外走,还有两个针对特殊分布的成员。Quantile 回归解决的是"我不想预测均值,我想预测分位数"的需求,比如预测房价时给出一个"90% 情况下不会超过"的上限,比一个均值更有工程价值,alpha 参数就用来指定分位点。Poisson 回归则面向计数型数据——点击次数、事故起数这类非负整数,它假设目标服从泊松分布,用对数链接把线性预测映射成期望计数。
| 目标函数 | 对异常值 | 优化点 | 典型场景 |
|---|---|---|---|
| regression_l2 | 敏感 | 均值 | 数据干净、目标近似正态的默认选择 |
| regression_l1 | 稳健 | 中位数 | 有异常值、想求稳健预测 |
| huber | 折中 | 均值与中位数之间 | 想兼顾小误差精度与大误差稳健 |
| fair | 折中 | 可调稳健度 | 需要比 Huber 更精细调节的场景 |
| quantile | 稳健 | 指定分位数 | 预测上限、下限、区间 |
| poisson | 视分布而定 | 期望计数 | 点击量、事故数等非负整数 |
二分类的默认目标是 binary,也就是二元交叉熵。它要求模型输出样本属于正类的概率,然后衡量"这个概率离真实标签 0 或 1 有多远"。它有一个漂亮的特性:梯度光滑稳定,而且直接产出概率,做风控、点击率预估这类需要概率的场景特别顺手。
binary 有个容易忽视的盲区——类别不平衡。当正样本只占千分之一时,模型只要把所有样本都预测成负类,损失就已经很低了,它根本没有动力去学那稀少的正类。针对这个痛点,LightGBM 在较新版本里提供了 focal_loss。它的核心是 focal_loss_gamma 参数:gamma 越大,越会压低"已经分对的样本"的损失权重,让模型的注意力集中到"还分不对的难样本"上。gamma 为 0 时它退化成普通交叉熵;调到 1.0、2.0 时,对不平衡数据的改善通常很明显。
多分类的默认目标是 multiclass,用的是 Softmax 加多类交叉熵。Softmax 会把模型对每个类别的原始输出压成一个和为 1 的概率分布,模型要同时面对所有类别竞争。它需要你额外指定 num_class 告诉模型一共有几类。另一种选择是 multiclassova,也就是 One-vs-All:把多分类拆成若干个"这一类 vs 其余所有类"的二分类问题分别训练。类别数特别多、且某些类别之间区分度差异很大时,ova 有时会更稳,代价是训练成本随类别数线性上升。
| 目标函数 | 适用任务 | 关键参数 | 特点 |
|---|---|---|---|
| binary | 二分类 | 无 | 默认选择,输出概率,梯度稳定 |
| focal_loss | 二分类 | focal_loss_gamma | 聚焦难样本,缓解类别不平衡 |
| multiclass | 多分类 | num_class | Softmax 多类竞争,默认选择 |
| multiclassova | 多分类 | num_class | 拆成多个二分类,类别多时更稳 |
前两类目标函数关心的是"预测值准不准",排序任务关心的却是"排在前面的对不对"。搜索引擎、推荐系统里,用户只会看前几条结果,所以你并不需要精确预测每条结果的相关性分数,只需要让"更相关的排在更相关的上面"。
LightGBM 里最经典的排序目标是 lambdarank。它直接对列表整体的排序质量做优化,思想来自 LambdaRank 算法:不逐条看误差,而是看"任意两条结果的相对顺序排错会造成多大的 NDCG 损失",再把梯度按这个损失反传。因此它优化的是 NDCG 这类排序指标本身,而不是某个逐点的回归误差。使用它时,数据要按"查询"分组组织,同一个查询下的所有文档放在一组,还要告诉模型每组的样本数,这样才能在组内正确计算排序梯度。较新的版本还提供了 rank_xendcg 等面向 XENDCG 指标的目标函数,作为 lambdarank 的补充。
除了目标函数本身,排序任务对数据的组织方式还有额外要求。LightGBM 需要知道哪些样本属于同一次查询,才能在组内正确计算排序梯度。通常的做法是把同一个查询下的所有文档放在连续的位置,再用一个分组长度信息告诉模型每组各有多少条;组长度一旦搞错,梯度就会算串,模型学出来的排序关系也是错的。这也是排序任务比普通回归、分类更容易在数据准备阶段翻车的地方——它不是换一个字符串就能跑通,数据形状也得跟着改。
下面这张图把四类任务到目标函数的选择路径串了起来:
内置的目标函数覆盖了绝大多数场景,但总有覆盖不到的时候——比如你要优化一个业务上自造的损失。LightGBM 允许你传入一个自定义目标函数,它接收真实值和预测值,返回一阶梯度和二阶梯度。对梯度提升来说,只要你能算出这两样东西,就能把任何光滑的损失函数接进来。这给了框架很大的弹性,但也要提醒一句:自定义目标函数意味着你要自己保证梯度的正确性,出错了框架不会帮你兜底,排查起来远比换一个内置字符串麻烦。
选目标函数的顺序,我的建议是:先看任务类型确定大类,再看数据脾气决定是否换掉默认值。数据干净就留在默认,有异常值就切 L1 或 Huber,类别不平衡就切 focal_loss,计数数据切 Poisson,要分位数切 Quantile,排序切 lambdarank。绝大多数项目走到这一步就够了,真到了需要自定义目标函数的那一天,你多半已经很清楚自己在做什么。
# 四类任务怎么填 objective,其余差异藏在数据形状与配套参数里 reg = {'objective': 'regression_l2'} bin_clf = {'objective': 'binary'} multi = {'objective': 'multiclass', 'num_class': 3} rank = {'objective': 'lambdarank'}
这四行几乎就是四类任务的开关语句,选定之后,剩下的工作才是围绕它去搭数据、配参数。很多人调参卡壳,其实卡在第一步就把 objective 当成了"随便填填"的选项,结果在错误的优化方向上反复折腾。记住:目标函数是这四类任务里最不该省的一次判断。
⚠️ 常见坑:切换 objective 时忘了同步改 metric,或者反过来只改 metric 不改 objective。前者会让你对着一个和优化目标脱节的数字误判早停时机,后者则根本改变不了模型学什么。两个字符串要一起审视。
💡 关键直觉:目标函数选错,模型练得再久也是在错误的方向上用力。与其纠结"哪个目标函数最强",不如先问一句"我的数据里,异常值、不平衡、分位数需求这三样占了哪一样"——答案往往直接指向正确选项。
目标函数决定了模型"往哪学",可模型学得好不好,最终还得靠评估指标来量。下一节我们就把各任务对应的评估指标串一遍,看看 auc 为什么比准确率更抗类别不平衡、NDCG 又凭什么能评价排序。