Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 32 additions & 36 deletions tests/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ def test_table_edit_metric(self):
self.assertTrue(table_result.success)
self.assertIsInstance(table_result.score, float)
# 验证固定内容的确定分数
self.assertAlmostEqual(table_result.score, 0.868852, places=5,
msg=f"table_edit分数应该是0.868852,实际: {table_result.score}")
self.assertAlmostEqual(table_result.score, 0.9333333333333333, places=5,
msg=f"table_edit分数应该是0.9333333333333333,实际: {table_result.score}")

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

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

def test_table_sample_edit_distance(self):
"""测试表格样本的编辑距离"""
groundtruth = """## 销售数据统计

| 产品 | 销量 | 收入 |
|------|------|------|
| 产品A | 100 | 1000 |
| 产品B | 200 | 3000 |"""

predicted = """## 销售数据统计

| 产品 | 销量 | 收入 |
|---|---|---|
| 产品A | 100 | 1000 |
| 产品B | 200 | 3000 |"""

results = self.calculator.calculate_all(
predicted_content=predicted,
groundtruth_content=groundtruth
)

# 验证表格编辑距离(分隔符长度差异导致的固定分数)
self.assertIn("table_edit", results)
self.assertTrue(results["table_edit"].success)
self.assertAlmostEqual(results["table_edit"].score, 0.888889, places=5,
msg=f"table_edit分数应该是0.888889,实际: {results['table_edit'].score}")

# 验证TEDS指标(表格结构完全相同,满分)
self.assertIn("table_TEDS", results)
self.assertTrue(results["table_TEDS"].success)
self.assertAlmostEqual(results["table_TEDS"].score, 1.000000, places=5,
msg=f"table_TEDS分数应该是1.000000,实际: {results['table_TEDS'].score}")
def test_table_sample_edit_distance(self):
"""测试渲染一致,表格样式不一致的编辑距离"""
groundtruth = """
| 产品 | 销量 | 收入 |
|------|------|------|
| 产品A | 100 | 1000 |
| 产品B | 200 | 3000 |
"""

predicted = """
<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>"""

results = self.calculator.calculate_all(
predicted_content=predicted,
groundtruth_content=groundtruth
)

# 验证表格编辑距离(分隔符长度差异导致的固定分数)
self.assertIn("table_edit", results)
self.assertTrue(results["table_edit"].success)
self.assertAlmostEqual(results["table_edit"].score, 1.0, places=5,
msg=f"table_edit分数应该是1.0,实际: {results['table_edit'].score}")

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

def test_formula_sample_edit_distance(self):
"""测试公式样本的编辑距离"""
Expand Down
30 changes: 18 additions & 12 deletions webmainbench/metrics/table_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from .base import BaseMetric, MetricResult
from .teds_metrics import TEDSMetric, StructureTEDSMetric
from .text_metrics import EditDistanceMetric

from bs4 import BeautifulSoup

class TableEditMetric(EditDistanceMetric):
"""表格编辑距离指标"""
Expand All @@ -20,18 +20,24 @@ def _calculate_score(self, predicted: str, groundtruth: str,
groundtruth_content_list: List[Dict[str, Any]] = None,
**kwargs) -> MetricResult:
"""计算表格内容的编辑距离"""

# 从content_list中提取表格内容
pred_table = self._extract_table_content(predicted, predicted_content_list)
gt_table = self._extract_table_content(groundtruth, groundtruth_content_list)

# 计算编辑距离
result = super()._calculate_score(pred_table, gt_table, **kwargs)

# 1. 提取原始表格内容
pred_raw = self._extract_table_content(predicted, predicted_content_list)
gt_raw = self._extract_table_content(groundtruth, groundtruth_content_list)

# 2. 复用TEDSMetric的归一化方法,统一转换为HTML格式
teds = TEDSMetric("temp_teds") # 实例化TEDSMetric以调用其方法
pred_html = teds._normalize_to_html(pred_raw) # 调用TEDS的归一化方法
gt_html = teds._normalize_to_html(gt_raw)

# 3. 基于归一化后的文本计算编辑距离
result = super()._calculate_score(pred_html, gt_html, **kwargs)
result.metric_name = self.name
result.details.update({
"predicted_table_length": len(pred_table),
"groundtruth_table_length": len(gt_table),
"content_type": "table"
"predicted_table_length": len(pred_html),
"groundtruth_table_length": len(gt_html),
"content_type": "table",
"normalization": "teds_based" # 标记使用TEDS的归一化方法
})

return result
Expand All @@ -41,7 +47,7 @@ def _extract_table_content(self, text: str, content_list: List[Dict[str, Any]] =
# 使用统一的内容分割方法
content_parts = self.split_content(text, content_list)
return content_parts.get('table', '')

def _extract_tables_from_content_list(self, content_list: List[Dict[str, Any]]) -> List[str]:
"""递归从content_list中提取表格内容"""
tables = []
Expand Down
28 changes: 23 additions & 5 deletions webmainbench/metrics/teds_metrics.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
"""
TEDS (Tree-Edit Distance based Similarity) metrics for WebMainBench.

Based on the paper:
"Image-based table recognition: data, model, and evaluation" by IBM Research.

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

from typing import Dict, Any, List, Optional
Expand Down