本博客內(nèi)容來源于網(wǎng)絡以及其他書籍蹭沛,結合自己學習的心得進行重編輯章鲤,因為看了很多文章不便一一標注引用,如圖片文字等侵權帚呼,請告知刪除皱蹦。
傳統(tǒng)2D計算機視覺學習筆記目錄------->傳送門
傳統(tǒng)3D計算機視覺學習筆記目錄------->傳送門
深度學習學習筆記目錄 ------------------->傳送門
本文簡介
本文的主要目的就是描述出怎么使用numpy實現(xiàn)一個簡單的神經(jīng)網(wǎng)絡,通過反向傳播完成訓練的過程沈自,正如題目一樣辜妓。當然我們不會像成熟的深度學習框架一樣內(nèi)部實現(xiàn)自動求導籍滴,那就太麻煩了。通過自己手寫這么一份代碼捶索,可以讓自己加深深度神經(jīng)網(wǎng)絡到底是怎么運作的灰瞻,以達到我們的目的辅甥,而不再是完完全全的黑箱了燎竖。
目前網(wǎng)上有很多相關的文章构回,我自己也通過那些文章得到很多的認識再最初學習的時候,但是總是感覺有一些不足脐供,比如為了追求代碼簡潔借跪,而失去了結構性,而我們使用的pytorch或者tensorflow有很好的面型對象的結構歇由。所以本文實現(xiàn)的代碼更注重結構性果港,和可拓展性,可以在此基礎上在實現(xiàn)其他的一些簡單的層谢谦。那么開始吧
分步實現(xiàn)思路
首先我們知道神經(jīng)網(wǎng)絡是有一些layer(層)組成的的回挽,我們目前主要關注隱藏層欠气,因為神經(jīng)網(wǎng)絡的主要計算是在隱藏層。這些層分別可以進行前向推導队塘,反向傳播宜鸯,參數(shù)更新,所以我們先寫這些層的基類鸿市,為方便調(diào)試,我們在初始化類時陌凳,要給層一個名字内舟。
class BaseLayer:
def __init__(self,name):
self.name = name
def forward(self, input): #前向推導
pass
def backward(self,grad): #反向傳播
pass
def update(self): #參數(shù)更新
pass
接著我們要實現(xiàn)全連接層验游,激活函數(shù),以及損失函數(shù)崔梗。激活函數(shù)我們實現(xiàn)簡單的sigmoid激活函數(shù)垒在,損失函數(shù)我們實現(xiàn)帶有softmax的CrossEntropyLoss。有關簡單的激活函數(shù)和損失函數(shù)我會在其他文章詳細描述权悟。
我們先實現(xiàn)sigmoid激活函數(shù)推盛,由于sigmoid 中我們不需要更新任何的參數(shù)耘成,所以不用重載參數(shù)更新函數(shù)驹闰。
class SigmoidLayer(BaseLayer):
def __init__(self, name):
super(SigmoidLayer,self).__init__(name)
def forward(self,input):
self.output = 1/(1+np.exp(-input))
return self.output
def backward(self,grad):
grad = grad * self.output*(1-self.output)
return grad
然后我們實現(xiàn)全連接層,在此我們將學習率簡化為1师妙,初始參數(shù)設置為正太分布隨機參數(shù)屹培,優(yōu)化器也是最簡單的批量梯度下降(BGD)
class LinearLayer(BaseLayer):
def __init__(self,name,input_channels,output_channels):
super(LinearLayer,self).__init__(name)
self.weight = np.random.randn( input_channels,output_channels )
self.bias = np.random.randn(1,output_channels)
def forward(self,input):
self.input = input
self.output = np.dot(self.input,self.weight)+ self.bias # y = wx +b
return self.output
def backward(self,grad):
self.batch_size = grad.shape[0]
self.grad_w = np.dot(self.input.T,grad )/self.batch_size # δw = δg * x
self.grad_b = np.sum( grad , axis=0,keepdims= True )/self.batch_size
grad = np.dot(grad,self.weight.T)
return grad
def update(self):
self.weight -= self.grad_w
self.bias -= self.grad_b
然后我們來實現(xiàn)損失函數(shù)褪秀,以及softmax,我們可以將softmax的反向傳播與CrossEntropy反向傳播一起執(zhí)行仑氛,可以簡化整個過程。
class SoftMaxLayer(BaseLayer):
def __init__(self, name):
super(SoftMaxLayer,self).__init__(name)
def forward(self,input):
vec_max = np.max( input,axis=1 )[np.newaxis,:].T
input -= vec_max
exp = np.exp(input)
output = exp / (np.sum(exp,axis=1)[np.newaxis,:].T)
return output
class SMCrossEntropyLossLayer(BaseLayer):
def __init__(self, name):
super(SMCrossEntropyLossLayer,self).__init__(name)
def forward(self,pred,real):
self.softmax_p = SoftMaxLayer("softmax").forward(pred)
self.real = real
loss = 0
for i in range(self.real.shape[0]):
loss += -np.log( self.softmax_p[i,real[i]] )
loss /= self.real.shape[0]
return loss
def backward(self):
for i in range(self.real.shape[0]):
self.softmax_p[i,self.real[i]] -= 1
self.softmax_p = self.softmax_p / self.real.shape[0]
return self.softmax_p
現(xiàn)在我們將神經(jīng)網(wǎng)絡的基本的幾個層實現(xiàn)完了,現(xiàn)在我們要將這些隱層組建成一個網(wǎng)絡出吹。我們實現(xiàn)一個基本的網(wǎng)絡框架趋箩,然后再通過新的子類繼承基類,只需要該變隱層結構就可以了跳芳。由于準備訓練一個mnist手寫數(shù)字數(shù)據(jù)竹勉,所以第一層的輸入的維度是784。
class NetBase:
def __init__(self):
self.layers = []
def forward(self,input):
for layer in self.layers:
input = layer.forward(input)
pred = SoftMaxLayer("softmax").forward(input)
return input,pred
def backward(self,grad):
for layer in reversed(self.layers):
grad = layer.backward(grad)
layer.update()
class SimpleNet(NetBase):
def __init__(self):
super(SimpleNet,self).__init__()
self.layers = [
LinearLayer(name="full1",input_channels= 784, output_channels= 512),
SigmoidLayer(name="relu1"),
LinearLayer(name="full2",input_channels=512,output_channels=128),
SigmoidLayer(name="sigmoid2"),
LinearLayer(name="full3",input_channels=128,output_channels=10)
]
整體代碼
現(xiàn)在我們將網(wǎng)絡結構的代碼以及訓練代碼放到一起吓歇。
#BaseNet.py
import numpy as np
class BaseLayer:
def __init__(self,name):
self.name = name
def forward(self, input):
pass
def backward(self,grad):
pass
def update(self):
pass
class SigmoidLayer(BaseLayer):
def __init__(self, name):
super(SigmoidLayer,self).__init__(name)
def forward(self,input):
self.output = 1/(1+np.exp(-input))
return self.output
def backward(self,grad):
grad = grad * self.output*(1-self.output)
return grad
class LinearLayer(BaseLayer):
def __init__(self,name,input_channels,output_channels):
super(LinearLayer,self).__init__(name)
self.weight = np.random.randn( input_channels,output_channels )
self.bias = np.random.randn(1,output_channels)
def forward(self,input):
self.input = input
self.output = np.dot(self.input,self.weight)+ self.bias
return self.output
def backward(self,grad):
self.batch_size = grad.shape[0]
self.grad_w = np.dot(self.input.T,grad )/self.batch_size
self.grad_b = np.sum( grad , axis=0,keepdims= True )/self.batch_size
grad = np.dot(grad,self.weight.T)
return grad
def update(self):
self.weight -= self.grad_w
self.bias -= self.grad_b
class SoftMaxLayer(BaseLayer):
def __init__(self, name):
super(SoftMaxLayer,self).__init__(name)
def forward(self,input):
vec_max = np.max( input,axis=1 )[np.newaxis,:].T
input -= vec_max
exp = np.exp(input)
output = exp / (np.sum(exp,axis=1)[np.newaxis,:].T)
return output
class SMCrossEntropyLossLayer(BaseLayer):
def __init__(self, name):
super(SMCrossEntropyLossLayer,self).__init__(name)
def forward(self,pred,real):
self.softmax_p = SoftMaxLayer("softmax").forward(pred)
self.real = real
loss = 0
for i in range(self.real.shape[0]):
loss += -np.log( self.softmax_p[i,real[i]] )
loss /= self.real.shape[0]
return loss
def backward(self):
for i in range(self.real.shape[0]):
self.softmax_p[i,self.real[i]] -= 1
self.softmax_p = self.softmax_p / self.real.shape[0]
return self.softmax_p
class NetBase:
def __init__(self):
self.layers = []
def forward(self,input):
for layer in self.layers:
input = layer.forward(input)
pred = SoftMaxLayer("softmax").forward(input)
return input,pred
def backward(self,grad):
for layer in reversed(self.layers):
grad = layer.backward(grad)
layer.update()
class SimpleNet(NetBase):
def __init__(self):
super(SimpleNet,self).__init__()
self.layers = [
LinearLayer(name="full1",input_channels= 784, output_channels= 512),
SigmoidLayer(name="relu1"),
LinearLayer(name="full2",input_channels=512,output_channels=128),
SigmoidLayer(name="sigmoid2"),
LinearLayer(name="full3",input_channels=128,output_channels=10)
]
訓練部分代碼,由于numpy沒有使用gpu來進行訓練杏慰,訓練整體還是比較慢的,所以我們只訓練了 前100個數(shù)據(jù)缘滥,通過觀察loss 就可以驗證我們的網(wǎng)絡是否進行工作。
#train.py
import BaseNet
import numpy as np
import matplotlib.pyplot as plt
import os
training_set_inputs = []
training_set_outputs = []
def read_mnist(mnist_image_file, mnist_label_file):
if 'train' in os.path.basename(mnist_image_file):
num_file = 60000
else:
num_file = 10000
with open(mnist_image_file, 'rb') as f1:
image_file = f1.read()
with open(mnist_label_file, 'rb') as f2:
label_file = f2.read()
image_file = image_file[16:]
label_file = label_file[8:]
for i in range(num_file):
label = int(label_file[i])
image_list = [int(item) for item in image_file[i*784:i*784+784]]
image_np = np.array(image_list, dtype=np.uint8).reshape(28*28)
training_set_outputs.append([label])
training_set_inputs.append( image_np )
train_image_file = '/home/eric/data/mnist/train-images-idx3-ubyte'
train_label_file = '/home/eric/data/mnist/train-labels-idx1-ubyte'
read_mnist(train_image_file, train_label_file)
training_set_inputs = np.array( training_set_inputs )
training_set_outputs = np.array( training_set_outputs )
training_set_inputs = training_set_inputs[:100,:]
training_set_outputs = training_set_outputs[:100,:]
net = BaseNet.SimpleNet()
loss = BaseNet.SMCrossEntropyLossLayer("loss")
x = []
y=[]
for i in range(10000):
input = training_set_inputs
output,pred = net.forward(input)
loss_value = np.squeeze(loss.forward(output,training_set_outputs))
print(i,loss_value,np.sum( (np.equal(pred.argmax(axis = 1),training_set_outputs.T)))/ training_set_outputs.shape[0] )
x.append(i)
y.append(loss_value)
delta = loss.backward()
net.backward(delta)
plt.plot(x,y,'r--')
plt.title('loss')
plt.show()
總結
寫完這篇文章甫恩,才發(fā)現(xiàn)代碼太多,沒有太多的文字敘述,感覺要是一點點解釋肖抱,怕是累死我异旧,估計沒有人像我這么笨吧。自己認為學習的過程還是需要自己用手就敲一遍荤崇,觀察一下每個狀態(tài)的輸出,才能更好的理解术荤。雖然代碼很多但是其實也可以壓縮成十幾行瓣戚,但是對初學者就太不友好了。
重要的事情說三遍:
如果我的文章對您有所幫助舱权,那就點贊加個關注唄 ( * ^ __ ^ * )
如果我的文章對您有所幫助仑嗅,那就點贊加個關注唄 ( * ^ __ ^ * )
如果我的文章對您有所幫助,那就點贊加個關注唄 ( * ^ __ ^ * )
傳統(tǒng)2D計算機視覺學習筆記目錄------->傳送門
傳統(tǒng)3D計算機視覺學習筆記目錄------->傳送門
深度學習學習筆記目錄 ------------------->傳送門
任何人或團體鸵贬、機構全部轉載或者部分轉載脖捻、摘錄,請保留本博客鏈接或標注來源颜价。博客地址:開飛機的喬巴
作者簡介:開飛機的喬巴(WeChat:zhangzheng-thu)诉濒,現(xiàn)主要從事機器人抓取視覺系統(tǒng)以及三維重建等3D視覺相關方面夕春,另外對slam以及深度學習技術也頗感興趣及志,歡迎加我微信或留言交流相關工作。