目标:基于图像的序列识别问题
方法:第一次提出一个端到端可训练的网络架构,包括特征提取、序列建模、转录三个部分
四个特点:
①端到端可训练,现有方法都是分开训练和调整(微调)
②能处理任意长度的序列,无需字符级别的分割
③不受限于预先定义的词典,没有词典也能有很好的结果
④模型小,参数量少,更有实际价值
现有的方法,主要分为三类:
①基于CNN的方法:第一种是先检测单个字符,然后对每个字符使用分类网络进行分类;第二种是将每个词作为一个类别,构建一个类别数量非常大(可能几万,几百万类)的分类网络,直接对输入图像进行分类
②基于RNN的方法:先对输入图像提取几何特征(如HOG特征等,用人工设计的算子),然后将提取到的特征作为RNN的输入进行分类
③其他:将文本图像和文字内容(字符串)嵌入一个公共空间,将识别问题转成一个检索问题,即在这个公共空间,文本图像的高维表示和哪个字符串的高维表示更接近
主要贡献:
①只需要序列级标注,无需字符级的标注
②从图像数据学习有效表征,无需手工设计特征提取器,无需前处理(比如二值化、分割、连通域(元素定位))
③能循环产生序列标签
④可以处理任意长度的输入
⑤更好的精度
⑥更少的参数
具体方法:
CNN部分主要包括卷积层和最大池化层 --> 关于卷积和最大池化操作参考
CNN的主要作用:有效提取图像特征
RNN部分主要包括两层双向LSTM --> 关于两层双向LSTM参考 (应该有一个基础知识部分)
RNN的主要作用:捕获上下文信息,使用上下文线索,比使用单个字符更好(歧义字符,反向传播,端到端训练,可以处理变长序列
Transcription(转录)层:使用被定义在连续时序分类( Connectionist Temporal Classifification (CTC) )上的条件概率,即找出相对于对于输入最可能的标签序列(字符串),使用CTC可以有效忽略位置信息;
转录有两种方式:有词典(输入图像上的内容一定是词典中的一个词)和无词典
损失函数是最大化条件概率,那么可以转成,最小化负对数似然,即:
p
(
l
∣
y
)
=
∑
π
:
B
(
π
)
=
l
p
(
π
∣
y
)
p(l|y) = sum_{π:B(π)=l}p(π|y)
p(l∣y)=π:B(π)=l∑p(π∣y)
p ( π ∣ y ) = ∏ t = 1 T y π t t p(π|y)=prod_{t=1}^Ty_{πt}^t p(π∣y)=t=1∏Tyπtt
o = − ∑ I i , l i ∈ Ω l o g ( p ( l i ∣ y i ) ) o=-sum_{I_{i},l_{i}inOmega}log(p(li|yi)) o=−Ii,li∈Ω∑log(p(li∣yi))
输入图像经过CNN得到特征映射一,这组特征映射输入RNN(包含多个按序排列的特征向量),得到特征映射二,由于网络设置,如上图所示,可能有几个特征向量对应同一个字,也可能一个特征向量对应多个字,这个时候映入了空白符"-",用于占位,方便对齐,在得到识别序列后,先移除空白符之间的重复字符,然后在移除空白符,得到最终识别结果;那么由于引入了空白符,依赖前述方法获得最终识别结果时,就有可能的情况可以得到正确结果(公式一),如果直接计算所有可能结果,那么复杂度将是指数级的,这个时候使用动态规划计算近似解,使用CTC损失函数。
对一个序列的所有路径求和可以被分解为一个迭代路径的求和,具体关于CTC的解释,可以参考(这个讲的比较明白)
[https://blog.csdn.net/luodongri/article/details/77005948]:
整个方法的大致流程为:输入固定大小的文本图像(eg:(320,1,32,160)(batch_size,input_channel,height,width)) --> CNN --> 高的维度被压缩为1的一组特征向量((320,512,1,40))–> RNN --> 一组特征向量((320,512,1,40))–> 维度变换((320,40,512)(W,B,C)),这里的W指预设的可识别的最大长度,每个位置用一个C维的向量来进行表征,对这个向量进行变换,将其映射到字典大小,即将其维度变为V,向量中每个元素表示其所在位置对应的字符是字典中每一个字符的概率值(这里也可不进行变换,令C==V),然后根据这个概率值,在训练的时候,计算损失函数,使用梯度反向传播算法进行参数更新,在正常使用时,根据贪心算法和beamsearch方法得到最后的结果。
网络模型代码大致如下(pytorch):
class CNN(nn.Module):
def __init__(self,input_channel,output_channel,kernel_list,stride_list,padding_list):
'''
输入参数均是list,其中每个元素对应每个层的参数,直接设置即可,可根据需要继续添加卷积层,简单示意代码
'''
self.conv0 = nn.Conv2d(input_channel[0],output_channel[0],kernel_size=kernel_list[0], stride=stride_list[0], padding=padding_list[0], bias=False)
self.relu = nn.ReLU(inplace=True)
self.bn0 = nn.BatchNorm2d(output_channel[0])
self.maxpool1 = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
def forward(self,input):
feature = self.conv0(input)
feature = self.relu(feature)
feature = self.bn0(feature)
feature = self.maxpool1(feature)
return feature
class BidirectionalLSTM(nn.Module):
# Inputs hidden units Out
def __init__(self, nIn, nHidden, nOut):
super(BidirectionalLSTM, self).__init__()
self.rnn = nn.LSTM(nIn, nHidden, bidirectional=True)
self.embedding = nn.Linear(nHidden * 2, nOut)
def forward(self, input):
recurrent, _ = self.rnn(input)
T, b, h = recurrent.size()
t_rec = recurrent.view(T * b, h)
output = self.embedding(t_rec) # [T * b, nOut]
output = output.view(T, b, -1)
return output
class CRNN(nn.Module):
def __init__(self):
super(CRNN, self).__init__()
self.cnn = CNN()
self.rnn = nn.Sequential(BidirectionalLSTM(512, nh, nh),BidirectionalLSTM(nh, nh, nclass))
def forward(self, input):
# conv features
conv = self.cnn(input)
#b, c, h, w = conv.size()
#print(conv.size())
assert h == 1, "the height of conv must be 1"
conv = conv.squeeze(2) # b *512 * width
conv = conv.permute(2, 0, 1) # [w, b, c]
recu = self.rnn(conv)
output = F.log_softmax(, dim=2)
return output
'''CTC损失函数在pytorch中也有API'''
loss = torch.nn.CTCLoss()
作者在实验中使用了合成数据集,在4个数据集上做了实验,同时也进行了乐谱识别,证明提出方法有很好的泛化性能,使用的adadelta作为优化器;CRNN是非常经典的方法,基于已有的技术,进行融合创新。



