项目作者: guyuchao

项目描述 :
numpy实现的bp算法,代码是pytorch风格,可供pytorch入门学习,配合plotly.js获得良好的训练可视化
高级语言: Python
项目地址: git://github.com/guyuchao/BP-Neural-Network.git
创建时间: 2018-05-27T11:40:46Z
项目社区:https://github.com/guyuchao/BP-Neural-Network

开源协议:

关键词:
bp plotly pytorch

下载


BP-Neural-Network

Author: Yuchao Gu

E-mail: 2015014178@buct.edu.cn

Date: 2018-05-27

Description: numpy实现的Bp神经网络,代码风格参照pytorch,实现了全连接层,MSEloss,Sigmoid、Relu激活函数,以及优化器。前端可实时监控训练集loss与测试集loss以及训练测试集拟合曲线

结果展示

2d可视化训练

可拟合常用一元函数,支持的数学符号:sin,cos,power(乘方),pi 。loss曲线中橙色为验证集loss,蓝色为训练集loss

3d可视化训练

可拟合常用二元曲面,支持的数学符号参考2d部分

手写数字识别MNIST训练

训练mnist数据集,可设置验证epoch,在每个验证epoch随机抽取十张验证集数据可视化

第一轮60000张图片训练结束结果展示

代码解析

网络层基类

构造了所有网络层应实现的前向传播以及后向传播方法,以及获取参数的方法。
所有的网络层应该继承实现这个基类。

  1. class BaseNetwork(object):
  2. def __init__(self):
  3. pass
  4. def forward(self,*x):
  5. pass
  6. def parameters(self):
  7. pass
  8. def backward(self,grad):
  9. pass
  10. def __call__(self,*x):
  11. return self.forward(*x)

全连接层

继承基类,实现了基类的方法。将该层参数与梯度封装进Variable,供优化器更新。

  1. class Linear(BaseNetwork):
  2. def __init__(self,inplanes,outplanes,preweight=None):
  3. super(Linear,self).__init__()
  4. if preweight is None:
  5. self.weight = np.random.randn(inplanes, outplanes) * 0.5
  6. self.bias = np.random.randn(outplanes) * 0.5
  7. else:
  8. self.weight, self.bias = preweight
  9. self.input=None
  10. self.output=None
  11. self.wgrad=np.zeros(self.weight.shape)
  12. self.bgrad=np.zeros(self.bias.shape)
  13. self.variable=Variable(self.weight,self.wgrad,self.bias,self.bgrad)
  14. def parameters(self):
  15. return self.variable
  16. def forward(self,*x):
  17. x=x[0]
  18. self.input=x
  19. self.output=np.dot(self.input,self.weight)+self.bias
  20. return self.output
  21. def backward(self,grad):
  22. self.bgrad=grad
  23. self.wgrad += np.dot(self.input.T, grad)
  24. grad = np.dot(grad, self.weight.T)
  25. return grad

激活函数

Sigmoid激活函数存在梯度消失的问题,建议使用Relu激活函数

  1. class Relu(BaseNetwork):
  2. def __init__(self):
  3. super(Relu,self).__init__()
  4. self.input=None
  5. self.output=None
  6. def forward(self,*x):
  7. x=x[0]
  8. self.input=x
  9. x[self.input<=0]*=0
  10. self.output=x
  11. return self.output
  12. def backward(self,grad):
  13. grad[self.input>0]*=1
  14. grad[self.input<=0]*=0
  15. return grad
  1. class Sigmoid(BaseNetwork):
  2. def __init__(self):
  3. super(Sigmoid,self).__init__()
  4. self.input=None
  5. self.output=None
  6. def forward(self,*x):
  7. x=x[0]
  8. self.input=x
  9. self.output=1/(1+np.exp(-self.input))
  10. return self.output
  11. def backward(self,grad):
  12. grad*=self.output*(1-self.output)
  13. return grad

损失函数

损失函数实现了MSE(均方差损失)

  1. class MSE(object):
  2. def __init__(self):
  3. self.label=None
  4. self.pred=None
  5. self.grad=None
  6. self.loss=None
  7. def __call__(self, pred,label):
  8. return self.forward(pred,label)
  9. def forward(self,pred,label):
  10. self.pred,self.label=pred,label
  11. self.loss=np.sum(0.5*np.square(self.pred-self.label))
  12. return self.loss
  13. def backward(self,grad=None):
  14. self.grad=(self.pred-self.label)
  15. ret_grad=np.sum(self.grad,axis=0)
  16. return np.expand_dims(ret_grad,axis=0)

优化器

根据梯度一次性更新所有参数

  1. class SGD(object):
  2. def __init__(self,parameters,lr=0.01,momentum=0.9):
  3. self.parameters=parameters
  4. self.lr=lr
  5. self.momentum=momentum
  6. def zero_grad(self):
  7. for parameters in self.parameters:
  8. parameters.wgrad*=0
  9. parameters.bgrad*=0
  10. def step(self):
  11. for parameters in self.parameters:
  12. #parameters.v_weight=parameters.v_weight*self.momentum-self.lr*parameters.wgrad
  13. parameters.weight-=self.lr*parameters.wgrad
  14. parameters.bias-=self.lr*parameters.bgrad

定义自己的网络结构

仅需在Sequence中叠加自己的网络层即可

  1. class Mynet(BaseNetwork):
  2. def __init__(self,inplanes,outplanes):
  3. super(Mynet,self).__init__()
  4. self.layers=Sequence(
  5. Linear(inplanes, 100),
  6. Relu(),
  7. Linear(100, outplanes)
  8. )
  9. self.criterion=MSE()
  10. def parameters(self):
  11. return self.layers.parameters()
  12. def forward(self,*x):
  13. x=x[0]
  14. return self.layers.forward(x)
  15. def backward(self,grad=None):
  16. grad=self.criterion.backward(grad)
  17. self.layers.backward(grad)
  1. class Sequence(BaseNetwork):
  2. def __init__(self,*layer):
  3. super(Sequence,self).__init__()
  4. self.layers=[]
  5. self.parameter=[]
  6. for item in layer:
  7. self.layers.append(item)
  8. for layer in self.layers:
  9. if isinstance(layer,Linear):
  10. self.parameter.append(layer.parameters())
  11. def add_layer(self,layer):
  12. self.layers.append(layer)
  13. def forward(self,*x):
  14. x=x[0]
  15. for layer in self.layers:
  16. x=layer(x)
  17. return x
  18. def backward(self,grad):
  19. for layer in reversed(self.layers):
  20. grad=layer.backward(grad)
  21. def parameters(self):
  22. return self.parameter

训练过程

参考pytorch的写法

  1. mynet=Mynet()
  2. criterion=mynet.criterion
  3. optimizer=SGD(mynet.parameters(),lr=0.00001,momentum=0.9)
  4. for i in tqdm.tqdm(range(1000)):
  5. for row in range(trainx.shape[0]):
  6. optimizer.zero_grad()
  7. input=trainx[row:row+1]
  8. label=trainy[row:row+1]
  9. pred=mynet(input)
  10. loss=criterion(pred,label)
  11. running_loss+=loss
  12. mynet.backward()
  13. optimizer.step()

局限与不足

本课设由于时间仓促,存在以下局限,供参考者改进:

  • 没有实现batch的参数更新

  • 动量仍存在问题

  • 只有基础网络结构

代码结构

  1. BP
  2. ├── MnistNpy # 存放手写数字识别预处理后的numpy文件
  3. ├── readmeDisplay # 存放本说明文件使用的图片数据
  4. ├── static # 存放网页展示所需的外部js
  5. ├── templates # 存放网页展示所需的html
  6. ├── mnist.html # 训练mnist的html与js
  7. ├── train2d.html # 拟合一元函数的html与js
  8. └── train3d.html # 拟合三维曲面的html与js
  9. ├── AIFlask.py # 后端代码
  10. ├── BPmain # 给后端代码写好调用接口的网络代码
  11. ├── BPmainOriginal.py # 不包含后端代码的原生实现,建议初学者阅读
  12. └── readme.md # 此说明文件

环境

  • Python 3.6

  • 依赖:flask plotly.js

  • 系统支持:ubuntu

  • 编程环境:pycharm

致谢

本课设感谢 wlj961012 提供的关于plotly.js的基础实现,感谢 DIYer22 提供的mnist训练的灵感