這是一篇關(guān)于CNN(卷積神經(jīng)網(wǎng)絡(luò))的簡單指南,本文將介紹CNN如何工作,以及如何在Python中從頭開始構(gòu)建一個(gè)CNN。
在過去的幾年中,有很多關(guān)于卷積神經(jīng)網(wǎng)絡(luò)(CNN)的討論,尤其是因?yàn)樗鼈円呀?jīng)徹底改變了計(jì)算機(jī)視覺領(lǐng)域。在這篇文章中,我們將基于神經(jīng)網(wǎng)絡(luò)的基本背景知識,探索CNN是什么,理解它們是如何工作的,并使用Python中的numpy從頭開始構(gòu)建一個(gè)真正的卷積神經(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、動機(jī)
CNN的經(jīng)典用例是執(zhí)行圖像分類,例如查看寵物的圖像并確定它是貓還是狗。這是一項(xiàng)看似非常簡單的任務(wù),你可能會有這樣的疑惑:為什么不使用普通的神經(jīng)網(wǎng)絡(luò)呢?不得不說這是一個(gè)好問題。
原因1:圖像很大
目前用于計(jì)算機(jī)視覺問題的圖像通常為224x224甚至更大。想象一下,構(gòu)建一個(gè)神經(jīng)網(wǎng)絡(luò)來處理224x224彩色圖像:包括圖像中的3個(gè)顏色通道(RGB),即224x224x3=150528個(gè)輸入權(quán)重!這種網(wǎng)絡(luò)中的典型隱藏層可能有1024個(gè)節(jié)點(diǎn),因此我們必須僅為第一層訓(xùn)練150528x1024=15億個(gè)權(quán)重。想象一下?lián)碛?5億個(gè)權(quán)重的神經(jīng)網(wǎng)絡(luò),是不是太大了?這幾乎是不可能完成訓(xùn)練的。
最重要的是其實(shí)我們并不需要那么多的權(quán)重,相反我們僅知道像素點(diǎn)其鄰居的點(diǎn)才是最有用的。因?yàn)閳D像中的物體是由小的局部特征組成的,如圓形虹膜或一張紙的方角。對于第一個(gè)隱藏層中的每個(gè)節(jié)點(diǎn)來說,查看每個(gè)像素似乎是很浪費(fèi)的!
原因2:位置可變
如果你訓(xùn)練了一個(gè)網(wǎng)絡(luò)來檢測狗,那么無論圖像出現(xiàn)在哪張照片中,你都希望它能夠檢測到狗。想象一下,訓(xùn)練一個(gè)在某個(gè)狗圖像上運(yùn)行良好的網(wǎng)絡(luò),然后為它提供相同圖像的略微移位版本,此時(shí)的網(wǎng)絡(luò)會有完全不同的反應(yīng)!
那么CNN是如何幫助我們解決這些問題的呢?不要著急我們很快就會看到CNN如何幫助我們緩解這些問題!
2、數(shù)據(jù)集
在這篇文章中,我們將解決計(jì)算機(jī)視覺的“Hello,World!”:MNIST手寫數(shù)字分類問題。很簡單:給定圖像,將其分類為數(shù)字。
來自MNIST數(shù)據(jù)集的樣本圖像
MNIST數(shù)據(jù)集中的每個(gè)圖像都是28x28,其中包含了一個(gè)居中的灰度數(shù)字。說實(shí)話,一個(gè)正常的神經(jīng)網(wǎng)絡(luò)實(shí)際上可以很好地解決這個(gè)問題。你可以將每個(gè)圖像視為28x28=784維矢量,將其輸入到784-dim圖層,堆疊一些隱藏圖層,最后輸出10個(gè)節(jié)點(diǎn)的輸出圖層,每個(gè)數(shù)字1個(gè)。
這樣做可以完成任務(wù),因?yàn)镸NIST數(shù)據(jù)集包含的都是些居中的小圖像,因此我們不會遇到上述的大小或移位問題。但是,請記住,大多數(shù)現(xiàn)實(shí)世界的圖像分類問題并不容易。
那么就讓我們進(jìn)入CNN吧!
3、卷積
什么是卷積神經(jīng)網(wǎng)絡(luò)?
它們基本上是使用卷積層的神經(jīng)網(wǎng)絡(luò),即Conv層,它們基于卷積的數(shù)學(xué)運(yùn)算。Conv圖層由一組過濾器組成,你可以將其視為2d數(shù)字矩陣。這是一個(gè)示例3x3過濾器:
一個(gè)3x3過濾器
我們可以通過將濾波器與輸入圖像進(jìn)行卷積來產(chǎn)生輸出圖像。
這包括:
在某個(gè)位置覆蓋圖像頂部的過濾器;
在過濾器中的值與圖像中的相應(yīng)值之間執(zhí)行逐元素乘法;
所有元素(element-wise products)求和,此和是輸出圖像中目標(biāo)像素的輸出值。
重復(fù)所有位置。
注釋:實(shí)際上我們(以及許多CNN實(shí)現(xiàn))在技術(shù)上使用互相關(guān)(Cross-correlation)而不是卷積,但它們幾乎完全相同。
這4步描述有點(diǎn)抽象,所以讓我們舉個(gè)例子吧,考慮這個(gè)微小的4x4灰度圖像和這個(gè)3x3過濾器:
4x4圖像(左)和3x3濾鏡(右)
圖像中的數(shù)字表示像素強(qiáng)度,其中0是黑色,255是白色。我們將對輸入圖像和過濾器進(jìn)行卷積以生成2x2輸出圖像:
2x2輸出圖像
首先,讓我們將過濾器疊加在圖片的左上角:
第1步:將過濾器(右)疊加在圖像上方(左)
接下來,我們在重疊圖像值和過濾器值之間執(zhí)行逐元素乘法。以下是結(jié)果,從左上角開始向右,然后向下:
第2步:執(zhí)行逐元素乘法。
接下來,我們總結(jié)所有結(jié)果。這很容易:62-33=29。
最后,我們將結(jié)果放在輸出圖像的目標(biāo)像素中。由于我們的過濾器覆蓋在輸入圖像的左上角,因此我們的目標(biāo)像素是輸出圖像的左上角像素:
我們做同樣的步驟來生成輸出圖像的其余部分:
3.1 這有用嗎?
用過濾器卷積圖像有什么作用?我們可以先使用我們一直使用的示例3x3濾波器,這通常被稱為垂直索貝爾濾波器:
垂直索貝爾濾波器
以下是垂直Sobel濾波器的示例:
垂直Sobel濾波器卷積的圖像
同樣,還有一個(gè)水平Sobel濾波器:
水平Sobel濾波器
水平Sobel濾波器卷積的圖像
到底發(fā)生了什么?Sobel濾波器是邊緣檢測器。垂直Sobel濾波器檢測的是垂直邊緣,水平Sobel濾波器的是檢測水平邊緣。現(xiàn)在可以輕松解釋輸出圖像:輸出圖像中的亮像素(具有高值的像素)表示原始圖像中存在強(qiáng)邊緣。
你能看出為什么邊緣檢測圖像可能比原始圖像更有用嗎?回想一下我們的MNIST手寫數(shù)字分類問題。在MNIST上訓(xùn)練的CNN可以尋找數(shù)字1,例如,通過使用邊緣檢測濾波器并檢查圖像中心附近的兩個(gè)突出的垂直邊緣。通常,卷積有助于我們查找特定的本地化圖像特征。
3.2 填充(Padding)
還記得先用3x3過濾器對4x4的輸入圖像進(jìn)行卷積,以產(chǎn)生2x2輸出圖像嗎?通常,我們希望輸出圖像的大小與輸入圖像的大小相同。為此,我們在圖像周圍添加零,以便我們可以在更多位置疊加過濾器。3x3濾鏡需要1個(gè)像素的填充:
4x4輸入與3x3濾波器卷積,使用填充以產(chǎn)生4x4輸出
這稱為“相同”填充,因?yàn)檩斎牒洼敵鼍哂邢嗤某叽纭2皇褂萌魏翁畛洌@是我們一直在做的,有時(shí)也被稱為“有效”填充。
3.3 Conv圖層
既然我們知道圖像卷積是如何工作的以及為什么用它,那讓我們看看它是如何在CNN中實(shí)際使用的。如前所述,CNN包括使用一組過濾器將輸入圖像轉(zhuǎn)換為輸出圖像的conv layer。conv層的主要參數(shù)是它具有的過濾器數(shù)量。
對于我們的MNIST CNN,我們將使用一個(gè)帶有8個(gè)過濾器的小conv layer作為我們網(wǎng)絡(luò)中的初始層。這意味著它會將28x28輸入圖像轉(zhuǎn)換為26x26x8輸出向量:
提醒:輸出為26x26x8而不是28x28x8,因?yàn)槲覀兪褂糜行畛洌@會將輸入的寬度和高度減少2。
conv layer中的4個(gè)過濾器中的每一個(gè)都會產(chǎn)生26x26輸出,因此堆疊在一起它們構(gòu)成26x26x8的向量。
3.4 執(zhí)行卷積
是時(shí)候?qū)⑽覀儗W(xué)到的東西放到代碼中了!我們將實(shí)現(xiàn)一個(gè)conv layer的前饋部分,該部分負(fù)責(zé)處理帶有輸入圖像的過濾器,以產(chǎn)生輸出向量。為簡單起見,我們假設(shè)過濾器是3x3(其實(shí)5x5和7x7過濾器也很常見)。
讓我們開始實(shí)現(xiàn)一個(gè)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
該類只有一個(gè)參數(shù):過濾器的數(shù)量。在構(gòu)造函數(shù)中,我們存儲過濾器的數(shù)量并使用NumPy的randn()方法初始化隨機(jī)過濾器數(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
接下來,實(shí)際卷積:
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()是一個(gè)輔助生成器方法,為我們生成所有有效的3x3圖像區(qū)域,這對于稍后實(shí)現(xiàn)此類的后向部分非常有用。
上面是實(shí)際執(zhí)行卷積的代碼行。讓我們分解一下:
im_region:一個(gè)包含相關(guān)圖像區(qū)域的3x3陣列。
self.filters:一個(gè)3d數(shù)組。
im_region*self.filtersself.filters:我們使用numpy的廣播(broadcasting)功能以元素方式乘以兩個(gè)數(shù)組,結(jié)果是具有相同尺寸的3d數(shù)組。
axis=(1,2)num_filters:我們使用np.sum()上一步的結(jié)果,產(chǎn)生一個(gè)長度為1d的數(shù)組,其中每個(gè)元素包含相應(yīng)過濾器的卷積結(jié)果。
我們將結(jié)果分配給output[i,j],其中包含輸出中像素的卷積結(jié)果(i,j)。
對輸出中的每個(gè)像素執(zhí)行上面的序列,直到我們獲得我們想要的結(jié)果!讓我們的代碼進(jìn)行測試運(yùn)行:
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實(shí)現(xiàn)中,我們假設(shè)輸入是一個(gè)2d numpy數(shù)組,因?yàn)檫@是我們的MNIST圖像的存儲方式。這對我們有用,我們將它用作網(wǎng)絡(luò)中的第一層,但大多數(shù)CNN都有更多的Conv層。如果我們要構(gòu)建一個(gè)需要Conv3x3多次使用的更大的網(wǎng)絡(luò),我們必須使輸入成為3d numpy數(shù)組。
4、池化(Pooling)
圖像中的相鄰像素傾向于具有相似的值,因此conv layer通常也會為輸出中的相鄰像素產(chǎn)生類似的值。但是conv layer中輸出的大部分信息都是冗余的,例如,如果我們使用邊緣檢測過濾器并在某個(gè)位置找到強(qiáng)邊緣,那么我們也可能會在距離原始像素1個(gè)像素偏移的位置找到相對較強(qiáng)的邊緣。但是,我們可能并沒有找到任何新的東西。
池化層解決了這個(gè)問題。他們所做的就是減少通過猜測在輸入中產(chǎn)生的匯總值。該池通常是通過簡單的操作完成max,min或average等這些操作。以下是池化大小為2的Max Pooling圖層的示例:
4x4圖像上的最大池(池大小為2)以產(chǎn)生2x2輸出
為了執(zhí)行最大池化,我們以2x2塊(因?yàn)槌卮笮?2)遍歷輸入圖像,并將最大值放入相應(yīng)像素的輸出圖像中。
對于我們的MNIST CNN,我們將在初始conv layer之后放置一個(gè)池大小為2的Max Pooling層,這樣池化層就會將26x26x8輸入轉(zhuǎn)換為13x13x8輸出:
4.1執(zhí)行池化
我們將使用MaxPool2與上一節(jié)中的conv類相同的方法實(shí)現(xiàn)一個(gè)類:
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
此類與我們之前實(shí)現(xiàn)的Conv3x3類工作方式類似。特別注意的是為了找到給定圖像區(qū)域的最大值,我們使用np.amax()。我們設(shè)置是axis=(0,1),因?yàn)槲覀冎幌M谇皟蓚€(gè)維度(高度和寬度)上進(jìn)行最大化,而不是第三個(gè)維度,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進(jìn)行預(yù)測。我們將通過使用標(biāo)準(zhǔn)最終層來實(shí)現(xiàn)多類分類問題:Softmax層,一個(gè)使用softmax激活函數(shù)的標(biāo)準(zhǔn)全連接(密集)層。
提示:完全連接層將每個(gè)節(jié)點(diǎn)連接到前一層的每個(gè)輸出。我們在之前的神經(jīng)網(wǎng)絡(luò)的介紹中使用了完全連接的圖層。
Softmax將任意實(shí)際值轉(zhuǎn)換為概率。如果你對其背后的數(shù)學(xué)有興趣,自己可以簡單了解下,因?yàn)樗芎唵巍?/p>
5.1用法
我們將使用一個(gè)帶有10個(gè)節(jié)點(diǎn)的softmax層,每個(gè)節(jié)點(diǎn)都代表一個(gè)數(shù)字,softmax層是我們CNN的最后一層。圖層中的每個(gè)節(jié)點(diǎn)都將連接到輸入,在使用softmax變換之后,概率最高的節(jié)點(diǎn)表示的數(shù)字將成為CNN的輸出!
5.2交叉熵?fù)p失函數(shù)
你可能會有這樣的疑問,為什么還要將輸出轉(zhuǎn)換為概率呢?最高輸出值是否總是具有最高概率?如果你有這樣的疑問,那說明你的感覺很對。我們實(shí)際上不需要使用softmax來預(yù)測數(shù)字,因?yàn)槲覀兛梢赃x擇網(wǎng)絡(luò)輸出最高的數(shù)字!
softmax真正做的是幫助我們量化我們對預(yù)測的確定程度,這在訓(xùn)練和評估我們的CNN時(shí)非常有用。更具體地說,它可以幫我們確定每個(gè)預(yù)測的正確程度。
5.3實(shí)施Softmax
讓我們實(shí)現(xiàn)一個(gè)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的整個(gè)編碼工作!把它放在一起:
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%
想親自嘗試或修改這些代碼?在瀏覽器中運(yùn)行此CNN,你也可以在Github上找到它。
-
python
+關(guān)注
關(guān)注
56文章
4797瀏覽量
84776 -
卷積神經(jīng)網(wǎng)絡(luò)
+關(guān)注
關(guān)注
4文章
367瀏覽量
11878
原文標(biāo)題:手把手帶你走進(jìn)卷積神經(jīng)網(wǎng)絡(luò)!
文章出處:【微信號:Imgtec,微信公眾號:Imagination Tech】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論