几个星期前,我们复盘一个电商推荐项目的召回效果,线上点击率连续几天小幅下滑,团队里有人怀疑是模型参数或者特征出了问题。排查到最后才发现,问题出在一个最不起眼的地方:相似度计算方法与当前的特征分布不匹配。
这个经历让我意识到,推荐系统里的“相似度计算”虽然看起来只是调一个函数的事,但它直接决定了召回结果的天花板。它不仅出现在协同过滤的User-Based、Item-Based逻辑里,也是向量召回、Embedding匹配、粗排打分的基础组件。选错方法、用错前提、忽略数据标准化,都会让后续所有环节的努力大打折扣。
这篇文章我会把推荐系统里常用的相似度计算方法成体系地拆一遍,包括欧氏距离、余弦相似度、皮尔逊相关系数、Jaccard等,重点讲它们的数学直觉、业务适用场景、选型逻辑,以及工程落地时容易踩的坑。无论你是刚接触推荐系统的新人,还是已经上线过召回服务的开发者,这篇内容都可以当作一份实操参照。
1. 相似度计算在推荐链路中的真实位置:从矩阵到向量的升级
1.1 相似度不只是协同过滤的专利
很多人对相似度计算的认知还停留在“用户-物品评分矩阵里算User Similarity”的阶段。但如今的主流推荐架构里,相似度计算的覆盖面远比这个广。
先说协同过滤这条老路。User-Based CF的核心是找“和我口味相似的其他用户”,Item-Based CF的核心是找“和我买过的物品相似的物品”,这两种场景本质都是在一个共现矩阵或评分矩阵上做相似度计算。早期我们用皮尔逊相关系数处理评分偏置,用余弦相似度处理文本向量,这些都是标配。
再看Embedding时代。把用户和物品映射到低维稠密向量之后,召回阶段最常用的操作就是从向量库里找出与用户向量最接近的TopK物品向量,这个“接近程度”依然靠相似度函数来度量。无论是双塔模型、图神经网络产出的节点向量,还是矩阵分解得到的隐向量,最后都要落到相似度计算这个出口上。
所以,相似度计算实际上是整个推荐链路里的“通用底座”。召回管道的第一跳、粗排阶段的批量打分、甚至精排模型里的一些交叉特征,都会用到它。
1.2 为什么一个“调包操作”值得单独研究
在多数框架里,计算相似度确实只需要一行代码。Scikit-Learn有pairwise_distances,PyTorch有F.cosine_similarity,写起来毫无门槛。但真正到了业务里,问题往往出在“这行代码是否匹配你的数据分布和目标语义”。
举个例子,你在一个评分稀疏的平台上用余弦相似度计算用户之间的相似度,用户A只评过两部电影,用户B也只评过两部电影,碰巧重合了一部,余弦值可能非常高。这能说明A和B口味一致吗?大概率不能。这里的相似度更多反映的是“数据稀疏带来的偶然重合”。
反过来,在稠密Embedding场景里,如果你不先做向量归一化就直接用内积作为相似度,则向量的模长会混入打分,一个模长很大的物品可能仅仅因为“向量尺度大”就被频繁召回,而不是因为它和用户语义上真的相关。
这些细节,才是我认为相似度计算值得单独研究的原因。
1.3 从相似度到业务指标之间的传导关系
相似度计算还会通过“召回率”和“TopK命中率”直接影响业务指标。推荐系统里,召回决定了候选集合的边界,排序模型只能在召回集合内做优化。如果召回阶段选用的相似度方法不合理,真正的优质物品根本进不了候选集,后续精排模型再强大也无力回天。
从我个人经验来看,一次相似度方法的调整,对曝光点击率的影响幅度通常在5%到15%之间。这不一定是因为某一种方法绝对优于另一种,而是因为相似度方法的改变会带来召回候选分布的整体变化,这种变化会传导到排序、多样性控制、甚至冷启动策略上。
理解了这一点,下面的方法拆解才有意义:每一种相似度方法都不只是数学公式,而是一种对“什么是相似”的假设。
2. 五种相似度方法的数学直觉与业务适用面
2.1 欧氏距离:两点之间的“直线距离”,量纲敏感是最大隐患
欧氏距离是大多数人最早接触的距离度量,它的几何直觉也很简单:在n维空间里,两个点之间的直线距离。
在推荐场景里,如果用户和物品的特征向量是稠密的数值型特征,比如用户的年龄、收入、活跃天数,物品的价格、销量、评分均值,那么欧氏距离可以直接衡量它们在特征空间中的接近程度。距离越小,相似度越高。
公式很简单,两个n维向量x和y的欧氏距离是:
[ d = \sqrt{\sum_{i=1}^{n}(x_i - y_i)^2} ]
如果要把距离转成相似度,常用的转换方式是:
[ sim = \frac{1}{1+d} ]
这样距离为0时相似度为1,距离为无穷大时相似度趋近于0。
但这里有一个最大的坑:欧氏距离对特征的量纲极其敏感。假设特征里有一个“收入”字段,单位是元,取值范围可能是几千到几万,而“年龄”字段取值范围是18到60。那么在欧氏距离计算中,收入这一维的数值差会完全主导距离值,年龄维度的贡献几乎可以忽略。
解决办法是标准化。上线前必须对每个特征维度做归一化或z-score标准化,让所有维度处于相近的尺度。我见过不止一次因为漏掉标准化,导致相似度结果几乎等价于单一特征排名的案例。
另一个容易被忽略的点是:欧氏距离对异常点非常敏感。L2范数会把较大的差值平方化,一个离群值就能把距离拉得很远。如果你的特征里存在脏数据或者长尾分布,可以先对特征做截断或取log,再计算欧氏距离。这也是为什么有些场景会用曼哈顿距离(L1)替代欧氏距离,因为曼哈顿距离在异常值面前稳健得多,后面我会专门提。
2.2 余弦相似度:只看方向不看长度,文本与稀疏向量的经典选择
余弦相似度衡量的是两个向量之间的夹角,数学定义是:
[ sim = \cos(\theta) = \frac{x \cdot y}{|x| |y|} ]
它的核心特点是:只关心方向,不关心模长。同样的向量,即使整体放大两倍,余弦相似度依然不变。
这个特性在文本场景中是天然优势。TF-IDF或Word2Vec出来的文档向量,词频高低会让向量模长差异很大,但我们衡量两篇文档是否相关,往往更关心它们在主题方向上的重合度,而不是绝对词频。
在推荐系统里,余弦相似度的经典应用场景有两个:一是基于物品属性的稀疏向量(比如标签、品类、类目one-hot编码)做Item相似度,二是Embedding向量的相似度计算。
不过余弦相似度也有自己的盲区。在用户评分场景里,如果两个用户的分值习惯不同,一个喜欢打高分,一个喜欢打低分,余弦相似度会把这种“整体偏移”当成“方向差异”,导致本来口味一致的两个用户算出很低的相似度。皮尔逊相关系数就是为这个问题而生的。
2.3 皮尔逊相关系数:先减均值再算余弦,评分偏好的矫正器
皮尔逊相关系数的公式看起来比余弦复杂一些,但它和余弦相似度有很深的关系:
[ r = \frac{\sum (x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum (x_i - \bar{x})^2} \sqrt{\sum (y_i - \bar{y})^2}} ]
本质上,它就是在对两个向量分别做“去中心化”(减去各自均值)之后,再计算余弦相似度。
去中心化这一步带来的效果非常直观。假设用户A习惯给电影打8到10分,用户B习惯给同样的电影打6到8分,他们的评分水平和风格不同,但偏好的排序几乎一致。如果用余弦相似度直接计算,A和B的向量方向差异较大,相似度可能只有0.6左右;但减去各自均值之后,两人对每一部电影的主观态度(高于平均水平还是低于平均水平)就变得一致了,皮尔逊相关系数会接近0.9。
所以皮尔逊相关系数特别适合评分数据场景,它消除了用户的个人评分偏移和物品的总体评分偏移。这也是为什么早期协同过滤论文里,User-Based CF最喜欢用它。
它的问题在于计算前提比较严格:两个用户必须有足够数量的共同评分项,否则协方差和标准差的估计都不稳定。如果共同评分数太少,算出来的相关系数方差极大,几乎没有参考价值。所以在稀疏评分矩阵上,皮尔逊相关系数的表现往往不如余弦相似度,因为它对数据稠密度更敏感。
2.4 Jaccard相似度:集合成员的直接对比,适合离散ID与隐式反馈
Jaccard相似度的定义非常朴素:两个集合的交集大小除以并集大小。
[ J(A, B) = \frac{|A \cap B|}{|A \cup B|} ]
如果两个用户购买过的商品集合分别是A和B,那么Jaccard值越高,说明他们的购买交集越大,相似度越高。它天然适合“隐式反馈”场景,比如用户的浏览记录、点击记录、购买记录,这些数据通常是二值化的:发生过或没发生过,没有评分。
Jaccard的一个优势是计算上很容易并行化,而且不需要向量化,直接对集合做交集运算就行。在物品冷启动阶段,如果还没有丰富的评分数据,用类目或标签集合来计算Jaccard,可以快速建立物品间的关系。
它的问题在于完全不考虑“程度”。如果用户A在一个商品上花了大量时间反复浏览,而用户B只是偶然点过一次,在Jaccard里这两个记录都是“1”,信息被压缩得很厉害。此外,当集合非常稀疏时,交集很容易为0,大量相似度会变成0,区分度不够。
2.5 扩展方法速览:曼哈顿距离、汉明距离、KL散度的适用边界
除了上面四种主流方法,有些特定场景也用得上以下这些度量。
曼哈顿距离(L1距离):和欧氏距离相比,它计算的是各维度绝对值差之和,对异常值的敏感度低很多。在特征维度较多且存在长尾分布的电商画像场景里,L1距离往往比L2更稳定。
汉明距离:主要用于二进制向量,比如用户对一些布尔特征的编码。它统计的是两个等长字符串或向量中对应位置不同的个数。在标签体系比较规整的场景里,可以用来做快速去重或相似度初筛。
KL散度:严格来说它不是一个对称的相似度度量,而是衡量两个概率分布差异的方法。在推荐系统里,如果物品的类目分布或标签分布建模成了概率向量,可以用KL散度来度量分布差异,但要注意它不对称,业务上需要做对称化处理(比如使用JS散度)。
我把这些方法整理成一个表格,方便对照:
| 方法 | 核心直觉 | 典型场景 | 主要短板 |
|---|---|---|---|
| 欧氏距离 | 空间直线距离 | 稠密数值特征 | 对量纲和异常点敏感 |
| 余弦相似度 | 向量夹角,忽略模长 | 文本向量、稀疏向量 | 忽略评分偏移 |
| 皮尔逊相关系数 | 去中心化后的余弦 | 评分协同过滤 | 需要足够共同评分 |
| Jaccard | 集合交集占比 | 隐式反馈、离散ID | 不区分行为程度 |
| 曼哈顿距离 | 绝对差之和 | 高维稠密特征 | 部分信息丢失 |
3. 选型判断:数据形态与业务目标共同决定“谁最合适”
3.1 数据形态决定“能不能用”
选相似度方法,第一件事不是看哪个公式更漂亮,而是看手里的数据长什么样。
如果数据是稠密数值特征,比如用户向量是Embedding输出的128维稠密向量,那么欧氏距离、余弦相似度、内积都可以用。如果数据是稀疏计数或one-hot编码,比如物品的标签向量,大部分维度是0,那么欧氏距离的空间直觉就不太适用了,因为高维稀疏空间里大部分点之间的距离都差不多,区分度极差。这种情况下,余弦相似度反而是更稳定的选择。
如果数据是用户评分记录,且用户之间的共同评分项比较少,那么皮尔逊相关系数的前提就不成立,你强行去算只会得到一堆方差极大的噪声值。这时候要么退回到余弦相似度,要么先做评分填补或数据平滑。
所以我的习惯是,在选型前先统计几个基本指标:特征稠密度、缺失率、共同非零项的数量、量纲差异。这些数字能过滤掉一半以上的不适用方案。
3.2 业务语义决定“怎么选”
数据形态决定了哪些方法可用,而业务语义决定了哪些方法最合理。
举一个场景:做电商的相似商品推荐。商品之间的相似性应该怎么定义?如果平台希望“价格相近、风格相近”的商品被召回,那么属性向量的欧氏距离或余弦相似度都可以用;如果平台希望“经常被同一个人一起购买”的商品被召回,那么共现矩阵上的余弦相似度或者基于购买行为的Embedding向量相似度会更合适。
再举一个内容推荐的场景:做资讯Feed流,希望“看过某篇文章的用户也喜欢另一篇”。文章可以用类别、关键词、Embedding来表示。如果你希望召回语义相关的文章,Embedding的余弦相似度最合适;如果你希望召回同一作者、同一栏目下的文章,Jaccard反而更直观。
这里没有绝对的“最优方法”,只有“最匹配业务目标的方法”。我在实际项目中通常会同时计算多套相似度,把它们作为召回通道里的不同路召回,最后做融合。这比纠结“选哪个最好”要务实得多。
3.3 冷启动、可解释性、计算成本的交叉权衡
选型还需要考虑三个工程层面的因素。
冷启动:新物品没有任何交互行为时,基于协同过滤的相似度计算是无解的。此时只能依赖物品本身的属性特征,用类目、标签的Jaccard或属性向量的余弦计算相似度。所以冷启动要求高的场景,属性相似度要优先建设。
可解释性:如果推荐结果需要向用户解释“为什么推荐这个”,那么Jaccard这类基于集合重叠的方法很容易解释:因为你和很多用户一样都看过/买过这个商品。而欧氏距离、余弦相似度就只能在特征层面解释,对普通用户来说并不友好。
计算成本:欧氏距离和余弦相似度都需要向量化计算,适合GPU或批量矩阵运算。Jaccard如果直接做集合笛卡尔积,在物品量级很大的时候,计算量是惊人的。实际工程里会对热门物品做采样裁剪,或先用粗筛缩小候选集再做精确计算。
3.4 两个实际选型的复盘案例
第一个案例是某内容社区的好友推荐。一开始用的用户Embedding余弦相似度,召回结果在活跃用户上效果不错,但在低活跃用户上召回率很差,原因是低活跃用户的Embedding向量不稳定,模长偏小。后来我们增加了L2归一化,并针对低活跃用户切换到了“共同关注的作者集合”的Jaccard相似度,作为独立召回通道,低活跃用户的好友请求通过率提升了20%以上。
第二个案例是某电商平台的相似商品召回。早期直接用商品标题的Word2Vec向量算余弦,结果经常召回一些标题词面相似但实际品类完全不同的商品。后来我们改用了“类目+品牌+价格档位”拼接成特征向量,用加权余弦来计算,同时保留了一个基于购买共现的Jaccard通道,整体点击率和加购率都明显改善。
这两个案例都说明了一件事:相似度选型不是一个纯数学问题,而是要结合用户行为特点和数据稀疏情况去设计通道。
4. Embedding 场景下的相似度陷阱:归一化、内积与近邻索引
4.1 为什么Embedding上线前几乎都要做L2归一化
推荐系统发展到今天,Embedding已经成了大多数召回模型的标准输出。但在使用Embedding计算相似度之前,有一个关键步骤经常被忽略:对向量做L2归一化。
L2归一化就是把向量除以它的模长,让所有向量的模长都变成1。这步操作的意义在于:当所有向量的模长相等时,余弦相似度和内积就是完全等价的(因为内积分母固定为1)。这样你可以用内积做高效计算,同时享受余弦相似度“只关心方向”的特性。
更重要的是,它消除了模长带来的干扰信号。在双塔模型训练过程中,由于样本不均衡或训练动态,某些物品的Embedding模长可能系统性偏大或偏小。如果不归一化,内积召回时模长大的物品会天然占据优势,形成热度偏置,明明不相关的物品也会因为向量尺度大而被频繁召回到前排。归一化之后,排序只由方向决定,语义更干净。
有一个经典的操作细节:模型训练时,在最后一层对用户向量和物品向量分别做L2归一化,然后直接优化内积。这样得到的向量天然位于单位超球面上,后续做召回时直接算内积即可。
4.2 归一化之后,欧氏距离与余弦相似度的等价关系
在L2归一化后的单位向量上,欧氏距离和余弦相似度之间存在一个非常干净的数学关系:
[ |x - y|^2 = |x|^2 + |y|^2 - 2x \cdot y = 2 - 2\cos(x, y) ]
也就是说,欧氏距离的平方和余弦相似度呈一一对应的单调递减关系。距离从小到大排序,等价于余弦相似度从大到小排序。
这个等价关系在工程上很有价值。很多向量索引库(比如Faiss)默认支持用欧氏距离度量,但业务上你想要的可能是余弦相似度。此时只要确保向量已经L2归一化,用欧氏距离做索引搜索,和用余弦相似度做搜索结果完全一致,不需要为索引库单独开发余弦距离算子。
我在项目里就用过这个技巧:Faiss的IndexFlatL2加上L2归一化向量,既保留了精确近邻的查询能力,又得到了余弦相似度的排序结果。性能上和语义上都满足了需求。
4.3 ANN近邻索引的误差对相似度排序的影响
当召回规模大到几十万上百万,精确计算所有物品与用户向量相似度是不可行的,大家都用近似最近邻(ANN)索引来加速,比如HNSW、IVF-PQ这些算法。
ANN索引的引入带来一个容易被忽略的问题:索引的召回率不是100%,排序结果会有误差,尤其在高维向量上更明显。如果你只保留TopK结果做精排,可能一部分真正相似但不是特别靠近的物品已经被索引截断了。
因此我的建议是:召回阶段用ANN索引,把候选集扩大一些(比如原来要Top50,就取Top200),然后对扩大的候选用精确的相似度方法做再排序。这样既保证了效率,又抵消了一部分ANN索引误差。
此外,PQ量化误差也需要格外注意。IVF-PQ会把向量压缩成残差码,相似度计算是在压缩域上完成的,误差比Flat索引大不少。在粗排和召回阶段可以容忍,但如果要做精排打分,务必切回原始向量做精确计算。
4.4 粗排与精排相似度不一致问题
有些团队在召回阶段用Embedding余弦相似度,到了精排阶段又用另一个版本的Embedding或直接改用了带权重的内积,导致最终的排序口径和召回口径完全不一致。这种情况在线上会出现一个很奇怪的现象:召回出来的候选非常符合语义,精排却把一批冷门的长尾物品顶到了前面。
要避免这个问题,应该保证召回和精排阶段使用同一个Embedding版本、同一套归一化规则、同一个相似度函数。如果确实需要升级Embedding,最好是灰度切换,而不是让线上两个阶段各自用不同版本,否则业务指标出现波动时,你很难定位是召回的问题还是精排的问题。
5. 工程化落地:从批量计算到线上实时服务
5.1 离线批量计算:稀疏矩阵与分块策略
离线场景计算相似度,最常见的是计算“物品i和物品j之间相似度”的大规模相似度矩阵。比如某电商平台有500万在线商品,两两计算相似度的规模是百万亿级别的,直接算完全不可行。
实际工程里会做两件事。第一,先通过类目、品牌、共现次数做候选召回,把每个物品的相似候选缩小到几千个以内,再在候选集内计算相似度。第二,使用分块矩阵计算,避免一次性加载全部数据。在Spark或大数据平台上,通常以行块为单位加载数据,对每个行块做局部相似度计算,然后合并结果。
离线计算还需要注意数据版本问题。Embedding向量每天都在更新,相似度结果如果不重算,线上就会一直使用旧关系。一般我们会设置相似度表的T+1更新频率,重要场景甚至会做小时级更新。
5.2 在线召回:向量索引与本地缓存部署
在线服务阶段,高并发请求不允许实时遍历全量物品。最常见的技术栈是Faiss或类似向量检索库。
线下用Embedding向量建好向量索引,加载到线上服务内存里,每次接收用户请求后,从索引中查询TopK结果,返回候选集。这里有一个容易被忽略的性能点:Faiss的查询本身很快,但每次查询都需要从特征服务或内存KV里获取用户向量。如果用户向量存储在远端Redis,网络开销会成为性能瓶颈。
我遇到的团队踩过这个坑:上线初期把用户Embedding放在Redis,每次召回都要从Redis拉取,P99延迟飙到几十毫秒,后来改成在搜索节点本地维护一份用户向量的LRU缓存,命中率提升到90%以上,延迟降到了个位数毫秒。
5.3 参数调优实例:nlist与nprobe的实验数据
以IVF索引为例,有两个参数影响检索质量:nlist(聚类中心数量)和nprobe(查询时探测的聚类簇数量)。
nprobe越大,检索越精确但耗时越高。我的经验是,在100万量级的向量库上,nprobe从1调到16,召回率会从不到80%提升到95%以上,但单次查询耗时也会从0.1ms涨到0.6ms左右;继续调到64,召回率提升就非常有限了,耗时却开始线性上涨。
所以调参的核心思路是:先定延迟预算,再找满足召回率的最小nprobe值。nlist的影响更多体现在索引构建质量上,通常取向量量的开方数量级,比如100万向量取1024或2048。
5.4 在线离线一致性:特征版本与Vector版本管理
在线离线一致性问题,排在推荐工程各类问题前三名。具体到相似度计算,容易出问题的点包括:
第一,离线算相似度时用的Embedding版本,和线上索引里的Embedding版本不一致。第二,特征处理和归一化参数不一致,比如离线标准化用的均值方差来自昨天的数据,线上却用了今天的统计量。第三,相似度分数在离线结果和在线结果之间出现了偏移,导致模型训练时和推理时的分布差距过大。
我在团队里推动了向量版本管理机制:每次Embedding做发布时,打一个唯一版本号,并记录对应的离线特征版本和在线特征服务版本。上线时按版本号整体切换索引,不允许散落升级。这个机制落实之后,线上一致性相关的投诉基本绝迹了。
6. 验证与迭代:用指标检验相似度选择
6.1 离线评估:召回率、命中率与分层抽样
给相似度计算做离线评估,很多人只会看Online AUC或离线loss,其实对于召回阶段来说更直接的是两类指标。
召回率:全量交互样本里,有多少比例能在相似度召回TopK中找到。比如用户对物品b产生了点击,那么b是否会出现在与用户相似的其他物品候选集合里。这个指标直接反映召回的覆盖能力。
命中率:线上真实推荐里,候选集是否包含用户最终发生行为的物品。如果命中率低,说明召回通道根本没把用户想要的物品送上来。
还有一个容易被忽视的细节:离线评估时要分层抽样,不要只看全量平均。低活跃用户和高活跃用户的召回效果往往截然不同,分开统计才能发现相似度方法在哪个群体上失效。
6.2 线上灰度:分桶实验与指标解读
离线实验通过后,上线前还要做灰度实验。相似度计算的改动,最直接的线上指标是曝光点击率、人均推荐次数、多样性指数,甚至次日留存。
灰度过程中我会特别关注两个维度:分桶是否均匀,以及指标是否出现群体分化。曾有一次我们调整了用户Embedding的归一化方式,整体UV点击率没有明显变化,但按新老用户拆开看,新用户点击率涨了10%,老用户跌了3%。这个信息比整体指标重要得多,因为它决定了改动是应该全量上还是只对部分用户生效。
6.3 一个真实调参复盘:从余弦到皮尔逊的取舍
我之前参与过一个本地生活推荐项目,场景是推荐周边店铺,特征是用户对店铺的星级评分记录。第一版用余弦相似度,整体效果尚可,但经过分桶分析后发现,活跃评分用户和老用户的推荐结果都很正常,唯独“评分习惯明显偏向高分或低分”的用户群体召回效果不佳。
后来我们把相似度从余弦切换成皮尔逊相关系数,过滤掉共同评分不足5个的用户对,对这部分群体的召回命中率有了明显提升。代价是部分极度稀疏用户的召回通道出现空窗,于是我们又为这些用户单独加了一路基于地理位置的Jaccard召回通道。
最终方案是“皮尔逊为主体、Jaccard兜底”,在线指标比单一余弦方案整体提升了5%左右。这个调参过程让团队对相似度计算的理解发生了质变:没有哪一种方法通吃所有人群,组合通道才是工程上的常态。
6.4 长期迭代建议
相似度计算不是一个“选一次就完事”的组件。当用户行为分布、物品规模、Embedding模型迭代时,之前调好的相似度参数可能逐步失效。
我的建议是建立周期性的相似度质量监控:每周跑一次召回命中率和TopK多样性,稍微有点异常波动就去查数据分布是不是发生变化了。同时,相似度计算的代码和参数要写入配置中心,方便快速回滚和迭代,不要散落在代码里。
写在最后:一点实践经验
推荐系统里,相似度计算这个组件常常被低估。你可能花费大量精力调模型结构、优化训练样本,最后发现一个简单的方法切换带来的收益,比模型上的小改动更大。但也正因为它是底层组件,一旦选错,影响范围会覆盖整个召回层。
结合我自己的实践经验,可以给你几个直接能用的建议。
第一,在动手计算相似度之前,先画一条数据分布检查清单:特征是不是标准化了,向量是不是归一化了,共同非零项够不够多,稀疏比例高不高。这些基础检查做一遍,能避免八成以上的选型错误。
第二,不要只依赖一种相似度方法。多路召回、多方法融合,是应对复杂用户行为的务实手段。你可以用统一的相似度打分入口,内部支持多种方法切换,这样每一次调整的成本都会很低。
第三,相似度分数应该作为特征流入下游排序模型。很多团队只把召回结果当成一个候选ID列表,丢掉相似度分数,这等于把最有价值的信息弃用了。把召回分数保留下来,进入排序特征体系,往往能带来又快又稳的线上提升。
最后再分享一个小技巧:如果你在做向量召回,可以定期检查统一向量空间里不同类目、不同热度段的向量分布。向量分布一旦发生漂移,相似度结果也会跟着漂,这时候最应该做的不是换相似度函数,而是回头审视Embedding模型本身。把这层关系理清了,推荐系统的很多“玄学”问题都能回归到工程本身去解决。