前言
最近由于要对OCR文字识别系统的表格识别部分做指标评测分析。评测方法之前是将ground truth 和recognition result 展平后统计非空单元格之间的两两关系,得到非空单元格的关系矩阵。然后基于这个矩阵去统计Recall,Precision和 F1 score。但是这样的评测方式是有问题的:
-
只检查非空单元格之间的直接关系,而对于由空单元格和非直接关系单元格之间未对齐引起的错误无法检测。
-
另一个问题是无法同时对单元格的内容进行评测
经过一番搜索发现了TEDS评价方法
TEDS介绍
TEDS评价是将表格结构用树状结构表示,树的root节点下有两个children节点thead(表格头)和tbody(表格体),thead和tbody的children节点是tr(表格行),树的叶子节点是td(单元格),每个叶子节点包含三种属性rowspan(行跨度),colspan(列跨度),content(单元格内容)。采用树的距离来进行两颗树之间相似度的度量的一种方法。
公式如下:
编辑距离相似度(teds)=1-编辑距离(editdist)/max(字符串1长度,字符串2的长度)
编辑距离
编辑距离(Edit Distance),又称Levenshtein距离,俄罗斯科学家Vladimir Levenshtein在1965年提出这个概念,又叫 Levenshtein 距离。是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。如比较S1=“内审协会”和s2=“中国内审协会”,就可以通过在S1中插入“中”、“国”两个汉字实现与S2一直,故S1和S2编辑距离为2。
其核心算法:
设计一个二维表格,表格列数为字符串1的长度加1,行数为字符串2的长度加1。-
表格的第1行按照列,自左往右,依序填列0,1,2,…字符串1的长度n;-
表格的1列按照行,自上往下,依序填列0,1,2,…字符串2的长度m;-
然后自第2行,第2列开始,自左往右,填充数据,-
该数据的规则是:如果两个字符串对应位置的字符相同,则取左上角单元格的值;如果不同,则取该单元格左方、左上方、上方的三个但单元格的值的最小值+1。重复上述操作,直到填满最后一个单元格,其数字就是编辑距离。
python实现方式
第一种
def minDistance(word1, word2):
if not word1:
return len(word2 or '') or 0
if not word2:
return len(word1 or '') or 0
size1 = len(word1)
size2 = len(word2)
last = 0
tmp = range(size2 + 1)
value = None
for i in range(size1):
tmp[0] = i + 1
last = i
# print word1[i], last, tmp
for j in range(size2):
if word1[i] == word2[j]:
value = last
else:
value = 1 + min(last, tmp[j], tmp[j + 1])
# print(last, tmp[j], tmp[j + 1], value)
last = tmp[j+1]
tmp[j+1] = value
# print tmp
return value
测试:
assert minDistance('horse', '') == 5
assert minDistance('', 'ros') == 3
assert minDistance('h', 'r') == 1
assert minDistance('horse', 'ros') == 3
第二种,基于Python-Levenshtein
pip install python-Levenshtein
所有用法:
#关于 Levenshtein 所有函数的用法和注释
apply_edit() #根据第一个参数editops()给出的操作权重,对第一个字符串基于第二个字符串进行相对于权重的操作
distance() #计算2个字符串之间需要操作的绝对距离
editops() #找到将一个字符串转换成另外一个字符串的所有编辑操作序列
hamming() #计算2个字符串不同字符的个数,这2个字符串长度必须相同
inverse() #用于反转所有的编辑操作序列
jaro() #计算2个字符串的相识度,这个给与相同的字符更高的权重指数
jaro_winkler() #计算2个字符串的相识度,相对于jaro 他给相识的字符串添加了更高的权重指数,所以得出的结果会相对jaro更大(%百分比比更大)
matching_blocks() #找到他们不同的块和相同的块,从第六个开始相同,那么返回截止5-5不相同的1,第8个后面也开始相同所以返回8-8-1,相同后面进行对比不同,最后2个对比相同返回0
median() #找到一个列表中所有字符串中相同的元素,并且将这些元素整合,找到最接近这些元素的值,可以不是字符串中的值。
median_improve() #通过扰动来改进近似的广义中值字符串。
opcodes() #给出所有第一个字符串转换成第二个字符串需要权重的操作和操作详情会给出一个列表,列表的值为元祖,每个元祖中有5个值
#[('delete', 0, 1, 0, 0), ('equal', 1, 3, 0, 2), ('insert', 3, 3, 2, 3), ('replace', 3, 4, 3, 4)]
#第一个值是需要修改的权重,例如第一个元祖是要删除的操作,2和3是第一个字符串需要改变的切片起始位和结束位,例如第一个元祖是删除第一字符串的0-1这个下标的元素
#4和5是第二个字符串需要改变的切片起始位和结束位,例如第一个元祖是删除第一字符串的0-0这个下标的元素,所以第二个不需要删除
quickmedian() #最快的速度找到最相近元素出现最多从新匹配出的一个新的字符串
ratio() #计算2个字符串的相似度,它是基于最小编辑距离
seqratio() #计算两个字符串序列的相似率。
setmedian() #找到一个字符串集的中位数(作为序列传递)。 取最接近的一个字符串进行传递,这个字符串必须是最接近所有字符串,并且返回的字符串始终是序列中的字符串之一。
setratio() #计算两个字符串集的相似率(作为序列传递)。
subtract_edit() #从序列中减去一个编辑子序列。看例子这个比较主要的还是可以将第一个源字符串进行改变,并且是基于第二个字符串的改变,最终目的是改变成和第二个字符串更相似甚至一样
def TEDS(str1,str2):
maxL=len(str1)
if len(str2)>maxL:
maxL=len(str2)
distan=ls.distance(str1,str2)
teds_result=100-distan/maxL*100
return teds_result