在得物社区做搜推搜索与推荐融合调参这几年我最大的体会是排序公式看起来只是几个权重相乘相加的数学题真正落地时却是一道工程题。社区内容的排序信号和电商商品的差异非常大——帖子有标题、有配图、有作者等级、有历史互动、有发布时长还有实时在涨的点赞评论数每个信号都想在最终总得分里占据一席之地。我们内部把这一类问题统称融合调参最后上线的方案是一套叫加乘树3.0的调参框架它把原来靠人肉拍脑袋的加权公式改造成可解析、可搜索、可解释的表达式树并配了一套自动搜索流程。这篇文章就从头到尾拆一遍这个框架的设计思路和实战细节希望能给同样在做搜推融合的同路人一些参考。1. 社区搜推为什么要重做公式融合1.1 我们面对的信号矩阵先交代一下背景得物社区是一个典型的UGC内容社区用户会在社区里晒鞋、做穿搭测评、发开箱视频、讨论潮流资讯。社区搜索和推荐的排序对象不是纯商品而是“内容创作者消费决策”混合体。这就导致排序时需要考虑的信号特别杂。我简单列一下当时常用的特征大家感受一下量纲和语义的差异特征类代表特征量纲信号特点文本相关性query与标题/正文的向量相似度0~1搜索场景核心信号推荐场景缺省图片质量视觉模型输出的美观度分0~1对穿搭/开箱类内容极其重要内容新鲜度发布时间衰减函数值0~1社区内容的生命力来源但容易抖动作者影响力作者粉丝量、历史互动率0~1长期稳定信号但会压制新作者实时互动近1小时点赞、评论、转发达标量量级差异巨大能反映热点爆发但噪声也大在实际排序里这些信号不是“各自安好”的关系。文本相关性很高的帖子可能图片质量很差用户点进去就退出实时互动特别高的内容往往是标题党或者引战帖光靠互动维度加权会把社区氛围带偏新鲜度则需要和作者影响力互相制衡否则热门作者的新帖永远排在最前面普通用户的内容连曝光机会都没有。单纯把几个分数用固定权重加起来听起来可行实际操作时根本无法收敛。因为同一个权重放在搜索场景和推荐场景表现完全不同放在“潮流资讯”类目和“球鞋测评”类目也完全不同。所以我们需要一套机制能根据不同场景、不同目标动态调整融合公式而不只是拍一个静态的加权和。1.2 旧的线性加权为什么撑不住早年我们用的也是业内常见的线性加权融合形式很简单score w1 * text_score w2 * img_score w3 * fresh_score w4 * author_score w5 * interact_score这版公式在冷启动阶段确实能跑但越往后越痛苦。问题集中在这几个地方一是特征量纲不一致导致权重语义漂移。比如实时互动分数如果没做压伸它的数值范围可能是文本相关性的几十倍那么训练出来或人工调的 w5 会小到失去区分度而 w1 会被压得很低文本相关性的作用名存实亡。即使每次做 min-max 归一化分布偏斜严重的特征依然会让权重失去可解释性。二是特征之间的关系不是线性的。文本相关性低到一定程度时图片质量再高也没用——用户根本没搜索意愿文本相关性已经很高时再提升它的边际收益又很有限这时候反而是实时互动的增量更有价值。这种“门槛效应”和“边际递减效应”线性加权完全表达不了。三是场景切换时需要大量人工介入。搜索场景用户带着明确意图相关性权重必须拉高推荐场景是刷信息流新鲜度和互动权重就要加大。如果靠手工调五六个权重每调一次要等一天的数据回流还要人工判断涨跌是实验效果还是数据波动整个团队的技术债就是这样慢慢堆起来的。我印象很深的一次翻车某次大促活动运营希望社区搜索结果页多给活动相关内容曝光我们直接调大了活动标签特征的权重结果搜索“篮球鞋”时候选集里一半是无关的活动贴搜索点击率掉了将近10个点。这种“粗暴调权”的教训后来直接推动了加乘树3.0的立项。2. 加乘树3.0的设计核心2.1 为什么叫“加乘树”加乘树这个名字字面拆开就是加法和乘法组合出来的表达式树。更准确地说它是一棵二叉树树的内部节点只允许是加法节点或乘法节点叶子节点是特征分数树的每条边上可以挂一个可学习权重有时候内部节点还带一个偏置项。比如我们线上最终跑过的一个简化版公式展开来长这样score (w1 * text_score w2 * img_score) * (w3 * interact_score w4) w5 * fresh_score w6 * author_score用树的形式表达根节点是一个加法节点左边是一个乘法子树右边是一个加法子树。左边乘法子树的内部左孩子又是一个加法节点包含文本和图片分数右孩子是一个加法节点包含互动分数和偏置。整棵树的语义很清晰先综合内容质量和热度再叠加上新鲜度和作者影响力。树结构带来的核心价值是可解释性。搜推团队日常要跟产品、运营、算法反复对齐“为什么这个内容排前面”线性加权只能说“哪个特征的权重高”加乘树可以直接说“文本相关性和图片质量必须同时达标然后再用互动热度做加成”。这种表达方式跨部门沟通成本低非常多。2.2 “加”和“乘”分别解决什么问题加法节点解决的叫“补偿问题”。多个信号可以互相弥补比如文本相关性不够高时图片足够精美也能把整体分数拉回来。乘法则解决“门槛问题”几个信号必须同时达标任何一个挂了都会把乘积压到接近零。比如我们后续在部分场景里加了“内容安全分”作为乘法因子安全分很低的内容整体分数就断崖式下降这就比用加法惩罚“温和得多但更决绝”。本质上是概率论里“独立事件联合概率”的思想。加法对应“或”的逻辑乘法对应“且”的逻辑。搜推排序中想要实现“既要有相关性又要有互动热度同时内容质量不能差”这种带条件的融合逻辑天然适合用加乘树来表示。不过乘法节点也有风险就是数值敏感度太高。一个特征微小的扰动经过多个乘法层叠后可能放大好几倍。我们后面用了一组归一化保护层把每个叶子节点的输入值压缩到一个相对稳定的区间才把数值抖动的问题压下去。这个细节等会儿在踩坑部分详细说。2.3 从v1到v3的演进过程加乘树不是一步到位的前后迭代了三版所以内部才叫3.0。v1版本只有一种固定结构就是线性加权和。它解决的是“从无到有”的问题帮我们梳理出了核心特征集合也暴露出特征分布和权重的各种问题。v2版本尝试了两层加乘混合。比如先把相关性和质量做成乘法结构再把新鲜度和互动做成加法结构。它证实了加乘混合在社区内容排序上比纯线性加权有显著提升。但v2的表达式模板是写死的换一个业务场景就要改代码重新上线代价依然很高。v3版本也就是加乘树3.0做了三件关键升级。第一把表达式变成了配置化模板不写代码也能调整树结构第二在树结构上扩展出一套参数搜索空间支持离线自动寻优第三配套了完整的离线评估和线上AB实验流程让调参不再是不可控的手工活。这里补充一句内部版本号的“3.0”并不代表这是业界标准命名只是我们团队自己的迭代代号。现在写文章复盘时依然用这个名字是想保持和当时项目文档的一致性避免对不上号。3. 实操从模板设计到自动搜索上线3.1 用配置化DSL定义公式模板加乘树3.0使用了一套轻量级的表达式DSL通过配置解析成树结构。我们定义了几类标准节点节点类型作用示例leaf叶子特征节点text_score:0.8add加法节点子节点分数加权求和add(w1,c1,w2,c2)mul乘法节点子节点分数相乘后乘系数mul(w1,c1,w2,c2)bias偏置项bias:0.5实战时先写一个表达式模板比如score mul(add(0.6, leaf(text_score), 0.4, leaf(img_score)), add(1.0, leaf(interact_score), 0.3)) 0.2 * leaf(fresh_score) 0.1 * leaf(author_score)注意这里模板里的数字是初始值后续会被搜索算法替换。DSL解析器会把字符串编译成树对象同时维护每个节点的参数索引。树对象内部有多条遍历路径既支持前向计算分数也支持反向收集所有参数梯度方便后续接自动搜索。我把这套配置和参数分开管理结构模板放在配置中心参数放在特征平台每次实验只需要部署一份参数集不需要重新发布排序服务。这个改造非常关键它让算法同学可以自己在实验平台跑搜索、看结果、迭代参数而不是每次都要麻烦工程团队发版。3.2 参数搜索空间怎么定公式模板有了参数从哪来加乘树3.0最常见的做法是从贝叶斯优化和TPE算法里选一个做搜索搜索空间包含三部分第一是连续权重参数。比如某个加法节点里两个子节点的权重分配取值范围 [0,1]某个乘法节点的整体缩放系数取值范围 [0.5, 2]。这些参数直接决定信号之间的相对重要性。第二是离散结构参数。比如某个节点应该用加法还是乘法某个子树是否保留。因为结构参数是离散的我们一般不用梯度类方法而是先按模板库做一轮结构枚举再在结构固定的前提下搜索连续权重。这样既控制了搜索复杂度又能验证不同结构的腹部效果。第三是归一化相关参数。比如特征分数是否需要log变换截断阈值是多少这类参数经常被忽略但对线上表现影响很大。我们后来把常见变换操作直接做成了可选算子搜索算法会在变换空间里一起搜索。搜索空间定义好之后目标函数要非常小心。如果直接用点击率做目标很容易被标题党、擦边内容带偏如果直接优化人均阅读时长又会牺牲新内容曝光导致新鲜度低的旧帖霸榜。我们当时的离线目标函数是“点击率 互动率 新内容曝光占比”的加权组合三项指标都过了预设阈值才允许进入线上实验评审。3.3 离线评估与线上AB实验闭环离线评估阶段我们回放近14天的搜索点击日志和推荐曝光日志构造训练集和验证集。每个样本包括请求上下文、候选排序特征、用户的点击/互动行为。评估时特别注意一个点不能只用GAUC一个指标下结论。某一次实验的GAUC涨了结果线上推荐页的人均展示时长反而降了原因是搜索和推荐的用户意图完全不同搜索页里用户明确想找某类内容相关性权重越高越好推荐页里用户是在漫无目的地刷新鲜度和惊喜感反而更重要。于是我们建立了分场景评估机制搜索场景单独评估相关性指标和点击指标推荐场景单独评估互动指标和时长指标最后再看整体大盘有没有显著负向。所有候选参数组合必须同时满足搜索、推荐两个场景的最低要求才能推上线。线上AB实验阶段我们使用标准的流量切分实验组和对照组各切5%到10%流量实验周期为3到5天。实验开始后的前24小时只看核心指标是否出现极端异常比如点击率跌了3个点以上或者搜索无结果率上升一旦触线立即熔断回滚。如果没有异常三天后看显著性检验通过后逐步扩量到全量。整个流程跑下来一个加乘树3.0的实验迭代周期可以压缩到5天左右相比之前手工调参动辄两周起步效率提升非常明显。4. 踩坑实录与常见问题排查4.1 高频问题速查表这里把实战中反复出现的问题整理成一张速查表遇到类似情况可以直接对号入座。现象可能原因解决思路离线GAUC涨线上CTR跌特征在线/离线不一致日志拼接缺失做线上特征一致性校验关键特征在AB实验日志里直接打印所有权重搜索完都趋近某个固定分布搜索空间约束太宽目标函数对参数不敏感缩小权重范围在目标函数里增加业务约束项新内容曝光占比下降新鲜度特征的加法权重被压制乘法节点权重过高把新鲜度从加法节点移到加法子树的偏置项并加入曝光占比目标实时互动特征数值剧烈抖动原始互动量没有做非线性变换对互动量做log1p变换搜索变换算子排序结果过于极端乘法节点过多特征小扰动被放大增加归一化保护层限制乘法节点深度人工review时发现排序解释性变差树结构太复杂超过三层设置结构复杂度惩罚项优先选择层数更浅的表达式模型在某一类目表现奇差搜索与推荐共用一套公式类目差异被忽略按类目配置不同模板或增加类目作为树分裂特征4.2 印象最深的三类坑第一类坑是离线与线上特征不一致。一度离线评测时某组参数GAUC提升了0.5%信心满满推上线结果线上点击率反而跌了1%。查了一天发现是线上实时计算的图片质量分和离线日志里拼接的图片质量分来自两个不同版本的特征计算服务字段名相同语义完全不同。从那以后我们强制要求任何离线实验候选集都必须使用线上服务的完整日志回放而不是自己拼特征。这个教训非常深刻也顺便把特征血缘管理提上了日程。第二类坑是乘法节点导致数值爆炸。某版本尝试把“文本相关性、图片质量、作者影响力、互动热度、新鲜度”全部放在一条乘法链上结果是线上每个候选的分数分布极度两极化排序变成了少数高分内容的独角戏。后来我们给每个特征加了截断和缩放限制乘法链深度不超过2层并且每次乘法节点后都接一个sigmoid式的压伸函数才把分数分布拉回正常。第三类坑是搜索空间过大导致参数过拟合。一开始我们把结构参数和权重参数放一起做联合搜索搜索了几百轮后发现效果最好的几组参数在验证集上很亮眼但到了测试集上拉胯。拆解后发现树结构搜索空间太大了算法在验证集上已经过拟合。解决方法是先固定结构搜索权重结构候选控制在5到8个模板以内权重搜索时加入L2正则项并限制搜索轮数。简单说宁可少跑几组实验也要保证每组的泛化能力。如果你也在做类似框架我的建议是把线上业务的安全边界放在最高优先级任何改动都先过一波人工review的历史case列表而不是完全信任离线指标。搜推调参的本质不是找一个静态最优参数而是建立一套能够快速响应业务变化和自我否定的迭代机制。5. 写在最后的实战心得加乘树3.0上线之后社区搜推的整体点击率和互动率都有稳定提升但相比绝对值的变化我更看重的是这套框架带来的几个隐性收益。一是沟通语言统一了。产品和运营现在可以对着表达式树直接提需求“搜索场景里文本相关性还是要守住的这块能不能做成乘法门槛”“推荐场景里新作者的内容能不能给个偏置补偿”这些需求可以直接映射到模板结构调整上不再是含糊的“权重高一点低一点”。二是调参成本大幅降低。以前调一次权重算法、工程、产品三个角色来回拉扯现在只需要算法同学在配置中心改模板跑一轮离线搜索再走一遍AB实验流程。整个流程拉通之后团队终于有余力去优化特征本身而不是天天耗在权重上。三是试错成本可控。树结构加参数搜索本质上把“调公式”变成了一种可量化、可回滚的工程行为。任何一个新想法都可以先用模板试一版效果不好直接丢弃不会污染线上主链路。这种“低成本试错”的文化对搜推团队长期迭代来说比单次提升更重要。最后再分享一个小技巧不要一上来就把模板设计得很复杂。加乘树这类框架的收益来自于用加法乘法组合把业务先验表达出来而不是把数学表达式堆得越花哨越好。我们在实践里得到的一个明显规律是绝大多数场景一个两层到三层的加乘结构配5到7个特征已经能覆盖80%的排序需求。流传在算法群里那些深度很大的公式往往只说明了业务还没被充分理解。先把简单模板的效果压榨干净再决定要不要加深结构这条路我目前看下来是性价比最高的。