使用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ā)一個(gè)僅使用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ò)。
打算將本文作為一個(gè)實(shí)踐教程,而不是一個(gè)全面指導(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ù),如圖像分類、對象定位、圖像分割等。它們大致模擬了人類的視覺皮層,每個(gè)生物神經(jīng)元只對視野的一小部分做出反應(yīng)。此外,高級神經(jīng)元對其他低級神經(jīng)元的輸出做出反應(yīng)[1]。
正如我在上一篇文章中所展示的,即使是經(jīng)典的神經(jīng)網(wǎng)絡(luò)也可以用于圖像分類等任務(wù)。問題是,它們僅適用于小尺寸圖像,并且在應(yīng)用于中型或大型圖像時(shí)效率極低。原因是經(jīng)典神經(jīng)網(wǎng)絡(luò)需要大量的參數(shù)。
例如,200x200像素的圖像具有40'000個(gè)像素,如果網(wǎng)絡(luò)的第一層具有1'000個(gè)單位,則僅第一層的權(quán)重為4000萬。由于CNN實(shí)現(xiàn)了部分連接的層和權(quán)重共享,這一問題得到了高度緩解。
卷積神經(jīng)網(wǎng)絡(luò)的主要組成部分包括:
· 卷積層
· 池化層
卷積層
卷積層由一組濾波器(也稱為核)組成,當(dāng)應(yīng)用于層的輸入時(shí),對原始圖像進(jìn)行某種修改。濾波器是一種矩陣,其元素值定義了對原始圖像執(zhí)行的修改類型。類似以下的3x3內(nèi)核具有突出顯示圖像中垂直邊的效果:
不同的是,該核突出了水平邊:
核中元素的值不是手動選擇的,而是網(wǎng)絡(luò)在訓(xùn)練期間學(xué)習(xí)的參數(shù)。
卷積的作用是隔離圖像中存在的不同特征。Dense層稍后使用這些功能。
池化層
池化層非常簡單。池化層的任務(wù)是收縮輸入圖像,以減少網(wǎng)絡(luò)的計(jì)算負(fù)載和內(nèi)存消耗。事實(shí)上,減少圖像尺寸意味著減少參數(shù)的數(shù)量。
池化層所做的是使用核(通常為2x2維)并將輸入圖像的一部分聚合為單個(gè)值。例如,2x2最大池核獲取輸入圖像的4個(gè)像素,并僅返回具有最大值的像素。
Python實(shí)現(xiàn)
此GitHub存儲庫中提供了所有代碼。
這個(gè)實(shí)現(xiàn)背后的想法是創(chuàng)建表示卷積和最大池層的Python類。此外,由于該代碼后來被應(yīng)用于MNIST分類問題,我為softmax層創(chuàng)建了一個(gè)類。
每個(gè)類都包含實(shí)現(xiàn)正向傳播和反向傳播的方法。
這些層隨后被連接在一個(gè)列表中,以生成實(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),并將每個(gè)元素除以核大小的平方進(jìn)行歸一化。
patches_generator()方法是一個(gè)生成器。它產(chǎn)生切片。
forward_prop()方法對上述方法生成的每個(gè)切片進(jìn)行卷積。
最后,back_prop()方法負(fù)責(zé)計(jì)算損失函數(shù)相對于層的每個(gè)權(quán)重的梯度,并相應(yīng)地更新權(quán)重值。注意,這里提到的損失函數(shù)不是網(wǎng)絡(luò)的全局損失。相反,它是由最大池層傳遞給前一卷積層的損失函數(shù)。
為了顯示這個(gè)類的實(shí)際效果,我用32個(gè)3x3濾波器實(shí)例化了一個(gè)卷積層對象,并將正向傳播方法應(yīng)用于圖像。輸出包含32個(gè)稍小的圖像。
原始輸入圖像的大小為28x28像素,如下所示:
在應(yīng)用卷積層的前向傳播方法后,我獲得了32幅尺寸為26x26的圖像。這里我繪制了其中一幅:
如你所見,圖像稍小,手寫數(shù)字變得不那么清晰?紤]到這個(gè)操作是由一個(gè)填充了隨機(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個(gè)值。它們可以被解釋為與數(shù)字0–9相對應(yīng)的圖像的概率。
結(jié)論
你可以克隆包含代碼的GitHub存儲庫并使用main.py腳本。該網(wǎng)絡(luò)一開始沒有達(dá)到最先進(jìn)的性能,但在幾個(gè)epoch后達(dá)到96%的準(zhǔn)確率。
參考引用
原文標(biāo)題 : 使用Numpy從頭構(gòu)建卷積神經(jīng)網(wǎng)絡(luò)
請輸入評論內(nèi)容...
請輸入評論/評論長度6~500個(gè)字
最新活動更多
-
即日-10.29立即報(bào)名>> 2024德州儀器嵌入式技術(shù)創(chuàng)新發(fā)展研討會
-
10月31日立即下載>> 【限時(shí)免費(fèi)下載】TE暖通空調(diào)系統(tǒng)高效可靠的組件解決方案
-
即日-11.13立即報(bào)名>>> 【在線會議】多物理場仿真助跑新能源汽車
-
11月14日立即報(bào)名>> 2024工程師系列—工業(yè)電子技術(shù)在線會議
-
12月19日立即報(bào)名>> 【線下會議】OFweek 2024(第九屆)物聯(lián)網(wǎng)產(chǎn)業(yè)大會
-
即日-12.26火熱報(bào)名中>> OFweek2024中國智造CIO在線峰會
推薦專題
- 1 Intel宣布40年來最重大轉(zhuǎn)型:年底前裁員15000人、拋掉2/3房產(chǎn)
- 2 因美封殺TikTok,字節(jié)股價(jià)骨折!估值僅Meta1/5
- 3 宏山激光重磅發(fā)布行業(yè)解決方案,助力智能制造產(chǎn)業(yè)新飛躍
- 4 國產(chǎn)AI芯片公司破產(chǎn)!白菜價(jià)拍賣
- 5 具身智能火了,但規(guī)模落地還需時(shí)間
- 6 國產(chǎn)英偉達(dá)們,抓緊沖刺A股
- 7 三次錯失風(fēng)口!OpenAI前員工殺回AI編程賽道,老東家捧金相助
- 8 英特爾賦能智慧醫(yī)療,共創(chuàng)數(shù)字化未來
- 9 英偉達(dá)的麻煩在后頭?
- 10 將“網(wǎng)紅”變成“商品”,AI“爆改”實(shí)力拉滿
- 高級軟件工程師 廣東省/深圳市
- 自動化高級工程師 廣東省/深圳市
- 光器件研發(fā)工程師 福建省/福州市
- 銷售總監(jiān)(光器件) 北京市/海淀區(qū)
- 激光器高級銷售經(jīng)理 上海市/虹口區(qū)
- 光器件物理工程師 北京市/海淀區(qū)
- 激光研發(fā)工程師 北京市/昌平區(qū)
- 技術(shù)專家 廣東省/江門市
- 封裝工程師 北京市/海淀區(qū)
- 結(jié)構(gòu)工程師 廣東省/深圳市