【文档智能 RAG】RAG增强之路:增强PDF解析并结构化技术路线方案及思路

news2024/11/13 10:46:13

前言

现阶段,尽管大模型在生成式问答上取得了很大的成功,但由于大部分的数据都是私有数据,大模型的训练及微调成本非常高,RAG的方式逐渐成为落地应用的一种重要的选择方式。然而,如何准确的对文档进行划分chunks,成为一种挑战,在现实中,大部分的专业文档都是以 PDF 格式存储,低精度的 PDF 解析会显著影响专业知识问答的效果。因此,本文将介绍针对pdf,介绍一些pdf结构化技术链路供参考。

一、可编辑文档

对于可编辑文档,常使用pdf解析工具对其进行解析,常见的解析工具及方法总结可以参考往期文章总结:

《【预处理】大模型下开源文档解析工具总结及技术思考》

1.1 语义分段

经pdf解析工具后,原始文档的段落信息全部丢失,需要进行段落的划分和重组。下面介绍一种语义分段模型的训练思路和一种开源的分段模型。

  • 语义分段训练思路
    《【预处理】大模型下开源文档解析工具总结及技术思考》

  • 开源的模型

from modelscope.outputs import OutputKeys
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks

p = pipeline(
    task=Tasks.document_segmentation,
    model='damo/nlp_bert_document-segmentation_chinese-base')

result = p(documents='移动端语音唤醒模型,检测关键词为“小云小云”。模型主体为4层FSMN结构,使用CTC训练准则,参数量750K,适用于移动端设备运行。模型输入为Fbank特征,输出为基于char建模的中文全集token预测,测试工具根据每一帧的预测数据进行后处理得到输入音频的实时检测结果。模型训练采用“basetrain + finetune”的模式,basetrain过程使用大量内部移动端数据,在此基础上,使用1万条设备端录制安静场景“小云小云”数据进行微调,得到最终面向业务的模型。后续用户可在basetrain模型基础上,使用其他关键词数据进行微调,得到新的语音唤醒模型,但暂时未开放模型finetune功能。')

print(result[OutputKeys.TEXT])
# 输出
'''
   移动端语音唤醒模型,检测关键词为“小云小云”。模型主体为4层FSMN结构,使用CTC训练准则,参数量750K,适用于移动端设备运行。模型输入为Fbank特征,输出为基于char建模的中文全集token预测,测试工具根据每一帧的预测数据进行后处理得到输入音频的实时检测结果。
   模型训练采用“basetrain + finetune”的模式,basetrain过程使用大量内部移动端数据,在此基础上,使用1万条设备端录制安静场景“小云小云”数据进行微调,得到最终面向业务的模型。后续用户可在basetrain模型基础上,使用其他关键词数据进行微调,得到新的语音唤醒模型,但暂时未开放模型finetune功能。
'''

二、可编辑文档(扫描件)

2.1 版面分析

版面分析指的是对图片形式的文档(扫描件)进行区域划分,通过bounding box定位其中的关键区域,如:文字、标题、表格、图片等,通常采用一些CV目标检测模型进行版式分析,如:参数量大的有:DINO等基于transformer的目标检测模型;参数量小的有MaskRCNN、YOLO系列等。

版式分析的优势,通过大量标注的数据,准确的划分出文档关键区域,如下:

  • 文本区域:页眉、页脚、标题、段落、页码、脚注、图片标题、表格标题等
  • 表格
  • 公式
  • 图片

2.2 文本识别

对于经由版式分析划分出来的文本区域,通常采用OCR进行相应区域的文字识别,常见的开源OCR识别工具有读光OCR、PaddleOCR等,以下是PaddleOCR的使用例子:

import cv2
from paddleocr import PaddleOCR
paddleocr = PaddleOCR(lang='ch', show_log=False, enable_mkldnn=True) 
img = cv2.imread('1.jpg')  
result = paddleocr.ocr(img)
for i in range(len(result[0])):
    print(result[0][i][1][0])   # 输出识别结果

然而,像paddleOCR等开源ocr方式,在实际应用中还是存在很多的问题,如:

  • 漏识别:开源的一些ocr模型通常有检测和识别两阶段构成,如果检测模型检测不准,将会错误累积,ocr识别时也不准确。

  • 识别文字错误:开源模型毕竟免费,没有在特定的领域场景上进行特定的训练,因此在识别时难免出现错误。

2.3 表格解析

对于经由版式分析划分出来的表格区域,通常采用表格解析模型进行解析,并转化为特定的格式,如:csv、html、markdown格式等。常见的开源模型有ppstructure等,如下:

import os
import cv2
from paddleocr import PPStructure,save_structure_res
table_engine = PPStructure(layout=False, show_log=True) 
save_folder = './output'
img_path = 'table.jpg'
img = cv2.imread(img_path)
result = table_engine(img)
save_structure_res(result, save_folder, os.path.basename(img_path).split('.')[0])
for line in result:
    line.pop('img')
    print(line)

在实际使用过程中,常见的开源方法经常遇到的问题就是,无法准确的对表格进行解析,这种问题常见与复杂表格,尤其是表格合并单元格时,容易解析错误,行列不对齐等。

2.4 公式解析

对于经由版式分析划分出来的公式区域,通常采用公式解析模型进行解析,并转化为特定的格式,如:tex等。

下面是一个使用LatexOCR进行公式解析的例子:

from PIL import Image
from pix2tex.cli import LatexOCR

model = LatexOCR()
img = Image.open('1.jpg')
print(model(img))

三、阅读顺序

经上述解析后,需要根据boungding box进行排序,以便恢复文档的格式信息。下面将介绍一种基于规则的方法和一种基于Layoutreader模型的方法。

  • xy-cut

    import numpy as np
    
    def xy_cut(bboxes, direction="x"):
        result = []
        K = len(bboxes)
        indexes = range(K)
        if len(bboxes) <= 0:
            return result
        if direction == "x":
            # x first
            sorted_ids = sorted(indexes, key=lambda k: (bboxes[k][0], bboxes[k][1]))
            sorted_boxes = sorted(bboxes, key=lambda x: (x[0], x[1]))
            next_dir = "y"
        else:
            sorted_ids = sorted(indexes, key=lambda k: (bboxes[k][1], bboxes[k][0]))
            sorted_boxes = sorted(bboxes, key=lambda x: (x[1], x[0]))
            next_dir = "x"
    
        curr = 0
        np_bboxes = np.array(sorted_boxes)
        for idx in range(len(sorted_boxes)):
            if direction == "x":
                # a new seg path
                if idx != K - 1 and sorted_boxes[idx][2] < sorted_boxes[idx + 1][0]:
                    rel_res = xy_cut(sorted_boxes[curr:idx + 1], next_dir)
                    result += [sorted_ids[i + curr] for i in rel_res]
                    curr = idx + 1
            else:
                # a new seg path
                if idx != K - 1 and sorted_boxes[idx][3] < sorted_boxes[idx + 1][1]:
                    rel_res = xy_cut(sorted_boxes[curr:idx + 1], next_dir)
                    result += [sorted_ids[i + curr] for i in rel_res]
                    curr = idx + 1
    
        result += sorted_ids[curr:idx + 1]
        return result
    
    
    def augment_xy_cut(bboxes,
                       direction="x",
                       lambda_x=0.5,
                       lambda_y=0.5,
                       theta=5,
                       aug=False):
        if aug is True:
            for idx in range(len(bboxes)):
                vx = np.random.normal(loc=0, scale=1)
                vy = np.random.normal(loc=0, scale=1)
                if np.abs(vx) >= lambda_x:
                    bboxes[idx][0] += round(theta * vx)
                    bboxes[idx][2] += round(theta * vx)
                if np.abs(vy) >= lambda_y:
                    bboxes[idx][1] += round(theta * vy)
                    bboxes[idx][3] += round(theta * vy)
                bboxes[idx] = [max(0, i) for i in bboxes[idx]]
        res_idx = xy_cut(bboxes, direction=direction)
        res_bboxes = [bboxes[idx] for idx in res_idx]
        return res_idx, res_bboxes
    
    
    bboxes = [[58.54924774169922, 1379.6373291015625, 1112.8863525390625, 1640.0870361328125],
              [60.1091423034668, 483.88677978515625, 1117.4927978515625, 586.197021484375],
              [57.687435150146484, 1098.1053466796875, 387.9796142578125, 1216.916015625],
              [63.158992767333984, 311.2080993652344, 1116.2508544921875, 365.2145080566406],
              [138.85513305664062, 144.44039916992188, 845.18017578125, 198.04937744140625],
              [996.1032104492188, 1053.6279296875, 1126.1046142578125, 1071.3463134765625],
              [58.743492126464844, 634.3077392578125, 898.405029296875, 700.9544677734375],
              [61.35755920410156, 750.6771240234375, 1051.1060791015625, 850.3980712890625],
              [426.77691650390625, 70.69780731201172, 556.0884399414062, 109.58145141601562],
              [997.040283203125, 903.5933227539062, 1129.2984619140625, 921.10595703125],
              [59.40523910522461, 1335.1563720703125, 329.7382507324219, 1357.46533203125],
              [568.9025268554688, 14.365530967712402, 1087.898193359375, 32.60292434692383],
              [998.1250610351562, 752.936279296875, 1128.435546875, 770.4116821289062],
              [59.6968879699707, 947.9129638671875, 601.4513549804688, 999.4548950195312],
              [58.91489028930664, 1049.8773193359375, 487.3372497558594, 1072.2935791015625],
              [60.49456024169922, 902.8802490234375, 600.7571411132812, 1000.3502197265625],
              [60.188941955566406, 247.99755859375, 155.72970581054688, 272.1385192871094],
              [996.873291015625, 637.3861694335938, 1128.3558349609375, 655.1572875976562],
              [59.74936294555664, 1272.98828125, 154.8768310546875, 1295.870361328125],
              [58.835716247558594, 1050.5926513671875, 481.59027099609375, 1071.966796875],
              [60.60163116455078, 750.1132202148438, 376.1781921386719, 771.8764038085938],
              [57.982513427734375, 419.16058349609375, 155.35882568359375, 444.25115966796875],
              [1017.0194091796875, 1336.21826171875, 1128.002197265625, 1355.67724609375],
              [1019.8740844726562, 486.90814208984375, 1127.482421875, 504.61767578125]]
    
    res_idx, res_bboxes = augment_xy_cut(bboxes, direction="y")
    print(res_idx)
    # res_idx, res_bboxes = augment_xy_cut(bboxes, direction="x")
    # print(res_idx)
    
    new_boxs = []
    for i in res_idx:
        # print(i)
    
        new_boxs.append(bboxes[i])
    
    print(new_boxs)
    
    
  • Layoutreader

    该模型及其介绍可以查阅往期文章《文档智能】符合人类阅读顺序的文档模型-LayoutReader及非官方权重开源》

    import torch
    from model import LayoutLMv3ForBboxClassification
    from collections import defaultdict
    
    CLS_TOKEN_ID = 0
    UNK_TOKEN_ID = 3
    EOS_TOKEN_ID = 2
    
    
    def BboxesMasks(boxes):
        bbox = [[0, 0, 0, 0]] + boxes + [[0, 0, 0, 0]]
        input_ids = [CLS_TOKEN_ID] + [UNK_TOKEN_ID] * len(boxes) + [EOS_TOKEN_ID]
        attention_mask = [1] + [1] * len(boxes) + [1]
        return {
            "bbox": torch.tensor([bbox]),
            "attention_mask": torch.tensor([attention_mask]),
            "input_ids": torch.tensor([input_ids]),
        }
    
    
    def decode(logits, length):
        logits = logits[1: length + 1, :length]
        orders = logits.argsort(descending=False).tolist()
        ret = [o.pop() for o in orders]
        while True:
            order_to_idxes = defaultdict(list)
            for idx, order in enumerate(ret):
                order_to_idxes[order].append(idx)
            order_to_idxes = {k: v for k, v in order_to_idxes.items() if len(v) > 1}
            if not order_to_idxes:
                break
            for order, idxes in order_to_idxes.items():
                idxes_to_logit = {}
                for idx in idxes:
                    idxes_to_logit[idx] = logits[idx, order]
                idxes_to_logit = sorted(
                    idxes_to_logit.items(), key=lambda x: x[1], reverse=True
                )
                for idx, _ in idxes_to_logit[1:]:
                    ret[idx] = orders[idx].pop()
        return ret
    
    
    def layoutreader(bboxes):
        inputs = BboxesMasks(bboxes)
        logits = model(**inputs).logits.cpu().squeeze(0)
        orders = decode(logits, len(bboxes))
        return orders
    
    
    if __name__ == '__main__':
        bboxes = [[584, 0, 595, 1], [35, 120, 89, 133],
                  [35, 140, 75, 152]]
        model_path = ""
        model = LayoutLMv3ForBboxClassification.from_pretrained()
    
        print(layoutreader(bboxes))
    # [1, 2, 0]
    

总结

本文详细介绍了可编辑pdf和不可编辑pdf(扫描件)的一些开源技术方案和路线,整个技术链路是一个pipline的路线,每一个步骤都需要精细的优化。在RAG中,准确的划分chunks,需要依赖文档的版式分析的精准性。因此,尤其是在对文档进行版面分析时,目标检测的粒度及标签需要对落地场景进行特定的分析,不要妄想着存在一个通用的版式分析模型解决一切文档版式分析问题。

参考文献

  • LaTeX-OCR:https://github.com/lukas-blecher/LaTeX-OCR
  • PaddleOCR:https://github.com/PaddlePaddle/PaddleOCR
  • 语义分段模型,https://modelscope.cn/models/iic/nlp_bert_document-segmentation_chinese-base/summary

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1808776.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Golang的协程调度器GMP

目录 GMP 含义 设计策略 全局队列 P的本地队列 GMP模型以及场景过程 场景一 场景2 场景三 场景四 场景五 场景六 GMP 含义 协程调度器&#xff0c;它包含了运行协程的资源&#xff0c;如果线程想运行协程&#xff0c;必须先获取P&#xff0c;P中还包含了可运行的G…

时序数据库是Niche Market吗?

引言 DB-Engines的流行程度排行从其评估标准[4]可以看出完全不能够做为市场规模的评估标准。甚至于在知道市场规模后可以用这个排行作为一个避雷手册。毕竟现存市场小&#xff0c;可预见增长规模小&#xff0c;竞争大&#xff0c;创新不足&#xff0c;那只能卷价格&#xff0c…

01、Linux网络设置

目录 1.1 查看及测试网络 1.1.1 查看网络配置 1、查看网络接口地址 2、查看主机状态 3、查看路由表条目 4、查看网络连接qing 1.1.2 测试网络连接 1.测试网络连接 2.跟踪数据包的路由路径 3.测试DNS域名解析 1.2 设置网络地址参数 1.2.1 使用网络配置命令 1.修改网卡…

C# MES通信从入门到精通(11)——C#如何使用Json字符串

前言 我们在开发上位机软件的过程中&#xff0c;经常需要和Mes系统进行数据交互&#xff0c;并且最常用的数据格式是Json&#xff0c;本文就是详细介绍Json格式的类型&#xff0c;以及我们在与mes系统进行交互时如何组织Json数据。 1、在C#中如何调用Json 在C#中调用Json相关…

【题解】—— LeetCode一周小结23

&#x1f31f;欢迎来到 我的博客 —— 探索技术的无限可能&#xff01; &#x1f31f;博客的简介&#xff08;文章目录&#xff09; 【题解】—— 每日一道题目栏 上接&#xff1a;【题解】—— LeetCode一周小结22 3.分糖果 II 题目链接&#xff1a;1103. 分糖果 II 排排坐…

【漏洞复现】用友NC pagesServlet SQL注入漏洞(XVE-2024-13067)

0x01 产品简介 用友NC是由用友公司开发的一套面向大型企业和集团型企业的管理软件产品系列。这一系列产品基于全球最新的互联网技术、云计算技术和移动应用技术&#xff0c;旨在帮助企业创新管理模式、引领商业变革。 0x02 漏洞概述 用友NC /portal/pt/servlet/pagesServlet…

Springboot校园美食推荐系统的开发-计算机毕业设计源码44555

摘要 随着人们生活水平的提高&#xff0c;人们对美食的要求也越来越高&#xff0c;对各类美食信息需求越来越大。因此&#xff0c;结合计算机快速发展、普及&#xff0c;在此基础上制作一个页面简单、美观,功能实用的校园美食推荐系统势在必行&#xff0c;满足用户分享美食的需…

Spring-Security(二)OAuth2认证详解(持续更新)

Spring Security & Oauth2系列&#xff1a; Spring Security&#xff08;一&#xff09; 源码分析及认证流程 Spring Security&#xff08;二&#xff09;OAuth2认证详解及自定义异常处理 文章目录 1、OAuth2.0 简介1.1 OAuth2.0 相关名词解释1.2 四种授权模式 1.3 、OAu…

QT 信号和槽 信号关联到信号示例 信号除了可以绑定槽以外,信号还可以绑定信号

信号除了可以关联到槽函数&#xff0c;还可以关联到类型匹配的信号&#xff0c;实现信号的接力触发。上个示例中因为 clicked 信号没有参数&#xff0c;而 SendMsg 信号有参数&#xff0c;所以不方便直接关联。本小节示范一个信号到信号的关联&#xff0c;将按钮的 clicked 信号…

Python 深度探讨 *args

点击下方卡片&#xff0c;关注“小白玩转Python”公众号 作为Python中最独特的语法之一&#xff0c;*args 在编程过程中给我们带来了很多灵活性和便利性。我认为它们反映了“Pythonic”和“Python之禅”。然而&#xff0c;我发现它们对于学习者&#xff08;尤其是初学者&#x…

DeepSpeed Learning Rate Scheduler

Learning Rate Range Test (LRRT) 训练试跑&#xff0c;该lr scheduler从小到大增长lr&#xff0c;同时记录下validatin loss&#xff1b;人来观察在训练多少step之后&#xff0c;loss崩掉&#xff08;diverge)了&#xff0c;进而为真正跑训练&#xff0c;挑选合适的lr区间&…

一、Electron 环境初步搭建

新建一个文件夹&#xff0c;然后进行 npm init -y 进行初始化&#xff0c;然后我们在进行 npm i electron --save-dev , 此时我们按照官网的教程进行一个初步的搭建&#xff0c; 1.在 package.json 文件进行修改 {"name": "electron-ui","version…

嵌入式应用之FIFO模块原理与实现

FIFO介绍与原理 FIFO是First-In First-Out的缩写&#xff0c;它是一个具有先入先出特点的缓冲区。FIFO在嵌入式应用的非常广泛&#xff0c;可以说有数据收发的地方&#xff0c;基本就有FIFO的存在。或者为了降低CPU负担&#xff0c;提高数据处理效率&#xff0c;可以在积累到一…

使用 Scapy 库编写 TCP FIN 洪水攻击脚本

一、介绍 TCP FIN洪水攻击是一种分布式拒绝服务攻击&#xff08;DDoS&#xff09;&#xff0c;攻击者通过向目标服务器发送大量伪造的TCP FIN&#xff08;终止&#xff09;数据包&#xff0c;使目标服务器不堪重负&#xff0c;无法正常处理合法请求。FIN包通常用于关闭一个TCP…

电路笔记 : 嘉立创EDA 导入、查找、设计管理器(快速寻找网络标签)功能+DRC错误检查和处理

导入功能 查找功能 可查找多种类型&#xff0c;如原件名称、网络标签等 设计管理器 图层查看 DRC错误 规则设置 线距问题 大多数PCB制造商能够可靠地生产5 mil间距的走线和间隙。这是一个常见的标准&#xff0c;适合大多数消费级和工业级电子产品。在5 mil以上的间距&#xff…

操作系统复习-存储管理之虚拟内存

虚拟内存概述 有些进程实际需要的内存很大&#xff0c;超过物理内存的容量。多道程序设计&#xff0c;使得每个进程可用物理内存更加稀缺。不可能无限增加物理内存&#xff0c;物理内存总有不够的时候。虚拟内存是操作系统内存管理的关键技术。使得多道程序运行和大程序运行称…

Collections工具类及其案例

package exercise;public class Demo1 {public static void main(String[] args) {//可变参数//方法形参的个数是可以发生变化的//格式&#xff1a;属性类型...名字//int...argsint sum getSum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);System.out.println(sum);}//底层&#xff1a;可…

6.9总结

Vue生命周期 生命周期&#xff1a;指一个对象从创建到销毁的整个过程生命周期的八个阶段&#xff1a;每触发一个生命周期事件&#xff0c;会自动执行一个生命周期的方法&#xff08;钩子&#xff09; mounted&#xff1a;挂载完成&#xff0c;Vue初始化成功&#xff0c;HTML渲…

简记:为Docker配置服务代理

简记 为Docker配置服务代理 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_28550263/art…

设置路径别名

一、描述 如果想要给路径设置为别名&#xff0c;就是常见的有些项目前面的引入文件通过开头的&#xff0c;也就是替换了一些固定的文件路径&#xff0c;怎么配置。 二、配置 import { defineConfig } from vite import react from vitejs/plugin-react import path from path…