歡迎大家前往騰訊雲社群,獲取更多騰訊海量技術實踐乾貨哦~
作者介紹:高成才,騰訊Android開發工程師,2016.4月校招加入騰訊,主要負責企鵝電競推流SDK、企鵝電競APP的功能開發和技術優化工作。本文發表於QQ會員技術團隊的專欄
本文主要是對CS231n課程學習筆記的提煉,新增了一些Deep Learning Book和Tensorflow 實戰,以及Caffe框架的知識。
一、卷積神經網路
1.1 卷積神經網路與常規神經網路
1.1.1 相同點
卷積網路是一種專門用來處理具有類似網格結構的資料的神經網路。它常規神經網路非常相似:它們都是由神經元組成,神經元中有具有學習能力的權重和偏差。每個神經元都得到一些輸入資料,進行內積運算後再進行啟用函式運算。整個網路依舊是一個可導的評分函式:該函式的輸入是原始的影像畫素,輸出是不同類別的評分。在最後一層(往往是全連線層),網路依舊有一個損失函式(比如SVM或Softmax),並且在神經網路中我們實現的各種技巧和要點依舊適用於卷積神經網路。
1.1.2 不同點
卷積神經網路的結構基於一個假設,即輸入資料是影像,基於該假設,我們就向結構中新增了一些特有的性質。這些特有屬性使得前向傳播函式實現起來更高效,並且大幅度降低了網路中引數的數量。常規神經網路的神經網路的輸入是一個向量,然後在一系列的隱層中對它做變換。每個隱層都是由若干的神經元組成,每個神經元都與前一層中的所有神經元連線。但是在一個隱層中,神經元相互獨立不進行任何連線。
常規神經網路對於大尺寸影像效果不盡人意。在CIFAR-10中,影像的尺寸是32x32x3(寬高均為32畫素,3個顏色通道),因此,對應的的常規神經網路的第一個隱層中,每一個單獨的全連線神經元就有32x32x3=3072個權重。這個數量看起來還可以接受,但是很顯然這個全連線的結構不適用於更大尺寸的影像。舉例說來,一個尺寸為200x200x3的影像,會讓神經元包含200x200x3=120,000個權重值。而網路中肯定不止一個神經元,那麼引數的量就會快速增加!顯而易見,這種全連線方式效率低下,大量的引數也很快會導致網路過擬合。
卷積神經網路針對輸入全部是影像的情況,將結構調整得更加合理,獲得了不小的優勢。與常規神經網路不同,卷積神經網路的各層中的神經元是3維排列的:寬度、高度和深度(這裡的深度指的是啟用資料體的第三個維度,而不是整個網路的深度,整個網路的深度指的是網路的層數)。我們將看到,層中的神經元將只與前一層中的一小塊區域連線,而不是採取全連線方式。如下圖為常規神經網路和卷積神經網路示意圖:
1.2 卷積神經網路結構
一個簡單的卷積神經網路是由各種層按照順序排列組成,網路中的每個層使用一個可以微分的函式將啟用資料從一個層傳遞到另一個層。卷積神經網路主要由三種型別的層構成:卷積層,池化(Pooling)層和全連線層(全連線層和常規神經網路中的一樣)。通過將這些層疊加起來,就可以構建一個完整的卷積神經網路,結構如下圖所示:
1.2.1 卷積層
卷積層的引數是由一些可學習的濾波器集合構成的。每個濾波器在空間上(寬度和高度)都比較小,但是深度和輸入資料一致。 在前向傳播的時候,讓每個濾波器都在輸入資料的寬度和高度上滑動(更精確地說是卷積),然後計算整個濾波器和輸入資料任一處的內積。當濾波器沿著輸入資料的寬度和高度滑過後,會生成一個2維的啟用圖(activation map),啟用圖給出了在每個空間位置處濾波器的反應。直觀地來說,網路會讓濾波器學習到當它看到某些型別的視覺特徵時就啟用,具體的視覺特徵可能是某些方位上的邊界,或者在第一層上某些顏色的斑點,甚至可以是網路更高層上的蜂巢狀或者車輪狀圖案。在每個卷積層上,我們會有一整個集合的濾波器(比如12個),每個都會生成一個不同的二維啟用圖。將這些啟用對映在深度方向上層疊起來就生成了輸出資料,如下圖左所示,32*32的影像經過多個濾波器輸出資料:
(1) 區域性連線
區域性連線會大大減少網路的引數。在處理影像這樣的高維度輸入時,讓每個神經元都與前一層中的所有神經元進行全連線是不現實的。相反,我們讓每個神經元只與輸入資料的一個區域性區域連線。該連線的空間大小叫做神經元的感受野(receptive field),它的尺寸是一個超引數(其實就是濾波器的空間尺寸)。在深度方向上,這個連線的大小總是和輸入量的深度相等。需要再次強調的是,我們對待空間維度(寬和高)與深度維度是不同的:連線在空間(寬高)上是區域性的,但是在深度上總是和輸入資料的深度一致。
(2) 空間排列
上文講解了卷積層中每個神經元與輸入資料體之間的連線方式,但是尚未討論輸出資料體中神經元的數量,以及它們的排列方式。共有3個超引數控制著輸出資料體的尺寸:深度(depth),步長(stride)和零填充(zero-padding)。下面是對它們的討論:
1) 輸出資料體深度
它和使用的濾波器的數量一致,每個濾波器會在輸入資料中尋找一些不同的東西。舉例來說,如果第一個卷積層的輸入是原始影像,那麼在深度維度上的不同神經元將可能被不同方向的邊界,或者是顏色斑點啟用。我們將這些沿著深度方向排列、感受野相同的神經元集合稱為深度列(depthcolumn)。如下圖所示卷積層共有6個濾波器,輸出的資料深度也為6。
2) 步長
在滑動濾波器的時候,必須指定步長。當步長為1,濾波器每次移動1個畫素。當步長為2(或者不常用的3,或者更多,這些在實際中很少使用),濾波器滑動時每次移動2個畫素。這個操作會讓輸出資料體在空間上變小。如下圖所示,移動的步長為1,輸入的資料尺寸6 * 6,而輸出的資料尺寸為4 * 4。
3) 填充
卷積層濾波器輸出會減少資料的尺寸,如下圖,32 * 32 * 3的輸入經過一個5 * 5 * 3的濾波器時輸出28 * 28 * 1尺寸的資料。
而零填充有一個良好性質,即可以控制輸出資料體的空間尺寸(最常用的是用來保持輸入資料體在空間上的尺寸,這樣輸入和輸出的寬高都相等)。將輸入資料體用0在邊緣處進行填充是很方便的,這個零填充(zero-padding)的尺寸是一個超引數。如下圖所示通過零填充保持了輸入輸出資料寬高的一致。
卷積層輸出計算公式:
假設輸入資料體的尺寸為:
卷積層4個超引數為:
則輸出資料體的尺寸為:
其中:
對這些超引數,常見的設定是F=3,S=1,P=1。比如輸入是7x7,濾波器是3x3,步長為1,填充為0,那麼就能得到一個5x5的輸出。如果步長為2,輸出就是3x3。
(3) 引數共享
在卷積層中使用引數共享是用來控制引數的數量。每個濾波器與上一層區域性連線,同時每個濾波器的所有區域性連線都使用同樣的引數,此舉會同樣大大減少網路的引數。
這裡作一個合理的假設:如果一個特徵在計算某個空間位置(x,y)的時候有用,那麼它在計算另一個不同位置(x2,y2)的時候也有用。基於這個假設,可以顯著地減少引數數量。換言之,就是將深度維度上一個單獨的2維切片看做深度切片(depth slice),比如一個資料體尺寸為55x55x96的就有96個深度切片,每個尺寸為55x55。在每個深度切片上的神經元都使用同樣的權重和偏差。在反向傳播的時候,都要計算每個神經元對它的權重的梯度,但是需要把同一個深度切片上的所有神經元對權重的梯度累加,這樣就得到了對共享權重的梯度。這樣,每個切片只更新一個權重集。
在一個深度切片中的所有權重都使用同一個權重向量,那麼卷積層的前向傳播在每個深度切片中可以看做是在計算神經元權重和輸入資料體的卷積(這就是“卷積層”名字由來)。這也是為什麼總是將這些權重集合稱為濾波器(filter)(或卷積核(kernel)),因為它們和輸入進行了卷積。下圖動態的顯示了卷積的過程:
1.2.2 池化層
通常,在連續的卷積層之間會週期性地插入一個池化層。它的作用是逐漸降低資料體的空間尺寸,這樣的話就能減少網路中引數的數量,使得計算資源耗費變少,也能有效控制過擬合。池化層使用Max操作,對輸入資料體的每一個深度切片獨立進行操作,改變它的空間尺寸。最常見的形式是池化層使用尺寸2x2的濾波器,以步長為2來對每個深度切片進行降取樣,將其中75%的啟用資訊都丟掉。每個Max操作是從4個數字中取最大值(也就是在深度切片中某個2x2的區域)。深度保持不變。
池化層的計算公式:
輸入資料體尺寸:
池化層有兩個超引數:
輸出資料體尺寸:
其中:
因為對輸入進行的是固定函式計算,所以沒有引入引數。在池化層中很少使用零填充。
在實踐中,最大池化層通常只有兩種形式:一種是F=3, S=2,另一個更常用的是F=2, S=2。對更大感受野進行池化需要的池化尺寸也更大,而且往往對網路有破壞性。
普通池化(General Pooling):除了最大池化,池化單元還可以使用其他的函式,比如平均池化(average pooling)或L-2正規化池化(L2-norm pooling)。平均池化歷史上比較常用,但是現在已經很少使用了。因為實踐證明,最大池化的效果比平均池化要好。池化層的示意圖如下圖所示:
反向傳播:回顧一下反向傳播的內容,其中函式的反向傳播可以簡單理解為將梯度只沿最大的數回傳。因此,在向前傳播經過匯聚層的時候,通常會把池中最大元素的索引記錄下來(有時這個也叫作道岔(switches)),這樣在反向傳播的時候梯度的路由就很高效。
1.2.3 歸一化層
在卷積神經網路的結構中,提出了很多不同型別的歸一化層,有時候是為了實現在生物大腦中觀測到的抑制機制。但是這些層漸漸都不再流行,因為實踐證明它們的效果即使存在,也是極其有限的。
1.2.4 全連線層
在全連線層中,神經元對於前一層中的所有啟用資料是全部連線的,這個常規神經網路中一樣。它們的啟用可以先用矩陣乘法,再加上偏差。
1.3 常用CNN模型
1.3.1 LeNet
這是第一個成功的卷積神經網路應用,是Yann LeCun在上世紀90年代實現,被用於手寫字型的識別,也是學習神經網路的“Hello World”。網路結構圖如下圖所示:
C1層:卷積層,這層包含6個特徵卷積核,卷積核的大小為5 * 5,然後可以得到6個特徵圖,每個特徵圖的大小為32-5+1=28。
S2層:這是下采樣層,使用最大池化進行下采樣,池化的尺寸為2x2,因此我們可以得到6個14x14的特徵圖。
C3層:卷積層,這層包含16個特徵卷積核,卷積核的大小依然為5x5,因此可以得到16個特徵圖,每個特徵圖大小為14-5+1=10。
S4層:下采樣層,依然是2x2的最大池化下采樣,結果得到16個5x5的特徵圖。
C5層:卷積層,這層使用120個5x5的卷積核,最後輸出120個1x1的特徵圖。
之後就是全連線層,然後進行分類。
1.3.2 AlexNet
Alexnet的意義重大,他證明了CNN在複雜模型下的有效性,使得神經網路在計算機視覺領域大放異彩。 它由Alex Krizhevsky,Ilya Sutskever和Geoff Hinton實現。AlexNet在2012年的ImageNet ILSVRC 競賽中奪冠,效能遠遠超出第二名(16%的top5錯誤率,第二名是26%的top5錯誤率)。這個網路的結構和LeNet非常類似,但是更深更大,並且使用了層疊的卷積層來獲取特徵(之前通常是隻用一個卷積層並且在其後馬上跟著一個匯聚層)。結構圖如下圖所示:
每層的詳細資訊如下圖所示:
AlexNet輸入尺寸為227x227x3。
第一個卷基層由96個大小為11x11的濾波器,步長為4。輸出尺寸為(227 -11)/4 +1 = 55,深度為64。大概有11x11x3x643萬5千個引數。
第二層池化的尺寸為3x3,步長為2,所以輸出的尺寸為(55-3)/2+1=27。
這是第一次使用ReLU,至少第一次將ReLU發揚光大。
使用現在已經不怎麼使用的資料規範化。
使用了很多資料增強。
1.3.3 ZFNet
Matthew Zeiler和Rob Fergus發明的網路在ILSVRC 2013比賽中奪冠,它被稱為 ZFNet(Zeiler & Fergus Net的簡稱)。它通過修改結構中的超引數來實現對AlexNet的改良,具體說來就是增加了中間卷積層的尺寸,讓第一層的步長和濾波器尺寸更小。他們基於試驗做了一些引數的調整。結構圖如下圖所示:
1.3.4 VGGNet
VGGNet並沒有瘋狂的架構選擇,沒有在如何設定濾波器的個數,尺寸大小上做非常多的工作。整個VGG網路只用3*3卷積核,滑動步長2和2*2池化視窗,滑動步長2。整個過程就維持了這樣的引數設定。VGG的關鍵點在於這個操作你重複了多少層,最後同樣的這組引數設定的網路結構重複了16層。選擇這個層的原因可能是他們發現這樣有最好的表現。對於每張圖片需要200M記憶體,所有的引數加起來,最終總的引數量會達到1.4億。
為什麼使用3*3濾波器?3*3是最小的能夠捕獲上下左右和中心的感受野,多個3*3的卷積層比一個更大尺寸濾波器卷積層有更多的非線性。在步長為1的情況下, 兩個3*3的濾波器的最大感受野區域是5*5, 三個3*3的濾波器的最大感受野區域是7*7, 可以替代更大的濾波器尺寸多個3*3的卷積層比一個大尺寸的filter有更少的引數,假設卷積層的輸入和輸出的特徵圖大小相同為10,那麼含有3個3*3的濾波器的卷積層引數個數3*(3*3*10*10)=2700, 因為三個3*3的filter可以看成是一個7*7的filter分解而來的(中間層有非線性的分解), 但是1個7*7的卷積層引數為7*7*10*10=4900 1*1濾波器作 用是在不影響輸入輸出維數的情況下,對輸入線進行線性形變,然後通過Relu進行非線性處理,增加網路的非線性表達能力。
VGGNet不好的一點是它耗費更多計算資源,並且使用了更多的引數,結構圖如下所示:
1.3.5 GoogLeNet
VGGNet效能不錯,但是有大量的引數。一般來說,提升網路效能最直接的辦法就是增加網路深度和寬度,這也就意味著巨量的引數。但是,巨量引數容易產生過擬合也會大大增加計算量。
一般文章認為解決上述兩個缺點的根本方法是將全連線甚至一般的卷積都轉化為稀疏連線。一方面現實生物神經系統的連線也是稀疏的,另一方面有文獻表明:對於大規模稀疏的神經網路,可以通過分析啟用值的統計特性和對高度相關的輸出進行聚類來逐層構建出一個最優網路。“稀疏連線結構”的理解是這樣的,用盡可能的“小”、“分散”的可堆疊的網路結構,去學習複雜的分類任務,Inception之所以能提高網路精度,可能就是歸功於它擁有多個不同尺度的kernels,每一個尺度的kernel會學習不同的特徵,把這些不同kernels學習到的特徵匯聚給下一層,能夠更好的實現全方位的深度學習。
普通的Inception結構如下圖所示:
降維實現的Inception如下圖所示,將256維降到64維,減少了引數量:
為什麼VGG網路的引數那麼多?就是因為它在最後有兩個4096的全連層。Szegedy吸取了教訓,為了壓縮GoogLeNet的網路引數,他把全連層取消了。GoogLeNet完整結構如下圖所示:
1.3.6 ResNet
由何愷明和同事完成的殘差網路,他們不僅僅在2015年的ImageNet上獲勝,同時還贏得了相當多的比賽,幾乎所有重要比賽的第一名。在深度網路優化中存在一個著名的障礙是:梯度消失和梯度爆炸。這個障礙可以通過合理的初始化和一些其他技術來解決,但是隨著網路的深度增加,準確度飽和並迅速減少,這一現象稱為degradation,並且廣泛的存在於深層網路中,說明不是所有的系統都很容易被優化。
對於plainnet 如果你單純的提高網路層數,將沒有什麼用處。如上圖,在cifar-10上,實線是測試集上的錯誤率,虛線是訓練集上的錯誤率。我們看到層數更深的網路錯誤率反而更高,這不科學,按道理來說,層數更深的網路容量更大,那是因為我們在優化引數上做的不夠好,沒法選擇更優的引數。而殘差網路模型的訓練錯誤率和測試錯誤率都隨著網路深度的增加在持續的改進。訓練ResNet需要2-3周8個GPU訓練。
何愷明提出了深度殘差學習的概念來解決這一問題。首先我們假設我們要求的對映是H(x),通過上面的觀察我們意識到直接求得H(x)並不那麼容易,所以我們轉而去求H(x)的殘差形式F(x)=H(x)-x,假設求F(x)的過程比H(x)要簡單,這樣,通過F(x)+x我們就可以達到我們的目標,簡單來說就是上面這幅圖,我們將這個結構稱之為一個residual block。 相信很多人都會對第二個假設有疑惑,也就是為什麼F(x)比H(x)更容易求得,關於這一點,論文中也沒有明確解釋。但是根據後的實驗結果確實可以得到這一個結論。
在ResNet中有這些有趣的跳躍連線,如上圖所示。在殘差網路的反向傳播中,梯度除了流經這些權值向後傳播,還有這些跳躍連線,這些跳躍連線是加法處理,可以分散梯度,讓梯度流向之前的一部分,因此你可以訓練出離影像很近的一些特徵。
通過下圖ImageNet上神經網路演算法的深度和錯誤率統計,我們可以看到,神經網路層數越來約深,同時錯誤率也越來越低。
二、Tensorflow實戰
2.1 深度學習開源框架對比
深度學習框架很多,我們這裡只介紹兩個用的比較多的框架:
Tensorflow:TensorFlow 是一個使用資料流圖(data flow graphs)進行數值計算的開源軟體庫, 一個用於機器智慧的開源軟體庫。TensorFlow擁有產品級的高質量程式碼,有Google強大的開發、維護能力的加持,整體架構設計也非常優秀。 Google作為巨頭公司有比高校或者個人開發者多得多的資源投入到TensorFlow的研發,可以預見,TensorFlow未來的發展將會是飛速的,可能會把大學或者個人維護的深度學習框架遠遠甩在身後。TensorFlow是相對高階的機器學習庫,使用者可以方便地用它設計神經網路結構,而不必為了追求高效率的實現親自寫C++或CUDA程式碼。 TensorFlow也有內建的TF.Learn和TF.Slim等上層元件可以幫助快速地設計新網路 TensorFlow的另外一個重要特點是它靈活的移植性,可以將同一份程式碼幾乎不經過修改就輕鬆地部署到有任意數量CPU或GPU的PC、伺服器或者移動裝置上。 除了支援常見的網路結構卷積神經網路(Convolutional Neural Network,CNN)、迴圈神經網路(Recurent Neural Network,RNN)外,TensorFlow還支援深度強化學習乃至其他計算密集的科學計算(如偏微分方程求解等)。
Caffe: Caffe是一個被廣泛使用的開源深度學習框架,在Tensorflow出現之前一直是深度學習領域Github star最多的專案。Caffe的主要優勢為:1.容易上手,網路結構都是以配置檔案形式定義,不需要用程式碼設計網路。訓練速度快,元件模組化,可以方便的擴充到新的模型和學習任務上。但是Caffe最開始設計時的目標只針對於影像,沒有考慮文字、語音或者時間序列的資料,因此Caffe對卷積神經網路的支援非常好,但是對於時間序列RNN,LSTM等支援的不是特別充分。
2.2 Tensorflow環境搭建
2.2.1 作業系統
Tensorflow支援在window、linux、mac上面執行,我搭建的環境使用的是Ubuntu16.04 64位。Tensorflow需要依賴python環境,這裡預設使用python3.5作為python的基礎版本。推薦使用Anaconda作為Python環境,因為可以避免大量的相容性問題。
2.2.1 安裝Anaconda
Anaconda是Python的一個科學計算髮行版,內建了數百個Python經常使用會使用的庫,其中可能有一些還是Tensorflow的依賴庫。Anaconda的Python版本要和Tensorflow版本一致,不然會存在問題。我這裡下載的是Anaconda3-4.2.0-Linux-x86_64.sh。執行:
bash Anaconda3-4.2.0-Linux-x86_64.sh
安裝Anconda,安裝完成後,程式會提示我們是否把Anaconda3的binary路徑加入到.bashrc,這裡建議新增,這樣以後python命令就會自動使用Anaconda Python3.5的環境了。
2.2.2 Tensorflow安裝
Tensowflow分為cpu版本和gpu版本,如果你的電腦上有NVIDIA顯示卡的話,建議裝GPU版本,他會加快你訓練的速度。cpu版本安裝比較簡單,這裡就不在贅述,主要講下gpu版本的安裝。首先使用:
lspci | grep -i nvidia
檢視nvidia顯示卡的型號,然後去developer.nvidia.com/cuda-gpus檢視你的顯示卡是否支援cuda,只有支援cuda的gpu才能安裝tensorflow的gpu版本。我的電腦是GeForce 940M 支援cuda。
(1) 安裝CUDA和cuDNN
首先在nvidia官網上下載對應的cuda版本。這邊下載是cuda_8.0.61_375.26_linux.run。這裡下載會比較慢,建議使用迅雷下載。在安裝前需要暫停NVIDIA的驅動X server,首先使用ctrl+alt+f2接入ubuntu的命令介面,如果進不去,有些電腦需要使用fn+ctrl+alt+f2然後執行
sudo /etc/init.d/lightdm stop
暫停X Server。然後執行如下命令安裝:
chmod u+x cuda_8.0.61_375.26_linux.runsudo ./cuda_8.0.61_375.26_linux.run
先按q跳過開頭的license,接著輸入accept接受協議,然後按y鍵選擇安裝驅動程式,在之後的選擇中,我們選擇不安裝OpenGL,否則可能出現在登入介面迴圈登入的問題。然後按n鍵選擇不安裝samples。
接下來安裝cuDNN,cuDNN是NVIDIA推出的深度學習中的CNN和RNN的高度優化的實現,底層使用了很多先進的技術和介面,因此比其他GPU上的神經網路庫效能高不少。首先從官網上下載cuDNN,這一步需要先註冊NVIDIA的帳號,並等待稽核。執行
cd /usr/local sudo tar -xzvf ~/Downloads/cudnn-8.0-linux-x64-v6.0.tgz
就完可以成了cuDNN的安裝。
(2) 安裝Tensorflow
在github上下載對應的版本,這裡下載的是tensorflow_gpu-1.2.1-cp35-cp35m-
linux_x86_64.whl。然後執行如下命令即可完成安裝。
pip install tensorflow_gpu-1.2.1-cp35-cp35m-linux_x86_64.whl
2.2 Hello World-MINST手寫數字識別
2.2.1 使用線性模型 並使用softmax分類器
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)print(mnist.train.images.shape, mnist.train.labels.shape)print(mnist.test.images.shape, mnist.test.labels.shape)print(mnist.validation.images.shape, mnist.validation.labels.shape)
sess = tf.InteractiveSession()
x = tf.placeholder(tf.float32, [None, 784])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
y = tf.nn.softmax(tf.matmul(x, W) + b)
y_ = tf.placeholder(tf.float32, [None, 10])
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
tf.global_variables_initializer().run()
for i in range(1000):
batch_xs, batch_ys = mnist.train.next_batch(1000)
train_step.run({x: batch_xs, y_: batch_ys})
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))print(accu複製程式碼
2.2.2 使用CNN模型
from tensorflow.examples.tutorials.mnist import input_dataimport tensorflow as tf
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
sess = tf.InteractiveSession()def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1) return tf.Variable(initial)def bias_variable(shape):
initial = tf.constant(0.1, shape=shape) return tf.Variable(initial)def conv2d(x, W):
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')def max_pool_2x2(x):
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
x = tf.placeholder(tf.float32, [None, 784])
y_ = tf.placeholder(tf.float32, [None, 10])
x_image = tf.reshape(x, [-1, 28, 28, 1])
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y_conv), reduction_indices=[1]))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
tf.global_variables_initializer().run()for i in range(10000):
batch = mnist.train.next_batch(50) if i % 100 == 0:
train_accuracy = accuracy.eval(feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0})
print("step %d, training accuracy %g" % (i, train_accuracy))
train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
print("test accuracy %g" % accuracy.eval(feed_dict={x: mnist.test.images,
y_: mnist.test.labels, keep_prob: 1.0}))複製程式碼
三、王者榮耀英雄識別
在企鵝電競的業務場景中,需要識別主播當前在王者榮耀中使用的英雄。這裡初次嘗試使用深度學習的方法來分類,使用的框架是Caffe。
3.1 設計特徵
首先是準備訓練資料,我們需要根據業務場景設計需要分類的特徵。我們可以選擇整張遊戲畫面、遊戲中英雄畫面或者技能鍵作為分類特徵,如下圖所示。使用整張遊戲畫面時,圖片包含較多無關元素,能夠標識英雄的特徵區域較小。使用英雄截圖可以作為明顯特徵,但是由於每個英雄存在多套皮膚,準備資料比較困難,以及會對識別準確率有影響。所以使用技能鍵區域作為分類特徵是較好的選擇。
在設計分類特徵是需要特徵儘可能清晰,並且不同種類之間的特徵差別大。
3.2 收集資料
收集資料並標註是繁瑣但是很重要的任務,同一個模型在不同的資料訓練下可能效果會差別很大。這裡是在Youtube針對不同的英雄分別下載一段15分鐘左右的視訊,然後使用如下ffmpeg命令擷取技能鍵區域的圖片並將其尺寸壓縮。
ffmpeg -i videos/1/test.mp4 -r 1 -vf "crop=380:340:885:352,scale=224:224" images/1/test_%4d.png
資料集圖片資料夾如下圖所示。需要人肉篩選其中不包含技能鍵的噪聲圖片,並將其放入0資料夾中。0資料夾作為背景分類,對應於無法識別英雄的分類。
這裡每個分類大概包含1000-2000張圖片。資料集當然越大越好,並且覆蓋儘可能多的場景,增加模型的泛化能力。
3.3 處理資料
資料集生成好了之後,需要將其轉換為Caffe能夠識別的格式。首先使用下圖左側的python程式碼生成訓練集和測試集所包含的圖片,生成檔案如下圖右側所示,包含圖片路徑和分類。其中測試集為所有圖片的五分之一。
然後將圖片轉換為LMDB格式的資料,Caffe提供了工具幫助我們轉換。處理指令碼如下圖所示。
3.4 選擇模型
上文介紹的六種模型為常用的圖片分類模型,這裡我們選擇GoogLeNet作為模型網路。在Caffe工程的models資料夾中有這些網路模型,檢視caffe/models/bvlc_googlenet資料夾,其中檔案如下圖:
3.4.1 solver.prototxt
solver.prototxt資料夾中定義了訓練模型所用到的引數,其中引數的具體含義如下圖所示:
3.4.2 train_val.prototxt
train_val.prototxt為GoogLeNet網路的結構的具體定義,網路檔案很多,這裡介紹下獨立的網路層結構。首先是資料層,其引數如下圖所示:
3.4.2 train_val.prototxt
train_val.prototxt為GoogLeNet網路的結構的具體定義,網路檔案很多,這裡介紹下獨立的網路層結構。首先是資料層,其引數如下圖所示:
卷積層、ReLU層和池化層結構如下圖所示:
LRN層的結構如下圖所示:
Dropout層和全連線層如下圖所示:
3.4.3 deploy.prototxt
deploy檔案為部署時使用的網路結構,其大部分內容與train_val.prototxt檔案相似,去掉了一些測試層的內容。
3.5 訓練模型
將資料和模型準備好了之後,就可以進行模型的訓練了。這裡呼叫如下程式碼開始訓練:
caffe train -solver solver.prototxt
可以新增-snapshot 引數可以接著上次訓練的模型訓練,訓練日誌如下圖所示:
當模型收斂,或者準確率達到我們要求之後,可以停止訓練。
3.6 裁剪模型
GoogLeNet本身是比較大的網路,我們可以根據自己的需求裁剪網路。 GoogLeNet中的LRN層影響不大可以去掉,或者刪除一些卷積層,降低網路的層數。
3.7 finetuning
當我們的資料量比較小的時候,訓練完整的網路引數比較容易過擬合。這時我們可以通過只訓練網路的某幾層來解決這個問題。 首先修改train_val.prototxt中的全連線層的名稱,即loss1/classifier,loss2/classifier,loss3/classifier這三層的名稱,以及他們的輸出分類個數,預設是1000,需要改成我們自己的種類總數。這樣我們再載入訓練好的model時,這三層的引數才會重新初始化。然後將所有其他層的lr_mult該為0,這樣其他層的引數不會改變,使用預先訓練好的引數。 下載bvlc_googlenet.caffemodel,這是谷歌在ImageNet上訓練出來的引數。然後呼叫caffe train -solver solver.prototxt -weights bvlc_googlenet.caffemodel即可訓練。
Tips
區域性極小值問題:可行的trick是開始階段batchsize調小。
Loss爆炸:學習率調小。
Loss始終不收斂:要懷疑資料集或label是否有問題。
過擬合:資料增強,正則化、Dropout、BatchNormalization以及early stopping等策略。
權值初始化:一般xavier或gaussian。
Finetune: 使用GoogLeNet或VGG,可以finetune已有模型。
參考文件:
1、本文主要整理自CS231n課程筆記, 中文翻譯連結 ,中文公開課連結 。強烈建議大家看看。
2、Yoshua Bengio 《Deep Learning Book》 炸裂推薦,非常值得一看。
3、周志華《機器學習》 適合機器學習入門。
4、《Tensorflow 實戰》Tensorflow實踐不錯。
5、史丹佛大學深度學習教程 。
李濤 編輯