1. 文档解析评测这件事,为什么一直缺一把公认的尺子
做文档解析这行的朋友应该都有同感:模型跑出来的结果,你说它好,好在哪?你说它差,差在哪?过去两年我参与过好几个PDF解析项目的选型和验收,最头疼的从来不是“能不能解析”,而是“怎么证明它解析得对”。一个合同PDF丢进去,A工具输出了带层级的Markdown,B工具吐出了扁平文本,C工具连表格都拆成了乱序的键值对——三家都宣称自己准确率90%以上,可你拿什么去比?
这就是OmniDocBench出现的背景。它本质上是一套面向文档解析任务的评测基准,目标很明确:给文档解析这件事画一把尽量公平的尺子。所谓“公平”,不是说让所有工具都考同一张卷子就完事了,而是这张卷子本身要覆盖足够多的文档类型、足够刁钻的版面结构、足够细的评测维度,并且评分逻辑要经得起推敲。它涉及的核心概念包括CDM(文档结构层面的匹配度量思路)和TEDS(表格结构相似度评测),这两个指标是理解整套评测体系的关键抓手。
这篇文章适合谁看?如果你正在做PDF解析、文档结构化、OCR后处理、RAG前的文档预处理,或者你是个技术负责人需要给团队选一套解析方案,那这篇内容能帮你把“评测”这件事从玄学变成工程。我会从设计思路、核心指标、实操跑测、踩坑排查几个层面,把OmniDocBench这套东西拆开讲透,尽量让你看完就能自己搭一套评测流程。
2. 评测基准的整体设计思路拆解
2.1 为什么不能只用“字符准确率”这一把尺子
刚入行的时候我也觉得,评测嘛,把解析出来的文本和标注文本做字符级对比,算个准确率、召回率、F1就完事了。后来被现实狠狠教育了一顿。举个真实例子:一份两栏排版的学术PDF,解析工具把左栏和右栏的文字按行交错读了出来,字符一个没漏、一个没多,字符准确率接近100%,但语义完全乱了——你拿这种结果去喂给下游的问答系统,它能把“实验结论”和“参考文献”缝在一起。
所以文档解析的评测必须分层。OmniDocBench的设计思路我理解下来,大致是这么几个层次:
- 文本层:字符是否正确,这个是最基础的,但远远不够。
- 结构层:段落、标题、列表、章节层级有没有还原对,阅读顺序对不对。
- 版面层:表格、图片、公式、页眉页脚这些元素有没有被正确识别和定位。
- 语义层:表格的单元格对应关系、公式的LaTeX表达、跨页内容的拼接是否合理。
这四层里,文本层用传统的编辑距离就能算,但结构层和版面层就需要专门的度量方法,CDM和TEDS就是为这些层服务的。CDM更偏向文档整体结构的匹配,TEDS则专门盯表格。为什么表格要单独拎出来?因为表格是文档解析里最容易翻车、也最难评测的部分,后面我会专门展开。
2.2 数据集构成:覆盖度决定了尺子公不公平
一把尺子公不公平,首先看它量过的东西够不够杂。如果评测集里全是干净的、单栏的、印刷体PDF,那测出来的高分在实际业务里毫无意义。OmniDocBench在数据集设计上,我观察到它刻意追求了几种“难”:
- 版面难度:单栏、双栏、多栏混排、图文环绕、跨页表格。
- 元素类型:纯文本、表格、公式、图片、页眉页脚、页码、脚注。
- 文档来源:学术论文、财务报表、合同、招标文件、扫描件、幻灯片导出件。
- 语言与字体:中英文混排、不同字号、加粗斜体、特殊符号。
这种覆盖度的意义在于,它能暴露一个解析工具在“舒适区”之外的真实表现。我见过太多工具在标准学术论文上表现优异,一遇到扫描版合同里的手写签名区域和盖章就歇菜。评测集如果不包含这些,那评测结果就是自欺欺人。
提示:你自己搭内部评测集的时候,一定要把业务里最脏、最乱的那批文档放进去,哪怕只有几十份,也比几千份干净文档有价值。
2.3 评分逻辑:为什么“部分正确”也要给分
传统的准确率是二值的——对就是对,错就是错。但文档解析里大量存在“部分正确”的情况。比如一个表格,10个单元格对了8个,你判它0分显然不合理,判它100分更不合理。所以OmniDocBench引入了基于相似度的连续评分,TEDS就是典型代表:它计算的是预测表格结构和标注表格结构之间的树编辑距离相似度,输出一个0到1之间的分数。
这种设计的好处是,它能区分“完全错”“错一半”“基本对”这三种状态,让评测结果更有区分度。坏处是,评分阈值需要人为设定——到底0.8算及格还是0.9算及格,取决于你的业务容忍度。这一点我在实操部分会给出我的经验值。
3. 核心指标CDM与TEDS到底怎么算
3.1 CDM:文档结构匹配的底层逻辑
CDM这个名字,我理解它想解决的是“文档整体结构一致性”的度量问题。文档解析出来的结果,本质上是一棵结构树:根节点是文档,子节点是章节,章节下面是段落,段落里可能嵌套列表或表格。CDM要做的,就是把这棵树和标注树做对比。
它的计算思路大致是这样的:
- 把预测结果和标注结果都解析成带层级的结构树。
- 对树中的每个节点,匹配它在另一棵树中的对应节点。
- 根据匹配上的节点类型、层级深度、内容一致性,计算加权得分。
- 对未匹配上的节点做惩罚。
这里的关键在于“匹配”策略。如果只是简单地按顺序对齐,那遇到解析工具漏掉一个章节的情况,后面所有节点都会错位,分数会断崖式下跌,这显然不公平。所以实际实现里通常会引入基于内容相似度的动态匹配,允许一定程度的错位容忍。
我实测下来的感受是,CDM对“章节层级错误”特别敏感。比如标注里是“一级标题-二级标题-正文”,解析结果变成了“一级标题-正文-二级标题”,CDM分数会明显下降。这个特性很符合实际需求,因为章节层级错乱对下游RAG的切片质量影响极大。
3.2 TEDS:表格评测为什么这么难
表格评测是文档解析评测里最硬的一块骨头。原因很简单:表格的结构是二维的,而文本是一维的。一个表格可能有合并单元格、嵌套表头、跨页续表,这些结构用纯文本根本表达不清楚。
TEDS的核心思想是树编辑距离。它把表格转换成一棵树:表格是根,行是子节点,单元格是行的子节点,单元格里的内容再往下挂。然后计算预测树和标注树之间的编辑距离,包括节点的插入、删除、替换操作,最后归一化成相似度分数。
TEDS有两个变体,我在实操中会区分使用:
| 指标变体 | 评测重点 | 适用场景 |
|---|---|---|
| TEDS | 表格结构和内容整体相似度 | 通用表格评测 |
| TEDS-Struct | 只评结构,忽略文字内容 | 单独评估表格结构识别能力 |
为什么要拆开?因为有些工具表格结构识别得很准,但单元格里的文字OCR错了;有些工具文字识别很准,但行列关系搞乱了。拆开评测才能定位问题到底出在哪一层。
3.3 指标之间的权重分配:没有标准答案,只有业务答案
OmniDocBench不会告诉你文本、结构、表格各占多少权重,因为这事没有普适答案。做合同解析的,可能表格权重高;做学术论文解析的,可能公式和章节结构权重高;做扫描件OCR的,可能纯文本准确率权重高。
我的建议是,你先明确下游任务是什么。如果下游是RAG问答,那阅读顺序和章节结构的权重应该拉高,因为切片质量直接决定检索质量。如果下游是财务数据抽取,那表格的TEDS权重必须拉高。如果下游只是全文检索,那文本层准确率占大头就行。
4. 实操:从零跑通一套文档解析评测流程
4.1 环境准备与依赖安装
这套流程我是在Linux环境下跑的,Python 3.9以上,建议用虚拟环境隔离依赖。核心依赖包括文档解析库、评测指标库和数据处理库。
python -m venv doc_eval_env source doc_eval_env/bin/activate pip install pdfplumber PyMuPDF python-docx pip install numpy pandas scipy pip install lxml rapidfuzz这里解释一下选型逻辑。PyMuPDF用来做PDF的底层解析和页面渲染,速度快,对版面元素的定位信息保留得比较全。pdfplumber在表格提取上更细腻,尤其是对线条表格的识别。rapidfuzz用来做字符串相似度计算,比标准库的difflib快很多,在大量文本对比时优势明显。lxml是为了处理XML格式的标注数据,很多评测集的标注是XML或HTML格式的。
注意:不要用
pip install一把梭把所有包都装上,版本冲突会让你痛不欲生。尤其是PyMuPDF和pdfplumber对底层PDF库的依赖有时候会打架,建议先装PyMuPDF,再装pdfplumber。
4.2 数据准备:标注格式与目录结构
评测要跑起来,你得有两样东西:待评测的PDF文档,以及对应的标注文件。标注文件的格式各家不同,但核心信息是一致的:每个文本块的内容、位置、类型、层级关系。
我习惯把目录组织成这样:
eval_dataset/ ├── pdfs/ │ ├── doc_001.pdf │ ├── doc_002.pdf │ └── ... ├── annotations/ │ ├── doc_001.json │ ├── doc_002.json │ └── ... └── predictions/ ├── doc_001.md ├── doc_002.md └── ...标注JSON的结构我一般设计成扁平化的块列表,每个块包含type、content、bbox、level、parent_id这几个字段。扁平化比嵌套树更好处理,需要树结构的时候现场构建就行。
{ "doc_id": "doc_001", "blocks": [ {"id": "b1", "type": "title", "level": 1, "content": "第一章 总则", "bbox": [72, 100, 500, 130], "parent_id": null}, {"id": "b2", "type": "paragraph", "level": 2, "content": "本合同由以下双方签订...", "bbox": [72, 140, 500, 200], "parent_id": "b1"} ] }4.3 解析结果标准化:把不同工具的输出拉到同一维度
这是整个流程里最容易被忽视、但最影响评测公平性的一步。不同解析工具输出的格式五花八门:有的输出Markdown,有的输出HTML,有的输出JSON。你必须先把它们统一成和标注一样的块列表格式,才能做对比。
我写了一个标准化脚本,核心逻辑是:
- 读取解析工具的原始输出。
- 根据输出格式选择对应的解析器(Markdown用正则+层级推断,HTML用lxml解析)。
- 把每个内容块转换成统一的块结构。
- 如果解析工具提供了bbox信息,保留;没有的话,bbox留空,后续评测时对版面相关指标做降级处理。
import re def normalize_markdown(md_text): blocks = [] lines = md_text.split('\n') for line in lines: line = line.strip() if not line: continue if line.startswith('#'): level = len(re.match(r'^#+', line).group()) content = line.lstrip('#').strip() blocks.append({'type': 'title', 'level': level, 'content': content}) elif line.startswith('|') and line.endswith('|'): blocks.append({'type': 'table_row', 'level': 0, 'content': line}) else: blocks.append({'type': 'paragraph', 'level': 0, 'content': line}) return blocks这个脚本很粗糙,但够用。实际业务里你可能需要处理列表、代码块、公式块等更多类型。关键原则是:标准化逻辑要对所有待评测工具一视同仁,不能对某个工具有特殊照顾。
4.4 跑分与结果解读:数字背后的含义
标准化完成后,就可以跑评测了。文本层用编辑距离算准确率,结构层用CDM思路算层级匹配度,表格层用TEDS算相似度。我一般会输出一张汇总表:
| 文档ID | 文本准确率 | 结构匹配度 | 表格TEDS | 综合得分 |
|---|---|---|---|---|
| doc_001 | 0.96 | 0.88 | 0.82 | 0.89 |
| doc_002 | 0.91 | 0.75 | 0.68 | 0.78 |
| doc_003 | 0.99 | 0.95 | 0.91 | 0.95 |
看这张表的时候,不要只盯着综合得分。doc_002的文本准确率有0.91,不算低,但结构匹配度只有0.75,表格TEDS只有0.68,说明这个工具在处理doc_002这类文档时,结构还原和表格识别是短板。如果你下游是RAG,doc_002的解析结果切片质量会很差,哪怕文本准确率看起来还行。
提示:综合得分的权重一定要根据业务调,我一般会给结构匹配度至少0.4的权重,因为结构错了,文本再准也是白搭。
5. 常见问题与排查技巧实录
5.1 评测分数虚高:你可能踩了这几个坑
坑一:评测集和训练集重叠。有些解析工具在公开评测集上分数很高,因为它的训练数据里包含了这些文档。你拿它跑你自己的业务文档,分数直接腰斩。解决办法很简单:用你自己的业务文档做评测集,哪怕只有50份。
坑二:标注质量不过关。我见过一个团队,标注文件是实习生做的,表格的合并单元格标注得乱七八糟,结果所有工具在这个评测集上的TEDS都低得离谱。后来重新标注后,分数才恢复正常。标注质量是评测的天花板,标注错了,评测就没有意义。
坑三:标准化脚本有偏。如果你的标准化脚本对某种输出格式处理得特别粗糙,那这种格式的工具就会吃亏。比如Markdown的表格标准化如果没处理好,TEDS会异常低。建议标准化脚本写完后,拿几个已知正确的结果做回归测试。
5.2 表格评测的典型翻车场景
表格评测翻车的方式千奇百怪,我整理了几个高频场景:
| 翻车场景 | 表现 | 排查思路 |
|---|---|---|
| 合并单元格丢失 | TEDS骤降,结构匹配度正常 | 检查解析工具是否支持合并单元格识别 |
| 跨页表格断裂 | 两个半截表格,TEDS极低 | 检查是否有跨页拼接逻辑 |
| 表头识别错误 | TEDS-Struct低,TEDS正常 | 检查表头行的识别规则 |
| 空单元格处理不一致 | 结构对但内容对不上 | 统一空单元格的表示方式 |
跨页表格是我踩过最大的坑。一份财务报表,表格从第3页跨到第4页,解析工具把两页的表格当成两个独立表格输出,TEDS直接掉到0.3。后来我在标准化脚本里加了一个跨页表格合并逻辑:如果上一页最后一个块是表格,下一页第一个块也是表格,且列数一致,就尝试合并。合并后TEDS回升到0.85。
5.3 结构匹配度低但文本准确率高,怎么定位问题
这种情况通常意味着解析工具“读对了字,但排错了序”。排查步骤:
- 先看阅读顺序。把解析结果的块顺序和标注的块顺序并排打印出来,肉眼找第一个错位的地方。
- 再看层级关系。检查标题和正文的父子关系是否正确,有没有把二级标题当成一级标题。
- 最后看元素类型。有没有把表格误判成段落,或者把图片说明误判成正文。
我遇到过一次,解析工具把双栏PDF的右栏内容全部排到了左栏后面,但每一栏内部的顺序是对的。文本准确率0.98,结构匹配度0.45。这种问题在标准化阶段很难发现,必须做阅读顺序的专项检查。
5.4 评测流程的自动化与持续集成
如果你只是偶尔评测一次,手动跑跑脚本没问题。但如果你在持续迭代解析工具,建议把评测流程做成自动化的。我的做法是:
- 把评测脚本封装成命令行工具,输入是预测结果目录,输出是评分报告。
- 用CI工具在每次解析工具更新后自动跑评测。
- 评分报告和历史版本对比,分数下降超过阈值就告警。
python run_eval.py --pred_dir ./predictions --anno_dir ./annotations --output report.json这样做的价值在于,你能第一时间发现解析工具的回归问题。我经历过一次,解析工具升级后文本准确率涨了2个点,但结构匹配度掉了8个点,因为新版本把标题识别逻辑改激进了,把很多加粗正文误判成了标题。如果没有自动化评测,这个问题可能要等到线上出事故才会被发现。
6. 我个人在文档解析评测上的一些经验
跑了这么多轮评测,我最大的体会是:评测基准是死的,业务场景是活的。OmniDocBench提供了一套相对完整的评测框架和指标,但它不能替你回答“我这个业务到底需要什么样的解析质量”这个问题。
我的做法是,先用OmniDocBench的完整指标体系跑一遍,拿到一个全面的画像,然后根据业务需求裁剪指标。比如做合同解析,我会把表格TEDS和章节结构匹配度的权重拉到最高,文本准确率只要不低于0.95就行。做扫描件OCR,文本准确率权重最高,结构匹配度可以放宽。
另外一点,评测集要持续更新。业务文档的版式会变,今天评测集里没有的版式,明天可能就出现了。我一般每个季度会往评测集里补充一批新的业务文档,保持评测集的“新鲜度”。旧文档保留,用来做纵向对比;新文档加入,用来发现新问题。
最后分享一个实用技巧:如果你没有精力做完整的标注,可以先用“弱标注”跑一轮。所谓弱标注,就是只标注关键字段,比如章节标题、表格位置、关键数据,不标注每个段落的精确边界。这种评测虽然不够精细,但能快速筛掉明显不合格的工具,性价比很高。等筛出两三个候选工具后,再对它们做精细评测。
文档解析这个领域,工具会不断迭代,评测方法也会不断演进。但核心逻辑不会变:你得知道你要什么,然后找到合适的方法去衡量它。OmniDocBench给了一把尺子,但怎么用这把尺子,还是得看你自己。