拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

社区搜索推荐融合调参实战:加乘树3.0框架全解析

社区搜索推荐融合调参实战:加乘树3.0框架全解析

在得物社区做搜推(搜索与推荐)融合调参这几年,我最大的体会是:排序公式看起来只是几个权重相乘相加的数学题,真正落地时却是一道工程题。社区内容的排序信号和电商商品的差异非常大——帖子有标题、有配图、有作者等级、有历史互动、有发布时长,还有实时在涨的点赞评论数,每个信号都想在最终总得分里占据一席之地。我们内部把这一类问题统称融合调参,最后上线的方案是一套叫加乘树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.8
add加法节点,子节点分数加权求和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%的排序需求。流传在算法群里那些深度很大的公式,往往只说明了业务还没被充分理解。先把简单模板的效果压榨干净,再决定要不要加深结构,这条路我目前看下来是性价比最高的。

返回列表