吴恩达神经网络第一课学习笔记

0.前言

什么是神经网络?
神经网络是上个世纪出现的产物,其思想就是模拟人体神经网络的方式来实现机器的自主学习。他在许多领域都会有使用,例如:语音识别、图像识别、语言翻译等。

神经网络的思想如下图所示:
神经网络

假设 x1 表示房价; x2 表示房子大小; x3 买房者所拥有的资金; x4 表示房子所在地区的空气质量等等,当然实际生活中还可能会考虑许多东西,这里就不举例了。而 y 表示对于房子的是否想买的态度,即想买或不想买。那么神经网络的工作就是将一个房子的上面四个变量放入进去,而神经网络给你预测出你是否想买。

那么他是怎么预测的呢?我们知道房价和房子大小往往是存在正相关性的,即房子越大房价越高,在这里我们将它们统称为“房子的状况”;“买房者所拥有的资金”以及一些其他的买房者的信息统称为“买房者的状况”;“房子周边的空气质量”有决定这“房子周边环境”。因此第二层的三个圆圈,可以看做:“房子的状况”、“买房者的状况”、“房子周边环境”。而第三层的一个圆圈可以看做“对房子的评分”。然后如果评分大于一个值,那么表示买,如果小于表示不买。

神经网络就是做了那么一个事情,我们经过一定的计算可以根据前面的节点,来获取下一个节点表示的特征,来达到预测的目的。那么问题来了,在上面图中第二层的三个圆各代表哪一个情况呢?答案是无法预知,有可能上面三种情况,有可能是上面情况中的两种,有可能是一些其他的有相关性的情况,甚至可能是一些我们经验无法解释的组合。正因为这样,所以神经网络被诟病为“黑盒”,里面的东西往往无法预测也无法解释。

我们先从一个节点来解释,神经网络的工作方式。

附:在本文中,激活函数使用sigmoid函数,数据集使用鸢尾花数据集前100行,可以在R中直接输”iris”出现,在python中

from  sklearn  import datasets
iris = datasets.load_iris()

并将前50行数据的Species设置为0,后50行的设置为1

1. 神经元

神经网络是由一个个神经元构成的,例如在上面的例子中,第一层有三个神经元,第二层有一个神经元。这里我们先讲一讲什么是神经元,神经元如下图所示
这里写图片描述

在神经元中,会对x先进行加权求和,在将加权求和的结果进行线性变换以得到 y 。在上面中x为传入参数, w 为权重,b为截距, y 为输出结果。

举个例子,假设

  • 今天气温为20度,风力2级,湿度15%,空气质量51;
  • 明天气温为25度,风力1级,湿度10%,空气质量152;
  • 后天气温为22度,风力5级,湿度12%,空气质量51。

对于是否适合运动来说

  • 气温的权重为10;
  • 风力权重为-50;
  • 湿度权重为-5;
  • 空气质量权重为-3;
  • 截距200。

那么加权求和的结果为:今天指数为72,明天指数为-156,后天指数为-33.我们再使用sigmoid函数y=11+ex进行线性变换,可以近似得到”1、0、0”,因此可以得出今天适合运动,明天和后天不适合运动的结论。当然这个例子十分粗糙但是神经网络就是这样子预测的。

上面的公式为

z=i=0nwixi+by=f(x)f(x)=σ(x)=11+ex

其中 f 称为激活函数(activation function)。在本例子中采用sigmoid(σ)函数,这个概念后面会说。

上面的式子可能只会出现一次,在后面的使用中我们矩阵来代替多个样本,因此对于存在变量 (x1,x2,x3,...,xn) ,存在样本 (x(1),x(2),x(3),...,x(m)) 对应的矩阵为:

x(1)1x(1)2x(1)nx(2)1x(2)2x(2)nx(m)1x(m)2x(m)n
表示存在 m 个样本,每个样本有n个变量。在以后的使用中,称之为 x

现在有两个问题:
1. 激活函数是什么,如何选择?
2. w b 如何计算出来?
在下面我们会解决这个问题。

1.1 激活函数(activation function)?

激活函数的作用是什么,可以参考:神经网络激励函数的作用是什么?有没有形象的解释? - lee philip的回答 - 知乎

现常用的激活函数有:

  • sigmoid函数:公式为
    y=11+ex
    图像为:
    这里写图片描述
    • tanh函数:公式为
      y=sinh(x)cosh(x)=exexex+ex
      图像为
      这里写图片描述
      ReLU函数:公式为
      y=max(0,x)
      即:
      if(x<0):y=0
      if(x>=0):y=x
      图像为:
      这里写图片描述
    • 激活函数是要求定义域为 R ,且在定义域内处处可导的。对于上面的ReLu函数,在0的方,可以设置其导数为0或1。激活函数主要用来做线性变换,除非极特殊情况(例如:将输入加权求和后直接输出)是不会使用线性回归的。

      在本文中,主要使用sigmoid函数,sigmoid函数又称为logistic函数,其函数形式为:

      y=11+ex

      它主要用于二分分类,而二分分类就是将数据分为两类,例如:根据空气温度、湿度、云高等信息将天气分为晴天或非晴天。而他能够作为二分分类的一个非常重要的原因就是他的值域为 (0,1) ,正是由于这个特点,他可以将输入的参数能够很好的向概率映射。
      另外当 x 远远大于0的时候,y会无限趋近于1;当 x 远远小于0的时候,y又无限趋近于0。

      使用sigmoid进行二元分类的效果:
      这里写图片描述
      (图片来源:Logistic Regression – Geometric Intuition

      1.2 梯度下降法

      在说完了激活函数之后,我们还剩下一个问题如何确定 w b呢?因为我们之前并不知道 w b,因此需要使用样本来训练出 w b。我们使用梯度下降法来得到 w b。关于梯度下降法,可以参考一下文章:

      梯度下降法的基本思路就是:先随机初始化一个 w b。然后根据该 w b求出相应的预测值 y^ ,在根据误差反向修正 w b。不断迭代使误差最小,来达到求出一个 w b使误差最小的目的。如下图所示:
      这里写图片描述
      (图片来源:博客园)
      在上图中,我们先初始化一个随机的 w b,当然此时的误差可能非常大。然后求 w b对误差的偏导,并向梯度下降的方向修正,随着不断的修正,误差会不断减小,当达到误差最小值时的 w b就是我们模型使用的参数。

      1.2.1 初始化 w b

      在神经网络使用前需要先初始化 w b

      (1) w 的初始化
      初始w一般采用随机初始化的方法,初始化的个数和前面传入参数的个数相同,例如,假设有三个传入参数 x1,x2,x3 那么初始化三个 w 则为:

      import numpy
      w = (numpy.random.random(3)*0.01).reshape(3,1)
      [[ 0.00380803]
       [ 0.00945647]
       [ 0.00059899]]
      

      一般在w初始化的过程中都会乘上0.01,使他小于0.01。这样做的目的是为了使 w x加权求和的结果较小,如果加权求和的结果很大,那么例如在sigmoid函数中,他的梯度会十分小,下降十分慢。

      (2)b的初始化
      b 的初始化就简单了,直接等于0就可以了,当然也可以随机初始化

      1.2.2 正向传递

      正向传递就是从前向后计算,其计算的过程为:
      这里写图片描述
      (图片来源:吴恩达神经网络课件
      计算过程也是相当简单,公式为:

      z=wTx+ba=σ(z)=11+ez

      其中第一个公式是相加求和的过程,第二个公式是使用激活函数做线性变换的过程,在神经元中的计算就是这两步。

      使用python编程如下:

      from  sklearn  import datasets
      import numpy as np
      # 获取计算数据
      iris = datasets.load_iris()
      x = iris['data'][:100].T
      y = iris['target'][:100].T
      # 初始化w和b
      np.random.seed(1)
      w = (np.random.random(x.shape[0])*0.01).reshape(x.shape[0],1)
      b = 0
      # 正向传递
      z = np.dot(w.T,x)+b
      a = 1/(1+np.exp(-z))
      

      输出a为:

      array([[ 0.51176926,  0.5106609,  0.51081246,  0.51052831,  0.51184505,  0.51295288,  0.51114382,  0.51148511,  0.50995984,  0.51076538,  0.51244182,  0.51127673,  0.51048115,  0.50996,  0.51339837,  0.5141651,  0.51295277,  0.5118448,  0.51300994,  0.51238476,  0.51190196,  0.51228032,  0.51142814,  0.51163601,  0.51127682,  0.51076517,  0.51163622,  0.51187349,  0.51169348,  0.51081254,  0.51073675,  0.51205298,  0.51287778,  0.51344578,  0.51076538,  0.51112504,  0.51218602,  0.51076538,  0.51013982,  0.51158931,  0.51174058,  0.50905949,   0.51049983,  0.51196728,  0.51246041,  0.51063224,  0.51230925,  0.51070828,  0.51233763,  0.51130509,
       0.51411624,  0.51356665,  0.5139077 ,  0.51085794,  0.51295104,  0.51196642,  0.513718  ,  0.51018583,  0.51308412,  0.51134081,  0.50957006,  0.51268573,  0.51097233,  0.51263875,  0.51204194,  0.51362368,  0.51237325,  0.5116639 ,  0.5115586 ,  0.51117101,  0.51327242,  0.51238305,  0.51220285,  0.51230771,  0.51287567,  0.51333955,  0.51318812,  0.51367049,  0.51261004,  0.51137954,  0.51088678,  0.5108112 ,  0.51181492,  0.51232581,  0.51216486,  0.51358536,  0.51369929,  0.51169167,  0.51222206,  0.51121792,  0.51132248,  0.51281869,  0.51163497,  0.51011004,  0.51168215,  0.51225074,  0.51214631,  0.51266729,  0.51064972,  0.5119663 ]])

      1.2.3 损失函数(Loss Function )与成本函数(Cost Function )

      损失函数又叫做误差函数(error function)用来计算单个样本预测结果与实际结果的误差,存在数据 {(x(1)y(1))(x(2)y(2))(x(n)y(n))} ,我们希望 y^=y ( y^ 表示预测值,即前面的 a ),误差在许多时候是无法消除的,而为了使预测值更接近于实际值,即y^y,而损失函数就是衡量预测值与真实值差别的函数

      一般来说损失函数公式为:

      L(y^y)=12(y^y)2

      损失函数是衡量单个训练样本的表现,而成本函数是整个训练样本的表现。成本函数公式为:

      J(w,b)=1mi=1mL(y^(i)y(i))
      而我们的目的是选择出一个 w b来使成本函数 J(wb) 最小。

      但是对于sigmoid函数来说则不能使用这个损失函数函数,因为他对于sigmoid函数来说是一个非凸函数(虽然我试了半天也没有证明出来),存在许多极小值,有可能会陷入局部拟合状态,在网上经常会看到这样一张图:
      这里写图片描述
      我们的目的是使其全局最优,但是非凸函数往往会陷入到局部最优。

      因为在神经网络的中使用的是梯度下降法(顺着梯度下降),因为对非凸函数做梯度下降时容易陷入局部拟合的特点,使用梯度下降法一般会避免非凸函数。因此对于sigmoid函数来说它将使用一个不同的损失函数,起到衡量误差的作用。其损失函数为:

      L(y^y)=(ylogy^+(1y)log(1y^))

      此时成本函数公式为:

      J(w,b)=1mi=1nL(y^(i)y(i))=1mi=1m[y(i)log(y^(i))+(1y(i))log(1y^(i))]

      1.2.4 误差反向传播

      在上面提到,我们的目的是找到一个 w b的值来使成本函数 J(w,b) 的值最小,因此计算 J(w,b) w 的偏导数为:

      因为:

      z=w1x1+w2x2+by^=σ(z)=11+ezL(y^y)=(ylogy^+(1y)log(1y^))J(w,b)=1mi=1nL(y^(i)y(i))

      所以变量在损失函数上的偏导数为

      L(y^y)www1=((ylogy^+(1y)log(1y^)))w=(yy^+1y1y^)×y^w=(yy^+1y1y^)×(11+ez)w=(yy^+1y1y^)×y^(1y^)×zw=(yy^+1y1y^)×y^(1y^)×(w1x1+w2x2+b)w=(yy^+1y1y^)×y^(1y^)×x1=(y^y)×x1

      相应的 wi L(y^y)wi=(y^y)×xi
      b (可以看做其对应的x=1)时 L(y^y)b=y^y

      变量在成本函数上的偏导数为:

      J(wi,b)wi=(1mi=1nL(y^(i)y(i)))wi=1mi=1n(L(y^(i)y(i)))wi=i=1n(y^y)×xim

      因此

      J(wi,b)wi=i=1n(y^y)×ximJ(wi,b)b=i=1n(y^y)m

      上面求出了成本函数在 w 上的偏导数,下面就可以对w b 进行修正,修正方式为原来的值减去一个学习率乘以偏导数的积,即使用下面的公式修正:
      w w=wαJ(w,b)w
      b b=bαJ(w,b)
      由于偏导数本身自带方向,因此在这里不需要考虑方向的问题。

      对于学习率的选择一般在0-1之间,例如: 0.1,0.005,0.001,0.0005 等等。较大的学习率可以使梯度下降速度较快,能使模型更快的达到较好结果的位置,但是在最低点的时候会不断抖动,不易落到最低点,如下图所示:
      这里写图片描述
      (图片来源:吴恩达神经网络作业
      而较小的学习率梯度下降速度慢,但是在最低点附近时候,能够较好的落到最低点附近,如下图所示:
      这里写图片描述
      (图片来源:吴恩达神经网络作业

      现在也有许多方法来避免这个问题例如:学习率动态变化、基于惯性的梯度下降法等等。

      1.3 公式总结

      综上所述,在神经元训练的过程中会有三个过程:
      (1)正向传递
      总前向后计算,所用到的公式:

      z=wTx+by^=σ(z)=11+ez

      (2)计算误差
      所用到的公式:
      J(w,b)=1mi=1m[y(i)log(y^(i))+(1y(i))log(1y^(i))]

      (3)反向传播
      根据梯度下降法,从后向前反向修正 w b

      dw=J(w,b)w=x(y^y)mda=J(wi,b)b=y^ymw=wα×dwb=bα×db

      使用python代码如下(采用鸢尾花数数据集):

      from  sklearn  import datasets
      import numpy as np
      # 获取计算数据
      iris = datasets.load_iris()
      x = iris['data'][:100].T
      y = iris['target'][:100].T
      m = x.shape[1]
      alpat = 0.005
      iterations_number = 20000
      # 初始化w和b
      np.random.seed(1)
      w = (np.random.random(x.shape[0])*0.01).reshape(x.shape[0],1)
      b = 0
      for i in range(iterations_number):
          # 正向传递
          z = np.dot(w.T,x)+b
          a = 1/(1+np.exp(-z))
          # 计算误差
          J = - np.sum(y * np.log(a)+(1-y)*np.log(1-a))/m
          if(i%10000 == 0):
              print("当前迭代次数"+str(i)+"\t误差:"+str(J))
          # 反向传递
          dw = np.dot(x,(a-y).T)/m
          db = np.sum(a-y)/m
          w = w - alpat*dw
          b = b - alpat*db
      
      # 预测数据
      z = np.dot(w.T,x)+b
      a = 1/(1+np.exp(-z))
      y_predict  = (a > 0.5 )+ 0
      
      print("预测结果:"+str(y_predict)+"\n实际结果:"+str(y)+"\n预测准确率:"+str((1-np.sum(np.abs(y_predict-y))/m)*100)+"%")

      输出结果:

      当前迭代次数0 误差:0.69281000899
      当前迭代次数10000 误差:0.0149878476054
      预测结果:[[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
        0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
        1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]]
      实际结果:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
       0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
       1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
      预测准确率:100.0%

      2. 多层神经网络

      有了上面神经元的概念,那么接下来的多层神经网络就比较好理解了,多层神经网络就是多个神经元所组成的网络。
      这里写图片描述
      (图片来源:吴恩达神经网络课件
      其实神经网络也很容易明白,即上一层节点的输出,等于下一层节点的输入。这里介绍几个概念“输入层”、“隐藏层”、“输出层”
      在上图中, x1,x2,x3 是输入层,一般情况下输入层会被省略,在上图中也没有画出输入层节点;在中间的三个是“隐藏层”;最后面的一个圆是“输出层”。上图中没有画出输入层,只画出了一个输出层和一个隐藏层。

      一个神经网络中必有一个输入层和一个输出层,有0个或多个隐藏层。输出层一般只有一个神经元,但是有时也会有多个输出因此有多个神经元。

      我们现在做如下定义:上标“ [i] ”表示当前为第 i 层的节点,隐藏层为第0层忽略掉。上标“(j)”表示该层的第 j 个节点。因此上图中,

      • 隐藏层节点的输入可以表示为x[1]1,x[1]2,x[1]3

        • 隐藏层第一个节点的输入可以表示为 x[1](1)1,x[1](1)2,x[1](1)3
        • 隐藏层的第四个节点的输入可以表示为 x[1](4)1,x[1](4)2,x[1](4)3
        • 输出层的输入可以表示为 x[2]1,x[2]2,x[2]3,x[2]4 (由于只有一个输出层节点,因此有无上标是一样的)。
        • 2.1 梯度下降法

          2.1.1 正向传递

          由上面定义得对于隐藏层第二个节点来说:输入为 x[1](2) ,权重 w[1](2) ,截距 b[1](2) ,输出 a[1](2)

          他的正向传递计算公式为:

          z[1](2)=w[1](2)Tx[1](2)+b[1](2)a[1](2)=σ(z[1](2))=11+ez[1](2)

          我们再抽象掉节点个数,即隐藏层公式来说:输入为 x[1] ,权重 w[1] ,截距 b[1] ,输出 a[1]
          他的计算公式为:
          z[1]=w[1]Tx[1]+b[1]a[1]=σ(z[1])=11+ez[1]

          因为上一层节点的输出等于下一层节点的输入,因此存在: x[2]=a[1] ,因此上图中的输出层的计算可以表示为:
          z[2]=w[2]Tx[2]+b[2]=w[2]Ta[1]+b[2]a[2]=σ(z[2])=11+ez[2]

          因此,对于

          x[1]=x(1)1x(1)2x(1)nx(2)1x(2)2x(2)nx(m)1x(m)2x(m)n("(i)"i)

          w[1]=w[1](1)1w[1](1)2w[1](1)nw[1](2)1w[1](2)2w[1](2)nw[1](p)1w[1](p)2w[1](p)n

          b[1]=[b[1](1)b[1](2)b[1](p)]

          可以求出
          a[1]=a[1](1)1a[1](1)2a[1](1)pa[1](2)1a[1](2)2a[1](2)pa[1](m)1a[1](m)2a[1](m)p

          而此层的 a 又是下一层的x,因此就可以向下计算。

          所以上图中最后的公式为:

          z[1]=w[1]Tx[1]+b[1]a[1]=σ(z[1])=11+ez[1]x[2]=a[1]z[2]=w[2]Tx[2]+b[2]a[2]=σ(z[2])=11+ez[2]y^=a[2]

          对于更多的隐藏层来说,例如,隐藏层个数变为2,那么只需要不断地进行循环即可,即:

          foriin1:L
          z[i]=w[i]Tx[i]+b[i]
          a[i]=σ(z[i])=11+ez[i]
          x[i+1]=a[i]
          y^=a[i]

          2.1.2 损失函数(Loss Function )与成本函数(Cost Function )

          和神经元没什么区别…略

          2.1.3 误差反向传播

          误差反向就是反向推导回去,如果明白了上面的内容也并不难。
          我们从后向前推导:
          (1). 计算预测结果对误差的偏导数;
          因为:

          L(y^y)=(ylogy^+(1y)log(1y^))J(w,b)=1mi=1nL(y^(i)y(i))

          所以:
          J(wi,b)y^=(1mi=1nL(y^(i)y(i)))y^=1mi=1n(L(y^(i)y(i)))y^=1m(yy^+1y1y^)

          (2). 计算输出层权值对误差的偏导数;
          因为:
          J(wi,b)y^=1m(yy^+1y1y^)y^=a[2]a[2]=σ(z[2])=11+ez[2]z[2]=w[2]Tx[2]+b[2]

          所以:
          J(wi,b)w[2]=y^w[2]×J(wi,b)y^=(a[2]y)Tx[2]m

          同理:
          J(wi,b)b[2]=(a[2]y)Tm

          (3). 修正输出层节点间的 w b

          w[2]w[2]=w[2]αJ(w,b)w[2]b[2]b[2]=b[2]αJ(w,b)

          (4). 计算隐藏层的输出对误差的偏导数
          因为:

          J(wi,b)y^=1m(yy^+1y1y^)y^=a[2]a[2]=σ(z[2])=11+ez[2]z[2]=w[2]Tx[2]+b[2]x[2]=a[1]

          所以:
          J(wi,b)a[1]=y^a[1]×J(wi,b)y^=w[2]T(a[2]y)m

          (5). 计算隐藏层的权值对误差的偏导数
          因为:

          J(w,b)a[1]=w[2]T×(a[2]y)mz[1]=w[1]Tx[1]+b[1]a[1]=σ(z[1])=11+ez[1]

          所以:
          J(wi,b)w[1]=a[1]w[1]×J(w,b)a[1]=a[1]T(1a[1]T)x[1]×J(w,b)a[1]T

          (...)
          同理:
          J(wi,b)b[1]=a[1]T(1a[1]T)×J(w,b)a[1]T

          (............)

          2.2 公式总结

          好吧,我已经晕了,总结一下公式,上标“ [i] ”表示当前层的信息, L 表示隐藏层和输出层的共计个数。

          (1)正向传递
          总前向后计算,所用到的公式

          foriin1:L
          z[i]=w[i]Tx[i]+b[i]
          a[i]=f(z[i])=σ(z[i])=11+ez[i]
          x[i+1]=a[i]
          y^=a[i]

          (2)计算误差
          所用到的公式:

          J(w,b)=ylog(y^)+(1y)log(1y^)m

          (3)反向传播
          根据梯度下降法,从后向前反向修正 w b
          dy^=J(wi,b)y^=1m(yy^+1y1y^)
          da[L]=J(wi,b)a[L]=dy^
          foriinL:1
          dz[i]=a[i]z[i]×da[i]=f(a[i])×da[i]=σ(a[i])×da[i]=a[i](1a[i])×da[i]
          dw[i]=z[i]w[i]dz[i]T=x[i]dz[i]T
          db[i]=z[i]b[i]dz[i]=dz[i]
          dx[i]=z[i]x[i]dz[i]=w[i]dz[i]
          w[i]=w[i]α×dw[i]
          b[i]=b[i]α×db[i]
          da[i1]=dx[i]

          使用python代码如下(采用鸢尾花数数据集,在这里隐藏层使用tanh函数):

          from sklearn import datasets
          import math
          import numpy as np
          
          def sigmoid(x):
              return 1/(1+np.exp(-x))
          
          def tanh(x):
              return (np.exp(x)-np.exp(-x))/(np.exp(x)+np.exp(-x))
          
          def sigmoid_T(yhat):
              return (1-yhat)*yhat
          
          def tanh_T(yhat):
              return 1-yhat**2
          
          def get_wb(last_node_number,this_node_number,n = 0.01):
              w = np.random.random(last_node_number*this_node_number)*n
              w = np.reshape(w,(last_node_number,this_node_number))
              b = np.random.random(this_node_number)*n
              b = np.reshape(b,(1,this_node_number))
              return w,b
          
          def dj2dz(y,yhat):
              return (1-y)/(1-yhat)-y/yhat
          
          iris = datasets.load_iris()['data'][0:100]
          y = datasets.load_iris()['target'][0:100]
          x = iris.T
          y = np.reshape(y,(1,-1))
          alpat = 0.05
          m = x.shape[1]
          iterations_number=25000
          
          # 隐藏层每个层中节点个数
          lay_node_number = [3]
          
          # 隐藏层和输出层的激活函数和激活函数的反函数
          func = [tanh,sigmoid]
          funct = [tanh_T,sigmoid_T]
          
          # # 隐藏层每个层中节点个数
          # lay_node_number = [10,3]
          
          # # 隐藏层和输出层的激活函数和激活函数的反函数
          # func = [tanh,tanh,sigmoid]
          # funct = [tanh_T,tanh,sigmoid_T]
          
          # 加入输入层和输出层的节点个数
          lay_node_number.insert(0,x.shape[0])
          lay_node_number.append(1)
          lay_number = len(lay_node_number)
          
          # 初始化w和b
          w = []
          b = []
          np.random.seed(1)
          for i in range(lay_number-1):
              tmp_w,tmp_b = get_wb(lay_node_number[i],lay_node_number[i+1])
              w.append(tmp_w)
              b.append(tmp_b)
          
          for j in range(iterations_number):
              # 正向计算
              x_cache = [x]
              for i in range(lay_number-1):
                  z = np.dot(w[i].T,x_cache[i])+b[i].T
                  a = func[i](z)
                  x_cache.append(a)
              y_hat=x_cache[len(x_cache)-1]
          
              # 计算误差
              J = - np.sum(y * np.log(y_hat)+(1-y)*np.log(1-y_hat))/m
              if(j%1000 == 0):
                  print("当前迭代次数:"+str(j)+"\t\t误差:"+str(J))
          
              # 反向传播
              da = dj2dz(y,y_hat)
              for i in range(len(w)-1,-1,-1):  
                  dz = da * funct[i](x_cache[i+1])
                  dx = np.dot(w[i],dz)/m
                  dw = np.dot(x_cache[i],dz.T)/m
                  db = np.sum(dz.T,axis=0,keepdims=True)/m
                  w[i] = w[i] - alpat*dw
                  b[i] = b[i] - alpat*db
                  da = dx
          
          # 预测结果
          a = x
          for i in range(lay_number-1):
              z = np.dot(w[i].T,a)+b[i].T
              a = func[i](z)
          
          y_predict  = (a > 0.5 )+ 0
          print("预测结果:"+str(y_predict)+"\n实际结果:"+str(y)+"\n预测准确率:"+str((1-np.sum(np.abs(y_predict-y))/m)*100)+"%")

          输出结果:

          当前迭代次数:0        误差:0.693080329612
          当前迭代次数:1000     误差:0.494298198202
          当前迭代次数:2000     误差:0.0533060416615
          当前迭代次数:3000     误差:0.0198922213283
          当前迭代次数:4000     误差:0.011537471035
          当前迭代次数:5000     误差:0.0079584332934
          当前迭代次数:6000     误差:0.00601276052925
          当前迭代次数:7000     误差:0.00480316927673
          当前迭代次数:8000     误差:0.00398362148443
          当前迭代次数:9000     误差:0.00339409719053
          当前迭代次数:10000        误差:0.00295095246304
          当前迭代次数:11000        误差:0.00260641671979
          当前迭代次数:12000        误差:0.00233131261355
          当前迭代次数:13000        误差:0.00210685474327
          当前迭代次数:14000        误差:0.00192042332913
          当前迭代次数:15000        误差:0.00176323853379
          当前迭代次数:16000        误差:0.00162901015476
          当前迭代次数:17000        误差:0.00151311778557
          当前迭代次数:18000        误差:0.00141209385204
          当前迭代次数:19000        误差:0.00132328687338
          当前迭代次数:20000        误差:0.00124463585068
          当前迭代次数:21000        误差:0.00117451533242
          当前迭代次数:22000        误差:0.00111162667063
          当前迭代次数:23000        误差:0.00105492020129
          当前迭代次数:24000        误差:0.00100353857836
          预测结果:[[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
            0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
            1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]]
          实际结果:[[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
            0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
            1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]]
          预测准确率:100.0%

          2.3 关于随机初始化参数

          为什么要随机初始化 w 而不能直接都为0呢?假设在同一层有两个节点,他们的初始化的w都为0,那么他们的正向传递的结果会相同,反向传递的结果也相同,那么最终这两个节点的 w 会完全相同。

          那么这两个节点的作用实际上和一个节点没有什么区别,因此w是需要随机初始化的。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值