來源 |《Python人工智慧開發從入門到精通》
作者 | 楊柳、郭坦、魯銀芝
責編 | 晉兆雨
深度學習在技術與應用上的突破引發了第三次人工智慧浪潮,獲得了空前成功。在前述章節的基礎上,本章將主要介紹訓練卷積神經網絡和深度神經網絡的重要方法與技巧,深度神經網絡的遷移學習策略,以及如何訓練深度神經網絡以解決實際問題等內容。作為人工智慧的核心研究內容,以卷積神經網絡(ConvolutionalNeural Networks, CNNs)為代表的深度學習技術已在計算機視覺應用,如智能監控、智慧醫療及機器人自動駕駛等領域取得突破性進展,而這些應用的成功落地很大程度上依賴於視覺識別模塊。
結合前文內容,本章將詳細介紹如何構建並利用CNNs 這一功能強大的深度學習模型解決實際的圖像識別問題。
*文末有贈書福利
受20世紀中期興起的神經科學及腦科學研究的啟發,通過模擬生物神經元接收和處理信息的基本特性,研究人員提出並設計了人工神經元。作為計算機科學、生物學和數學的交叉融合,卷積神經網絡已經發展成為計算機視覺領域中最具影響力和有效的基礎技術。
早在20 世紀60 年代,生物學家Hubel 和Wiesel 通過研究貓的視覺皮層,發現每個視覺神經元都只對一個小區域範圍內的視覺圖像產生響應,即感受野(Receptive Field)。初級視覺皮層中的神經元能夠響應視覺環境中特定的簡單特徵,除此之外,Hubel 和Wiesel 通過研究發現了簡單和複雜兩種不同類型的細胞,其中簡單細胞只在特定的空間位置對它們偏好的方向產生最強烈響應,而複雜細胞具有更大的空間不變性。
根據這些實驗和分析,他們得出結論:複雜細胞通過在來自多個簡單細胞(每個都有一個不同的偏好位置)的輸入進行池化而實現這種不變性,這兩個特性,即對特定特徵的選擇性和通過前饋連接增大空間不變性,構成了CNN人工視覺系統的生物及神經學基礎。
發展至80年代,日本科學家Kunihiko Fukushima 通過研究並融合有關生物視覺的相關領域知識,提出了Neocognitron 神經認知機的概念,該神經認知機由S 細胞和C 細胞構成,可通過無監督的方式學習識別簡單的圖像。20 世紀90 年代,Yann LeCun 等人發表論文,確立了CNN 影響至今的經典網絡結構,後來經過對網絡結構的不斷完善與改進,得到一種多層的人工神經網絡,命名為LeNet-5,在手寫數字識別任務上取得良好效果。和其他神經網絡一樣,LeNet-5能夠使用反向傳播算法(Back Propagation)訓練。
LeNet-5 網絡雖然較小,但它含有諸多神經網絡學習的關鍵模塊,具體包括卷積層、池化層及全連接層,這些基本模塊構成當前深度神經網絡模型的基礎,下文將對LeNet-5 的結構及工作原理進行深入分析。同時,藉助實例加深讀者對卷積神經網絡各個模塊功能的理解。
卷積神經網絡與LeNet-5
LeNet-5 出自Yann LeCun 教授於1998 發表的論文Gradient-Based Learning Applied to DocumentRecognition 中,LeNet-5 模型共有7 層,如圖11-1 所示為LeNet-5 的基本網絡架構。
LeNet-5 的基本網絡架構
該模型除了輸入層之外,每層都包含可訓練參數,每個網絡層產生多個特徵圖,每個特徵圖可通過一種卷積濾波器提取輸入數據一種類型的特徵。各個網絡層的功能與參數情況介紹如下。
1. 輸入層
首先是輸入數據網絡層,上例中輸入圖像尺寸統一歸一化為32×32×1,其中1 表示輸入圖像為單通道的灰度圖,一般不將該層作為LeNet-5 網絡的基本構成,即不將輸入層視為網絡層次結構之一。
2. C1 層
C 取自Convolutional 的首字母,指卷積。讀者可能對卷積的概念並不陌生,對數字圖像做卷積運算,本質上是通過卷積核(卷積模板)在圖像上滑動,將圖像上的像素灰度值與對應卷積核上的數值相乘,然後將所有相乘後的值相加作為卷積核中間像素對應像素的灰度值,以此方式遍歷完成對整張圖像像素的卷積計算。如圖11-2顯示了圖像卷積計算過程中一次相乘後相加的運算過程,該卷積核大小為3×3,卷積核內共有9 個數值,數值個數即為圖像像素值與卷積核上數值相乘次數,運算結果-4 代替了原圖像中對應位置處的值。按此方式,沿著圖片以步長為1 滑動,每次滑動1個像素都進行一次相乘再相加的操作,即可得到最終的輸出結果。
卷積計算過程
圖像卷積計算中,卷積核的設計十分重要,一般需遵循如下基本規則。
卷積核大小一般是奇數,奇數大小的卷積核使得卷積核關於中間像素點中心對稱,因此卷積核尺寸一般是3×3、5×5或7×7。卷積核有中心,相應地就有半徑的概念,如7×7 大小的卷積核,其半徑為3。卷積核所有的元素之和一般應等於1,這是為了保持圖像卷積計算過程中像素能量(亮度)的守恆。若濾波器矩陣所有元素之和大於1,那麼濾波後的圖像就會比原圖像更亮;反之,若小於1,那麼得到的圖像將會變暗。濾波後可能會出現負數或大於255 的數值。對這種情況,通常將它們直接截斷到0~255之間即可。而對於負數,也可以取絕對值。經卷積計算所得輸出通常被稱為「響應」,如果是邊緣檢測算子,那麼響應為圖像邊緣,能夠檢測到特定的圖像邊緣。在LeNet-5網絡中得到的響應是特徵圖(Feature Map),計算結果為輸入圖像的特徵表達,卷積核的參數權重可以通過優化算法在監督信息的指導下自適應地學習得到。LeNet-5 網絡中C1 層輸入圖像尺寸為32×32×1,卷積核大小為5×5,一共包括6 種大小為5×5 的卷積核,卷積核滑動一行之後,得到的結果的邊長變為32-5+1,提取的特徵映射大小是28×28,即(32-5+1)=28。6種不同的卷積核,可以從不同的角度提取圖像不同特性的特徵。
神經元數量為28×28×6,則可訓練參數為(5×5+1)×6,即每個濾波器含5×5=25個單元權值參數和1個偏置參數,一共6 個濾波器,因此總的連接數為(5×5+1)×6×28×28=122 304。針對122 304 個連接,通過權值共享策略,只需學習156 個參數。
3. S2 層
S 指的是Subsamples,該網絡層完成下採樣操作,得到對應的特徵圖。下採樣的原則是在減少數據量的同時儘可能保留有用的信息。與普通插值下採樣的方式不同,該層實際採用的是一種被稱為池化(Pooling)的方法。具體是將一幅圖像分割成若干塊,每個圖像塊的輸出是該圖像塊原有像素的統計結果。
圖像下採樣池化方法有很多,如Mean-pooling( 均值採樣)、Max-pooling( 最大值採樣)、Overlapping ( 重疊採樣)、L2-pooling( 均方採樣)、Local Contrast Normalization( 局部對比歸一化)、Stochastic-pooling( 隨機採樣) 和Def-pooling( 形變約束採樣) 等,其中最經典的是最大池化,也是最常用的,下面簡要介紹最大池化的實現原理。
為直觀起見,假設有如圖11-3(a)中大小為4×4 的圖像,圖像中每個像素點的值是上面各個格子中的數值。現在對這張4×4 大小的圖像進行池化操作,池化的大小為(2,2),步長為2。採用最大池化操作,首先對圖像進行分塊,每個圖像塊大小為2×2,然後按照圖11-3(b)中方式統計每個圖像塊的最大值,作為下採樣後圖像的像素值,得到圖11-3(c)中結果,該過程即為最大池化。
除此之外,還有其他池化方法,如均值池化,具體是對每個塊求取平均值作為下採樣的新像素值。上述例子未涉及重疊採樣,即每個圖像塊之間沒有相互重疊的部分,而步長為2 時,圖像分塊不重疊。
最大池化操作示意圖
LeNet-5 網絡中的S2 層的輸入是上一層的輸出,共有6 個特徵映射,每個特徵映射的尺寸為28×28,使用2×2 大小的核進行池化操作,得到S2,即6 個14×14 大小的特徵映射(28/2=14)。換言之,S2 中的池化層是對C1 中的2×2 區域內的像素求和乘以一個權值係數再加上一個偏置,然後將這個結果再做一次映射。與卷積層連接數的計算方法一樣,連接數=參數個數×特徵映射大小,即(2×2+1)×6×14×14=5880。
4. C3 層
C3 層同樣是卷積層,輸入為S2 中所有6 個或若干個特徵圖的組合。具體地,該層卷積核大小為5×5,一共有6種卷積核,輸出特徵圖大小為10×10,即(14-5+1)=10。需要注意的是,C3 中每個特徵圖是連接到S2 中的所有6 個或若干個特徵圖的,即該層的特徵圖是上一層提取到的特徵圖的不同組合。如圖11-4 所示,LeCun 在原論文中給出的一種組合連接方式。
LeNet-5 網絡C3 層特徵映射組合方式
圖11-4 中共有6 行16 列,橫軸代表C3 特徵映射索引,縱軸代表S2 特徵圖索引。每列的X表示C3 中的每個特徵映射與S2 中的特徵圖的連接情況,可以看到C3 的前6 個特徵圖,對應上圖第1 個紅框的6 列,以S2 中3 個相鄰的特徵圖子集為輸入,緊接著6 個特徵圖(對應上圖第2 個紅框的6 列)以S2 中4 個相鄰特徵圖子集為輸入。
然後,接下來的3 個特徵圖(對應上圖第3 個紅框的3 列)以S2 中不相鄰的4 個特徵圖子集為輸入,C3 中的最後一個特徵圖對應上圖第4 個紅框的1 列將S2 中所有特徵圖為輸入。這裡得到的每一個特徵圖為多核多通道卷積,將每一列稱為一個卷積核,它由若干個卷積模板構成,因為是多個卷積核模板卷積的結果得到一個特徵圖,仍然認為是一個卷積核,所以每列只有一個偏置參數。之所以採取這種組合方式,LeCun 主要是基於以下兩點考慮:減少參數;採用不對稱的組合連接方式有利於提取多種組合特徵。
5. S4 層
S4 層為下採樣層,即池化層,窗口大小為2×2,包括16 個特徵圖,C3 層的16 個10×10 的特徵圖分別進行以2×2 為單位的池化得到16 個5×5 的特徵圖,步長為2,即本網絡層的輸出張量大小為5×5×16,一共有5×5×5×16=2000 個連接,連接的方式與S2 層類似。
6. C5 層
C5 層是一個卷積層,輸入為S4 層的全部16 個特徵圖,該層卷積原理與普通卷積層一致,只是因為恰巧卷積核大小與輸入特徵圖尺寸一樣,因此得到一維,即1×1(5-5+1)的輸出,卷積核種類為120,得到120 維的卷積結果,每個都與上一層的16 個特徵圖相連,因此一共有(5×5×16+1)×120=48 120 個可訓練參數。
7. F6 層
F6 層是全連接層,採用全連接的方式與C5 層連接,由對C5 層的輸入乘以權重加上偏置,結果通過激活Sigmoid 函數輸出。F6 層有84 個節點,對應於一個7×12 的比特圖,-1 表示白色,1 表示黑色,這樣每個符號的比特圖的黑白色對應一個編碼,F6 層的訓練參數/ 連接數為(120+1)×84=10 164。
8. Output 輸出層
Output 輸出層同樣是全連接層,共有10 個節點,分別代表數字0~9,且如果節點i 的值為0,則網絡識別的結果是數字i。採用的是徑向基函數(RBF)的網絡連接方式。假設x 是上一層的輸入,y 是RBF 的輸出,則RBF 輸出的計算方式如式(11-1)所示。
式中wij 的值由i 的比特圖編碼確定,i 取值為0~9,j 取值為0~(7*12-1)。RBF 輸出的值越接近於0,表示當前網絡的輸入越接近於i,即越接近於i 的ASCII 編碼,該層一共包含84×10=840 個可學習參數。卷積神經網絡在本質上是在不需要獲取輸入和輸出之間精確的數學表達的情況下,學習從輸入數據到目標輸出的複雜映射,卷積神經網絡的優勢在於能夠很好地利用圖像的二維結構信息,LeNet-5 在銀行支票手寫體字符識別問題上得到成功應用。
不考慮輸入層,LeNet-5 是一個7 層的網絡,卷積層的參數較少,這得益於卷積層的若干重要特性,即局部連接和共享權重。現在常用的LeNet-5 結構和Yann LeCun 教授在1998 年論文中提出的結構在某些細節上存在一定的區別,如激活函數的使用,現在一般使用ReLU 作為激活函數,而輸出層一般選用Softmax。CNN 能夠提取原始圖像的有效表徵,這賦予CNN 經過較少的預處理,即可從原始像素中學習和識別視覺規律的能力。然而,由於LeNet-5 提出伊始,缺乏大規模的訓練數據,計算機的計算能力也難以滿足要求,CNN 的網絡架構在不同文獻中的描述略有差異。
不過,CNN 的基本組成單元和模塊相對一致,可以像搭積木一樣將不同功能的網絡層組合起來,從而實現規模更大、深度更深的網絡。因此,從某種意義上說,CNN 或深度學習中的網絡層本質上是能夠進行信息處理的積木單元。LeNet-5 對於更複雜問題的處理效果並不理想,但通過對LeNet-5 的網絡結構的分析與研究,可以直觀地了解卷積神經網絡的構建方法,能夠為分析和構建更複雜、更深層的卷積神經網絡打下堅實的基礎。
總結卷積神經網絡的成功經驗,主要在於局部連接(LocalConnection)、權值共享(Weight Sharing)和池化層(Pooling)中的降採樣(Down-Sampling)。
(1)卷積層(Convolutions Layer)。卷積層由很多的卷積核(Convolutional Kernel)組成,卷積核用來計算不同的特徵圖,卷積層是卷積神經網絡的核心。在圖像識別裡用到的卷積是二維卷積,具體是二維濾波器滑動到二維圖像上所有位置,並在每個位置上與該像素點及其領域像素點做內積。卷積操作被廣泛應用於圖像處理領域,不同類型的卷積核可以提取圖像不同類型的特徵,例如,邊緣、角點等特徵。在深層卷積神經網絡中,通過卷積操作可以提取出圖像低級簡單到抽象複雜的特徵,學習輸入數據具有較強普適性的特徵表達。除此之外,激活函數能夠為CNN 卷積神經網絡引入非線性,增強網絡的複雜建模能力,常用的非線性激活函數有Sigmoid、Tanh 和ReLu 等,前兩者常見於全連接層,後者ReLu 則多用於卷積層。
(2)池化層(Pooling Layer)。池化是非線性下採樣的一種形式,主要作用是通過減少網絡的參數來減小計算量,同時池化層能降低卷積層輸出的特徵向量,通常在卷積層的後面會加上一個池化層,通過卷積層與池化層交替使用可以獲得更複雜的高層抽象特徵,並且能夠在一定程度上避免和緩解過擬合現象。常用的池化操作包括最大池化、平均池化等,其中最大池化是用不重疊的矩形框將輸入層分成不同的區域,對於每個矩形框內的數值取最大值作為統計輸出。
(3)全連接層(Full Connected Layer)。如果說卷積層、池化層和激活函數映射等操作是將原始數據映射到隱層特徵空間的話,那麼全連接層則起到將學到的「分布式特徵表示」映射到樣本標記空間的作用,將多層的特徵表達拉直成一個一維的向量,實現神經網絡的高層抽象推理能力,在整個卷積神經網絡中起到「分類器」的作用。
(4)局部連接(Local Connection)。局部連接指的是每個神經元僅與輸入神經元的一塊區域相連,該局部區域也被稱為感受野(Receptive Field)。局部連接的思想可追溯至生物學裡面的視覺系統結構,即視覺皮層的神經元實質上是局部接收信息的。在圖像卷積操作中,神經元在空間維度上是局部連接的,但在深度上是全部連接的。對於二維圖像本身而言,局部像素關聯較強,這種局部連接保證了學習後的過濾器能夠對於局部的輸入特徵有最強的響應。
(5)權重共享(Weight Sharing)。實際中,圖像的底層邊緣特徵與特徵在圖中的具體位置無關,即特徵可能出現在圖像的任意位置,權重共享正是利用這一特點,具體是指卷積核內權重參數被整張圖共享,而不會因圖像內位置的不同而改變,可在圖像中的不同位置學習到同樣的特徵,權重共享可以在很大程度上減少參數數量。
LeNet-5 的TensorFlow 實現
前文介紹了LeNet-5 的基本網絡結構,以及各個網絡功能層的特點與作用,本節將利用TensorFlow 具體實現這一網絡。首先需要說明以下幾點。
(1)LeNet-5 主要採用Tanh 和Sigmoid 作為非線性激活函數,但相對這兩者採用ReLu 激活函數的卷積神經網絡更加有效。
(2)LeNet-5 採用平均池化作為下採樣操作,但是目前最大池化操作應用更為廣泛。
(3)LeNet-5 網絡最後一層採用Gaussian 連接層,用於輸出0~9 這10 個類別中的一類,但是目前分類器操作已經被Softmax 層取代。
第1 步:建立config.py 文件,可以將超參數設置在config.py 中,方便後期對模型進行調整。代碼實現與說明如程序清單11-1 所示。
程序清單11-1 config.py 文件建立及超參數設置
1. """2. 設置模型的超參數3.4.5. KEEP_PROB: 網絡隨機失活的概率6. LEARNING_RATE: 學習的速率,即梯度下降的速率7. BATCH_SIZE: 一次訓練所選取的樣本數8. PARAMETER_FILE: 模型參數保存的路徑9. MAX_ITER: 最大迭代次數10. """11.12. KEEP_PROB = 0.513. LEARNING_RATE = 1e-514. BATCH_SIZE =5015. PARAMETER_FILE = "checkpoint/variable.ckpt"16. MAX_ITER = 50000第2 步:構建LeNet 模型的LeNet.py 文件,建立一個名為Lenet 的類,類中實現模型的初始化與構建,代碼實現與說明如程序清單11-2 所示。
程序清單11-2 構建LeNet 模型與LeNet.py 文件
1. import tensorflow as tf2. import tensorflow.contrib.slim as slim3. import config as cfg4.5. class Lenet:6. def __init__(self):7. """8. 初始化LeNet 網絡9. """10. # 設置網絡輸入的圖片為二維張量,數據的類型為float32,行數不固定,列固定為78411. self.raw_input_image = tf.placeholder(tf.float32, [None, 784])12.13. # 改變網絡輸入張量的形狀為四維,-1 表示數值不固定14. self.input_images = tf.reshape(self.raw_input_image, [-1, 28, 28, 1])15.16. # 設置網絡輸入標籤為二維張量,數據類型為float,行數不固定,列固定為1017. self.raw_input_label = tf.placeholder("float", [None, 10])18.19. # 改變標籤的數據類型為int3220. self.input_labels = tf.cast(self.raw_input_label,tf.int32)21.22. # 設置網絡的隨機失活概率23. self.dropout = cfg.KEEP_PROB24.25. # 構建兩個網絡26. # train_digits 為訓練網絡,開啟dropout27. # pred_digits 為預測網絡,關閉dropout28. with tf.variable_scope("Lenet") as scope:29. self.train_digits = self.construct_net(True)30. scope.reuse_variables31. self.pred_digits = self.construct_net(False)32.33. # 獲取網絡的預測數值34. self.prediction = tf.argmax(self.pred_digits, 1)35.36. # 獲取網絡的預測數值與標籤的匹配程度37. self.correct_prediction = tf.equal(tf.argmax(self.pred_digits, 1), tf.argmax(self.input_labels, 1))38.39. # 將匹配程度轉換為float 類型,表示為精度40. self.train_accuracy = tf.reduce_mean(tf.cast(self.correct_prediction, "float"))41.42. # 計算train_digits 與labels 之間的係數softmax 交叉熵,定義為loss43. self.loss = slim.losses.softmax_cross_entropy(self.train_digits, self.input_labels)44.45. # 設置學習速率46. self.lr = cfg.LEARNING_RATE47. self.train_op = tf.train.AdamOptimizer(self.lr).minimize(self.loss)48.49.50. defconstruct_net(self,is_trained = True):51. """52. 接收is_trained 參數判斷是否開啟dropout53. 用slim 構建LeNet 模型54. 第一、三、五層為卷積層、第二、四層為池化層55. 接下來對第五層扁平化,再接入全連接56. 接著進行隨機失活防止過擬合,再次接入全連接層57. 最後返回構建的網絡58. """59. with slim.arg_scope([slim.conv2d], padding='VALID',60. weights_initializer=tf.truncated_normal_initializer(stddev=0.01),61. weights_regularizer=slim.l2_regularizer(0.0005)):62. net = slim.conv2d(self.input_images,6,[5,5],1,padding='SAME',scope='conv1')63. net = slim.max_pool2d(net, [2, 2], scope='pool2')64. net = slim.conv2d(net,16,[5,5],1,scope='conv3')65. net = slim.max_pool2d(net, [2, 2], scope='pool4')66. net = slim.conv2d(net,120,[5,5],1,scope='conv5')67. net = slim.flatten(net, scope='flat6')68. net = slim.fully_connected(net, 84, scope='fc7')69. net = slim.dropout(net, self.dropout,is_training=is_trained, scope='dropout8')70. digits = slim.fully_connected(net, 10, scope='fc9')71. return digits本模型是對著名的手寫字體MNIST數據集進行訓練,可以在網站http://yann.lecun.com/exdb/mnist/ 上很方便地直接下載數據,得到如圖11-5 的MNIST 數據集。
MNIST 數據集列表
第3 步:建立模型訓練文件Train.py,主要實現數據讀取、模型訓練等功能,代碼實現與說明如程序清單11-3 所示。
程序清單11-3 模型訓練文件Train.py 的建立
1. import tensorflow.examples.tutorials.mnist.input_data as input_data2. import tensorflow as tf3. import config as cfg4. import os5. import lenet6. from lenet import Lenet7.8.9. def main:10. # 從指定路徑加載訓練數據11. mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)12.13. # 開啟TensorFlow 會話14. sess = tf.Session15.16. # 設置超參數17. batch_size = cfg.BATCH_SIZE18. parameter_path = cfg.PARAMETER_FILE19. lenet = Lenet20. max_iter = cfg.MAX_ITER21.22. # 加載已保存的模型參數文件,如果不存在則調用初始化函數生成初始網絡23. saver = tf.train.Saver24. if os.path.exists(parameter_path):25. saver.restore(parameter_path)26. else:27. sess.run(tf.initialize_all_variables)28.29. # 迭代訓練max_iter 次,每次抽取50 個樣本進行訓練30. # 每100 次列印出當前數據的精度31. # 訓練完成後保存模型參數32. for i in range(max_iter):33. batch = mnist.train.next_batch(50)34. if i % 100 == 0:35. train_accuracy = sess.run(lenet.train_accuracy,feed_dict={36. lenet.raw_input_image: batch[0],lenet.raw_input_label: batch[1]37. })38. print("step %d, training accuracy %g" % (i, train_accuracy))39. sess.run(lenet.train_op,feed_dict={lenet.raw_input_image: batch[0],lenet.raw_input_label: batch[1]})40. save_path = saver.save(sess, parameter_path)41.42. if __name__ == '__main__':43. main(第4 步:在上述完成步驟的基礎上,運行Train.py。如圖11-6 所示,可以看到隨著不斷迭代優化,模型精度在逐步提高。
模型迭代優化過程
第5 步:建立測試文件Inference.py。具體地,建立一個Inference 類完成對圖片的識別,成員函數predict 接收圖片作為參數,返回預測值,代碼實現與說明如程序清單11-4 所示。
程序清單11-4 測試文件Inference.py 的建立
1. import tensorflow as tf2. from PIL import Image,ImageOps3. import numpy as np4. from lenet import Lenet5. import config as cfg6.7. class inference:8. def __init__(self):9. """10. 構建Lenet 網絡,設置模型參數文件路徑,開啟TensorFlow 會話11. """12. self.lenet = Lenet13. self.sess = tf.Session14. self.parameter_path = cfg.PARAMETER_FILE15. self.saver = tf.train.Saver16.17. def predict(self,image):18. """19. 接收要測試的圖片作為參數,返回預測值20. """21. # 將圖片轉換為合適的大小進行輸入22. img = image.convert('L')23. img = img.resize([28, 28], Image.ANTIALIAS)24. image_input = np.array(img, dtype="float32") / 25525. image_input = np.reshape(image_input, [-1, 784])26.27. # 讀取模型參數並對圖片進行預測,返回預測值28. self.saver.restore(self.sess,self.parameter_path)29. predition = self.sess.run(self.lenet.prediction, feed_dict={self.lenet.raw_input_image: image_input})30. return predition第6 步:為了方便地實現對手寫數字的識別,可以使用python 的tkinter方便地繪製一個UI 進行識別,具體實現代碼如程序清單11-5 所示。
程序清單11-5 利用tkinter 建立UI
1. import tkinter2. from PIL import Image,ImageDraw3. from Inference import inference4.5. class MyCanvas:6. """7. 設置一個256*256 大小的容器進行手寫界面的繪製8. 背景色設置為黑色,繪製軌跡設置為白色9. """10. def __init__(self,root):11. self.root=root12. self.canvas=tkinter.Canvas(root,width=256,height=256,bg='black')13. self.canvas.pack14. self.image1 = Image.new("RGB", (256, 256), "black")15. self.draw = ImageDraw.Draw(self.image1)16. self.canvas.bind('<B1-Motion>',self.Draw)17.18. # 繪製軌跡19. def Draw(self,event):20. self.canvas.create_oval(event.x,event.y,event.x,event.y,outline="white",width = 20)21. self.draw.ellipse((event.x-10,event.y-10,event.x+10,event.y+10),fill=(255,255,255))22.23.24. def main:25. # 建立一個tkinter 對象, 設置大小為380*30026. root = tkinter.Tk27. root.geometry('380x300')28. # 創建一個256*256 的框架容納手寫的容器,位於tkinter 對象的左邊,填充y 方向29. frame = tkinter.Frame(root, width=256, height=256)30. frame.pack_propagate(0)31. frame.pack(side="left", fill='y')32. # 將frame 導入canvas 容器33. canvas1 = MyCanvas(frame)34. # 創建一個圖像識別的實例35. infer = inference36.37. # 定義識別按鈕觸發函數38. # 按下的時候將cavas 導出為圖片,放入infer 中進行圖像識別,並將結果顯示在label2 中39. def inference_click:40. img = canvas1.image141. result = infer.predict(img)42. result = int(result)43. label2["text"] = str(result)44.45. # 定義清除按鈕的觸發函數46. # 按下的時候將canvas 情況並重新繪製背景,並將label 設置為空47. def clear_click:48. canvas1.canvas.delete("all")49. canvas1.image1 = Image.new("RGB", (256, 256), "black")50. canvas1.draw = ImageDraw.Draw(canvas1.image1)51. label2["text"] = ""52.53. # 定義識別按鈕的樣式54. botton_Inference = tkinter.Button(root,55. text=" 檢測",56. width=14,57. height=2,58. command=inference_click59. )60. # 定義清除按鈕的樣式61. botton_Clear = tkinter.Button(root,62. text=" 清屏",63. width=14,64. height=2,65. command=clear_click66. )67. # 綁定識別按鈕到tkinter 中,設置位置為頂層68. botton_Inference.pack(side="top")69.70. # 綁定清除按鈕到tkinter 中71. botton_Clear.pack(side="top")72.73. # 定義label174. label1 = tkinter.Label(root, justify="center", text=" 檢測結果為:")75. label1.pack(side="top")76.77. # 定義label278. label2 = tkinter.Label(root, justify="center")79.80. # 設置字體樣式與大小81. label2["font"] = ("Arial, 48")82. label2.pack(side="top")83. root.mainloop84.85. if __name__ == '__main__':86. main第7 步:運行代碼並進行如下的幾組測試,測試結果如圖11-7 所示。
手寫數字測試示例
#歡迎留言在評論區和我們討論#
看完本文,對於人工智慧、卷積神經網絡你有什麼想說的?
歡迎在評論區留言
我們將在 8 月 22日精選出 3 條優質留言
贈送《Python人工智慧開發從入門到精通》紙質書籍一本哦!