卷積神經網絡的直觀指南(Python實例)

卷積神經網絡的直觀指南(Python實例)

介紹

卷積神經網絡(CNN)是一個深度網絡家族,可以利用數據的空間結構(例如圖像)來了解數據,以便該算法可以輸出一些有用的信息。考慮一個問題,我們想要確定某個圖像中是否有人。例如,如果我給CNN一個人的圖像,這個深度神經網絡首先需要學習一些局部特徵(例如眼睛,鼻子,嘴巴等)。這些局部特徵是在卷積層中學習的。

然後,CNN將查看給定圖像中存在的局部特徵,然後生成全局表示這些局部特徵映射的存在的特定激活模式(或激活矢量)。這些激活模式由CNN中完全連接的層產生。例如,如果圖像是非人物,則激活模式將與針對人物圖像的激活模式不同。

CNN在模塊化層面

現在我們來看看CNN中存在哪些子模塊。典型的CNN有三種不同的組件。它們是卷積層,池化層和全連接層。

首先我們討論卷積層深入的內容。卷積層由許多內核組成。這些內核(有時稱為卷積濾波器)存在於卷積層中,學習圖像中存在的局部特徵(例如,人的眼睛如何看起來像)。卷積層學習的這種局部特徵稱為特徵映射。然後將這些功能捲入圖像。這種卷積操作將產生一個矩陣(有時稱為激活圖)。如果在卷積濾波器中表示的特徵出現在輸入的該位置處,則激活圖在給定位置處產生高值。

池化層使這些特徵通過CNN平移不變(例如,無論人的眼睛是在[ x = 10,y = 10 ]還是[ x = 12,y = 11 ]位置,池化輸出將是相同)。請注意,我們討論每層輕微的平移變化。然而,彙總幾個這樣的層,使我們有更高的平移不變性。

最後我們有全連接層。完全連接的層負責根據激活的特徵圖集和圖像中的位置生成不同的激活圖案,並激活特徵圖。這就是CNN在視覺上的樣子。

卷積神經網絡的直觀指南(Python實例)

在瞭解CNN的整體結構的基礎上,讓我們繼續理解構成CNN的每個子組件。

卷積層

卷積操作究竟做了什麼?如果卷積特徵存在於該位置,則卷積操作為給定位置輸出高值,否則輸出低值。更具體地說,在卷積核的給定位置,我們採用每個核心單元值和與核心單元重疊的對應圖像像素值的元素方式乘法,然後取其和。精確值根據以下公式確定(m - 核寬和高,h - 卷積輸出,x - 輸入,w - 卷積核)。

卷積神經網絡的直觀指南(Python實例)

圖像上的卷積過程可以如下進行可視化。

卷積神經網絡的直觀指南(Python實例)

僅僅知道卷積運算的作用還不夠,我們還需要了解卷積輸出代表什麼。設想卷積輸出中的值的顏色(0 - 黑色,100 - 白色)。如果你想象這幅圖像,它將代表在眼睛所在位置點亮的二值圖像。

卷積神經網絡的直觀指南(Python實例)

卷積運算也可以被認為是對給定圖像執行一些變換。這種轉換可能導致各種效果(例如,提取邊緣,模糊等)。

池化層

現在讓我們瞭解池操作的作用。池化(或有時稱為二次採樣)層使得CNN在卷積輸出方面具有一點平移不變性。實踐中使用了兩種不同的池化機制(max-pooling和average-pooling)。我們將max-pooling稱為pooling,因為max-pooling與平均池化相比被廣泛使用。更準確地說,在給定位置的池操作輸出落入內核的輸入的最大值。在數學上,

卷積神經網絡的直觀指南(Python實例)

讓我們通過在前面看到的卷積輸出上應用池操作來理解池的工作原理

卷積神經網絡的直觀指南(Python實例)

如您所見,我們使用同一圖像的兩個變體; 一個原始圖像和另一個圖像在x軸上稍微平移。但是,池操作會為兩個圖像輸出完全相同的特徵圖(黑色 - 0,白色 - 100)。所以我們說池化操作使得CNN翻譯中的知識不變成為可能。需要注意的一點是,我們一次不移動1個像素,而是一次移動2個像素。這就是所謂的strided-pooling,這意味著我們正在以2的步幅進行合併。

全連接層

全連接層將結合由不同卷積核所學習的特徵,以便網絡可以建立關於整體圖像的全局表示。我們可以理解完全連接的層,如下所示。

卷積神經網絡的直觀指南(Python實例)

根據由卷積特徵表示的各種實體是否實際存在於輸入中,全連接層中的神經元將被激活。當完全連接的神經元為此激活時,它會根據輸入圖像中存在的特徵產生不同的激活模式。這為圖像中存在的輸出層提供了一種緊湊的表示形式,即輸出圖層可以輕鬆用於正確分類圖像。

Weaving Them Together

現在我們所要做的就是將所有這些組合起來,形成一個從原始圖像到決策的端到端模型。一旦連接CNN將看起來像這樣。總而言之,卷積層將學習數據中的各種局部特徵(例如眼睛的樣子),那麼池化層將使得CNN對於這些特徵的平移不變(例如,如果眼睛在兩個圖像中稍微翻譯,CNN還承認它作為一個眼睛)。最後,我們有全連接層,說:“我們發現了兩隻眼睛,一隻鼻子和一隻嘴巴,所以這必須是一個人,並激活正確的輸出。

卷積神經網絡的直觀指南(Python實例)

添加越來越多的層會有什麼作用?

增加更多層顯然可以提高深度神經網絡的性能。事實上,深度學習中最引人注目的突破性研究大多與解決如何添加更多層次的問題有關。而不會中斷模型的訓練。因為模型越深入,訓練越困難。

但擁有更多層可以幫助CNN以分層方式學習功能。例如,第一層學習圖像中的各種邊緣方向,第二層學習基本形狀(圓形,三角形等),第三層學習更多先進形狀(例如,眼睛的形狀,鼻子的形狀)等等上。與使用單層圖層學習CNN相比,這將帶來更好的性能。

訓練CNN(又名反向傳播)

現在需要記住的一點是,當你實現CNN時,這些卷積特徵(眼睛,鼻子,嘴巴)不會神奇地出現。目標是學習給定數據的這些特徵。為此,我們定義一個成本函數,獎勵正確識別的數據並懲罰錯誤分類的數據。示例成本函數將是均方根誤差或二元交叉熵損失。

在定義了損失之後,我們可以優化特徵的權重(即特徵的每個單元格值),以反映引導CNN正確識別某個人的有用特徵。更具體地說,我們通過在每個參數的梯度相對於損失顯示的相反方向上採取一小步來優化每個卷積核和完全連接的神經元。但是,要實現CNN,您不需要知道如何實現梯度傳播的確切細節。這是因為,大多數深度學習庫(例如TensorFlow,PyTorch)都在內部實現這些差異化操作,當您自動定義正向計算時。

用Keras實現和運行CNN

這裡我們將簡要討論如何實現CNN。瞭解基礎知識還不夠,我們也應該瞭解如何使用像Keras這樣的標準深度學習庫來實現模型。Keras是一個奇妙的工具,特別是快速原型模型。下面以Python進行講解:

首先我們定義我們想要使用的Keras API。我們將使用 sequential API。

# Define a sequential model

model = Sequential()

然後我們定義一個卷積層,如下所示:

# Added a convolution layer

model.add(Conv2D(32, (3,3), activation=’relu’, input_shape=[28, 28, 1]))

在這裡,32是層中的核數,(3,3)是卷積層的核大小(高度和寬度)。我們使用非線性激活Relu和input shape[28,28,1],即[image height, image width, color channels]。注意,input shape應該是上一層產生的輸出的shape 。對於第一個卷積層我們有實際的數據輸入。對於層的其餘部分,它將是上一層產生的輸出。接下來,我們將討論如何實現最大池化層:

# Add a max pool lyer

model.add(MaxPool2D())

這裡我們不提供任何參數,因為我們將使用Keras中提供的默認值。如果你沒有指定參數,Keras將使用(2,2)的內核大小和(2,2)的步幅。接下來我們定義全連接層。但在此之前,我們需要將輸出flatten,因為全連接層處理1D數據:

model.add(Flatten())

model.add(Dense(256, activation=’relu’))

model.add(Dense(10, activation=’softmax’))

在這裡我們定義了兩個完全連接或密集的層。第一個完全連接的層具有256神經元並使用Relu激活。最後我們定義一個具有10個softmax激活的輸出節點的密集層。這充當輸出層,它將激活具有相同對象的圖像的特定神經元。最後我們編譯我們的模型,

model.compile(

optimizer=’adam’, loss=’categorical_crossentropy’, metrics=[‘accuracy’]

)

在這裡,我們說使用Adam優化器(訓練模型),交叉熵損失並使用accuracy模型來評估模型。最後我們可以使用數據來訓練和測試我們的模型。我們將使用MNIST數據集(http://yann.lecun.com/exdb/mnist/),我們將使用定義的函數maybe_download和read_mnist函數將其下載並讀入內存中。MNIST數據集包含手寫數字(0-9)的圖像,目標是通過分配圖像表示的數字來正確分類圖像。

def maybe_download(url, filename, expected_bytes, force=False):

"""Download a file if not present, and make sure it's the right size."""

if force or not os.path.exists(filename):

print('Attempting to download:', filename)

filename, _ = urlretrieve(url + filename, filename)

print('\nDownload Complete!')

statinfo = os.stat(filename)

if statinfo.st_size == expected_bytes:

print('Found and verified', filename)

else:

raise Exception(

'Failed to verify ' + filename + '. Can you get to it with a browser?')

return filename

def read_mnist(fname_img, fname_lbl, one_hot=False):

print('\nReading files %s and %s'%(fname_img, fname_lbl))

# Processing images

with gzip.open(fname_img) as fimg:

magic, num, rows, cols = struct.unpack(">IIII", fimg.read(16))

print(num,rows,cols)

img = (np.frombuffer(fimg.read(num*rows*cols), dtype=np.uint8).reshape(num, rows, cols,1)).astype(np.float32)

print('(Images) Returned a tensor of shape ',img.shape)

img *= 1.0 / 255.0

# Processing labels

with gzip.open(fname_lbl) as flbl:

# flbl.read(8) reads upto 8 bytes

magic, num = struct.unpack(">II", flbl.read(8))

lbl = np.frombuffer(flbl.read(num), dtype=np.int8)

if one_hot:

one_hot_lbl = np.zeros(shape=(num,10),dtype=np.float32)

one_hot_lbl[np.arange(num),lbl] = 1.0

print('(Labels) Returned a tensor of shape: %s'%lbl.shape)

print('Sample labels: ',lbl[:10])

if not one_hot:

return img, lbl

else:

return img, one_hot_lbl

接下來我們通過調用以下函數來訓練我們的模型:

model.fit(x_train, y_train, batch_size = batch_size)

我們可以用一些測試數據來測試我們的模型,如下所示:

test_acc = model.evaluate(x_test, y_test, batch_size=batch_size)


分享到:


相關文章: