当前位置:   article > 正文

多层神经网络以及反向传播的代码推导_多层神经网络代码

多层神经网络代码

发现草稿箱里还有一篇很早之前的学习笔记,希望可以帮助到有需要的童鞋~

目录

多层全连接神经网络搭建

(1)、input -> (affine_forward) -> out* -> (relu_forward) -> out ,全连接和relu激活

(2)、batch-normalization 批量归一化

   3、随机失活(DropOut)

(4)、任意深度神经网络

神经网络优化 - 针对训练过程中的梯度下降

1、SGD with momentum

2、RMSProp

3、Adam


       原来都是用的c++学习的传统图像分割算法。主要学习聚类分割、水平集、图割,欢迎一起讨论学习。

       刚刚开始学习cs231n的课程,正好学习python,也做些实战加深对模型的理解。

       课程链接

       1、这是自己的学习笔记,会参考别人的内容,如有侵权请联系删除。

       2、代码参考WILL 、杜克,但是有了很多自己的学习注释

       3、有些原理性的内容不会讲解,但是会放上我觉得讲的不错的博客链接

       4、由于之前没怎么用过numpy,也对python不熟,所以也是一个python和numpy模块的学习笔记

       5、本文参考参考

       本章前言:本章实现了多层全连接的神经网络和优化算法的使用,比如批量归一化、SGD+Momentum、Adam等,本章重点:反向传播以及优化算法

       在jupyter中写的代码,要import需要下载成为.py文件,import之后如果.py文件中的内容有了修改需要重新打开jupyter,很麻烦,现在在import之后加上以下代码,更改.py文件后就不需要重新打开jupyter了。

  1. #自动加载外部模块
  2. %reload_ext autoreload
  3. %autoreload 2

多层全连接神经网络搭建

       之前实现的是一个两层的神经网络,结构为 input -> hidden ->relu -> score -> softmax - output。层数较少,给出的推导是从后往前一步一步推导,这在多层神经网络中是不太现实的,层数变多,一层一层推太过于麻烦。在实际过程中,往往采用模块化的反向传播推导。

       多层全连接神经网络结构将会被模块化分割:

(1)、input -> (affine_forward) -> out* -> (relu_forward) -> out ,全连接和relu激活

接下来是实现向前传播代码:

  1. def affine_forward(x,w,b):
  2. out = None
  3. x_reshape = np.reshape(x,(x.shape[0],-1))
  4. out = x_reshape.dot(w) + b
  5. cache = (x,w,b)
  6. return out,cache #返回线性输出,和中间参数(x,w,b)
  7. def relu_forward(x):
  8. out = np.maximum(0,x)
  9. cache = x #缓存线性输出a
  10. return out,cache
  11. #模块化
  12. def affine_relu_forward(x,w,b):
  13. a,fc_cache = affine_forward(x,w,b) #a是线性输出,fc_cache中存储(x,w,b)
  14. out,relu_cache = relu_forward(a) #relu_cache存储线性输出a
  15. cache = (fc_cache,relu_cache) #缓冲元组:(x,w,b,(a))
  16. return out,cache #返回激活值out 和参数(x,w,b,(a))

既然有了向前传播模块,那就得有反向传播模块:

  1. def affine_backward(dout,cache):
  2. """
  3. 输出层反向传播
  4. dout 该层affine_forward正向输出数据out的梯度,对应softmax_loss/relu中的输出dz
  5. cache 元组,正向流入输入层的数据x,和输出层的参数(w,b)
  6. """
  7. z,w,b = cache #z为上一层的激活值,也是本层的输入
  8. dx,dw,db = None, None,None
  9. x_reshape = np.reshape(z, (z.shape[0],-1))
  10. dz = np.reshape(dout.dot(w.T),z.shape) #参考公式
  11. dw = (x_reshape.T).dot(dout) #参考公式
  12. db = np.sum(dout,axis=0) #参考公式
  13. return dz,dw,db
  14. def relu_backward(dout,cache): #传入的是
  15. """
  16. relu激活,小于0的梯度为0,大于0的梯度为1
  17. """
  18. dx,x = None, cache
  19. dx = (x>0) * dout
  20. return dx
  21. def affine_relu_backward(dout,cache):
  22. fc_cache, relu_cache = cache #relu_cache 存储线性输出a
  23. da = relu_backward(dout,relu_cache) #计算关于relu的梯度,这边是一个复合函数,z=relu(a),a=w1x+b1 -> dz /dx = dz/da *da/dx
  24. dx,dw,db = affine_backward(da,fc_cache)
  25. return dx,dw,db

(2)、batch-normalization 批量归一化

       据说,批量归一化可以减小随机初始化权重的影响,加速收敛,学习率适当增大,减少过拟合,使用较低的dropout,减小L2正则化系数等优点。

首先对输入数据进行归一化,使数据的特征均值为0,方差为一,也就是服从标准高斯分布。然后对该数据进行变换重构,使其能后恢复原来的特征分布。

       那现在的神经网络结构就变成了:input -> affine_forward -> BN(batch_normlize) -> relu_forward,也就是在全连接之后加上BN,然后进行激活输出。

  1. #批量归一化(加速收敛,学习率适当增大,加快寻,减少过拟合,使用较低的dropout,减小L2正则化系数)
  2. def batchnorm_forward(x,gamma,beta,bn_param):
  3. mode = bn_param['mode']
  4. eps = bn_param.get('eps',1e-5) #防止除以0
  5. momentum = bn_param.get('momentum',0.9)
  6. N,D = x.shape #N样本个数,D特征个数
  7. #移动均值和方差,会随着train过程不断变化
  8. running_mean = bn_param.get('running_mean',np.zeros(D, dtype = x.dtype))
  9. running_var = bn_param.get('running_var',np.zeros(D, dtype = x.dtype))
  10. out,cache=None,None
  11. if mode =='train':
  12. sample_mean = np.mean(x,axis=0)
  13. sample_var = np.var(x,axis = 0)
  14. x_hat = (x-sample_mean)/(np.sqrt(sample_var+eps))
  15. out = gamma*x_hat +beta
  16. cache = (x,sample_mean,sample_var,x_hat,eps,gamma,beta)
  17. running_mean = momentum*running_var + (1-momentum)*sample_mean
  18. running_var = momentum*running_var+(1-momentum)*sample_var
  19. elif mode == 'test':
  20. out = (x-running_mean)*gamma/(np.sqrt(running_var+eps))+beta
  21. else:
  22. raise ValueError('invalid forward batchnorm mode "%s"' %mode)
  23. bn_param['running_mean'] = running_mean
  24. bn_param['running_var'] = running_var
  25. return out,cache #cache(线性输出,均值,方差,归一化值,eps,gamma,beta) #out 变换重构的值
  26. def affine_bn_relu_forward(x,w,b,gamma,beta,bn_param):
  27. a,fc_cache = affine_forward(x,w,b)
  28. a_bn, bn_cache = batchnorm_forward(a,gamma,beta,bn_param) #BN层
  29. out,relu_cache = relu_forward(a_bn) #将归一化后的值激活,relu_cache中缓存变换重构值
  30. cache = (fc_cache,bn_cache,relu_cache)
  31. return out,cache

有了向前传播自然要有反向传播:其实我也没看公式,直接抄的代码......因为反向传播的链式求导原理是一样的

  1. def batchnorm_backward(dout,cache):
  2. x,mean,var,x_hat,eps,gamma,beta = cache
  3. N = x.shape[0]
  4. dgamma = np.sum(dout*x_hat,axis=0)
  5. dbeta = np.sum(dout*1, axis=0)
  6. dx_hat = dout*gamma
  7. dx_hat_numerator = dx_hat/np.sqrt(var +eps)
  8. dx_hat_denominator = np.sum(dx_hat * (x-mean),axis=0)
  9. dx_1 = dx_hat_numerator
  10. dvar = -0.5*((var+eps)**(-1.5))*dx_hat_denominator
  11. dmean = -1.0*np.sum(dx_hat_numerator,axis = 0)+dvar*np.mean(-2.0*(x-mean),axis=0)
  12. dx_var = dvar*2.0/N*(x-mean)
  13. dx_mean =dmean*1.0/N
  14. dx = dx_1+dx_var+dx_mean
  15. return dx,dgamma,dbeta
  16. def affine_bn_relu_backward(dout,cache):
  17. fc_cache,bn_cache,relu_cache = cache
  18. da_bn = relu_backward(dout,relu_cache)
  19. da,dgamma,dbeta = batchnorm_backward(da_bn,bn_cache)
  20. dx,dw,db = affine_backward(da,fc_cache)
  21. return dx,dw,db,dgamma,dbeta

(3)、随机失活(DropOut)

在全连接神经网络中,层数越深,参数越多,测试集的准确率也越来越高,但是在验证集上效果不好,这是因为出现了过拟合。通俗来说,网络为了迎合测试集,提取了过多的特征,而这些特征的作用并没有那么大。随机失活就是随机让神经元失效,也就是某些特征的作用被取消了,这样就达到了一定程度上防止过拟合的能力。

  1. #随机失活
  2. def dropout_forward(x,dropout_param):
  3. """
  4. dropout_param p 失活概率
  5. """
  6. p,mode = dropout_param['p'],dropout_param['mode']
  7. if 'seed' in dropout_param:
  8. np.random.seed(dropout_param['seed'])
  9. mask = None
  10. out = None
  11. if mode == 'train':
  12. keep_prob = 1-p
  13. mask = (np.random.rand(*x.shape)<keep_prob)/keep_prob #随机失活后损失下降,所以除以概率保持分布同意
  14. out = mask * x
  15. elif mode == 'test':
  16. out = x
  17. cache = (dropout_param,mask)
  18. out = out.astype(x.dtype,copy=False)
  19. return out,cache

反向传播:原理和Relu差不多,随机失活是利用概率掩膜来实现的,掩膜为1不失活,否则失活为0,也就是梯度为1或者为0

  1. def dropout_backward(dout,cache):
  2. dropout_param,mask = cache
  3. mode = dropout_param['mode']
  4. dx = None
  5. if mode == 'train':
  6. dx = mask * dout
  7. elif mode == 'test':
  8. dx = dout
  9. return dx

(4)、任意深度神经网络

       所有的模块都被搭建完成,那就可以模块化神经网络了。总共分为三个部分,一个是参数初始化,一个是损失函数计算,还有一个应该是训练。这边暂时只有两个部分

向前传播网络结构(BN,Dropout): (input ->  BN -> relu -> Dropout) * N(重复N次) -> affine_forward -> softmax -> loss

反向传播(BN,Dropout):  softmax_loss -> affine_backward -> dropout_backward -> (affine_bn_relu_backward) * N

  1. from layers import *
  2. import numpy as np
  3. class FullyConnectedNet(object):
  4. def __init__(self
  5. ,hidden_dims #列表,元素个数是隐藏层层数,元素值为神经元个数
  6. ,input_dim = 3*32*32 #输入神经元3072
  7. ,num_classes = 10 #输出10类
  8. ,dropout = 0 #默认不开启dropout,(0,1)
  9. ,use_batchnorm = False #默认不开批量归一化
  10. ,reg = 0.0 #默认无L2正则化
  11. ,weight_scale =1e-2 #权重初始化标准差
  12. ,dtype=np.float64 #精度
  13. ,seed = None #无随机种子,控制dropout层
  14. ):
  15. self.use_batchnorm = use_batchnorm
  16. self.use_dropout = dropout>0 #dropout为0,则关闭随机失活
  17. self.reg = reg #正则化参数
  18. self.num_layers = 1+len(hidden_dims)
  19. self.dtype =dtype
  20. self.params = {} #参数字典
  21. in_dim = input_dim
  22. #有几个隐藏层,就有几个对应的W,最后输出层还有一个W
  23. for i,h_dim in enumerate(hidden_dims):
  24. self.params['W%d' %(i+1,)] = weight_scale*np.random.randn(in_dim,h_dim)
  25. self.params['b%d' %(i+1,)] = np.zeros((h_dim,))
  26. if use_batchnorm:
  27. #使用批量归一化
  28. self.params['gamma%d' %(i+1,)] = np.ones((h_dim,))
  29. self.params['beta%d' %(i+1,)] = np.zeros((h_dim,))
  30. in_dim = h_dim #将隐藏层中特征个数传递给下一层
  31. #输出层参数
  32. self.params['W%d'%(self.num_layers,)] = weight_scale*np.random.randn(in_dim,num_classes)
  33. self.params['b%d'%(self.num_layers,)] = np.zeros((num_classes,))
  34. #dropout
  35. self.dropout_param = {} #dropou参数字典
  36. if self.use_dropout: #如果use_dropout为(0,1),启用dropout
  37. self.dropout_param = {'mode':'train','p':dropout}
  38. if seed is not None:
  39. self.dropout_param['seed'] = seed
  40. #batch normalize
  41. self.bn_params = [] #bn算法参数列表
  42. if self.use_batchnorm: #开启批量归一化,设置每层的mode为训练模式
  43. self.bn_params=[{'mode':'train'} for i in range(self.num_layers - 1)]
  44. #设置所有参数的计算精度为np.float64
  45. for k,v in self.params.items():
  46. self.params[k] = v.astype(dtype)
  47. def loss(self,X,y = None):
  48. #调整精度
  49. #X的数据是N*3*32*32
  50. #Y(N,)
  51. X = X.astype(self.dtype)
  52. mode = 'test' if y is None else 'train'
  53. if self.dropout_param is not None:
  54. self.dropout_param['mode'] = mode
  55. if self.use_batchnorm:
  56. for bn_params in self.bn_params:
  57. bn_params['mode'] = mode
  58. scores = None
  59. #向前传播
  60. fc_mix_cache = {} #混合层向前传播缓存
  61. if self.use_dropout: #开启随机失活
  62. dp_cache = {} #随即失活层向前传播缓存
  63. out = X
  64. #只计算隐藏层中的向前传播,输出层单独的全连接
  65. for i in range(self.num_layers -1):
  66. w = self.params['W%d'%(i+1,)]
  67. b = self.params['b%d'%(i+1,)]
  68. if self.use_batchnorm:
  69. #利用模块向前传播
  70. gamma = self.params['gamma%d'%(i+1,)]
  71. beta = self.params['beta%d'%(i+1,)]
  72. out,fc_mix_cache[i] = affine_bn_relu_forward(out,w,b,gamma,beta,self.bn_params[i])
  73. else:
  74. out,fc_mix_cache[i] = affine_relu_forward(out,w,b)
  75. if self.use_dropout:
  76. #开启随机失活,并且记录随机失活的缓存
  77. out,dp_cache[i] = dropout_forward(out,self.dropout_param)
  78. #输出层向前传播
  79. w = self.params['W%d'%(self.num_layers,)]
  80. b = self.params['b%d'%(self.num_layers,)]
  81. out,out_cache = affine_forward(out,w,b)
  82. scores = out
  83. if mode == 'test':
  84. return scores
  85. #反向传播
  86. loss,grads=0.0, {}
  87. #softmaxloss
  88. loss,dout = softmax_loss(scores,y)
  89. #正则化loss,只计算了输出层的W平方和
  90. loss += 0.5*self.reg*np.sum(self.params['W%d'%(self.num_layers,)]**2)
  91. #输出层的反向传播,存储到梯度字典
  92. dout,dw,db = affine_backward(dout,out_cache)
  93. #正则化
  94. grads['W%d'%(self.num_layers,)] = dw+self.reg*self.params['W%d'%(self.num_layers,)]
  95. grads['b%d'%(self.num_layers,)] = db
  96. #隐藏层梯度反向传播
  97. for i in range(self.num_layers-1):
  98. ri = self.num_layers -2 - i #倒数第ri+1层
  99. loss+=0.5*self.reg*np.sum(self.params['W%d'%(ri+1,)]**2) #继续正则化loss
  100. if self.use_dropout: #如果使用随即失活,加上梯度下降
  101. dout = dropout_backward(dout,dp_cache[ri])
  102. if self.use_batchnorm: #如果使用BN,加上梯度下降部分
  103. dout,dw,db,dgamma,dbeta = affine_bn_relu_backward(dout,fc_mix_cache[ri])
  104. grads['gamma%d'%(ri+1,)] = dgamma
  105. grads['beta%d'%(ri+1,)] = dbeta
  106. else: #否则直接梯度下降
  107. dout,dw,db = affine_relu_backward(dout,fc_mix_cache[ri])
  108. #存储到字典中
  109. grads['W%d'%(ri+1,)] = dw+self.reg * self.params['W%d'%(ri+1,)]
  110. grads['b%d'%(ri+1,)] = db
  111. #返回本次loss和梯度值
  112. return loss,grads


神经网络优化 - 针对训练过程中的梯度下降

1、SGD with momentum

       上面的loss函数输出的是当前的损失值和模型参数的梯度,梯度下降过程中也就是在train的过程中,在负梯度方向上对模型参数进行更新。

       随机梯度下降(SGD)之前使用过,w -= learning_rate * dW

       SGD + momentum (随机梯度下降的动量更新方法)。w0 =  w0*mu - learning_rate*dW个人理解,原本是按照梯度来走,但是现在更新后有了自己的速度,速度不可瞬间变化,把梯度看作一个力,这个力将会概念速度的大小和方向。

  1. def sge_momentum(w,dw,config = None):
  2. if config is None:
  3. config = {}
  4. config.setdefault('learning_rate',1e-2)
  5. config.setdefault('momentum',0.9)
  6. v = config.get('velocity',np.zeros_like(w))
  7. next_w = None
  8. v = config['momentum']*v - config['learning_rate']*dw
  9. next_w = w + v
  10. config['velocity'] = v
  11. return next_w,config

2、RMSProp

3、Adam


实例化神经网络及训练

参考链接:课程作业第52页的Solver

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/144862
推荐阅读
相关标签
  

闽ICP备14008679号