一:根据文档的特定属性或内容进行拆分(Document Specific Splitting)
让我们开始处理 .txt 中普通散文以外的文档类型。如果有图片怎么办?或PDF?或代码片段?
我们的前两个级别对此不太适用,因此我们需要找到不同的策略。
Markdown
\n#{1,6}
- 按新行分割,后跟标题(H1 到 H6)
```\n
- 代码块\n\\*\\*\\*+\n
- 水平线\n---+\n
- 水平线\n___+\n
- 水平线\n\n
双换行\n
- 换行" "
- 空格""
- 字符
from langchain.text_splitter import MarkdownTextSplitter
splitter = MarkdownTextSplitter(chunk_size = 40, chunk_overlap=0)
markdown_text = """
# Fun in California
## Driving
Try driving on the 1 down to San Diego
### Food
Make sure to eat a burrito while you're there
## Hiking
Go to Yosemite
"""
splitter.create_documents([markdown_text])
[Document(page_content='# Fun in California\n\n## Driving'),
Document(page_content='Try driving on the 1 down to San Diego'),
Document(page_content='### Food'),
Document(page_content="Make sure to eat a burrito while you're"),
Document(page_content='there'),
Document(page_content='## Hiking\n\nGo to Yosemite')]
请注意,分割点倾向于靠近Markdown部分。然而,这仍然不是完美的。注意有一个只包含 "there" 的块。在处理较小的片段时,你可能会遇到这种情况
Python
\nclass
- Classes first\ndef
- Functions next\n\tdef
- Indented functions\n\n
- Double New lines\n
- New Lines" "
- Spaces""
- Characters
from langchain.text_splitter import PythonCodeTextSplitter
python_text = """
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person("John", 36)
for i in range(10):
print (i)
"""
python_splitter = PythonCodeTextSplitter(chunk_size=100, chunk_overlap=0)
python_splitter.create_documents([python_text])
[Document(page_content='class Person:\n def __init__(self, name, age):\n self.name = name\n self.age = age'),
Document(page_content='p1 = Person("John", 36)\n\nfor i in range(10):\n print (i)')]
注意类是如何在单个文档中保持在一起的,然后其余的代码在第二个文档中。
JS
与python非常相似
Separators: 分隔符:
\nfunction
- Indicates the beginning of a function declaration\nconst
- Used for declaring constant variables\nlet
- Used for declaring block-scoped variables\nvar
- Used for declaring a variable\nclass
- Indicates the start of a class definition\nif
- Indicates the beginning of an if statement\nfor
- Used for for-loops\nwhile
- Used for while-loops\nswitch
- Used for switch statements\ncase
- Used within switch statements\ndefault
- Also used within switch statements\n\n
- Indicates a larger separation in text or code\n
- Separates lines of code or text" "
- Separates words or tokens in the code""
- Makes every character a separate element
from langchain.text_splitter import RecursiveCharacterTextSplitter, Language
javascript_text = """
// Function is called, the return value will end up in x
let x = myFunction(4, 3);
function myFunction(a, b) {
// Function returns the product of a and b
return a * b;
}
"""
js_splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.JS, chunk_size=65, chunk_overlap=0
)
js_splitter.create_documents([javascript_text])
[Document(page_content='// Function is called, the return value will end up in x'),
Document(page_content='let x = myFunction(4, 3);'),
Document(page_content='function myFunction(a, b) {'),
Document(page_content='// Function returns the product of a and b\n return a * b;\n}')]
PDFs w/ tables
PDF 是语言模型工作中极其常见的数据类型。它们通常会包含包含信息的表格。
这可以是财务数据、研究、学术论文等。
尝试通过基于字符的分隔符拆分表是不可靠的。我们需要尝试一种不同的方法。
实现此目的的一个非常方便的方法是使用 Unstructured,这是一个专门用于使您的数据LLM 准备就绪的库。
import os
from unstructured.partition.pdf import partition_pdf
from unstructured.staging.base import elements_to_json
#让我们加载 PDF,然后对其进行分区。这是来自 Salesforce 收入报告的 PDF 文件
filename = "static/SalesforceFinancial.pdf"
# Extracts the elements from the PDF
elements = partition_pdf(
filename=filename,
# Unstructured Helpers
strategy="hi_res",
infer_table_structure=True,
model_name="yolox"
)
elements[-4].metadata.text_as_html
'<table><thead><th>Revenue)</th><th>Guidance $7.69 - $7.70 Billion</th><th>Guidance $31.7 - $31.8 Billion</th></thead><tr><td>Y/Y Growth</td><td>~21%</td><td>~20%</td></tr><tr><td>FX Impact?)</td><td>~($200M) y/y FX</td><td>~($600M) y/y FX®</td></tr><tr><td>GAAP operating margin</td><td></td><td>~3.8%</td></tr><tr><td>Non-GAAP operating margin)</td><td></td><td>~20.4%</td></tr><tr><td>GAAP earnings (loss) per share</td><td>($0.03) - ($0.02)</td><td>$0.38 - $0.40</td></tr><tr><td>Non-GAAP earnings per share</td><td>$1.01 - $1.02</td><td>$4.74 - $4.76</td></tr><tr><td>Operating Cash Flow Growth (Y/Y)</td><td></td><td>~21% - 22%</td></tr><tr><td>Current Remaining Performance Obligation Growth (Y/Y)</td><td>~15%</td><td></td></tr></table>'
该表可能看起来很混乱,但由于它是 HTML 格式,因此 LLM 能够比制表符或逗号分隔更容易地解析它。您可以将该 html 复制并粘贴到在线 html 查看器中以查看其重建情况。
Multi-Modal (text + images)
接下来,我们将深入探讨多模态文本拆分的领域。这是一个非常活跃的领域,最佳实践正在不断发展。我将向您展示一种由LangChain的Lance Martin广泛采用的方法。您可以在这里here.查看他的源代码。如果您找到更好的方法,请与社区分享
#!pip3 install "unstructured[all-docs]"
from typing import Any
from pydantic import BaseModel
from unstructured.partition.pdf import partition_pdf
首先,让我们获取一个PDF文件进行操作。这将来自一篇关于视觉指导调整的论文paper.。
filepath = "static/VisualInstruction.pdf"
# 获取元素
raw_pdf_elements = partition_pdf(
filename=filepath,
# 使用PDF格式查找嵌入的图像块
extract_images_in_pdf=True,
# 使用布局模型(YOLOX)获取边界框(用于表格)并查找标题
# 标题是文档的任何子部分
infer_table_structure=True,
# 后处理以在获取标题后聚合文本
chunking_strategy="by_title",
# 用于聚合文本块的分块参数
# 尝试创建新块3800个字符
# 尝试保持块> 2000个字符
# 块的硬性上限
max_characters=4000,
new_after_n_chars=3800,
combine_text_under_n_chars=2000,
image_output_dir_path="static/pdfImages/",
)
如果你到 static/pdfImages/ 文件夹并查看解析的图像。
但图像只是放在一个文件夹里什么都做不了,我们需要对它们做点什么!虽然这有点超出了分块的范围,但让我们谈谈如何处理这些图像。
常见的策略要么使用多模型来生成图像的摘要,要么将图像本身用于您的任务。其他人获取图像的嵌入(如CLIP)。
让我们生成摘要,这样你就会受到启发,将其推进到下一步。我们将使用GPT-4V。在这里查看其他模型。
from langchain.chat_models import ChatOpenAI
from langchain.schema.messages import HumanMessage
import os
from dotenv import load_dotenv
from PIL import Image
import base64
import io
load_dotenv()
输出true
接下来我们使用gpt-4-vision
llm = ChatOpenAI(model="gpt-4-vision-preview")
# Function to convert image to base64
def image_to_base64(image_path):
with Image.open(image_path) as image:
buffered = io.BytesIO()
image.save(buffered, format=image.format)
img_str = base64.b64encode(buffered.getvalue())
return img_str.decode('utf-8')
image_str = image_to_base64("../RetrievalTutorials/static/pdfImages/figure-15-6.jpg")
#我正在创建一个快速的辅助函数,将图像从文件转换为base64,以便我们可以将其传递给GPT-4V。
chat = ChatOpenAI(model="gpt-4-vision-preview",
max_tokens=1024)
msg = chat.invoke(
[
HumanMessage(
content=[
{"type": "text", "text" : "Please give a summary of the image provided. Be descriptive"},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image_str}"
},
},
]
)
]
)
#然后,返回的摘要就是我们将放入向量数据库的内容。当进行检索过程时,我们将使用这些嵌入进行语义搜索。
msg.content
图中显示了一个烤盘,上面摆放着一些食物碎片(可能是饼干或其他烘焙食品),松散地排列成类似地球大陆的形状,就像从太空中看到的地球一样。这个排列并不准确,但是它是一种充满趣味的表现,不规则的形状旨在模仿大陆的轮廓。在盘子上方有一段文字,写着:“有时候我只是看着太空中的地球图片,惊叹于一切的美丽。”这段文字增添了一些幽默色彩,因为观众并不是在从太空中看地球,而是在欣赏一幅富有创意的、地球的想象图景。
嗯,看来是这样啊!有很多方法可以实现这一点。