diff --git a/tests/test_metrics.py b/tests/test_metrics.py
index 70aa381..cd9736a 100644
--- a/tests/test_metrics.py
+++ b/tests/test_metrics.py
@@ -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')
@@ -874,8 +874,8 @@ 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)
@@ -883,38 +883,34 @@ def test_html_table_edit_distance(self):
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 = """
+
| 产品 | 销量 | 收入 |
|---|
| 产品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, 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):
"""测试公式样本的编辑距离"""
diff --git a/webmainbench/metrics/table_metrics.py b/webmainbench/metrics/table_metrics.py
index bae375e..3698627 100644
--- a/webmainbench/metrics/table_metrics.py
+++ b/webmainbench/metrics/table_metrics.py
@@ -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):
"""表格编辑距离指标"""
@@ -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
@@ -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 = []
diff --git a/webmainbench/metrics/teds_metrics.py b/webmainbench/metrics/teds_metrics.py
index 0cfb8c2..583f196 100644
--- a/webmainbench/metrics/teds_metrics.py
+++ b/webmainbench/metrics/teds_metrics.py
@@ -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 问题:空字符串强制转为 (无效空表格),违背 “空输入即无表格” 的语义,导致空输入与有效表格的分数计算失真。
+v2 优化:空字符串直接返回空,_parse_html_table 识别为空表格,避免生成无效 HTML 结构,空输入场景的评测结果更符合实际语义。
+节点序列化标准化
+v1 问题:用字典存储节点信息,无统一格式,易因字典键值差异导致解析异常。
+v2 优化:新增 _to_bracket_notation 方法,将节点转为 apted 兼容的 “括号表示法”(如 table(tr(th:产品))),标准化节点描述格式,消除解析格式差异问题。
+四、整体价值提升
+准确性:复杂表格(嵌套、合并单元格)的 TEDS 分数更贴近真实结构差异,文本微小差异的量化使结果更客观。
+效率:apted 库的优化算法大幅提升复杂表格的计算速度,支持更大规模批量评测。
+鲁棒性:空输入处理修正、算法失败回退机制,确保评测流程不中断,适配更多异常场景。
+灵活性:文本差异的分级量化,支持 OCR 识别误差、格式微小偏差等实际场景的评测需求。
"""
from typing import Dict, Any, List, Optional