修改项目结构+7,4 一部分
86
docs/chapter7/7.1 LLM的评测.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# 7.1 LLM 的评测
|
||||
|
||||
近年来,随着人工智能领域的迅猛发展,大规模预训练语言模型(简称大模型)成为了推动技术进步的核心力量。这些大模型在自然语言处理等任务中展现出了令人惊叹的能力。然而,要准确衡量一个大模型的性能,必须依靠科学而合理的评测。
|
||||
|
||||
什么是大模型评测?大模型评测就是通过各种标准化的方法和数据集,对大模型在不同任务上的表现进行量化和比较。这些评测不仅包括模型在特定任务上的准确性,还涉及模型的泛化能力、推理速度、资源消耗等多个方面。通过评测,我们能够更全面地了解大模型的实际表现,以及它们在现实世界中的应用潜力。
|
||||
|
||||
大模型的开发成本高昂,涉及大量的计算资源和数据,因此评测对于确保模型的实际价值至关重要。首先,评测能够揭示模型在各种任务中的表现,帮助研究人员和企业判断模型的适用性和可靠性。其次,评测可以暴露模型的潜在弱点,例如偏见、鲁棒性问题等,从而为进一步优化和改进提供依据。此外,公平、公开的评测还为学术界和工业界提供了一个共同的标准,促进了技术的交流与进步。
|
||||
|
||||
## 7.1.1 LLM 的评测数据集
|
||||
|
||||
在大模型的评测过程中,使用标准化的评测集至关重要。目前,主流的大模型评测集主要从以下几个方面进行评估,每个评测集都有其独特的用途和典型应用场景:
|
||||
|
||||
1. **通用评测集**:
|
||||
- **MMLU(Massive Multitask Language Understanding)**:MMLU评测模型在多种任务中的理解能力,包括各类学科和知识领域。具体包含了历史、数学、物理、生物、法律等任务类型,全面考察模型在不同学科的知识储备和语言理解能力。
|
||||
|
||||
2. **工具使用评测集**:
|
||||
- **BFCL V2**:用于评测模型在复杂工具使用任务中的表现,特别是在执行多步骤操作时的正确性和效率。这些任务通常涉及与数据库交互或执行特定指令,以模拟实际工具使用场景。
|
||||
- **Nexus**:用于测试模型在多步骤操作中的工具使用能力,主要评估其在多任务操作中的协调性和任务管理能力,如进行文件操作、数据整合等复杂流程。
|
||||
|
||||
3. **数学评测集**:
|
||||
- **GSM8K**:GSM8K是一个包含小学数学问题的数据集,用于测试模型的数学推理和逻辑分析能力。具体任务包括算术运算、简单方程求解、数字推理等。GSM8K中的问题虽然看似简单,但模型需要理解问题语义并进行正确的数学运算,体现了逻辑推理和语言理解的双重挑战。
|
||||
- **MATH**:MATH数据集用于测试模型在更复杂的数学问题上的表现,包括代数和几何。
|
||||
|
||||
4. **推理评测集**:
|
||||
- **ARC Challenge**:ARC Challenge评测模型在科学推理任务中的表现,尤其是常识性和科学性问题的解答,典型应用场景包括科学考试题解答和百科问答系统的开发。
|
||||
- **GPQA**:用于评测模型在零样本条件下对开放性问题的回答能力,通常应用于客服聊天机器人和知识问答系统中,帮助模型在缺乏特定领域数据的情况下给出合理的回答。
|
||||
- **HellaSwag**:评测模型在复杂语境下选择最符合逻辑的答案的能力,适用于故事续写、对话生成等需要高水平理解和推理的场景。
|
||||
|
||||
5. **长文本理解评测集**:
|
||||
- **InfiniteBench/En.MC**:评测模型在处理长文本阅读理解方面的能力,尤其是对科学文献的理解,适用于学术文献自动摘要、长篇报道分析等应用场景。
|
||||
- **NIH/Multi-needle**:用于测试模型在多样本长文档环境中的理解和总结能力,应用于政府报告解读、企业内部长文档分析等需要处理海量信息的场景。
|
||||
|
||||
6. **多语言评测集**:
|
||||
- **MGSM**:用于评估模型在不同语言下的数学问题解决能力,考察模型的多语言适应性,尤其适用于国际化环境中的数学教育和跨语言技术支持场景。
|
||||
|
||||
这些评测集的多样性帮助我们全面评估大模型在不同任务和应用场景中的表现,确保模型在处理多样化任务时能够保持高效和精准的表现。例如,在MMLU评测中,某些大模型在历史、物理等学科任务中表现优异,展现出对多领域知识的深度理解;在GSM8K数学评测中,最新的大模型在算术和方程求解方面表现接近甚至超越了一些人类基准,显示出在复杂数学推理任务中的潜力。这些实际评测结果展示了模型在各类复杂任务中的进步和应用潜力。
|
||||
|
||||
|
||||
## 7.1.2 主流的评测榜单
|
||||
|
||||
大模型的评测不仅限于使用特定的数据集,许多机构还会根据评测结果发布模型排行榜,这些榜单为学术界和工业界提供了重要的参考,帮助他们了解当前最前沿的技术和模型。以下是一些主流的评测榜单:
|
||||
|
||||
### Open LLM Leaderboard
|
||||
|
||||
由Hugging Face提供的开放式榜单,汇集了多个开源大模型的评测结果,帮助用户了解不同模型在各种任务上的表现。该榜单通过多个标准化测试集来评估模型的性能,并通过持续更新的方式反映最新的技术进展,为研究者和开发者提供了高价值的对比参考。
|
||||
|
||||

|
||||
|
||||
### lmsys Chatbot Arena Leaderboard
|
||||
|
||||
由lmsys提供的聊天机器人评测榜单,通过多维度的评估,展示各类大模型在对话任务中的能力。该榜单采用真实用户与模型交互的方式来评测对话质量,重点考察模型的自然语言生成能力、上下文理解能力以及用户满意度,是当前评估聊天机器人性能的重要工具。
|
||||
|
||||

|
||||
|
||||
### OpenCompass
|
||||
|
||||
OpenCompass是国内的评测榜单,针对大模型在多种语言和任务上的表现进行评估,提供了中国市场特定应用的参考。该榜单结合了中文语言理解和多语言能力的测试,以适应本地化需求,并特别关注大模型在中文语境下的准确性、鲁棒性和适应性,为国内企业和研究者选择合适的模型提供了重要参考。
|
||||
|
||||

|
||||
|
||||
## 7.1.3 特定的评测榜单
|
||||
|
||||
此外,还有针对不同领域特定任务的大模型评测榜单,这些榜单专注于特定应用领域,帮助用户了解大模型在某一垂直领域的能力:
|
||||
|
||||
- 金融榜:基于CFBenchmark评测集,评估大模型在金融自然语言处理、金融预测计算、金融分析与安全检查等多项基础任务中的能力。由同济大学与上海人工智能实验室及东方财经提供。
|
||||
|
||||
- 安全榜:基于Flames评测集,评估大模型在公平、安全、数据保护以及合法五大维度的抗性,帮助深入了解模型在安全性上的表现。由上海人工智能实验室与复旦大学提供。
|
||||
|
||||
- 通识榜:基于BotChat评测集,评估大语言模型生成日常多轮对话能力的综合程度,判断模型在对话中是否具备类人水平。由上海人工智能实验室提供。
|
||||
|
||||
- 法律榜:基于LawBench评测集,评估模型在法律领域的理解、推理和应用能力,涵盖法律问题回答、文本生成、法律判例分析等任务。由南京大学提供。
|
||||
|
||||
- 医疗榜:基于MedBench评测集,评估大语言模型在医学知识问答、安全伦理理解等方面的表现。由上海人工智能实验室提供。
|
||||
|
||||

|
||||
|
||||
**参考文献**
|
||||
|
||||
- [Open LLM Leaderboard](https://huggingface.co/spaces/open-llm-leaderboard/open_llm_leaderboard)
|
||||
- [lmsys Chatbot Arena Leaderboard](https://huggingface.co/spaces/awacke1/lmsys-chatbot-arena-leaderboard)
|
||||
- [OpenCompass](https://rank.opencompass.org.cn/home)
|
||||
- [金融榜 CFBenchmark](https://specialist.opencompass.org.cn/CFBenchmark)
|
||||
- [安全榜 Flames](https://flames.opencompass.org.cn/leaderboard)
|
||||
- [通识榜 BotChat](https://botchat.opencompass.org.cn/?lang=zh-CN)
|
||||
- [法律榜 LawBench](https://lawbench.opencompass.org.cn/leaderboard?lang=en-US?lang=zh-CN)
|
||||
- [医疗榜 MedBench](https://medbench.opencompass.org.cn/leaderboard?lang=zh-CN?lang=zh-CN)
|
||||
@@ -1,54 +0,0 @@
|
||||
# 7.1 强化学习的目标
|
||||
|
||||
在我们进入强化学习的细节之前,让我们先看看它的起源。强化学习(Reinforcement Learning,简称RL)其实并不是什么新鲜事物。它的理论基础可以追溯到20世纪初的行为心理学,尤其是Edward Thorndike和B.F. Skinner对于动物学习的研究。Thorndike提出了“效果律”,即如果一个行为带来积极的结果,那么这种行为重复发生的概率会增加。Skinner则进一步发展了这种思想,提出操作性条件作用学说,通过奖励和惩罚来塑造行为。
|
||||
|
||||
计算机科学领域的强化学习是从这些心理学原理生发出来的。在20世纪80年代,随着计算能力的提升和数学理论的发展,人们开始尝试将这些生物心理学的学习概念应用于机器和计算机程序,从而发展出了现代意义上的强化学习。
|
||||
|
||||
## 7.1.1 强化学习的基本原理
|
||||
|
||||
现在,我们进入核心部分——强化学习的基本原理。
|
||||
|
||||
- 状态(State) :这是一个系统在某一时刻的具体状况。比如在一个棋盘游戏中,状态可以表示棋盘上所有棋子的当前排列情况。对于一个自动驾驶汽车来说,状态可能包括汽车的速度、位置,以及周围障碍物的位置等。
|
||||
- 动作(Action) :动作是智能体在给定状态下可执行的操作。以自行车为例,动作可能包括前进、停止、转弯等。在一个复杂的系统中,动作集可以非常庞大。
|
||||
- 奖励(Reward) :这是智能体在执行某个动作后获得的反馈,通常是一个数值。奖励可以是立即的,也可以是延后的。一个好的动作可能会得到正奖励,而不好的动作可能会得到负奖励。
|
||||
- 策略(Policy) :策略是一套指导智能体如何选择动作的规则。简单来说,策略就是告诉智能体在每个状态下应该做什么。
|
||||
- 价值函数(Value Function) :这是一种对策略的评估工具,旨在预测从当前状态出发,长期来看能够获得的总奖励。值函数帮助智能体不仅考虑当前步骤的奖励,而且能更好地权衡短期和长期的收益。
|
||||
- 模型(Model) :在有些强化学习系统中,我们会建立一个环境模型,帮助智能体预见其动作的结果。这在很多复杂计算情况下非常有用。
|
||||
|
||||

|
||||
|
||||
这些元素共同作用,帮助智能体通过不断地在虚拟环境中试错来学习最佳的行动策略。在强化学习中,智能体是学习和决策的主体。它通过以下步骤与环境进行交互:
|
||||
|
||||
1. 观察状态 :智能体首先观察当前的状态(State)。
|
||||
2. 选择动作 :根据观察到的状态和预先确定的策略,智能体选择一个动作(Action)。
|
||||
3. 执行动作 :智能体执行所选的动作。
|
||||
4. 接收奖励和新状态 :执行动作后,智能体从环境中接收到相应的奖励(Reward)和更新后的新状态(State)。
|
||||
5. 更新策略 :智能体使用获得的奖励信息来调整策略,以便在未来获得更好的结果。
|
||||
|
||||
将这个过程不断重复,智能体在反复的交互中不断优化其策略,目标是让它在给定的任务中表现得越来越好。
|
||||
|
||||
## 7.1.2 强化学习的目标
|
||||
|
||||
强化学习的目标十分明确:***通过在给定环境中反复试探和学习,使得智能体能够选择一系列动作从而最大化其总累计奖励。*** 这听起来可能有些抽象,我们可以用玩游戏来比喻。在游戏中,玩家的目标是通过一系列操作(比如走路、跳跃、打怪)来赢得高分数或完成关卡。在强化学习中,这种高分或成功通过关卡的概念对应于“最大化奖励”。
|
||||
|
||||
在数学上,这个目标可以表示为训练一个策略 $\pi$,使得在所有状态 $s$ 下,智能体选择的动作能够使得回报 $R(\tau)$ 的期望值最大化。具体来说,我们希望最大化以下期望值:
|
||||
|
||||
$$
|
||||
E(R(\tau))_{\tau \sim P_{\theta}(\tau)} = \sum_{\tau} R(\tau) P_{\theta}(\tau)
|
||||
$$
|
||||
|
||||
其中:
|
||||
- $E(R(\tau))_{\tau \sim P_{\theta}(\tau)}$:表示在策略 $P_{\theta}(\tau)$ 下轨迹 $\tau$ 的回报 $R(\tau)$ 的期望值。
|
||||
- $R(\tau)$:轨迹 $\tau$ 的回报,即从起始状态到终止状态获得的所有奖励的总和。
|
||||
- $\tau$:表示一条轨迹,即智能体在环境中的状态和动作序列。
|
||||
- $P_{\theta}(\tau)$:在参数 $\theta$ 下生成轨迹 $\tau$ 的概率,通常由策略或策略网络确定。
|
||||
- $\theta$:策略的参数,控制着策略 $P_{\theta}$ 的行为。
|
||||
|
||||
为了找到这个策略,我们使用梯度上升的方法,不断更新策略参数 $\theta$,使得 $E(R(\tau))_{\tau \sim P_{\theta}(\tau)}$ 不断增大。
|
||||
|
||||
这种学习方式非常有效,因为它不依赖于大量的标注数据,而是通过对环境直接进行交互和反馈进行学习。这使得强化学习在许多需要适应和决策的复杂任务中,比如机器人控制、自动驾驶、金融交易乃至游戏中,都展现出了巨大的潜力。
|
||||
|
||||
而强化学习在大模型中的应用,比如 AlphaGo、AlphaZero 等,更是让人们看到了强化学习在复杂任务中的强大能力。这些模型通过强化学习的方法,不断优化策略,最终在围棋、象棋等游戏中击败了人类顶尖选手,展现出了强化学习在复杂任务中的巨大潜力。
|
||||
|
||||
强化学习也可以用于偏好对齐问题,比如可以让大模型学习模仿人类的交流方式,也会用于自动驾驶等领域。强化学习的应用领域非常广泛,未来也会有更多的应用场景。
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# 7.2 奖励模型
|
||||
|
||||
在自然语言处理领域,大语言模型(如Llama 系列、Qwen系列等)已经展现了强大的文本理解和生成能力。然而,这些预训练模型并不总是能直接满足特定的业务需求和人类价值观。为此,人们通常需要对预训练模型进行“指令微调”(Instruction Tuning),即向模型提供特定的指令(prompts)和示例,使其在对话、问答、文本生成等任务中表现得更符合人类期望。
|
||||
|
||||
在完成初步的指令微调后,我们还想要使模型的回答不仅正确,还能最大程度上满足人类的审美、价值观和安全标准。为此,引入了强化学习与人类反馈(Reinforcement Learning from Human Feedback, RLHF)的概念。在 RLHF 中,我们会先从人类标注者那里获得对模型回答的偏好(例如,给出多个模型回答,让人类标注者对它们进行排名),然后通过这些反馈来指导模型学习,从而不断提高模型生成内容与人类偏好的契合度。
|
||||
|
||||
为了在 RLHF 流程中自动对模型的回答进行“打分”(赋予奖励),我们需要构建一个专门的奖励模型(Reward Model)。这个奖励模型会根据人类标注的数据进行训练,并在实际部署中独立对模型输出进行自动评分,从而减少持续人工参与的成本和延迟。
|
||||
|
||||
## 7.2.1 数据集构建
|
||||
|
||||
在构建奖励模型(Reward Model)之前,我们首先需要准备高质量的人类反馈数据集。此数据集的核心目标是为每条给定的提示(prompt)提供多个候选回答(completion),并由人类标注者对这些回答进行细致的评定与排序。通过对回答的对比和筛选,我们得以为机器模型提供明确的参考标准,帮助其进一步学习在给定任务下如何生成更符合人类期望的输出。
|
||||
|
||||
可以按照以下步骤进行数据收集:
|
||||
|
||||
1. 收集初始回答:首先,我们需要从一个已经过基本微调的“大模型”(往往是具有一定指令理解和生成能力的预训练模型)中,为一组精心设计的提示生成多条回答。这些回答将作为后续人类标注工作的基础。
|
||||
|
||||
|
||||
2. 人工标注与评估:拥有多条候选回答后,我们邀请专业标注人员或众包标注者对每条回答的质量进行评价。这些评估通常会基于一系列预先设计的评价标准,如回答的准确性、完整性、上下文相关性、语言流畅度以及是否遵循道德与安全准则。对不同回答的比较与排序帮助我们识别最佳和最差的回答,从而形成有价值的训练数据。
|
||||
|
||||
3. 数据格式化与整理:标注完成后,我们将数据进行整理与格式化,通常采用 JSON、CSV 或其他便于计算机处理的结构化数据格式。数据集中需明确标识每个问题(prompt)、其对应的多个回答(completions),以及人类标注者对这些回答的选择(如标记为 "chosen" 的最佳答案与 "rejected" 的较差答案)。这些标记信息可直接作为奖励模型学习的监督信号,使其在训练中自动倾向于生成高质量回答。
|
||||
|
||||
下面是一个简单的数据示例,其中展示了两个问题(question)及其对应的回答和人类评价结果。通过 "chosen" 与 "rejected" 字段的对比,我们可以直观地看出哪条回答更为优质。
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"question": "Python中的列表是什么?",
|
||||
"chosen": "Python中的列表是一种有序的可变容器,允许存储多个元素,并且可以通过索引访问。",
|
||||
"rejected": "Python中的列表用于存储数据。"
|
||||
},
|
||||
{
|
||||
"question": "Python中的元组是什么?",
|
||||
"chosen": "Python中的元组是一种有序的不可变容器,允许存储多个元素,并且一旦创建就不能修改。",
|
||||
"rejected": "Python中的元组用于存储数据。"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
在上述示例中,人类标注者认为 "chosen" 字段下的回答相对于对应的 "rejected" 回答在描述、准确性和信息量等方面都更为优质。例如,对于列表的定义,"chosen" 答复更清晰地解释了列表的特征(有序、可变、支持索引访问),而非仅仅停留在“用于存储数据”这种笼统描述。
|
||||
|
||||
|
||||
## 7.2.2 奖励模型训练
|
||||
|
||||
我们可以借助大模型强化学习框架 TRL(Transformer Reinforcement Learning)来训练奖励模型。TRL 是一个基于强化学习的训练框架,旨在通过人类反馈指导模型生成更符合人类期望的回答。在 TRL 中,我们会将奖励模型作为一个独立的组件,用于评估模型生成的回答,并根据评估结果给予奖励或惩罚。
|
||||
|
||||
299
docs/chapter7/7.3 RAG.md
Normal file
@@ -0,0 +1,299 @@
|
||||
# 7.3 RAG
|
||||
|
||||
## 7.3.1 RAG 的基本原理
|
||||
|
||||
大语言模型(LLM)在生成内容时,虽然具备强大的语言理解和生成能力,但也面临着一些挑战。例如,LLM有时会生成不准确或误导性的内容,这被称为大模型“幻觉”。此外,模型所依赖的训练数据可能过时,尤其在面对最新的信息时,生成结果的准确性和时效性难以保证。对于特定领域的专业知识,LLM 的处理效率也较低,无法深入理解复杂的领域知识。因此,如何提升大模型的生成质量和效率,成为了当前研究的重要方向。
|
||||
|
||||
在这样的背景下,检索增强生成(Retrieval-Augmented Generation,RAG)技术应运而生,成为AI领域中的一大创新趋势。RAG 在生成答案之前,首先从外部的大规模文档数据库中检索出相关信息,并将这些信息融入到生成过程之中,从而指导和优化语言模型的输出。这一流程不仅极大地提升了内容生成的准确性和相关性,还使得生成的内容更加符合实时性要求。
|
||||
|
||||
RAG 的核心原理在于将“检索”与“生成”结合:当用户提出查询时,系统首先通过检索模块找到与问题相关的文本片段,然后将这些片段作为附加信息传递给语言模型,模型据此生成更为精准和可靠的回答。通过这种方式,RAG 有效缓解了大语言模型的“幻觉”问题,因为生成的内容建立在真实文档的基础上,使得答案更具可追溯性和可信度。同时,由于引入了最新的信息源,RAG 技术大大加快了知识更新速度,使得系统可以及时吸收和反映最新的领域动态。
|
||||
|
||||
## 7.3.2 搭建一个 RAG 框架
|
||||
|
||||
接下来我会带领大家一步一步实现一个简单的RAG模型,这个模型是基于RAG的一个简化版本,我们称之为Tiny-RAG。Tiny-RAG只包含了RAG的核心功能,即检索和生成,其目的是帮助大家更好地理解RAG模型的原理和实现。
|
||||
|
||||
### Step 1: RAG流程介绍
|
||||
|
||||
RAG通过在语言模型生成答案之前,先从广泛的文档数据库中检索相关信息,然后利用这些信息来引导生成过程,从而极大地提升了内容的准确性和相关性。RAG有效地缓解了幻觉问题,提高了知识更新的速度,并增强了内容生成的可追溯性,使得大型语言模型在实际应用中变得更加实用和可信。
|
||||
|
||||
RAG的基本结构有哪些呢?
|
||||
|
||||
- 向量化模块:用来将文档片段向量化。
|
||||
- 文档加载和切分模块:用来加载文档并切分成文档片段。
|
||||
- 数据库:存放文档片段及其对应的向量表示。
|
||||
- 检索模块:根据Query(问题)检索相关的文档片段。
|
||||
- 大模型模块:根据检索到的文档回答用户的问题。
|
||||
|
||||
上述这些也就是TinyRAG的所有模块内容。
|
||||
|
||||

|
||||
|
||||
接下来,让我们梳理一下RAG的流程是什么样的呢?
|
||||
|
||||
- **索引**:将文档库分割成较短的片段,并通过编码器构建向量索引。
|
||||
- **检索**:根据问题和片段的相似度检索相关文档片段。
|
||||
- **生成**:以检索到的上下文为条件,生成问题的回答。
|
||||
|
||||
如下图所示的流程,图片出处 ***[Retrieval-Augmented Generation for Large Language Models: A Survey](https://arxiv.org/pdf/2312.10997.pdf)***
|
||||
|
||||

|
||||
|
||||
### Step 2: 向量化
|
||||
|
||||
首先我们来动手实现一个向量化的类,这是RAG架构的基础。向量化类主要用来将文档片段向量化,将一段文本映射为一个向量。
|
||||
|
||||
首先我们要设置一个 `BaseEmbeddings` 基类,这样我们在使用其他模型时,只需要继承这个基类,然后在此基础上进行修改即可,方便代码扩展。
|
||||
|
||||
```python
|
||||
class BaseEmbeddings:
|
||||
"""
|
||||
Base class for embeddings
|
||||
"""
|
||||
def __init__(self, path: str, is_api: bool) -> None:
|
||||
self.path = path
|
||||
self.is_api = is_api
|
||||
|
||||
def get_embedding(self, text: str, model: str) -> List[float]:
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def cosine_similarity(cls, vector1: List[float], vector2: List[float]) -> float:
|
||||
"""
|
||||
calculate cosine similarity between two vectors
|
||||
"""
|
||||
dot_product = np.dot(vector1, vector2)
|
||||
magnitude = np.linalg.norm(vector1) * np.linalg.norm(vector2)
|
||||
if not magnitude:
|
||||
return 0
|
||||
return dot_product / magnitude
|
||||
```
|
||||
|
||||
`BaseEmbeddings`基类有两个主要方法:`get_embedding`和`cosine_similarity`。`get_embedding`用于获取文本的向量表示,`cosine_similarity`用于计算两个向量之间的余弦相似度。在初始化类时设置了模型的路径和是否是API模型,例如使用OpenAI的Embedding API需要设置`self.is_api=True`。
|
||||
|
||||
继承`BaseEmbeddings`类只需要实现`get_embedding`方法,`cosine_similarity`方法会被继承下来。这就是编写基类的好处。
|
||||
|
||||
```python
|
||||
class OpenAIEmbedding(BaseEmbeddings):
|
||||
"""
|
||||
class for OpenAI embeddings
|
||||
"""
|
||||
def __init__(self, path: str = '', is_api: bool = True) -> None:
|
||||
super().__init__(path, is_api)
|
||||
if self.is_api:
|
||||
from openai import OpenAI
|
||||
self.client = OpenAI()
|
||||
self.client.api_key = os.getenv("OPENAI_API_KEY")
|
||||
self.client.base_url = os.getenv("OPENAI_BASE_URL")
|
||||
|
||||
def get_embedding(self, text: str, model: str = "text-embedding-3-large") -> List[float]:
|
||||
if self.is_api:
|
||||
text = text.replace("\n", " ")
|
||||
return self.client.embeddings.create(input=[text], model=model).data[0].embedding
|
||||
else:
|
||||
raise NotImplementedError
|
||||
```
|
||||
|
||||
### Step 3: 文档加载和切分
|
||||
|
||||
接下来我们来实现一个文档加载和切分的类,这个类主要用于加载文档并将其切分成文档片段。
|
||||
|
||||
文档可以是文章、书籍、对话、代码等文本内容,例如pdf文件、md文件、txt文件等。完整代码可以在 ***[RAG/utils.py](./RAG/utils.py)*** 文件中找到。该代码支持加载pdf、md、txt等类型的文件,只需编写相应的函数即可。
|
||||
|
||||
```python
|
||||
def read_file_content(cls, file_path: str):
|
||||
# 根据文件扩展名选择读取方法
|
||||
if file_path.endswith('.pdf'):
|
||||
return cls.read_pdf(file_path)
|
||||
elif file_path.endswith('.md'):
|
||||
return cls.read_markdown(file_path)
|
||||
elif file_path.endswith('.txt'):
|
||||
return cls.read_text(file_path)
|
||||
else:
|
||||
raise ValueError("Unsupported file type")
|
||||
```
|
||||
|
||||
文档读取后需要进行切分。我们可以设置一个最大的Token长度,然后根据这个最大长度来切分文档。切分文档时最好以句子为单位(按`\n`粗切分),并保证片段之间有一些重叠内容,以提高检索的准确性。
|
||||
|
||||
```python
|
||||
def get_chunk(cls, text: str, max_token_len: int = 600, cover_content: int = 150):
|
||||
chunk_text = []
|
||||
|
||||
curr_len = 0
|
||||
curr_chunk = ''
|
||||
|
||||
lines = text.split('\n')
|
||||
|
||||
for line in lines:
|
||||
line = line.replace(' ', '')
|
||||
line_len = len(enc.encode(line))
|
||||
if line_len > max_token_len:
|
||||
print('warning line_len = ', line_len)
|
||||
if curr_len + line_len <= max_token_len:
|
||||
curr_chunk += line
|
||||
curr_chunk += '\n'
|
||||
curr_len += line_len
|
||||
curr_len += 1
|
||||
else:
|
||||
chunk_text.append(curr_chunk)
|
||||
curr_chunk = curr_chunk[-cover_content:] + line
|
||||
curr_len = line_len + cover_content
|
||||
|
||||
if curr_chunk:
|
||||
chunk_text.append(curr_chunk)
|
||||
|
||||
return chunk_text
|
||||
```
|
||||
|
||||
### Step 4: 数据库与向量检索
|
||||
|
||||
完成文档切分和Embedding模型加载后,需要设计一个向量数据库来存放文档片段和对应的向量表示,以及设计一个检索模块用于根据Query检索相关文档片段。
|
||||
|
||||
向量数据库的功能包括:
|
||||
|
||||
- `persist`:数据库持久化保存。
|
||||
- `load_vector`:从本地加载数据库。
|
||||
- `get_vector`:获取文档的向量表示。
|
||||
- `query`:根据问题检索相关文档片段。
|
||||
|
||||
完整代码可以在 ***[RAG/VectorBase.py](RAG/VectorBase.py)*** 文件中找到。
|
||||
|
||||
```python
|
||||
class VectorStore:
|
||||
def __init__(self, document: List[str] = ['']) -> None:
|
||||
self.document = document
|
||||
|
||||
def get_vector(self, EmbeddingModel: BaseEmbeddings) -> List[List[float]]:
|
||||
# 获得文档的向量表示
|
||||
pass
|
||||
|
||||
def persist(self, path: str = 'storage'):
|
||||
# 数据库持久化保存
|
||||
pass
|
||||
|
||||
def load_vector(self, path: str = 'storage'):
|
||||
# 从本地加载数据库
|
||||
pass
|
||||
|
||||
def query(self, query: str, EmbeddingModel: BaseEmbeddings, k: int = 1) -> List[str]:
|
||||
# 根据问题检索相关文档片段
|
||||
pass
|
||||
```
|
||||
|
||||
`query` 方法用于将用户提出的问题向量化,然后在数据库中检索相关文档片段并返回结果。
|
||||
|
||||
```python
|
||||
def query(self, query: str, EmbeddingModel: BaseEmbeddings, k: int = 1) -> List[str]:
|
||||
query_vector = EmbeddingModel.get_embedding(query)
|
||||
result = np.array([self.get_similarity(query_vector, vector) for vector in self.vectors])
|
||||
return np.array(self.document)[result.argsort()[-k:][::-1]].tolist()
|
||||
```
|
||||
|
||||
### Step 5: 大模型模块
|
||||
|
||||
接下来是大模型模块,用于根据检索到的文档回答用户的问题。
|
||||
|
||||
首先实现一个基类,这样可以方便扩展其他模型。
|
||||
|
||||
```python
|
||||
class BaseModel:
|
||||
def __init__(self, path: str = '') -> None:
|
||||
self.path = path
|
||||
|
||||
def chat(self, prompt: str, history: List[dict], content: str) -> str:
|
||||
pass
|
||||
|
||||
def load_model(self):
|
||||
pass
|
||||
```
|
||||
|
||||
`BaseModel` 包含两个方法:`chat`和`load_model`。对于本地化运行的开源模型需要实现`load_model`,而API模型则不需要。
|
||||
|
||||
下面以 ***[InternLM2-chat-7B](https://huggingface.co/internlm/internlm2-chat-7b)*** 模型为例:
|
||||
|
||||
```python
|
||||
class InternLMChat(BaseModel):
|
||||
def __init__(self, path: str = '') -> None:
|
||||
super().__init__(path)
|
||||
self.load_model()
|
||||
|
||||
def chat(self, prompt: str, history: List = [], content: str='') -> str:
|
||||
prompt = PROMPT_TEMPLATE['InternLM_PROMPT_TEMPLATE'].format(question=prompt, context=content)
|
||||
response, history = self.model.chat(self.tokenizer, prompt, history)
|
||||
return response
|
||||
|
||||
def load_model(self):
|
||||
import torch
|
||||
from transformers import AutoTokenizer, AutoModelForCausalLM
|
||||
self.tokenizer = AutoTokenizer.from_pretrained(self.path, trust_remote_code=True)
|
||||
self.model = AutoModelForCausalLM.from_pretrained(self.path, torch_dtype=torch.float16, trust_remote_code=True).cuda()
|
||||
```
|
||||
|
||||
可以用一个字典来保存所有的prompt,方便维护:
|
||||
|
||||
```python
|
||||
PROMPT_TEMPLATE = dict(
|
||||
InternLM_PROMPT_TEMPLATE="""先对上下文进行内容总结,再使用上下文来回答用户的问题。如果你不知道答案,就说你不知道。总是使用中文回答。
|
||||
问题: {question}
|
||||
可参考的上下文:
|
||||
···
|
||||
{context}
|
||||
···
|
||||
如果给定的上下文无法让你做出回答,请回答数据库中没有这个内容,你不知道。
|
||||
有用的回答:"""
|
||||
)
|
||||
```
|
||||
|
||||
这样我们就可以利用InternLM2模型来做RAG啦!
|
||||
|
||||
### Step 6: Tiny-RAG Demo
|
||||
|
||||
接下来,我们来看看Tiny-RAG的Demo吧!
|
||||
|
||||
```python
|
||||
from RAG.VectorBase import VectorStore
|
||||
from RAG.utils import ReadFiles
|
||||
from RAG.LLM import OpenAIChat, InternLMChat
|
||||
from RAG.Embeddings import JinaEmbedding, ZhipuEmbedding
|
||||
|
||||
# 没有保存数据库
|
||||
docs = ReadFiles('./data').get_content(max_token_len=600, cover_content=150) # 获取data目录下的所有文件内容并分割
|
||||
vector = VectorStore(docs)
|
||||
embedding = ZhipuEmbedding() # 创建EmbeddingModel
|
||||
vector.get_vector(EmbeddingModel=embedding)
|
||||
vector.persist(path='storage') # 将向量和文档内容保存到storage目录,下次再用可以直接加载本地数据库
|
||||
|
||||
question = 'git的原理是什么?'
|
||||
|
||||
content = vector.query(question, model='zhipu', k=1)[0]
|
||||
chat = InternLMChat(path='model_path')
|
||||
print(chat.chat(question, [], content))
|
||||
```
|
||||
|
||||
也可以从本地加载已处理好的数据库:
|
||||
|
||||
```python
|
||||
from RAG.VectorBase import VectorStore
|
||||
from RAG.utils import ReadFiles
|
||||
from RAG.LLM import OpenAIChat, InternLMChat
|
||||
from RAG.Embeddings import JinaEmbedding, ZhipuEmbedding
|
||||
|
||||
# 保存数据库之后
|
||||
vector = VectorStore()
|
||||
|
||||
vector.load_vector('./storage') # 加载本地数据库
|
||||
|
||||
question = 'git的原理是什么?'
|
||||
|
||||
embedding = ZhipuEmbedding() # 创建EmbeddingModel
|
||||
|
||||
content = vector.query(question, EmbeddingModel=embedding, k=1)[0]
|
||||
chat = InternLMChat(path='model_path')
|
||||
print(chat.chat(question, [], content))
|
||||
```
|
||||
|
||||
**参考文献**
|
||||
|
||||
- [When Large Language Models Meet Vector Databases: A Survey ](http://arxiv.org/abs/2402.01763)
|
||||
- [Retrieval-Augmented Generation for Large Language Models: A Survey](https://arxiv.org/abs/2312.10997)
|
||||
- [Learning to Filter Context for Retrieval-Augmented Generation](http://arxiv.org/abs/2311.08377)
|
||||
- [In-Context Retrieval-Augmented Language Models](https://arxiv.org/abs/2302.00083)
|
||||
39
docs/chapter7/7.4 Agent.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 7.4 Agent
|
||||
|
||||
## 7.4.1 什么是 LLM Agent?
|
||||
|
||||
LLM Agent,即基于大型语言模型(LLM)构建的智能代理,是一种能够自主进行推理、规划、行动并与环境交互的 AI 系统. 与传统的聊天机器人不同,LLM Agent 具备独立决策能力,能够利用各种工具,记住之前的交互信息,并执行复杂的任务流程,而这一切只需要最少的人工干预. 这种自主性使得 LLM Agent 非常适合处理需要多步骤推理和规划、与 API 和工具进行实时交互、感知环境变化并做出适应以及在不确定性下自主执行的任务。
|
||||
|
||||
例如,当用户提出“在中国合同违约可能有哪些法律后果?”这样的问题时,一个 LLM Agent 可以自主地完成以下步骤:搜索法律数据库,提取相关信息,总结案例,并最终呈现潜在的法律结果,而无需人工的逐步指导。
|
||||
|
||||
LLM Agent 的核心在于其能够像人类一样进行“链式思考”推理,理解问题解决的原理,并将复杂的任务分解为更小、更简单的步骤. 这种能力是通过提示模型逐步思考来实现的,并且可以通过让模型回顾自身过去的行动并纠正错误来进行改进。
|
||||
|
||||
一个设计完善的 LLM Agent 通常由四个关键模块组成:
|
||||
|
||||
- 大脑(核心 LLM): 作为中央决策者,负责执行推理、规划和语言生成。
|
||||
- 记忆: 用于存储和检索信息的模块,帮助 Agent 记住之前的交互和决策。
|
||||
- 感知: 将输入(文本、图像、音频)转换为可理解的格式,使 Agent 能够观察和解释环境。
|
||||
- 行动: 通过调用 API、与工具交互或生成文本/代码来执行决策,有时也包括具身行动(例如,在机器人具身只能技术中)
|
||||
|
||||
LLM Agent 通过将大型语言模型的强大语言理解和生成能力与规划、记忆和工具使用等关键模块相结合,实现了超越传统大模型的自主性和复杂任务处理能力,这种能力使得 LLM Agent 在许多垂直领域(如法律、医疗、金融等)都具有广泛的应用潜力。
|
||||
|
||||
## 7.4.2 LLM Agent 的类型
|
||||
|
||||
LLM Agent 可以根据其功能、设计和应用场景进行多种分类. 以下是一些常见的类型:
|
||||
|
||||
- 对话型 Agent (Conversational Agents): 这些 Agent 旨在与用户进行自然的对话,提供信息、回答问题并协助完成各种任务。它们依赖 LLM 来理解和生成类似人类的响应,例如客户支持聊天机器人和医疗保健聊天机器人.
|
||||
|
||||
- 任务型 Agent (Task-Oriented Agents): 这些 Agent 专注于执行特定的任务或实现预定义的目标。它们与用户交互以理解需求,然后执行操作以满足这些需求,例如 AI 助手和 HR 机器人. 任务型 Agent 能够分解复杂的目标,制定行动计划,并利用工具来完成任务. 它们通常用于自动化重复性工作流程,提高生产力.
|
||||
|
||||
- 创造型 Agent (Creative Agents): 这些 Agent 能够生成原创和创造性的内容,如艺术品、音乐或写作。它们使用 LLM 来理解人类的偏好和艺术风格,从而产生能引起受众共鸣的内容,例如内容生成工具和图像生成工具.
|
||||
|
||||
- 协作型 Agent (Collaborative Agents): 这些 Agent 与人类或其他 AI 协同工作以完成共同的目标或任务,促进团队成员之间或人机之间的沟通、协调与合作。LLM 可以通过协助决策、生成报告或提供见解来支持协作型 Agent,例如企业 AI Agent 和项目管理聊天机器人.
|
||||
|
||||
- 多模态 Agent (Multimodal Agents): 这些 Agent 不仅限于文本,还可以处理和生成各种格式的内容,包括音频、图像和视频。这种能力实现了更丰富和更具沉浸感的交互,例如能够解释视觉数据或根据文本描述生成图像的虚拟助手.
|
||||
|
||||
- 自主型 Agent (Autonomous Agents): 这些 Agent 被设计为以最少的人工干预运行,能够独立做出决策并执行操作。它们通常用于需要持续运行的环境中,例如处理重复性业务流程的机器人流程自动化机器人.
|
||||
|
||||
- 多 Agent 系统 (Multi-Agent Systems): 这些系统涉及多个 LLM Agent 协同工作,通过利用各种专业 Agent 的优势来处理复杂的任务。它们相互通信和协调以实现共同目标,例如一个 Agent 收集数据,另一个分析数据,第三个根据分析生成报告.
|
||||
|
||||
- 规则型 Agent (Rule-Based Agents): 这些 Agent 基于预定义的算法和严格的协议运行。它们遵循确定性原则,即某些输入会导致基于既定规则的特定响应。这些 Agent 在需要一致输出的情况下非常有效,例如客户支持或故障排除指南。然而,规则型 Agent 缺乏灵活性,难以处理超出其编程规则的情况.
|
||||
|
||||
117
docs/chapter7/RAG/Embeddings.py
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
@File : Embeddings.py
|
||||
@Time : 2024/02/10 21:55:39
|
||||
@Author : 不要葱姜蒜
|
||||
@Version : 1.0
|
||||
@Desc : None
|
||||
'''
|
||||
|
||||
import os
|
||||
from copy import copy
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
import numpy as np
|
||||
|
||||
os.environ['CURL_CA_BUNDLE'] = ''
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
_ = load_dotenv(find_dotenv())
|
||||
|
||||
|
||||
class BaseEmbeddings:
|
||||
"""
|
||||
Base class for embeddings
|
||||
"""
|
||||
def __init__(self, path: str, is_api: bool) -> None:
|
||||
self.path = path
|
||||
self.is_api = is_api
|
||||
|
||||
def get_embedding(self, text: str, model: str) -> List[float]:
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def cosine_similarity(cls, vector1: List[float], vector2: List[float]) -> float:
|
||||
"""
|
||||
calculate cosine similarity between two vectors
|
||||
"""
|
||||
dot_product = np.dot(vector1, vector2)
|
||||
magnitude = np.linalg.norm(vector1) * np.linalg.norm(vector2)
|
||||
if not magnitude:
|
||||
return 0
|
||||
return dot_product / magnitude
|
||||
|
||||
|
||||
class OpenAIEmbedding(BaseEmbeddings):
|
||||
"""
|
||||
class for OpenAI embeddings
|
||||
"""
|
||||
def __init__(self, path: str = '', is_api: bool = True) -> None:
|
||||
super().__init__(path, is_api)
|
||||
if self.is_api:
|
||||
from openai import OpenAI
|
||||
self.client = OpenAI()
|
||||
self.client.api_key = os.getenv("OPENAI_API_KEY")
|
||||
self.client.base_url = os.getenv("OPENAI_BASE_URL")
|
||||
|
||||
def get_embedding(self, text: str, model: str = "text-embedding-3-large") -> List[float]:
|
||||
if self.is_api:
|
||||
text = text.replace("\n", " ")
|
||||
return self.client.embeddings.create(input=[text], model=model).data[0].embedding
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
class JinaEmbedding(BaseEmbeddings):
|
||||
"""
|
||||
class for Jina embeddings
|
||||
"""
|
||||
def __init__(self, path: str = 'jinaai/jina-embeddings-v2-base-zh', is_api: bool = False) -> None:
|
||||
super().__init__(path, is_api)
|
||||
self._model = self.load_model()
|
||||
|
||||
def get_embedding(self, text: str) -> List[float]:
|
||||
return self._model.encode([text])[0].tolist()
|
||||
|
||||
def load_model(self):
|
||||
import torch
|
||||
from transformers import AutoModel
|
||||
if torch.cuda.is_available():
|
||||
device = torch.device("cuda")
|
||||
else:
|
||||
device = torch.device("cpu")
|
||||
model = AutoModel.from_pretrained(self.path, trust_remote_code=True).to(device)
|
||||
return model
|
||||
|
||||
class ZhipuEmbedding(BaseEmbeddings):
|
||||
"""
|
||||
class for Zhipu embeddings
|
||||
"""
|
||||
def __init__(self, path: str = '', is_api: bool = True) -> None:
|
||||
super().__init__(path, is_api)
|
||||
if self.is_api:
|
||||
from zhipuai import ZhipuAI
|
||||
self.client = ZhipuAI(api_key=os.getenv("ZHIPUAI_API_KEY"))
|
||||
|
||||
def get_embedding(self, text: str) -> List[float]:
|
||||
response = self.client.embeddings.create(
|
||||
model="embedding-2",
|
||||
input=text,
|
||||
)
|
||||
return response.data[0].embedding
|
||||
|
||||
class DashscopeEmbedding(BaseEmbeddings):
|
||||
"""
|
||||
class for Dashscope embeddings
|
||||
"""
|
||||
def __init__(self, path: str = '', is_api: bool = True) -> None:
|
||||
super().__init__(path, is_api)
|
||||
if self.is_api:
|
||||
import dashscope
|
||||
dashscope.api_key = os.getenv("DASHSCOPE_API_KEY")
|
||||
self.client = dashscope.TextEmbedding
|
||||
|
||||
def get_embedding(self, text: str, model: str='text-embedding-v1') -> List[float]:
|
||||
response = self.client.call(
|
||||
model=model,
|
||||
input=text
|
||||
)
|
||||
return response.output['embeddings'][0]['embedding']
|
||||
113
docs/chapter7/RAG/LLM.py
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
@File : LLM.py
|
||||
@Time : 2024/02/12 13:50:47
|
||||
@Author : 不要葱姜蒜
|
||||
@Version : 1.0
|
||||
@Desc : None
|
||||
'''
|
||||
import os
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
|
||||
PROMPT_TEMPLATE = dict(
|
||||
RAG_PROMPT_TEMPALTE="""使用以上下文来回答用户的问题。如果你不知道答案,就说你不知道。总是使用中文回答。
|
||||
问题: {question}
|
||||
可参考的上下文:
|
||||
···
|
||||
{context}
|
||||
···
|
||||
如果给定的上下文无法让你做出回答,请回答数据库中没有这个内容,你不知道。
|
||||
有用的回答:""",
|
||||
InternLM_PROMPT_TEMPALTE="""先对上下文进行内容总结,再使用上下文来回答用户的问题。如果你不知道答案,就说你不知道。总是使用中文回答。
|
||||
问题: {question}
|
||||
可参考的上下文:
|
||||
···
|
||||
{context}
|
||||
···
|
||||
如果给定的上下文无法让你做出回答,请回答数据库中没有这个内容,你不知道。
|
||||
有用的回答:"""
|
||||
)
|
||||
|
||||
|
||||
class BaseModel:
|
||||
def __init__(self, path: str = '') -> None:
|
||||
self.path = path
|
||||
|
||||
def chat(self, prompt: str, history: List[dict], content: str) -> str:
|
||||
pass
|
||||
|
||||
def load_model(self):
|
||||
pass
|
||||
|
||||
class OpenAIChat(BaseModel):
|
||||
def __init__(self, path: str = '', model: str = "gpt-3.5-turbo-1106") -> None:
|
||||
super().__init__(path)
|
||||
self.model = model
|
||||
|
||||
def chat(self, prompt: str, history: List[dict], content: str) -> str:
|
||||
from openai import OpenAI
|
||||
client = OpenAI()
|
||||
client.api_key = os.getenv("OPENAI_API_KEY")
|
||||
client.base_url = os.getenv("OPENAI_BASE_URL")
|
||||
history.append({'role': 'user', 'content': PROMPT_TEMPLATE['RAG_PROMPT_TEMPALTE'].format(question=prompt, context=content)})
|
||||
response = client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=history,
|
||||
max_tokens=150,
|
||||
temperature=0.1
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
|
||||
class InternLMChat(BaseModel):
|
||||
def __init__(self, path: str = '') -> None:
|
||||
super().__init__(path)
|
||||
self.load_model()
|
||||
|
||||
def chat(self, prompt: str, history: List = [], content: str='') -> str:
|
||||
prompt = PROMPT_TEMPLATE['InternLM_PROMPT_TEMPALTE'].format(question=prompt, context=content)
|
||||
response, history = self.model.chat(self.tokenizer, prompt, history)
|
||||
return response
|
||||
|
||||
|
||||
def load_model(self):
|
||||
import torch
|
||||
from transformers import AutoTokenizer, AutoModelForCausalLM
|
||||
self.tokenizer = AutoTokenizer.from_pretrained(self.path, trust_remote_code=True)
|
||||
self.model = AutoModelForCausalLM.from_pretrained(self.path, torch_dtype=torch.float16, trust_remote_code=True).cuda()
|
||||
|
||||
class DashscopeChat(BaseModel):
|
||||
def __init__(self, path: str = '', model: str = "qwen-turbo") -> None:
|
||||
super().__init__(path)
|
||||
self.model = model
|
||||
|
||||
def chat(self, prompt: str, history: List[Dict], content: str) -> str:
|
||||
import dashscope
|
||||
dashscope.api_key = os.getenv("DASHSCOPE_API_KEY")
|
||||
history.append({'role': 'user', 'content': PROMPT_TEMPLATE['RAG_PROMPT_TEMPALTE'].format(question=prompt, context=content)})
|
||||
response = dashscope.Generation.call(
|
||||
model=self.model,
|
||||
messages=history,
|
||||
result_format='message',
|
||||
max_tokens=150,
|
||||
temperature=0.1
|
||||
)
|
||||
return response.output.choices[0].message.content
|
||||
|
||||
|
||||
class ZhipuChat(BaseModel):
|
||||
def __init__(self, path: str = '', model: str = "glm-4") -> None:
|
||||
super().__init__(path)
|
||||
from zhipuai import ZhipuAI
|
||||
self.client = ZhipuAI(api_key=os.getenv("ZHIPUAI_API_KEY"))
|
||||
self.model = model
|
||||
|
||||
def chat(self, prompt: str, history: List[Dict], content: str) -> str:
|
||||
history.append({'role': 'user', 'content': PROMPT_TEMPLATE['RAG_PROMPT_TEMPALTE'].format(question=prompt, context=content)})
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=history,
|
||||
max_tokens=150,
|
||||
temperature=0.1
|
||||
)
|
||||
return response.choices[0].message
|
||||
52
docs/chapter7/RAG/VectorBase.py
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
@File : VectorBase.py
|
||||
@Time : 2024/02/12 10:11:13
|
||||
@Author : 不要葱姜蒜
|
||||
@Version : 1.0
|
||||
@Desc : None
|
||||
'''
|
||||
|
||||
import os
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
import json
|
||||
from RAG.Embeddings import BaseEmbeddings, OpenAIEmbedding, JinaEmbedding, ZhipuEmbedding
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
class VectorStore:
|
||||
def __init__(self, document: List[str] = ['']) -> None:
|
||||
self.document = document
|
||||
|
||||
def get_vector(self, EmbeddingModel: BaseEmbeddings) -> List[List[float]]:
|
||||
|
||||
self.vectors = []
|
||||
for doc in tqdm(self.document, desc="Calculating embeddings"):
|
||||
self.vectors.append(EmbeddingModel.get_embedding(doc))
|
||||
return self.vectors
|
||||
|
||||
def persist(self, path: str = 'storage'):
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
with open(f"{path}/doecment.json", 'w', encoding='utf-8') as f:
|
||||
json.dump(self.document, f, ensure_ascii=False)
|
||||
if self.vectors:
|
||||
with open(f"{path}/vectors.json", 'w', encoding='utf-8') as f:
|
||||
json.dump(self.vectors, f)
|
||||
|
||||
def load_vector(self, path: str = 'storage'):
|
||||
with open(f"{path}/vectors.json", 'r', encoding='utf-8') as f:
|
||||
self.vectors = json.load(f)
|
||||
with open(f"{path}/doecment.json", 'r', encoding='utf-8') as f:
|
||||
self.document = json.load(f)
|
||||
|
||||
def get_similarity(self, vector1: List[float], vector2: List[float]) -> float:
|
||||
return BaseEmbeddings.cosine_similarity(vector1, vector2)
|
||||
|
||||
def query(self, query: str, EmbeddingModel: BaseEmbeddings, k: int = 1) -> List[str]:
|
||||
query_vector = EmbeddingModel.get_embedding(query)
|
||||
result = np.array([self.get_similarity(query_vector, vector)
|
||||
for vector in self.vectors])
|
||||
return np.array(self.document)[result.argsort()[-k:][::-1]].tolist()
|
||||
160
docs/chapter7/RAG/utils.py
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
@File : utils.py
|
||||
@Time : 2024/02/11 09:52:26
|
||||
@Author : 不要葱姜蒜
|
||||
@Version : 1.0
|
||||
@Desc : None
|
||||
'''
|
||||
|
||||
import os
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
|
||||
import PyPDF2
|
||||
import markdown
|
||||
import html2text
|
||||
import json
|
||||
from tqdm import tqdm
|
||||
import tiktoken
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
|
||||
enc = tiktoken.get_encoding("cl100k_base")
|
||||
|
||||
|
||||
class ReadFiles:
|
||||
"""
|
||||
class to read files
|
||||
"""
|
||||
|
||||
def __init__(self, path: str) -> None:
|
||||
self._path = path
|
||||
self.file_list = self.get_files()
|
||||
|
||||
def get_files(self):
|
||||
# args:dir_path,目标文件夹路径
|
||||
file_list = []
|
||||
for filepath, dirnames, filenames in os.walk(self._path):
|
||||
# os.walk 函数将递归遍历指定文件夹
|
||||
for filename in filenames:
|
||||
# 通过后缀名判断文件类型是否满足要求
|
||||
if filename.endswith(".md"):
|
||||
# 如果满足要求,将其绝对路径加入到结果列表
|
||||
file_list.append(os.path.join(filepath, filename))
|
||||
elif filename.endswith(".txt"):
|
||||
file_list.append(os.path.join(filepath, filename))
|
||||
elif filename.endswith(".pdf"):
|
||||
file_list.append(os.path.join(filepath, filename))
|
||||
return file_list
|
||||
|
||||
def get_content(self, max_token_len: int = 600, cover_content: int = 150):
|
||||
docs = []
|
||||
# 读取文件内容
|
||||
for file in self.file_list:
|
||||
content = self.read_file_content(file)
|
||||
chunk_content = self.get_chunk(
|
||||
content, max_token_len=max_token_len, cover_content=cover_content)
|
||||
docs.extend(chunk_content)
|
||||
return docs
|
||||
|
||||
@classmethod
|
||||
def get_chunk(cls, text: str, max_token_len: int = 600, cover_content: int = 150):
|
||||
chunk_text = []
|
||||
|
||||
curr_len = 0
|
||||
curr_chunk = ''
|
||||
|
||||
token_len = max_token_len - cover_content
|
||||
lines = text.splitlines() # 假设以换行符分割文本为行
|
||||
|
||||
for line in lines:
|
||||
line = line.replace(' ', '')
|
||||
line_len = len(enc.encode(line))
|
||||
if line_len > max_token_len:
|
||||
# 如果单行长度就超过限制,则将其分割成多个块
|
||||
num_chunks = (line_len + token_len - 1) // token_len
|
||||
for i in range(num_chunks):
|
||||
start = i * token_len
|
||||
end = start + token_len
|
||||
# 避免跨单词分割
|
||||
while not line[start:end].rstrip().isspace():
|
||||
start += 1
|
||||
end += 1
|
||||
if start >= line_len:
|
||||
break
|
||||
curr_chunk = curr_chunk[-cover_content:] + line[start:end]
|
||||
chunk_text.append(curr_chunk)
|
||||
# 处理最后一个块
|
||||
start = (num_chunks - 1) * token_len
|
||||
curr_chunk = curr_chunk[-cover_content:] + line[start:end]
|
||||
chunk_text.append(curr_chunk)
|
||||
|
||||
if curr_len + line_len <= token_len:
|
||||
curr_chunk += line
|
||||
curr_chunk += '\n'
|
||||
curr_len += line_len
|
||||
curr_len += 1
|
||||
else:
|
||||
chunk_text.append(curr_chunk)
|
||||
curr_chunk = curr_chunk[-cover_content:]+line
|
||||
curr_len = line_len + cover_content
|
||||
|
||||
if curr_chunk:
|
||||
chunk_text.append(curr_chunk)
|
||||
|
||||
return chunk_text
|
||||
|
||||
@classmethod
|
||||
def read_file_content(cls, file_path: str):
|
||||
# 根据文件扩展名选择读取方法
|
||||
if file_path.endswith('.pdf'):
|
||||
return cls.read_pdf(file_path)
|
||||
elif file_path.endswith('.md'):
|
||||
return cls.read_markdown(file_path)
|
||||
elif file_path.endswith('.txt'):
|
||||
return cls.read_text(file_path)
|
||||
else:
|
||||
raise ValueError("Unsupported file type")
|
||||
|
||||
@classmethod
|
||||
def read_pdf(cls, file_path: str):
|
||||
# 读取PDF文件
|
||||
with open(file_path, 'rb') as file:
|
||||
reader = PyPDF2.PdfReader(file)
|
||||
text = ""
|
||||
for page_num in range(len(reader.pages)):
|
||||
text += reader.pages[page_num].extract_text()
|
||||
return text
|
||||
|
||||
@classmethod
|
||||
def read_markdown(cls, file_path: str):
|
||||
# 读取Markdown文件
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
md_text = file.read()
|
||||
html_text = markdown.markdown(md_text)
|
||||
# 使用BeautifulSoup从HTML中提取纯文本
|
||||
soup = BeautifulSoup(html_text, 'html.parser')
|
||||
plain_text = soup.get_text()
|
||||
# 使用正则表达式移除网址链接
|
||||
text = re.sub(r'http\S+', '', plain_text)
|
||||
return text
|
||||
|
||||
@classmethod
|
||||
def read_text(cls, file_path: str):
|
||||
# 读取文本文件
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
return file.read()
|
||||
|
||||
|
||||
class Documents:
|
||||
"""
|
||||
获取已分好类的json格式文档
|
||||
"""
|
||||
def __init__(self, path: str = '') -> None:
|
||||
self.path = path
|
||||
|
||||
def get_content(self):
|
||||
with open(self.path, mode='r', encoding='utf-8') as f:
|
||||
content = json.load(f)
|
||||
return content
|
||||
@@ -1,42 +0,0 @@
|
||||
[
|
||||
{"question":"什么是神经网络?", "chosen":"神经网络是一种模拟人脑神经结构的计算模型,通常用于处理复杂的模式识别和分类任务。","rejected":"神经网络用于处理数据。"},
|
||||
{"question":"如何选择合适的机器学习模型?", "chosen":"选择合适的机器学习模型可以通过考虑问题的特性、数据量和模型的复杂性来实现。","rejected":"选择合适的模型是很重要的。"},
|
||||
{"question":"什么是特征工程?", "chosen":"特征工程是机器学习中准备数据的一步,包括选择、提取和转换输入特征。","rejected":"特征工程是机器学习的一部分。"},
|
||||
{"question":"Python适合初学者吗?", "chosen":"Python非常适合初学者,因为其代码简洁易读,并且拥有广泛的社区支持。","rejected":"Python适合初学者。"},
|
||||
{"question":"什么是监督学习?", "chosen":"监督学习是一种机器学习方法,其中模型通过已知的输入输出对进行训练,以预测未知数据的结果。","rejected":"监督学习是机器学习的一种方法。"},
|
||||
{"question":"如何实现数据可视化?", "chosen":"数据可视化可以通过使用Matplotlib、Seaborn或其他可视化库来生成图表和图形。","rejected":"数据可以用图形展示。"},
|
||||
{"question":"Python有哪些常用的库?", "chosen":"Python常用的库包括NumPy用于数值计算,Pandas用于数据处理,以及Matplotlib用于可视化。","rejected":"Python有很多库。"},
|
||||
{"question":"什么是无监督学习?", "chosen":"无监督学习是一种机器学习方法,通过分析数据的内在结构进行模式识别和聚类,而无需已标记的数据。","rejected":"无监督学习不需要标签。"},
|
||||
{"question":"如何处理数据的缺失值?", "chosen":"处理缺失值的方法包括删除含有缺失值的记录、用均值填充缺失值或使用预测模型进行插补。","rejected":"缺失值需要被处理。"},
|
||||
{"question":"什么是支持向量机?", "chosen":"支持向量机是一种用于分类和回归分析的监督学习算法,通过在高维空间中寻找最佳分隔超平面实现。","rejected":"支持向量机是一种算法。"},
|
||||
{"question":"深度学习中的反向传播是什么?", "chosen":"反向传播是一种用于训练神经网络的算法,通过计算损失函数的梯度调整权重。","rejected":"反向传播用于训练网络。"},
|
||||
{"question":"如何评价机器学习模型的性能?", "chosen":"模型的性能可以通过准确率、精确率、召回率和F1分数等指标进行评价。","rejected":"模型性能需要被评估。"},
|
||||
{"question":"Python如何管理包和依赖?", "chosen":"Python使用工具如pip或conda来管理包和依赖,方便安装和更新。","rejected":"Python用pip管理包。"},
|
||||
{"question":"如何提升深度学习模型的性能?", "chosen":"提升深度学习模型的性能可以通过架构调整、数据扩增和优化算法调整等实现。","rejected":"模型性能可以被提高。"},
|
||||
{"question":"什么是数据标准化?", "chosen":"数据标准化是调整数据尺度的方法,将特征调整到同一范围以提高模型训练效果。","rejected":"标准化用于调整数据。"},
|
||||
{"question":"机器学习中的过拟合是什么?", "chosen":"过拟合是指模型在训练集上表现良好,但在未见数据上效果不佳,通常由于模型过于复杂。","rejected":"过拟合影响模型性能。"},
|
||||
{"question":"什么是逻辑回归?", "chosen":"逻辑回归是一种用于分类问题的回归分析方法,通过逻辑函数将输入映射到一个概率。","rejected":"逻辑回归用于分类。"},
|
||||
{"question":"如何处理数据中的异常值?", "chosen":"处理异常值的方法包括删除异常值、替换或对其进行数据转换。","rejected":"异常值需要被处理。"},
|
||||
{"question":"Python的面向对象编程特性怎么样?", "chosen":"Python支持面向对象编程,允许定义类和对象并支持继承和多态。","rejected":"Python支持OOP。"},
|
||||
{"question":"有什么方法可以提高机器学习模型的泛化能力?", "chosen":"可以通过降低模型复杂度、使用正则化、以及增加训练数据等方法来提高模型的泛化能力。","rejected":"泛化能力需要被提高。"},
|
||||
{"question":"什么是卷积神经网络(CNN)?", "chosen":"卷积神经网络是一种专门用于处理具有网格数据的深度学习算法,广泛应用于图像和视频识别。","rejected":"卷积神经网络用于图像处理。"},
|
||||
{"question":"如何评估回归模型的性能?", "chosen":"回归模型的性能可以通过均方误差(MSE)、均方根误差(RMSE)和R^2分数等指标进行评估。","rejected":"回归模型需要性能评估。"},
|
||||
{"question":"什么是特征选择?", "chosen":"特征选择是在训练模型之前选择最具信息量的特征,以提高模型的性能并减少复杂度。","rejected":"特征选择能提升性能。"},
|
||||
{"question":"如何使用Python进行文本分析?", "chosen":"可以使用Python的NLTK或spaCy库进行文本分析,处理自然语言数据。","rejected":"Python用于文本分析。"},
|
||||
{"question":"机器学习中的泛化是什么?", "chosen":"泛化是指模型在处理未见数据时的表现,是评价模型性能的重要标准。","rejected":"泛化影响模型性能。"},
|
||||
{"question":"什么是随机森林?", "chosen":"随机森林是一种集成学习方法,通过构建多棵决策树来提高分类或回归性能。","rejected":"随机森林是集成方法。"},
|
||||
{"question":"如何处理数据中的类别变量?", "chosen":"处理类别变量可以采用编码方法如独热编码或标签编码,将类别转换为数值形式。","rejected":"类别变量需要编码。"},
|
||||
{"question":"什么是梯度下降算法?", "chosen":"梯度下降是一种优化算法,用于通过不断调整参数以最小化损失函数。","rejected":"梯度下降用于优化。"},
|
||||
{"question":"为什么使用正则化技术?", "chosen":"正则化用于减少模型的过拟合,通过在损失函数中增加惩罚项限制权重的大小。","rejected":"正则化减少过拟合。"},
|
||||
{"question":"如何处理时间序列数据?", "chosen":"处理时间序列数据时,通常需要考虑时间依赖性,可能使用平滑、差分等技术来提高分析效果。","rejected":"时间序列需要特殊处理。"},
|
||||
{"question":"什么是集成学习?", "chosen":"集成学习是一种将多个学习器结合以提高整体预测性能的机器学习方法。","rejected":"集成学习结合多模型。"},
|
||||
{"question":"如何通过Python实现自动化测试?", "chosen":"可以使用Python的unittest或pytest库实现自动化测试,以保证代码质量。","rejected":"Python可以测试代码。"},
|
||||
{"question":"什么是主成分分析(PCA)?", "chosen":"主成分分析是一种降维技术,通过对数据进行线性变换来提取主要特征。","rejected":"PCA用于降维。"},
|
||||
{"question":"如何提升算法的计算效率?", "chosen":"可以通过优化算法实现、利用并行计算、以及高效的数据结构来提升计算效率。","rejected":"计算效率需要提升。"},
|
||||
{"question":"什么是生物信息学?", "chosen":"生物信息学是结合生物学和信息技术来处理生物数据的学科,涉及基因组、蛋白质组等领域的研究。","rejected":"生物信息学处理生物数据。"},
|
||||
{"question":"如何实现数据采样?", "chosen":"数据采样可以通过随机采样、分层采样等方法来从数据集中选择部分样本进行分析。","rejected":"数据采样用于选择样本。"},
|
||||
{"question":"金融科技的关键技术有哪些?", "chosen":"金融科技的关键技术包括区块链、人工智能、大数据分析和云计算。","rejected":"金融科技涉及技术。"},
|
||||
{"question":"Python的垃圾回收机制是什么?", "chosen":"Python的垃圾回收机制通过引用计数和垃圾回收器(GC)来管理内存释放。","rejected":"Python自动管理内存。"},
|
||||
{"question":"如何使用随机搜索调优超参数?", "chosen":"随机搜索通过在参数空间中随机选择一组参数组合来找到较优的模型配置。","rejected":"随机搜索优化参数。"},
|
||||
{"question":"什么是过采样和下采样?", "chosen":"过采样和下采样是处理不平衡数据集的方法,通过增加少数类样本或减少多数类样本来平衡类别分布。","rejected":"采样处理不平衡数据。"}
|
||||
]
|
||||
|
Before Width: | Height: | Size: 21 KiB |
BIN
docs/chapter7/images/8-1-Open LLM Leaderboard.png
Normal file
|
After Width: | Height: | Size: 178 KiB |
BIN
docs/chapter7/images/8-1-lmsys Chatbot Arena Leaderboard.png
Normal file
|
After Width: | Height: | Size: 206 KiB |
BIN
docs/chapter7/images/8-1-opencompass.png
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
docs/chapter7/images/8-1-垂直领域榜单.png
Normal file
|
After Width: | Height: | Size: 265 KiB |
BIN
docs/chapter7/images/8-3-rag.png
Normal file
|
After Width: | Height: | Size: 400 KiB |
BIN
docs/chapter7/images/8-3-tinyrag.png
Normal file
|
After Width: | Height: | Size: 566 KiB |