使用 TensorFlow 識別簡單圖像驗證碼

使用 TensorFlow 識別簡單圖像驗證碼

公司有一個業務需要抓取某網站數據,登錄需要識別驗證碼,類似下面這種,這應該是很多網站使用的驗證碼類型。

使用 TensorFlow 識別簡單圖像驗證碼

首先由於驗證碼比較簡單,圖像不復雜,而且全部是數字。於是試著採用傳統方式,按照網上教程自己簡單改了一個,使用 PHP 識別。大概流程就是切割二值化去噪等預處理,然後用字符串數組形式保存起來,識別傳來的圖片同樣預處理後比較字符串的相似度,選出一個相識度最高的分類。識別率不是很理想(驗證碼比較簡單,應該能優化得更好),隱約記得只能超過60%。

因為識別效果不理想,目標網站登錄狀態還是能保持很久,沒必要花太多精力在這上面,於是找了一個人工打碼服務。簡直太便宜了,一個月花不了多少錢,效果還好,只是有時候延遲比較高。反正對於我們的業務來說是足夠用了。

機器學習大潮來臨,我尋思著能不能用在這上面,於是參考 TensorFlow 識別手寫數字教程,開始照貓畫虎。

本文描述的只是作為一個普通開發者的一些粗淺理解,所有的代碼和數據均在文後的 GitHub 有存留,建議結合代碼閱讀本文。如果有什麼理解錯誤或 Bug 歡迎留言交流 ^_^

TensorFlow 是什麼

TensorFlow 是谷歌出的一款機器學習框架。看名字,TensorFlow 就是“張量流”。呃。。什麼是張量呢?張量我的理解就是數據。張量有自己的形狀,比如 0 階張量是標量,1 階是向量,2 階是矩陣。。。所以在後文我們會看到在 TensorFlow 裡面使用的量幾乎都要定義其形狀,因為它們都是張量。

我們可以把 TensorFlow 看作一個黑盒子,裡面有一些架好的管道,餵給他一些“張量”,他吐出一些“張量”,吐出的東西就是我們需要的結果。

所以我們需要確定喂進去的是什麼,吐出來的是什麼,管道如何搭建。

更多的入門概念可以查看這個 keras新手指南 » 一些基本概念

為什麼使用 TensorFlow

沒別的什麼原因,只是因為谷歌大名,也沒想更多。先擼起袖子幹起來。如果為了快速成型,我建議可以看一下 Keras,號稱為人類設計的機器學習框架,也就是用戶體驗友好,提供好幾個機器學習框架更高層的接口。

大體流程

  1. 抓取驗證碼
  2. 給驗證碼打標籤
  3. 圖片預處理
  4. 保存數據集
  5. 構建模型訓練
  6. 提取模型使用

抓取驗證碼

這個簡單,隨便什麼方式,循環下載一大堆,這裡不再贅述。我這裡下載了 750 張驗證碼,用 500 張做訓練,剩下 250 張驗證模型效果。

使用 TensorFlow 識別簡單圖像驗證碼

給驗證碼打標籤

這裡的驗證碼有750張之巨,要是手工給每個驗證碼打標籤,那一定累尿了。這時候就可以使用人工打碼服務,用廉價勞動力幫我們做這件事。人工打碼後把識別結果保存下來。這裡的代碼就不提供了,看你用哪家的驗證碼服務,相信聰明的你一定能解決 :)

使用 TensorFlow 識別簡單圖像驗證碼

圖片預處理

  1. 圖片信息: 此驗證碼是 68x23,JPG格式
  2. 二值化: 我確信這個驗證碼足夠簡單,在丟失圖片的顏色信息後仍然能被很好的識別。並且可以降低模型複雜度,因此我們可以將圖片二值化。即只有兩個顏色,全黑或者全白。
  3. 切割驗證碼: 觀察驗證碼,沒有特別扭曲或者粘連,所以我們可以把驗證碼平均切割成4塊,分別識別,這樣圖片識別模型就只需要處理10個分類(如果有字母那將是36個分類而已)由於驗證碼外面有一圈邊框,所以順帶把邊框也去掉了。
  4. 處理結果: 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 識別簡單圖像驗證碼

構建模型訓練

數據準備好啦,到了要搭建“管道”的時候了。 也就是你需要告訴 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 條測試數據裡面,正確率是多少。當然這些也是定義完操作步驟,交給會話來運行處理的。

使用 TensorFlow 識別簡單圖像驗證碼

提取模型使用

在上面我們已經把模型訓練好了,而且效果還不錯哦,近 99% 的正確率,或許比人工打碼還高一些呢(獲取測試數據時候常常返回有錯誤的值)。但是問題來了,我現在要把這個模型用於生產怎麼辦,總不可能每次都訓練一次吧。在這裡,我們就要使用到 TensorFlow 的模型保存和載入功能了。

保存模型

先在模型訓練的時候保存模型,定義一個 saver,然後直接把會話保存到一個目錄就好了。

saver = tf.train.Saver()
# 訓練代碼
# ...
saver.save(sess, 'model/model')
sess.close()
複製代碼

當然這裡的 saver 也有不少配置,比如保存最近多少批次的訓練結果之類,可以自行查資料。

恢復模型

同樣恢復模型也很簡單

saver.restore(sess, "model/model")
複製代碼

當然你還是需要定義好模型,才能恢復。我的理解是這裡模型保存的是訓練過程中各個變量的值,權重偏置什麼的,所以結構架子還是要事先搭好才行。

使用 TensorFlow 識別簡單圖像驗證碼

最後

這裡只是展示了使用 TensorFlow 識別簡單的驗證碼,效果還不錯,上機器學習應該也不算是殺雞用牛刀。畢竟模型無腦,節省很多時間。如果需要識別更加扭曲,更加變態的驗證碼,或許需要上卷積神經網絡之類,圖片結構和顏色信息都不能丟掉了。另一方面,做網站安全這塊,純粹的圖形驗證碼恐怕不能作為判斷是不是機器人的依據。對抗到最後,就變成這樣的變態驗證碼哈哈哈。

使用 TensorFlow 識別簡單圖像驗證碼

相關鏈接

  1. https://github.com/purocean/tensorflow-simple-captcha
  2. https://keras-cn.readthedocs.io/en/latest/for_beginners/concepts/
  3. http://wiki.jikexueyuan.com/project/tensorflow-zh/
"


分享到:


相關文章: