RAG

本实现类型采用 RAG (Retrieval Augmented Generation) 检索增强生成技术,构建了相关的知识数据库,再与检索器提示工程等技术结合,来提升大模型的知识能力。

此外,根据检索方式,我们又将其派生出 Sparse(稀疏)和 Dense(稠密)两种具体的实现类型,这两种实现会在检索效率检索质量进行平衡。

处理逻辑

检索增强生成的流程可以直观地分为检索 -> 增强 -> 生成。此外,由于本项目仅搜集了中大相关的知识数据,因此检索时可能会出现无效检索的情况(也就是无法检索到与问题相关的文本),因此我们额外引入了兜底策略的逻辑。

def talk(self, query: str) -> str:
    if self._searcher is None:
        return self._caller.single_call(query)

    # 1. 使用 searcher 进行向量检索
    data = self._searcher.search_with_label(query, 3)

    # 2. 当检索效果不好时执行兜底策略
    if len(data) == 0:
        return self._last_strategy(query)

    # 3. Prompt 生成
    final_prompt = self._generate_prompt(query, data)

    # 4. 生成最终回答
    return self._caller.single_call(final_prompt)

1. 检索

检索部分主要分为数据集制作检索方式两个部分。

(1) 数据库制作

想要完成检索任务,首先我们需要有相对应的数据。下面简单介绍一下如何收集校庆数据,以及数据的格式:

  1. 确定数据结构:首先,我们需要确定数据库的数据结构。为了便于后续的 ElasticSearch 索引构建,我们选择使用 JSON 格式来构建数据库,以使用键值对的方式组织数据,非常适合表示结构化的数据。

  2. 设计数据字段:我们可以将每个数据项视为一个对象,其中包含三个字段:query、document 和 metadata。

    • query 字段表示查询的关键词或问题

    • document 字段表示查询结果的文本或内容

    • metadata 字段表示与查询结果相关的元数据/关键词信息。

  3. 收集数据:我们在中山大学官网中大百年校庆官网中山大学百度百科、校园公众号、视频号等平台,收集有关校园生活、人文历史、招生政策、院系分布等数据,并将数据拆分为 300+个 JSON 对象。 下面是一个示例:

    "1":{
        "query":"中山大学成立的校史",
        "document":"1924年,孙中山亲手将广州地区多所高校整合创立国立广东大学。1926年定名为国立中山大学。如今该校由1952年院系调整后分设的中山大学和中山医科大学于2001年10月合并而成。",
        "metadata":"校名,校史,简介,发展史,改称"
    },

(2) 检索方式

检索方式主要分为 Dense 和 Sparse 两种,两种比较如下。具体技术细节请查看 检索器 Searcher

Dense
Sparse

检索对象

文本向量

文本

检索速度

较慢

较快

检索效果

较好

较差

实现

FastText, Vector

ElasticSearch

2. 增强

为了将检索的相关信息用于增强大模型能力,我们利用大模型强大的 上下文学习(In-context Learning) 能力,通过提示词工程来实现。 因此,我们定义了如下的 prompt 模板:

system_prompt = \
"""### 示范一
[用户问题]
国际学生政策是怎样的?
[参考资料]
参考资料1:
标题:国际学生政策
内容:详情可见中山大学留学生办公室官网
[回答]
国际学生政策,官网有最权威的信息哦!快去中山大学留学生办公室官网看看吧,你会有新发现的!

### 示范二
[用户问题]
可以介绍一下你自己嘛?
[参考资料]
参考资料1:
标题:中山大学校校歌歌词
内容:白云山高,珠江水长,吾校矗立,蔚为国光,中山手创
[回答]
嗨!我是中小大,中大软件工程学院的大三学生,也是中大介绍官。我超爱写代码和阅读历史书籍!

### 示范三
[用户问题]
为什么皮卡丘喜欢放电
[参考资料]
参考资料1:
标题:中山大学的微电子科学与技术学院本科招生专业
内容:微电子科学与工程
参考资料2:
标题:中山大学的集成电路学院本科招生专业
内容:微电子科学与工程
[回答]
很抱歉,我是中大介绍官,不能回答关于皮卡丘的问题哦~

### 开始任务
[用户问题]
{query}
[参考资料]
{data_str}
[回答]"""

data_prompt = \
"""参考资料{i}:
标题:{query}
内容:{document}
"""

模板包含两个部分:

示范(demonstration): 通过 Few-shot Learning,增强大模型的指令遵循能力。 我们提供了三个范例,分别对应三种情况:

  • 问题与检索内容相关,可以根据问题与检索内容回答。

  • 问题相关,检索得到的内容不相关,只根据用户问题。

  • 用户问题与中大无关,不应该回答。

检索增强内容:[参考资料] 的形式给到模型。

3. 生成

将最终融合的 prompt 给到调用器 Caller 生成答案。

4. 兜底策略

兜底策略并不会是任何 RAG 对象都是生效的,此处在实现时需要进行区分。例如以下 HyDE 兜底策略通常效果会在 Dense 中更好。

Hyde 兜底

Hyde(Hypothetical Document Embeddings,假设性文档嵌入)通过使用一个大语言模型,在响应查询的时候建立一个假设的文档。 通过计算假设文档的向量而在向量数据库中搜索。 该方法来源于论文 Precise Zero-Shot Dense Retrieval without Relevance Labels

HyDE 考虑到的是在 查询-回答 任务中,查询回答 的相似度可能不高,不如生成一个假设的回答,从而通过这个假设的回答在向量数据库中进行检索。

代码实现上也非常简单:

# 基本的检查...

# 1. 生成 hyde 假设性回答
query = searcher.prompt_template.format(query=query)
query += caller.single_call(query, False)

# 2.使用 hyde 检索得到 top-k 相似结果
retrieve_res = searcher.search_with_label(query, k)

return retrieve_res

效果

部份来自用户的 query 可能包含的关键词带有一些昵称,比如 鸭大。 这种时候,直接使用 Searcher,可能无法检索到相应的结果。 比如下面的例子

[query]
send "鸭大什么水平" 

[w/o HyDE]
[] (超过相似度阈值的结果为空)

HyDE 在这时启动,生成的假设性文档嵌入以及检索结果如下:

[HyDE]
请回答用户关于中山大学信息的查询
查询: 鸭大什么水平
回答: 中山大学作为一所知名的综合性大学,位于广东省广州市,是中国一流大学之一。学校设有多个学科门类,涵盖文、理、工、医、法、经、教育等多个领域。中山大学在教学水平、科研水平以及社会影响力等方面 均属于国内前列大学,被广泛认可为高水平的学府之一。如果您对中山大学有更具体的问题,欢迎继续提问。

[w/ HyDE]
1. 中山大学简介...
2. 中山大学的办学情况,校区分布...
3. 中山大学的办学层次...

可见,HyDE 技术能够在用户查询措辞较为“奇特”的情况下,很好地完成检索工作。

最后更新于