当前位置:首页>python>【Python语音识别系列】语音识别评价:词错率(WER)与字错率(CER)的计算(案例+源码)

【Python语音识别系列】语音识别评价:词错率(WER)与字错率(CER)的计算(案例+源码)

  • 2026-09-08 23:56:04
【Python语音识别系列】语音识别评价:词错率(WER)与字错率(CER)的计算(案例+源码)

这是我的第470篇原创文章。

写在前面

『数据杂坛』以Python语言为核心,垂直于数据科学领域,专注于(可戳👉)Python程序开发|数据采集|数据分析|数据可视化|特征工程|机器学习|时序数据|深度学习|人工智能等技术栈交流学习,涵盖数据挖掘、计算机视觉、自然语言处理等应用领域。(文末有惊喜福利)

一、引言

词错率(Word Error Rate, WER)是一项用于评价ASR性能的重要指标,用来评价预测文本与标准文本之间错误率,因此词错率最大的特点是越小越好。像英语、阿拉伯语语音转文本或语音识别任务中研究者常用WER衡量ASR效果好坏。

因为英文语句中句子的最小单位是单词,而中文语句中的最小单位是汉字,因此在中文语音转文本任务或中文语音识别任务中使用字错率(Character Error Rate, CER)来衡量中文ASR效果好坏。

两者计算方式相同,为行文统一,下文统一使用WER表示该性能。

它们的计算公式是:

假设有一个参考例句Ref和一段ASR系统转写语音后生成的预测文本Hyp。带入上面公式,S表示将Hyp转化为Ref时发生的替换数量,D表示将Hyp转化为Ref时发生的删除数量,I代表将Hypo转化为Ref时发生的插入数量,N代表Ref句子中总的字数或者英文单词数。C代表Hyp句子中识别正确的字数。即原参考句子总字数N = S+ D + C。

再说一句,根据维基百科里面的说法,N就是原样例文本总的字数。

二、实现过程

函数:

def levenshtein_distance(hypothesis: list, reference: list):    """编辑距离    计算两个序列的levenshtein distance,可用于计算 WER/CER    词错率(Word Error Rate, WER)是一项用于评价ASR性能的重要指标,用来评价预测文本与标准文本之间错误率,因此词错率最大的特点是越小越好。    像英语、阿拉伯语语音转文本或语音识别任务中研究者常用WER衡量ASR效果好坏。    因为英文语句中句子的最小单位是单词,而中文语句中的最小单位是汉字,    因此在中文语音转文本任务或中文语音识别任务中使用字错率(Character Error Rate, CER)来衡量中文ASR效果好坏。    两者计算方式相同,为行文统一,下文统一使用WER表示该性能。    WER = (S+D+I)/N = (S+D+I)/(S+D+C)    参考资料:        https://www.cuelogic.com/blog/the-levenshtein-algorithm        https://martin-thoma.com/word-error-rate-calculation/    N: number         真实序列的字数    C: correct        预测序列识别正确的字数    W: wrong          预测序列识别错误的字数    I: insert         相较于真实序列,预测序列错误插入的数量    D: delete         相较于真实序列,预测序列错误删除的数量    S: substitution   相较于真实序列,预测序列错误替换的数量    :param hypothesis: 预测序列    :param reference: 真实序列    :return: 1: 错误操作,所需要的 S,D,I 操作的次数;             2: ref 与 hyp 的所有对齐下标             3: 返回N、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-substitution    ops_matrix = np.zeros((len_hyp + 1, len_ref + 1), dtype=np.int8)    for i in range(len_hyp + 1):        cost_matrix[i][0] = i    for j in range(len_ref + 1):        cost_matrix[0][j] = j    # 生成 cost 矩阵和 operation矩阵,i:外层hyp,j:内层ref    for 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] + 1                insertion = cost_matrix[i-1][j] + 1                deletion = 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) + 1                cost_matrix[i][j] = min_val                ops_matrix[i][j] = operation_idx    match_idx = []  # 保存 hyp与ref 中所有对齐的元素下标    i = len_hyp    j = len_ref    nb_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:     # correct            if i-1 >= 0 and j-1 >= 0:                match_idx.append((j-1, i-1))                nb_map['C'] += 1            # 出边界后,这里仍然使用,应为第一行与第一列必然是全零的            i -= 1            j -= 1        # elif ops_matrix[i_idx][j_idx] == 1:   # insert        elif ops_matrix[i_idx][j_idx] == 2:   # insert            i -= 1            nb_map['I'] += 1        # elif ops_matrix[i_idx][j_idx] == 2:   # delete        elif ops_matrix[i_idx][j_idx] == 3:   # delete            j -= 1            nb_map['D'] += 1        # elif ops_matrix[i_idx][j_idx] == 3:   # substitute        elif ops_matrix[i_idx][j_idx] == 1:   # substitute            i -= 1            j -= 1            nb_map['S'] += 1        # 出边界处理        if i < 0 and j >= 0:            nb_map['D'] += 1        elif j < 0 and i >= 0:            nb_map['I'] += 1    match_idx.reverse()    wrong_cnt = cost_matrix[len_hyp][len_ref]    nb_map["W"] = wrong_cnt    try:        wer = (nb_map["S"] + nb_map["D"] + nb_map["I"])/(nb_map["S"] + nb_map["D"] + nb_map["C"])    except:        wer = float('inf')    # 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_map, wer

举例1:Ref: 你吃了吗 Hyp: 你吃了么

标准文本为“你吃了吗”,转写结果为“你吃了么”,上例发生了一次错误替换,Hyp将“吗”替换成了么,即S=1,D=0, I =0, 参考文本字数N=4, 因此本次转写结果WER= 1/4 = 25%.

代码:

wrong_cnt, match_idx, nb_map, wer = levenshtein_distance(list("你吃了么"), list("你吃了吗"))print(wrong_cnt, match_idx, nb_map, wer)

结果:

举例2:Ref: 今天天气很好 Hyp: 今天天气很好啊

举例2中,Hyp文本相比标准文本错误插入了一个“啊”,即S=0,D=0,I =1,N=6,因此字错率WER= 1/6 = 16.7%。

代码:

wrong_cnt, match_idx, nb_map, wer = levenshtein_distance(list("今天天气很好啊"), list("今天天气很好"))print(wrong_cnt, match_idx, nb_map, wer)

结果:

三、小结

句错率,SER,SER表述为句子中如果有一个词识别错误,那么这个句子被认为识别错误,句子识别错误的的个数,除以总的句子个数即为SER。

写在后面

作者简介:

读研期间发表6篇SCI数据算法相关论文,目前在某研究院从事数据算法相关研究工作,结合自身科研实践经历不定期持续分享关于Python、数据分析、特征工程、机器学习、深度学习、人工智能系列基础知识与案例。

致力于只做原创,以最简单的方式理解和学习,关注我一起交流成长。

1、关注下方公众号,点击“领资料”即可免费领取电子资料书籍。

2、文章底部点击喜欢作者即可联系作者获取相关数据集和源码。

3、数据算法方向论文指导或就业指导,点击“联系我”添加作者微信直接交流。

4、有商务合作相关意向,点击“联系我”添加作者微信直接交流。

免费电子书籍,带你入门人工智能:

最新文章

随机文章