在当今数据驱动的世界中,组织机构们坐拥着无数的PDF文档,这些文档中蕴含着丰富的信息宝藏。然而,尽管人类可以轻易地阅读这些文件,但对于试图理解和利用其内容的机器来说,却构成了巨大的挑战。无论是研究论文、技术手册还是商业报告,PDF文件常常包含能够驱动智能系统、助力数据驱动决策的有价值知识。但如何将这些非结构化的PDF数据转化为机器能够高效处理的结构化知识,成为了现代信息处理系统面临的核心挑战之一。
一、非结构化PDF数据的挑战
尽管PDF文档中的信息丰富多样,但面对非结构化数据时,三大核心挑战应运而生:
-
缺乏可解释性:难以追踪系统是如何得出特定答案的。
-
分析能力受限:非结构化数据限制了复杂分析的可能性。
-
精度降低:在处理大量信息时,这一点尤为明显。
这些限制凸显了结构化格式(如表格和知识图谱)的强大之处。它们能够将原始信息转化为有组织、可查询的数据,从而使机器能够更有效地处理。为了将非结构化文档与结构化数据之间的鸿沟缩小,以便进行高级分析和AI应用,我们必须采取创新的手段。这不仅是现代信息处理系统的核心挑战,也是构建综合性知识图谱的重要目标。
二、PDF解析与结构化知识提取
将PDF内容转化为知识图谱的过程,不仅仅是简单的文本提取,而是需要理解上下文、识别关键概念以及识别思想之间的关系。这要求我们首先解析PDF内容。
1. PDF解析工具选择
在众多可用的解析库中,本文选择了PyMuPDF及其扩展PyMuPDF4LLM。PyMuPDF4LLM的Markdown提取功能保留了诸如标题和列表等关键结构元素,这极大地提升了大型语言模型(LLMs)对文档结构的识别和解释能力,从而显著增强了检索增强生成(Retrieval-Augmented Generation, RAG)的结果。
在解析PDF时,我使用PyMuPDF4LLM生成的Markdown作为每页文档的文本内容,同时提取所有图像,并将它们的OCR输出附加到相同的Markdown输出中。这种方法能够自动处理不同类型的PDF:仅有图像的扫描PDF、包含文本的PDF以及同时包含图像和文本的PDF。
2. OCR技术
对于图像中的文本提取,我们使用了PyTesseract,它是Google Tesseract-OCR引擎的Python封装。
import base64``from typing import Union, List, Dict, Any`` ``import pymupdf``import pymupdf4llm``import numpy as np``from PIL import Image``from langchain_core.language_models import BaseLanguageModel``from langchain_core.messages import SystemMessage, HumanMessage`` `` ``def ocr_images_pytesseract(images: List[Union[np.ndarray | Image.Image]]) -> str:` `import pytesseract`` ` `all_text: str = ""` `for image in images:` `if isinstance(image, Image.Image):` `image = image.filter(ImageFilter.SMOOTH())` `all_text += '\n' + pytesseract.image_to_string(image, lang="eng", config='--psm 3 --dpi 300 --oem 1')` `return all_text.strip('\n \t')`` ``# Use PyMuPDF to open the document and PyMuPDF4LLM to get the markdown output``doc = pymupdf.Document(pdf_path)``doc_markdown = pymupdf4llm.to_markdown(doc, page_chunks=True, write_images=False, force_text=True)`` `` ``def extract_page_info(page_num: int, doc_metadata: dict, toc: list = None) -> Dict[str, Any]:` `"""Extracts text and metadata from a single page of a PDF document.`` ` `Args:` `page_num (int): The page number` `doc_metadata (dict): The whole document metadata to store along with each page metadata` `toc (list | None): The list that represents the table-of-contents of the document` ` Returns:` `A dictionary with the following keys:` `1) text: Text content extracted from the page` `2) page_metadata: Metadata specific to the page only like page number, chapter number etc.` `3) doc_metadata: Metadata common to the whole document like filename, author etc.` ` """` `page_info = {}`` ` `# Read the page of`` page = doc[page_num]`` ` `# doc_markdown stores the page-by-page markdown output of the document` `text_content: str = self.doc_markdown[page_num]['text']`` ` `# Get a list of all the images on this page - automatically, perform OCR on all these images and store the output as page text` `# There are 3 options available: each with different preprocessing steps and all of them implemented in the self._ocr_func method` `images_list: list[list] = page.get_images()` `if len(images_list) > 0:` `imgs = []` `for img in images_list:` `xref = img[0]` `pix = pymupdf.Pixmap(doc, xref)` `# using PyTesseract requires storing the image as PIL.Image though it could've been bytes or np.ndarray as well` `cspace = pix.colorspace` `if cspace is None:` `mode: str = "L"` `elif cspace.n == 1:` `mode = "L" if pix.alpha == 0 else "LA"` `elif cspace.n == 3:` `mode = "RGB" if pix.alpha == 0 else "RGBA"` `else:` `mode = "CMYK"` `img = Image.frombytes(mode, (pix.width, pix.height), pix.samples)` `if mode != "L":` `img = img.convert("L")` `imgs.append(img)` ` text_content += '\n' + ocr_images_pytesseract(imgs)` `text_content = text_content.strip(' \n\t')` `page_info["text"] = text_content` `page_info["page_metadata"] = get_page_metadata(page_num=page_num+1, page_text=text_content, toc=toc)` `page_info["doc_metadata"] = doc_metadata` `return page_info
三、构建知识图谱的架构
在提取PDF中的结构化内容之前,我们首先需要定义输出结构。这通常通过现代LLMs提供的“结构化输出”功能来实现。本文使用了LangChain来处理与LLMs相关的所有流程,并使用了其with_structured_output方法。需要注意的是,此方法并非所有模型都支持,因此在使用前需确认模型兼容性。
1. 节点与关系模型
知识图谱架构([GraphRAG原理深入剖析-知识图谱构建])由两个主要组件构成:节点和关系。使用Pydantic库定义了节点和关系的模型。
-
节点类:定义了单个实体,包括唯一标识符、实体类型、附加属性(可选)、别名(可选)以及文本定义(可选)。虽然别名和定义是可选的,但它们在后续处理步骤中至关重要,它们有助于:
-
在后续处理步骤中进行节点消歧。
-
指导LLMs保持一致性,防止重复创建节点。
-
丰富知识图谱,提供有价值的上下文,提升RAG性能。
-
关系类:捕获节点之间的连接,包括源和目标节点ID、关系类型、属性(可选)以及提取上下文(可选)。与节点别名和定义类似,关系上下文保留了关于连接如何和在哪里被识别的有价值信息,提高了图谱对下游任务的实用性。
from pydantic import BaseModel, Field`` ``class Property(BaseModel):` `"""A single property consisting of key and value"""` `key: str = Field(..., description="key")` `value: str = Field(..., description="value")`` ``class Node(BaseModel):` `id: str = Field(..., description="The identifying property of the node in Title Case")` `type: str = Field(..., description="The entity type / label of the node in PascalCase.")` `properties: Optional[List[Property]] = Field(default=[], description="Detailed properties of the node")` `aliases: List[str] = Field(default=[], description="Alternative names or identifiers for the entity in Title Case")` `definition: Optional[str] = Field(None, description="A concise definition or description of the entity")`` ``class Relationship(BaseModel):` `start_node_id: str = Field(..., description="The id of the first node in the relationship")` `end_node_id: str = Field(..., description="The id of the second node in the relationship")` `type: str = Field(..., description="TThe specific, descriptive label of the relationship in SCREAMING_SNAKE_CASE")` `properties: List[Property] = Field(default=[], description="Detailed properties of the relationship")` `context: Optional[str] = Field(None, description="Additional contextual information about the relationship")` `class KnowledgeGraph(BaseModel):` `"""Generate a knowledge graph with entities and relationships."""` `nodes: list[Node] = Field(` `..., description="List of nodes in the knowledge graph")` `rels: list[Relationship] = Field(` `..., description="List of relationships in the knowledge graph"` `)
2. 数据提取提示
接下来,我们定义了详细的提取提示,包括:
-
最终目标:构建具有丰富上下文信息的知识图谱。
-
节点ID、节点类型和关系类型的输出格式。
-
保持节点一致性并解决指代消解至其最完整形式。
利用这个提示和结构化架构,创建了一个数据提取链,该链将在后续步骤中用于从文本中提取知识图谱。
from langchain_core.prompts import ChatPromptTemplate`` ``DATA_EXTRACTION_SYSTEM = """# Knowledge Graph Extraction for Rich Information Retrieval``## 1. Overview``You are an advanced algorithm designed to extract knowledge from various types of informational content. \``Your task is to build a knowledge graph that provides rich, contextual information for downstream tasks.`` ``## 2. Content Focus``- Extract detailed information about concepts, entities, processes, and their relationships from the given text.``- Prioritize information that provides rich context and is likely to be useful for answering a wide range of questions.``- Include relevant attributes, properties, and descriptive information for each extracted entity.`` ``## 3. Node Extraction``- **Node IDs**: Use clear, unambiguous identifiers in Title Case. Avoid integers, abbreviations, and acronyms.``- **Node Types**: Use PascalCase. Be as specific and descriptive as possible to aid in Wikidata matching.``- Include all relevant attributes of the entity in the node properties.``- Extract and include alternative names or aliases for entities when present in the text.`` ``## 4. Relationship Extraction``- Use SCREAMING_SNAKE_CASE for relationship types.``- Create detailed, informative relationship types that clearly describe the nature of the connection.``- Include directional relationships where applicable (e.g., PRECEDED_BY, FOLLOWED_BY instead of just RELATED_TO).`` ``## 5. Contextual Information``- For each node and relationship, strive to capture contextual information that might be useful for answering questions.``- Include temporal information when available (e.g., dates, time periods, sequence of events).``- Capture geographical or spatial information if relevant.`` ``## 6. Handling Definitions and Descriptions``- For key concepts, include concise definitions or descriptions as node properties.``- Capture any notable characteristics, functions, or use cases of entities.`` ``## 7. Coreference and Consistency``- Maintain consistent entity references throughout the graph.``- Resolve coreferences to their most complete form, including potential aliases or alternative names.`` ``## 8. Granularity``- Strike a balance between detailed extraction and maintaining a coherent graph structure.``- Create separate nodes for distinct concepts, even if closely related.``"""`` `` ``prompt = ChatPromptTemplate.from_messages(` `[` `("system", DATA_EXTRACTION_SYSTEM),` `("human", "Use the given format to extract information from the following input which is a small sample from a much larger text belonging to the same subject matter: {input}"),` `("human", "Tip: Make sure to answer in the correct format"),` `])``
四、节点和关系提取
(一)基本提取过程
给定一系列文档,使用上述模式和提示从文本中提取节点和关系。通过在每个文档上调用文档提取链,可以得到输出节点和关系的列表。
nodes = []``rels = []`` ``for doc in docs:` `output: KnowledgeGraph = data_ext_chain.invoke(` `{` `"input": doc.page_content` `}` `)` `nodes.extend(format_nodes(output.nodes))` `rels.extend(format_rels(output.rels))
(二)面临的挑战
-
节点类型增殖
没有对创建不同节点类型进行限制,导致结果图谱 “分散”,降低了对下游任务的有效性。 -
重复实体管理
没有防止文档之间重复节点和关系的措施,可能导致后续处理步骤中的可扩展性问题。 -
来源可追溯性
没有跟踪每个节点的来源,降低了信息本身的可靠性,无法验证信息。
(三)解决措施
-
针对节点类型增殖问题
更新提示,接受现有节点类型列表和一个 “主题” 字符串,作为提取实体类型的指导和限制。 -
针对重复实体管理问题
用集合替换列表,并在代码中添加检查,确保只提取唯一的节点和关系。 -
针对来源可追溯性问题
在文档的元数据中添加从每个文档中提取的节点列表,在创建知识图谱时,可以为每个文档创建节点,并建立文档节点与从该文档中提取的每个实体节点之间的关系,如 (:Document)-[:MENTIONS]->(:Entity)。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。