Discuz验证码识别(编码篇)-写给程序员的TensorFlow教程
歡迎大家回到《寫給程序員的TensorFlow教程》系列中來,本系列希望能給廣大想轉型機器學習的程序員帶來一些不一樣的內容,我們不講公式,只調方法,不聊文獻,只說代碼。不求最好,只求有用。帶大家迅速上手TensorFlow(以下簡稱TF。我是強迫癥患者,每次都敲駝峰太累了)。
下面正式要開始了我們真正的TensorFlow編程,這篇文章主要內容分為兩部分,一部分是介紹TF的基礎知識和一些常用接口;第二部分是接著上節課的內容繼續執行我們的解題思路。
我們先進入第一部分
Part 1、TF基礎知識
雖然我們機器學習的基礎概念可以先不深究,但是TF還是得講一講的,不然咱們就算抄再多代碼也是天書,完全達不到滲透法的學習目的,所以我們先給大家講一講TF的基本代碼結構。
對于開發網頁或者移動應用來說,程序是沿著我們寫出來的代碼邏輯一步一步執行的,因此我們一般都是可以在代碼中任意地方插入print來打印我們需要了解的變量的值。但是在TF中卻不是這樣,TF中我們的代碼一般會被分為兩個模塊:
1.先預定義一張圖
注意:這個圖不是圖片的圖,而是一個節點之間相互連接的結構,為了能打擊大家的學習積極性,我先放一張簡單的圖給大家看看:
2.再循環把數據扔進這張圖中執行
舉一個不恰當的比方:
機器學習任務類似與起房子,一條一條的數據就是一塊一塊的磚,最終訓練出來的模型就是房子本身,那么相對應的起房子的流程就是:
1.先畫一個設計的圖紙
2.根據這個設計的圖紙,用磚一點一點把房子造出來
這里稍微有些不恰當的細節,不過初學者不用在意這些細節。
那么接著我們就要認識到TF中的一個最重要的概念,那就是Session,這個Session就可以理解為施工隊,Session的具體用法如下:
#創建一個施工隊
sess = tf.Session()
#把圖紙和磚交給施工隊并讓他們開始干活
sess.run(...)
是不是很簡單呢?掌握了TF代碼結構中這個最大的不一樣,剩下的相對來說就比較直觀了。
前面提到,TF代碼結構主要分為兩個部分。那么在真實的項目中,實際上會更加復雜一些,一般來說我們會把代碼分為四個部分:
1.圖定義模塊
2.數據讀取處理模塊
3.訓練執行模塊
4.模型保存模塊
也就是比基本流程多 "數據讀取處理" 和 "模型保存" 兩個模塊,由于篇幅限制,今天我們不打算去討論模型的保存,我們就把前三項完成即可。
Part 2、繼續完成上篇文章的解題思路
好了,大家看完了上面的部分,起碼應該對TF有了一個基本的認知,而且還學會了兩個函數調用,恭喜大家,離學會TF只剩下十萬八千里的距離,我們繼續努力。
說完TF基礎知識,我們回顧一下我們上篇文章里的問題和解題思路,我們的要解決的問題是識別Discuz系統中的驗證碼:
我們打算用TF來解決這個問題,那么我們的解題思路如下:
第一步:將問題分解成輸入(x)到輸出(y)這樣的結構,如Discuz驗證碼的輸入是圖片,輸出是四個字符的字符串
第二步:找到很多同時包含輸入輸出的數據,比如很多有識別結果的驗證碼圖片
第三步:針對不同問題,找到算法大神們的已經定義好的算法并實現成代碼
第四步:嘗試使用這個算法訓練這些數據,如果效果不好,算法中有一些參數可以手動調整,至于怎么調,可以參考前人經驗,也可以自己瞎調積累經驗。
第五步:寫一個程序載入模型,接受一個新的輸入值,通過模型計算出新的輸出值。
粗體部分是我們上篇文章已經完成了的步驟,這篇文章主要來完成第三步。這里我們結合本文Part 1討論的內容,我們將第三步繼續分解一下:
1.圖定義模塊
??? 針對圖像識別問題,我們決定使用CNN。
??? 大神們定義了很多CNN。但是因為我們是菜鳥,所以我們打算用最簡單的那種。
??? 我們還不會寫TF代碼,所以得先抄一個CNN的實現
2.數據讀取模塊(如果是本地的TF,請自行解決這個部分)
??? 我們的數據在哪里? - 神箭手爬下來的數據
??? 我們怎么讀取到這些數據?- 通過官方定義的接口
??? 讀入數據怎么傳給TF -抄官方的Demo
3.訓練執行模塊
??? 這個我們已經會了,創建一個施工隊,并讓他們干活
好了,思路已經很清晰了,大家可以按照這個思路去抄代碼了...
好吧,前面這句是開玩笑的,就以大家現在的水準,怎么可能能抄的出來呢。當然是我先抄下來給大家了,代碼奉上:
import tensorflow as tf
import numpy as np
import pandas as pd
######## 圖定義模塊 ########
x = tf.placeholder(tf.string,shape=[None,])
image_bin = tf.decode_base64(x)
image_bin_reshape = tf.reshape(image_bin,shape=[-1,])
images = tf.map_fn(lambda img: tf.image.decode_png(img), image_bin_reshape,dtype=tf.uint8)
image_gray = tf.image.rgb_to_grayscale(images)
image_resized = tf.image.resize_images(image_gray, [48, 48],tf.image.ResizeMethod.NEAREST_NEIGHBOR)
image_resized_float = tf.image.convert_image_dtype(image_resized, tf.float32)
y1 = tf.placeholder(tf.float32,shape=[None, 24])
y2 = tf.placeholder(tf.float32,shape=[None, 24])
y3 = tf.placeholder(tf.float32,shape=[None, 24])
y4 = tf.placeholder(tf.float32,shape=[None, 24])
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
conv2 = tf.layers.conv2d(hidden1, filters=64, kernel_size=[5, 5], padding='same')
norm2 = tf.layers.batch_normalization(conv2)
activation2 = tf.nn.relu(norm2)
pool2 = tf.layers.max_pooling2d(activation2, pool_size=[2, 2], strides=2, padding='same')
hidden2 = pool2
flatten = tf.reshape(hidden2, [-1, 12 * 12 * 64])
hidden3 = tf.layers.dense(flatten, units=1024, activation=tf.nn.relu)
letter1 = tf.layers.dense(hidden3, units=24)
letter2 = tf.layers.dense(hidden3, units=24)
letter3 = tf.layers.dense(hidden3, units=24)
letter4 = tf.layers.dense(hidden3, units=24)
letter1_cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y1, logits=letter1))
letter2_cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y2, logits=letter2))
letter3_cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y3, logits=letter3))
letter4_cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y4, logits=letter4))
loss = letter1_cross_entropy + letter2_cross_entropy+letter3_cross_entropy+letter4_cross_entropy
optimizer = tf.train.AdamOptimizer(1e-4)
train_op = optimizer.minimize(loss)
predict_concat = tf.stack([tf.argmax(letter1,1),
?????????????????????????? tf.argmax(letter2,1),
?????????????????????????? tf.argmax(letter3,1),
?????????????????????????? tf.argmax(letter4,1)],1)
y_concat = tf.stack([tf.argmax(y1,1),
???????????????????? tf.argmax(y2,1),
???????????????????? tf.argmax(y3,1),
???????????????????? tf.argmax(y4,1)],1)
accuracy_internal = tf.cast(tf.equal(predict_concat, y_concat),tf.float32),
accuracy = tf.reduce_mean(tf.reduce_min(accuracy_internal,2))
accuracy_letter =? tf.reduce_mean(tf.reshape(accuracy_internal,[-1]))
initer = tf.global_variables_initializer()
sess = tf.Session()
sess.run(initer)
######## 數據讀取模塊 ########
pickup = "BCEFGHJKMPQRTVWXY2346789"
reader = pd.read_source(souce_id=802522,iterator=True)
identity = np.identity(24)
for i in range(1000):
? df = reader.get_chunk(100)
? if df.empty:
??? reader = pd.read_source(souce_id=802522,iterator=True)
??? continue
? batch_y = df["code"].values
? batch_x = df["content"].values
? batch_y_1 = [identity[pickup.find(code[0])] for code in batch_y]
? batch_y_2 = [identity[pickup.find(code[1])] for code in batch_y]
? batch_y_3 = [identity[pickup.find(code[2])] for code in batch_y]
? batch_y_4 = [identity[pickup.find(code[3])] for code in batch_y]
? ######## 訓練執行模塊 ########
? if i%10 == 0:
??? print("step:"+str(i))
??? accuracy_letter_,accuracy_ = sess.run([accuracy_letter,accuracy],feed_dict={x:batch_x,y1:batch_y_1,y2:batch_y_2,y3:batch_y_3,y4:batch_y_4})
??? print(accuracy_letter_)
??? print("accuracy is ====>%f"%accuracy_)
??? if accuracy_ > 0.80:
????? break
? sess.run(train_op,feed_dict={x:batch_x,y1:batch_y_1,y2:batch_y_2,y3:batch_y_3,y4:batch_y_4})
特別說明兩點!!!!!
1.這段代碼由于有數據讀取處理模塊,這個調用了pd.read_source這個方法,因此只能在神箭手上執行,如果希望本地執行的朋友,需要替換pd.read_source這個方法
2.pd.read_source里面的source_id是我的數據id,大家是讀取不了了,需要用我上一篇文章中的爬蟲代碼跑出來的數據對應的數據id替換進去即可。
好了,肯定有朋友要說了,這么一長串,我抄下來還是不理解啊。沒關系,咱們先把代碼跑通其實最重要,后面我們會需要不斷的改動這段代碼,改著改著就懂了。
關于這段代碼,我在補充以下幾點,大家可以帶著這些問題去把代碼看一看,嘗試簡單理解下:
一、關于圖定義部分
從x到accuracy_letter都是定義圖的部分,這張圖看著很大,不過其實包含了三個部分,
1.從x到image_x是數據預處理部分(等等,圖里面怎么也有預處理?這個我在下一篇文章中會講到,網上一般代碼是不會這么寫的)
2.從image_x到train_op是核心需要訓練的圖部分,這部分圖是核心的CNN,是我根據從github中找來的代碼簡化的,值得注意的是這里的CNN跟傳統的CNN不一樣,因為我們要輸出4個結果(就是驗證碼的4個字符),一般的識別只輸出一個結果(比如是貓,還是狗,還是兔子),不過這個不一樣還是很簡單的,后面我們進一步過代碼的時候,大家就會明白。(另外,由于我們數據集可以是無窮大,所以我扔掉防止過擬合的代碼,讓訓練能快一點)
3.從train_op到accuracy_letter是我們計算我們訓練結果準確率的部分,是用來判斷目前模型的表現的,有沒有不影響核心功能。
二、關于數據處理部分
神箭手官方的接口是給python的數據清洗類庫pandas增加了一個read_source方法,通過get_chunk返回的是一個標準的pandas的dataframe對象,用過pandas的朋友應該很熟悉,沒用過的直接抄就行,這段代碼在大部分時候都不用改。
特別注意的是,我們數據中的輸出部分y(就是驗證碼的識別結果),是一個四個字符的字符串,但是這樣子是不能傳給TF的,TF需要另外一種格式的數據y(one-hot encoding),轉換代碼就是中間一段,我單獨截取出來看下,暫時不理解的也無所謂(不過建議大家可以打印下這個one-hot encoding一般是什么樣子的,有個更直觀的理解)。
pickup = "BCEFGHJKMPQRTVWXY2346789"
identity = np.identity(24)
batch_y_1 = [identity[pickup.find(code[0])] for code in batch_y]
batch_y_2 = [identity[pickup.find(code[1])] for code in batch_y]
batch_y_3 = [identity[pickup.find(code[2])] for code in batch_y]
batch_y_4 = [identity[pickup.find(code[3])] for code in batch_y]
三、關于訓練執行模塊
傳統的話我們應該把我們的數據集分成三部分,訓練集,驗證集和測試集。是不是聽著就很麻煩,不過因為我們的數據集可以是無限大,那就不用這么麻煩了,直接就用訓練集做驗證了。正常情況來說起碼還是得分一個驗證集,這個我們后面再討論。
我們計算了兩個準確值accuracy_letter和accuracy,accuracy_letter是計算所有字符的準確度,accuracy是計算組合成字符串后的準確度,例如我們的標準答案是:5CD4,8ACF 兩個結果,而我們計算出來的答案是3CE4,8ACF,那么我們accuracy_letter就是6/8=0.75,因為只有第一個5和第三個D算錯了,但是accuracy就是1/2=0.5,因為從字符串角度來說,整個第一個字符串識別錯了,只有第二個是對的。這里我們關心的實際上是accuracy,但是由于accuracy變動很慢,我們通過觀察accuracy_letter來判斷我們的模型到底跑怎么樣了。
好了,如果大家之前已經把數據爬好了,那么這個代碼直接復制到神箭手應用中(本地朋友替換掉read_source代碼既可直接運行),再修改數據id(source_id)就可以直接運行了,我們可以從日志中查看準確率:
?
====================================================================
如果是剛入門的朋友,今天看到這么多代碼肯定還是很蒙的。不要緊,大家只需要掌握文章中提到的以下幾個要點既可:
1.TF的代碼結構
2.核心類Session的使用
3.了解除了圖定義以外的其他代碼的含義。代碼不多,最大的難點是one-hot encoding的轉換,大家可以參考代碼多寫幾次就既可,至于為什么要轉換成這么詭異的樣子,主要是因為這樣子更適合訓練中的向量計算,當然你也可以理解為大家都說這么做好,所以我們也這么做就行了,其實深度學習中有大量的這種經驗性的東西,不太用深究。
目前的代碼雖然可以訓練模型,但是不會保存,無法繼續訓練,更無法上線使用。下篇文章將在這篇文章的代碼的基礎上,完成最后一棒:就是模型的部署上線。
總結
以上是生活随笔為你收集整理的Discuz验证码识别(编码篇)-写给程序员的TensorFlow教程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Discuz验证码识别(上线篇)-写给程
- 下一篇: 学习机器学习:这10年我们能在各自的领域