Code前端首页关于Code前端联系我们

Python从头开始实现全连接神经网络

terry 2年前 (2023-09-23) 阅读数 72 #AI人工智能

Python 从头开始实现一个全连接的神经网络

Python从头开始实现全连接神经网络。您可能会问,为什么我们必须自己实现它?有很多库和框架可以为我们做到这一点,例如 Tensorflow、Pytorch 等。在这里我只想说,只有你亲手实现了,它才是你的。

我想知道自从我开始研究神经网络以来已经过去了多少两三年了。我还尝试使用tensorflow或pytorch框架来实现一些经典网络。然而,反向传播机制目前还比较不清楚。

梯度

梯度是函数上升最快的方向。最快的方向意味着该方向上函数的形状非常陡峭,因此也是函数下降最快的方向。

虽然有些理论,梯度消失和节点饱和可以得到1,2,3,但我仍然没有足够的信心去深入挖掘。毕竟我自己从来没有做过反向传播,也没有经历过训练过程。所以这种感觉还浮在表面,但我知道原因。

由于最近有点空闲时间,我就利用这段时间休息时间来深入梳理和理解这部分知识。 标量

nLn^LnL 表示第 L 层的神经元数量 向量 BLB^LBL L×1 表示第 L-1 层 1nL× 1矩阵WLW^LWL 表示层 L 的权重 nL×nLn^L \times n^LnL×nL♷ZL 表示编号。 L层输入到激活函数的值 ZL=WLA(L−1)+BLZ^L=W^LA^{(L-1)} + B^LZL=WLA(L− 1) +BL nL×1n^L \times 1nL×1向量ALA^LAL表示第L层的输出值Ⓝ(ZL)AL^L^AL^ L = \sigma(Z^L ) AL=σ(ZL)nL×1n^L \times 1nL×1

我们可能都知道神经网络的训练过程,包括更新网络参数,以及更新的方向是减小损失函数的值。这意味着学习问题转化为优化问题。那么如何更新参数呢?我们需要计算所涉及的训练参数相对于损失函数的导数,然后求解梯度,然后使用梯度下降方法来更新参数。通过重复这个过程,我们可以找到最小化损失函数的最优解。

我们知道反向传播主要用于平衡损失函数相对于权重和偏差的导数

您可能听说过或读过很多有关网络中通过反向传播进行误差传播的内容。然后根据神经元对畸变的贡献w和b的大小。这意味着每个神经元都会分配一个错误。但这里的错误是什么意思呢?这个错误的确切定义是什么?答案是神经网络的每一层都会产生这些误差,并且特定层的误差根据后续层的误差来共享。网络第ll层的误差用δl\delta^lδl表示。

反向传播基于 4 个基本方程。使用这些方程计算误差 δL\delta^LδL 和损失函数。这里依次给出四个方程 δ(L)=∇aC⊙σ′( zL)(BP1)\delta^{(L)} = \nabla_a C \odot \sigma^{\prime}(z^ L ) \tag{BP1}δ(L)=∇a​C⊙σ′(zL )(BP1)δl=((wl)Tδl+1)⊙σ′(zl)(BP1)\delta^l = ( ( w ^l)^T \delta^{l+1}) \odot \sigma^ {\prime}(z^l) \tag{BP1}δl=((wl)Tδl+1)⊙σ′(zl ) ( BP1)∂C∂bjl=δjl(BP3)\frac{\partial C}{ \partial b_{j}^l} = \delta_j^l \tag{BP3}∂bjl​∂C​=δjl​( BP3 ) ∂C∂wjkl=akl−1δjl(BP4)\frac{\partial C}{ \partial w_{jk}^l} = a_k^{l-1}\delta_j^l \tag{BP4}∂wjkl​ ∂ C ​=akl−1​δjl​(BP4)

关于如何解释这个方程 4 A 我稍后分享给大家来说明一下。

class NeuralNetwork(object):
  def __init__(self):
    pass
  def forward(self,x):
    # 返回前向传播的 Z 也就是 w 和 b 线性组合,输入激活函数前的值
    # 返回激活函数输出值 A
    # z_s , a_s
    pass
  def backward(self,y,z_s,a_s):
    #返回前向传播中学习参数的导数 dw db
    pass
  def train(self,x,y,batch_size=10,epochs=100,lr=0.001):
    pass
复制代码

我们都在学习神经网络的过程中,这是一个训练的过程。主要分为两个阶段前向传播反向传播

  • 在前向传播的函数中,主要计算Z和A传播。 Z和A的详细信息可以参见上一页表格
  • 计算反向传播中可学习变量w和b的导数
  def __init__(self,layers = [2 , 10, 1], activations=['sigmoid', 'sigmoid']):
    assert(len(layers) == len(activations)+1)
    self.layers = layers
    self.activations = activations
    self.weights = []
    self.biases = []
    for i in range(len(layers)-1):
      self.weights.append(np.random.randn(layers[i+1], layers[i]))
      self.biases.append(np.random.randn(layers[i+1], 1))
复制代码
  • 层参数用于指定每层神经元的数量
  • 激活指定每层的激活函数为 σ( wx+b) \sigma(wx + b)σ(wx+b) 我们简单读一下代码 assert(len(layers) == length(activations)+1 )
for i in range(len(layers)-1):
  self.weights.append(np.random.randn(layers[i+1], layers[i]))
  self.biases.append(np.random.randn(layers[i+1], 1))
复制代码

因为连接Wab神经元每一层的权重是两层之间的方程。上面的代码是对于

前向传播

对于前向传播输入X放入a_s中,z=wx+bz = wx + bz=wx+b然后计算a=σ(z)a=\sigma(z )a=σ(z) 为输出,

def feedforward(self, x):
  # 返回前向传播的值
  a = np.copy(x)
  z_s = []
  a_s = [a]
  for i in range(len(self.weights)):
      activation_function = self.getActivationFunction(self.activations[i])
      z_s.append(self.weights[i].dot(a) + self.biases[i])
      a = activation_function(z_s[-1])
      a_s.append(a)
  return (z_s, a_s)
复制代码

这里激活函数,这个函数的返回值就是python中使用的函数lambda返回函数。简答在这里留下伏笔,稍后再编辑。

  @staticmethod
  def getActivationFunction(name):
        if(name == 'sigmoid'):
            return lambda x : np.exp(x)/(1+np.exp(x))
        elif(name == 'linear'):
            return lambda x : x
        elif(name == 'relu'):
            def relu(x):
                y = np.copy(x)
                y[y<0] = 0
                return y
            return relu
        else:
            print('Unknown activation function. linear is used')
            return lambda x: x
复制代码

[@staticmethod]
def getDerivitiveActivationFunction(name):
    if(name == 'sigmoid'):
        sig = lambda x : np.exp(x)/(1+np.exp(x))
        return lambda x :sig(x)*(1-sig(x))
    elif(name == 'linear'):
        return lambda x: 1
    elif(name == 'relu'):
        def relu_diff(x):
            y = np.copy(x)
            y[y>=0] = 1
            y[y<0] = 0
            return y
        return relu_diff
    else:
        print('Unknown activation function. linear is used')
        return lambda x: 1
复制代码

反向传播

这是本次分享的重点

  def backpropagation(self,y, z_s, a_s):
      dw = []  # dC/dW
      db = []  # dC/dB
      deltas = [None] * len(self.weights)  # delta = dC/dZ 计算每一层的误差
      # 最后一层误差
      
      deltas[-1] = ((y-a_s[-1])*(self.getDerivitiveActivationFunction(self.activations[-1]))(z_s[-1]))
      # 反向传播
      for i in reversed(range(len(deltas)-1)):
          deltas[i] = self.weights[i+1].T.dot(deltas[i+1])*(self.getDerivitiveActivationFunction(self.activations[i])(z_s[i]))        
      #a= [print(d.shape) for d in deltas]
      batch_size = y.shape[1]
      db = [d.dot(np.ones((batch_size,1)))/float(batch_size) for d in deltas]
      dw = [d.dot(a_s[i].T)/float(batch_size) for i,d in enumerate(deltas)]
      # 返回权重(weight)矩阵 and 偏置向量(biases)
      return dw, db
复制代码

首先计算最后一层的误差。根据BP1方程,我们可以得到如下公式

deltas[-1] = ((y-a_s[- 1])*(self.getDerivativeActivationFunction(self.activations[-1]))(z_s[- 1] ]))δL=(aL−y)σ(zL)\delta^L = (a^L - y)\sigma(z^L)δL=(aL−y)σ(zL)

接下来,根据上一层的误差 δl+1\delta^{l+1}δl+1 \delta^lδlδl=((wl)Tδl+1)⊙σ′(zl)( BP1 ) 计算当前层 δl )\delta^l = ((w ^l)^T \delta^{l+1}) \odot \sigma^{ \prime}(z^l) \tag{BP1}δl=((wl)Tδl + 1)⊙σ′(zl)( BP1)

batch_size = y.shape[1]
db = [d.dot(np.ones((batch_size,1)))/float(batch_size) for d in deltas]
dw = [d.dot(a_s[i].T)/float(batch_size) for i,d in enumerate(deltas)]
复制代码
∂C∂bjl=δjl(BP3)\frac{\partial C }{\partial b_{j}^l} = \delta_j^l \tag{BP3 } ∂bjl​∂C​=δjl​ (BP3)∂C∂wjkl=akl−1δjl(BP4)\frac{\partial C }{\partial w_{jk}^l} = a_k^{l-1}\ delta_j ^l \tag{BP4}∂wjkl​∂C​=akl−1​δjl​(BP4)

开始训练

  def train(self, x, y, batch_size=10, epochs=100, lr = 0.01):
# update weights and biases based on the output
      for e in range(epochs): 
          i=0
          while(i<len(y)):
              x_batch = x[i:i+batch_size]
              y_batch = y[i:i+batch_size]
              i = i+batch_size
              z_s, a_s = self.feedforward(x_batch)
              dw, db = self.backpropagation(y_batch, z_s, a_s)
              self.weights = [w+lr*dweight for w,dweight in  zip(self.weights, dw)]
              self.biases = [w+lr*dbias for w,dbias in  zip(self.biases, db)]
              # print("loss = {}".format(np.linalg.norm(a_s[-1]-y_batch) ))

作者:zidea
来源:稀土金块

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门