Milvus × RAG助力快看漫画多业务应用

文摘   2024-07-26 17:30   中国香港  




01.

快看介绍


快看漫画创办于2014年,集漫画阅读、创作互动、线下漫画沉浸体验、周边衍生品购买等体验于一体,是年轻人的一站式漫画生活方式平台。截止到2023年底,快看总用户超过3.8亿,在中国漫画市场渗透率超过50%。经过9年的创作者生态建设,快看已汇聚超过12万注册创作者,发表漫画作品超13000部。目前,快看漫画已有超过500部作品登陆全球近200个国家和地区,成为中国文化出海的代表。



除了线上平台,快看还经常在线下组织沉浸式漫画乐园、漫画狂欢夜、漫画家见面会等活动,打造漫画粉丝们的嘉年华。



02.

Milvus在快看基础业务的应用



快看基础业务包括常见的搜索、推荐、广告,此外还包括图像相关的业务,比如图像去重、以图搜图等,为了更好的服务上述场景需求,需要构建向量检索系统,首先要做的一件事,就是确定一款向量检索引擎,我们当时也做了一些技术选型,包括 faiss、milvus、elasticsearch、vearch、proxima等。最后选定了 Milvus作为我们的向量检索引擎,因为milvus有如下的优点:

  • 开箱即用:几乎不用考虑基础建设,专注于业务侧工作。

  • 高性能:在 ANN向量检索 benchmark中排名靠前。

  • 高可用:Milvus 系统组件相互独立、隔离,能充分确保系统弹性和可靠性。

  • 混合查询:支持向量与结构化数据的混合查询。

  • 开发者友好:支持 Java、Python、Go等 sdk。

  • 分布式:Milvus 的分布式架构和高吞吐量特性使其非常适合处理大规模向量数据。

  • 监控报警:Milvus 会生成关于系统运行状态的详细时序 metrics。可以通过 Prometheus、Grafana等可视化工具进行观察。

  • 社区活跃:github star 28.5k+、社区人数5000+、贡献者260+。



Milvus的社区非常活跃,这也是我们考量的一个重点因素,在 Github issue上提一些问题,都能得到及时和耐心的反馈,并且有专门的微信运营服务群,在群里不仅可以反映自己遇到的问题,也能看到别人遇到的各种问题,还能起到相互学习的作用。


有了向量检索引擎之后,就可以根据向量检索的三要素搭建向量检索服务:


Embedding模型:通常是在自己业务数据上训练Embedding模型,如果是文本向量也可以使用开源的Text Embedding模型。之后就可以获取到每个物料的embedding向量,这里我们会对embedding向量先进行 L2归一化,再考虑入库建索引。


创建collection&index:入库在 milvus中的操作就是创建一个collection集合,然后对向量字段构建index索引,这些通过milvus的API可以很方便的完成。


向量查询:创建好索引后,就可以做向量查询了,向量查询本质上是将Query向量和collection中的所有向量进行相似度匹配,所以需要确定一个相似度指标,比如欧氏距离、Cosine相似度等。我们这里使用的L2欧式距离。其实最开始是想用Cosine相似度,但是当时Milvus还不支持,所以我们入库前先将向量进行L2归一化,查询的时候再使用欧氏距离,这种方式和直接使用Cosine相似度是成反比的,两种方案匹配出的结果都是一样的,只不过一个分数越小越好,一个分数越大越好。



我们最开始使用的索引是 Ivf_Flat,后面切换到了 HNSW索引,在我们的业务数据上做了一些实验,发现 Hnsw索引和 Ivf_Flat相比:

  • 两者在查询速度上差不多,细究下来,hnsw要快一点。

  • hnsw在不同@k下的平均召回率都要好于 ivf_flat,这个是业务上更加关心的点,所以后面就切到了HNSW索引。



以快看搜索场景为例,目前在多个场景应用了向量检索技术,搭建了如下的向量召回全景架构:



并在线上多个业务场景进行了实验,以数据量最大的社区帖子Feed流为例,应用了上述架构之后,在消费时长、下滑深度、曝光、点击等指标上均有不同程度的正向提升。



03.

快看RAG技术探索和应用


以ChatGPT为代表的大语言模型(LLM)在自然语言理解和生成任务上,展现了前所未有的能力,但是大语言模型(LLM)在特定领域任务中,会出现信息延迟和幻觉现象,检索增强生成(RAG)通过引用外部知识可以有效缓解这些问题,是LLM在工业领域应用的关键技术。


快看在大语言模型及RAG出现之后,迅速切入到以大语言模型为代表的技术领域,开始探索快看内部的应用场景,并在快看AI智能问答、IP角色互动两个场景取得了实质性进展,接下来以这两个场景为例,详细介绍下我们的技术方案。



  • 快看智能问答


这件事的背景是,大语言模型的对话模式,具有交互性强、沉浸式体验、拟人化等特点,是对传统搜索的一种创新,因此有必要搭建快看基于大语言模型的AI智能问答系统,期望提高作品的搜索转化率,为快看搜索带来新的活力。但是要实现这个事并不容易,主要有以下几个挑战:

  • 新目标新挑战:RAG是一个新的技术方向,当前业界并无成熟的落地方案可以参考,此外部署和训练大模型都需要较大的成本。

  • 数据在哪:构建知识库首先要有数据,尤其是文本类数据,但是企业内部有比较多的结构化数据库表,如何有效的利用这些数据,是值得深思的一个点。

  • 模型多:整个RAG系统中,涉及到的模型比较多,比如Embedding、Rerank、大语言模型,以及意图识别、实体抽取等环节都需要用到模型,需要对这些模型有一个全局的认识。

  • 如何评估:搭建好RAG系统后,就需要考虑如何来评估这个事的好坏,以方便进行迭代。

基于以上挑战,我们制定了在RAG方向的工作流程,包括知识挖掘、数据索引、检索模块、重排模块、评估模块五个关键阶段。



1.知识挖掘


我们的文本知识采取【内部数据为主,外部数据为辅】的策略:

  • 内部数据

  1. 社区UGC帖子:用户帖子中会包含对某个漫画的评价、剧情讨论、作者讨论、角色关系等,这些内容是对问答有正向帮助的。

  2. 快看增长业务问答数据:由标题、答案、背景知识等组成的正文。

  3. 结构化数据:MySQL、Mongo、ES等结构化数据库表,这部分数据我们有两种用法,一是将部分关键字段按照特定的文本模板拼成一个text段落,去构建文本知识库。另一种做法是利用Text2SQL的技术,直接去查询结构化数据库表进行生成。


  • 外部数据

外部数据主要是对内部数据进行定向的补充,以及爬取一些问答QA对。


有了原始数据后,就需要做数据清洗,得到和领域相关的干净的格式,同时保证数据的质量和多样性。为此,我们制定了下面的数据清洗pipeline,最后会按照不同的用途统一成不同的数据格式。



2.数据索引



数据清洗之后就可以构建知识库,我们内部知识库所包含的知识信息如上图所示,根据知识库的用途和专业度,我们划分为三种类型:

  • PGC作品知识库

  • UGC帖子知识库

  • 问答知识库

构建知识库的过程就是给文本数据构建索引,首先要基于清洗好的数据切分成不同的chunk块,每个chunk块称作是一个doc,这是构建向量的最小单位,然后使用Embedding模型在doc上编码出向量,存储到Milvus中并建好向量索引,此时一个知识库就构建好了。



除了Embedding的 dense索引,我们还会基于ES在每个知识库上构建倒排索引。



3.检索模块


所以检索的时候就包括ES索引和Milvus向量索引的混合检索,目前的混合策略是先进行ES前置检索,返回父层级的索引,然后在这些父索引范围内,进行更精确的doc层级的向量索引。



  • ES检索:通过paddle的实体抽取模型,从Query中抽取实体信息的关键字段,在ES检索的时候进行不同的加权,ES返回匹配的source文档集合,source可以认为是doc向量索引的parent_id。

  • milvus向量检索:对source集合内的每个子doc进行更精确的向量检索匹配,我们选取了3个异质化的Embedding模型,构建了三个collection集合,在这三个collection上并行检索topk,最后再通过RRF算法进行融合截断。


4.重排模块



Rerank重排模块还是很有必要的,重排和召回所关注的目标不一样,召回重点是在topk中能把真实相关的doc给捞出来,而重排则需要把真实相关的doc排在前面,同时减少topk的数量。


rerank模型一般是Cross-Encoder架构,就是把query和每个候选doc组成一个pair对,对每个pair进行相似性打分,所以rerank模型会有一点性能上的开销,因为要把query和每个候选doc进行逐一的打分,但是匹配结果会更精准。


Rerank的排序结果,一般会取一个小的topk进行截断,比如top3,因为后面要丢到大语言模型中进行生成,doc数量比较多会对大模型推理造成干扰,并且会有token的开销。其实这里的终极目标应该是只包括真实相关doc不包含噪声doc,但是这个目标任重而道远,我们所做的模型和策略都要向这个终极目标靠拢,只要能向这个终极目标更近一步,那就是一个更好的优化。


我们在重排之后,并不会把top3 doc丢到大语言模型里进行生成,因为这top3 doc,我们看作是3个定位锚点,接下来会以每个锚点为中心进行上下文扩展,增强背景信息,但是在扩展的过程中要注意几个细节,比如docId对齐、doc去重、扩展边界、doc还原顺序等等。扩展后的内容会合并成一个context,丢到大语言模型中进行生成。



5.评估模块


我们从三个维度来评估RAG系统,分别是召回、重排和端到端评估。


5.1 评估召回


召回阶段关注的是,在不同@k下捞出真实相关doc的能力,所以我们主要看 recall指标,这里我们关注的是整个召回模块,这个模块的输入就是给定一个Query,输出topk 个docs。这里给出一些我们的实验结果,供大家参考。



  • 左边的图是最初只使用开源的 m3e-base模型做召回的评测结果,这个结果作为baseline。

  • 中间的图是对m3e-base模型进行微调后的评测结果,提升还是很明显的,Recall大概有20%的提升。

  • 右边的图是使用微调后的 m3e-base模型,加上ES索引前置检索的混合检索,相比 baseline大概有32%的提升


5.2 评估重排


重排的目标是把真实相关的doc排在前面,所以谁能把这个事干的越好,谁就是好的模型/策略。


我们的做法是先把一批召回结果保存下来,然后让不同的重排模型/策略基于这份结果进行排序,为了方便对比,我们的baseline就是不使用任何排序模型,直接对召回后的结果进行topk截断。下面是我们的一些实验结果:



  • 左边的图是 baseline结果,直接对召回结果进行topk截断。

  • 中间的图是使用开源的 bge-rerank-base模型,在排序指标上反而是有下降的。

  • 右边的图是对bge-rerank-base模型进行微调后的结果,相比于baseline,map有5%的提升,相比于不微调,map有8%左右的提升。


5.3 端到端评估



端到端评估,就是把RAG系统看作黑盒子,这个黑盒子的输入是一个query,输出是一个answer和引用docs,根据系统的输入和最终的输出进行评估。这种端到端评估我们认为有两种方式,一是从生成角度评估<真实答案,生成答案>的相似性,二是从三元组的角度进行评估。


  • 评估 <真实答案,生成答案>:这种方式本质上是在看两个字符串的相似度,前提是需要准备一批带ground truth答案的QA对作为测试集,然后逐一评测真实的A和RAG系统生成的A' 之间的相似度,这里我列举了几个相似度评价指标,比如Rouge、EM、BLEU、embedding sim等,但是具体要以哪一个指标为准,这个也没有一个共识,倒是有一些研究会把这几个指标进行加权融合,算出一个融合分数,以最后的融合分数为主。

  • 评估<query,context,answer>

    目前一些开源的RAG评估框架,是从这个三元组的角度来评测的,比如 ragas,如果是评 retrieval侧的指标,同样需要先准备一批带 ground truth的 QA对,评 generation侧的指标则不需要。不过 ragas中使用了LLM作为打分器,由于LLM输出结果的不稳定性,会导致算出来的指标值有差异,这就需要考虑稳定性和置信度的问题了。


总体上,我个人不太建议使用端到端的方式进行评估,看似比较省事,实则有很多麻烦需要考虑,比如:

  • 要事先准备一批带 ground truth答案的QA对作为测试集,这个就比较消耗人工成本, 而且标准答案不好制定,比如”中国的首都是哪“ 这个问题,可以把”北京“两个字作为答案,也可以把”北京是中国的首都“作为答案,但是答案字符串的长短、字数、表达方式等,都会影响到评测指标的变化。

  • 大语言模型的生成有一个特点,就是同一个输入Prompt,模型每次生成的答案字符串是不一致的,具体表现在答案的长短、字数、表达方式等,那就导致同一份测试集,第一轮评测和第二轮评测的指标值是会变化的,需要考虑指标稳定性和置信度的问题。当然如果像ragas中那样,借助LLM来打分,那就需要考虑双重稳定性和置信度的问题了。


所以我个人更建议从召回和重排的结果上去评测,因为这个结果输出是稳定的,评测指标也是稳定的,而且RAG的核心是检索,检索对了模型才有可能回答对,至于生成端的问题,就和检索无关了,可以去调prompt或者换更好的生成模型来解决。


6.高级RAG


上面所说的算是一个比较标准的RAG流程,但是企业应用中的数据和用户query都是多种多样的,为了解决更复杂的业务case,我们做了一些改造,包括query转换、检索时机意图识别、Text2SQL等。


6.1 query转换


用户的query实际是多种多样的,有简单的query,也有复杂的query,我举几个例子:

  • Q1:偷偷藏不住的作者是谁?

  • Q2:偷偷藏不住和难哄是一个作者吗?

  • Q3:偷偷藏不住的作者还写过哪些作品?

其中Q1类型的问题,我们称之为简单的单点问题,目前在这类问题上,我们的RAG已经可以很好的回答了。比较难处理的是复杂类型的问题,比如Q2和Q3,我们称之为多跳类问题,针对问题Q2,实际上需要先做query分解,分解为两个子问题,即【偷偷藏不住的作者是谁】和【难哄的作者是谁】,然后这两个子问题并行去做检索,最后将各自检索到的docs汇总在一起,丢给大语言模型进行生成。针对问题Q3,则需要做串行分解,因为有依赖关系,先根据【偷偷藏不住的作者是谁】去做检索和生成,根据答案再组成第二个子问题【XX还写过哪些作品】,再去做一次检索和生成。


所以加了query转换模块后,我们的流程是这样的,用户输入一个query,首先会并行的做几个转换:

  • 普通的query改写

  • hyde生成假设答案

  • query分解

如果query不能分解,则以前面两个转换为主,继续后面的RAG流程,如果能分解,则以分解的结果为主,同时还要看是并行的分解,还是串行的分解:

  • 并行分解:并行执行多个子问题的检索,最后将检索结果汇总成context进行生成

  • 串行分解:进行迭代式的检索生成



6.2 检索时机


有的query是不需要在私域知识库上进行检索的,比如【中国的首都是哪】这种常识类问题,或者用户输入【嗯嗯】这种语气句,因此有必要做检索时机的判断,在需要检索的问题上执行RAG,不需要检索的问题则直接调用大语言模型进行生成,这样也可以减少后端不必要的调用,提高前端响应速度。但是在这个问题上没有很好的解决方案,我们做了一些探索,可以分享下我们的方案,供大家参考。



我们目前的做法是先用大语言模型进行判断,会在 prompt中写清楚知识库的范围,这个prompt会写的比较严格,尽量让判断为检索的问题确实是需要检索的。判定为不需要检索的问题,则进行第二关句型维度的判断,我们做了一个假设,就是用户的输入如果是疑问句和祈使句大概率是需要检索的,其它句型如陈述句、感叹句、语气句等,则不需要检索,直接调用大语言模型进行生成即可,为此我们专门训练了一个Bert的句型分类模型。当然,这种做法仅供参考,大家如果有更好的意图识别方案也欢迎交流分享。


6.3 Text2SQL


此外在RAG方向上,我们还结合了Text2SQL的技术,因为企业通常会有很多结构化的数据表,这些表是企业非常重要的数据资产,但是生产环境的数据表之间有非常复杂的关联,目前LLM还不能很好的处理这些关联关系。而快看有自建的 Elasticsearch引擎,相当于是聚合了很多业务表的大宽表,因此我们探索了通过大语言模型生成标准SQL语句,继而来查询ES引擎关键字段进行生成的方案,期望通过这种方式来回答用户实时性和客观性的问题,增强模型回答的准确度和事实性。比如【某某作品更新到第几话了】、【现在最流行的漫画是哪个】这种实时性和客观性的问题,其实在ES中是有特定的字段可以回答的,查出来后再结合字段的物理意义,组装成prompt让大语言模型进行生成。


但实际在执行的时候就遇到了不少问题,比如 ES需要用DSL语法来查询,而大语言模型生成SQL语句还勉强可以,生成DSL就完全不能用了,所以需要做SQL到DSL的翻译,好在ES官方提供了翻译的API,但是这个API又有些限制,不能翻译带select嵌套的SQL,所以我们又做了一层SQL语句的select分解,分解成多个子sql,每个子sql会先转成 dsl再去查es,将查询到的结果回填到下个子sql,重复这个过程直到最后一个子sql的结果,将查询结果和用户query组装成prompt,让大语言模型进行推理生成。



6.4 整体方案



所以结合了query转换、检索时机、Text2SQL三个改造,我们的整体方案是这样的:

  • 用户query先进行检索时机的判断,如果不需要检索,则直接调用大语言模型进行生成,如果需要检索,则并行的执行 RAG和Text2SQL两个分支。

  • 如果 Text2SQL分支有返回结果,则以这个分支的prompt为主进行生成,有返回结果大概率是置信的,否则在中间过程就会出错了,比如生成sql有问题、转换dsl有问题、查询es有问题等等。

  • 如果 Text2SQL分支没有返回结果,则以RAG分支的prompt为主进行生成,这个分支就包括了刚才提到的query转换模块,转换之后就是标准的检索、重排等等。


7.大模型微调



快看作为二次元领域的龙头,积累了非常多的领域知识,因此也在尝试基于开源大模型进行微调,训练快看在二次元领域的垂直大模型。此外快看还基于自身的数据优势,制作了一份二次元领域的基准评估数据集,并开放到了 Huggingface平台

 https://huggingface.co/datasets/gctian/comic-eval-benchmark),欢迎贡献更多二次元领域语料及二次元大模型,如需评测请联系作者获取评测脚本。


以下是我们基于 Baichuan2-13b大模型微调后的评测结果,分别在zero-shot和3-shot下进行了评估,相比于不微调,均有不同程度的提升。



  • IP角色互动


快看作为国内最大的漫画平台,拥有超过1.3万部漫画作品,聚集了国内80%的头部优质IP,这种IP优势是独一无二的。我们希望通过这些IP衍生出IP角色互动类的应用,用户可以跟IP中的角色进行沉浸式聊天,而不是纯虚拟角色的聊天,这是我们IP角色互动和市场上常见的虚拟角色扮演类应用不同的地方。


而这背后的实现方案就是角色扮演大模型+知识库RAG,知识库又分为原创剧情知识库和用户个性化记忆知识库,原创剧情知识库是AI角色在IP剧情中的记忆,包括角色的成长经历、故事线、社交关系等,这个知识库也是快看最大的优势,用户个性化知识库是由每个用户和AI角色的聊天历史组成的。



1.构建知识库


整体RAG方案和上面讲的类似,这里说一下两个知识库的构建策略。首先是剧情知识库,我们会将剧本做三级切分,即chapter-session-chunk,在每个chunk块上利用LLM抽取summary、question以及一些结构化字段,将这些信息构建向量索引存储到 Milvus中作为剧情知识库,但有很多细节要注意,比如脏数据过滤、故事线划分、业务字段过滤、chunk长度、大模型抽取准确性等。



用户和AI角色的聊天历史,首先会切分成对话session,每个session作为一个chunk,利用LLM在session上抽取对话摘要,然后构建摘要的向量索引存储到 Milvus中。



当用户输入一个query后,会同时检索这两个知识库,将query、检索到的剧情块、对话列表组装成prompt,调用角色扮演大模型进行生成,相当于模型既能知道角色的剧情记忆又知道和用户短期内的聊天记忆,如果聊天历史比较短,也可以不检索,直接带上最近N条聊天历史即可。


2.知识库评测


在原创剧情知识库方案上,我们也使用了头部厂商的知识库方案进行构建,并和我们自建的知识库进行对比,使用原创剧情上提取出的5000个问题作为测试集,评测不同知识库方案的剧情召回能力。评测结果显示,我们自建知识库在各个指标上均超过了头部厂商的知识库方案。



此外在角色扮演模型评测方向,快看也做了很多工作,包括人工的主观评估和定量的自动化评估:

  • 人工主观评估:快看内部开发了用于人工pk的匿名pk平台,可以来评估不同模型的角色扮演能力。



  • 自动化评估:虽然目前有一些角色模型的评测指标,但是这些指标在置信度和稳定性上都还不够完善,因此快看选择自训Reward打分模型,基于打分模型开发了自动化单轮pk评估和自动化多轮pk评估体系,假设总共收集300轮票数,自动单轮pk就是两个模型分别和某个角色聊一轮,总共聊300个角色。自动多轮pk就是两个模型分别和角色深度聊N轮,N是随机的,聊完就换下一个角色,直到收集到300轮票数。经过验证,这套自动评测体系和人工pk结果是一致的。



04.

RAG经验分享


这里分享下RAG的一些经验吧,其中有的是我实践过程中踩过的坑,有的是个人思考,不一定对,仅供参考。



作者介绍

田贵成

快看资深算法工程师,

目前负责大语言模型方向的探索和落地。


推荐阅读



Zilliz
Simply The Fastest Vector Database for AI. Period.
 最新文章