【飛槳開發者說】魏宏煒,福建省三明學院,本科三年級,研究方向爲目標檢測、OCR識別。php
賽題背景網絡
關於光學字符識別(Optical Character Recognition, 簡稱OCR),是指將圖像上的文字轉化爲計算機可編輯的文字內容,衆多的研究人員對相關的技術研究已久,也有很多成熟的OCR技術和產品產生,好比PaddleOCR。中文漢字識別是OCR的一個分支。由於漢語做爲咱們的母語,漢字主要在我國普遍使用,對漢字的種類、內涵、造字原理國內的掌握狀況較透徹,因此關於漢字識別的深刻研究主要集中在國內。框架
中文場景文字識別技術在人們的平常生活中受到普遍關注,具備豐富的應用場景,如:拍照翻譯、圖像檢索、場景理解等。然而,中文場景中的文字面臨着包括光照變化、低分辨率、字體以及排布多樣性、中文字符種類多等複雜狀況。如何解決上述問題成爲一項極具挑戰性的任務。ide
在本月中文場景文字識別賽中,筆者使用了飛槳開源深度學習框架,在百度學習與實訓社區AI Studio上完成了數據處理、模型搭建、模型訓練、模型預測等整個工做過程,拿到了6月份前十名,得到了800元京東卡。很是感謝AI Studio爲參賽選手提供的GPU在線訓練環境,以及豐厚GPU使用時長,對於在學習深度學習過程當中硬件條件不足的學生黨來講,提供了很是大的幫助。學習
下載安裝命令 ## CPU版本安裝命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle ## GPU版本安裝命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu
比賽連接:測試
https://aistudio.baidu.com/aistudio/competition/detail/20字體
項目連接:優化
https://aistudio.baidu.com/aistudio/projectdetail/1136775ui
賽題數據分析spa
要求實現對街拍圖片中的文字提取,而且提取出的文字序列仍是長短不一的。考慮卷積神經網絡在圖像特徵提取方面有很好的效果,輸出能夠採用CTC實現長度可變的預測。
1.圖片信息:
(a) 標註:魅派集成吊頂
(b) 標註:母嬰用品連鎖
2.訓練集標註文件:
h表示圖片高,w表示圖片寬,name爲圖片路徑,value爲圖片對應的標註(如:母嬰用品連鎖)。
3.要求的預測結果格式爲:
name爲測試集圖片名字,value爲預測出的結果(如:母嬰用品連鎖)
模型原理
模型採用CRNN-CTC結構(CNN+RNN+CTC):先用CNN網絡提取圖像特徵,轉化爲時間序列再傳入RNN網絡,最後輸出使用CTC層(不一樣樣本的標籤序列長度能夠不一致)。
結構圖:
1. CNN層搭建:
卷積層的份量是經過從標準CNN模型中提取卷積層和最大池化層來構造的(全鏈接層被移除)。該模塊用於從輸入圖像中提取序列特徵表示。在被輸入到網絡以前,全部的圖像都須要縮放到相同的高度。而後從卷積層份量產生的特徵映射中提取一系列特徵向量,這是遞歸層的輸入。
#卷積層的Paddle實現 paddle.fluid.layers.conv2d(input, num_filters, filter_size, stride=1, padding=0, dilation=1, groups=None, param_attr=None, bias_attr=None, use_cudnn=True, act=None, name=None, data_format="NCHW") #最大池化層的Paddle實現 paddle.fluid.layers.pool2d(input, pool_size=-1, pool_type='max', pool_stride=1, pool_padding=0, global_pooling=False, use_cudnn=True, ceil_mode=False, name=None, exclusive=True, data_format="NCHW") #全鏈接層的Paddle實現 paddle.fluid.layers.fc(input, size, num_flatten_dims=1, param_attr=None, bias_attr=None, act=None, name=None)
2.LSTM層(遞歸層):
(a)圖是傳統的LSTM結構:一個LSTM由一個單元模塊和三個門組成,即輸入門、輸出門和遺忘門。
(b)圖是論文中使用的結構:深層雙向LSTM的結構。將前向(從左到右)和後向(從右到左)LSTM相結合構成雙向LSTM。堆疊2個雙向LSTM構成深層雙向LSTM。
本次比賽使用代碼實現用的是雙層GRU單元:
paddle.fluid.layers.dynamic_gru(input, size, param_attr=None, bias_attr=None, is_reverse=False, gate_activation='sigmoid', candidate_activation='tanh', h_0=None, origin_mode=False)
飛槳也提供了LSTM的實現方法
paddle.fluid.layers.dynamic_lstm(input, size, h_0=None, c_0=None, param_attr=None, bias_attr=None, use_peepholes=True, is_reverse=False, gate_activation='sigmoid', cell_activation='tanh', candidate_activation='tanh', dtype='float32', name=None)
3.CTC層(轉錄層):
轉錄是將RNN所作的每幀預測轉換爲標籤序列的過程。從數學上講,轉錄是找到基於每幀預測的機率最高的標籤序列。在實踐中,存在兩種轉錄模式,即無詞典轉錄和基於詞典的轉錄。詞彙是一組標籤序列,預測是對的約束,例如。拼寫檢查字典。在無詞彙模式下,預測是在沒有任何詞彙的狀況下進行的。在基於詞彙的模式下,預測是經過選擇機率最高的標籤序列來進行的。
我是使用第二種:預測經過選擇機率最高的標籤序列來進行。
#Paddle已經提供了代碼實現
paddle.fluid.layers.ctc_greedy_decoder(input, blank, name=None)
4.如今來總結一下該模型的搭建吧!
論文中提供的網絡層和參數的圖片已經很直觀了,稍微解釋一下(從下往上看):
第一層(卷積層):圖片(input)通過1層步長爲1(s表示),填充爲1(p表示)的3x3卷積,過濾器數量爲64.
第二層(最大池化層):第一層的輸出進行2x2的最大池化,步長爲2,以此類推。BatchNormalization表示批歸一化:用batch_norm實現
#batch_norm的Paddle實現 paddle.fluid.layers.batch_norm(input, act=None, is_test=False, momentum=0.9, epsilon=1e-05, param_attr=None, bias_attr=None, data_layout='NCHW', in_place=False, name=None, moving_mean_name=None, moving_variance_name=None, do_model_average_for_mean_and_var=False, use_global_stats=False)
Bidirectional-LSTM在論文中爲2層的雙向LSTM。實現代碼中我使用的是2層的GRU單元,讀者能夠嘗試使用LSTM。
回溯時間(BPTT)是在遞歸層的底部,將傳播的差分序列鏈接成映射,將特徵映射轉換爲特徵序列的操做倒置,並反饋給卷積層。在實踐中,咱們建立了一個自定義網絡層,稱爲「映射到等」,做爲卷積層和遞歸層之間的橋樑。
完整步驟的代碼實現爲:
import paddle.fluid as fluid from paddle.fluid import ParamAttr from paddle.fluid.clip import GradientClipByNorm from paddle.fluid.regularizer import L2Decay from paddle.fluid.initializer import MSRA, Normal from paddle.fluid.layers import conv2d, conv2d_transpose, batch_norm, fc, dynamic_gru, im2sequence, elementwise_mul, \ pool2d, dropout, concat class CRNN(object): def __init__(self, num_classes, label_dict): self.outputs = None self.label_dict = label_dict self.num_classes = num_classes#類別數 def name(self): return 'crnn' def conv_bn_pool(self, x, n_filters, n_ConvBN, pool_stride, w_conv, is_test): w_bn = ParamAttr(regularizer=L2Decay(0.001))#設置L2正則化,初始化權重 b_bn = ParamAttr(regularizer=L2Decay(0.001), initializer=Normal(0.0, 0.0)) for _ in range(n_ConvBN): x = conv2d(x, n_filters, 3, 1, 1, param_attr=w_conv)#定義卷積層 #批歸一化 x = batch_norm(x, act='relu', param_attr=w_bn, bias_attr=b_bn, is_test=is_test) assert pool_stride in [2, (2, 1), (3, 1)]#使用斷言 if pool_stride == 2: x = pool2d(x, 2, 'max', pool_stride, 0, ceil_mode=True)#定義池化層,最大池化 elif pool_stride == (2, 1): x = pool2d(x, (2, 1), 'max', pool_stride, 0, ceil_mode=True) elif pool_stride == (3, 1): x = pool2d(x, (3, 1), 'max', pool_stride, 0, ceil_mode=True) return x def ocr_convs(self, x, is_test): w_conv1 = ParamAttr(regularizer=L2Decay(0.001)) w_conv2 = ParamAttr(regularizer=L2Decay(0.001)) w_conv3 = ParamAttr(regularizer=L2Decay(0.001)) x = self.conv_bn_pool(x, 128, 1, 2, w_conv1, is_test) x = self.conv_bn_pool(x, 256, 1, 2, w_conv2, is_test) x = self.conv_bn_pool(x, 512, 2, 2, w_conv2, is_test) x = self.conv_bn_pool(x, 1024, 2, (2, 1), w_conv3, is_test) return x def net(self, images, rnn_hidden_size=750, is_test=False): w_fc = ParamAttr(regularizer=L2Decay(0.001)) b_fc1 = ParamAttr(regularizer=L2Decay(0.001), initializer=Normal(0.0, 0.0)) b_fc2 = ParamAttr(regularizer=L2Decay(0.001), initializer=Normal(0.0, 0.0), learning_rate=2.0) b_fc3 = ParamAttr(regularizer=L2Decay(0.001), initializer=Normal(0.0, 0.0)) x = self.ocr_convs(images, is_test) x = im2sequence(x, (x.shape[2], 1), (1, 1))#用 filter 掃描輸入的Tensor並將輸入Tensor轉換成序列 fc_1 = fc(x, rnn_hidden_size * 3, param_attr=w_fc, bias_attr=b_fc1)#定義全鏈接層,將cnn層輸出處理成序列,用於代入RNN層 fc_2 = fc(x, rnn_hidden_size * 3, param_attr=w_fc, bias_attr=b_fc1) gru_forward = dynamic_gru(fc_1, rnn_hidden_size, param_attr=w_fc, bias_attr=b_fc2, candidate_activation='relu')#用於在完整序列上逐個時間步的進行單層Gated Recurrent Unit(GRU)的計算 gru_backward = dynamic_gru(fc_2, rnn_hidden_size, param_attr=w_fc, bias_attr=b_fc2, candidate_activation='relu', is_reverse=True)#使用2層結構 bigru = gru_forward + gru_backward bigru = dropout(bigru, 0.5, is_test)#使用隨機丟棄單元的正則化方法 fc_out = fc(bigru, self.num_classes + 1, param_attr=w_fc, bias_attr=b_fc3)#全鏈接層 self.outputs = fc_out return fc_out def get_infer(self, images):#CTC轉錄層 return fluid.layers.ctc_greedy_decoder(input=self.outputs, blank=self.num_classes)
方法總結
該項目基於官方基線(baseline)上進行優化,在測試集上的分數大約爲75.79,我在此基礎上大約提升了5%。本項目主要是作了數據加強,加上必要的調參,網絡部分基本上改動不大。原數據集是21萬張訓練集,我將他增長到了42萬張(翻了2倍)。
本次使用的數據加強方法有:
-
對這42萬張訓練集圖片都進行調整亮度、色度、對比度、飽和度,增長模型魯棒性;
-
對其中21萬張圖片再進行隨機的小幅度左右旋轉(旋轉角度爲10到10之間)以及隨機放大(1-2倍之間)。
在比賽以前,一般掌握的都是各方面各類課程的比較零散的知識,經過比賽,用這些知識,一步步構建出一個項目,優化模型,達到比較好的效果,而且還拿到了獎品,這是很是值得開心的。
下載安裝命令 ## CPU版本安裝命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle ## GPU版本安裝命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu