Skip to content

Commit 4cf8b09

Browse files
authored
Merge pull request #43 from pekopoke/dev
Optimize table edit distance calculation by using normalize
2 parents 40f0617 + 839938f commit 4cf8b09

File tree

3 files changed

+73
-53
lines changed

3 files changed

+73
-53
lines changed

tests/test_metrics.py

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ def test_table_edit_metric(self):
129129
self.assertTrue(table_result.success)
130130
self.assertIsInstance(table_result.score, float)
131131
# 验证固定内容的确定分数
132-
self.assertAlmostEqual(table_result.score, 0.868852, places=5,
133-
msg=f"table_edit分数应该是0.868852,实际: {table_result.score}")
132+
self.assertAlmostEqual(table_result.score, 0.9333333333333333, places=5,
133+
msg=f"table_edit分数应该是0.9333333333333333,实际: {table_result.score}")
134134

135135
# 验证详细信息
136136
self.assertEqual(table_result.details['content_type'], 'table')
@@ -874,47 +874,43 @@ def test_html_table_edit_distance(self):
874874
# 验证表格编辑距离(分隔符长度差异导致的固定分数)
875875
self.assertIn("table_edit", results)
876876
self.assertTrue(results["table_edit"].success)
877-
self.assertAlmostEqual(results["table_edit"].score, 0.593573, places=5,
878-
msg=f"table_edit分数应该是0.593573,实际: {results['table_edit'].score}")
877+
self.assertAlmostEqual(results["table_edit"].score, 0.5935733724094621, places=5,
878+
msg=f"table_edit分数应该是0.5935733724094621,实际: {results['table_edit'].score}")
879879

880880
# 验证TEDS指标(表格结构完全相同,满分)
881881
self.assertIn("table_TEDS", results)
882882
self.assertTrue(results["table_TEDS"].success)
883883
self.assertAlmostEqual(results["table_TEDS"].score, 0.9984520490180891, places=5,
884884
msg=f"table_TEDS分数应该是0.0.9984520490180891,实际: {results['table_TEDS'].score}")
885885

886-
def test_table_sample_edit_distance(self):
887-
"""测试表格样本的编辑距离"""
888-
groundtruth = """## 销售数据统计
889-
890-
| 产品 | 销量 | 收入 |
891-
|------|------|------|
892-
| 产品A | 100 | 1000 |
893-
| 产品B | 200 | 3000 |"""
894-
895-
predicted = """## 销售数据统计
896-
897-
| 产品 | 销量 | 收入 |
898-
|---|---|---|
899-
| 产品A | 100 | 1000 |
900-
| 产品B | 200 | 3000 |"""
901-
902-
results = self.calculator.calculate_all(
903-
predicted_content=predicted,
904-
groundtruth_content=groundtruth
905-
)
906-
907-
# 验证表格编辑距离(分隔符长度差异导致的固定分数)
908-
self.assertIn("table_edit", results)
909-
self.assertTrue(results["table_edit"].success)
910-
self.assertAlmostEqual(results["table_edit"].score, 0.888889, places=5,
911-
msg=f"table_edit分数应该是0.888889,实际: {results['table_edit'].score}")
912-
913-
# 验证TEDS指标(表格结构完全相同,满分)
914-
self.assertIn("table_TEDS", results)
915-
self.assertTrue(results["table_TEDS"].success)
916-
self.assertAlmostEqual(results["table_TEDS"].score, 1.000000, places=5,
917-
msg=f"table_TEDS分数应该是1.000000,实际: {results['table_TEDS'].score}")
886+
def test_table_sample_edit_distance(self):
887+
"""测试渲染一致,表格样式不一致的编辑距离"""
888+
groundtruth = """
889+
| 产品 | 销量 | 收入 |
890+
|------|------|------|
891+
| 产品A | 100 | 1000 |
892+
| 产品B | 200 | 3000 |
893+
"""
894+
895+
predicted = """
896+
<table><tr><th>产品</th><th>销量</th><th>收入</th></tr><tr><td>产品A</td><td>100</td><td>1000</td></tr><tr><td>产品B</td><td>200</td><td>3000</td></tr></table>"""
897+
898+
results = self.calculator.calculate_all(
899+
predicted_content=predicted,
900+
groundtruth_content=groundtruth
901+
)
902+
903+
# 验证表格编辑距离(分隔符长度差异导致的固定分数)
904+
self.assertIn("table_edit", results)
905+
self.assertTrue(results["table_edit"].success)
906+
self.assertAlmostEqual(results["table_edit"].score, 1.0, places=5,
907+
msg=f"table_edit分数应该是1.0,实际: {results['table_edit'].score}")
908+
909+
# 验证TEDS指标(表格结构完全相同,满分)
910+
self.assertIn("table_TEDS", results)
911+
self.assertTrue(results["table_TEDS"].success)
912+
self.assertAlmostEqual(results["table_TEDS"].score, 1.0, places=5,
913+
msg=f"table_TEDS分数应该是1.0,实际: {results['table_TEDS'].score}")
918914

919915
def test_formula_sample_edit_distance(self):
920916
"""测试公式样本的编辑距离"""

webmainbench/metrics/table_metrics.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from .base import BaseMetric, MetricResult
88
from .teds_metrics import TEDSMetric, StructureTEDSMetric
99
from .text_metrics import EditDistanceMetric
10-
10+
from bs4 import BeautifulSoup
1111

1212
class TableEditMetric(EditDistanceMetric):
1313
"""表格编辑距离指标"""
@@ -20,18 +20,24 @@ def _calculate_score(self, predicted: str, groundtruth: str,
2020
groundtruth_content_list: List[Dict[str, Any]] = None,
2121
**kwargs) -> MetricResult:
2222
"""计算表格内容的编辑距离"""
23-
24-
# 从content_list中提取表格内容
25-
pred_table = self._extract_table_content(predicted, predicted_content_list)
26-
gt_table = self._extract_table_content(groundtruth, groundtruth_content_list)
27-
28-
# 计算编辑距离
29-
result = super()._calculate_score(pred_table, gt_table, **kwargs)
23+
24+
# 1. 提取原始表格内容
25+
pred_raw = self._extract_table_content(predicted, predicted_content_list)
26+
gt_raw = self._extract_table_content(groundtruth, groundtruth_content_list)
27+
28+
# 2. 复用TEDSMetric的归一化方法,统一转换为HTML格式
29+
teds = TEDSMetric("temp_teds") # 实例化TEDSMetric以调用其方法
30+
pred_html = teds._normalize_to_html(pred_raw) # 调用TEDS的归一化方法
31+
gt_html = teds._normalize_to_html(gt_raw)
32+
33+
# 3. 基于归一化后的文本计算编辑距离
34+
result = super()._calculate_score(pred_html, gt_html, **kwargs)
3035
result.metric_name = self.name
3136
result.details.update({
32-
"predicted_table_length": len(pred_table),
33-
"groundtruth_table_length": len(gt_table),
34-
"content_type": "table"
37+
"predicted_table_length": len(pred_html),
38+
"groundtruth_table_length": len(gt_html),
39+
"content_type": "table",
40+
"normalization": "teds_based" # 标记使用TEDS的归一化方法
3541
})
3642

3743
return result
@@ -41,7 +47,7 @@ def _extract_table_content(self, text: str, content_list: List[Dict[str, Any]] =
4147
# 使用统一的内容分割方法
4248
content_parts = self.split_content(text, content_list)
4349
return content_parts.get('table', '')
44-
50+
4551
def _extract_tables_from_content_list(self, content_list: List[Dict[str, Any]]) -> List[str]:
4652
"""递归从content_list中提取表格内容"""
4753
tables = []

webmainbench/metrics/teds_metrics.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
11
"""
22
TEDS (Tree-Edit Distance based Similarity) metrics for WebMainBench.
33
4-
Based on the paper:
5-
"Image-based table recognition: data, model, and evaluation" by IBM Research.
6-
7-
The TEDS algorithm represents tables as tree structures and calculates
8-
similarity using tree edit distance.
4+
一、核心算法升级:树编辑距离计算更精准高效
5+
替换自定义简化 DP 算法为专业 APTED 库
6+
v1 问题:自定义动态规划算法仅支持基础编辑操作,对嵌套表格(如多层表头、合并单元格)的层级差异处理不准确,且复杂表格计算效率低(DP 矩阵膨胀导致速度慢)。
7+
v2 优化:采用 apted 库(专门用于有序树编辑距离计算),严格遵循学术级算法,能精准识别子节点顺序、嵌套关系等复杂差异,计算效率提升 5-10 倍(100 节点内表格),彻底解决 v1 对复杂表格的误判问题。
8+
新增算法失败回退机制
9+
v1 问题:算法异常(如嵌套过深)直接返回错误,中断评测流程。
10+
v2 优化:apted 计算失败时,自动回退到 “节点数量差” 兜底(如预测 5 节点、真实 3 节点,距离为 2),确保批量评测不中断,鲁棒性显著提升。
11+
二、文本差异计算:从 “非黑即白” 到 “量化分级”
12+
引入 Levenshtein 文本编辑距离
13+
v1 问题:文本必须完全一致才判定节点相等(如 “产品 A” vs “产品 A” 因空格差异被判定为不相等),文本差异成本固定为 1.0,无法区分 “微小差异” 与 “巨大差异”。
14+
v2 优化:通过 rapidfuzz.distance.Levenshtein 量化文本差异,将差异归一化为 0-1 区间的成本
15+
三、边界场景处理:鲁棒性大幅增强
16+
空输入逻辑修正
17+
v1 问题:空字符串强制转为 <table><tr><td></td></tr></table>(无效空表格),违背 “空输入即无表格” 的语义,导致空输入与有效表格的分数计算失真。
18+
v2 优化:空字符串直接返回空,_parse_html_table 识别为空表格,避免生成无效 HTML 结构,空输入场景的评测结果更符合实际语义。
19+
节点序列化标准化
20+
v1 问题:用字典存储节点信息,无统一格式,易因字典键值差异导致解析异常。
21+
v2 优化:新增 _to_bracket_notation 方法,将节点转为 apted 兼容的 “括号表示法”(如 table(tr(th:产品))),标准化节点描述格式,消除解析格式差异问题。
22+
四、整体价值提升
23+
准确性:复杂表格(嵌套、合并单元格)的 TEDS 分数更贴近真实结构差异,文本微小差异的量化使结果更客观。
24+
效率:apted 库的优化算法大幅提升复杂表格的计算速度,支持更大规模批量评测。
25+
鲁棒性:空输入处理修正、算法失败回退机制,确保评测流程不中断,适配更多异常场景。
26+
灵活性:文本差异的分级量化,支持 OCR 识别误差、格式微小偏差等实际场景的评测需求。
927
"""
1028

1129
from typing import Dict, Any, List, Optional

0 commit comments

Comments
 (0)