端到端文字識別CRNN論文解讀

-牧野-發表於2018-05-04

CRNN 論文: An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition

CRNN不定長中文識別專案下載地址: https://download.csdn.net/download/dcrmg/10248818


CRNN是一種卷積迴圈神經網路結構,用於解決基於影象的序列識別問題,特別是場景文字識別問題。CRNN網路結構:



網路結構包含三部分,從下到上依次為:

1. 卷積層,作用是從輸入影象中提取特徵序列;
2. 迴圈層,作用是預測從卷積層獲取的特徵序列的標籤(真實值)分佈;
3. 轉錄層,作用是把從迴圈層獲取的標籤分佈通過去重整合等操作轉換成最終的識別結果;


卷積層


CRNN卷積層由標準的CNN模型中的卷積層和最大池化層組成,自動提取出輸入影象的特徵序列。
與普通CNN網路不同的是,CRNN在訓練之前,先把輸入影象縮放到相同高度(影象寬度維持原樣),論文中使用的高度值是32。

提取的特徵序列中的向量是從特徵圖上從左到右按照順序生成的,每個特徵向量表示了影象上一定寬度上的特徵,論文中使用的這個寬度是1,就是單個畫素。


特別強調序列的順序是因為在之後的迴圈層中,先後順序是LSTM訓練中的一個重要參考量。


迴圈層


迴圈層由一個雙向LSTM迴圈神經網路構成,預測特徵序列中的每一個特徵向量的標籤分佈(真實結果的概率列表),迴圈層的誤差被反向傳播,最後會轉換成特徵序列,再把特徵序列反饋到卷積層,這個轉換操作由論文中定義的“Map-to-Sequence”自定義網路層完成,作為卷積層和迴圈層之間連線的橋樑。


轉錄層


轉錄是將LSTM網路預測的特徵序列的所有可能的結果進行整合,轉換為最終結果的過程。論文中實在雙向LSTM網路的最後連線上一個CTC模型,做到端對端的識別。

CTC模型(Connectionist temporal classification) 聯接時間分類,CTC可以執行端到端的訓練,不要求訓練資料對齊和一一標註,直接輸出不定長的序列結果。

CTC一般連線在RNN網路的最後一層用於序列學習和訓練。對於一段長度為T的序列來說,每個樣本點t(t遠大於T)在RNN網路的最後一層都會輸出一個softmax向量,表示該樣本點的預測概率,所有樣本點的這些概率傳輸給CTC模型後,輸出最可能的標籤,再經過去除空格(blank)和去重操作,就可以得到最終的序列標籤。

網路結構簡圖:


網路結構Keras定義:

def get_model(height,nclass):
    
    input = Input(shape=(height,None,1),name='the_input')
    m = Conv2D(64,kernel_size=(3,3),activation='relu',padding='same',name='conv1')(input)
    m = MaxPooling2D(pool_size=(2,2),strides=(2,2),name='pool1')(m)
    m = Conv2D(128,kernel_size=(3,3),activation='relu',padding='same',name='conv2')(m)
    m = MaxPooling2D(pool_size=(2,2),strides=(2,2),name='pool2')(m)
    m = Conv2D(256,kernel_size=(3,3),activation='relu',padding='same',name='conv3')(m)
    m = Conv2D(256,kernel_size=(3,3),activation='relu',padding='same',name='conv4')(m)

    m = ZeroPadding2D(padding=(0,1))(m)
    m = MaxPooling2D(pool_size=(2,2),strides=(2,1),padding='valid',name='pool3')(m)

    m = Conv2D(512,kernel_size=(3,3),activation='relu',padding='same',name='conv5')(m)
    m = BatchNormalization(axis=1)(m)
    m = Conv2D(512,kernel_size=(3,3),activation='relu',padding='same',name='conv6')(m)
    m = BatchNormalization(axis=1)(m)
    m = ZeroPadding2D(padding=(0,1))(m)
    m = MaxPooling2D(pool_size=(2,2),strides=(2,1),padding='valid',name='pool4')(m)
    m = Conv2D(512,kernel_size=(2,2),activation='relu',padding='valid',name='conv7')(m)

    m = Permute((2,1,3),name='permute')(m)
    m = TimeDistributed(Flatten(),name='timedistrib')(m)

    m = Bidirectional(GRU(rnnunit,return_sequences=True),name='blstm1')(m)
    m = Dense(rnnunit,name='blstm1_out',activation='linear')(m)
    m = Bidirectional(GRU(rnnunit,return_sequences=True),name='blstm2')(m)
    y_pred = Dense(nclass,name='blstm2_out',activation='softmax')(m)

    basemodel = Model(inputs=input,outputs=y_pred)

    labels = Input(name='the_labels', shape=[None,], dtype='float32')
    input_length = Input(name='input_length', shape=[1], dtype='int64')
    label_length = Input(name='label_length', shape=[1], dtype='int64')
    loss_out = Lambda(ctc_lambda_func, output_shape=(1,), name='ctc')([y_pred, labels, input_length, label_length])
    model = Model(inputs=[input, labels, input_length, label_length], outputs=[loss_out])
    # sgd = SGD(lr=0.001, decay=1e-6, momentum=0.9, nesterov=True, clipnorm=5)

    sgd = SGD(lr=0.0003, decay=1e-6, momentum=0.6, nesterov=True, clipnorm=5)
    #model.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer='adadelta')
    model.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer=sgd)
    model.summary()

    return model,basemodel

相關文章