1. 为什么搜推要搞“公式融合”而不是纯模型
1.1 公式融合的本质
说到搜推排序,很多人第一反应是上大模型、上深度排序模型,觉得手工配公式是很“古老”的做法。我在一线做过几年搜推工程之后,反而觉得公式融合这件事一直被低估了——尤其是在社区内容场景里,它压根不是过渡方案,而是线上运营和算法工程之间最重要的一座桥。
公式融合,本质上就是把排序分拆成几个可解释、可干预的因子,用显式的数学关系把它们组合在一起。比如典型的社区内容排序公式长这样:
score = w1 * text_sim + w2 * quality_score + w3 * recency_score + w4 * author_authority每一项都有明确的业务含义:文本相关性负责“搜得准”,内容质量负责“内容好不好”,时效性负责“新不新”,作者权威度负责“谁发的”。权重 w1 到 w4 一调,整个排序的走向立刻改变。
为什么不用一个端到端模型把所有东西都学进去?模型当然能做,但有个绕不开的问题:业务方想要的不是“效果最优”,而是“效果可控”。运营说“今天要重点推一下球鞋测评内容”,算法说“我重新训练一版模型,明天上线”,这个交互节奏基本就崩了。公式融合的好处是,运营或者算法同学改一个权重、加一个约束,几分钟后就能在全链路看到变化,这种敏捷性在内容生态运营里极其重要。
还有一个原因——公式融合天然是“灰盒”。它不像深度模型那样难以解释,每个因子提权还是降权,一查便知。出了问题可以快速定位是哪一项分数异常,而不是对着 embedding 向量发呆。这在高频迭代的社区场景里,比“黑盒精度”更宝贵。
1.2 得物社区场景的特殊性
得物社区是一个典型的“潮流内容 + 交易”双轮驱动的场景。这里的搜索和推荐,跟纯信息流产品有个很大的不同:内容背后连接着商品、品牌、用户兴趣甚至潮流趋势,价值不仅体现在消费时长上,还体现在种草转化上。
所以在做社区搜推公式融合时,你会发现几个特殊矛盾:
- 相关性 vs. 时效性:潮流内容尤其吃时效。同一条“AJ 最新配色开箱”,刚发布和一周后,用户点击意愿天差地别。但单纯给时效性提权,又容易把旧的高质量测评全部压没,用户搜“AJ 怎么搭配”时需要的是历史沉淀内容,不是三天前的碎片动态。
- 互动热度 vs. 内容质量:社区里互动量高并不代表质量高,玩梗、引战帖的互动数据往往比认真写的测评更好。公式里互动权重一高,整个社区内容风向就跑偏。
- 个性化 vs. 公共排序:同一个搜索词,新用户和潮人用户想看的东西完全不同。但纯个性化排序在搜索场景里容易让结果变得“偏”,导致相关性失控。
这些问题恰好都不是一个单独模型能优雅解决的,它们是“多目标 + 多约束”的问题,而公式融合恰恰是最适合承载这种业务约束的表达方式。加乘树3.0这个框架,就是在这样的背景下被推到前台的。
2. 加乘树3.0到底是什么
2.1 从线性加权到加乘混合
最早大家用的都是纯加法公式,也就是上面提到的 w1 * score1 + w2 * score2 这种形式。纯加法公式有个天生的毛病:各项之间的“补偿关系”太强了。比如文本相关性很低的内容,只要质量分很高,总分照样能排到前面;或者时效性拉满的垃圾内容,能把高质量长文压下去。
这在社区场景里是致命的。理论上我们用“病态补偿”这个词来描述这种现象——某一个因子的劣势被其他因子的优势无限填补,导致排序结果里混进一堆“偏科生”。
加乘混合的思路是在加法之外引入乘法项。乘法的本质是“门控”和“放大”,它能表达“如果 A 不好,那 B 再好也没用”这种条件逻辑。举个例子:
score = w1 * text_sim + w2 * quality_score * (1 + interaction_bonus)这里面 interaction_bonus 是互动带来的加成,但它被 quality_score 卡住了——内容质量不高,互动给到的加成再大也放不出来。这种逻辑用纯加法很难优雅地表达,靠调权重也调不出这种“悬崖式”的约束。
我在实际调参中体会特别深的一点是:加法公式调参像是在调节奏,乘法的引入让调参变得像在调“开关”。加乘混合之后,整个公式的表达能力一下子丰富了很多,每一个参数的语义变得更明确。你说不清到底是哪个权重在起作用的问题,变少了。
2.2 树状分桶的引入
加乘混合解决的是“因子间关系”,但还有一个问题悬而未决——不同内容类目、不同用户群体,是不是应该用同一套参数?
答案显然是否定的。球鞋测评内容和潮流穿搭内容的排序逻辑,天然就该不同:前者重专业度,后者重视觉和时效。年轻用户可能更吃互动热度,成熟用户更吃内容质量。
机器学习领域遇到这种问题,通常的解法是特征交叉或者分模型。但在公式融合框架里,我们不能为了差异把所有内容都建一个独立公式,那样参数数量爆炸、维护成本失控。于是树状分桶结构被引了进来。
树状分桶的想法很朴素:先根据关键维度做决策树分叉,把物品和用户分到不同的桶里,每个桶内部单独维护一组加乘公式参数。比如:
第一层:内容类目(球鞋 / 穿搭 / 数码 / 生活方式) 第二层:内容年龄(新鲜发布 / 常规内容 / 长尾内容) 第三层:用户活跃度分层(高热 / 中热 / 新用户)树的深度和分桶维度都不是拍脑袋定的,背后有数据分布和业务侧重点的考量。分桶粒度太粗,等于没分;分桶粒度太细,每个桶的样本量不够,调出来的参数很容易过拟合。
2.3 加乘树3.0的公式结构
把加法、乘法、树状分桶这三层融合起来,就得到“加乘树”这个框架。我按自己落地时的理解,把 3.0 版本的公式结构拆成三个层次:
**加法层(基座):**负责兜住基础排序逻辑。每个桶内都有一个确定性加权公式,保证主要的业务目标(相关性、质量、时效、权威)有一个稳健的底座。
**乘法层(调制):**在加法基座之上,叠加条件性的加成或者衰减。典型的做法是对某些特定子场景做提权——比如对“新品”标签内容,给一个时效性乘性放大;对“高举报率”内容,给一个信任度乘性衰减。
**树状层(分桶策略):**决定什么场景应用哪一组参数。它不是公式本身,而是公式的“路由策略”。
三层合在一起,线上公式表达式大概是这个感觉:
score = base_score(桶A) * (1 + fresh_boost(桶A)) * (1 - trust_decay(桶A)) + category_affinity_boost(桶A)这个结构最大的优势是,每一层参数都可以独立调试和上线。想调时效性,不用动基座加法权重,只动乘法层的 fresh_boost;想调某个类目,不用全局改,只动树状层对应分桶那一组参数。调参的影响范围被严格限制住了,线上风险随之大降。
我对加乘树3.0的判断是,它不是一个激进的新发明,而是一个非常务实的中庸方案——在深度模型统治搜推的时代,它重新把“结构化的先验知识”和“人工干预能力”放到了排序公式的核心位置,填补了模型与业务需求之间的缺口。
3. 调参框架的整体设计
3.1 调参为什么难:三个痛点
聊完了公式本身,接下来重点说调参框架。公式融合之后,最大的麻烦就变成了“参数怎么定、怎么调”。
第一个痛点是参数之间互相耦合。你以为在调 w2,但实际上 w2 变化后被 w3 的乘法项放大了,排序结果面目全非。参数没有独立性,调参就变成了盲人摸象。
第二个痛点是离线指标和线上表现经常不一致。离线评测用的是历史数据回放,但线上排序一变,用户行为跟着变,曝光分布也不一样了,反馈链路一通,离线测出来的“提升”往往是幻觉。
第三个痛点是调参无法沉淀。今天调好的参数,沉淀在一个没人看的配置文件里;明天新同学接手,不知道每个参数为什么是这个值,只好推倒重来。没有框架的调参,基本等于靠手艺吃饭,人一走,经验就没了。
这三个痛点叠加起来,形成了一整套很常见的“负循环”——参数越调越乱,越乱越不敢调,越不敢调公式的适应性就越差,最后整个公式被业务方吐槽“不灵敏”。
3.2 框架组成和闭环流程
我实践下来的一套调参框架,核心不是某个牛逼的算法,而是把“配置、观测、实验、决策、回滚”这几个环节全部打通。框架由五个模块组成:
- **参数配置中心:**所有公式参数集中管理,支持按分桶、按场景灵活配置,线上读取走配置中心下发,不用发版。
- **分数分解日志:**排序打分时把每个因子的分项明细和最终总分布入日志。这一步是调参的基础,没有分项分数,后面所有分析都是猜。
- **离线回放引擎:**用历史的搜索请求和当时的候选集合,灌入新的公式参数,重新计算排序,再评估指标变化。
- **在线分桶实验平台:**支持流量切分,同一批请求使用不同参数版本,实时对比点击率、交互率、时长等指标。
- **参数诊断报表:**把每个参数在不同分桶下的实际影响可视化,帮人判断是“参数本身不合适”还是“分桶策略有问题”。
整个调参流程闭环是:启动诊断 -> 定位敏感参数 -> 离线回放推演 -> 小流量实验验证 -> 全量上线 -> 监控反馈 -> 沉淀调参记录。
这套闭环的核心思路是把“调参”从一次性的手动操作,变成一个可观测、可验证、可复盘的工程流程。调参不是玄学,是因为你缺了一套能快速定位“该调哪里”的仪表盘。
3.3 一份可落地的配置模板
配置模板要体现框架的可操作性,我写过一份简化版,核心结构是这样的:
bucket_rules: - bucket_id: "sneaker_fresh_user_high" dims: ["category=sneaker", "content_age<3d", "user_activity=high"] formula: add: text_sim: 1.0 quality_score: 0.8 recency_score: 0.6 author_authority: 0.4 multiply: quality_gate: true fresh_boost: 0.15 trust_decay: 0.0 bias: 0.2 - bucket_id: "sneaker_longtail_user_mid" dims: ["category=sneaker", "content_age>=3d", "user_activity=mid"] formula: add: text_sim: 1.0 quality_score: 0.9 recency_score: 0.2 author_authority: 0.6 multiply: quality_gate: true fresh_boost: 0.0 trust_decay: 0.05 bias: 0.0模板里每一项都不是随便写的。add 层里的权重反映的是该分桶下各因子的基准重要性;multiply 层里的 fresh_boost 和 trust_decay 是对特殊策略的调制;bias 则是日志里观测到的系统性偏差补偿。
这套配置文件最大的价值在于“可读性”和“可追溯性”。任何一个新同学拿到配置,都能看懂桶的划分逻辑和参数的业务含义,而不是面对一个无法解释的权重向量。
4. 实操:从一个参数开始调
4.1 基准与指标定义
所有调参动作开始之前,先定义清楚“什么算作好”。在得物社区搜推场景里,我用的指标组合是“C 端体验 + B 端生态”双维度。
C 端看搜索点击率、单次会话深度、点击后停留时长、搜索后转化行为;B 端看内容曝光集中度、中小创作者曝光占比、低质内容曝光占比。这个组合的意义在于,单纯提升点击率很容易,把最热的内容排最前就行,但那样中小创作者会失去曝光,生态会恶化。所以调参永远是对多维指标的权衡,不是单点最大化。
实操那天我给自己定了一个非常聚焦的目标:在不降低整体点击率的前提下,提升“新鲜内容”的曝光占比 5 个点。这个目标必须落到一个可量化的指标上,我选的代理指标是“曝光内容中发布时间 24 小时以内的占比”。
我强烈建议做调参时,不要同时追着三四个指标跑。一次只盯一个核心目标,其他指标作为守住下限的护栏,这样参数调整的因果链路才清晰。一次调一堆,后期出问题根本分不清是哪个参数带来的。
4.2 第一次调参:时效性权重
目标定了,我开始在加乘树的配置里调 recency_score 的权重。
刚接手这个公式时,默认的 recency_score 权重是 0.5,我先把候选集的时效性分数分布拉出来。结果发现,新鲜内容的 recency_score 集中在 0.9 到 1.0 区间,旧内容集中在 0.2 到 0.4 区间,区分度尚可,但权重在整个总分的占比不算高。所以我决定先在 add 层把 recency_score 的权重从 0.5 提到 0.7,离线回放一下看看影响。
回放结果立刻暴露了一个问题:整体点击率确实没掉,新鲜内容曝光占比提升了 4 个点左右,但“长尾优质内容”的曝光下降了 6 个点。原因很清楚——在纯加性结构下,给时效性加权重,所有桶的表现一起变,没有对长尾内容做保护。这就是我前面说的,没有分桶限制的调参,就像全域开火,容易误伤。
于是我改在乘法层做干预。我新增了一个 fresh_boost 参数,只作用于“内容年龄小于 24 小时”且“内容类目属于球鞋/穿搭”的桶,公式变成:
score = base_score * (1 + fresh_boost)经过两轮小流量实验,fresh_boost 从 0 逐步升到 0.15,新鲜内容曝光占比成功提升 5 个点,长尾优质内容曝光只掉了 1 个点,完全可控。
实操里最值得记住的一点:能用乘性调制解决的问题,就别去动加法基座。乘法项是局部“放大镜”,加法项是全局“天平”。全局天平一动,所有内容一起受影响,排查范围就扩大了。
4.3 树状分桶策略的二次调整
fresh_boost 上线稳定后,我又发现了一个新现象:不同用户活跃度分层下,fresh_boost 的表现差异非常大。
高活跃用户在收到新鲜内容时,点击率和互动率明显上升;而新用户对新鲜内容的敏感性反而一般,他们更依赖类目权威内容来做冷启动熟悉。这说明现用的“内容年龄 + 类目”的桶划分,还缺了一个用户维度。
于是我把树状层的第一层扩展为“类目分桶”,第二层扩展为“用户活跃度分层”,第三层才是“内容年龄”。调整后,fresh_boost 只在“高活跃用户 + 新鲜内容”的桶里保持 0.15,在中活跃用户桶里降到 0.08,在新用户桶里直接归零。
这次调整带来的提升不是曝光占比的大幅跳升,而是点击率的稳定性变好了。之前 fresh_boost 对高活跃桶有效,对部分新用户群体有点“强推”的意思,用户不买账,点击率被拉低。分桶一细化,参数各回各家,整个漏斗的健康度都上来了。
从这次实操我提炼出一个经验:分桶的维度不是“越多越好”,而是“越贴近业务可干预的动作越好”。桶分得跟业务叙事完全对应,调参的人才知道这个参数动了之后到底在影响谁的体验。
5. 常见问题与排查实录
5.1 参数震荡与同期对比幻觉
调参过程中最容易踩的坑,首推“参数震荡”。
我有一次在做另一个分桶的调优时,连续三天看到某指标稳步上升,第四天突然掉回原点。排查半天才发现,问题不在参数,在于算法团队同期上线了新版本的召回模型,召回的候选分布变了。排序公式在候选集不同时,同样的参数打出来的效果完全不一样。
从那以后,我养成了一个习惯:任何调参实验期间,都必须记录同期上线的其他相关变更。比较理想的做法是建立一张变更记录表,把推荐召回、搜索召回、审核策略、公式参数这些维度的变更全部登记起来。否则你会很容易把别人的功劳或者锅,都算在自己那一个参数头上。
另一个高频问题是“同期对比幻觉”。分桶实验经常遇到这种情况:实验桶的点击率提升了,但你以为是参数起效,其实是那天恰好有大促活动,整体流量上涨,所有指标都被带起来了。为了规避这个,我建议不要只看实验组自身的前后对比,一定要和对照组做“差分对比”——实验组提升 3 个点,对照组也提升了 3 个点,那就等于没有提升。
表格化的对比方式比较好用:
| 指标 | 实验组(新参数) | 对照组(旧参数) | 差值 |
|---|---|---|---|
| 点击率 | 11.2% | 10.8% | +0.4pp |
| 互动率 | 3.1% | 3.0% | +0.1pp |
| 新鲜内容曝光占比 | 28.7% | 24.1% | +4.6pp |
| 长尾内容曝光占比 | 31.2% | 32.0% | -0.8pp |
只看绝对值很迷惑,看差值才靠谱。
5.2 加乘结构导致的放大效应
加乘树里乘法项是把双刃剑,最典型的问题是“放大效应”。乘法项会把基座分数的差异一起放大,导致排序头部集中度急剧升高。
我遇到过一种情况:一个小众类目里,fresh_boost 给了 0.2,但因为该类目内容整体基数偏低,乘法后头部几条内容分数被顶得很高,其余内容被压得毫无曝光,结果这个类目的内容多样性指标暴跌。
排查的时候我用的是分数分解日志,把每一条候选内容的分项分数和总分打出来看。很快发现,出问题的不是参数绝对值,而是该类目下分数的分布太扁、方差太小。pinpoint 下来之后,解决方案不是减少 fresh_boost,而是给该类目加一层“分布校准偏置”:
score = base_score * (1 + fresh_boost) + category_biascategory_bias 是一个小常数,让整个类目的分数抬升一点,这样乘法项的放大效应就不会把尾部内容“打死”。这个例子再次验证了加乘树 3.0 的一个核心设计哲学:加法负责保底,乘法负责提优,树负责分流,三个结构各司其职,排查问题的时候也按这个逻辑去定位。
5.3 回放实验的偏差来源
离线回放引擎虽然好用,但一定要清楚它的局限。最大的偏差来源是“曝光偏差”——线上旧参数决定了哪些内容能进入用户视野,回放实验用的是旧日志里的曝光集合,那部分没有被曝光的内容,就算新参数会给它很高分,也无法在回放数据里体现出来。
这个偏差无法完全消除,只能缓解。我常用的做法是,在回放引擎里做一个“近似未曝光集合估计”,从历史日志中采样一部分排名靠后的内容纳入候选集合,让新参数有机会把“以前没机会”的内容翻上来。这套操作让离线指标和线上趋势的一致性有了明显提升。
回放实验的定位应该是“快速筛出明显不行的方案”,而不是“预判绝对有效的方案”。任何离线回放表现好的参数,都还是需要小流量实验来兜底验证的。这不是程序员的保守,是见多了“离线猛如虎,上线原地杵”之后的真实教训。
6. 我个人的实操体会
做了这么多轮加乘树调参之后,我最大的感受是:公式融合调参,真正的难点不在数学,不在工程,而在于对业务节奏的把握。
你手里有一套参数体系,它们每天都产生线上影响,每一个权重背后都对应着一类创作者的生计、一群用户的体感。调参不是调一个数字,是调一套利益分配的平衡。所以在我的工作习惯里,任何参数上线之前,都会再过一遍三件事:动了之后谁受益?谁受损?受损的那部分影响在不在可接受范围内?
加乘树 3.0 这个框架帮我理清了三层思路:加法解决基准,乘法解决策略,树决定战场。三层各司其职,调参就从“两难抉择”变成了“结构化的权衡”:在哪个桶、给什么策略、做多大程度的调制。
最后分享一个小技巧:每次完成一个调参动作,一定要记录为什么调、预期的效果是什么、实际效果是什么、和预期的差异在哪。这份记录的价值会在三个月后放大十倍——因为到那时候,你早就忘了当时是被哪张数据表说服的。没有记录的调参,等于白调。调参框架的意义,本质上就是把经验从人肉记忆里解放出来,变成组织的沉淀。