智能化收据查询:利用rag提升信息提取效率的5个步骤
Photo by Christina Radevich on Unsplash
在当今的数字时代,从收据中管理和提取信息可能是一项繁琐的任务。但如果你可以像在数据库中搜索一样智能地查询你的收据呢?借助检索增强生成的力量,结合大型语言模型和向量搜索,这成为现实。通过将你的收据转化为可搜索的数据,并将其与先进的人工智能模型结合,你可以提出复杂的问题,并实时获得准确、上下文相关的答案。在本文中,我将带你了解如何利用检索增强生成使收据查询更智能、更快速、更直观!
RAG 是如何工作的
在深入代码之前,让我们首先更清楚地了解 RAG 的工作原理。请查看下面的图示:
所有图像均由作者创建,除非另有说明
- 您的私人文档(本文中的收据)被转换为 词向量嵌入,一个过程称为 嵌入。
- 一旦创建了嵌入,它们会存储在像 ChromaDB 这样的向量数据库中,或直接保存在存储中。
- 当用户提出问题时,问题也会转换为 嵌入。
- 问题的嵌入随后与文档嵌入进行比较,使用一种称为 相似性搜索 的技术。
- 最后,相似性搜索的结果被传递给 LLM,LLM 使用它们为用户生成连贯的响应。
现在您对 RAG 的工作原理有了清晰的了解,让我们来看看代码吧!
安装 Tesseract
我们将使用一款名为 Tesseract 的光学字符识别 (OCR) 软件从收据中提取文本内容。
Tesseract 是一个开源的光学字符识别 (OCR) 引擎,可以从图像和扫描文档中提取文本。它最初由惠普开发,现在由谷歌维护。Tesseract 支持多种语言,并且在结合基于机器学习的预处理技术时,能够高精度识别打印和手写文本。
对于 Windows 用户,您需要首先访问 https://github.com/UB-Mannheim/tesseract/wiki 安装 Tesseract。然后,下载 tesseract-ocr-w64-setup-5.5.0.20241111.exe 文件。
对于 Mac 用户,安装 Tesseract 的最简单方法是通过 brew:
一旦安装了 Tesseract,接下来要做的就是安装 pytesseract
Python 包:
在这个例子中,让我们使用来自维基百科的一个示例收据。以下是收据:
一张在 Grosse Scheidegg 顶部的瑞士山餐厅获得的收据。包括餐点和价格列表、桌号 (7/01)、两种货币 (瑞士法郎和欧元) 的总价格信息、关于 7.6% 税的说明、联系信息,包括公司的税务注册号和收银员的名字 (Ursula). https://simple.wikipedia.org/wiki/Receipt#/media/File:ReceiptSwiss.jpg
要从此图像中提取文本,请使用 pytesseract
模块的 image_to_string()
方法:
from PIL import Image
import pytesseract
image = Image.open("./receipts/ReceiptSwiss.jpg")
extracted_text = pytesseract.image_to_string(image)
print("Extracted Text:", extracted_text)
提取的文本将被打印出来:
Extracted Text: Berghote |
Grosse Scheidegg
3818 Grindelwald
Familie R. Muller
Rech. Nr. 4572 30.07. 2007/13:29: 17
Bar Tisch 7/01
éxLatte Macchiato 4,50 9, 00
IxGloki 5.00 5.00
ixSchweinschnitze | 22. 00 22.00
IxChasspaétz |i 18.50
Total : CHF
Incl. 7.6% MwSt 9 54.50 CHE: 3.85
Entspricht in Euro 36.33 EUR
Es bediente Sie: Ursula
MwSt Nr.: 480 234
Tel.: 033 853 67 16
Fax: 0388 8bs 67 19
E-mail: grossescheidegg@b luewin. ch
请注意提取的文本,并观察并非所有内容都是准确的。
创建向量存储索引
下一步是创建一个 向量存储索引。向量存储索引是一种在检索增强生成中用于基于向量嵌入存储和检索文档的索引类型,而不是基于精确文本匹配。它允许进行语义搜索,这意味着即使查询中不包含文档中存在的确切单词,也可以检索相关文档。
对于本文,您需要一个 OpenAI API 密钥,因为我们将使用 OpenAI 的嵌入模型:
import os
os.environ["OPENAI_API_KEY"] = "xxxxxxxx"
您还需要安装 llama_index
包:
LlmaIndex 是一个用于构建检索增强生成(RAG)应用程序的框架。
接下来,从提取的收据文本创建一个 Document
对象,并使用它创建向量存储索引:
from llama_index.core import Document, VectorStoreIndex
doc = Document(text = extracted_text)
index = VectorStoreIndex.from_documents([doc])
index.storage_context.persist(persist_dir = "./receiptsembeddings")
上面的代码片段做了以下事情:
**Document**
:表示一段文本(例如,收据、文章或任何文档),将被存储和索引以便检索。**VectorStoreIndex**
:一种基于向量嵌入存储和检索文档的索引类型。它使相似性搜索成为可能,对于检索增强生成(RAG)非常有用。如果未提供自定义嵌入模型,LlamaIndex 默认使用 OpenAI 的 gpt-3.5-turbo 或其他模型来生成嵌入。向量索引允许基于语义相似性进行查询,而不是精确单词匹配。**storage_context.persist(...)**
:将生成的索引保存到磁盘,以便可以在以后加载而无需重新计算嵌入。索引存储在receiptsembeddings
目录中,允许在程序的多次运行中重复使用。
有关向量嵌入的更多信息,请查看我之前关于此主题的文章:
如果您想使用另一个嵌入模型,例如 gpt-4o-mini
,可以通过 Settings
类进行设置,如下代码片段所示:
from llama_index.llms.openai import OpenAI
from llama_index.core import Document, VectorStoreIndex, Settings
llm = OpenAI(model="gpt-4o-mini")
Settings.llm = OpenAI(model="gpt-4o-mini")
doc = Document(text = extracted_text)
index = VectorStoreIndex.from_documents([doc])
index.storage_context.persist(persist_dir = "./receiptsembeddings")
使用之前的 RAG 工作原理图,这一部分的作用如下:
加载保存的向量存储索引
要将嵌入加载回向量存储索引,您可以使用以下方法:
from llama_index.core import load_index_from_storage
persist_dir = "./receiptsembeddings"
storage_context = StorageContext.from_defaults(persist_dir = persist_dir)
index = load_index_from_storage(storage_context)
保存和加载嵌入可以实现高效重用,减少计算开销,并确保会话间的持久性。想象一下,您有很多文档,并花费了相当多的时间生成嵌入。保存和重新加载嵌入使您能够快速访问和使用它们,而无需重新计算,从而节省未来处理或查询的时间和资源。
执行查询
随着向量存储索引加载了嵌入,您现在可以使用自然语言进行检索:
query_engine = index.as_query_engine()
while True:
question = input("问题: ")
if question.lower() == 'quit': break
response = query_engine.query(question)
print(response.response)
在上述代码片段中,向量存储索引现在使用 as_query_engine()
方法转换为查询引擎。当提出问题时,查询引擎首先将其转换为嵌入(向量表示)。
查询引擎使用与索引相同的嵌入模型将您的问题转换为嵌入:
然后,它通过相似性搜索将查询嵌入与存储的文档嵌入进行比较,从向量存储索引中检索最相关的文档,基于它们的语义相似性识别最接近的匹配:
查询嵌入使用相似性度量与存储的文档嵌入进行比较,例如:
- 余弦相似度(最常见)
- 欧几里得距离
- 点积
检索到的文档将传递给大型语言模型(如 GPT-4 或 gpt-4o-mini)以生成响应,利用文档中的信息提供相关且具有上下文意识的答案:
在我们的示例中,如果您之前使用以下语句设置了大型语言模型:
Settings.llm = OpenAI(model="gpt-4o-mini")
在这种情况下使用的大型语言模型将是 gpt-4o-mini(它将用于嵌入和响应生成)。如果您没有全局设置大型语言模型,则默认使用的将是 gpt-3.5-turbo。
如果您希望使用其他大型语言模型进行响应生成,可以通过在 as_query_engine()
方法中设置 llm
参数来实现,如下所示:
query_engine = index.as_query_engine(llm=OpenAI(model="gpt-4"))
简而言之:
- 您可以使用 一个模型进行嵌入,该模型将为您的 文本(文档)和 问题(查询)生成向量表示。
- 您可以使用 另一个模型生成响应,该模型将处理检索到的相关文档并生成自然语言答案。
以下是一个示例问题及其响应:
这是另一个示例:
这是另一个问题:
如果您想知道从向量存储中检索到的内容(传递给大型语言模型以生成响应的结果),可以使用查询引擎中的 retrieve()
方法:
query_engine.retrieve("税是多少?")
您将看到以下内容:
NodeWithScore(
node=TextNode(
id_='11a65a30-e304-4c95-bbd4-eba0152c39c1',
embedding=None,
metadata={},
excluded_embed_metadata_keys=[],
excluded_llm_metadata_keys=[],
relationships={
<NodeRelationship.SOURCE: '1'>: RelatedNodeInfo(
node_id='73f5e67a-247c-49b1-94fe-fe44fb76cec7',
node_type='4',
metadata={},
hash='5e789778b7dfa4bf058e03b875da4672f9e509d1ca79dbaad2610cffaad8b1cc')
},
metadata_template='{key}: {value}',
metadata_separator='\\n',
text='Berghote |\\nGrosse Scheidegg\\n3818 Grindelwald\\nFamilie R. Muller\\n\\nRech. Nr. 4572 30.07. 2007/13:29: 17\\nBar Tisch 7/01\\n\\néxLatte Macchiato 4,50 9, 00\\n\\nIxGloki 5.00 5.00\\nixSchweinschnitze | 22. 00 22.00\\nIxChasspaétz |i 18.50\\n\\nTotal : CHF\\n\\nIncl. 7.6% MwSt 9 54.50 CHE: 3.85\\n\\nEntspricht in Euro 36.33 EUR\\nEs bediente Sie: Ursula\\n\\nMwSt Nr.: 480 234\\nTel.: 033 853 67 16\\nFax: 0388 8bs 67 19\\nE-mail: grossescheidegg@b luewin. ch',
mimetype='text/plain',
start_char_idx=0,
end_char_idx=417,
metadata_seperator='\\n',
text_template='{metadata_str}\\n\\n{content}'
),
score=0.7346381148945849
)
无论您问什么问题,在此示例中您将始终看到相同的检索文档,因为我们在向量存储中只有一个文档。如果您添加多个文档,检索结果将根据查询而变化。
使用 Gradio 包装应用程序
与其硬编码特定的收据,不如允许用户上传他们收据的图像,然后询问相关问题?您可以使用 Gradio 非常轻松地做到这一点。
Gradio 是一个开源的 Python 库,使得创建和分享机器学习和数据科学应用程序变得简单,提供了一个简单的 Web 接口。它允许开发者快速构建 AI 模型、数据处理工具或其他 Python 函数的交互式 UI,而无需广泛的 Web 开发技能。
首先,定义一个名为 query_rag()
的函数,如下所示:
import numpy as np
from PIL import Image
import pytesseract
from llama_index.llms.openai import OpenAI
from llama_index.core import Document, VectorStoreIndex, Settings
saved_image = np.empty(0)
query_engine = None
def query_rag(image, question):
global saved_image, query_engine
if not np.array_equal(saved_image, image):
image = Image.fromarray(image)
extracted_text = pytesseract.image_to_string(image)
print(extracted_text)
llm = OpenAI(model="gpt-4o-mini")
Settings.llm = OpenAI(model="gpt-4o-mini")
doc = Document(text=extracted_text)
index = VectorStoreIndex.from_documents([doc])
query_engine = index.as_query_engine()
saved_image = image
response = query_engine.query(question)
return response.response
该函数接受收据的图像以及一个问题。请注意,如果再次提交相同的图像,就不需要重复文本提取和嵌入过程,因为这会耗费时间并产生额外成本。
接下来,使用 Gradio 的 Interface
类创建一个 Web 前端:
import gradio as gr
interface = gr.Interface(
fn=query_rag,
inputs=["image", "text"],
outputs=gr.Textbox(label="Answer"),
title="使用检索增强生成查询收据",
description="拖动您的收据并提问!",
)
interface.launch()
上述代码片段创建了一个 UI,包含两个输入——一个图像和一个文本框,以及一个输出——一个文本框。运行上述代码将显示以下内容:
将扫描的收据拖放到图像输入框中,输入您的问题,然后点击 提交 以接收回应!
让我们再问一个问题!
再问一个:
看到大型语言模型能够回答与收据相关的问题真是太有趣了!
摘要
在本文中,我展示了如何构建一个简单的检索增强生成系统,允许用户查询自己的收据。尽管最终结果很简单,但本文涵盖了几个关键概念:
- 光学字符识别用于文本提取:您可以使用像Tesseract这样的OCR软件从图像中提取文本。
- 词向量嵌入:要构建一个检索增强生成系统,您需要从数据中生成词向量嵌入。在本文中,我们使用了LlamaIndex中的
向量存储索引
类来实现这一目的。 - 嵌入模型选择:默认情况下,
向量存储索引
使用GPT-3.5-Turbo进行嵌入,但您可以用其他选择的模型替换它。 - 查询处理:当用户提出问题时,它会使用与嵌入数据相同的模型转换为嵌入。
- 相似性搜索:
向量存储索引
查询引擎执行相似性搜索,以从存储的数据中检索相关结果。 - 大型语言模型响应生成:检索到的结果随后传递给LLM以生成连贯的响应。
俗话说,垃圾进,垃圾出。您扫描的收据的质量在响应的准确性中起着重要作用。如果图像不清晰或扫描质量差,请期待您的查询得到不准确的答案!