ARTICLE DETAIL

资讯详情

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

加乘树3.0在社区搜推排序融合公式调参中的落地实践

加乘树3.0在社区搜推排序融合公式调参中的落地实践 去年我们把社区搜推排序里的最后一层融合公式从手工调参换成了加乘树3.0自动搜索出来的结构前前后后折腾了三个月踩了不少坑。这篇文章不是理论介绍纯讲我们在这套公式融合调参框架里是怎么落地的哪些设计值得保留哪些地方一定要提前防。如果你也在做社区类产品的搜索或推荐排序手上有七八个分数但不知道怎么优雅组合这篇文章应该能帮你少走两个月弯路。1. 先说说为什么搜索和推荐要换融合方式1.1 社区搜推的信号通道其实都是偏科选手得物社区的排序不是单一模型打天下而是多个信号通道并行产出分数。按我们线上的结构至少有七个通道会同时参与最终排序文本相关性分数、语义相似度分数、CTR模型预估分、互动率预估分、内容质量分、作者权威度、时效新鲜度。这七个分数放在一起第一个问题是尺度和分布完全不一致。有的通道输出范围是[0,1]有的模型输出是LogOdds从-5到5都有可能有的则是百分制。如果不做归一化直接相加等于让某些通道天然享受权重优势这不是调参能救的是结构性问题。第二个问题每个通道都是偏科选手。文本相关性分数擅长处理用户搜了某双鞋标题里确实命中关键词这种强意图场景但对长尾、意图模糊的query比较迟钝互动率预估分对爆款内容敏感但新发布内容互动数据稀疏预估噪声很大内容质量分又只能反映内容本身的创作水平完全不管用户此刻想不想看。没有一个通道能单独承担排序决策大家必须组合。1.2 固定加权公式的死穴逻辑冲突我们最早用的是最常规的加法加权公式score 0.30*rel 0.25*interact 0.20*quality 0.15*freshness 0.10*author这个公式在大多数流量上跑得还行但逻辑上有几个喘不过气的地方。加法加权假设的是各信号可以线性互相补偿相关性差一点互动率高一点总分还能兜住。可实际里有很多场景是一票否决的比如用户明确搜索皮卡丘联名款一个完全不相关但互动爆棚的泛内容不应该因为互动分高就排上来。反过来一个相关性很强但质量分极低的内容比如错别字一堆、图片模糊也不应该只靠相关性强就进前排。另一个痛点是搜索和推荐对融合逻辑的诉求是冲突的。搜索是强意图场景用户明确要某个东西相关性必须站C位推荐流是弱意图场景用户没有明确目标互动信号和新鲜度反而应该占更重。同一个加法公式在两个场景下都得用靠权重去妥协最后变成两头都不舒服。这些问题的本质在于融合公式的结构本身和参数一样重要。什么时候该用加法让信号互补什么时候该用乘法让信号缺一不可不应该靠人肉拍脑袋而是应该交给搜索算法去找。这是我们决定落地加乘树3.0的根本原因。2. 加乘树的核心机制拆解2.1 从两个极端开始理解纯加法与纯乘积融合公式抛去所有花活最本质的选择就是加法和乘法。加法表达的是多信号协同一个信号弱了其他信号可以顶上来。它天然适合互不冲突的互补信号比如内容质量分和作者权威度两者都弱但都不至于归零加权相加很稳健。乘法表达的是多信号都不可缺任何一个乘数为0整体输出就归零。它天然适合强依赖信号比如相关性和互动率——内容不相关互动率再高也没用互动率为0相关性再强也说明内容冷门可能不值得推。纯加法和纯乘积是两个极端。现实里的融合逻辑远比这复杂某些信号组之间需要乘法约束组内信号之间又可以加法互补甚至同一个信号在不同流量场景下参与的运算符号都不同。这种部分加法、部分乘法的混合结构正是加乘树想表达的空间。2.2 用树来表达一切加乘树的想法很直白把最终的融合公式表示成一棵二叉树。叶子节点是各个信号通道内部节点是加法运算或乘法运算每条边可以挂一个权重对应幂运算或加权系数。我举个我们实际用过的例子简化成表达式是这样((rel * interact) (quality * author)) * freshness ^ 0.3翻译成树是什么感觉呢根节点是乘法意味着新鲜度是前提约束内容太旧的后面加出花来也压一压左子树是加法意味着相关性×互动率作为一个整体和质量×作者权威度之间可以互补子树的内部又是乘法意味着相关性和互动率必须同时在线这种表达比一个扁平的加权公式信息量大得多。它在说相关性差但互动高的内容和有强相关性但作者一般的内容可以在同一档位里竞争但新鲜度不行就直接降权。加乘树3.0的核心价值就是把选择加法还是乘法、权重放多少、树长多深这些决策从人工手里剥夺让搜索算法自己找。你只需要提供信号通道、归一化方式和评估指标框架给出一个最优或接近最优的树结构。2.3 3.0版本的演进路线我们自己内部把加乘树分了三代这个版本演进思路可以给团队做参考。1.0时代我理解是固定模板小规模枚举给定几类预设结构纯加法、纯乘法、加乘交替两层人工枚举不同叶子位置跑离线评估挑最好的。这个版本能解决一部分问题但搜索空间被模板锁死结构本身很可能不在预设里。2.0时代引入演化搜索我们用的NSGA-II为主树深、节点运算符号、叶子归属都成了可变异对象。搜索空间大了很多但也带来两个新麻烦一是经常搜出深度七八层、结构诡异、线上没法解释的黑树二是搜出来的公式在离线AUC上很漂亮上线上就崩。3.0在我们团队的定位核心是三件事约束可控、场景自适配、公式可解释。约束可控是指树的深度、某个叶子最多出现几次、运算符号的变换概率都有了明确的约束器搜出来的结果必须在线上可解释的范围内场景自适配是指同一个树框架可以按query意图或者内容品类动态切换叶子权重而不是一套公式走天下公式可解释则所有线上生效的公式都需要先转成人工读得懂的表达式过了评审才能发布。这套演进路径不一定和别家团队完全一致但思路应该是共通的搜索能力越强约束和可解释性就越要跟上。3. 调参框架的关键模块设计与实现3.1 公式表达与配置编码加乘树落地到工程第一件事是解决公式怎么在系统里表达。我们在框架里没有直接保存二叉树对象而是用一套统一的JSON配置来描述公式树线上引擎只认配置。我们线上实际生效的一条公式配置大致长这样{ formula_name: search_amt_v3, operands: { rel: stabilize(minmax, 0.0, 1.0), interact: stabilize(zscore, -3.0, 3.0), quality: stabilize(log, 0.0, 1.0), freshness: stabilize(decay, 0.0, 1.0) }, root: { op: mul, children: [ { op: add, children: [ { op: pow, leaf: rel, weight: 0.61 }, { op: pow, leaf: interact, weight: 0.39 } ] }, { op: add, children: [ { op: pow, leaf: quality, weight: 0.72 }, { op: pow, leaf: freshness, weight: 0.28 } ] } ] } }这里有几个设计点很关键。第一每个叶子通道都带一个stabilize处理。这个不是为了好看是防止上游分数分布漂移直接打爆公式。minmax、zscore、log、decay这些归一化方式的选择也不是随便定的比如时效分数本身就是衰减形态用decay再归一化最自然相关性分数在搜索场景里分布极不均用minmax保序互动率预估分会受爆款事件冲击zscore截断能压制尾部。第二权重挂在了pow运算上。加乘树里我们不单独为每个边配一个权重再去做乘法和加法而是直接让权重变成叶子分数的幂次这样一来权重变换天然是平滑的不会出现其中一个通道被乘了0.001直接消失的问题。第三配置和代码分离。框架改动不需要发版线上引擎从配置中心拉取公式配置实时生效。这看着简单但实际是后面所有调参迭代能跑起来的前提。如果每次改公式都要发一次版本A/B实验根本没法高频跑。3.2 搜索器设计加乘树3.0的离线搜索器是我们框架里最核心的模块负责在给定约束下搜索最优公式结构。我们在搜索器里做了四件事。第一种群初始化不是纯随机。我们会把线上当前生效的公式、人工总结的几种经验公式比如纯加法、相关×互动的两层结构作为初始种子的固定比例其余种群再随机生成。这样搜索起点不会离合理区域太远收敛快很多。第二变异算子有方向性。基础操作是节点运算符号翻转加法变乘法、乘法变加法、叶子通道替换、权重扰动、子树交叉。但3.0里我们给变异加了局部约束引导比如某个叶子通道在树里已经出现两次就不允许再变异进同一个子树比如加法节点变乘法节点前会先检查两个子节点是否有归零风险。这些约束都在算子层直接拦住而不是等评估时靠分数惩罚效率高很多。第三多目标评估。我们没有只看一个指标而是同时看GAUC、交互率预估分和新鲜度保护指数即新发布内容在前排的占比。三个目标放到NSGA-II里跑Pareto前沿最后从前沿选解。单一目标的搜索很容易为了点击率牺牲掉交互和新鲜度这是短视频社区内容平台的大忌。第四引入评估噪声。3.0里每个个体在评估时会在叶子分数上注入小幅度的高斯噪声同一个个体跑三次取平均。这个设计看起来多此一举实际是防止过拟合的关键。线上叶子分数本身就是有波动的如果离线评估时公式对某个通道的微小变化极度敏感线上分数抖一下排序就乱套这种解必须在搜索阶段就被淘汰。3.3 线上执行引擎与监控公式配置有了搜索器出解了接下来考验的是线上执行引擎。我们做的是一个通用的表达式执行器解析JSON配置后生成一个计算图单条打分链路耗时控制在微秒级。这里有一个比较隐蔽的工程坑每个通道的归一化参数必须在配置里冻结版本。比如rel分数用的minmax它的最大值、最小值是离线算好的固定值线上服务启动时加载快照不能实时统计否则不同机器看到的分桶都不一样排序结果会乱。线上监控我们分两层。第一层是结果监控实时看融合分最终输出的分布和昨天的分位点对比如果P50或P95漂移超过阈值就告警。这一层能拦住大部分公式配置错误引发的线上事故。第二层是叶子监控每个信号通道的独立分布也要盯。很多时候公式没错但某一个上游模型升级了叶子分数整体偏高导致公式里其他通道被压制。这个光看融合分发现不了必须每个叶子都拉出来看。4. 从离线候选到线上收益实战复盘4.1 离线评估怎么评估加乘树的离线评估最容易犯的错误是拿一个统一的AUC就拍板。搜索和推荐融合公式的评估至少要分场景、分流量层级看。我们的做法是把样本按query意图分成三类强意图泛query比如搜AJ1 芝加哥配色明确要单品、弱意图query比如搜夏天穿什么鞋探索性需求、以及推荐流样本。每一类单独算GAUC和交互指标然后加权汇总。为什么这么分因为强意图场景里相关性分应该主导AUC会高弱意图场景里互动相关分应该主导AUC反而可能下降。整体AUC一平均可能看不出任何问题但拆开看能发现加乘树确实学到了不同场景不同融合策略。另一个离线评估重点是护栏指标。我们每次公式候选都要看几个红线相关性bad case比例搜索场景下融合公式把明显不相关的内容排进前10的比例、新鲜内容曝光占比、低质内容曝光率。这些指标不参与Pareto前沿的优化目标但作为硬性门槛任何一个超线候选直接淘汰。线上踩过太多次指标好看但内容明显不行的坑护栏必须是pre-condition而不能是目标。4.2 A/B实验设计与结果我们第一个完整的A/B实验对照组是线上的加法加权公式实验组是加乘树3.0搜索出的公式流量按1:1分桶跑了七天。实验组公式就是我们配置示例里那个结构——根乘法约束新鲜度左子树相关×互动加右子树质量×新鲜度。有意思的是搜索器没有选择把交互类信号作为独立的乘子而是把它和相关做了幂乘后和另一个加法支路合并。这说明在得物社区搜索场景里相关性好的内容如果互动率极低依然还有机会靠质量分和作者分抢救一下但如果新鲜度不够整体就往下压。这个逻辑要是人工肉眼看历史数据很难拍得这么准。七天实验下来几个核心指标指标对照组加乘树实验组变化搜索结果点击率31.2%32.8%1.6pp有效交互率点赞/评论/收藏4.1%4.5%0.4pp新鲜内容前20曝光占比15.8%19.2%3.4pp相关性bad case比例1.2%1.1%持平这个结果对我们是惊喜的。通常调整融合权重点击率和交互率很难双涨涨点击率往往靠牺牲新鲜度涨交互往往又压低了点击。加乘树搜出来的结构让这两个目标都没有明显打架核心原因就是我们前面说的它把新鲜度这种约束性信号放在了根的乘法位上把质量分和作者分放在加法互补位上各得其所。不过我也要提醒一句这类指标涨跌和业务生态强相关其他团队哪怕复现一模一样的框架结果也不一定相同。加乘树的价值是帮你找到结构合理的公式不是保证一个固定收益。5. 踩过的坑零值吞并、搜索过拟合与场景泛化5.1 零值乘法的一票否决引发的冷启动雪崩落地加乘树之后遇到的第一个上线事故让我记忆很深。搜出来的候选公式里有一个包含interact × quality子树的解离线评估时AUC和交互率都很好。我们单独把它放了一组小流量实验结果发现新内容的曝光占比直接断崖式下跌。排查链路是这样的第一步我们先确认公式生效了配置没问题线上解析正常。第二步按叶子维度拆曝光数据发现新内容发布两小时内的内容几乎不出现在前50位。第三步拉新内容的叶子分数分布发现interact互动率预估分在发布初期因为特征稀疏模型预估趋近于极小值在归一化后接近0。这个0值进了乘法子树整个左子树输出趋近0右子树再强也没用。这个问题的本质是零值乘法一票否决在新内容上被放大了。传统加权公式里一个信号为0只是少了一部分分数其他信号还能撑住加乘树的乘法子树是整体乘积任何一个乘数接近0整个子树直接塌掉。我们最后用两个手段解决第一对所有进树的叶子分数做置信度加权底保。新内容的互动率预估分置信度低我们先把它做shrinkage处理再和实时统计到的平均互动基准值做加权保证分数不会掉到接近0的死区。第二在搜索器的变异约束里显式加了规则乘法子树的任一子节点其叶子分数的历史5%分位必须大于某个阈值。如果不够算子会强制把它变成加法结构或者换成别的叶子。这个约束直接堵死了零值乘法的结构出现。这个坑也让我们意识到加乘树的搜出来好用不代表可以用必须用分布下限而不是平均值去验证每个叶子在真实线上会不会有塌陷风险。5.2 搜索过拟合离线AUC涨了线上效果却掉了第二个坑是搜索算法最常见的通病过拟合。加乘树3.0在离线评估时GAUC比对照组高了0.012这个涨幅在搜索场景里已经非常显著了。我们欢天喜地上了全量结果线上点击率不升反降时长也有小幅下跌。后来逐层排查发现搜索器找到的是一个对叶子分数微小扰动极度敏感的公式。某个叶子的权重被调到很高的幂次导致这个通道即使有极小波动在融合分里也会被放大成巨大的排序变化。线上分数本身就是有噪声的不像离线样本那样干净公式被噪声主导后排序的稳定性大幅下降用户体感就是刷出来的东西开始乱跳。回头在搜索器里做的修复有三个个体评估时注入高斯噪声前面提到的那次改造就是从这个坑里长出来的让候选公式必须对噪声鲁棒。增加复杂度惩罚项树的节点数越多、权重方差越大目标值打折越多。禁止同一个叶子通道在同一棵树上出现两次以上。有些搜索出来的树同一个信号被用了三次看起来是在放大该信号实际上是因为某个通道分数刚好很稳定被搜索器抓住当万能补丁了。5.3 场景泛化问题一个公式没法覆盖所有流量加乘树3.0刚落地时我们用的是一个全局公式也就是搜索流量和推荐流量共用同一个树结构。一段时间后我们复盘发现公式在强意图搜索场景表现不错但在推荐流场景两个问题逐渐暴露第一推荐流的用户无明确意图相关性和交互信号的关系不像搜索那么强依赖乘法结构并不合适第二推荐流里热点内容爆发时新鲜度信号的波动极大一个全局的新鲜度指数约束要么太紧压制了优质老内容要么太松让低质新内容涌进来。最终我们在3.0框架上加了场景路由的能力同一个公式树框架按流量场景分发不同的归一化参数和叶子权重。搜索场景沿用相关×互动 质量×新鲜度的结构推荐流场景换成互动质量×新鲜度作者的结构让交互信号站得更前。这个改造对框架的代码改动不算大但对公式管理平台的数据结构要求提高了。每条线上生效的公式必须标注适用的流量范围公式变更时的离线回归也要按场景分开跑不能只看全局平均数。6. 一些真实的心得体会加乘树3.0不是银弹它解决的是融合公式结构搜索这个具体问题。我个人的体会是框架落地过程中最难的不是算法本身而是约束设计——你要把搜索能力限制在一个线上可解释、运维可接受的范围内否则出来的公式再漂亮也上不了线。如果想在自己团队复现这套方案我建议从最小闭环开始先选三四个信号通道把归一化方式定好然后拿历史日志跑一批候选公式人工挑两个上小流量实验。不要一上来就把七个通道全塞进去搜索空间膨胀之后你根本分不清公式变好是因为结构优于旧公式还是单纯因为信号变多了。另外公式融合框架一定要和监控平台深度绑定每个叶子通道的分布漂移监控和融合分的结果监控缺一不可。加乘树搜出来的公式对输入分布的敏感度远高于人工加权公式这是这类方案固有的特性早做准备能避免很多深夜被叫醒的麻烦。
返回列表