首页 > 数据库 >构建RAG应用-day01: 词向量和向量数据库 文档预处理

构建RAG应用-day01: 词向量和向量数据库 文档预处理

时间:2024-04-17 23:24:39浏览次数:41  
标签:RAG text day01 content embedding pdf page 向量

词向量和向量数据库

image-20240417145729703

词向量(Embeddings)是一种将非结构化数据,如单词、句子或者整个文档,转化为实数向量的技术。

词向量搜索和关键词搜索的比较

优势1:词向量可以语义搜索

比如百度搜索,使用的是关键词搜索。而词向量搜索,是对句子的语义进行搜索,他会找到意思相近的前k个句子。

优势2:词向量可以对多模态数据进行搜索

当传统数据库存储文字、声音、图像、视频等多种媒介时,很难去将上述多种媒介构建起关联与跨模态的查询方法;但是词向量却可以通过多种向量模型将多种数据映射成统一的向量形式。

缺点:准确度问题

向量数据库

定义
向量数据库是一种专门用于存储和检索向量数据(embedding)的数据库系统。
在向量数据库中,数据被表示为向量形式,每个向量代表一个数据项。这些向量可以是数字、文本、图像或其他类型的数据。向量数据库使用高效的索引和查询算法来加速向量数据的存储和检索过程。

原理
向量数据库中的数据以向量作为基本单位,对向量进行存储、处理及检索。向量数据库通过计算与目标向量的余弦距离、点积等获取与目标向量的相似度。当处理大量甚至海量的向量数据时,向量数据库索引和查询算法的效率明显高于传统数据库。

主流向量数据库

  • Chroma:是一个轻量级向量数据库,拥有丰富的功能和简单的 API,具有简单、易用、轻量的优点,但功能相对简单且不支持GPU加速,适合初学者使用。
  • Weaviate:是一个开源向量数据库。除了支持相似度搜索和最大边际相关性(MMR,Maximal Marginal Relevance)搜索外还可以支持结合多种搜索算法(基于词法搜索、向量搜索)的混合搜索,从而搜索提高结果的相关性和准确性。
  • Qdrant:Qdrant使用 Rust 语言开发,有极高的检索效率和RPS(Requests Per Second),支持本地运行、部署在本地服务器及Qdrant云三种部署模式。且可以通过为页面内容和元数据制定不同的键来复用数据。

使用 OpenAI Embedding API

有三种Embedding模型,性能如下所示:

模型 每美元页数 MTEB得分 MIRACL得分
text-embedding-3-large 9,615 54.9 64.6
text-embedding-3-small 62,500 62.3 44.0
text-embedding-ada-002 12,500 61.0 31.4
  • MTEB得分为embedding model分类、聚类、配对等八个任务的平均得分。
  • MIRACL得分为embedding model在检索任务上的平均得分。

text-embedding-3-large性能最好,text-embedding-3-small性价比高,text-embedding-ada-002效果不好,不推荐。

如果没有openai apikey可以使用自己的github账号去这个项目领取一个免费的使用:
chatanywhere/GPT_API_free: Free ChatGPT API Key,免费ChatGPT API,支持GPT4 API(免费),ChatGPT国内可用免费转发API,直连无需代理。可以搭配ChatBox等软件/插件使用,极大降低接口使用成本。国内即可无限制畅快聊天。 (github.com)

import os
from openai import OpenAI
from dotenv import load_dotenv, find_dotenv


# 读取本地/项目的环境变量。
# find_dotenv()寻找并定位.env文件的路径
# load_dotenv()读取该.env文件,并将其中的环境变量加载到当前的运行环境中  
# 如果你设置的是全局的环境变量,这行代码则没有任何作用。
_ = load_dotenv(find_dotenv())

# 如果你需要通过代理端口访问,你需要如下配置
os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'
os.environ["HTTP_PROXY"] = 'http://127.0.0.1:7890'

def openai_embedding(text: str, model: str=None):
    # 获取环境变量 OPENAI_API_KEY
    api_key=os.environ['OPENAI_API_KEY']
    client = OpenAI(api_key=api_key)

    # embedding model:'text-embedding-3-small', 'text-embedding-3-large', 'text-embedding-ada-002'
    if model == None:
        model="text-embedding-3-small"

    response = client.embeddings.create(
        input=text,
        model=model
    )
    return response

response = openai_embedding(text='要生成 embedding 的输入文本,字符串形式。')

执行代码:

response = openai_embedding(text='要生成 embedding 的输入文本,字符串形式。')
# 返回一个Embedding对象
print(response)
# 获取真正的Embedding数据,是一个list
print(response.data[0].embedding)
# 更多
print(f'本次embedding model为:{response.model}')
print(f'本次token使用情况为:{response.usage}')
'''
返回对象:
CreateEmbeddingResponse(data=[Embedding(embedding=[0.03884002938866615, 0.013516489416360855, -0.0024250170681625605, ... 中间很长省略 ..., 0.002844922710210085, -0.012999682687222958], index=0, object='embedding')], model='text-embedding-3-small', object='list', usage=Usage(prompt_tokens=12, total_tokens=12, completion_tokens=0))
'''
{
  "object": "list",
  "data": [
    {
      "object": "embedding",
      "index": 0,
      "embedding": [
        -0.006929283495992422,
        ... (省略)
        -4.547132266452536e-05,
      ],
    }
  ],
  "model": "text-embedding-3-small",
  "usage": {
    "prompt_tokens": 5,
    "total_tokens": 5
  }
}

文档预处理

使用 langchain text_splitter

使用re处理\n、空格、·
使用langchain RecursiveCharacterTextSplitter 按照分隔符的优先级进行递归的文档分割。
设置 单段文本长度(CHUNK_SIZE)、知识库中相邻文本重合长度(OVERLAP_SIZE)。CHUNK_SIZE = 500 OVERLAP_SIZE = 50

import re
from langchain.document_loaders.pdf import PyMuPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 创建一个 PyMuPDFLoader Class 实例,输入为待加载的 pdf 文档路径
loader = PyMuPDFLoader("books/1-民法典总则编 理解与适用 上.pdf")

# 调用 PyMuPDFLoader Class 的函数 load 对 pdf 文件进行加载
pdf_pages = loader.load()

# 载入后的变量类型为:<class 'list'>, 该 PDF 一共包含 540 页
print(f"载入后的变量类型为:{type(pdf_pages)},", f"该 PDF 一共包含 {len(pdf_pages)} 页")

pdf_page = pdf_pages[100]
print(f"每一个元素的类型:{type(pdf_page)}.",
      f"该文档的描述性数据:{pdf_page.metadata}",
      f"查看该文档的内容:\n{pdf_page.page_content}",
      sep="\n------\n")

# 清除字符之间的换行符\n
pattern = re.compile(r'[^\u4e00-\u9fff](\n)[^\u4e00-\u9fff]', re.DOTALL)

# re.sub(匹配模式,对匹配到的内容使用函数处理,被匹配的字符串),如下就是将匹配到的\n替换为空字符串
pdf_page.page_content = re.sub(pattern, lambda match: match.group(0).replace('\n', ''), pdf_page.page_content)
print("清除换行符之后的内容:", pdf_page.page_content, sep="\n------\n")

# 清除 • 和 空格
pdf_page.page_content = pdf_page.page_content.replace('•', '')
pdf_page.page_content = pdf_page.page_content.replace(' ', '')

# 替换\n\n
pdf_page.page_content = pdf_page.page_content.replace('\n\n', '\n')
print("继续清洗的结果:", pdf_page.page_content, sep="\n------\n")

# 知识库中单段文本长度(CHUNK_SIZE)、知识库中相邻文本重合长度(OVERLAP_SIZE)
CHUNK_SIZE = 500
OVERLAP_SIZE = 50

# 使用递归字符文本分割器 (递归地尝试按不同的分隔符进行分割文本。)
''' 
* RecursiveCharacterTextSplitter
按不同的字符递归地分割(按照这个优先级["\n\n", "\n", " ", ""]),这样就能尽量把所有和语义相关的内容尽可能长时间地保留在同一位置

RecursiveCharacterTextSplitter需要关注的是4个参数:
* separators - 分隔符字符串数组
* chunk_size - 每个文档的字符数量限制
* chunk_overlap - 两份文档重叠区域的长度
* length_function - 长度计算函数
'''
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=CHUNK_SIZE,
    chunk_overlap=OVERLAP_SIZE
)
# 将第一页的前 1000 个字符进行分割
chunks = text_splitter.split_text(pdf_page.page_content[0:1000])

for i, chunk in enumerate(chunks, start=1):
    print(f'第{i}块的内容如下:', chunk, sep="\n------\n", end="\n\n")
print(f"前1000个字符分块的数量为:{len(chunks)}", sep="\n------\n")
split_docs = text_splitter.split_documents(pdf_pages)
print(f"切分后的文件数量:{len(split_docs)}", sep="\n------\n")
print(f"切分后的字符数(可以用来大致评估 token 数):{sum([len(doc.page_content) for doc in split_docs])}")

使用 open-parse

项目地址:Filimoa/open-parse: Improved file parsing for LLM’s (github.com)

open-parse推荐使用语义进行分块,并且预设了一些文档预处理流程,还可以将自己的处理方式添加到管道中,或者是自定义预处理管道。

import os
from openparse import processing, DocumentParser

basic_doc_path = "books/人生亏钱指南-TEST.pdf"

# 使用语义来进行分块,使用 openai embedding 时间上会有点旧
# SemanticIngestionPipeline 是一个预处理管道,中间集成了各种预处理方式,按照顺序执行
semantic_pipeline = processing.SemanticIngestionPipeline(
    openai_api_key=os.getenv("OPENAI_API_KEY"),
    model="text-embedding-3-large",
    min_tokens=64,
    max_tokens=1024,
)
parser = DocumentParser(
    processing_pipeline=semantic_pipeline,
)
parsed_content = parser.parse(basic_doc_path)
print('chunk数量:', len(parsed_content.nodes))

for node in parsed_content.nodes:
    print('chunk:', node.text, end='\n')
print('---------------------')
for data in parsed_content:
    print(data[0], data[1], end='\n')

添加你的处理流程:

from openparse import processing, Node
from typing import List


class CustomCombineTables(processing.ProcessingStep):
    """
    Let's combine tables that are next to each other
    """

    def process(self, nodes: List[Node]) -> List[Node]:
        new_nodes = []
        print("Combining concurrent tables")
        for i in range(len(nodes) - 1):
            if "table" in nodes[i].variant and "table" in nodes[i + 1].variant:
                new_node = nodes[i] + nodes[i + 1]
                new_nodes.append(new_node)
            else:
                new_nodes.append(nodes[i])

        return new_nodes


# add a custom processing step to the pipeline
custom_pipeline = processing.BasicIngestionPipeline()
custom_pipeline.append_transform(CustomCombineTables())

parser = openparse.DocumentParser(
    table_args={"parsing_algorithm": "pymupdf"}, processing_pipeline=custom_pipeline
)
custom_10k = parser.parse(meta10k_path)

自定义整个处理管道:

from openparse import processing, Node
from typing import List


class BasicIngestionPipeline(processing.IngestionPipeline):
    """
    A basic pipeline for ingesting and processing Nodes.
    """

    def __init__(self):
        self.transformations = [
            processing.RemoveTextInsideTables(),
            processing.RemoveFullPageStubs(max_area_pct=0.35),
        ]

更多参考:

标签:RAG,text,day01,content,embedding,pdf,page,向量
From: https://www.cnblogs.com/passion2021/p/18142036

相关文章

  • CF81C Average Score 题解
    题目简述给定一个长度为$n$的序列,在其中取出$x$个数,构成一个数列$a$,剩下的$y$个数构成数列$b$。若第$i$个数在数列$a$中,$ans_i$等于$1$,否则等于$2$,请你给出一种方案使得两数列的平均数之和最大且$ans$的字典序最小.题目分析我们先考虑$x=y$的情况,在这种情......
  • 【视频】R语言支持向量回归SVR预测水位实例讲解|附代码数据
    全文链接:https://tecdat.cn/?p=35914原文出处:拓端数据部落公众号分析师:MiaoqiaoWang当我们面对样本需要建立相应模型时,使用传统统计方法建立模型需要大量的样本数据,只有在样本量足够大时,该模型才具有一定的可靠性,而实际实验中,不一定每次实验都拥有足够大的样本,甚至是小样本,这......
  • 认识什么是LLM、RAG、LangChain以及开发LLM应用的整体流程?
    认识大语言模型LLM时间:2024-04-15,星期一一、大型语言模型(LLM)理论简介大语言模型(LLM)的概念⼤语⾔模型(LLM,LargeLanguageModel),也称⼤型语⾔模型,是⼀种旨在理解和⽣成⼈类语⾔的⼈⼯智能模型。LLM的发展历程20世纪90年代,统计学习⽅法来预测词汇2003年深度学习先驱B......
  • 向量数据库之Lancedb学习记录
    简介Lancedb是一个用于人工智能的开源矢量数据库,旨在存储、管理、查询和检索大规模多模式数据的嵌入。Lancedb的核心是用Rust编写的,并构建在Lance之上,专为高性能ML工作负载和快速随机访问而设计。快速开始安装pipinstalllancedb目前0.6.8需要pyarrow-12.0.0及以上,亲测15......
  • P10320 勇气(Courage)
    原题链接题解请看这我补充一点:由于\(x\leqslant2\)和\(n\leqslant2\)很明显不对劲,所以要特判一下二点:像这种看起来需要模拟来找答案的不妨手推一下数学式子来找答案code#include<bits/stdc++.h>usingnamespacestd;intmain(){doublex,n;cin>>x>>n;......
  • day01-02_我的Java学习笔记 (IDEA的安装、配置及使用、IDEA常用快捷键、IEDA创建空工
    1.IDEA的安装及配置1.1IDEA的安装具体操作,详见《04、IDEA安装详解.pdf》1.2IDEA主题配置、字体配置1.3IDEA常用快捷键1.4IDEA修改快捷键在IDEA工具中,Ctrl+空格的快捷键,可以帮助我们补全代码,但是这个快捷键和Windows中的输入法切换快捷键冲突,需要修改IDEA中......
  • day01-03_我的Java学习笔记(Java基础语法--注释、字面量、变量、二进制、ASCII编码、
    1.Java基础语法1.1注释1.2字面量(Python中叫数据类型)1.3变量1.3.1变量的定义及使用1.3.2变量使用注意事项1.4数据的存储形式:二进制字节、字、bit、byte的关系:字word字节byte位bit,来自英文bit,音译为“比特”,表示二进制位。字长是指字的......
  • RAG知识库优化之Rerank应用
          前面几篇文章介绍了最基本的RAG实现,也了解了RAG的基本原理。最基本的RAG流程为用户Query问题,RAG应用在向量库查询出Context,然后将Query与Context喂到LLM,LLM生成满足Query的Response答案。      从这里可以存在三元组:Query、Context和Response,如要想进一步......
  • RAG 工具和框架介绍: Haystack、 LangChain 和 LlamaIndex
     Haystack、LangChain和LlamaIndex,以及这些工具是如何让我们轻松地构建RAG应用程序的? 我们将重点关注以下内容:HaystackLangChainLlamaIndex增强LLM那么,为什么会有这些工具存在呢?如你所知,ChatGPT和其他LLM是在某个时间点之前的一组数据上进行训练的。更重要的是,它......
  • 【数学】向量点乘、叉乘的理论、应用及代码实现(C++)
    前言我总结了一下向量点乘,叉乘的概念,以及他们的应用及相关C++代码的实现。blog这类问题也是技术面试经常碰到的,一次研究透了会有收获。1向量向量具有大小和方向。共线向量:两个平行的向量为共线向量。1.1叉积CrossProduct$$\vec{a}\times\vec{b}=|\vec{a}||\vec{b}|\sin......