這是一篇關(guān)于CNN(卷積神經(jīng)網(wǎng)絡(luò))的簡單指南,本文將介紹CNN如何工作,以及如何在Python中從頭開始構(gòu)建一個CNN。
在過去的幾年中,有很多關(guān)于卷積神經(jīng)網(wǎng)絡(luò)(CNN)的討論,尤其是因為它們已經(jīng)徹底改變了計算機視覺領(lǐng)域。在這篇文章中,我們將基于神經(jīng)網(wǎng)絡(luò)的基本背景知識,探索CNN是什么,理解它們是如何工作的,并使用Python中的numpy從頭開始構(gòu)建一個真正的卷積神經(jīng)網(wǎng)絡(luò)。
本文假設(shè)讀者有一定的神經(jīng)網(wǎng)絡(luò)的基本知識。如果你想要了解一些關(guān)于神經(jīng)網(wǎng)絡(luò)的知識,你可以讀一下我的關(guān)于對神經(jīng)網(wǎng)絡(luò)的介紹。
1、動機
CNN的經(jīng)典用例是執(zhí)行圖像分類,例如查看寵物的圖像并確定它是貓還是狗。這是一項看似非常簡單的任務(wù),你可能會有這樣的疑惑:為什么不使用普通的神經(jīng)網(wǎng)絡(luò)呢?不得不說這是一個好問題。
原因1:圖像很大
目前用于計算機視覺問題的圖像通常為224x224甚至更大。想象一下,構(gòu)建一個神經(jīng)網(wǎng)絡(luò)來處理224x224彩色圖像:包括圖像中的3個顏色通道(RGB),即224x224x3=150528個輸入權(quán)重!這種網(wǎng)絡(luò)中的典型隱藏層可能有1024個節(jié)點,因此我們必須僅為第一層訓(xùn)練150528x1024=15億個權(quán)重。想象一下?lián)碛?5億個權(quán)重的神經(jīng)網(wǎng)絡(luò),是不是太大了?這幾乎是不可能完成訓(xùn)練的。
最重要的是其實我們并不需要那么多的權(quán)重,相反我們僅知道像素點其鄰居的點才是最有用的。因為圖像中的物體是由小的局部特征組成的,如圓形虹膜或一張紙的方角。對于第一個隱藏層中的每個節(jié)點來說,查看每個像素似乎是很浪費的!
原因2:位置可變
如果你訓(xùn)練了一個網(wǎng)絡(luò)來檢測狗,那么無論圖像出現(xiàn)在哪張照片中,你都希望它能夠檢測到狗。想象一下,訓(xùn)練一個在某個狗圖像上運行良好的網(wǎng)絡(luò),然后為它提供相同圖像的略微移位版本,此時的網(wǎng)絡(luò)會有完全不同的反應(yīng)!
那么CNN是如何幫助我們解決這些問題的呢?不要著急我們很快就會看到CNN如何幫助我們緩解這些問題!
2、數(shù)據(jù)集
在這篇文章中,我們將解決計算機視覺的“Hello,World!”:MNIST手寫數(shù)字分類問題。很簡單:給定圖像,將其分類為數(shù)字。

來自MNIST數(shù)據(jù)集的樣本圖像
MNIST數(shù)據(jù)集中的每個圖像都是28x28,其中包含了一個居中的灰度數(shù)字。說實話,一個正常的神經(jīng)網(wǎng)絡(luò)實際上可以很好地解決這個問題。你可以將每個圖像視為28x28=784維矢量,將其輸入到784-dim圖層,堆疊一些隱藏圖層,最后輸出10個節(jié)點的輸出圖層,每個數(shù)字1個。
這樣做可以完成任務(wù),因為MNIST數(shù)據(jù)集包含的都是些居中的小圖像,因此我們不會遇到上述的大小或移位問題。但是,請記住,大多數(shù)現(xiàn)實世界的圖像分類問題并不容易。
那么就讓我們進入CNN吧!
3、卷積
什么是卷積神經(jīng)網(wǎng)絡(luò)?
它們基本上是使用卷積層的神經(jīng)網(wǎng)絡(luò),即Conv層,它們基于卷積的數(shù)學(xué)運算。Conv圖層由一組過濾器組成,你可以將其視為2d數(shù)字矩陣。這是一個示例3x3過濾器:

一個3x3過濾器
我們可以通過將濾波器與輸入圖像進行卷積來產(chǎn)生輸出圖像。
這包括:
在某個位置覆蓋圖像頂部的過濾器;
在過濾器中的值與圖像中的相應(yīng)值之間執(zhí)行逐元素乘法;
所有元素(element-wise products)求和,此和是輸出圖像中目標(biāo)像素的輸出值。
重復(fù)所有位置。
注釋:實際上我們(以及許多CNN實現(xiàn))在技術(shù)上使用互相關(guān)(Cross-correlation)而不是卷積,但它們幾乎完全相同。
這4步描述有點抽象,所以讓我們舉個例子吧,考慮這個微小的4x4灰度圖像和這個3x3過濾器:

4x4圖像(左)和3x3濾鏡(右)
圖像中的數(shù)字表示像素強度,其中0是黑色,255是白色。我們將對輸入圖像和過濾器進行卷積以生成2x2輸出圖像:

2x2輸出圖像
首先,讓我們將過濾器疊加在圖片的左上角:

第1步:將過濾器(右)疊加在圖像上方(左)
接下來,我們在重疊圖像值和過濾器值之間執(zhí)行逐元素乘法。以下是結(jié)果,從左上角開始向右,然后向下:

第2步:執(zhí)行逐元素乘法。
接下來,我們總結(jié)所有結(jié)果。這很容易:62-33=29。
最后,我們將結(jié)果放在輸出圖像的目標(biāo)像素中。由于我們的過濾器覆蓋在輸入圖像的左上角,因此我們的目標(biāo)像素是輸出圖像的左上角像素:

我們做同樣的步驟來生成輸出圖像的其余部分:
3.1 這有用嗎?
用過濾器卷積圖像有什么作用?我們可以先使用我們一直使用的示例3x3濾波器,這通常被稱為垂直索貝爾濾波器:

垂直索貝爾濾波器
以下是垂直Sobel濾波器的示例:

垂直Sobel濾波器卷積的圖像
同樣,還有一個水平Sobel濾波器:

水平Sobel濾波器

水平Sobel濾波器卷積的圖像
到底發(fā)生了什么?Sobel濾波器是邊緣檢測器。垂直Sobel濾波器檢測的是垂直邊緣,水平Sobel濾波器的是檢測水平邊緣?,F(xiàn)在可以輕松解釋輸出圖像:輸出圖像中的亮像素(具有高值的像素)表示原始圖像中存在強邊緣。
你能看出為什么邊緣檢測圖像可能比原始圖像更有用嗎?回想一下我們的MNIST手寫數(shù)字分類問題。在MNIST上訓(xùn)練的CNN可以尋找數(shù)字1,例如,通過使用邊緣檢測濾波器并檢查圖像中心附近的兩個突出的垂直邊緣。通常,卷積有助于我們查找特定的本地化圖像特征。
3.2 填充(Padding)
還記得先用3x3過濾器對4x4的輸入圖像進行卷積,以產(chǎn)生2x2輸出圖像嗎?通常,我們希望輸出圖像的大小與輸入圖像的大小相同。為此,我們在圖像周圍添加零,以便我們可以在更多位置疊加過濾器。3x3濾鏡需要1個像素的填充:

4x4輸入與3x3濾波器卷積,使用填充以產(chǎn)生4x4輸出
這稱為“相同”填充,因為輸入和輸出具有相同的尺寸。不使用任何填充,這是我們一直在做的,有時也被稱為“有效”填充。
3.3 Conv圖層
既然我們知道圖像卷積是如何工作的以及為什么用它,那讓我們看看它是如何在CNN中實際使用的。如前所述,CNN包括使用一組過濾器將輸入圖像轉(zhuǎn)換為輸出圖像的conv layer。conv層的主要參數(shù)是它具有的過濾器數(shù)量。
對于我們的MNIST CNN,我們將使用一個帶有8個過濾器的小conv layer作為我們網(wǎng)絡(luò)中的初始層。這意味著它會將28x28輸入圖像轉(zhuǎn)換為26x26x8輸出向量:

提醒:輸出為26x26x8而不是28x28x8,因為我們使用有效填充,這會將輸入的寬度和高度減少2。
conv layer中的4個過濾器中的每一個都會產(chǎn)生26x26輸出,因此堆疊在一起它們構(gòu)成26x26x8的向量。
3.4 執(zhí)行卷積
是時候?qū)⑽覀儗W(xué)到的東西放到代碼中了!我們將實現(xiàn)一個conv layer的前饋部分,該部分負(fù)責(zé)處理帶有輸入圖像的過濾器,以產(chǎn)生輸出向量。為簡單起見,我們假設(shè)過濾器是3x3(其實5x5和7x7過濾器也很常見)。
讓我們開始實現(xiàn)一個conv layer類:
importnumpyasnpclassConv3x3:#AConvolutionlayerusing3x3filters.def__init__(self,num_filters):self.num_filters=num_filters#filtersisa3darraywithdimensions(num_filters,3,3)#Wedivideby9toreducethevarianceofourinitialvaluesself.filters=np.random.randn(num_filters,3,3)/9
該類只有一個參數(shù):過濾器的數(shù)量。在構(gòu)造函數(shù)中,我們存儲過濾器的數(shù)量并使用NumPy的randn()方法初始化隨機過濾器數(shù)組。
注意:在初始化期間初始值很重要,如果初始值太大或太小,則訓(xùn)練網(wǎng)絡(luò)將無效。
要了解更多信息,請閱讀Xavier Initialization:https://www.quora.com/What-is-an-intuitive-explanation-of-the-Xavier-Initialization-for-Deep-Neural-Networks
接下來,實際卷積:
classConv3x3:#...defiterate_regions(self,image):'''Generatesallpossible3x3imageregionsusingvalidpadding.-imageisa2dnumpyarray'''h,w=image.shapeforiinrange(h-2):forjinrange(w-2):im_region=image[i:(i+3),j:(j+3)]yieldim_region,i,jdefforward(self,input):'''Performsaforwardpassoftheconvlayerusingthegiveninput.Returnsa3dnumpyarraywithdimensions(h,w,num_filters).-inputisa2dnumpyarray'''h,w=input.shapeoutput=np.zeros((h-2,w-2,self.num_filters))forim_region,i,jinself.iterate_regions(input):output[i,j]=np.sum(im_region*self.filters,axis=(1,2))returnoutput
iterate_regions()是一個輔助生成器方法,為我們生成所有有效的3x3圖像區(qū)域,這對于稍后實現(xiàn)此類的后向部分非常有用。
上面是實際執(zhí)行卷積的代碼行。讓我們分解一下:
im_region:一個包含相關(guān)圖像區(qū)域的3x3陣列。
self.filters:一個3d數(shù)組。
im_region*self.filtersself.filters:我們使用numpy的廣播(broadcasting)功能以元素方式乘以兩個數(shù)組,結(jié)果是具有相同尺寸的3d數(shù)組。
axis=(1,2)num_filters:我們使用np.sum()上一步的結(jié)果,產(chǎn)生一個長度為1d的數(shù)組,其中每個元素包含相應(yīng)過濾器的卷積結(jié)果。
我們將結(jié)果分配給output[i,j],其中包含輸出中像素的卷積結(jié)果(i,j)。
對輸出中的每個像素執(zhí)行上面的序列,直到我們獲得我們想要的結(jié)果!讓我們的代碼進行測試運行:
importmnistfromconvimportConv3x3#ThemnistpackagehandlestheMNISTdatasetforus!#Learnmoreathttps://github.com/datapythonista/mnisttrain_images=mnist.train_images()train_labels=mnist.train_labels()conv=Conv3x3(8)output=conv.forward(train_images[0])print(output.shape)#(26,26,8)
注意:為簡單起見,在我們的Conv3x3實現(xiàn)中,我們假設(shè)輸入是一個2d numpy數(shù)組,因為這是我們的MNIST圖像的存儲方式。這對我們有用,我們將它用作網(wǎng)絡(luò)中的第一層,但大多數(shù)CNN都有更多的Conv層。如果我們要構(gòu)建一個需要Conv3x3多次使用的更大的網(wǎng)絡(luò),我們必須使輸入成為3d numpy數(shù)組。
4、池化(Pooling)
圖像中的相鄰像素傾向于具有相似的值,因此conv layer通常也會為輸出中的相鄰像素產(chǎn)生類似的值。但是conv layer中輸出的大部分信息都是冗余的,例如,如果我們使用邊緣檢測過濾器并在某個位置找到強邊緣,那么我們也可能會在距離原始像素1個像素偏移的位置找到相對較強的邊緣。但是,我們可能并沒有找到任何新的東西。
池化層解決了這個問題。他們所做的就是減少通過猜測在輸入中產(chǎn)生的匯總值。該池通常是通過簡單的操作完成max,min或average等這些操作。以下是池化大小為2的Max Pooling圖層的示例:

4x4圖像上的最大池(池大小為2)以產(chǎn)生2x2輸出
為了執(zhí)行最大池化,我們以2x2塊(因為池大小=2)遍歷輸入圖像,并將最大值放入相應(yīng)像素的輸出圖像中。
對于我們的MNIST CNN,我們將在初始conv layer之后放置一個池大小為2的Max Pooling層,這樣池化層就會將26x26x8輸入轉(zhuǎn)換為13x13x8輸出:

4.1執(zhí)行池化
我們將使用MaxPool2與上一節(jié)中的conv類相同的方法實現(xiàn)一個類:
importnumpyasnpclassMaxPool2:#AMaxPoolinglayerusingapoolsizeof2.defiterate_regions(self,image):'''Generatesnon-overlapping2x2imageregionstopoolover.-imageisa2dnumpyarray'''h,w,_=image.shapenew_h=h//2new_w=w//2foriinrange(new_h):forjinrange(new_w):im_region=image[(i*2):(i*2+2),(j*2):(j*2+2)]yieldim_region,i,jdefforward(self,input):'''Performsaforwardpassofthemaxpoollayerusingthegiveninput.Returnsa3dnumpyarraywithdimensions(h/2,w/2,num_filters).-inputisa3dnumpyarraywithdimensions(h,w,num_filters)'''h,w,num_filters=input.shapeoutput=np.zeros((h//2,w//2,num_filters))forim_region,i,jinself.iterate_regions(input):output[i,j]=np.amax(im_region,axis=(0,1))returnoutput
此類與我們之前實現(xiàn)的Conv3x3類工作方式類似。特別注意的是為了找到給定圖像區(qū)域的最大值,我們使用np.amax()。我們設(shè)置是axis=(0,1),因為我們只希望在前兩個維度(高度和寬度)上進行最大化,而不是第三個維度,num_filters。
我們來試試吧!
importmnistfromconvimportConv3x3frommaxpoolimportMaxPool2#ThemnistpackagehandlestheMNISTdatasetforus!#Learnmoreathttps://github.com/datapythonista/mnisttrain_images=mnist.train_images()train_labels=mnist.train_labels()conv=Conv3x3(8)pool=MaxPool2()output=conv.forward(train_images[0])output=pool.forward(output)print(output.shape)#(13,13,8)
MNIST CNN馬上要完成了!
5、Softmax
為了完成我們的CNN,我們需要讓它能夠?qū)嶋H進行預(yù)測。我們將通過使用標(biāo)準(zhǔn)最終層來實現(xiàn)多類分類問題:Softmax層,一個使用softmax激活函數(shù)的標(biāo)準(zhǔn)全連接(密集)層。
提示:完全連接層將每個節(jié)點連接到前一層的每個輸出。我們在之前的神經(jīng)網(wǎng)絡(luò)的介紹中使用了完全連接的圖層。
Softmax將任意實際值轉(zhuǎn)換為概率。如果你對其背后的數(shù)學(xué)有興趣,自己可以簡單了解下,因為它很簡單。
5.1用法
我們將使用一個帶有10個節(jié)點的softmax層,每個節(jié)點都代表一個數(shù)字,softmax層是我們CNN的最后一層。圖層中的每個節(jié)點都將連接到輸入,在使用softmax變換之后,概率最高的節(jié)點表示的數(shù)字將成為CNN的輸出!

5.2交叉熵?fù)p失函數(shù)
你可能會有這樣的疑問,為什么還要將輸出轉(zhuǎn)換為概率呢?最高輸出值是否總是具有最高概率?如果你有這樣的疑問,那說明你的感覺很對。我們實際上不需要使用softmax來預(yù)測數(shù)字,因為我們可以選擇網(wǎng)絡(luò)輸出最高的數(shù)字!
softmax真正做的是幫助我們量化我們對預(yù)測的確定程度,這在訓(xùn)練和評估我們的CNN時非常有用。更具體地說,它可以幫我們確定每個預(yù)測的正確程度。
5.3實施Softmax
讓我們實現(xiàn)一個Softmax圖層類:
importnumpyasnpclassSoftmax:#Astandardfully-connectedlayerwithsoftmaxactivation.def__init__(self,input_len,nodes):#Wedividebyinput_lentoreducethevarianceofourinitialvaluesself.weights=np.random.randn(input_len,nodes)/input_lenself.biases=np.zeros(nodes)defforward(self,input):'''Performsaforwardpassofthesoftmaxlayerusingthegiveninput.Returnsa1dnumpyarraycontainingtherespectiveprobabilityvalues.-inputcanbeanyarraywithanydimensions.'''input=input.flatten()input_len,nodes=self.weights.shapetotals=np.dot(input,self.weights)+self.biasesexp=np.exp(totals)returnexp/np.sum(exp,axis=0)
我們現(xiàn)在已經(jīng)完成了CNN的整個編碼工作!把它放在一起:
importmnistimportnumpyasnpfromconvimportConv3x3frommaxpoolimportMaxPool2fromsoftmaximportSoftmax#Weonlyusethefirst1ktestingexamples(outof10ktotal)#intheinterestoftime.Feelfreetochangethisifyouwant.test_images=mnist.test_images()[:1000]test_labels=mnist.test_labels()[:1000]conv=Conv3x3(8)#28x28x1->26x26x8pool=MaxPool2()#26x26x8->13x13x8softmax=Softmax(13*13*8,10)#13x13x8->10defforward(image,label):'''CompletesaforwardpassoftheCNNandcalculatestheaccuracyandcross-entropyloss.-imageisa2dnumpyarray-labelisadigit'''#Wetransformtheimagefrom[0,255]to[-0.5,0.5]tomakeiteasier#toworkwith.Thisisstandardpractice.out=conv.forward((image/255)-0.5)out=pool.forward(out)out=softmax.forward(out)#Calculatecross-entropylossandaccuracy.np.log()isthenaturallog.loss=-np.log(out[label])acc=1ifnp.argmax(out)==labelelse0returnout,loss,accprint('MNISTCNNinitialized!')loss=0num_correct=0fori,(im,label)inenumerate(zip(test_images,test_labels)):#Doaforwardpass._,l,acc=forward(im,label)loss+=lnum_correct+=acc#Printstatsevery100steps.ifi%100==99:print('[Step%d]Past100steps:AverageLoss%.3f|Accuracy:%d%%'%(i+1,loss/100,num_correct))loss=0num_correct=0
執(zhí)行cnn.py,我們可以得到:
MNISTCNNinitialized![Step100]Past100steps:AverageLoss2.302|Accuracy:11%[Step200]Past100steps:AverageLoss2.302|Accuracy:8%[Step300]Past100steps:AverageLoss2.302|Accuracy:3%[Step400]Past100steps:AverageLoss2.302|Accuracy:12%
想親自嘗試或修改這些代碼?在瀏覽器中運行此CNN,你也可以在Github上找到它。
-
python
+關(guān)注
關(guān)注
58文章
4885瀏覽量
90302 -
卷積神經(jīng)網(wǎng)絡(luò)
+關(guān)注
關(guān)注
4文章
374瀏覽量
12913
原文標(biāo)題:手把手帶你走進卷積神經(jīng)網(wǎng)絡(luò)!
文章出處:【微信號:Imgtec,微信公眾號:Imagination Tech】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
FPGA實現(xiàn)CNN卷積層的高效窗口生成模塊設(shè)計與驗證
使用PYTHON進行的跨平臺仿真
[VirtualLab] 使用Python運行VirtualLab Fusion光學(xué)仿真
如何在 VisionFive 上使用 Python 包?
如何在 Vision Five 2 上安裝 python 庫?
Termux中調(diào)試圣誕樹Python代碼
迅為如何在RK3576上部署YOLOv5;基于RK3576構(gòu)建智能門禁系統(tǒng)
CNN卷積神經(jīng)網(wǎng)絡(luò)設(shè)計原理及在MCU200T上仿真測試
構(gòu)建CNN網(wǎng)絡(luò)模型并優(yōu)化的一般化建議
Pico Technology發(fā)布Python軟件包pyPicoSDK
如何在TPU上使用JAX訓(xùn)練GPT-2模型
如何在Python中從頭構(gòu)建CNN?
評論