如何在中文場景文字識別賽中贏取飛槳周邊?

飛槳開發者說】魏宏煒,福建省三明學院,本科三年級,研究方向爲目標檢測、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倍)。

本次使用的數據加強方法有:

  1. 對這42萬張訓練集圖片都進行調整亮度、色度、對比度、飽和度,增長模型魯棒性;

  2. 對其中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
相關文章
相關標籤/搜索