简介
OCR是一门精通的科学,而且已经有一段时间了。如今的问题在于数据的提取和处理,而 LLM 在这部分大放异彩。摩根大通最近推出了 DocLLM,这是一种专门为此设计的 LLM。该模型尚未推出,而且在上下文窗口大小方面有一些限制。因此,我决定采取相反的做法,做一个窗口尺寸更大的完全开放源代码。
该项目分为两部分,即 OCR 层和 LLM 层。在生产就绪的项目中,这将是不同的微服务,甚至是不同的服务。但划分是明确的,首先是读取所有内容(OCR 层),然后是提取特定内容(LLM 层)。
OCR 层
将页面转换为图像
首先,必须将任何类型的文件转换为图像。这种方法可确保访问文件中的所有内容。通过将页面分割成图像,可以在后续步骤中对它们进行单独处理。
OCR 预处理图像
通常,图像质量较差,需要进行调整以提高可读性。增强措施包括改变对比度以及使用各种库和框架。
超立方 OCR
它是世界上最著名、最流行的开源 OCR。它的历史也相当悠久,但随着时间的推移,它已被调整为支持更多语言结构和表格。本示例通过用"|"分割来支持表格。
它使用 pytesseract 通过 Python 连接到tesseract。代码如下:
def extract_text_with_pytesseract(list_dict_final_images):
image_list = [list(data.values())[0] for data in list_dict_final_images]
image_content = []
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = []
for index, image_bytes in enumerate(image_list):
future = executor.submit(process_image, index, image_bytes)
futures.append(future)
for future in concurrent.futures.as_completed(futures):
try:
raw_text = future.result()
image_content.append(raw_text)
except Exception as e:
raise Exception(f"Error processing image: {e}")
return image_content
# only this one matters, the rest is concurrency logic
def process_image(index, image_bytes):
try:
image = Image.open(BytesIO(image_bytes))
raw_text = str(image_to_string(image))
return raw_text
except Exception as e:
raise Exception(f"Error processing image {index}: {e}")
LLM 层
提取合同定义
从文档中获取内容后,我们需要以结构化的方式提取信息。下图显示了完整的提示:
协议部分可以用多种方式定义,但你应该使用与任何编程语言相似甚至相同的方式。这里的协议是一个伪代码,包括字段的名称、类型和描述。
你还可以询问文档的分类:
documentType:string => 只能是 "发票"、"销售单"、"有限责任公司创建文件"、"驱逐文件"
为什么选择 JSON 格式而不是 YAML 等其他格式,尤其是 YAML 通常会产生较小的有效载荷?答案在于训练数据的可用性。由于 JSON 是网络上的主要数据交换格式,因此它在用于训练的数据集中更为普遍。
正确提取 JSON
在 OpenAI 中,你已经可以使用 JSON 返回类型:
response = openai.Completion.create(
model="text-davinci-003",
prompt="Translate the following English text to French: 'Hello, how are you?'",
response_format={ "type": "json_object" }
)
由于该功能不可用,你需要手动添加并修剪 JSON 结构。通常情况下,用于培训目的,结果会以这种格式返回:
```json
[Content]
```
有了这样的认识,我们就可以对输入内容进行如下处理:
{
"model": "mistral-tiny",
"messages": [
{
"role": "user",
"content": "[Content]```json\n{"
}
],
...
}
输出内容如下:
{
...
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "[Content]```[Content]"
},
"finish_reason": "stop"
}
],
...
}
你可以使用该函数接收这两个内容,并给出提取的 JSON:
def extractJsonSubstring(str1, str2):
# Concatenate the two strings
combined_str = str1 + str2
# Define the start and end markers
start_marker = "```json"
end_marker = "```"
# Find the start and end positions of the substring
start_pos = combined_str.find(start_marker)
### jump start_pos to the end of the start_marker
start_pos = start_pos + len(start_marker)
end_pos = combined_str.find(end_marker, start_pos)
# Extract the substring
json_substring = combined_str[start_pos:end_pos]
return json_substring
本地运行 - OLLAMA
现在有几种在本地运行模型的方法,如 LLM studio 或 Ollama。在这种情况下,考虑到项目,你可以使用 LlamaIndex 和 Ollama。
查看代码并进行测试
在该 repo 中,你有一个带有一个端点的 FastAPI 应用程序,用于测试所有这些组件。但这只是一个简单的应用程序接口,你应特别针对你的用例进行扩展。
首先,你应指向适当的 Tessaract 可执行文件
如果你使用的是 docker,请使用第二个,并注释硬编码的路径。
此外,本示例使用直接请求 Mistral API 的方式。请确保更改了 config.py 文件中的密钥。
这是一个 FastAPI 项目,因此请确保安装了依赖项,并访问 localhost:8000/docs
到达后,带着文件和合同进入 /extract 端点。
提出要求,你就会得到适当的结果。请记住,这只是一个模板,有些事情可能与棘手的合同不符。
高级案例: 100万个tooken上下文
LLMLingua
LLM Lingua 是我最近看到的最喜欢的软件之一。它既聪明又简单,通过微调一个小得多的模型来压缩内容,这样就能将其传递到一个大得多的昂贵模型上。
根据论文所述,20 倍的压缩率大约会降低 1.5%,但这取决于数据的性质。
Mistral Yarn 128k 上下文窗口
YaRN(另一种 RoPE 扩展方法)使用旋转位置嵌入(RoPE)来增加窗口大小。
使用这两种技术,10x * 128k 上下文窗口压缩率将为你提供一个 1M 以上的窗口。你可以把整本圣经都放进去!
使用更大上下文的情况
有时一个页面并不包含要提取的全部值,有时会是多个页面。获取数据后再进行聚合可能会出现问题,因为他可能会提取错误的值而不是空值(如合同部分所述),因此一种解决方案是简单地增加上下文内容,这样就不会有任何内容遗漏。
结论
正如文档提取开源项目所展示的那样,OCR 和 LLM 技术的整合标志着在分析非结构化数据方面取得了重大进展。Tesseract 和 Mistral 等开源项目的结合是一种完美的实现方式,可用于内部使用案例。