欢迎回来
登录你的知识库账户
忘记密码?
还没有账户?立即注册
创建账户
注册你的专属知识库
已有账户?去登录
找回密码
输入注册邮箱获取验证码
返回登录
请输入图片中的验证码以继续注册
加载中...
取消
新建收藏
手动添加你喜欢的内容
取消
编辑头像与昵称
上传新头像或修改你的显示昵称
支持 JPG/PNG,最大 2MB
取消

问题反馈

notebasewww.notebase.cn
控制台
内容库
动态
管理
账户
U
用户
--
在线
v0.8.7 · 知识库
笔记
KnowledgeBase
网络无边,知识有迹。
0笔记
0工具
30推荐

分类导航

按主题直达

编辑精选

站内用户贡献 · 真实笔记

最新收录

每日更新
继续浏览全部内容 →
>
笔记
0
加载中...
工具
0
此页用于记录用户反馈问题后的每一次改进
笔记用法

“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。

md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。

要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。

工具用法

在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。

要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。

工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。

隐藏笔记

写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。

适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。

数据安全

你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。

技术

全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。

理念

这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。

原则

不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。

更多

产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。

举报

如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。

趋势
// 点击导航加载发现
归档
// 归档为空
最近浏览
// 暂无浏览记录
发布
// 加载中...
用户发布
// 加载中...
用户管理
// 加载中...
访问统计
// 加载中...
内容审核
// 加载中...
个人信息
// 加载中...
返回首页

知识库检索匹配的服务化实践

2022/10/28技术教程

背景

知识库就是企业面向客户和内部员工沉淀的文档库,里面各种教程、问答、案例啥的都有。知识库的检索匹配,说白了就是 NLP 里的一个基础问题——文本语义相似度计算,也叫语义匹配。

其实很多业务场景都能抽象成这个问题:搜索引擎、智能客服、知识检索、信息推荐等等。

简单来说就是:给你一个 query,然后有一大堆知识库文档,你要从里面找出最匹配的 TopK 个文档。

整体架构

(这里原文有图,我就不画了,直接说流程)

整体分两层:召回 和 精排。

  • 召回阶段:先用传统文本召回(ES),再加上向量召回(Milvus),保证能捞出足够多的候选
  • 精排阶段:对召回结果做二次排序,把最相关的排到前面

请求链路大概是:用户输入 query → DSL 改写 → 文本召回 + 向量召回 → 粗排(其实就是合并去重) → 精排 → 返回结果

算法模型

1. DSL 改写

接手之前,业务方已经自己调过 ES 的字段权重了,但效果到了瓶颈,再调也没啥大提升。

换个思路:从知识运营的角度,把运营认为重要的文档推到前面。文档之间互相有链接引用,这不就是 PageRank 的典型场景吗?

PageRank 的核心思想:被引用次数越多的文档越重要。

举个栗子,假设有四个网页 A、B、C、D:

A ← B
A ← C
B ← D
C ← D
D ← A

箭头表示从源网页能跳转到目标网页。比如 B→A,说明 B 引用了 A。

假设初始 PR 值都是 0.25,L(B)=2(B 链出了两次),L(C)=1,L(D)=3,那么:

PR(A) = (1-0.85)/4 + 0.85 * (PR(B)/L(B) + PR(C)/L(C) + PR(D)/L(D))
      = 0.0375 + 0.85 * (0.25/2 + 0.25/1 + 0.25/3)
      = 0.458

反复迭代直到收敛,就得到每个文档的最终 PR 值。

然后拿来改写 DSL:

new score = old score * log(1 + 2 * PageRank)

这里的 old score 是原来 ES 算出来的 BM25 分数,PageRank 缺失的文档用 1 代替。

2. 文本召回

最基础的召回方式。对 query 分词,拿关键词去 ES 的倒排索引里做 tf-idf 匹配。

优点:实现简单、不用训练模型、资源需求低、速度快。

缺点:忽略语法结构,解决不了一词多义和同义词问题。比如搜“苹果”,到底是水果还是手机?文本召回搞不定。

3. 向量召回

为了解决语义问题,上向量召回。

思路:把 query 和文档标题/相似问都转成向量,算余弦相似度,取 TopK。向量检索用 Milvus,性能更好。

模型结构:

query → Embedding → Transformer × 2 → MLP → L2归一化 → 向量
文档  → Embedding → Transformer × 2 → MLP → L2归一化 → 向量

典型的双塔结构,左右塔结构分离但参数共享。左塔算 query 向量,右塔算文档向量。

双塔的好处是天然适合召回:离线把海量文档都转成向量刷到 Milvus,在线只算 query 向量,然后去 Milvus 里搜最近邻就行。

向量召回流程:

  1. 离线:DP 平台跑任务,计算文档标题和相似问的向量,导入 Milvus
  2. 在线:query 过小盒子得到向量,去 Milvus 召回 TopK
  3. 因为 Milvus 对 string 类型不太友好,所以召回结果还要去 MySQL 里补全信息

4. 精排序

召回和粗排之后,文档基本是相关的了,但跟用户真正的意图还有差距。这时候用用户的点击数据来优化排序。

训练数据:用所有场景的用户点击数据,有点击就是正样本,没点击就是负样本。

query: "满减送没有赠品"
正样本: "满足条件满减送没有赠品"
负样本: "订单满减规则说明"

采用 in-batch 负采样,不需要提前构造负样本。一个 batch 里的其他文档自动成为当前 query 的负样本。

模型设计:

还是双塔结构,query 和文档分别过编码器得到向量,然后算相似度。损失函数用 InfoNCE:

L_i = -log( exp(sim(z_i, z_i+)/τ) / Σ_j exp(sim(z_i, z_j)/τ) )

分子是正例对的相似度,分母是正例 + 所有负例。最小化这个 loss,就是让正例对更相似,负例对更不相似。

在 batch 内,label 可以直接生成:batch_size=4 时,每行 label 就是 [0,1,2,3],对角位置是正例。

训练好之后,得到一个文本编码器,输入两个文本就能算出匹配分数。部署到小盒子,排序时把候选文档和 query 一起送进去算分,按分排序。

5. 排序优化

上面说的精排,每次只传 20 个文档(一页)到小盒子算分,没问题。

但每个文档还有多个相似问,只用标题不够稳。如果加上全部相似问,那每次要算的文档数就不固定了——少则 20,多则几百。

几百个 batch 走小盒子很容易超时,就算切成小 batch,一个失败全失败,RT 也没降多少。

解决方案:跟向量召回一样,改成输入一个文本输出一个向量。

每个文档的标题和所有相似问向量,跟 query 向量算相似度后取均值。等价于先算文档的均值向量,再跟 query 算相似度。

这样排序任务就变成了向量召回任务:

  1. 离线算好每个文档的均值向量
  2. 在线 query 过小盒子得到向量
  3. 去 Milvus 里搜,同时用 ES 召回和向量召回的结果做过滤
  4. 一次计算搞定,RT 和 QPS 都比小盒子版本好

局限性:

  • 只能优化“均值”这个逻辑,如果要取最大相似度就不行了
  • query 和文档只在最后算一次相似度,交互太少,效果可能不如多次交互的模型

工程实现

线上流程:

query → 小盒子算向量 → Milvus 召回 → 合并 ES 召回 → 精排 → 返回

离线训练(DP 平台)

海量知识库的向量化在自研 DP 平台跑离线任务:

  1. 语料预处理:文本清洗、筛选
  2. 向量化:用向量计算模型转成向量
  3. 导入 Milvus:批量导入集群,保证线上可用

在线推理(Sunfish 平台)

自研的算法平台,支持分布式训练、GPU/CPU 切换、模型版本管理、一键部署。

请求示例:

{
  "inputs": [
    {
      "name": "INPUT",
      "shape": [1, 1],
      "datatype": "BYTES",
      "data": ["满足条件满减送没有赠品"]
    }
  ],
  "outputs": [
    {
      "name": "OUTPUT"
    }
  ]
}

Milvus 向量检索

Milvus 是开源的向量相似性检索引擎,集成了 Faiss、Annoy 等索引。十亿级别向量检索毫秒级响应。

索引选择原则:

场景 推荐索引
数据量小,要 100% 召回率 FLAT
高性能 + 高召回率 IVF_FLAT
高性能 + 资源有限 IVFSQ8H
只有 CPU IVFSQ8

距离计算方式:

主要用内积(IP)。因为向量做了 L2 归一化,内积等价于余弦相似度,所以 Milvus 没有单独提供余弦距离。

内积距离 = A · B = ||A|| * ||B|| * cosθ
归一化后:||A|| = ||B|| = 1,所以内积 = cosθ

AI 模型接口服务

分两个服务:

  • ai-service:负责调用模型推理、Milvus 召回等底层操作
  • ai-app:负责业务逻辑

ai-service 配置示例:

{
  "model_name": "similarity_jira",
  "model_source_type": "YZ_MODEL",
  "model_version": 1,
  "model_invoke_timeout": 3000,
  "protocol": "kfserving",
  "infer_type": "triton",
  "feature_maps": [
    {
      "model_feature_key": "INPUT",
      "data_type": "string",
      "shape": "(-1,1)",
      "default_value": "",
      "feature_source": "PARAMS",
      "source_key": "jira_text",
      "is_required": 1
    }
  ],
  "param_mapping": {
    "jira_text": "<objectList.jira_text>"
  }
}

ai-app 接口示例:

Maven 依赖:

<dependency>
  <groupId>com.youzan</groupId>
  <artifactId>ai-app-api</artifactId>
  <version>1.0.13-RELEASE</version>
</dependency>

请求:

invoke com.youzan.ai.app.api.service.jira.Service.retrieve({
  "fromApp": "test",
  "scene": "similarity_predict",
  "Title": "满足条件没有赠品",
  "Key": "XXX"
})

返回:

{
  "code": 200,
  "data": {
    "Similaritys": [
      {"createdAt": 1648137600000, "score": 0.9390, "key": "XXX0123442334", "title": "满足条件没有赠品"},
      {"createdAt": 1636214400000, "score": 0.9010, "key": "XXX0123365819", "title": "满足条件没有送赠品"},
      {"createdAt": 1653408000000, "score": 0.8735, "key": "XXX0123482446", "title": "订单满足条件没有送赠品"},
      {"createdAt": 1655308800000, "score": 0.8312, "key": "XXX0123496337", "title": "订单满足条件但是没有送赠品"},
      {"createdAt": 1659628800000, "score": 0.8028, "key": "XXX0123527965", "title": "订单满条件但是赠品没有送"}
    ]
  },
  "success": true,
  "message": "successful"
}

服务场景

目前主要用在两个场景:

  1. 官网帮助中心:用户搜问题,返回最相关的帮助文档
  2. 相似商品推荐:根据商品描述推荐类似商品

总结

整体做下来,其实就是一套“召回 + 精排”的经典 NLP 流程,只不过针对知识库场景做了一些定制:

  • DSL 改写用 PageRank 引入了运营意图
  • 向量召回解决了语义匹配问题
  • 精排用对比学习优化了排序
  • 工程上做了低代码封装,方便快速接入

后续值得关注的点:

  1. 冷启动问题:低频或新文档的 Embedding 学习不够充分,可以考虑用图算法聚合更多语义信息
  2. Embedding 算法:可选方案很多,可以考虑把词嵌入模型模块化内置
  3. 性能稳定性:高准确率 + 高 QPS 下的响应性能永远是核心

最后打个广告:有赞数据中台团队,数据平台研发和数据开发岗位都在招人,感兴趣的同学可以关注官网。

编写使用方法
Markdown 格式 · Ctrl+Enter 确定
新建笔记
预览
数据表格
点击单元格编辑 · Tab 移动
A1fx
Sheet1
BIH1H2≡🔗</>
隐私提醒

取消
编辑工具
取消