Discuz验证码识别(上线篇)-写给程序员的TensorFlow教程
經過前兩篇文章的開發,咱們今天終于要進入令人激動的上線篇了。(最近剛剛發布的TensorFlow lite其實也是部署上線的工具集之一)話說我在學習TensorFlow的時候,發現這部分的教程是尤其少。大部分教程都是先上來教一個回歸,再來一個CNN,在來幾篇保存模型和TensorBoard就結束了。我們這篇文章就來重點聊一聊部署上線。
這篇文章會被分成四個部分,第一部分繼續上篇文章,聊一聊第四步調參;第二部分聊一聊訓練中的模型保存和載入;第三部分,介紹TensorFlow Serving;第四部分 就是最重要的部署上線流程。
Part 1 訓練中的調參
還是先回顧下我們再三提及的解題思路:
第一步:將問題分解成輸入(x)到輸出(y)這樣的結構,如Discuz驗證碼的輸入是圖片,輸出是四個字符的字符串
第二步:找到很多同時包含輸入輸出的數據,比如很多有識別結果的驗證碼圖片
第三步:針對不同問題,找到算法大神們的已經定義好的算法并實現成代碼
第四步:嘗試使用這個算法訓練這些數據,如果效果不好,算法中有一些參數可以手動調整,至于怎么調,可以參考前人經驗,也可以自己瞎調積累經驗。
第五步:寫一個程序載入模型,接受一個新的輸入值,通過模型計算出新的輸出值。
前兩篇文章已經走完了前三步,那么我們現在來看的第四步。不負責任的講,其實機器學習工程師大部分時間都是在調參。畢竟,大神都想好的算法就幾個,但是卻留了很多參數來調整。我們總還是得體現出自己的價值是不是,那我們來看看一般都有哪些參數值得調整(大家最好對照代碼來看,這個是熟悉代碼最好的方式):
1.圖片最終壓縮的長寬
image_resized = tf.image.resize_images(image_gray, [48, 48],tf.image.ResizeMethod.NEAREST_NEIGHBOR)
這一段是我們前面代碼中壓縮圖片的邏輯,那么最終圖片壓縮到多大是最合適的呢?太大的訓練的慢,內存占用高;太小了又會丟失重要信息。一般來說,我們挑一個樣本圖片,壓縮完之后用肉眼看一下,如果你自己肉眼還能識別的話,那就是ok的。
2.神經網絡層數和節點數
看了兩節課,大家應該大體能知道我們的神經網絡是由一層一層的節點組成的,比如這就是一層,這一層實現的是卷積層:
image_x = tf.reshape(image_resized_float,shape=[-1,48,48,1])
conv1 = tf.layers.conv2d(image_x, filters=32, kernel_size=[5, 5], padding='same')
norm1 = tf.layers.batch_normalization(conv1)
activation1 = tf.nn.relu(conv1)
pool1 = tf.layers.max_pooling2d(activation1, pool_size=[2, 2], strides=2, padding='same')
hidden1 = pool1
這樣的也是一層,這一層是叫做全連接層:
hidden3 = tf.layers.dense(flatten, units=1024, activation=tf.nn.relu)
大家可以看到我們這些層里面有非常多的參數,比如filters,kernel_size,pool_size,strides,units等等,這些參數到底應該寫啥呢。幸運的是,一般來說大神都給了我們一些常規建議,比如kernel_size都是[3,3]或者[5,5],比如pool_size都[2,2],max_pooling2d的strides都是2等等,這些如果咱自己不是大神就別瞎調了。
我們可以發揮一些主觀能動性的是filters和units。當然這里我這里也是一些常規值,大家可以在這些常規值上做一些2的倍數的調整(好像也沒有什么特別的原因,咱姑且認為是程序員的強迫癥吧),當然也不是完全瞎調,首先數量不能太大,因為太大之后,一方面最后模型極大,另外也可能會造成過擬合嚴重,訓練也慢。當然也不能太小,不然可能就擬合不上,準確率上不去。具體大家調整中自行感受,比如在調整filters的時候可以猜想下比如自己要來選擇特征會選擇幾個(所謂特征選擇就是有多少個轉角,有多少個弧度這種,每一個特點算一個特征)。
另外還有一個參數就是到底要寫幾層,這個常規來說大神也是定義好的,而且一般把層數都寫到算法名字上去了,沒啥可發揮的空間。不過強行發揮的話就是如果你覺得你要識別的這個東西比大神舉的例子簡單很多,就可以少來幾層(比如我們現在實現的這個簡化版CNN,原版好像是6層,被我刪成了4層)。
3.其他的一些(超)參數
還有一些比較常規的參數也可以調整,比如最常見的學習速率(代碼里的1e-4):
tf.train.AdamOptimizer(1e-4)
這個不建議大家初學的時候調整,除非發現準確率一直忽高忽低的情況,可以考慮調小一些。
調參->訓練->調參->訓練,經過幾個回合,理論上應該就可以獲得一個訓練速度符合要求的訓練代碼了,大家就可以直接啟動訓練了。
Part 2 模型的保存和載入
事實上,一般的機器學習最終輸出的東西都是一個模型。所謂模型,就是一堆變量的值(比如剛剛那個神經網絡層里面每個節點中的變量),那么我們如何保存我們訓練出來的模型呢?
TensorFlow提供了非常方便的實現,這個應該寫過代碼的人看一眼就懂了:
saver = tf.train.Saver()
saver.save(sess,'my-model')
注意,我們是通過傳入session來保存模型的。(神箭手線上會自動對模型進行起名,上傳,因此只需要傳入session一個參數即可)
對應的,載入模型也異常簡單:
saver.restore(sess,'my-model')
這句的意思就是把my-model這個模型中的所有變量賦值給session中的變量,咱就可以繼續跑了。(同樣,神箭手自動對模型進行處理,只需要傳入session即可)
當然,TensorFlow還提供了另外一種保存和載入模型的機制,這個就是我們第三部分要講到的主要用于部署上線的模型保存。
Part 3 TensorFlow Serving環境
事實上,TensorFlow對模型的部署上線提供了完善的工具集,其中本篇文章重點要講到的是TensorFlow Serving,當然類似TensorFlow Lite是另一個方面的重要工具,不過今天這里我們先不做討論。
那么什么是TensorFlow Serving呢?實際上我們使用數據訓練出了最終模型,是要對未知數據進行預測的,那么我們怎么預測呢?當然我們可以把我們的python代碼直接執行一遍就行了,不過通常這不符合生產環境的要求(比如速度不夠,沒有版本控制等等),可敬可愛的TensorFlow工程師急大家之所急,為大家寫了一套C++的包含版本控制等重要功能的運行環境:TensorFlow Serving(當然他們還寫了一套手機上的運行環境)。那么大家只需要把訓練好的模型保存出來,放到這個環境中去執行即可,那么這里就有幾個問題出現了:
1.我們得顯式告訴這個環境我們輸入和輸出是什么(就是在我們定義好的圖上調出輸入節點和輸出節點分別是什么)
2.我們在整個預測的這個流程中不能出現純粹python的代碼(比如TensorFlow中的py_func這段代碼就不能出現在Serving環境中)
3.我們要注意訓練環境和Serving環境的差異性
我們一條一條展開講:
1.我們得顯式告訴這個環境我們輸入和輸出是什么
也就是說我們需要在保存模型的時候,同時申明我們的輸入輸出節點分別是什么,這樣環境自動會把我們輸入的值賦值給指定的節點,然后輸出我們指定的輸出節點的值就結束了,我們看下代碼:
#指定輸入輸出值
prediction_signature = def_utils.predict_signature_def({'image_base64': x},{'label': predict_join})
可以看到,我們指定的輸入值是圖中的x,給了一個名字叫image_base64,輸出值是圖中的predict_join,給的名字是label。如果大家仔細看代碼會發現這個predict_join在上篇文章的代碼中沒有,沒關系,一會會給大家完整代碼。這個predict_join的計算相對復雜,我們就不在這里展開講了,目的就是基于模型輸出的向量結果,轉換成最終我們需要的驗證碼識別結果的字符串。
接著我們將我們的模型和這個輸入輸出申明一起保存起來:
legacy_init_op = tf.group(tf.tables_initializer(), name='legacy_init_op')
builder = tf.saved_model.builder.SavedModelBuilder(export_dir)
builder.add_meta_graph_and_variables(
????? sess, [tf.saved_model.tag_constants.SERVING],
????? signature_def_map={
?????????? tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY:
?????????????? prediction_signature,
????? },
????? legacy_init_op=legacy_init_op)
builder.save()
注意,同樣的這里的export_dir是保存路徑,神箭手上可以不傳入。
2.我們在整個預測的這個流程中不能出現純粹python的代碼
還記得我前兩篇文章一直在說的,為什么我們不適用python的base64,python的Image庫,而一定要使用TensorFlow的庫呢?是因為在TensorFlow Serving的環境中是不能執行python代碼的,輸入節點和輸出節點都得是預定義的圖上的節點,因此無論是直接的python代碼還是py_func導入的python代碼都是無法執行了,那么這時候我們有兩個選擇:第一個是圖像處理,base64等用python的庫,而在TensorFlow Serving之前在寫一個python的client去調用他,這樣也可以,但是顯然更復雜;另一個選擇就是我們把能寫進預定義圖的邏輯都寫進預定義圖中,這樣我們就可以去掉python的client直接調用了。
3.我們要注意訓練環境和Serving環境的差異性
雖然都是TensorFlow提供的環境,但是由于訓練環境是python實現的(雖然底層還是c++,但是畢竟還是有不同的部分),而TensorFlow Serving環境是c++實現的,因此難免部分函數的實現有差異。在實際使用中,我們發現,在Serving中tf.decode_base64的返回值的形式和訓練環境有差異,因此我們需要在調用完decode_base64后再調用如下一句來統一訓練和Serving環境:
image_bin_reshape = tf.reshape(image_bin,shape=[-1,])
當然,整個代碼中肯定還是會有其他差異性,大家在遇到的時候不要覺得奇怪,畢竟谷歌工程師也是人,也有疏漏的地方。
訓練+模型保存的完整代碼已經上傳了Github:
https://github.com/ShenJianShou/tensorflow_tutorial/blob/master/lession-1/python/tensorflow.py
Part 4 模型部署上線
好了,TensorFlow Serving環境的特殊性說完了,需要保存的用于Serving的模型我們也保存好了,下面該正式上線了。這里我們依然介紹兩種方案,分別是神箭手方案和線下方案:
1.神箭手方案
說真的,整個TensorFlow Serving的部署雖然不復雜,但是依然有些繁瑣,而且靈活性不佳。更要命的就是調用時候,由于需要將輸入輸出的變量打包成protobuff的格式,因此無論如何也要單獨寫client來處理就顯得更加麻煩。
我們的工程師急大家之所急,急TF工程師之不急,為大家提供了完全集成的Serving環境,大家在運行完上面的模型保存之后,保存的模型會自動進入模型管理中,大家只需要點擊右側的啟動Serving按鈕即可:
神箭手會自動將模型傳輸給TensorFlow Serving環境,并且自動解析輸入輸出變量。然后自動生成一個http的api接口供大家調用:
2.線下方案
如果不在神箭手上訓練模型,只需要自行搭建TensorFlow Serving的環境即可,環境的搭建這里不多贅述了,大家只需要按照官方教程一步一步走即可:
https://www.tensorflow.org/serving/
特別要提的依然是這個client的問題,很多人可能被這個弄的很暈。這里簡單給大家普及幾個概念。
首先TensorFlow Serving使用的是GRPC+ProtoBuff方案,這個方案和我們傳統使用的HTTP+JSON(或XML)是不兼容的。之所以使用這個方案,有大概3個考慮:1 這個方案解析快;2 這個方案傳輸存儲消耗都小;3 谷歌順便推廣自家其他的框架。(事實上在谷歌內部幾乎所有的存儲都是采用protobuff的結構的,所以在TensorFlow上這樣使用也不奇怪了)因此大家在自己調用的時候,需要通過先將變量打包成protobuff的結構,在通過編譯后的grpc接口去調用。具體實現可以參考官方提供的client接口:
https://github.com/tensorflow/serving/blob/master/tensorflow_serving/example/mnist_client.py
雖然是官方的例子是python的,我們依然可以用其他語言如JAVA或者C++來實現client,像是神箭手幫大家打包的接口就是通過PHP來實現的Client。不過如果使用非python語言,都需要下載TensorFlow里的proto文件,并在指定的語言上編譯好才可以。
好了,無論使用什么方案。當我們訓練完之后,將模型導入Serving環境中,我們就完成這樣一個Discuz驗證碼識別接口的編寫和實現,從此以后再也不需要調用打碼平臺啦。
?
=======================================================
最后再說下:本系列教程的目的是幫助大家入門TensorFlow編程以及了解在機器學習應用在實際問題中時的具體實踐。希望大家能夠舉一反三,沒必要局限在Discuz驗證碼識別這個問題上,包括我們后面也會繼續推進其他驗證碼的識別的。
好了,這個系列第一篇文章到此結束,因為是第一篇所以相對講的細一些(一篇分成了三篇來講),后面我們將以一篇解決一個問題來展開。之前文章聊過的細節就不會在去深入探討了。
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的Discuz验证码识别(上线篇)-写给程序员的TensorFlow教程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Tensorflow构建和训练自己的
- 下一篇: Discuz验证码识别(编码篇)-写给程