WER(word error rate)经常作为语音识别任务的性能评测指标,WER的计算公式,直接从网上粘贴过来了。

一些语音识别框架(如:Kaldi、ESPNet等)中,都会包含wer的计算方法,其中ESPNet的结果展示如下:

我们希望用python实现上面的效果,首先来看看wer是怎么计算的。
首先,随便写个例子,ref(reference)表示标注文本序列,hyp(hypothesis)表示预测文本序列,则可以计算 cer/wer = 3,其中一次替换错误(S),一次删除错误(D),一次插入错误(I)。

参考资料:https://martin-thoma.com/word-error-rate-calculation/
我们列出WER的计算公式如下,看似很绕,我们用图来画一下:

首先,横轴为ref(标注序列),我们列出来,最后加一个<b>作为占位,纵轴为hyp(预测序列),也列出来,同样最后加上<b>占位。

然后,中间的每个方格代表一个cost,什么是cost呢,就是到目前为止两个序列错了多少。如ref=b,hyp=b时,错误为1,因为此时的子序列 sub_ref=ab,sub_hyp=bb,所以只有一个替换错误(S)。
而前面公式中列出来的,就是计算递归计算cost的方法。也就是说当前位置ij的cost只与相邻的前面三个位置有关(上图中紫色部分),而且是三个紫色方块的最小值+1,翻译一下:
(1)对于位置 i、j,如果 hyp(i-1) == ref(j-i),则 cost(i,j)=cost(i-1,j-1);
(2)如果 hyp(i-1) != ref(j-i) ,也就是图中ij的位置,那么 cost(i,j) 就是三个紫色方块的最小值+1
而三个紫色方块代表的物理意义如下:(左上表示替换错误S,右上表示插入错误I,左下表示删除错误D)

一直迭代下去,直到所有cost都被计算出来之后,整个cost矩阵右下角的为的值就是你要的wer了(也就是<b>和<b>的位置),上上图中wer=3。

得到了wer,我们还想直到 I、D、S 到底各占多少,对齐的文本到底是什么样子的,这是我们要从右下角回溯。对于右下角的“3”,它的前三个值最小为3,说明没有发生错误。接下来(f,f)位置,前三个值为2、2、3,最小值为2,说明发生了错误,这时按照 substitution > insert > delete 的优先级,选择上方的方格,并记录一次插入错误。以此类推,直到遍历到左上角为止。如下图所示,我们就得到了所有的错误类型。

当然,优先级不同,回溯方法也不一样,如果优先级为:insert > delete > substitution,则结果如下:

下面看一个特殊情况,即句子开头有插入或者删除错误。

这时如果我们回溯整个矩阵发现,hyp先结束了,而ref还没有结束,为了得到所有的操作,我们必须要遍历的左上角才行,所以,强行从遍历结束的位置移动到左上角。那么,如果是hyp先结束,则所有移动都是删除错误(D),如果是ref先结束,那么所有错误都是插入错误(I)。

python的实现如下,供参考:

import numpy as npdef levenshtein_distance(hypothesis: list, reference: list):"""编辑距离计算两个序列的levenshtein distance,可用于计算 WER/CER参考资料:https://www.cuelogic.com/blog/the-levenshtein-algorithmhttps://martin-thoma.com/word-error-rate-calculation/C: correctW: wrongI: insertD: deleteS: substitution:param hypothesis: 预测序列:param reference: 真实序列:return: 1: 错误操作,所需要的 S,D,I 操作的次数;2: ref 与 hyp 的所有对齐下标3: 返回 C、W、S、D、I 各自的数量"""len_hyp = len(hypothesis)len_ref = len(reference)cost_matrix = np.zeros((len_hyp + 1, len_ref + 1), dtype=np.int16)# 记录所有的操作,0-equal;1-insertion;2-deletion;3-substitutionops_matrix = np.zeros((len_hyp + 1, len_ref + 1), dtype=np.int8)for i in range(len_hyp + 1):cost_matrix[i][0] = ifor j in range(len_ref + 1):cost_matrix[0][j] = j# 生成 cost 矩阵和 operation矩阵,i:外层hyp,j:内层reffor i in range(1, len_hyp + 1):for j in range(1, len_ref + 1):if hypothesis[i-1] == reference[j-1]:cost_matrix[i][j] = cost_matrix[i-1][j-1]else:substitution = cost_matrix[i-1][j-1] + 1insertion = cost_matrix[i-1][j] + 1deletion = cost_matrix[i][j-1] + 1# compare_val = [insertion, deletion, substitution]   # 优先级compare_val = [substitution, insertion, deletion]   # 优先级min_val = min(compare_val)operation_idx = compare_val.index(min_val) + 1cost_matrix[i][j] = min_valops_matrix[i][j] = operation_idxmatch_idx = []  # 保存 hyp与ref 中所有对齐的元素下标i = len_hypj = len_refnb_map = {"N": len_ref, "C": 0, "W": 0, "I": 0, "D": 0, "S": 0}while i >= 0 or j >= 0:i_idx = max(0, i)j_idx = max(0, j)if ops_matrix[i_idx][j_idx] == 0:     # correctif i-1 >= 0 and j-1 >= 0:match_idx.append((j-1, i-1))nb_map['C'] += 1# 出边界后,这里仍然使用,应为第一行与第一列必然是全零的i -= 1j -= 1# elif ops_matrix[i_idx][j_idx] == 1:   # insertelif ops_matrix[i_idx][j_idx] == 2:   # inserti -= 1nb_map['I'] += 1# elif ops_matrix[i_idx][j_idx] == 2:   # deleteelif ops_matrix[i_idx][j_idx] == 3:   # deletej -= 1nb_map['D'] += 1# elif ops_matrix[i_idx][j_idx] == 3:   # substituteelif ops_matrix[i_idx][j_idx] == 1:   # substitutei -= 1j -= 1nb_map['S'] += 1# 出边界处理if i < 0 and j >= 0:nb_map['D'] += 1elif j < 0 and i >= 0:nb_map['I'] += 1match_idx.reverse()wrong_cnt = cost_matrix[len_hyp][len_ref]nb_map["W"] = wrong_cnt# print("ref: %s" % " ".join(reference))# print("hyp: %s" % " ".join(hypothesis))# print(nb_map)# print("match_idx: %s" % str(match_idx))return wrong_cnt, match_idx, nb_mapdef test():"""id: (301225575230191207_spkb_f-301225575230191207_spkb_f_slice19)Scores: (#C #S #D #I) 27 4 1 2REF:  然 后 而 且 这 个 账 号 , 你 这 边 *** 做 车 商 续 费 的 话 就 发 真 车 应 该 *** 稍 微 再 便 宜 点 。HYP:  然 后 而 且 这 个 账 号 *** 你 这 边 要 做 车 商 续 费 的 话 就 发 真 车 应 该 还 有 一 个 便 宜 的 。Eval::return:"""wrong_cnt, match_idx, nb_map = levenshtein_distance(reference=list('abcdef'),hypothesis=list('cdefg'))wrong_cnt, match_idx, nb_map = levenshtein_distance(reference=list('cdefg'),hypothesis=list('abcdef'))wrong_cnt, match_idx, nb_map = levenshtein_distance(reference=list('cdefg'),hypothesis=list(''))wrong_cnt, match_idx, nb_map = levenshtein_distance(reference=list(''),hypothesis=list(''))wrong_cnt, match_idx, nb_map = levenshtein_distance(reference=list('abcdf'),hypothesis=list('bbdef'))wrong_cnt, match_idx, nb_map = levenshtein_distance(hypothesis=list('然后而且这个账号,你这边做车商续费的话就发真车应该稍微再便宜点。'),reference=list('然后而且这个账号你这边要做车商续费的话就发真车应该还有一个便宜的。'))if __name__ == '__main__':test()

编辑距离WER/CER计算的一种python实现相关推荐

  1. ML:文本、图像等数值化数据相似度计算之余弦相似度计算三种python代码实现

    ML:文本.图像等数值化数据相似度计算之余弦相似度计算三种python代码实现 目录 相似度计算之余弦相似度计算 输出结果 三种python代码实现

  2. Python 爬楼梯问题--有n阶台阶,上楼可以一步上1阶,2阶,3阶,计算共有多少种不同的走法?

    Python爬楼梯问题:有n阶台阶,上楼可以一步上1阶,2阶,3阶,计算共有多少种不同的走法? 总共n步台阶(先假设n>3),f(n)表示n步台阶的走法总数 1.第一步如果是只走1步台阶,剩下的 ...

  3. python爬楼梯多少种_Python 爬楼梯问题--有n阶台阶,上楼可以一步上1阶,2阶,3阶,计算共有多少种不同的走法?...

    Python爬楼梯问题:有n阶台阶,上楼可以一步上1阶,2阶,3阶,计算共有多少种不同的走法? 总共n步台阶(先假设n>3),f(n)表示n步台阶的走法总数 1.第一步如果是只走1步台阶,剩下的 ...

  4. python学会了能做什么-学会Python后都能做什么?介绍五种Python的实用场景

    如今,越来越多的人加入到学习Python的队伍当中. 有的学习者是设计师,学习Python可以帮助他们查找更多的海报案例;有的学习者是大学生,学习Python可以帮助他们更好地查阅论文资料;还有的学习 ...

  5. 离散度计算公式 python_被多种离散化场景困扰?8种python技巧!让数据处理更简单...

    前言 python数据处理与分析学习过程中,需要有这样的一种意识,即元"为什么选择了python而不是其他?"既然选择了python,那么在实际应用中,它到底哪里不一样?大家说的方 ...

  6. DL之DNN:自定义2层神经网络TwoLayerNet模型(计算梯度两种方法)利用MNIST数据集进行训练、预测

    DL之DNN:自定义2层神经网络TwoLayerNet模型(计算梯度两种方法)利用MNIST数据集进行训练.预测 导读 利用python的numpy计算库,进行自定义搭建2层神经网络TwoLayerN ...

  7. 玩转Python? 一文总结30种Python的窍门和技巧!

    Python作为2019年必备语言之一,展现了不可替代作用.对于所有的数据科学工作者,如何提高使用Python的效率,这里,总结了30种Python的最佳实践.技巧和窍门.希望这些可以帮助大家在202 ...

  8. 玩转Python? 一文总结30种Python的窍门和技巧,不可错过哈!

    Python作为2020年必备语言之一,展现了不可替代作用.对于所有的数据科学工作者,如何提高使用Python的效率,这里,总结了30种Python的最佳实践.技巧和窍门.希望这些可以帮助大家在202 ...

  9. cassandra可视化工具_耗时1个月整理!160种Python标准库、第三方库和外部工具都有了...

    耗时1个月整理!160种Python标准库.第三方库和外部工具都有了 北京尚学堂 2019-12-09 14:59:15 Python数据工具箱涵盖从数据源到数据可视化的完整流程中涉及到的常用库.函数 ...

最新文章

  1. Failed to load or instantiate TagLibraryValidator class: org.apache.taglibs.
  2. 关于maven仓库中的_remote.repositories
  3. java extensions JAR files
  4. 腾讯AI Lab正式开源业内最大规模多标签图像数据集
  5. tcp/ip ---数据封装过程
  6. python float 精度_浅谈Python里面小数点精度的控制
  7. Linux多进程拷贝文件
  8. 网站排障的一些小命令
  9. python俄罗斯方块代码34行_Python:游戏:300行代码实现俄罗斯方块
  10. MATLAB绘图总结
  11. 非计软专业的学生也能看懂的面向对象编程(《面向对象编程是怎样工作的》平野章/著 读书笔记)
  12. 夜深人静了,我们来学学分布式锁
  13. CentOS7 使用光盘镜像作为yum源
  14. Kubernetes(k8s) 对象使用
  15. 计算机应用oas,办公自动化系统(OAS)
  16. 简易命令行界面的C/S聊天室
  17. 大数据的学习需要预先了解哪些基础知识?
  18. 思维模型 黄金圈法则
  19. 解决latex提示Warning: Font shape `TU/phv/m/n‘ undefined (Font)
  20. 阿里P8传授给小老弟的Java面试宝典,竟让让小弟也拿到了P8的offer,傻眼了

热门文章

  1. 4418 选元素(递推)
  2. 一“站”成名 | 盘点全球域名收购经典案例
  3. 骨科医疗器械行业产业链及技术水平趋势、竞争格局、主要壁垒构成
  4. 项目实战:借鉴Android API源码(Splitter-split和TextView-setText),不惧NullPointerException(文末彩蛋)
  5. Oracle 19c OCP的1Z0-082-CHN、1Z0-083-CHN和1Z0-082、1Z0-083有什么不同
  6. 一周看点 | 腾讯回应“人员优化”;Go成大厂热门编程语言;京东开启裁员;万门大学疑似跑路...
  7. 计算机如何计算对数函数
  8. 百趣代谢组学文献解读-膜脂代谢对桃果实冷藏过程中冷害的影响
  9. 100元人民币兑换成1元、2元、5元人名币的所有兑换方案
  10. Linux倒序赋值用molloc函数,请教一个C语言函数malloc的问题