一段不到二十行的 Python 函数,开头是 def recall(query, k=8, alpha=0.6)。参数摆得很直白:一次取 8 条结果,混合权重 0.6。剩下的问题只有一个——这 0.6 到底在混什么?
![]()
先把查询向量化,qvec = embed(query)。接着一条 SQL 打到 memories_fts 这张全文索引表上,用 MATCH 匹配查询词,取 bm25(memories_fts) 作为排序依据,ORDER BY rank LIMIT 50。也就是说,先靠关键词检索粗筛出 50 条候选,真正的精排在 Python 里做。
候选行里带回了三样东西:id、正文内容,以及一个 embedding 二进制块。代码用 struct.unpack 按小端浮点把它还原成向量,长度由字节数除以 4 推出。然后逐维相乘求和得到点积,再分别算两个向量的模长,相除得到余弦相似度。
到这里,两路信号齐了:一路是 BM25 给出的关键词排名,一路是向量算出的语义相似度。打分公式把它们缝在一起——alpha * (1/(1+rank)) + (1-alpha) * 余弦值。rank 越小,倒数越大,关键词命中越靠前得分越高;余弦值则负责把语义上接近的条目往上抬。
alpha 取 0.6,意味着关键词这一路的权重略高于语义一路。这个偏向不难理解:全文索引的排序结果相对稳定,而向量相似度在短文本上容易抖动,给它四成话语权,算是留了余地又不让它主导。
值得注意的是 k=8 与 LIMIT 50 之间的落差。50 条候选进来,最终只留 8 条,中间那 42 条被混合打分筛掉。候选池开得比输出大六倍多,说明作者并不信任单一路径的排序,宁可多捞一些再算账。
整段逻辑其实是一个很典型的混合检索骨架:
- 全文索引负责召回,保证关键词能命中;
- 向量余弦负责语义兜底,处理字面不重合的情况;
- 倒数排名做归一,把 BM25 的原始分数压到 0 到 1 之间;
- alpha 做权重旋钮,一处改动就能调两路配比。
用 1/(1+rank) 而不是直接拿 BM25 分数参与加权,是个务实的选择。BM25 的绝对值随语料和查询浮动,没法跟余弦值放在同一个量纲上相加;换成排名倒数之后,两路都落在相近区间,加权才有意义。
代码在打分那一行截断了,scored.append 的括号没有闭合。但从已有部分能看清意图:每条候选算出一个融合分数,塞进列表,后续自然是排序取前 k。函数签名里的 k 和 alpha 都给了默认值,调用方不传参也能跑。
这类写法在记忆系统里很常见——既要能按关键词精确找回某条记录,又要在用户换个说法时仍然捞得出来。全文索引管前者,向量管后者,一个 alpha 把两种性格拧在一根轴上。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.