zoukankan      html  css  js  c++  java
  • 神经网络的理解与实现

    github:代码实现之神经网络
    本文算法均使用python3实现


    1. 什么是神经网络

      人工神经网络(artificial neural network,缩写ANN),简称神经网络(neural network,缩写NN)或类神经网络,是一种模仿生物神经网络(动物的中枢神经系统,特别是大脑)的结构和功能的数学模型或计算模型,用于对函数进行估计或近似
      神经网络主要由:输入层隐藏层输出层构成。当隐藏层只有一层时,该网络为两层神经网络,由于输入层未做任何变换,可以不看做单独的一层。实际中,网络输入层的每个神经元代表了一个特征,输出层个数代表了分类标签的个数(在做二分类时,如果采用sigmoid分类器,输出层的神经元个数为1个;如果采用softmax分类器,输出层神经元个数为2个),而隐藏层层数以及隐藏层神经元是由人工设定。一个基本的三层神经网络可见下图:

    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180614161435658-621551016.jpg)

    1.1 从逻辑回归到神经元

      为了便于大家理解,我们先回顾一下逻辑回归。逻辑回归模型如下: $$ h_ heta(x) = frac{1}{1+e^{- heta^T x}} $$
      其中 $ z = heta^T x = heta_0 + heta_1x_1 + heta_2 x_2 $ , $ h_ heta(x) = g(z) = frac{1}{1+e^{-z}} $
      对此我们可以用以下结构进行理解:

    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180614171610562-547994972.jpg)
      根据上图,我们可以看出,逻辑回归可以分为**线性变换**部分与**非线性变换**部分。而**只有输入层与输出层且输出层只有一个神经元**的神经网络的结构便于逻辑回归一致。只不过在神经网络中,**线性变换(求和)**与**非线性变换**被集成在一个神经元(隐藏层或输出层)中。如下图所示:
    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180614192339938-2118625997.jpg)
      于是,对于具有多层或多个输出神经元的神经网络就不难理解了。其**每个**隐藏层**神经元**/输出层**神经元**的值(**激活值**),都是由上一层神经元,经过**加权求和**与**非线性变换**而得到的。其中**非线性变换函数(又被称为激活函数)**可以是: $ sigmoid、tanh、relu $ 等函数。

    1.2 神经网络

      根据1.1中所讲述,我们可以得到以下这样一个基本的三层神经网络:

    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180614174002009-2118293708.jpg)
      其中 $ x_i (i=1,2,3) $ 为输入层的值,$ a_i^{(k)} (k=1,2,3...,K;i=1,2,3...,N_k) $ ,表示第 $ k $ 层中,第 $ i $ 个神经元的激活值, $ N_k $ 表示第 $ k $ 层的神经元个数。当 $ k=1 $ 时即为输入层,即 $ a_i^{(1)} = x_i $ ,而 $ x_0 = 1 与 a_0^{(2)} =1 $ 为偏置项。   为了求最后的**输出值 $ h_ heta(x)=a_1^{(3)} $**,我们需要计算隐藏层中每个神经元的激活值 $ a_{ji}^{(k)} (k=2,3) $。而隐藏层/输出层的每一个神经元,都是由上一层神经元经过**类似逻辑回归**计算而来。我们可以使用下图进行理解:
    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180614181211920-1940181115.jpg)
      我们使用 $ heta_{ji}^{(k)} $ 来表示第 $ k $ 层的参数(边权),其中下标 $ j $ 表示第 $ k+1 $ 层的第 $ j $ 个神经元,$ i $ 表示第 $ k $ 层的第 $ i $ 个神经元。于是我们可以计算出**隐藏层**的三个**激活值**: $$ a_1^{(2)} = g( heta_{10}^{(1)} x_0 + heta_{11}^{(1)} x_1 + heta_{12}^{(1)} x_2 + heta_{13}^{(1)} x_3) $$ $$ a_2^{(2)} = g( heta_{20}^{(1)} x_0 + heta_{21}^{(1)} x_1 + heta_{22}^{(1)} x_2 + heta_{23}^{(1)} x_3) $$ $$ a_3^{(2)} = g( heta_{30}^{(1)} x_0 + heta_{31}^{(1)} x_1 + heta_{32}^{(1)} x_2 + heta_{33}^{(1)} x_3) $$   再将隐藏层的三个激活值以及偏置项( $ a_0^{(2)},a_1^{(2)},a_2^{(2)},a_3^{(2)} $ )用来计算出输出层神经元的**激活值**即为该神经网络的输出: $$ a_1^{(3)} = g( heta_{10}^{(2)} a_0^{(2)} + heta_{11}^{(2)} a_1^{(2)} + heta_{12}^{(2)} a_2^{(2)} + heta_{13}^{(2)} a_3^{(2)}) $$   其中 $ g(z) $ 为**非线性变换函数(激活函数)**。   到此,我们就大致了解了什么是神经网络了。

    1.3 为什么要使用神经网络

      首先,神经网络应用在分类问题中效果很好。 工业界中分类问题居多。LR或者linear SVM更适用线性分类。如果数据非线性可分(现实生活中多是非线性的),LR通常需要靠特征工程做特征映射,增加高斯项或者组合项;SVM需要选择核。 而增加高斯项、组合项会产生很多没有用的维度,增加计算量。GBDT可以使用弱的线性分类器组合成强分类器,但维度很高时效果可能并不好。而神经网络在三层及以上时,能够很好地进行非线性可分。现在我们使用下面的例子进行一下解释。
      有这样一组样本,如下图:

    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180614183801848-567844685.jpg)
      若我们需要对上图中的样本进行分类,直观来看,**很难找到一条线性分类边界对其进行分类**,而观察上表中的输入输出值,我们可以看出分类结果与输入值是**异或**关系。**而逻辑回归可以通过改变参数,来实现“与”、“或”、“非”简单操作**。   (1)我们先来观察一下**逻辑回归**实现**逻辑“与”操作**,假设模型函数如下: $$ h_ heta^{(1)}(x) = g(-30 + 20x_1+20x_2) = frac{1}{1+e^{-(-30 + 20x_1+20x_2)}} $$     对应结构与结果为:
    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180614185340871-501761178.jpg)
      (2)**逻辑回归**实现**逻辑“或非”操作**,假设模型函数如下: $$ h_ heta^{(1)}(x) = g(-10 - 20x_1- 20x_2) = frac{1}{1+e^{-(10 - 20x_1 - 20x_2)}} $$     对应结果为:
    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180614190257001-249339049.jpg)
      (3)**逻辑回归**实现**逻辑“或”操作**,假设模型函数如下: $$ h_ heta^{(1)}(x) = g(-10 + 20x_1 + 20x_2) = frac{1}{1+e^{-(10 + 20x_1 + 20x_2)}} $$     对应结果为:
    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180614190834574-380450752.jpg)
      **观察(1)(2)中的 $ h_ heta^{(1)}(x) 与 h_ heta^{(2)}(x) $ 的值,通过“或”操作,便能够得到“异或”操作的结果。**
      也就是说,若将三个**逻辑回归**操作进行**叠加**,便能够对上述例子进行**非线性分类**。大致结构图可理解为下:
    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180614193043863-1365090635.jpg)
      而对**线性分类器**的逻辑与和逻辑或的**组合**可以完美的对平面样本进行分类。
    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180614193507478-883145667.jpg)
      隐层决定了最终的分类效果 :
    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180614193535176-1882831247.jpg)
      由上图可以看出,随着隐层层数的增多,凸域将可以形成任意的形状,因此可以解决任何复杂的分类问题。实际上,Kolmogorov理论指出:双隐层感知器就足以解决任何复杂的分类问题。   于是我们可以得出这样的结论:**神经网络**通过将线性分类器进行组合叠加,能够较好地进行**非线性分类**。

    2.神经网络目标函数

      同样的,对于神经网络我们也需要知道其目标函数,才能够对目标函数进行优化从而学习到参数。
      假设神经网络的输出层只有一个神经元,该网络有 $ K $ 层,则其目标函数为(若不止一个神经元,每个输出神经元的目标函数类似,仅仅是参数矩阵的不同):

    [J( heta) = - frac{1}{m} [ sum_{i=1}^m y^{(i)} log(h_ heta(a^{(K-1)})) + (1-y^{(i)}) log(1-h_ heta(a^{(K-1)}))] + frac{lambda}{2m} sum_{k=1}^{K-1} sum_{i=1}^{N_k} sum_{j=1}^{N_{k+1}} ( heta_{ji}^{(k)})^2 ]

      其中 $ a^{(i)} $ 倒数第2层的激活值,作为输出层的输入值。而其值为 $ a^{(k)} = g(a^{(k-1)}) $ , $ y^{(i)} $ 为实际分类结果 $ 0/1 $ , $ m $ 为样本数,$ N_k $ 为第 $ k $ 层的神经元个数。


    3.神经网络优化算法

      神经网络与普通的分类器不同,其是一个巨大的网络,最后一层的输出与每一层的神经元都有关系。而神经网络的每一层,与下一层之间,都存在一个参数矩阵。我们需要通过优化算法求出每一层的参数矩阵,对于一个有 $ K $ 层的神经网络,我们共需要求解出 $ K-1 $ 个参数矩阵。因此我们无法直接对目标函数进行梯度的计算来求解参数矩阵。
      对于神经网络的优化算法,主要需要两步:前向传播(Forward Propagation)反向传播(Back Propagation)

    3.1 前向传播

      前向传播就是从输入层到输出层,计算每一层每一个神经元的激活值。也就是先随机初始化每一层的参数矩阵,然后从输入层开始,依次计算下一层每个神经元的激活值,一直到最后计算输出层神经元的激活值
      以下面这个例子来看:

    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180614201612709-2070119124.jpg)
        (1)随机初始化参数矩阵 $ Theta^{(1)} 与 Theta^{(2)} $ : $$ Theta^{(1)} = egin{bmatrix} heta_{10}^{(1)} & heta_{11}^{(1)} & heta_{12}^{(1)} & heta_{13}^{(1)} \ heta_{20}^{(1)} & heta_{21}^{(1)} & heta_{22}^{(1)} & heta_{23}^{(1)} \ end{bmatrix} Theta^{(2)} = egin{bmatrix} heta_{10}^{(2)} & heta_{11}^{(2)} & heta_{12}^{(2)} \ end{bmatrix} $$     (2)计算隐藏层的每个神经元激活值: $$ a_1^{(2)} = g( heta_{10}^{(1)} x_0 + heta_{11}^{(1)} x_1 + heta_{12}^{(1)} x_2 + heta_{13}^{(1)} x_3) $$ $$ a_2^{(2)} = g( heta_{20}^{(1)} x_0 + heta_{21}^{(1)} x_1 + heta_{22}^{(1)} x_2 + heta_{23}^{(1)} x_3) $$       即: $$ a^{(2)} = g(Theta^{(1)} x) ,其中 a^{(2)} = egin{bmatrix} a_1^{(2)} \ a_2^{(2)} \ end{bmatrix} , x = egin{bmatrix} x_0 \ x_1 \ x_2 \ x_3 \ end{bmatrix} $$     (3)计算隐藏层的每个神经元激活值: $$ a_1^{(3)} = g( heta_{10}^{(2)} a_0^{(2)} + heta_{11}^{(2)} a_1^{(2)} + heta_{12}^{(2)} a_2^{(2)} ) $$       即: $$ a^{(3)} = g(Theta^{(2)} a^{(2)}) ,其中 a^{(2)} = egin{bmatrix} a_0^{(2)} \ a_1^{(2)} \ a_2^{(2)} \ end{bmatrix} $$   以上便是**前向传播**计算**激活值**的过程。

    3.2 反向传播

      反向传播总的来说就是根据前向传播计算出来的激活值,来计算每一层参数的梯度,并从后往前进行参数的更新
      在介绍反向传播的计算步骤之前,我们先引入一个概念---除输入层外每个神经元节点的“损失” ,$ delta_j^{k} $ 表示第 $ k $ 层第 $ j $ 个神经元的损失。
      于是我们可以计算求得(除输入层)每一层神经元的损失(以上一个例子来解释):

    [delta_1^{(3)} = a_1^{(3)} - y_1 ]

      其中 $ y_1 $ 为实际值。向量化表示如下:

    [delta^{(3)} = a^{(3)} - y ]

    [delta^{(2)} = ((Theta^{(2)})^T) delta^{(3)} cdot ast g'(z^{(2)}) ]

      其中 $ cdot ast $ 表示两个矩阵对应位置上元素相乘, $ g'(z^{(2)}) $ 是对函数求导。而 $$ z^{(2)} = egin{bmatrix} heta_{10}^{(1)} x_0 + heta_{11}^{(1)} x_1 + heta_{12}^{(1)} x_2 + heta_{13}^{(1)} x_3 heta_{20}^{(1)} x_0 + heta_{21}^{(1)} x_1 + heta_{22}^{(1)} x_2 + heta_{23}^{(1)} x_3 end{bmatrix}$$
      由上可看出,第二层的损失 $ delta^{(2)} $ 是基于第三层的损失 $ delta^{(3)} $ 计算而来。也就是说,我们可以先计算第三层的损失并对第二层的参数矩阵进行更新,再利用第三层的损失计算第二层的损失以及更新第一层的参数矩阵(至于为何可以这样进行,将在后面进行证明)。
      于是,基于反向传播算法的梯度更新步骤如下:
        (1)计算每一层的损失:$ delta^{k} $(见上面所示)。
        (2)计算每一层的 $ Delta $ (初始化为0):$ Delta^{(k)} = Delta^{(k)} + delta^{(k+1)} (a^{(k)})^T $
        (3)计算每一个参数的梯度: $$ D_{ji}^{(k)} = frac{1}{m} Delta_{ji}^{(k)} + lambda Theta_{(ji)}^{(k)} , 如果 i eq 0 $$ $$ D_{ji}^{(k)} = frac{1}{m} Delta_{ji}^{(k)} , 如果 i = 0 $$
      也就是说 $ frac{delta J(Theta)}{delta Theta_{ji}^{k}} = D_{ji}^{(k)} $。于是就可以使用梯度下降来进行参数的求解了。

    3.3 反向传播的推导

      大家可能都会有疑问,为什么求梯度时,要先对后一层进行计算,并利用其结果来求前一层的梯度?我们将针对如下例子进行推导证明:

    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180614201612709-2070119124.jpg)
      第一层的参数为: $$ Theta^{(1)} = egin{bmatrix} heta_{10}^{(1)} & heta_{11}^{(1)} & heta_{12}^{(1)} & heta_{13}^{(1)} \ heta_{20}^{(1)} & heta_{21}^{(1)} & heta_{22}^{(1)} & heta_{23}^{(1)} \ end{bmatrix} $$   第二层的参数为: $$ Theta^{(2)} = egin{bmatrix} heta_{10}^{(2)} & heta_{11}^{(2)} & heta_{12}^{(2)} \ end{bmatrix} $$   我们先来对第二层的参数求梯度 $ frac{delta J(Theta)}{delta heta^{(2)}} $ :
    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180615092823763-516209984.jpg)
      其中 $ y^i $ 为实际值, $ g( heta^{(2)} a^{(2)}) = a^{(3)} $ 。
    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180615093608361-1381445781.jpg)
      这一步的推导过程与逻辑回归一样,详细可参考[逻辑回归梯度求导过程](http://www.cnblogs.com/lliuye/p/9129493.html)。   现在我们来对第一层的参数求梯度 $ frac{delta J(Theta)}{delta heta^{(1)}} $ :
    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180615101433193-1305264930.jpg)
      先对中括号内的求导:
    ![](https://images2018.cnblogs.com/blog/1238724/201806/1238724-20180615101519630-1654478330.jpg)
      故:
    ![](https://images2018.cnblogs.com/blog/1238724/201808/1238724-20180813152703088-1913115862.jpg) ![](https://images2018.cnblogs.com/blog/1238724/201808/1238724-20180813152708715-1641335885.jpg)
      对比着3.2中的公式,我们可以看出,第 $ k $ 层的梯度可以根据第 $ k+1 $ 层的**损失**来计算(上式是用第 $ 2 $ 的损失来推导第 $ 1 $ 层的梯度)。   到此,反向传播的推导过程就完成了。如果对式子还有不理解的,可以自己动手多试试。

    4.神经网络算法分析

      该部分参考博文[3]
      (1)理论上,单隐层神经网络可以逼近任何连续函数(只要隐层的神经元个数足够)
      (2)对于一些分类数据(比如CTR预估),3层神经网络效果优于2层神经网络,但如果把层数不断增加(4,5,6层),对最后的结果的帮助没有那么大的跳变。
      (3)提升隐层数量或者隐层神经元个数,神经网络的“容量”会变大,空间表达能力会变强。
      (4)过多的隐层和神经元结点会带来过拟合问题。
      (5)不要试图降低神经网络参数量来减缓过拟合,用正则化或者dropout层
      注意:在代码中对参数的初始化并不是使用0来初始化,还是在范围 $ [-epsilon,epsilon] $ 间随机初始化。对应代码为:

    Theta = np.random.rand(nextUnit, Unit+1) * 2 * epsilon - epsilon
    
    

    引用及参考:
    [1] 《Machine Learning》Andrew Ng
    [2] https://www.jianshu.com/p/a3b89d79f325
    [3] https://blog.csdn.net/leiting_imecas/article/details/60463897
    [4] https://blog.csdn.net/a819825294/article/details/53393837

    写在最后:本文参考以上资料进行整合与总结,属于原创,文章中可能出现理解不当的地方,若有所见解或异议可在下方评论,谢谢!
    若需转载请注明https://www.cnblogs.com/lliuye/p/9183914.html

  • 相关阅读:
    TFS实现需求工作项自动级联保存
    Oracle PL/SQL Developer集成TFS进行团队脚本文件版本管理
    TFS2017持续集成构建
    TFS 测试用例步骤数据统计
    精细化容量管理的设备成本优化之路
    腾讯Web工程师的前端书单
    React Native For Android 架构初探
    SQL系列(八)—— 分组(group by)
    SQL系列(七)—— 相似(like)
    SQL系列(六)—— 过滤(where)
  • 原文地址:https://www.cnblogs.com/lliuye/p/9183914.html
Copyright © 2011-2022 走看看