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

公司有一個業(yè)務需要抓取某網站數(shù)據寡夹,登錄需要識別驗證碼航徙,類似下面這種,這應該是很多網站使用的驗證碼類型陷虎。

首先由于驗證碼比較簡單到踏,圖像不復雜,而且全部是數(shù)字尚猿。于是試著采用傳統(tǒng)方式窝稿,按照網上教程自己簡單改了一個,使用 PHP 識別凿掂。大概流程就是切割二值化去噪等預處理伴榔,然后用字符串數(shù)組形式保存起來,識別傳來的圖片同樣預處理后比較字符串的相似度庄萎,選出一個相識度最高的分類踪少。識別率不是很理想(驗證碼比較簡單,應該能優(yōu)化得更好)糠涛,隱約記得只能超過60%援奢。

因為識別效果不理想,目標網站登錄狀態(tài)還是能保持很久忍捡,沒必要花太多精力在這上面集漾,于是找了一個人工打碼服務切黔。簡直太便宜了,一個月花不了多少錢具篇,效果還好纬霞,只是有時候延遲比較高。反正對于我們的業(yè)務來說是足夠用了驱显。

機器學習大潮來臨诗芜,我尋思著能不能用在這上面,于是參考 TensorFlow 識別手寫數(shù)字教程秒紧,開始照貓畫虎绢陌。

本文描述的只是作為一個普通開發(fā)者的一些粗淺理解,所有的代碼和數(shù)據均在文后的 GitHub 有存留熔恢,建議結合代碼閱讀本文脐湾。如果有什么理解錯誤或 Bug 歡迎留言交流 ^_^

TensorFlow 是什么

TensorFlow 是谷歌出的一款機器學習框架⌒鹛剩看名字秤掌,TensorFlow 就是“張量流”。呃鹰霍。闻鉴。什么是張量呢?張量我的理解就是數(shù)據茂洒。張量有自己的形狀孟岛,比如 0 階張量是標量,1 階是向量督勺,2 階是矩陣渠羞。。智哀。所以在后文我們會看到在 TensorFlow 里面使用的量幾乎都要定義其形狀次询,因為它們都是張量。

我們可以把 TensorFlow 看作一個黑盒子瓷叫,里面有一些架好的管道屯吊,喂給他一些“張量”,他吐出一些“張量”摹菠,吐出的東西就是我們需要的結果盒卸。

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

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

為什么使用 TensorFlow

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

大體流程

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

抓取驗證碼

這個簡單专钉,隨便什么方式,循環(huán)下載一大堆累铅,這里不再贅述跃须。我這里下載了 750 張驗證碼,用 500 張做訓練娃兽,剩下 250 張驗證模型效果菇民。

給驗證碼打標簽

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

圖片預處理

  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

# 分離數(shù)字
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))

保存數(shù)據集

數(shù)據集有輸入輸入數(shù)據和標簽數(shù)據,訓練數(shù)據和測試數(shù)據率碾。
因為數(shù)據量不大叔营,簡便起見屋彪,直接把數(shù)據存成python文件,供模型調用绒尊。就不保存為其他文件畜挥,然后用 pandas 什么的來讀取了。

最終我們的輸入模型的數(shù)據形狀為 [[0,1,0,1,0,1,0,1...],[0,1,0,1,0,1,0,1...],...]
標簽數(shù)據很特殊婴谱,本質上我們是對輸入的數(shù)據進行分類蟹但,所以雖然標簽應該是0到9的數(shù)字,但是這里我們使標簽數(shù)據格式是 one-hot vectors [[1,0,0,0,0,0,0,0,0,0,0],...]
一個one-hot向量除了某一位的數(shù)字是1以外其余各維度數(shù)字都是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 向量其實代表著對應的數(shù)據分成這十類的概率瘟裸。概率為1就是正確的分類客叉。

相關 Python 代碼如下:

# 保存輸入數(shù)據
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")

# 保存標簽數(shù)據
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")

經過上面兩步,我們在就獲得了訓練和測試用的數(shù)據和標簽數(shù)據景描,吶十办,就像這樣

構建模型訓練

數(shù)據準備好啦,到了要搭建“管道”的時候了超棺。
也就是你需要告訴 TensorFlow:

1. 輸入數(shù)據的形狀是怎樣的向族?

x = tf.placeholder(tf.float32, [None, DLEN])

None 表示不定義我們有多少訓練數(shù)據,DLEN是 16*21棠绘,即一維化的圖片的大小件相。

2. 輸出數(shù)據的形狀是怎樣的?

y_ = tf.placeholder("float", [None, 10])

同樣None 表示不定義我們有多少訓練數(shù)據氧苍,10 就是標簽數(shù)據的維度夜矗,即圖片有 10 個分類。每個分類對應著一個概率让虐,所以是浮點類型紊撕。

3. 輸入數(shù)據,模型赡突,標簽數(shù)據怎樣擬合对扶?

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 中的變量惭缰,他們保存著模型在訓練過程中的數(shù)據浪南,需要定義出來。而我們模型訓練的目的漱受,也就是把 W 和 b 的值確定络凿,使得這個式子可以更好的擬合數(shù)據。
softmax 是所謂的激活函數(shù),把線性的結果轉換成我們需要的樣式絮记,也就是分類概率的分布摔踱。
關于 softmax 之類更多解釋請查看參考鏈接。

4. 怎樣評估模型的好壞到千?

模型訓練就是為了使模型輸出結果和實際情況相差盡可能小昌渤。所以要定義評估方式。
這里用所謂的交叉熵來評估憔四。

cross_entropy = -tf.reduce_sum(y_*tf.log(y))

5. 怎樣最小化誤差

現(xiàn)在 TensorFlow 已經知道了足夠的信息膀息,它要做的工作就是讓模型的誤差足夠小,它會使出各種方法使上面定義的交叉熵 cross_entropy 變得盡可能小了赵。
TensorFlow 內置了不少方式可以達到這個目的潜支,不同方式有不同的特點和適用條件。在這里使用梯度下降法來實現(xiàn)這個目的柿汛。

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})

我們把模型和訓練數(shù)據交給會話丛忆,底層就自動幫我們處理啦祠汇。
我們可以一次傳入任意數(shù)量數(shù)據給模型(上面設置None的作用),為了訓練效果蘸际,可以適當調節(jié)每一批次訓練的數(shù)據。甚至于有時候還要隨機選擇數(shù)據以獲得更好的訓練效果徒扶。在這里我們就一條一條訓練了粮彤,反正最后效果還可以。要了解更多可以查看參考鏈接。

檢驗訓練結果

這里我們的測試數(shù)據就要派上用場了

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}))

我們模型輸出是一個數(shù)組导坟,里面存著每個分類的概率屿良,所以我們要拿出概率最大的分類和測試標簽比較”怪埽看在這 250 條測試數(shù)據里面尘惧,正確率是多少。當然這些也是定義完操作步驟递递,交給會話來運行處理的喷橙。


提取模型使用

在上面我們已經把模型訓練好了,而且效果還不錯哦登舞,近 99% 的正確率贰逾,或許比人工打碼還高一些呢(獲取測試數(shù)據時候常常返回有錯誤的值)。但是問題來了菠秒,我現(xiàn)在要把這個模型用于生產怎么辦疙剑,總不可能每次都訓練一次吧。在這里践叠,我們就要使用到 TensorFlow 的模型保存和載入功能了言缤。

保存模型

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

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

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

恢復模型

同樣恢復模型也很簡單

saver.restore(sess, "model/model")

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


最后

這里只是展示了使用 TensorFlow 識別簡單的驗證碼悴务,效果還不錯,上機器學習應該也不算是殺雞用牛刀譬猫。畢竟模型無腦讯檐,節(jié)省很多時間。如果需要識別更加扭曲染服,更加變態(tài)的驗證碼别洪,或許需要上卷積神經網絡之類,圖片結構和顏色信息都不能丟掉了柳刮。另一方面挖垛,做網站安全這塊痒钝,純粹的圖形驗證碼恐怕不能作為判斷是不是機器人的依據。對抗到最后痢毒,就變成這樣的變態(tài)驗證碼哈哈哈送矩。


相關鏈接

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市哪替,隨后出現(xiàn)的幾起案子栋荸,更是在濱河造成了極大的恐慌,老刑警劉巖凭舶,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晌块,死亡現(xiàn)場離奇詭異,居然都是意外死亡库快,警方通過查閱死者的電腦和手機摸袁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來义屏,“玉大人靠汁,你說我怎么就攤上這事∶鲱恚” “怎么了蝶怔?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長兄墅。 經常有香客問我踢星,道長,這世上最難降的妖魔是什么隙咸? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任沐悦,我火速辦了婚禮,結果婚禮上五督,老公的妹妹穿的比我還像新娘藏否。我一直安慰自己,他們只是感情好充包,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布副签。 她就那樣靜靜地躺著,像睡著了一般基矮。 火紅的嫁衣襯著肌膚如雪淆储。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天家浇,我揣著相機與錄音本砰,去河邊找鬼。 笑死钢悲,一個胖子當著我的面吹牛点额,可吹牛的內容都是我干的青团。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼咖楣,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了芦昔?” 一聲冷哼從身側響起诱贿,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咕缎,沒想到半個月后珠十,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡凭豪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年焙蹭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫂伞。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡孔厉,死狀恐怖,靈堂內的尸體忽然破棺而出帖努,到底是詐尸還是另有隱情撰豺,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布拼余,位于F島的核電站污桦,受9級特大地震影響,放射性物質發(fā)生泄漏匙监。R本人自食惡果不足惜凡橱,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望亭姥。 院中可真熱鬧稼钩,春花似錦、人聲如沸致份。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽氮块。三九已至绍载,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間滔蝉,已是汗流浹背击儡。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蝠引,地道東北人阳谍。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓蛀柴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親矫夯。 傳聞我的和親對象是個殘疾皇子鸽疾,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內容