使用 TensorFlow 識別簡單圖像驗證碼
公司有一個業務需要抓取某網站數據,登錄需要識別驗證碼,類似下面這種,這應該是很多網站使用的驗證碼類型。
首先由於驗證碼比較簡單,圖像不復雜,而且全部是數字。於是試著採用傳統方式,按照網上教程自己簡單改了一個,使用 PHP 識別。大概流程就是切割二值化去噪等預處理,然後用字符串數組形式保存起來,識別傳來的圖片同樣預處理後比較字符串的相似度,選出一個相識度最高的分類。識別率不是很理想(驗證碼比較簡單,應該能優化得更好),隱約記得只能超過60%。
因為識別效果不理想,目標網站登錄狀態還是能保持很久,沒必要花太多精力在這上面,於是找了一個人工打碼服務。簡直太便宜了,一個月花不了多少錢,效果還好,只是有時候延遲比較高。反正對於我們的業務來說是足夠用了。
機器學習大潮來臨,我尋思著能不能用在這上面,於是參考 TensorFlow 識別手寫數字教程,開始照貓畫虎。
本文描述的只是作為一個普通開發者的一些粗淺理解,所有的代碼和數據均在文後的 GitHub 有存留,建議結合代碼閱讀本文。如果有什麼理解錯誤或 Bug 歡迎留言交流 ^_^
TensorFlow 是什麼
TensorFlow 是谷歌出的一款機器學習框架。看名字,TensorFlow 就是“張量流”。呃。。什麼是張量呢?張量我的理解就是數據。張量有自己的形狀,比如 0 階張量是標量,1 階是向量,2 階是矩陣。。。所以在後文我們會看到在 TensorFlow 裡面使用的量幾乎都要定義其形狀,因為它們都是張量。
我們可以把 TensorFlow 看作一個黑盒子,裡面有一些架好的管道,餵給他一些“張量”,他吐出一些“張量”,吐出的東西就是我們需要的結果。
所以我們需要確定喂進去的是什麼,吐出來的是什麼,管道如何搭建。
更多的入門概念可以查看這個 keras新手指南 » 一些基本概念
為什麼使用 TensorFlow
沒別的什麼原因,只是因為谷歌大名,也沒想更多。先擼起袖子幹起來。如果為了快速成型,我建議可以看一下 Keras,號稱為人類設計的機器學習框架,也就是用戶體驗友好,提供好幾個機器學習框架更高層的接口。
大體流程
- 抓取驗證碼
- 給驗證碼打標籤
- 圖片預處理
- 保存數據集
- 構建模型訓練
- 提取模型使用
抓取驗證碼
這個簡單,隨便什麼方式,循環下載一大堆,這裡不再贅述。我這裡下載了 750 張驗證碼,用 500 張做訓練,剩下 250 張驗證模型效果。
給驗證碼打標籤
這裡的驗證碼有750張之巨,要是手工給每個驗證碼打標籤,那一定累尿了。這時候就可以使用人工打碼服務,用廉價勞動力幫我們做這件事。人工打碼後把識別結果保存下來。這裡的代碼就不提供了,看你用哪家的驗證碼服務,相信聰明的你一定能解決 :)
圖片預處理
- 圖片信息: 此驗證碼是 68x23,JPG格式
- 二值化: 我確信這個驗證碼足夠簡單,在丟失圖片的顏色信息後仍然能被很好的識別。並且可以降低模型複雜度,因此我們可以將圖片二值化。即只有兩個顏色,全黑或者全白。
- 切割驗證碼: 觀察驗證碼,沒有特別扭曲或者粘連,所以我們可以把驗證碼平均切割成4塊,分別識別,這樣圖片識別模型就只需要處理10個分類(如果有字母那將是36個分類而已)由於驗證碼外面有一圈邊框,所以順帶把邊框也去掉了。
- 處理結果: 16x21,黑白2位
相關 Python 代碼如下:
img = Image.open(file).convert('L') # 讀取圖片並灰度化
img = img.crop((2, 1, 66, 22)) # 裁掉邊變成 64x21
# 分離數字
img1 = img.crop((0, 0, 16, 21))
img2 = img.crop((16, 0, 32, 21))
img3 = img.crop((32, 0, 48, 21))
img4 = img.crop((48, 0, 64, 21))
img1 = np.array(img1).flatten() # 扁平化,把二維弄成一維
img1 = list(map(lambda x: 1 if x <= 180 else 0, img1)) # 二值化
img2 = np.array(img2).flatten()
img2 = list(map(lambda x: 1 if x <= 180 else 0, img2))
img3 = np.array(img3).flatten()
img3 = list(map(lambda x: 1 if x <= 180 else 0, img3))
img4 = np.array(img4).flatten()
img4 = list(map(lambda x: 1 if x <= 180 else 0, img4))
複製代碼
保存數據集
數據集有輸入輸入數據和標籤數據,訓練數據和測試數據。 因為數據量不大,簡便起見,直接把數據存成python文件,供模型調用。就不保存為其他文件,然後用 pandas 什麼的來讀取了。
最終我們的輸入模型的數據形狀為 [[0,1,0,1,0,1,0,1...],[0,1,0,1,0,1,0,1...],...] 標籤數據很特殊,本質上我們是對輸入的數據進行分類,所以雖然標籤應該是0到9的數字,但是這裡我們使標籤數據格式是 one-hot vectors [[1,0,0,0,0,0,0,0,0,0,0],...] 一個one-hot向量除了某一位的數字是1以外其餘各維度數字都是0**,比如[1,0,0,0,0,0,0,0,0,0] 代表1,[0,1,0,0,0,0,0,0,0,0]代表2. 更進一步,這裡的 one-hot 向量其實代表著對應的數據分成這十類的概率。概率為1就是正確的分類。
相關 Python 代碼如下:
# 保存輸入數據
def px(prefix, img1, img2, img3, img4):
with open('./data/' + prefix + '_images.py', 'a+') as f:
print(img1, file=f, end=",\\n")
print(img2, file=f, end=",\\n")
print(img3, file=f, end=",\\n")
print(img4, file=f, end=",\\n")
# 保存標籤數據
def py(prefix, code):
with open('./data/' + prefix + '_labels.py', 'a+') as f:
for x in range(4):
tmp = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
tmp[int(code[x])] = 1
print(tmp, file=f, end=",\\n")
複製代碼
經過上面兩步,我們在就獲得了訓練和測試用的數據和標籤數據,吶,就像這樣
構建模型訓練
數據準備好啦,到了要搭建“管道”的時候了。 也就是你需要告訴 TensorFlow:
1. 輸入數據的形狀是怎樣的?
x = tf.placeholder(tf.float32, [None, DLEN])
複製代碼
None 表示不定義我們有多少訓練數據,DLEN是 16*21,即一維化的圖片的大小。
2. 輸出數據的形狀是怎樣的?
y_ = tf.placeholder("float", [None, 10])
複製代碼
同樣None 表示不定義我們有多少訓練數據,10 就是標籤數據的維度,即圖片有 10 個分類。每個分類對應著一個概率,所以是浮點類型。
3. 輸入數據,模型,標籤數據怎樣擬合?
W = tf.Variable(tf.zeros([DLEN, 10])) # 權重
b = tf.Variable(tf.zeros([10])) # 偏置
y = tf.nn.softmax(tf.matmul(x, W) + b)
複製代碼
是不是一個很簡單的模型?大體就是
y = softmax(Wx+b) 其中 W 和 b 是 TensorFlow 中的變量,他們保存著模型在訓練過程中的數據,需要定義出來。而我們模型訓練的目的,也就是把 W 和 b 的值確定,使得這個式子可以更好的擬合數據。 softmax 是所謂的激活函數,把線性的結果轉換成我們需要的樣式,也就是分類概率的分佈。 關於 softmax 之類更多解釋請查看參考鏈接。4. 怎樣評估模型的好壞?
模型訓練就是為了使模型輸出結果和實際情況相差儘可能小。所以要定義評估方式。 這裡用所謂的交叉熵來評估。
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
複製代碼
5. 怎樣最小化誤差?
現在 TensorFlow 已經知道了足夠的信息,它要做的工作就是讓模型的誤差足夠小,它會使出各種方法使上面定義的交叉熵 cross_entropy 變得儘可能小。 TensorFlow 內置了不少方式可以達到這個目的,不同方式有不同的特點和適用條件。在這裡使用梯度下降法來實現這個目的。
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
複製代碼
訓練準備
大家知道 Python 作為解釋型語言,運行效率不能算是太好,而這種機器學習基本是需要大量計算力的場合。TensorFlow 在底層是用 C++ 寫就,在 Python 端只是一個操作端口,所有的計算都要交給底層處理。這自然就引出了會話的概念,底層和調用層需要通信。也正是這個特點,TensorFlow 支持很多其他語言接入,如 Java, C,而不僅僅是 Python。 和底層通信是通過會話完成的。我們可以通過一行代碼來啟動會話:
sess = tf.Session()
# 代碼...
sess.close()
複製代碼
別忘了在使用完後關閉會話。當然你也可以使用 Python 的 with 語句來自動管理。
在 TensorFlow 中,變量都是需要在會話啟動之後初始化才能使用。
sess.run(tf.global_variables_initializer())
複製代碼
開始訓練
for i in range(DNUM):
batch_xs = [train_images.data[i]]
batch_ys = [train_labels.data[i]]
sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
複製代碼
我們把模型和訓練數據交給會話,底層就自動幫我們處理啦。 我們可以一次傳入任意數量數據給模型(上面設置None的作用),為了訓練效果,可以適當調節每一批次訓練的數據。甚至於有時候還要隨機選擇數據以獲得更好的訓練效果。在這裡我們就一條一條訓練了,反正最後效果還可以。要了解更多可以查看參考鏈接。
檢驗訓練結果
這裡我們的測試數據就要派上用場了
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
print(sess.run(accuracy, feed_dict={x: test_images.data, y_: test_labels.data}))
複製代碼
我們模型輸出是一個數組,裡面存著每個分類的概率,所以我們要拿出概率最大的分類和測試標籤比較。看在這 250 條測試數據裡面,正確率是多少。當然這些也是定義完操作步驟,交給會話來運行處理的。
提取模型使用
在上面我們已經把模型訓練好了,而且效果還不錯哦,近 99% 的正確率,或許比人工打碼還高一些呢(獲取測試數據時候常常返回有錯誤的值)。但是問題來了,我現在要把這個模型用於生產怎麼辦,總不可能每次都訓練一次吧。在這裡,我們就要使用到 TensorFlow 的模型保存和載入功能了。
保存模型
先在模型訓練的時候保存模型,定義一個 saver,然後直接把會話保存到一個目錄就好了。
saver = tf.train.Saver()
# 訓練代碼
# ...
saver.save(sess, 'model/model')
sess.close()
複製代碼
當然這裡的 saver 也有不少配置,比如保存最近多少批次的訓練結果之類,可以自行查資料。
恢復模型
同樣恢復模型也很簡單
saver.restore(sess, "model/model")
複製代碼
當然你還是需要定義好模型,才能恢復。我的理解是這裡模型保存的是訓練過程中各個變量的值,權重偏置什麼的,所以結構架子還是要事先搭好才行。
最後
這裡只是展示了使用 TensorFlow 識別簡單的驗證碼,效果還不錯,上機器學習應該也不算是殺雞用牛刀。畢竟模型無腦,節省很多時間。如果需要識別更加扭曲,更加變態的驗證碼,或許需要上卷積神經網絡之類,圖片結構和顏色信息都不能丟掉了。另一方面,做網站安全這塊,純粹的圖形驗證碼恐怕不能作為判斷是不是機器人的依據。對抗到最後,就變成這樣的變態驗證碼哈哈哈。
相關鏈接
- https://github.com/purocean/tensorflow-simple-captcha
- https://keras-cn.readthedocs.io/en/latest/for_beginners/concepts/
- http://wiki.jikexueyuan.com/project/tensorflow-zh/
閱讀更多 AI科技園 的文章