【OCR技术系列之七】端到端不定长文字识别CRNN算法详解

在之前的OCR任务中,识别过程分为两步:单字切割和分类任务。咱们通常都会讲一连串文字的文本文件先利用投影法切割出单个字体,在送入CNN里进行文字分类。可是此法已经有点过期了,如今更流行的是基于深度学习的端到端的文字识别,即咱们不须要显式加入文字切割这个环节,而是将文字识别转化为序列学习问题,虽然输入的图像尺度不一样,文本长度不一样,可是通过DCNN和RNN后,在输出阶段通过必定的翻译后,就能够对整个文本图像进行识别,也就是说,文字的切割也被融入到深度学习中去了。算法

现今基于深度学习的端到端OCR技术有两大主流技术:CRNN OCR和attention OCR。其实这两大方法主要区别在于最后的输出层(翻译层),即怎么将网络学习到的序列特征信息转化为最终的识别结果。这两大主流技术在其特征学习阶段都采用了CNN+RNN的网络结构,CRNN OCR在对齐时采起的方式是CTC算法,而attention OCR采起的方式则是attention机制。本文将介绍应用更为普遍的CRNN算法。网络

网络结构包含三部分,从下到上依次为:架构

  1. 卷积层,使用CNN,做用是从输入图像中提取特征序列;
  2. 循环层,使用RNN,做用是预测从卷积层获取的特征序列的标签(真实值)分布;
  3. 转录层,使用CTC,做用是把从循环层获取的标签分布经过去重整合等操做转换成最终的识别结果;

端到端OCR的难点在哪儿呢?在于怎么处理不定长序列对齐问题!CRNN OCR实际上是借用了语音识别中解决不定长语音序列的思路。与语音识别问题相似,OCR可建模为时序依赖的词汇或者短语识别问题。基于联结时序分类(Connectionist Temporal Classification, CTC)训练RNN的算法,在语音识别领域显著超过传统语音识别算法。一些学者尝试把CTC损失函数借鉴到OCR识别中,CRNN 就是其中表明性算法。CRNN算法输入100*32归一化高度的词条图像,基于7层CNN(广泛使用VGG16)提取特征图,把特征图按列切分(Map-to-Sequence),每一列的512维特征,输入到两层各256单元的双向LSTM进行分类。在训练过程当中,经过CTC损失函数的指导,实现字符位置与类标的近似软对齐。函数

CRNN借鉴了语音识别中的LSTM+CTC的建模方法,不一样点是输入进LSTM的特征,从语音领域的声学特征(MFCC等),替换为CNN网络提取的图像特征向量。CRNN算法最大的贡献,是把CNN作图像特征工程的潜力与LSTM作序列化识别的潜力,进行结合。它既提取了鲁棒特征,又经过序列识别避免了传统算法中难度极高的单字符切分与单字符识别,同时序列化识别也嵌入时序依赖(隐含利用语料)。在训练阶段,CRNN将训练图像统一缩放100×32(w × h);在测试阶段,针对字符拉伸致使识别率下降的问题,CRNN保持输入图像尺寸比例,可是图像高度仍是必须统一为32个像素,卷积特征图的尺寸动态决定LSTM时序长度。这里举个例子学习

如今输入有个图像,为了将特征输入到Recurrent Layers,作以下处理:测试

  • 首先会将图像缩放到 32×W×1 大小
  • 而后通过CNN后变为 1×(W/4)× 512
  • 接着针对LSTM,设置 T=(W/4) , D=512 ,便可将特征输入LSTM。
  • LSTM有256个隐藏节点,通过LSTM后变为长度为T × nclass的向量,再通过softmax处理,列向量每一个元素表明对应的字符预测几率,最后再将这个T的预测结果去冗余合并成一个完整识别结果便可。

CRNN中须要解决的问题是图像文本长度是不定长的,因此会存在一个对齐解码的问题,因此RNN须要一个额外的搭档来解决这个问题,这个搭档就是著名的CTC解码。
CRNN采起的架构是CNN+RNN+CTC,cnn提取图像像素特征,rnn提取图像时序特征,而ctc概括字符间的链接特性。字体

那么CTC有什么好处?因手写字符的随机性,人工能够标注字符出现的像素范围,可是太过麻烦,ctc能够告诉咱们哪些像素范围对应的字符:优化

咱们知道,CRNN中RNN层输出的一个不定长的序列,好比原始图像宽度为W,可能其通过CNN和RNN后输出的序列个数为S,此时咱们要将该序列翻译成最终的识别结果。RNN进行时序分类时,不可避免地会出现不少冗余信息,好比一个字母被连续识别两次,这就须要一套去冗余机制,可是简单地看到两个连续字母就去冗余的方法也有问题,好比cook,geek一类的词,因此CTC有一个blank机制来解决这个问题。这里举个例子来讲明。spa

如上图所示,咱们要识别这个手写体图像,标签为“ab”,通过CNN+RNN学习后输出序列向量长度为5,即t0~t4,此时咱们要将该序列翻译为最后的识别结果。咱们在翻译时遇到的第一个难题就是,5个序列怎么转化为对应的两个字母?重复的序列怎么解决?恰好位于字与字之间的空白的序列怎么映射?这些都是CTC须要解决的问题。翻译

咱们从肉眼能够看到,t0,t1,t2时刻都应映射为“a”,t3,t4时刻都应映射为“b”。若是咱们将连续重复的字符合并成一个输出的话,即“aaabb”将被合并成“ab”输出。可是这样子的合并机制是有问题的,好比咱们的标签图像时“aab”时,咱们的序列输出将可能会是“aaaaaaabb”,这样子咱们就没办法肯定该文本应被识别为“aab”仍是“ab”。CTC为了解决这种二义性,提出了插入blank机制,好比咱们以“-”符号表明blank,则若标签为“aaa-aaaabb”则将被映射为“aab”,而“aaaaaaabb”将被映射为“ab”。引入blank机制,咱们就能够很好地处理了重复字符的问题了。

但咱们还注意到,“aaa-aaaabb”能够映射为“aab”,一样地,“aa-aaaaabb”也能够映射为“aab”,也就是说,存在多个不一样的字符组合能够映射为“aab”,更总结地说,一个标签存在一条或多条的路径。好比下面“state”这个例子,也存在多条不一样路径映射为"state":

上面提到,RNN层输出的是序列中几率矩阵,那么\(p(\pi=--stta-t---e|x,S)=\prod_{t=1}^{T}y_{\pi_{t}}^{t}=(y_{-}^{1})\times(y_{-}^{2})\times(y_{s}^{3})\times(y_{t}^{4})\times(y_{t}^{5})\times(y_{a}^{6})\times(y_{-}^{7})\times(y_{t}^{8})\times(y_{-}^{9})\times(y_{-}^{10})\times(y_{-}^{11})\times(y_{e}^{12})\)

其中,\(y_{-}^{1}\)表示第一个序列输出“-”的几率,那么对于输出某条路径\(\pi\)的几率为各个序列几率的乘积。因此要获得一个标签能够有多个路径来得到,从直观上理解就是,咱们输出一张文本图像到网络中,咱们须要使得输出为标签L的几率最大化,因为路径之间是互斥的,对于标注序列,其条件几率为全部映射到它的路径几率之和:

其中\(\pi\in B^{-1}(l)\)的意思是,全部能够合并成l的全部路径集合。

这种经过映射B和全部候选路径几率之和的方式使得CTC不须要对原始的输入序列进行准确的切分,这使得RNN层输出的序列长度>label长度的任务翻译变得可能。CTC能够与任意的RNN模型,可是考虑到标注几率与整个输入串有关,而不是仅与前面小窗口范围的片断相关,所以双向的RNN/LSTM模型更为适合。

ctc会计算loss ,从而找到最可能的像素区域对应的字符。事实上,这里loss的计算本质是对几率的概括:

如上图,对于最简单的时序为2的(t0t1)的字符识别,可能的字符为“a”,“b”和“-”,颜色越深表明几率越高。咱们若是采起最大几率路径解码的方法,一看就是“--”的几率最大,真实字符为空即“”的几率为0.6*0.6=0.36。

可是咱们忽略了一点,真实字符为“a”的几率不仅是”aa” 即0.4*0.4 , 事实上,“aa”, “a-“和“-a”都是表明“a”,因此,输出“a”的几率为:

0.4*0.4 + 0.4 * 0.6 + 0.6*0.4 = 0.16+0.24+0.24 = 0.64

因此“a”的几率比空“”的几率高!能够看出,这个例子里最大几率路径和最大几率序列彻底不一样,因此CTC解码一般不适合采用最大几率路径的方法,而应该采用前缀搜索算法解码或者约束解码算法。

经过对几率的计算,就能够对以前的神经网络进行反向传播更新。相似普通的分类,CTC的损失函数O定义为负的最大似然,为了计算方便,对似然取对数。

\(O=-ln(\prod_{(x,z)\in S} p(l|x))=-\sum_{(x,z)\in S}lnp(l|x)\)

咱们的训练目标就是使得损失函数O优化得最小便可。

相关文章
相关标签/搜索