本博客内容来源于网络以及其他书籍,结合自己学习的心得进行重编辑,因为看了很多文章不便一一标注引用,如图片文字等侵权,请告知删除。
传统2D计算机视觉学习笔记目录------->传送门
传统3D计算机视觉学习笔记目录------->传送门
深度学习学习笔记目录 ------------------->传送门
本文简介
本文的主要目的就是描述出怎么使用numpy实现一个简单的神经网络,通过反向传播完成训练的过程,正如题目一样。当然我们不会像成熟的深度学习框架一样内部实现自动求导,那就太麻烦了。通过自己手写这么一份代码,可以让自己加深深度神经网络到底是怎么运作的,以达到我们的目的,而不再是完完全全的黑箱了。
目前网上有很多相关的文章,我自己也通过那些文章得到很多的认识再最初学习的时候,但是总是感觉有一些不足,比如为了追求代码简洁,而失去了结构性,而我们使用的pytorch或者tensorflow有很好的面型对象的结构。所以本文实现的代码更注重结构性,和可拓展性,可以在此基础上在实现其他的一些简单的层。那么开始吧
分步实现思路
首先我们知道神经网络是有一些layer(层)组成的的,我们目前主要关注隐藏层,因为神经网络的主要计算是在隐藏层。这些层分别可以进行前向推导,反向传播,参数更新,所以我们先写这些层的基类,为方便调试,我们在初始化类时,要给层一个名字。
class BaseLayer:
def __init__(self,name):
self.name = name
def forward(self, input): #前向推导
pass
def backward(self,grad): #反向传播
pass
def update(self): #参数更新
pass
接着我们要实现全连接层,激活函数,以及损失函数。激活函数我们实现简单的sigmoid激活函数,损失函数我们实现带有softmax的CrossEntropyLoss。有关简单的激活函数和损失函数我会在其他文章详细描述。
我们先实现sigmoid激活函数,由于sigmoid 中我们不需要更新任何的参数,所以不用重载参数更新函数。
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
然后我们实现全连接层,在此我们将学习率简化为1,初始参数设置为正太分布随机参数,优化器也是最简单的批量梯度下降(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
然后我们来实现损失函数,以及softmax,我们可以将softmax的反向传播与CrossEntropy反向传播一起执行,可以简化整个过程。
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
现在我们将神经网络的基本的几个层实现完了,现在我们要将这些隐层组建成一个网络。我们实现一个基本的网络框架,然后再通过新的子类继承基类,只需要该变隐层结构就可以了。由于准备训练一个mnist手写数字数据,所以第一层的输入的维度是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)
]
整体代码
现在我们将网络结构的代码以及训练代码放到一起。
#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个数据,通过观察loss 就可以验证我们的网络是否进行工作。
#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()
总结
写完这篇文章,才发现代码太多,没有太多的文字叙述,感觉要是一点点解释,怕是累死我,估计没有人像我这么笨吧。自己认为学习的过程还是需要自己用手就敲一遍,观察一下每个状态的输出,才能更好的理解。虽然代码很多但是其实也可以压缩成十几行,但是对初学者就太不友好了。
重要的事情说三遍:
如果我的文章对您有所帮助,那就点赞加个关注呗 ( * ^ __ ^ * )
如果我的文章对您有所帮助,那就点赞加个关注呗 ( * ^ __ ^ * )
如果我的文章对您有所帮助,那就点赞加个关注呗 ( * ^ __ ^ * )
传统2D计算机视觉学习笔记目录------->传送门
传统3D计算机视觉学习笔记目录------->传送门
深度学习学习笔记目录 ------------------->传送门
任何人或团体、机构全部转载或者部分转载、摘录,请保留本博客链接或标注来源。博客地址:开飞机的乔巴
作者简介:开飞机的乔巴(WeChat:zhangzheng-thu),现主要从事机器人抓取视觉系统以及三维重建等3D视觉相关方面,另外对slam以及深度学习技术也颇感兴趣,欢迎加我微信或留言交流相关工作。