使用Numpy從頭構(gòu)建卷積神經(jīng)網(wǎng)絡(luò)
使用該網(wǎng)絡(luò)對(duì)手寫(xiě)數(shù)字進(jìn)行分類。所獲得的結(jié)果不是最先進(jìn)的水平,但仍然令人滿意。現(xiàn)在想更進(jìn)一步,我們的目標(biāo)是開(kāi)發(fā)一個(gè)僅使用Numpy的卷積神經(jīng)網(wǎng)絡(luò)(CNN)。
這項(xiàng)任務(wù)背后的動(dòng)機(jī)與創(chuàng)建全連接的網(wǎng)絡(luò)的動(dòng)機(jī)相同:盡管Python深度學(xué)習(xí)庫(kù)是強(qiáng)大的工具,但它阻止從業(yè)者理解底層正在發(fā)生的事情。對(duì)于CNNs來(lái)說(shuō),這一點(diǎn)尤其正確,因?yàn)樵撨^(guò)程不如經(jīng)典深度網(wǎng)絡(luò)執(zhí)行的過(guò)程直觀。
解決這一問(wèn)題的唯一辦法是嘗試自己實(shí)現(xiàn)這些網(wǎng)絡(luò)。
打算將本文作為一個(gè)實(shí)踐教程,而不是一個(gè)全面指導(dǎo)CNNs運(yùn)作原則的教程。因此,理論部分很窄,主要用于對(duì)實(shí)踐部分的理解。
對(duì)于需要更好地理解卷積網(wǎng)絡(luò)工作原理的讀者,留下了一些很好的資源。
什么是卷積神經(jīng)網(wǎng)絡(luò)?
卷積神經(jīng)網(wǎng)絡(luò)使用特殊的結(jié)構(gòu)和操作,使其非常適合圖像相關(guān)任務(wù),如圖像分類、對(duì)象定位、圖像分割等。它們大致模擬了人類的視覺(jué)皮層,每個(gè)生物神經(jīng)元只對(duì)視野的一小部分做出反應(yīng)。此外,高級(jí)神經(jīng)元對(duì)其他低級(jí)神經(jīng)元的輸出做出反應(yīng)[1]。
正如我在上一篇文章中所展示的,即使是經(jīng)典的神經(jīng)網(wǎng)絡(luò)也可以用于圖像分類等任務(wù)。問(wèn)題是,它們僅適用于小尺寸圖像,并且在應(yīng)用于中型或大型圖像時(shí)效率極低。原因是經(jīng)典神經(jīng)網(wǎng)絡(luò)需要大量的參數(shù)。
例如,200x200像素的圖像具有40'000個(gè)像素,如果網(wǎng)絡(luò)的第一層具有1'000個(gè)單位,則僅第一層的權(quán)重為4000萬(wàn)。由于CNN實(shí)現(xiàn)了部分連接的層和權(quán)重共享,這一問(wèn)題得到了高度緩解。
卷積神經(jīng)網(wǎng)絡(luò)的主要組成部分包括:
· 卷積層
· 池化層
卷積層
卷積層由一組濾波器(也稱為核)組成,當(dāng)應(yīng)用于層的輸入時(shí),對(duì)原始圖像進(jìn)行某種修改。濾波器是一種矩陣,其元素值定義了對(duì)原始圖像執(zhí)行的修改類型。類似以下的3x3內(nèi)核具有突出顯示圖像中垂直邊的效果:
不同的是,該核突出了水平邊:
核中元素的值不是手動(dòng)選擇的,而是網(wǎng)絡(luò)在訓(xùn)練期間學(xué)習(xí)的參數(shù)。
卷積的作用是隔離圖像中存在的不同特征。Dense層稍后使用這些功能。
池化層
池化層非常簡(jiǎn)單。池化層的任務(wù)是收縮輸入圖像,以減少網(wǎng)絡(luò)的計(jì)算負(fù)載和內(nèi)存消耗。事實(shí)上,減少圖像尺寸意味著減少參數(shù)的數(shù)量。
池化層所做的是使用核(通常為2x2維)并將輸入圖像的一部分聚合為單個(gè)值。例如,2x2最大池核獲取輸入圖像的4個(gè)像素,并僅返回具有最大值的像素。
Python實(shí)現(xiàn)
此GitHub存儲(chǔ)庫(kù)中提供了所有代碼。
這個(gè)實(shí)現(xiàn)背后的想法是創(chuàng)建表示卷積和最大池層的Python類。此外,由于該代碼后來(lái)被應(yīng)用于MNIST分類問(wèn)題,我為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()方法對(duì)上述方法生成的每個(gè)切片進(jìn)行卷積。
最后,back_prop()方法負(fù)責(zé)計(jì)算損失函數(shù)相對(duì)于層的每個(gè)權(quán)重的梯度,并相應(yīng)地更新權(quán)重值。注意,這里提到的損失函數(shù)不是網(wǎng)絡(luò)的全局損失。相反,它是由最大池層傳遞給前一卷積層的損失函數(shù)。
為了顯示這個(gè)類的實(shí)際效果,我用32個(gè)3x3濾波器實(shí)例化了一個(gè)卷積層對(duì)象,并將正向傳播方法應(yīng)用于圖像。輸出包含32個(gè)稍小的圖像。
原始輸入圖像的大小為28x28像素,如下所示:
在應(yīng)用卷積層的前向傳播方法后,我獲得了32幅尺寸為26x26的圖像。這里我繪制了其中一幅:
如你所見(jiàn),圖像稍小,手寫(xiě)數(shù)字變得不那么清晰。考慮到這個(gè)操作是由一個(gè)填充了隨機(jī)值的濾波器執(zhí)行的,所以它并不代表經(jīng)過(guò)訓(xùn)練的CNN實(shí)際執(zhí)行的操作。
盡管如此,你可以得到這樣的想法,即這些卷積提供了較小的圖像,其中對(duì)象特征被隔離。
最大池層實(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)重來(lái)執(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相對(duì)應(yīng)的圖像的概率。
結(jié)論
你可以克隆包含代碼的GitHub存儲(chǔ)庫(kù)并使用main.py腳本。該網(wǎng)絡(luò)一開(kāi)始沒(méi)有達(dá)到最先進(jìn)的性能,但在幾個(gè)epoch后達(dá)到96%的準(zhǔn)確率。
參考引用
原文標(biāo)題 : 使用Numpy從頭構(gòu)建卷積神經(jīng)網(wǎng)絡(luò)
發(fā)表評(píng)論
請(qǐng)輸入評(píng)論內(nèi)容...
請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字
最新活動(dòng)更多
-
10月31日立即下載>> 【限時(shí)免費(fèi)下載】TE暖通空調(diào)系統(tǒng)高效可靠的組件解決方案
-
即日-11.13立即報(bào)名>>> 【在線會(huì)議】多物理場(chǎng)仿真助跑新能源汽車
-
11月28日立即報(bào)名>>> 2024工程師系列—工業(yè)電子技術(shù)在線會(huì)議
-
12月19日立即報(bào)名>> 【線下會(huì)議】OFweek 2024(第九屆)物聯(lián)網(wǎng)產(chǎn)業(yè)大會(huì)
-
即日-12.26火熱報(bào)名中>> OFweek2024中國(guó)智造CIO在線峰會(huì)
-
即日-2025.8.1立即下載>> 《2024智能制造產(chǎn)業(yè)高端化、智能化、綠色化發(fā)展藍(lán)皮書(shū)》
推薦專題
- 1 【一周車話】沒(méi)有方向盤(pán)和踏板的車,你敢坐嗎?
- 2 特斯拉發(fā)布無(wú)人駕駛車,還未迎來(lái)“Chatgpt時(shí)刻”
- 3 特斯拉股價(jià)大跌15%:Robotaxi離落地還差一個(gè)蘿卜快跑
- 4 馬斯克給的“驚喜”夠嗎?
- 5 大模型“新星”開(kāi)啟變現(xiàn)競(jìng)速
- 6 海信給AI電視打樣,12大AI智能體全面升級(jí)大屏體驗(yàn)
- 7 AI 投流卷哭創(chuàng)業(yè)者
- 8 打完“價(jià)格戰(zhàn)”,大模型還要比什么?
- 9 馬斯克致敬“國(guó)產(chǎn)蘿卜”?
- 10 神經(jīng)網(wǎng)絡(luò),誰(shuí)是盈利最強(qiáng)企業(yè)?
- 高級(jí)軟件工程師 廣東省/深圳市
- 自動(dòng)化高級(jí)工程師 廣東省/深圳市
- 光器件研發(fā)工程師 福建省/福州市
- 銷售總監(jiān)(光器件) 北京市/海淀區(qū)
- 激光器高級(jí)銷售經(jīng)理 上海市/虹口區(qū)
- 光器件物理工程師 北京市/海淀區(qū)
- 激光研發(fā)工程師 北京市/昌平區(qū)
- 技術(shù)專家 廣東省/江門(mén)市
- 封裝工程師 北京市/海淀區(qū)
- 結(jié)構(gòu)工程師 廣東省/深圳市