医疗保健、金融、法律、零售和制造等行业经常在其日常运营中处理大量文件。这些文件通常包含重要信息,这些信息对于及时决策非常关键,对于确保顶级客户满意度和减少客户流失至关重要。传统上,从文件中提取数据是手动进行的,这使得过程变慢,容易出错,成本高昂,且难以扩展。这种方法通常涉及手动劳动,从而消耗了关键活动的时间。虽然行业已经能够通过传统OCR工具实现一定程度的自动化,但这些方法已经证明是脆弱的、维护成本高的,且增加了技术债务。通过利用人工智能(AI)的智能文档处理(IDP),从大量具有不同类型和结构的文档中提取数据变得高效准确。这有助于及时高质量的业务决策,同时控制整体开支。
此外,基于大型语言模型(LLM)的生成式AI为IDP解决方案增添了强大的功能,通常弥补了即使是经过高度训练的机器学习模型仍存在的差距。在本文中,我简要讨论了IDP的各个阶段以及如何利用生成式AI增强现有的IDP工作负载或开发新的IDP工作负载。
智能文档处理的阶段
从高层次来看,标准的IDP工作流程包括四个阶段:文档存储,文档分类,提取和丰富。请注意,并非所有的用例都涉及所有这些阶段的实施,但最常见的用例是多个阶段的融合,其中最受欢迎的阶段是文档分类和提取阶段。以下是我们对这些IDP阶段的可视化。在本文中,我们将主要关注文档分类和提取阶段以及所涉及的AI组件和机制。对于这些IDP阶段,我们将简要讨论阶段的重要性,并深入介绍生成式AI和机器学习的实现方式。我们将讨论一些云基于AI服务,如Amazon Comprehend,Amazon Textract,以及通过Amazon Bedrock使用的LLM模型。我们还将讨论使用热门的开源LangChain Python库的实现细节。
图1:智能文档处理的阶段
文档分类
在处理大量文件时,一个常见的挑战是识别文件,将其按类别分类,然后进一步处理。这在传统上是人为或基于启发式的过程,但随着文件数量的增加,这些方法成为业务流程和结果的瓶颈。这个阶段的核心思想是使用人工智能自动化分类或标记。在这个阶段,原本不知名或只能整齐地分到不同类别的文件会被使用人工智能以自动化方式进行分类和标记。对于文档分类,我们利用Amazon Comprehend自定义分类器模型。
活动-ODSC西部2023
线下和线上会议
10月30日至11月2日
加入我们深入了解最新的数据科学和人工智能趋势,工具和技术,从LLMs到数据分析,从机器学习到负责任的人工智能。
使用Amazon Comprehend,我们可以训练我们的自定义文档分类模型,该模型专门用于识别您用例中的文档。该模型不仅考虑文档的布局,还考虑文档的内容(文本)进行文档分类。您可以训练单个模型对多达1000个不同类别的文档进行文档分类,而文档可以是PDF、JPG、PNG、DOCX或TXT格式。文档可以是单页或多页文档。对于多页文档,该模型会给出页面级文档类别及其置信度分数。
使用LLMs对文档进行分类是组织可能采用的另一种新兴技术,以增强其分类管道。当管道中出现模型未训练过的新文档时,这将非常有用。当现有的经过训练的分类器模型对某个文档进行分类时准确率极低时,这也会非常有用。这在业务流程演化中是非常常见的情况。下面是使用Amazon Bedrock和Anthropic Claude-v1模型进行文档分类的一个可能的实现方式。我们使用Amazon Textract的文档提取能力与LangChain一起从文档中获取文字,然后使用提示工程来识别可能的文档类别。LangChain使用Amazon Textract的DetectDocumentText API来提取打印、扫描或手写文件中的文本。
图2:低分分类被发送到LLM进行分类
```from langchain.document_loaders import AmazonTextractPDFLoaderfrom langchain.llms import Bedrockfrom langchain.prompts import PromptTemplatefrom langchain.chains import LLMChainloader = AmazonTextractPDFLoader("./samples/document.png")document = loader.load()template = """给定一组类别,将文档分类为其中之一。跳过任何导言文字,只给出类别名称。<classes>发票、银行对账单、健康计划、处方</classes><document>{doc_text}</document><classification>"""prompt = PromptTemplate(template=template, input_variables=["doc_text"])bedrock_llm = Bedrock(client=bedrock, model_id="anthropic.claude-v1")llm_chain = LLMChain(prompt=prompt, llm=bedrock_llm)class_name = llm_chain.run(document[0].page_content)print(f"提供的文档为:{class_name}")—-提供的文档为:健康计划```
请注意,此处使用的是anthropic.claude-v1模型,其令牌上下文窗口为100,000个令牌。此操作也可以使用Anthropic Claude v2模型执行,该模型的令牌上下文窗口也为100,000个,但所选择的模型可能会导致费用大大不同。总的来说,Anthropic Claude Instant和Claude v1模型是非常好的通用模型,适用于大量用例。下面的代码演示了一个跟踪机制,显示输入提示和生成的文本的标记计数。
```prompt = PromptTemplate(template=template, input_variables=["doc_text"])bedrock_llm = Bedrock(client=bedrock, model_id="anthropic.claude-v1")num_in_tokens = bedrock_llm.get_num_tokens(document[0].page_content)print (f"我们的提示有 {num_in_tokens} 个令牌 \n\n=========================")llm_chain = LLMChain(prompt=prompt, llm=bedrock_llm)class_name = llm_chain.run(document[0].page_content)num_out_tokens = bedrock_llm.get_num_tokens(class_name)print (f"我们的输出有 {num_out_tokens} 个令牌 \n\n=========================")print (f"总令牌数 = {num_in_tokens + num_out_tokens}")—-我们的提示有 1295 个令牌 =========================我们的输出有 9 个令牌 =========================总令牌数 = 1304```
文档提取
文档提取可能是IDP工作流程中最流行的阶段。在我的日常工作中,我遇到的大多数用例都倾向于从各种各样的文件中提取各种各样的信息的各种机制。这些文件范围从出生证明、抵押文件、税表、简历、医疗试验报告、健康保险文件、身份文件(护照、许可证等)、营销材料、报纸剪报等等。正如您所想象的那样,文档的格式和类型在用例之间以及用例内部变化很大。因此,从所有这些不同类型的文档中提取信息的方式有许多不同。然而,重要的是要记住,目前还没有一个“一刀切”能够以所需的格式从所有这些文档中提取的AI模型。但好消息是,有许多专门构建的以及通用模型可以帮助我们实现文档提取的目标。
Amazon Textract提供了许多通用功能,例如表单提取、表格提取、查询和布局。这些功能适用于几乎任何类型的文档,非常适合通用文档提取,例如从表单中提取或包含表格数据的文档的提取。Amazon Textract还提供了一些专门构建的AI模型,如用于识别和从美国身份证件中提取信息的AnalyzeID,能够读取和提取发票和收据的AnalyzeExpense,以及能够识别和提取抵押和贷款文件的AnalyzeLending。您可以在上面链接的文档中了解更多关于这些模型的工作原理,但在本节中,我们将重点介绍基于LLM的文档提取技术。我们将特别关注两个最常见的用法:基于模板的标准化键值实体提取和使用大型语言模型进行文档问答。
基于模板的规范化提取
在几乎所有的IDP使用场景中,提取的数据最终都会发送到下游系统进行进一步处理或分析。具有规范化的AI提取输出变得越来越重要,以便维护消耗机制和集成代码更容易、更可靠、更不容易出错。一组确定性的键对于你的集成代码可能会产生很大的区别,而与非确定性的键相比。例如,考虑一个提取申请人的名字、姓氏和出生日期的用例。但是所涉及的文件可能没有清楚标记为“名字”、“姓氏”、“出生日期”,而是以任何不同的方式呈现。
图3:文档提取实际输出 vs. 期望输出
注意实际输出与期望输出之间的差异。期望输出遵循更确定性的模式,使我们的后处理逻辑更简单、更易于维护,开发成本也更低。通过一个大型语言模型可以实现这一点,如下面的代码所示。
```from langchain.document_loaders import AmazonTextractPDFLoaderfrom langchain.llms import Bedrockfrom langchain.prompts import PromptTemplatefrom langchain.chains import LLMChainloader = AmazonTextractPDFLoader("./samples/document.png")document = loader.load()output_template= { "first_name":{ "type": "string", "description": "申请人的名字" }, "last_id":{ "type": "string", "description": "申请人的姓氏" }, "dob":{ "type": "string", "description": "申请人的出生日期" },}template = """你是一个有帮助的助手。请从文档中提取以下详细信息,并使用键将输出格式化为JSON。跳过任何序言文字并生成最终答案。<details>{details}</details><keys>{keys}</keys><document>{doc_text}<document><final_answer>"""details = "\n".join([f"{key}: {value['description']}" for key, value in output_template.items()])keys = "\n".join([f"{key}" for key, value in output_template.items()])prompt = PromptTemplate(template=template, input_variables=["details", "keys", "doc_text"])bedrock_llm = Bedrock(client=bedrock, model_id="anthropic.claude-v1")llm_chain = LLMChain(prompt=prompt, llm=bedrock_llm)output = llm_chain.run({"doc_text": full_text, "details": details, "keys": keys})print(output)—-{ "first_name": "John", "last_name": "Doe", "dob": "26-JUN-1981"}```
在这种情况下,只需使用清晰的指令为LLM构建我们的提示,我们不仅能够提取实体,而且还能够以符合我们自己模式的格式生成输出。从后处理的角度来看,这对我们的集成代码来说非常重要,因为它变得更简单和更确定性。除此之外,LangChain还提供了一种内置的方法,以更模块化的方式创建这些指令。以下是代码的样子。
```from langchain.output_parsers import ResponseSchemafrom langchain.output_parsers import StructuredOutputParserresponse_schems = list()for key, value in output_template.items(): schema = ResponseSchema(name=key, description=value['description'], type=value['type']) response_schems.append(schema)output_parser = StructuredOutputParser.from_response_schemas(response_schems)format_instructions= output_parser.get_format_instructions()print(format_instructions)—-输出应为一个以以下模式格式化的markdown代码片段,包括前导和尾随的"```json"和"```":```json{"first_name": string // 申请人的名字"provider_id": string // 申请人的姓氏"dob": string // 申请人的出生日期}``````
然后,我们可以简单地将预生成的格式化指令(存在变量format_instruction中)与我们的LLM链一起使用,如下所示。
```template = """您是一个有用的助手。请从文档中提取以下详细信息,并严格按照格式说明中描述的指示进行格式化输出。跳过任何文档的前言文本,生成最终答案<details>{details}</details><format_instructions>{format_instructions}</format_instructions><document>{doc_text}<document><final_answer>"""llm_chain = LLMChain(prompt=prompt, llm=bedrock_llm)output = llm_chain.run({"doc_text": full_text, "details": details, "format_instructions": format_instructions})parsed_output= output_parser.parse(output)parsed_output```
使用检索增强生成(RAG)进行文档问答
从文档中提取信息的第二种常见方法是使用类似聊天的问答方法。这种方法需要将文档内容和问题一起作为提示提供给LLM,以期从文档中获得答案(如果有的话)。然而,需要积极解决一些问题-防止模型产生幻象和限制令牌上下文窗口。
大型预训练语言模型由于存储的事实知识而在许多自然语言处理任务上展现出最先进的结果。然而,它们在知识处理方面的精度有限,导致在知识密集型任务上性能不佳。为了解决这个问题,一种称为“检索增强生成”(Retrieval Augmented Generation,RAG)的方法被发现非常有用。这种方法不仅帮助LLM以确切的方式回答问题,并在文档的上下文中保持一致,还可以防止幻象的发生并保持大型文档的LLM令牌上下文限制。RAG的思想是仅收集与提出的问题在语义上更接近的文档部分。这通过将文档的内容分块成多个较小部分,生成其向量嵌入,并将嵌入存储在向量数据库中来实现。向量数据库可以执行相似性搜索或最大边际相关性搜索(MMR)以从文档中收集最相关的块。一旦获取了这些相关部分,就可以与问题一起创建完整的上下文,并进行一些提示工程,以从模型中获得最准确的答案。
图4:具有文档嵌入和向量数据库的检索增强生成
在上图中,我们使用泰坦嵌入G1 – 文本模型通过Amazon Bedrock生成文本块的嵌入。LangChain库的有用内置模块(包括切分、生成嵌入和加载到向量数据库中)可以执行RAG的大部分机制,然后使用向量数据库作为检索器在文档内容上执行基于RAG的问答。下面的代码摘要展示了使用LangChain的检索问答链实现这种机制的可能方式,该链执行相关性搜索、上下文构建、使用上下文的提示增强等操作。在此示例中,我们使用[FAISS](https://ai.meta.com/tools/faiss/#:~:text=FAISS%20(Facebook%20AI%20Similarity%20Search,more%20scalable%20similarity%20search%20functions.)向量存储作为向量数据库。
```from langchain.embeddings import BedrockEmbeddingsfrom langchain.vectorstores import FAISSfrom langchain.document_loaders import AmazonTextractPDFLoaderfrom langchain.text_splitter import RecursiveCharacterTextSplitterfrom langchain.llms import Bedrockfrom langchain.prompts import PromptTemplatefrom langchain.chains import LLMChainloader = AmazonTextractPDFLoader(f"./samples/document.png")document = loader.load()text_splitter = RecursiveCharacterTextSplitter(chunk_size=400, separators=["\n\n", "\n", ".", "!", "?", ",", " ", ""], chunk_overlap=0)texts = text_splitter.split_documents(document)embeddings = BedrockEmbeddings(client=bedrock, model_id="amazon.titan-embed-text-v1")vector_db = FAISS.from_documents(documents=texts, embedding=embeddings)retriever = vector_db.as_retriever(search_type='mmr', search_kwargs={"k": 3})template = """尽可能真实地回答问题,严格只使用所提供的文本,如果答案不包含在文本中,回答"不知道"。跳过任何前言文本和推理,只给出答案。<text>{document}</text><question>{question}</question><answer>"""prompt = PromptTemplate(template=template, input_variables=["document","question"])bedrock_llm = Bedrock(client=bedrock, model_id="anthropic.claude-v1")llm_chain = LLMChain(prompt=prompt, llm=bedrock_llm)answer = llm_chain.run(document=full_context, question="每人药店费用是多少?")print(f"答案是 = {answer.strip()}")—-答案是 = $6,000```
在这个示例中,我们使用Amazon Textract提取了一个包含每页文本的文档。然后,我们将文档中的所有页面拆分成长度为400个字符的块。随后,我们生成这些块的向量嵌入,并将它们存储到一个FAISS向量存储中,然后将该向量存储作为我们基于RAG的问答机制的检索器。有关详细的逐步过程,请参阅此GitHub存储库中的Python笔记本。
结论
在本文中,我们讨论了智能文档处理的各个阶段,并探讨了使用Amazon Bedrock的生成式AI在增强或增强IDP工作流程方面的一些方式。本文未涵盖其他类型的摘要、自查询表格问答、标准化等提取方法。您可以通过上述提到的GitHub存储库中的Python笔记本了解更多关于这些提取类型的信息。如果您已经在用于您的用例的IDP工作流程中使用了生成式AI,那么通过增加LLM功能来增强您的工作流程,将会给您带来全新的可能性。如果您正在决策阶段的IDP工作流程中,也可以通过探索生成式AI的各种不同方式来增加价值。
关于作者
Anjan Biswas是Amazon Web Services(AWS)的高级AI专业解决方案架构师。Anjan专注于计算机视觉、自然语言处理和生成式AI技术,并在智能文档处理(IDP)用例上投入了大量时间。他在供应链、零售、技术和医疗保健行业构建大规模企业系统方面拥有超过16年的经验,并对数据科学和机器学习充满热情。