栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

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

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

在这篇文章中,准备用 Python 从头开始实现一个全连接的神经网络。你可能会问,为什么需要自己实现,有很多库和框架可以为我们做这件事,比如 Tensorflow、Pytorch 等。这里只想说只有自己亲手实现了,才是自己的。

想到今天自己从接触到从事与神经网络相关工作已经多少 2、3 年了,其中也尝试用 tensorflow 或 pytorch 框架去实现一些经典网络。不过对于反向传播背后机制还是比较模糊。

梯度

梯度是函数上升最快方向,最快的方向也就是说这个方向函数形状很陡峭,那么也是函数下降最快的方向。

虽然关于一些理论、梯度消失和结点饱和可以输出一个 1、2、3 但是深究还是没有底气,毕竟没有自己动手去实现过一个反向传播和完整训练过程。所以感觉还是浮在表面,知其所以然而。

因为最近有一段空闲时间、所以利用这段休息时间将要把这部分知识整理一下、深入了解了解

类型符号说明表达式维度
标量n^L n L表示第 L 层神经元的数量
向量B^L B L表示第 L 层偏置n^L times 1 1
矩阵W^L W L表示第 L 层的权重n^L times n^L n L
向量Z^L Z L表示第 L 层输入到激活函数的值ZL=WLA^{(L-1)} + B^L W L A ( L − 1 ) + B Ln^L times 1 1
向量A^L A L表示第 L 层输出值A^L = sigma(Z^L) σ ( Z L )n^L times 1 1

我们大家可能都了解训练神经网络的过程,就是更新网络参数,更新的方向是降低损失函数值。也就是将学习问题转换为了一个优化的问题。那么如何更新参数呢?我们需要计算参与训练参数相对于损失函数的导数,然后求解梯度,然后使用梯度下降法来更新参数,迭代这个过程,可以找到一个最佳的解决方案来最小化损失函数。

我们知道反向传播主要就是用来结算损失函数相对于权重和偏置的导数

可能已经听到或读到了,很多关于在网络通过反向传播来传递误差的信息。然后根据神经元的 w 和 b 对偏差贡献的大小。也就是将误差分配到每一个神经元上。 但这里的误差(error)是什么意思呢?这个误差的确切的定义又是什么?答案是这些误差是由每一层神经网络所贡献的,而且某一层的误差是后继层误差基础上分摊的,网络中第 l l 层的误差用 delta^l δ l 来表示。

反向传播是基于 4 个基本方程的,通过这些方程来计算误差 delta^L δ L 和损失函数,这里将这 4 个方程一一列出

delta^{(L)} = nabla_a C odot sigma{prime}(zL) tag{BP1}∇ aC ⊙ σ ′ ( z L ) ( B P 1 )

delta^l = ((wl)T delta^{l+1}) odot sigma{prime}(zl) tag{BP1}( ( w l ) T δ l + 1 ) ⊙ σ ′ ( z l ) ( B P 1 )

frac{partial C}{partial b_{j}^l} = delta_j^l tag{BP3}∂ b j l∂ C= δ j l( B P 3 )

frac{partial C}{partial w_{jk}^l} = a_k{l-1}delta_jl tag{BP4}∂ w j k l∂ C= a k l − 1δ j l( B P 4 )

关于如何解读这个 4 个方程,随后想用一期分享来说明。

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))
  • layers 参数用于指定每一层神经元的个数
  • activations 为每一层指定激活函数,也就是 sigma(wx + b) σ ( w x + b )

来简单读解一下代码 assert(len(layers) == len(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))

因为权重连接每一个层神经元的 w 和 b ,也就两两层之间的方程,上面代码是对

前向传播

在前向传播中,将输入 X 输入到 a_s 中, z = wx + b w x + b 然后对输出再计算 a=sigma(z) σ ( 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.getDerivitiveActivationFunction(self.activations[-1]))(z_s[-1]))

delta^L = (a^L - y)sigma(z^L)( a L − y ) σ ( z L )

接下来基于上一层的 delta^{l+1} δ l + 1 误差来计算当前层 delta^l δ l

delta^l = ((wl)T delta^{l+1}) odot sigma{prime}(zl) tag{BP1}( ( w l ) T δ l + 1 ) ⊙ σ ′ ( z l ) ( B P 1 )

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)]

frac{partial C}{partial b_{j}^l} = delta_j^l tag{BP3}∂ b j l∂ C= δ j l( B P 3 )

frac{partial C}{partial w_{jk}^l} = a_k{l-1}delta_jl tag{BP4}∂ w j k l∂ C= a k l − 1δ j l( B P 4 )

开始训练
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 

推荐阅读:

1.程序员用Python爬虫做副业半个月就赚了3W

2.程序员别瞎学,这几门语言要被淘汰了…

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/990972.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号