使用Numpy從頭構(gòu)建卷積神經(jīng)網(wǎng)絡(luò)
使用該網(wǎng)絡(luò)對手寫數(shù)字進(jìn)行分類。所獲得的結(jié)果不是最先進(jìn)的水平,但仍然令人滿意,F(xiàn)在想更進(jìn)一步,我們的目標(biāo)是開發(fā)一個僅使用Numpy的卷積神經(jīng)網(wǎng)絡(luò)(CNN)。
這項(xiàng)任務(wù)背后的動機(jī)與創(chuàng)建全連接的網(wǎng)絡(luò)的動機(jī)相同:盡管Python深度學(xué)習(xí)庫是強(qiáng)大的工具,但它阻止從業(yè)者理解底層正在發(fā)生的事情。對于CNNs來說,這一點(diǎn)尤其正確,因?yàn)樵撨^程不如經(jīng)典深度網(wǎng)絡(luò)執(zhí)行的過程直觀。
解決這一問題的唯一辦法是嘗試自己實(shí)現(xiàn)這些網(wǎng)絡(luò)。
打算將本文作為一個實(shí)踐教程,而不是一個全面指導(dǎo)CNNs運(yùn)作原則的教程。因此,理論部分很窄,主要用于對實(shí)踐部分的理解。
對于需要更好地理解卷積網(wǎng)絡(luò)工作原理的讀者,留下了一些很好的資源。
什么是卷積神經(jīng)網(wǎng)絡(luò)?
卷積神經(jīng)網(wǎng)絡(luò)使用特殊的結(jié)構(gòu)和操作,使其非常適合圖像相關(guān)任務(wù),如圖像分類、對象定位、圖像分割等。它們大致模擬了人類的視覺皮層,每個生物神經(jīng)元只對視野的一小部分做出反應(yīng)。此外,高級神經(jīng)元對其他低級神經(jīng)元的輸出做出反應(yīng)[1]。
正如我在上一篇文章中所展示的,即使是經(jīng)典的神經(jīng)網(wǎng)絡(luò)也可以用于圖像分類等任務(wù)。問題是,它們僅適用于小尺寸圖像,并且在應(yīng)用于中型或大型圖像時效率極低。原因是經(jīng)典神經(jīng)網(wǎng)絡(luò)需要大量的參數(shù)。
例如,200x200像素的圖像具有40'000個像素,如果網(wǎng)絡(luò)的第一層具有1'000個單位,則僅第一層的權(quán)重為4000萬。由于CNN實(shí)現(xiàn)了部分連接的層和權(quán)重共享,這一問題得到了高度緩解。
卷積神經(jīng)網(wǎng)絡(luò)的主要組成部分包括:
· 卷積層
· 池化層
卷積層
卷積層由一組濾波器(也稱為核)組成,當(dāng)應(yīng)用于層的輸入時,對原始圖像進(jìn)行某種修改。濾波器是一種矩陣,其元素值定義了對原始圖像執(zhí)行的修改類型。類似以下的3x3內(nèi)核具有突出顯示圖像中垂直邊的效果:
不同的是,該核突出了水平邊:
核中元素的值不是手動選擇的,而是網(wǎng)絡(luò)在訓(xùn)練期間學(xué)習(xí)的參數(shù)。
卷積的作用是隔離圖像中存在的不同特征。Dense層稍后使用這些功能。
池化層
池化層非常簡單。池化層的任務(wù)是收縮輸入圖像,以減少網(wǎng)絡(luò)的計算負(fù)載和內(nèi)存消耗。事實(shí)上,減少圖像尺寸意味著減少參數(shù)的數(shù)量。
池化層所做的是使用核(通常為2x2維)并將輸入圖像的一部分聚合為單個值。例如,2x2最大池核獲取輸入圖像的4個像素,并僅返回具有最大值的像素。
Python實(shí)現(xiàn)
此GitHub存儲庫中提供了所有代碼。
這個實(shí)現(xiàn)背后的想法是創(chuàng)建表示卷積和最大池層的Python類。此外,由于該代碼后來被應(yīng)用于MNIST分類問題,我為softmax層創(chuàng)建了一個類。
每個類都包含實(shí)現(xiàn)正向傳播和反向傳播的方法。
這些層隨后被連接在一個列表中,以生成實(shí)際的CNN。
卷積層實(shí)現(xiàn)
class ConvolutionLayer:
def __init__(self, kernel_num, kernel_size):
self.kernel_num = kernel_num
self.kernel_size = kernel_size
self.kernels = np.random.randn(kernel_num, kernel_size, kernel_size) / (kernel_size**2)
def patches_generator(self, image):
image_h(yuǎn), image_w = image.shape
self.image = image
for h in range(image_h(yuǎn)-self.kernel_size+1):
for w in range(image_w-self.kernel_size+1):
patch = image[h:(h+self.kernel_size), w:(w+self.kernel_size)]
yield patch, h, w
def forward_prop(self, image):
image_h(yuǎn), image_w = image.shape
convolution_output = np.zeros((image_h(yuǎn)-self.kernel_size+1, image_w-self.kernel_size+1, self.kernel_num))
for patch, h, w in self.patches_generator(image):
convolution_output[h,w] = np.sum(patch*self.kernels, axis=(1,2))
return convolution_output
def back_prop(self, dE_dY, alpha):
dE_dk = np.zeros(self.kernels.shape)
for patch, h, w in self.patches_generator(self.image):
for f in range(self.kernel_num):
dE_dk[f] += patch * dE_dY[h, w, f]
self.kernels -= alpha*dE_dk
return dE_dk
構(gòu)造器將卷積層的核數(shù)及其大小作為輸入。我假設(shè)只使用大小為kernel_size x kernel_size的平方核。
在第5行中,我生成隨機(jī)濾波器(kernel_num、kernel_size、kernel_size),并將每個元素除以核大小的平方進(jìn)行歸一化。
patches_generator()方法是一個生成器。它產(chǎn)生切片。
forward_prop()方法對上述方法生成的每個切片進(jìn)行卷積。
最后,back_prop()方法負(fù)責(zé)計算損失函數(shù)相對于層的每個權(quán)重的梯度,并相應(yīng)地更新權(quán)重值。注意,這里提到的損失函數(shù)不是網(wǎng)絡(luò)的全局損失。相反,它是由最大池層傳遞給前一卷積層的損失函數(shù)。
為了顯示這個類的實(shí)際效果,我用32個3x3濾波器實(shí)例化了一個卷積層對象,并將正向傳播方法應(yīng)用于圖像。輸出包含32個稍小的圖像。
原始輸入圖像的大小為28x28像素,如下所示:
在應(yīng)用卷積層的前向傳播方法后,我獲得了32幅尺寸為26x26的圖像。這里我繪制了其中一幅:
如你所見,圖像稍小,手寫數(shù)字變得不那么清晰?紤]到這個操作是由一個填充了隨機(jī)值的濾波器執(zhí)行的,所以它并不代表經(jīng)過訓(xùn)練的CNN實(shí)際執(zhí)行的操作。
盡管如此,你可以得到這樣的想法,即這些卷積提供了較小的圖像,其中對象特征被隔離。
最大池層實(shí)現(xiàn)
class MaxPoolingLayer:
def __init__(self, kernel_size):
self.kernel_size = kernel_size
def patches_generator(self, image):
output_h(yuǎn) = image.shape[0] // self.kernel_size
output_w = image.shape[1] // self.kernel_size
self.image = image
for h in range(output_h(yuǎn)):
for w in range(output_w):
patch = image[(h*self.kernel_size):(h*self.kernel_size+self.kernel_size), (w*self.kernel_size):(w*self.kernel_size+self.kernel_size)]
yield patch, h, w
def forward_prop(self, image):
image_h(yuǎn), image_w, num_kernels = image.shape
max_pooling_output = np.zeros((image_h(yuǎn)//self.kernel_size, image_w//self.kernel_size, num_kernels))
for patch, h, w in self.patches_generator(image):
max_pooling_output[h,w] = np.a(chǎn)max(patch, axis=(0,1))
return max_pooling_output
def back_prop(self, dE_dY):
dE_dk = np.zeros(self.image.shape)
for patch,h,w in self.patches_generator(self.image):
image_h(yuǎn), image_w, num_kernels = patch.shape
max_val = np.a(chǎn)max(patch, axis=(0,1))
for idx_h(yuǎn) in range(image_h(yuǎn)):
for idx_w in range(image_w):
for idx_k in range(num_kernels):
if patch[idx_h(yuǎn),idx_w,idx_k] == max_val[idx_k]:
dE_dk[h*self.kernel_size+idx_h(yuǎn), w*self.kernel_size+idx_w, idx_k] = dE_dY[h,w,idx_k]
return dE_dk
構(gòu)造函數(shù)方法只分配核大小值。以下方法與卷積層的方法類似,主要區(qū)別在于反向傳播函數(shù)不更新任何權(quán)重。事實(shí)上,池化層不依賴于權(quán)重來執(zhí)行。
Sigmoid層實(shí)現(xiàn)
class SoftmaxLayer:
def __init__(self, input_units, output_units):
self.weight = np.random.randn(input_units, output_units)/input_units
self.bias = np.zeros(output_units)
def forward_prop(self, image):
self.original_shape = image.shape
image_flattened = image.flatten()
self.flattened_input = image_flattened
first_output = np.dot(image_flattened, self.weight) + self.bias
self.output = first_output
softmax_output = np.exp(first_output) / np.sum(np.exp(first_output), axis=0)
return softmax_output
def back_prop(self, dE_dY, alpha):
for i, gradient in enumerate(dE_dY):
if gradient == 0:
continue
transformation_eq = np.exp(self.output)
S_total = np.sum(transformation_eq)
dY_dZ = -transformation_eq[i]*transformation_eq / (S_total**2)
dY_dZ[i] = transformation_eq[i]*(S_total - transformation_eq[i]) / (S_total**2)
dZ_dw = self.flattened_input
dZ_db = 1
dZ_dX = self.weight
dE_dZ = gradient * dY_dZ
dE_dw = dZ_dw[np.newaxis].T @ dE_dZ[np.newaxis]
dE_db = dE_dZ * dZ_db
dE_dX = dZ_dX @ dE_dZ
self.weight -= alpha*dE_dw
self.bias -= alpha*dE_db
return dE_dX.reshape(self.original_shape)
softmax層使最大池提供的輸出體積變平,并輸出10個值。它們可以被解釋為與數(shù)字0–9相對應(yīng)的圖像的概率。
結(jié)論
你可以克隆包含代碼的GitHub存儲庫并使用main.py腳本。該網(wǎng)絡(luò)一開始沒有達(dá)到最先進(jìn)的性能,但在幾個epoch后達(dá)到96%的準(zhǔn)確率。
參考引用
原文標(biāo)題 : 使用Numpy從頭構(gòu)建卷積神經(jīng)網(wǎng)絡(luò)

最新活動更多
推薦專題
- 1 UALink規(guī)范發(fā)布:挑戰(zhàn)英偉達(dá)AI統(tǒng)治的開始
- 2 北電數(shù)智主辦酒仙橋論壇,探索AI產(chǎn)業(yè)發(fā)展新路徑
- 3 “AI寒武紀(jì)”爆發(fā)至今,五類新物種登上歷史舞臺
- 4 降薪、加班、裁員三重暴擊,“AI四小龍”已折戟兩家
- 5 光計算迎來商業(yè)化突破,但落地仍需時間
- 6 大模型下半場:Agent時代為何更需要開源模型
- 7 中國“智造”背后的「關(guān)鍵力量」
- 8 優(yōu)必選:營收大增主靠小件,虧損繼續(xù)又逢關(guān)稅,能否乘機(jī)器人東風(fēng)翻身?
- 9 營收猛增46%,昆侖萬維成為AI“爆品工廠”
- 10 全球無人駕駛技術(shù)排名:誰才是細(xì)分賽道的扛把子?