CNN+BLSTM+CTC的驗證碼識別從訓練到部署
*本文原創作者:kerlomz,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載
一 前言
長話短說,開門見山,驗證碼是網路安全的一個重要組成部分,提高了暴力嘗試破解的成本,而驗證碼識別是其反面,本文將帶領大家看看如何使用深度學習進行驗證碼的識別,各廠可以通過本文來認識圖形驗證碼的弱點和不可靠性。
最新更新(2019/01/21):如果對於DLL呼叫感興趣或是其他語言的TensorFlow API感興趣的移步以下兩個專案:
https://github.com/kerlomz/captcha_library_c
https://github.com/kerlomz/captcha_demo_csharp
筆者選用的時下最為流行的CNN+BLSTM+CTC進行端到端的不定長驗證碼識別,程式碼中預留了DenseNet+BLSTM+CTC的選項,可以在配置中直接選用。首先,介紹個大概吧。
網格結構 | predict-CPU | predict-GPU | 模型大小 |
---|---|---|---|
CNN5+Bi-LSTM+H64+CTC | 15ms | 28ms | 2mb |
CNN5+Bi-LSTM+H16+CTC | 8ms | 28ms | 1.5mb |
DenseNet+Bi-LSTM+H64+CTC | 60ms | 60ms | 6.5mb |
H16/H64指的是Bi-LSTM的隱藏神經元個數num_units,這裡注意,你沒有看錯,也沒有寫反,LSTM有時序依賴,tf.contrib.rnn.LSTMCell的實現沒能很充分的利用GPU的計算資源,底層kernel函式之間的間隙非常大,不利於充分的利用 GPU 的並行性來進行計算。所以本專案使用GPU訓練,使用CPU進行預測。預測服務部署專案原始碼請移步此處: https://github.com/kerlomz/captcha_platform
二 環境依賴:
關於CUDA和cuDNN版本的問題,不少人很糾結,這裡就列出官方通過pip安裝的TensorFlow的版本對應表:
Linux
Version | Python version | Compiler | Build tools | cuDNN | CUDA |
---|---|---|---|---|---|
tensorflow_gpu-1.12.0 | 2.7, 3.3-3.6 | GCC 4.8 | Bazel 0.15.0 | 7 | 9 |
tensorflow_gpu-1.11.0 | 2.7, 3.3-3.6 | GCC 4.8 | Bazel 0.15.0 | 7 | 9 |
tensorflow_gpu-1.10.0 | 2.7, 3.3-3.6 | GCC 4.8 | Bazel 0.15.0 | 7 | 9 |
tensorflow_gpu-1.9.0 | 2.7, 3.3-3.6 | GCC 4.8 | Bazel 0.11.0 | 7 | 9 |
tensorflow_gpu-1.8.0 | 2.7, 3.3-3.6 | GCC 4.8 | Bazel 0.10.0 | 7 | 9 |
tensorflow_gpu-1.7.0 | 2.7, 3.3-3.6 | GCC 4.8 | Bazel 0.9.0 | 7 | 9 |
tensorflow_gpu-1.6.0 | 2.7, 3.3-3.6 | GCC 4.8 | Bazel 0.9.0 | 7 | 9 |
Windows
Version | Python version | Compiler | Build tools | cuDNN | CUDA |
---|---|---|---|---|---|
tensorflow_gpu-1.12.0 | 3.5-3.6 | MSVC 2015 update 3 | Bazel 0.15.0 | 7 | 9 |
tensorflow_gpu-1.11.0 | 3.5-3.6 | MSVC 2015 update 3 | Bazel 0.15.0 | 7 | 9 |
tensorflow_gpu-1.10.0 | 3.5-3.6 | MSVC 2015 update 3 | Cmake v3.6.3 | 7 | 9 |
tensorflow_gpu-1.9.0 | 3.5-3.6 | MSVC 2015 update 3 | Cmake v3.6.3 | 7 | 9 |
tensorflow_gpu-1.8.0 | 3.5-3.6 | MSVC 2015 update 3 | Cmake v3.6.3 | 7 | 9 |
tensorflow_gpu-1.7.0 | 3.5-3.6 | MSVC 2015 update 3 | Cmake v3.6.3 | 7 | 9 |
tensorflow_gpu-1.6.0 | 3.5-3.6 | MSVC 2015 update 3 | Cmake v3.6.3 | 7 | 9 |
如果希望使用上面對應之外的搭配的CUDA和cuDNN,可以自行編譯TensorFlow,或者去Github上搜索 TensorFlow Wheel
找到第三方編譯的對應版本的whl安裝包。提前預警,若是自己編譯將會苦難重重,坑很多,這裡就不展開了。
2.1 本專案環境依賴
目前在以下主流作業系統平臺均測試通過:| 作業系統 | 最低支援版本 || ——– | ———— || Ubuntu | 16.04 || Windows | 7 SP1 || MacOS | N/A |
本訓練專案主要的環境依賴清單如下| 依賴 | 最低支援版本 || ———- | ———— || Python | 3.6 || TensorFlow-GPU | 1.6.0 || Opencv-Python | 3.3.0.10 || Numpy | 1.14.1 || Pillow | 4.3.0 || PyYaml | 3.13 |
2.1.1 Ubuntu 16.04 下的 Python 3.6
1)先安裝Python環境
sudo apt-get install openssl sudo apt-get install libssl-dev sudo apt-get install libc6-dev gcc sudo apt-get install -y make build-essential zlib1g-dev libbz2-dev libreadline-dev $ libsqlite3-dev wget curl llvm tk-dev wget https://www.python.org/ftp/python/3.6.6/Python-3.6.6.tgz tar -vxf Python-3.6.6.tar.xz cd Python-3.6.6 ./configure --prefix=/usr/local--enable-shared make -j8 sudo make install -j8
經過上面指令就安裝好Python3.6環境了,如果提示找不到 libpython3.6m.so.1.0
就到/usr/local/lib路徑下將該檔案複製一份到/usr/lib和/usr/lib64路徑下。2)安裝相關依賴(這一步Windows和Linux通用)可以直接在專案路徑下執行 pip3 install -r requirements.txt
安裝所有依賴,注意這一步是安裝在全域性Python環境下的,強烈建議使用虛擬環境進行專案間的環境隔離,如Virtualenv或Anaconda等等。我一般使用的是Virtualenv,有修改程式碼需要的,建議安裝PyCharm作為Python IDE
virtualenv -p /usr/bin/python3 venv # venv is the name of the virtual environment. cd venv/ # venv is the name of the virtual environment. source bin/activate # to activate the current virtual environment. cd captcha_trainer # captcha_trainer is the project path. pip3 install -r requirements.txt
2.1.2 Ubuntu 16.04 下的 CUDA/cuDNN
網上看到過很多教程,我自己也部署過很多次,Ubuntu 16.04遇到的坑還是比較少的。14.04支援就沒那麼好,如果主機板不支援關閉SecureBoot的話千萬不要安裝Desktop版,因為安裝好之後一定會無限迴圈在登陸介面無法進入桌面。網上教程說要加驅動黑名單什麼的我直接跳過了,親測沒那個必要。就簡單的幾步:
1. 下載好安裝包注意下載runfile型別的安裝包,deb安裝會自動安裝預設驅動,極有可能導致登陸迴圈
NVIDIA 驅動下載: https://www.geforce.cn/drivers
CUDA 下載地址: https://developer.nvidia.com/cuda-downloads
cuDNN 下載地址: https://developer.nvidia.com/cudnn (需要註冊NVIDIA賬號且登陸,下載deb安裝包)
2. 關閉圖形介面Ctrl+alt+F1進入字元介面,關閉圖形介面
sudo service lightdm stop
3. 安裝Nvidia Driver
命令中的版本自己對應下載的版本改,在上面的下載地址根據自己的顯示卡型號下載最新版,切記是runfile格式的安裝包。
sudo chmod a+x NVIDIA-Linux-x86_64-384.90.run //獲取執行許可權 sudo ./NVIDIA-Linux-x86_64-384.90.run –no-x-check –no-nouveau-check –no-opengl-files //安裝驅動
安裝成功以後使用以下命令驗證,如果顯示顯示卡資訊則表示安裝成功
nvidia-smi
4. 安裝CUDA
1)先安裝一些系統依賴庫
sudo apt-get install freeglut3-dev build-essential libx11-dev libxmu-dev libxi-dev libgl1-mesa-glx libglu1-mesa libglu1-mesa-dev
2) 執行安裝程式,按指示無腦繼續就好了,如果提示是否安裝驅動選不安裝。
sudo sh cuda_9.0.176_384.81_linux.run
安裝完如果環境變數沒配上去,就寫到 ~/.bashrc 檔案的尾部
export PATH=/usr/local/cuda-9.0/bin${PATH:+:${PATH}} export LD_LIBRARY_PATH=/usr/local/cuda-9.0/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
然後在終端執行 sudo ldconfig
更新,安裝完畢就可以重啟機器重啟圖形介面了。
sudo service lightdm start
2.1.3 Windows 系統
在Windows其實簡單很多,只要到官網下載安裝包無腦安裝就可以了,下載連線同Ubuntu,先安裝Python,顯示卡驅動,CUDA,然後下載對應的cuDNN替換到對應路徑即可。
花了超長篇幅介紹了訓練環境的基本搭建,主要是給尚未入門的讀者看的,老鳥們隨便跳過
三 使用
入手的第一步環境搭建好了,那就是準備跑程式碼了,還是有幾個必要的條件,巧婦難為無米之炊,首先,既然是訓練,要先有訓練集,筆者不滿百度雲不開會員龜速10kb限速很久了,所以上傳到QQ群857149419共享裡,有一個新手嚐鮮的訓練集,是mnist手寫識別的例子,現在萬事俱備,只欠東風。
3.1 定義一個模型
本專案採用的是引數化配置,不需要改動任何程式碼,可以訓練幾乎任何字元型圖片驗證碼,下面從兩個配置檔案說起: config.yaml # 系統配置
# - requirement.txt-GPU: tensorflow-gpu, CPU: tensorflow # - If you use the GPU version, you need to install some additional applications. # TrainRegex and TestRegex: Default matching apple_20181010121212.jpg file. # - The Default is .*?(?=_.*\.) # TrainsPath and TestPath: The local absolute path of your training and testing set. # TestSetNum: This is an optional parameter that is used when you want to extract some of the test set # - from the training set when you are not preparing the test set separately. System: DeviceUsage: 0.7 TrainsPath: 'E:\Task\Trains\YourModelName\' TrainRegex: '.*?(?=_)' TestPath: 'E:\Task\TestGroup\YourModelName\' TestRegex: '.*?(?=_)' TestSetNum: 1000 # CNNNetwork: [CNN5, DenseNet] # RecurrentNetwork: [BLSTM, LSTM] # - The recommended configuration is CNN5+BLSTM / DenseNet+BLSTM # HiddenNum: [64, 128, 256] # - This parameter indicates the number of nodes used to remember and store past states. NeuralNet: CNNNetwork: CNN5 RecurrentNetwork: BLSTM HiddenNum: 64 KeepProb: 0.98 # SavedSteps: A Session.run() execution is called a Steps, # - Used to save training progress, Default value is 100. # ValidationSteps: Used to calculate accuracy, Default value is 100. # TestNum: The number of samples for each test batch. # - A test for every saved steps. # EndAcc: Finish the training when the accuracy reaches [EndAcc*100]%. # EndEpochs: Finish the training when the epoch is greater than the defined epoch. Trains: SavedSteps: 100 ValidationSteps: 500 EndAcc: 0.975 EndEpochs: 1 BatchSize: 64 TestBatchSize: 400 LearningRate: 0.01 DecayRate: 0.98 DecaySteps: 10000
上面看起來好多好多引數,其實大部分可以不用改動,你需要修改的僅僅是訓練集路徑就可以了, 注意:如果訓練集的命名格式和我提供的新手訓練集不一樣,請根據實際情況修改TrainRegex和TestRegex的正則表示式。 ,TrainsPath和TestPath路徑支援list引數,允許多個路徑,這種操作適用於需要將多種樣本訓練為一個模型,或者希望訓練一套通用模型的人。為了加快訓練速度,提高訓練集讀取效率,特別提供了make_dataset.py來支援將訓練集打包為tfrecords格式輸入,經過make_dataset.py打包之後的訓練集將輸出到本專案的dataset路徑下,只需修改TrainsPath鍵的配置如下即可
TrainsPath: './dataset/xxx.tfrecords'
TestPath是允許為空的,如果TestPath為空將會使用TestSetNum引數自動劃分出對應個數的測試集。如果使用自動劃分機制,那麼TestSetNum測試集總數引數必須大於等於TestBatchSize測試集每次讀取的批次大小。神經網路這塊可以講一講,預設提供的組合是CNN5(CNN5層模型)+BLSTM(Bidirectional LSTM)+CTC,親測收斂最快,但是訓練集過小,實際圖片變化很大特徵很多的情況下容易發生過擬合。DenseNet可以碰運氣在樣本量很小的情況下很好的訓練出高精度的模型,為什麼是碰運氣呢,因為收斂快不快隨機的初始權重很重要,運氣好前500步可能對測試集就有40-60%準確率,運氣不好2000步之後還是0,收斂快慢是有一定的運氣成分的。
NeuralNet: CNNNetwork: CNN5 RecurrentNetwork: BLSTM HiddenNum: 64 KeepProb: 0.99
隱藏層HiddenNum筆者嘗試過8~64,都能控制在很小的模型大小之內,如果想使用DenseNet代替CNN5直接修改如上配置中的CNNNetwork引數替換為:
NeuralNet: CNNNetwork: DenseNet ......
model.yaml # 模型配置
# ModelName: Corresponding to the model file in the model directory, # - such as YourModelName.pb, fill in YourModelName here. # CharSet: Provides a default optional built-in solution: # - [ALPHANUMERIC, ALPHANUMERIC_LOWER, ALPHANUMERIC_UPPER, # -- NUMERIC, ALPHABET_LOWER, ALPHABET_UPPER, ALPHABET] # - Or you can use your own customized character set like: ['a', '1', '2']. # CharExclude: CharExclude should be a list, like: ['a', '1', '2'] # - which is convenient for users to freely combine character sets. # - If you don't want to manually define the character set manually, # - you can choose a built-in character set # - and set the characters to be excluded by CharExclude parameter. Model: Sites: [] ModelName: YourModelName-CNN5-H64-150x50 ModelType: 150x50 CharSet: ALPHANUMERIC_LOWER CharExclude: [] CharReplace: {} ImageWidth: 150 ImageHeight: 50 # Binaryzation: [-1: Off, >0 and < 255: On]. # Smoothing: [-1: Off, >0: On]. # Blur: [-1: Off, >0: On]. # Resize: [WIDTH, HEIGHT] # - If the image size is too small, the training effect will be poor and you need to zoom in. # - ctc_loss error "No valid path found." happened Pretreatment: Binaryzation: -1 Smoothing: -1 Blur: -1
上述的配置只要關注ModelName、CharSet、ImageWidth、ImageHeight首先給模型取一個好名字是成功的第一步,字符集CharSet其實大多數情況下不需要修改,一般的圖形驗證碼離不開數字和英文,而且一般來說是大小寫不敏感的,不區分大小寫,因為打碼平臺收集的訓練集質量參差不齊,有些大寫有些小寫,不如全部統一為小寫,預設ALPHANUMERIC_LOWER則會自動將大寫的轉為小寫,字符集可定製化很靈活,除了配置備註上提供的幾種型別,還可以訓練中文,自定義字符集用list表示,示例如下:
CharSet: ['常', '世', '寧', '慢', '南', '制', '根', '難']
可以自己根據收集訓練集的實際字符集使用率來定義,也可以無腦網上找3500常用字來訓練, 注意:中文字符集一般比數字英文大很多,剛開始收斂比較慢,需要更久的訓練時間,也需要更多的樣本量,請量力而行 形如上圖的圖片能輕鬆訓練到95%以上的識別率。 ImageWidth、ImageHeight只要和當前圖片尺寸匹配即可,其實這裡的配置主要是為了方便後面的部署智慧策略。其他的如Pretreatment之下的引數是用來做圖片預處理的,因為筆者致力於做一套通用模型,模型只使用了灰度做預處理。其中可選的二值化、均值濾波、高斯模糊均未開啟,即使不進行那些預處理該框架已經能夠達到很理想的識別效果了,筆者自用的大多數模型都是98%以上的識別率。
3.2 開始訓練
按照上面的介紹,配置只要修改極少數的引數對應的值,就可以開啟正式的訓練之旅了,具體操作如下:可以直接使用PyCharm的Run,執行trains.py,也可以在啟用Virtualenv下使用終端亦或在安裝依賴的全域性環境下執行
python3 trains.py
剩下的就是等了,看過程,等結果。正常開始訓練的模樣應該是這樣的: 訓練結束會在專案的out路徑下生成一個pb和yaml檔案,下面該到部署環節了。
3.3 部署
真的很有必要認真的介紹一下部署專案,比起訓練,這個部署專案傾注了筆者更多的心血,為什麼呢?專案地址: https://github.com/kerlomz/captcha_platform
真的值得了解的幾點
同時管理多個模型,支援模型熱拔插
靈活的版本控制
支援批量識別
服務智慧路由策略
首先筆者重寫了Tensor Flow的Graph會話管理,設計會話池,允許同時管理多模型,實現多模型動態部署方案。 1) 訓練好的pb模型只要放在部署專案的graph路徑下,yaml模型配置檔案放在model,即可被服務發現並載入, 2) 如果需要解除安裝一個正在服務的模型,只需要在model中刪除該模型的yaml配置檔案,在graph中刪除對應的pb模型即可。 3) 如果需要更新一個已經服務中的模型,只需修改新版的模型yaml配置檔案的版本號高於原模型的版本號,按先放pb後放yaml的順序,服務便會自動發現新版的模型並載入使用,舊的模型將因版本低於新版模型不會被呼叫,可以按照上述的解除安裝方法解除安裝已被棄用的模型釋放記憶體。上面的操作中無需重啟服務,完全的無縫切換
其次,一套服務想要服務於各式各樣的影象識別需求,可以定義一套策略,訓練時將所有尺寸一樣的圖片訓練成一個模型,服務根據圖片尺寸自動選擇使用哪個模型,這樣的設計使定製化和通用性共存,等積累到一定多樣的訓練集時可以將所有的訓練集合到一起訓練一個通用模型,亦可以彼此獨立,每個模型的疊加僅僅增加了少量的記憶體或視訊記憶體,網上的方案大多是不同的模型單獨部署一套服務,每個程序載入了一整套TensorFlow框架勢必是過於龐大和多餘的。
用到批量識別需求的人相對少很多這裡就不展開介紹了。識別專案提供了多套可選的服務有:gRPC,Flask,Tornado,Sanic,其中Flask和Tornado提供了加密介面,類似於微信公眾號開發介面的SecretKey和AccessKey介面,感興趣的可以在demo.py中閱讀呼叫原始碼瞭解。
部署的使用可以經過package.py編譯為可執行檔案,這樣可以免去更換機器環境安裝的煩惱,部署專案安裝流程同訓練專案,專案中提供的requirements.txt已經將所需的依賴都列清楚了,強烈建議部署專案安裝cpu版TensorFlow。
Linux:
Tornado:
# 埠 19952 python3 tornado_server.py
Flask
# 方案1,裸啟動, 埠 19951 python flask_server.py # 方案2,使用gunicorn,埠 5000 pip install gunicorn gunicorn -c deploy.conf.py flask_server:app
Sanic:
# 埠 19953 python3 sanic_server.py
gRPC:
# 埠 50054 python3 grpc_server.py
Windows:Windows平臺下都是通過 python3 xxx_server.py
啟動對應的服務,注意,Tornado、Flask、Sanic的效能在Windows平臺都大打折扣,gRPC是Google開源的RPC服務,有較為優越的效能。
3.4 呼叫/測試
1. Flask服務:
請求地址 | Content-Type | 引數形式 | 請求方法 |
---|---|---|---|
http://localhost:19951/captcha/v1 | application/json | JSON | POST |
具體引數:| 引數名 | 必選 | 型別 | 說明 || ———- | —- | —— | ———————— || image | Yes | String | Base64 編碼 || model_site | No | String | 網站名,yaml配置中可繫結 || model_type | No | String | 類別,yaml配置中可繫結 |請求為JSON格式,形如:{“image”: “base64編碼後的影象二進位制流”}
返回結果:| 引數名 | 型別 | 說明 || ——- | —— | —————— || message | String | 識別結果或錯誤訊息 || code | String | 狀態碼 || success | String | 是否請求成功 |該返回為JSON格式,形如:{“message”: “xxxx”, “code”: 0, “success”: true}
2. Tornado服務:
請求地址 | Content-Type | 引數形式 | 請求方法 |
---|---|---|---|
http://localhost:19952/captcha/v1 | application/json | JSON | POST |
請求引數和返回格式同上
3. Sanic服務:| 請求地址 | Content-Type | 引數形式 | 請求方法 || ———– | —————- | ——– | ——– || http://localhost:19953/captcha/v1 | application/json | JSON | POST | 請求引數和返回格式同上
4. gRPC服務:需要安裝依賴,grpcio、grpcio_tools和對應的grpc.proto檔案,可以直接從專案中的示例程式碼demo.py中提取。
class GoogleRPC(object): def __init__(self, host: str): self._url = '{}:50054'.format(host) self.true_count = 0 self.total_count = 0 def request(self, image, model_type=None, model_site=None): import grpc import grpc_pb2 import grpc_pb2_grpc channel = grpc.insecure_channel(self._url) stub = grpc_pb2_grpc.PredictStub(channel) response = stub.predict(grpc_pb2.PredictRequest( image=image, split_char=',', model_type=model_type, model_site=model_site )) return {"message": response.result, "code": response.code, "success": response.success} if __name__ == '__main__': result = GoogleRPC().request("base64編碼後的圖片二進位制流") print(result)
3.5 奇技淫巧
該專案還可以直接用於識別帶顏色的圖片,本質是不同的顏色分別訓練,呼叫的時候通過傳參區分,如果希望獲得圖片中紅色的文字,就直接通過引數定位到訓練紅色的模型,希望獲取圖片中藍色的圖片就通過引數定位到藍色模型,如:
不過這種操作對樣本量要求較高,且效率不高,當顏色引數越來越多時就不適用,可以採用顏色提取的方式,這樣所需要的樣本量將大大減少,但對於顏色提取演算法效果要求高了。還有一種方案是同時預測驗證碼和每個字元對應的顏色,不過這需要修改現有的神經網路進行支援,在最後一層修改為雙輸出,一個輸出顏色,一個輸出對應字元,這對於樣本標註的要求較高,也提高的成本,所以如果能用無限生成樣本,那問題就迎刃而解了,比如上圖,筆者就寫了樣本生成程式碼,感興趣的可以移步: https://www.jianshu.com/p/da1b972e24f2 其實還有很多很多技巧,例如,用生成的樣本代替訓練集,其實網上的圖片驗證碼大多是採用開源的,稍作修改而已,大多數情況都能被近似生成出來,筆者收集了不少原始碼,上述展示的驗證碼圖片不代表任何實際的網站,如有雷同,筆者不承擔責任,該專案只能用於學習和交流用途,不得用於非法用途。
後記
如果各位好漢對深度學習、OCR感興趣的,歡迎大家一起學習和交流。走過路過點個星在此謝謝大家了!
https://github.com/kerlomz/captcha_trainer
https://github.com/kerlomz/captcha_platform
*本文原創作者:kerlomz,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載