CTC Loss (一)

论文:https://mediatum.ub.tum.de/doc/1292048/file.pdf

在文本识别模型CRNN中,一张包含单行文本的图片输入模型经过CNN、LSTM后输出m\times T大小的feature map, 假设T=25表示时间序列长度,m=26代表需要识别的字符集的大小(假设只识别小写英文字母),对每一个时间步t(1\leqslant t\leqslant T)接softmax后就得到识别结果的概率分布,对每一个时间步t满足\small \sum_{k=1}^{26}y_{k}^{t}=1,但是在与label进行loss计算时需要先将图片中的每一个字符与label对齐,这就需要对单个字进行位置和语义标注,非常麻烦。而且由于字体样式和大小的关系,每列输出并不一定能和每个字符一一对应。ctc loss是一种专门针对这种场景不需要对齐的loss计算方法。接下来介绍ctc loss的具体计算方法

  • 空白blank

L=\left \{ a,b,c,...,x,y,z \right \}表示预先定义的模型待识别字符集,因为输入图片中有的位置没有文字,引入空白blank字符,下文以 - 表示blank,LSTM的输出变成(m+1)\times T

  • \(\beta\)变换

定义\(\beta\)变换,LSTM输出首先经过decode,然后经过​​​​​​​\(\beta\)变换得到最终结果。​​​​​​​​​​​​​​\(\beta\)变换包括删除连续重复字符以及blank。例如,当T=12时,下列四个输出\pi _{1} - \pi _{4}经过​​​​​​​​​​​​​​\(\beta\)变换都变成state。

\beta (\pi _{1})=\beta (--stta-t---e)=state

\beta (\pi _{2})=\beta (sst-aaa-tee-)=state

\beta (\pi _{3})=\beta (--sttaa-tee-)=state

\beta (\pi _{4})=\beta (sst-aa-t---e)=state

给定输入x,模型输出为l的概率为

 \pi \in \beta ^{-1}(l)表示所有经过\beta变换后是l的路径\pi

其中,对于任意一条路径\pi

注意这里y_{\pi _{t}}^{t}中的\pi _{t},下标t表示路径\pi中的每一个时刻。而上面\pi _{1}-\pi _{4}的下标表示不同的路径

ctc的训练目标是通过梯度\frac{\partial p(l|x)}{\partial w}调整模型权重w,使得p(l|x)最大

在实际训练过程中,LSTM的输出特征图T的大小少为几十多则几百,如果遍历每一条路径,复杂度是指数级的,假如识别的是汉字,字符集长度m为几千,序列长度T上百,那要遍历m^{T}种选择,速度太慢。实际CTC借用了HMM的"前向 - 后向"(forward - backward)算法来计算p(l|x),具体过程如下

首先定义路径l^{'}为在路径l的头尾和每两个字符间插入blank

显然|l^{'}|=2|l|+1

定义所有经\beta变换后结果是l且在t时刻结果为l_{k}的路径集和为\left \{ \pi |\pi \in \beta ^{-1}(l),\pi _{t}=l_{k} \right \},求导

上式中第二项与y_{k}^{t}无关,因此

\frac{\partial p(l| x)}{\partial y_{k}^{t}}就是恰好与概率y_{k}^{t}相关的路径,即t时刻都经过l_{k}(\pi _{t}=l_{k})

上述的\pi _{1},\pi _{2},\pi _{3},\pi _{4}t=6时都经过\pi _{6}=a(此处下标代表路径\pit时刻的字符),所有类似于\pi _{1},\pi _{2},\pi _{3},\pi _{4}经过\beta变换后结果是l=state且在\pi _{6}=a的路径集和表示为\left \{ \pi |\pi \in \beta ^{-1}(l),\pi _{6}=a \right \}

如图,蓝色路径和红色路径分别为上述的\pi _{1}\pi _{2}\pi _{1}\pi _{2}可以表示为

\pi _{3}\pi _{4}可以表示为

 则

推广一下,所有经过 \beta变换结果为l\pi _{6}=a的路径\left \{ \pi |\pi \in \beta ^{-1}(l),\pi _{6}=a \right \}可以写成如下形式

进一步推广, 所有经过 \beta变换结果为l\pi _{t}=l_{k}的路径\left \{ \pi |\pi \in \beta ^{-1}(l),\pi _{t}=l_{k} \right \}可以写成如下形式

定义前向递推概率和forward=\alpha _{t}(s)

其中\pi \in \beta (\pi _{1:t})=l_{1:s} 表示路径\pi的前t个字符经过\beta变换变成l的前s个字符,\alpha _{t}(s)代表了t时刻经过l_{s}的所有路径的1\sim t的概率和,即前向递推概率和。

t=1时,路径只能从blankl_{1}开始,所以\alpha _{t}(s)有如下性质:

同理,定义后向递推概率和backward=\beta _{t}(s) 

 其中\pi \in \beta (\pi _{t:T})=l_{s:|l|}表示后T-t+1个字符经过\beta变换为l_{s:|l|}后半段子路径,\beta _{t}(s)表示t时刻经过l_{s}的所有路径的t\sim T的概率和,即后向递推概率和。

t=T时,路径只能以blankl_{|l^{'}|}结束,所以\beta _{t}(s)有如下性质:

计算递推loss

 forwardbackward相乘有

 当计算loss对ctc输入即LSTM输出中的某个值y_{k}^{t}的梯度时,只需考虑所有经过y_{k}^{t}的路径,因此可以得到

梯度如下 

接下来只需计算出\alpha _{t}(l_{k})\beta _{t}(l_{k})即可

前面我们给出了\alpha _{t}(s)的初始条件,即t=1时,路径只能从blankl_{1}开始。

  • t时刻字符sblank时,\alpha _{t}(s)可以由当前blank字符\alpha _{t-1}(s)或前一个非空白字符\alpha _{t-1}(s-1)得到。
  • l_{s}^{'}=l_{s-2}^{'}即当前字符s不是blank且和前一个字符相同时,\alpha _{t}(s)可以由当前字符\alpha _{t-1}(s)或前一个blank字符\alpha _{t-1}(s-1)得到,如下图所示

  • t时刻字符s不是blankl_{s}^{'}\neq l_{s-2}^{'}时,\alpha _{t}(s)可以由当前字符\alpha _{t-1}(s)、前一个blank字符\alpha _{t-1}(s-1)、前一个非空白字符\alpha _{t-1}(s-2)得到,如下图所示

由此可以得到递推公式

 根据初始条件和递推公式,便可以用动态规划计算出\alpha _{t}(s),代码如下

import numpy as np


def alpha_vanilla(y, labels):  # labels是插入blank后的
    T, V = y.shape  # T: time step, V: probs
    L = len(labels)  # label length
    alpha = np.zeros([T, L])

    # init
    alpha[0, 0] = y[0, labels[0]]
    alpha[0, 1] = y[0, labels[1]]

    for t in range(1, T):
        for i in range(L):
            s = labels[i]

            a = alpha[t - 1, i]
            if i - 1 >= 0:
                a += alpha[t - 1, i - 1]
            if i - 2 >= 0 and s != 0 and s != labels[i - 2]:
                a += alpha[t - 1, i - 2]

            alpha[t, i] = a * y[t, s]

    return alpha

同理可得后向递推公式 

def beta_vanilla(y, labels):
    T, V = y.shape
    L = len(labels)
    beta = np.zeros([T, L])

    # init
    beta[-1, -1] = y[-1, labels[-1]]
    beta[-1, -2] = y[-1, labels[-2]]

    for t in range(T - 2, -1, -1):
        for i in range(L):
            s = labels[i]

            a = beta[t + 1, i]
            if i + 1 < L:
                a += beta[t + 1, i + 1]
            if i + 2 < L and s != 0 and s != labels[i + 2]:
                a += beta[t + 1, i + 2]

            beta[t, i] = a * y[t, s]

    return beta

计算梯度

求导中,分子第一项是因为\alpha (k)\beta (k)中分别包含一个y_{k}^{t}项,其它项均为与y_{k}^{t}无关的常数。

另外,l中可能包含多个k字符,因为计算的梯度要进行累加。例如l=statey_{k}^{t}=y_{k=t}^{t=20},即求LSTM输出中timestep=20处的t字符的梯度,这里的t可能通过\beta变换成state中的第一个t也可能变换成第二个t。因此,最终的梯度计算结果为

其中,lab(l,k)=\left \{ s:l_{s}=k \right \} 

一般我们优化似然函数的对数,梯度如下

其中,p(l|x)=\alpha _{T}(|l^{'}|)+\alpha _{T}(|l^{'}|-1)可直接求得

梯度计算代码如下

def gradient(y, labels):
    T, V = y.shape

    alpha = alpha_vanilla(y, labels)
    beta = beta_vanilla(y, labels)
    p = alpha[-1, -1] + alpha[-1, -2]

    grad = np.zeros([T, V])
    for t in range(T):
        for s in range(V):
            lab = [i for i, c in enumerate(labels) if c == s]
            for i in lab:
                grad[t, s] += alpha[t, i] * beta[t, i]
            grad[t, s] /= y[t, s] ** 2

    grad /= p
    return grad

参考

一文读懂CRNN+CTC文字识别 - 知乎

【Learning Notes】CTC 原理及实现_MoussaTintin的博客-CSDN博客

Sequence Modeling with CTC

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

00000cj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值