编辑距离与字符错误率CER

在语音识别场景中,字符错误率(Character Error Rate,CER)是衡量语音识别效果的一个重要指标。下文将介绍CER的原理,并且给出python实现的代码。

1 编辑距离

说到CER,不得不提的是编辑距离(Edit Distance),它是一个用来衡量两个序列的相似度指标。

假设有两个字符串(a和b),编辑距离是指把字符串a修改成b(或者把b改成a)需要的最少编辑次数。编辑的操作只能有三种:

  • 插入(Insertion)
  • 删除(Deletion)
  • 替换(Substitution)

比如,把cat修改成cafe可以这样编辑:

  1. cat --> caf(替换)
  2. caf --> cafe(插入)

也可以这样编辑:

  1. cat --> cate(插入)
  2. cate --> cafe(替换)
1.1 原理

将两个字符串a和b的编辑距离表示为 l e v a , b lev_{a,b} leva,b。之所以用 l e v lev lev,是因为编辑距离的作者叫Levenshtein,所以编辑距离也叫Levenshtein Distance。

l e v a , b ( i , j ) lev_{a,b}(i,j) leva,b(i,j)表示字符串a的前i个字符与b的前j个字符间的编辑距离,比如, l e v c a t , c a f e ( 2 , 3 ) lev_{cat,cafe}(2,3) levcat,cafe(2,3)就表示ca与caf之间编辑距离。

Levenshtein Distance的思路就是,要计算字符串之间的距离,先计算子串间的距离,要计算子串间的距离,先计算子子串间的距离,如此分解下去。用数学语言表示,就是下面这个公式

l e v a , b ( i , j ) = { m a x ( i , j )  if  m i n ( i , j ) = 0 m i n { l e v a , b ( i − 1 , j ) + 1 l e v a , b ( i , j − 1 ) + 1 l e v a , b ( i − 1 , j − 1 ) + s i g n ( a i , b j )  if  m i n ( i , j ) ≠ 0 lev_{a,b}(i,j)=\begin{cases} max(i,j) & \text{ if } min(i,j)=0 \\ min\begin{cases} lev_{a,b}(i-1,j)+1 \\ lev_{a,b}(i,j-1)+1 \\ lev_{a,b}(i-1,j-1)+sign(a_i,b_j) \end{cases} & \text{ if } min(i,j)\neq0 \end{cases} leva,b(i,j)=max(i,j)minleva,b(i1,j)+1leva,b(i,j1)+1leva,b(i1,j1)+sign(ai,bj) if min(i,j)=0 if min(i,j)=0
其中,
s i g n ( a i , b j ) = { 0  if  a i = b j 1  if  a i ≠ b j sign(a_i,b_j)=\begin{cases} 0 & \text { if } a_i = b_j \\ 1 & \text { if } a_i \neq b_j \end{cases} sign(ai,bj)={01 if ai=bj if ai=bj

  • m i n ( i , j ) = 0 min(i,j)=0 min(i,j)=0说明有一个子串是空的,什么都没有,要修改成另外一个子串,光插入字符就行,所以需要的最少编辑次数就是 m a x ( i , j ) max(i,j) max(i,j)。比如 l e v c a t , c a f e ( 2 , 0 ) = l e v c a , 空 白 = 2 lev_{cat,cafe}(2,0)=lev_{ca,空白}=2 levcat,cafe(2,0)=levca,=2

  • m i n ( i , j ) ≠ 0 min(i,j) \neq 0 min(i,j)=0的时候, l e v a , b ( i , j ) lev_{a,b}(i,j) leva,b(i,j)等于以下三种情况的最小值:

    • l e v a , b ( i − 1 , j ) + 1 lev_{a,b}(i-1,j)+1 leva,b(i1,j)+1

    • l e v a , b ( i , j − 1 ) + 1 lev_{a,b}(i,j-1)+1 leva,b(i,j1)+1

    • l e v a , b ( i − 1 , j − 1 ) + s i g n ( a i , b j ) lev_{a,b}(i-1,j-1)+sign(a_i,b_j) leva,b(i1,j1)+sign(ai,bj)

      以cat和cafe为例, l e v c a t , c a f e lev_{cat, cafe} levcat,cafe等于以下三种情况的最小值:

    1. l e v c a , c a f e + 1 lev_{ca, cafe}+1 levca,cafe+1,cat的子串ca与cafe求编辑距离再加1,加1是因为 l e v c a , c a t = 1 lev_{ca, cat}=1 levca,cat=1
    2. l e v c a t , c a f + 1 lev_{cat, caf}+1 levcat,caf+1,cafe的子串caf与cat求编辑距离再加1,加1是因为 l e v c a f , c a f e = 1 lev_{caf, cafe}=1 levcaf,cafe=1
    3. l e v c a , c a f + 1 lev_{ca, caf}+1 levca,caf+1,cat和cafe各自的子串ca和caf求编辑距离再加1,加1是因为(ca)t替换成(caf)e需要一次操作。

根据上面的公式可知,当 i = ∣ a ∣ , j = ∣ b ∣ i=|a|,j=|b| i=a,j=b时, l e v a , b = l e v a , b ( i , j ) lev_{a,b}=lev_{a,b}(i,j) leva,b=leva,b(i,j),其中 ∣ a ∣ |a| a ∣ b ∣ |b| b分别表示a和b的长度。

1.2 演算过程

编辑距离的计算过程可以用一个矩阵来记录。

以cat和cafe为例,建立一个矩阵,用来记录计算的中间结果:

cafe
01234
c1 a 1 , 1 a_{1,1} a1,1 a 1 , 2 a_{1,2} a1,2 a 1 , 3 a_{1,3} a1,3 a 1 , 4 a_{1,4} a1,4
a2 a 2 , 1 a_{2,1} a2,1 a 2 , 2 a_{2,2} a2,2 a 2 , 3 a_{2,3} a2,3 a 2 , 4 a_{2,4} a2,4
t3 a 3 , 1 a_{3,1} a3,1 a 3 , 2 a_{3,2} a3,2 a 3 , 3 a_{3,3} a3,3 a 3 , 4 a_{3,4} a3,4

第二行的01234表示从什么都没有到c、ca、caf、cafe分别需要的最少编辑次数;同样地,第二列的0123表示从什么都没有到c、ca、cat分别需要的最少编辑次数。

a 1 , 1 = m i n ( a 1 , 0 + 1 , a 0 , 1 + 1 , a 0 , 0 ) a_{1,1}=min(a_{1,0}+1,a_{0,1}+1,a_{0,0}) a1,1=min(a1,0+1,a0,1+1,a0,0)

a 1 , 2 = m i n ( a 1 , 1 + 1 , a 0 , 2 + 1 , a 0 , 1 + 1 ) a_{1,2}=min(a_{1,1}+1,a_{0,2}+1,a_{0,1}+1) a1,2=min(a1,1+1,a0,2+1,a0,1+1)

a 2 , 1 = m i n ( a 1 , 1 + 1 , a 2 , 0 + 1 , a 1 , 0 + 1 ) a_{2,1}=min(a_{1,1}+1,a_{2,0}+1,a_{1,0}+1) a2,1=min(a1,1+1,a2,0+1,a1,0+1)

……

最终的矩阵如下:

cafe
01234
c10123
a21012
t32112

可以找出规律, 每 个 格 子 的 值 = m i n ( 上 方 格 子 的 值 + 1 , 左 方 格 子 的 值 + 1 , 左 上 方 格 子 + ( 0 或 1 ) ) 每个格子的值=min(上方格子的值+1,左方格子的值+1,左上方格子+(0或1)) =min(+1+1+(01)),0还是1取决于格子左边的字母是否等于格子上面的字母。

1.3 代码实现

找到编辑距离的计算规律后,代码实现就不难了。python代码如下:

def edit_distance(str1, str2):
    """计算两个字符串之间的编辑距离。
    Args:
        str1: 字符串1。
        str2: 字符串2。
    Returns:
        dist: 编辑距离。
    """
    matrix = [[i + j for j in range(len(str2) + 1)] for i in range(len(str1) + 1)]
    for i in range(1, len(str1) + 1):
        for j in range(1, len(str2) + 1):
            if str1[i - 1] == str2[j - 1]:
                d = 0
            else:
                d = 1
            matrix[i][j] = min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + d)
    dist = matrix[len(str1)][len(str2)]
    return dist

2 字符错误率

弄清楚编辑距离的原理后,理解字符错误率(CER)就很简单了。假设我说了一句话a,被语音识别系统识别成b,那么 c e r = l e v a , b ÷ ∣ a ∣ cer=lev_{a,b}\div|a| cer=leva,b÷a,就这么简单,所以代码也很简单:

def get_cer(src, trg):
    """把源字符串src修改成目标字符串trg的字符错误率。
    Args:
        src: 源字符串。
        trg: 目标字符串。
    Returns:
        cer: 字符错误率。
    """
    dist = edit_distance(src, trg)
    cer = dist / len(trg)
    return cer

最后,祝我的大宝雨水快乐!

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值