olmOCR 合成表格数据集解析:以 untable.md 为例理解 HTML 表格的线性化输出与 Bench 表格验证机制
【免费下载链接】olmocrToolkit for linearizing PDFs for LLM datasets/training项目地址: https://gitcode.com/GitHub_Trending/ol/olmocr
tests/sample_dataset/synth_table/untable.md是 olmOCR 仓库测试集中的一个合成表格样本:它以「YAML front matter 元数据 + 双语标题 + 复杂 HTML<table>」的形式,完整呈现了 PDF 中统计表格被线性化后的标准输出形态。本文以该文件为解剖对象,结合 table_parsing.py、tests.py、benchmark.py 等源码,深入讲解 olmOCR 生态中表格类文档的标注格式、解析算法(rowspan/colspan、表头关系图)与 Bench 自动验证机制,读完你将能够读懂并自行构造这类表格样本,并理解 olmOCR-Bench 如何机器化地检查表格 OCR 的正确性。
样本定位:它在测试体系中扮演什么角色
在仓库的测试数据目录结构中,tests/sample_dataset/下包含三个子目录:empty_document/(空白文档)、simple_document/(简单文档 edgar)与synth_table/(合成表格)。每个子目录内都有一对*.md与*.pdf文件,其中untable.md与 untable.pdf 共同构成"联合国统计年鉴合成表格"这一测试用例。
tests/sample_dataset/urls.jsonl记录了该样本的溯源信息:
{"id": "synth_table/untable", "url": "https://unstats.un.org/unsd/demographic/products/dyb/dyb2012/annexI.pdf"}即该合成表格对应《联合国人口统计年鉴 2012》附件 I(Annual mid-year population, United Nations estimates: 2003–2012)的一页。这类"内部爬取的 PDF 仓库中筛选出含表格的文档"正是 olmOCR-Bench 的 Tables(TA)类测试用例的典型来源——根据 README.md 的说明,Tables 类文档从构建 olmOCR-Mix 的同一内部 PDF 仓库中采样,过滤出含表格的页面后,用 Gemini-Flash-2.0 生成"随机选取单元格之间的关系"作为测试事实,并经人工复核。
front matter:样本的元数据协议
untable.md的第一部分不是表格内容,而是一段 YAML front matter,它定义了该页面样本的五个元数据字段:
--- primary_language: en is_rotation_valid: True rotation_correction: 0 is_table: True is_diagram: False ---各字段含义如下:
| 字段 | 取值 | 含义 |
|---|---|---|
primary_language | en | 页面主体语言。本例页面为英法双语排版(联合国出版物的典型形态),主语言标记为英语 |
is_rotation_valid | True | 页面朝向有效。结合rotation_correction: 0表示原始扫描页面无需旋转校正即可正确阅读 |
rotation_correction | 0 | 需要施加的旋转校正角度(度)。为 0 说明页面本身是正立的 |
is_table | True | 该页面主体内容是表格 |
is_diagram | False | 该页面不是图表/示意图 |
这套标记直接服务于数据清洗与基准评估流程:is_table/is_diagram用于区分页面类型,rotation_correction系列字段则与旋转相关的数据增强和评估逻辑(仓库 train 目录下存在大量rotation相关训练配置,例如 qwen25_vl_olmocrv4_rotation_1epoch.yaml)对应,确保模型训练与评估使用的样本是经过旋转校正的。
表格正文:一篇真实的联合国统计表
front matter 之后是两行双语标题,随后是一个完整的 HTML<table>:
<table> <thead> <tr> <th rowspan="2">Continent and country or area<br/>Continent et pays ou zone</th> <th class="header-label" colspan="10">Population estimates (in thousands) - Estimations de population (en milliers)¹</th> </tr> <tr> <th class="year">2003</th> ... <th class="year">2012</th> </tr> </thead> <tbody> ... </tbody> </table>这个表格集中体现了真实世界 PDF 表格线性化输出的难点,值得逐点拆解:
- 多级表头(multi-level header):表头区有两行
<tr>。第一行是分类标题colspan="10"横跨 10 列;第二行是 10 个具体年份列头。左下角的"Continent and country or area"单元格通过rowspan="2"纵向跨越两行表头。 rowspan/colspan属性:除了表头,正文中还大量使用colspan="11"的分区标题行(如AFRICA - AFRIQUE、AMERICA, NORTH - AMÉRIQUE DU NORD)和colspan="11"的空白分隔行(<td class="spacer" colspan="11"></td>)。- 双语单元格文本:国家列普遍采用"英文 - 法文"双命名,如
Algeria - Algérie、Egypt - Égypte;<br/>标签用于单元格内换行(如Democratic Republic of the Congo - République<br/> démocratique du Congo)。 - 语义化 class:
header-label、year、section-header、country、data、spacer等 class 标注了每类单元格的角色,便于解析器识别表头与数据。 - 数字格式化:人口数据以空格分隔千位(如
33 003),并且保留上标注释(Mauritius - Maurice<sup>a</sup>)。
这些结构特征正是 olmOCR-Bench 表格测试要机器化验证的"事实"——某个单元格存在、且其上下左右或表头方向上的相邻单元格满足特定文本关系。
源码解析:HTML 表格如何被还原成单元格关系图
untable.md中的 HTML 表格由 table_parsing.py 中的parse_html_tables()负责解析。其核心数据结构是TableData,源码注释明确描述了设计意图:
单元格文本只出现一次。例如第 0 行有一个 colspan 1 和一个 colspan 2 的列,那么文本只存储在 (0,0) 与 (0,1),(0,2) 不存在于数据中。但你可以查询任意 (row, col) 的 "left"、"right"、"up"、"down" 邻居集合——因为 rowspan/colspan 的存在,每个方向可能有多个单元格。
解析流程分三步:
- 抽取行与单元格:用 BeautifulSoup 找到所有
<tr>,对每个<tr>用find_all(["th", "td"], recursive=False)取出直接子单元格(不递归,避免嵌套表格被误收)。 - 归一化 span 属性:
_safe_span_int()将rowspan/colspan属性安全转换为正整数——None、空串、0、非法值一律回退为默认值1;rowspan="0"这一 HTML 特殊语义(延伸到表节末尾)被显式处理为total_rows - row_idx。 - 构建关系图:
_build_table_data_from_specs()维护一个occupancy网格和一个active_rowspans列表,将每个单元格的文本登记在(row, col)起始坐标,并通过循环扫描相邻行列,生成四个方向的关系字典up_relations/down_relations/left_relations/right_relations。若某行存在空位(None),则is_rectangular=False,表示该表格不是规整矩形。
其中is_heading的判定逻辑(table_parsing.py 中parse_html_tables第 434 行附近)值得注意:is_heading = cell.name == "th" or heading_context,即单元格是<th>标签,或位于<thead>内,都被视为表头。这正好覆盖了untable.md中"<th rowspan="2">第一列 + 双行<thead>表头"的结构。
基于关系图,TableData还提供了两个查询方法,用于"顺着连接图向上/向左走到表头":
top_heading_relations(row, col):沿up_relations向上走,收集沿途遇到的 heading 单元格;如果一路上没有 heading,则返回路径尽头的末端单元格。left_heading_relations(row, col):沿left_relations向左走,逻辑相同。
_walk_heading_relations用 BFS 遍历连接图,这正是 tests/test_table_parsing.py 中test_complex_multi_level_headers、test_left_headers_with_row_spans等用例所验证的行为——例如数据单元格(4, 2)的 January 数据应同时关联2024 Sales Data、First Half、Q1、Jan四级表头。
TableTest:表格事实的机器化验证
表格样本最终服务于评估,评估入口是 tests.py 中的TableTest类(type == "table")。它的设计思路是"以目标单元格为中心,验证其六个方向的邻居关系":
# 这些属性表示目标单元格的上/下/左/右紧邻单元格应具有的文本 up: str = "" down: str = "" left: str = "" right: str = "" # 这些属性表示一直向上/向左延伸到的表头单元格应具有的文本 top_heading: str = "" left_heading: str = ""run()的执行流程(tests.py 中TableTest.run)如下:
- 分别调用
parse_markdown_tables(content)与parse_html_tables(content)解析输出内容中的所有表格(可用ignore_markdown_tables=True跳过 Markdown 表格)。README 特别指出:部分测试依赖 rowspan/colspan 信息,只有 HTML 表格才能携带,因此只输出 Markdown 表格的模型无法在该类测试上拿满分。 - 对每个表格,用
fuzz.ratio模糊匹配找到所有与cell相似的单元格;匹配阈值由max_diffs推导(1.0 - max_diffs / len(cell),下限 0.5)。 - 对每个命中单元格,逐个检查
up/down/left/right/top_heading/left_heading各方向的邻居文本是否与期望值模糊匹配。 - 任一命中单元格满足全部关系即通过,否则汇总所有失败原因。
验证还包含语义宽容度设计:所有文本先经过normalize_text()处理——<br/>归一化为空格、去除 Markdown 加粗/斜体、空白折叠、Unicode 统一为 NFC,并把各种弯引号、长破折号统一为 ASCII 等价字符(é与e+◌́等价、-与—等价)。这意味着模型输出的格式差异不会影响表格事实的判定。
端到端:从合成样本到 Bench 评分
untable.md这类文件在基准测试流水线中的位置如下(benchmark.py):
- 每个候选 OCR 工具(candidate)在数据目录下对应一个子文件夹,其中存放对每份 PDF 的多次解析结果,文件命名遵循
{doc}_pg{page}_repeat{repeat}.md模式(如untable_pg1_repeat1.md)。 - 对每个测试,Bench 会读取该 PDF 对应页面的全部
_repeat文件逐一运行test.run(md_content),以"多数重复通过"(test_avg > 0.5)判定该测试最终是否通过。 - 最终得分按 JSONL 文件分组:每个 JSONL 文件的得分是其中测试的通过率,整体得分是各 JSONL 得分的平均,并给出 95% bootstrap 置信区间。
运行命令为(详见 README.md):
# 安装 bench 依赖并配置 playwright 用于数学公式渲染测试 pip install -e .[bench] playwright install chromium # 用候选 OCR 工具转换基准 PDF python -m olmocr.bench.convert olmocr_pipeline --dir ./olmOCR-bench/bench_data # 运行基准 python -m olmocr.bench.benchmark --dir ./olmOCR-bench/bench_data需要说明的是,仓库内tests/sample_dataset/是一份精简的本地样例(含urls.jsonl、三组 md/pdf 对),完整基准数据由外部数据集发布;实际评估时测试定义以 JSONL 格式给出,tests.py 的load_tests()用线程池并行加载并校验测试 ID 唯一性,TableTest即通过load_single_test()按type == "table"分支实例化。
小结与扩展阅读
tests/sample_dataset/synth_table/untable.md虽然只是一个小样本,却浓缩了 olmOCR 表格处理链路的三层设计:合成标注格式(front matter + HTML 表格,反映模型期望的线性化输出形态)、通用表格解析器(TableData单元格关系图,兼容 rowspan/colspan 与多级表头)、面向事实的机器化验证(TableTest六向邻居关系检查)。这套机制保证了表格类 OCR 质量评估的客观性与可复现性。
想进一步深入,可以继续阅读:
- 表格解析实现:olmocr/bench/table_parsing.py(
parse_html_tables、_build_table_data_from_specs、TableData) - 表格测试类与归一化:olmocr/bench/tests.py(
TableTest、normalize_text) - 解析器行为验证:tests/test_table_parsing.py(多级表头、rowspan/colspan、非矩形表格等 12 个用例)
- 基准运行器:olmocr/bench/benchmark.py 与 olmocr/bench/README.md
- 其他合成样本:empty_document/blanktext.md、simple_document/edgar.md
【免费下载链接】olmocrToolkit for linearizing PDFs for LLM datasets/training项目地址: https://gitcode.com/GitHub_Trending/ol/olmocr
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考