原文地址:https://blog.csdn.net/dcrmg/article/details/80189819
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