手写一个反向传播,预测三角函数的值
国庆假期没力气出去玩了,花了半天时间写个简单的反向传播小例子希望可以帮助到需要了神经网络是如何训练知识的人。例子非常简单,就是一个简单的全连接神经网络来预测三角函数曲线,代码思路也比较简单,就是输入一个值,然后计算网络层参数相对于损失函数的偏导数,然后不停更新迭代,利用链式法则以及矩阵的一些知识点来实现在网络中自动求导,具体的代码如下所示:
#NN.py
import math
import numpy as np
'''定义神经元,这是网络层参数的一个单元,就是简单的一个数值加上这个数值对应的梯度值'''
class Neuron(object):
def __init__(self,value:float=1.0,grad:float=1.0):
self.value=value
self.grad=grad
'''定义Layer的基类,其实正常的线性层,卷积层,这样的有参数的层以及激活函数这样的无参数的层都可以继承这个基类'''
class Layer(object):
def __init__(self,input_shape,output_shape,pre_layer,next_layer):
self.input_shape=input_shape
self.output_shape=output_shape
self.pre_layer=pre_layer
self.next_layer=next_layer
self.input=[]
self.output=[]
def ForwardOperator(self,*args, **kw):
pass
def PropagateOperator(self,*args, **kw):
pass
'''无参数层'''
class NoParaLayer(Layer):
def __init__(self,input_shape=None,output_shape=None,pre_layer=None,next_layer=None):
super(NoParaLayer, self).__init__(input_shape,output_shape,pre_layer,next_layer)
self.propagate_grad = None
self.propagate_shape = None
'''有参数层'''
class ParaLayer(Layer):
def __init__(self,input_shape,output_shape,pre_layer=None,next_layer=None):
super(ParaLayer, self).__init__(input_shape,output_shape,pre_layer,next_layer)
self.neurons=None
self.neurons_shape=None
self.propagate_grad=None
self.propagate_shape=None
def NeuronUpdateOperator(self,*args, **kw):
pass
'''线性层,该层有三个方法:
ForwardOperator——————计算正向传播的值
NeuronUpdateOperator——————计算反向传播时线性层的参数梯度
PropagateOperator——————计算反向传播时传递到下一层的梯度值'''
class LinearLayer(ParaLayer):
def __init__(self,input_shape:int,output_shape:int,pre_layer=None,next_layer=None):
super(LinearLayer, self).__init__(input_shape, output_shape, pre_layer, next_layer)
self.neurons_shape=[input_shape,output_shape]
self.neurons=[[Neuron(value=np.random.rand(1)[0],grad=0) for _ in range(output_shape)] for _ in range(input_shape)]
def ForwardOperator(self,input:list):
self.input=input
self.output=[]
for i in range(self.output_shape):
tem=0
for j in range(self.input_shape):
tem+=self.input[j]*self.neurons[j][i].value
self.output.append(tem)
return self.output
def NeuronUpdateOperator(self,grad_output:list):
for i in range(self.input_shape):
for j in range(self.output_shape):
self.neurons[i][j].grad=grad_output[j]*self.input[i]
def PropagateOperator(self,grad_output:list):
self.propagate_grad=[]
for i in range(self.input_shape):
tem=0
for j in range(self.output_shape):
tem+=grad_output[j]*self.neurons[i][j].value
self.propagate_grad.append(tem)
self.propagate_shape=self.input_shape
return self.propagate_grad
'''激活函数层,自身没有参数,所以只需要定义正向传播的方法以及需要传递给下一层的梯度即可'''
class Sigmoid(NoParaLayer):
def __init__(self):
super(Sigmoid, self).__init__()
def ForwardOperator(self,input):
self.input=input
self.input_shape=len(input)
self.output=[]
for num in input:
self.output.append(1.0 / (1.0 + np.power(np.e, -num)))
return self.output
def PropagateOperator(self,grad_output):
self.propagate_grad=[]
for i,num in enumerate(grad_output):
self.propagate_grad.append((np.power(np.e, -self.input[i]) / np.power(1 + np.power(np.e, -self.input[i]), 2))*num)
self.propagate_shape=self.input_shape
return self.propagate_grad
'''返回mseloss的梯度,注意不是计算损失值而是返回梯度'''
def MSELossGrad(predict:float,label:float):
return 2*(predict-label)
class NetWork(object):
def __init__(self,layers:list):
self.layers=layers
for i,layer in enumerate(self.layers):
if i!=0:
layer.pre_layer=self.layers[i-1]
if i!=len(self.layers)-1:
layer.next_layer=self.layers[i+1]
'''正向传播,没什么说的,一层一层传就好'''
def Fordward(self,input):
layer=self.layers[0]
x=input
while(layer!=None):
x=layer.ForwardOperator(x)
layer=layer.next_layer
return x
'''反向传播,将每一层输出对于输入的偏导数传递到上一层,并更新线性层的梯度值'''
def Backward(self,predict,label,loss_grad_fn):
grad=[loss_grad_fn(predict,label)]
layer=self.layers[-1]
while(layer!=None):
try:
layer.NeuronUpdateOperator(grad)
except:
pass
grad=layer.PropagateOperator(grad)
layer=layer.pre_layer
'''更新梯度参数'''
def Update(self):
for layer in self.layers:
if hasattr(layer,'neurons'):
for i in range(len(layer.neurons)):
for j in range(len(layer.neurons[0])):
layer.neurons[i][j].value-=(layer.neurons[i][j].grad*0.05)
下面是主函数:
#main.py
from tqdm import tqdm
from NN import *
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(1)
x = np.arange(0, 2 * np.pi, 0.01)
x = x.reshape((len(x), 1))
y = (np.sin(x) + 1.0) / 2.0
yt = np.array(y).ravel()
xs = np.array(x).ravel()
'''
绘制三角函数曲线,然后根据x和y值来来训练该网络
'''
layer1=LinearLayer(input_shape=1,output_shape=10)
layer2=LinearLayer(input_shape=10,output_shape=10)
layer3=LinearLayer(input_shape=10,output_shape=10)
layer4=LinearLayer(input_shape=10,output_shape=1)
'''
4个线性层组成的很简单全连接神经网络
'''
network=NetWork([layer1,
Sigmoid(),
layer2,
Sigmoid(),
layer3,
Sigmoid(),
layer4,
Sigmoid()
])
for i in tqdm(range(1000000)):
flag=i
i=i%len(x)
predict=network.Fordward(x[i])
network.Backward(predict=predict[0],label=yt[i],loss_grad_fn=MSELossGrad)
network.Update()
if flag%10000==0:
print(predict[0]-yt[i])
y=[network.Fordward([_])[0][0] for _ in x]
plt.plot(x,y)
plt.plot(x,yt)
plt.show()
使用方法非常简单,就是将和两个文件放在一个文件夹中,然后直接执行就可以了,下面就是正常运行的截图:
为了节省时间,博主用了那3层网络训练2000000次之后的结果,如下图所示:
橙色的曲线是的函数曲线,蓝色曲线是拟合结果,可以看出已经和橙色曲线比较接近了。