【零基础】浅层神经网络解析

回顾:

【零基础】AI神经元解析(含实例代码)

 

一、序言

  前两天写了关于单神经元的解析,这里再接再厉继续浅层神经网络的解析。浅层神经网络即是“层次较少”的神经网络,虽然层次少但其性能相对单神经元强大了不只一点。

  注:本文内容主要是对“床长”的系列教程进行总结,强烈推荐“床长”的人工智能系列教程(https://www.captainbed.net/)

二、浅层神经网络的构成

  回顾前面单神经元的构成,我们知道神经元包含4个关键函数:

  1)传播函数,由输入x、偏置w、阈值b计算出a

  2)激活函数,将a映射到0~1之间的结果y,可理解为(是、否)的概率

  3)反向传播函数,通过y、答案label计算出dw、db(用以更新w和b)

  4)损失函数,计算y与label间的误差

 

   直观上我们知道浅层神经网络自然是由数个神经元构成的,对于一个简单的两层神经网络其结构如下图所示:

 

  它包含了输入层(即X)、隐藏层、输出层,其中输入层不是神经元,所以说这是一个两层的神经网络。

  在实际实现时我们并不是挨个计算每一个经元的结果,最后再计算输出的结果。我们是一次计算出一层所有的神经元结果,再将每一层的结果作为输入计算下一层。而且第一层的传播函数并不与第二层传播函数分离,神经网络的传播函数包含了所有层的传播计算,反向传播函数也是包含了所有层的反向计算,所以从单神经元到神经网络代码的结构其实变化不大。

  下面我们直接上代码来解析,如果你看明白了前面单神经元的解析,那这里是非常好理解的。

  (文末附完整代码下载方式)

三、准备工作

  1)要处理的问题

  之前我们用单神经元要处理的问题是“从图片中识别出数字9”,即使使用单神经元也有93%的正确率,所以这里我们要增加问题的难度。将问题改为“从图片中识别出奇数”,代码上只需要很小的修改,将下面代码

  #将label不是9的数据全部转为0,将9转为1
  train_label = np.where(train_label==9,1,0)
  test_label = np.where(test_label==9,1,0)

  修改为:

  #找出图片中的奇数将label中13579置为1、02468置为0
  train_label = np.where((train_label%2)!=0,1,0)
  test_label = np.where((test_label%2)!=0,1,0)

  使用之前的单神经元代码执行后的结果为下图所示,可以看到预测效果大幅下降。

  2)要使用的网络结构

  首先要明确神经网络的层数,这里我们是一个简单的网络,所以只需要两层。其次是每一层神经元的个数,这里我们网络第一层设置4个神经元,第二层是输出层所以只要一个神经元。输入层跟以前一样,是784个输入(一张28x28图片的所有像素),网络结构如下图:

四、随机初始化参数

#初始化参数w和b
def initialize_parameters(input_num, hide_num, out_num):
  #input_num 输入层神经元个数
  #hide_num 隐藏层神经元个数
  #out_num 输出层神经元个数
  np.random.seed(2)
  #随机初始化第一层相关参数w、b
  W1 = np.random.rand(hide_num, input_num) * 0.01
  b1 = np.zeros(shape=(hide_num, 1))

  #随机初始化第二层相关参数w、b
  W2 = np.random.randn(out_num, hide_num) * 0.01
  b2 = np.zeros(shape=(out_num, 1))

  return W1,b1,W2,b2

   浅层神经网络与单神经元在初始化参数w、b的区别有以下几点:

  1)每一层神经元使用不同的w和b,所以有w1、w2、b1、b2,如果是三层网络则相应会有w3、b3.

  2)不同层神经元的w、b的数据形状是不一样的。比如我们输入的图片有784个像素,且第二层有4个神经元,所以第一层w的形状是(4,784)、b的形状是(4,1)。第二层神经元只有一个,来自第一层的输入只有4个参数,所以第二层w的形状是(1,4)、b的形状是(1,1)。

  3)w1和w2是随机生成的,之前单神经元结构中,w初始值为0,如果这里还使用全0的话,最终结果可能与单神经元一样,而且还乘以0.01确保初始值足够小。

五、传播函数

#向前传播函数
def forward(img, W1,b1,W2,b2):

  #第一层
  A1 = np.dot(W1, img) + b1
  Y1 = np.tanh(A1)#第一层和第二层使用不同的激活函数

  #第二层
  A2 = np.dot(W2, Y1) + b2
  Y2 = sigmoid(A2)
  return Y1,Y2

  传播函数与单神经元结构类似,不过需要注意的是,这里第一层使用的激活函数为tanh、第二层依旧使用的是sigmoid。他们的区别在于,sigmoid是将输出映射到0~1而tanh是将输出映射到-1~1。具体原因和区别以后再说(因为我也没搞清楚呢),这里只要知道有区别就行了。

 

 六、反向传播函数

#反向传播函数
def backward(img, label, W1,b1,W2,b2, Y1,Y2):
  m = img.shape[1]
  #第二层
  dZ2 = Y2 - label
  dW2 = np.dot(dZ2, Y1.T)/m
  db2 = np.sum(dZ2, axis=1, keepdims=True)/m
  #第一层
  dZ1 = np.multiply(np.dot(W2.T, dZ2), 1-np.power(Y1, 2))
  dW1 = np.dot(dZ1, img.T)/m
  db1 = np.sum(dZ1, axis=1, keepdims=True)/m

  return dW1,db1,dW2,db2

  与传播函数方向相反,这里是先计算第二层的反向传播再计算第一层的反向传播。需要注意的是,由于第一层使用的激活函数是tanh,所以其反向计算公式与第一层的公式不一样,因为使用不同激活函数其反向求导也就不一样了。

  另外np.sum中的两个参数axis=1、keepdims=True是为了确保db1的数据形式为(1,4),其中axis=1的意思是按行求和、keepdims=True的意思是保留矩阵的形状,不同参数的np.sum计算示意如下:

  np.sum(dZ1)/m                 0.0016664927232987162

  np.sum(dZ1, axis=1)/m            [0.00026393,0.00077922,0.0003274 ,0.00029595]

  np.sum(dZ1, axis=1, keepdims=True)/m   

                        [[0.00026393]
                        [0.00077922]
                        [0.0003274 ]
                        [0.00029595]]

七、梯度下降

#梯度下降 更新w、b参数
def update(W1,b1,W2,b2, dW1,db1,dW2,db2, learning_rate=1.2):
  W1 = W1 - learning_rate*dW1
  b1 = b1 - learning_rate*db1
  W2 = W2 - learning_rate*dW2
  b2 = b2 - learning_rate*db2

  return W1,b1,W2,b2

  梯度下降与单神经元的情况差不多。

八、损失函数

#损失函数
def costCal(Y2, label):
  m = label.shape[1]
  logprobs = np.multiply(np.log(Y2), label) + np.multiply((1-label), np.log(1-Y2))
  cost = -np.sum(logprobs)/m
  return cost

  损失函数与单神经元的情况也差不多,需要注意的是np.multiply就是将两个矩阵做对应元素的乘。

九、预测函数

#预测函数
def predict(W1,b1,W2,b2, img):
  Y1,Y2 = forward(img, W1,b1,W2,b2)
  predictions = np.round(Y2)#对结果四舍五入
  return predictions

  与单神经元的情况类似,预测函数其实就是做一次“向前传播”。

十、训练模型并预测

#训练模型
def model(img, label, hide_num, num_iterations = 1000, learning_rate=0.1, print_cost = False):
  np.random.seed(3)
  input_num = img.shape[0]
  out_num = label.shape[0]

  #初始化参数
  W1,b1,W2,b2 = initialize_parameters(input_num,hide_num,out_num)
  #循环若干次完成训练
  for i in range(0, num_iterations):
    #向前传播
    Y1,Y2 = forward(img, W1,b1,W2,b2)
    #计算本次成本
    cost = costCal(Y2, label)
    #反向传播,得到梯度
    dW1,db1,dW2,db2 = backward(img, label, W1,b1,W2,b2, Y1,Y2)
    #参数优化
    W1,b1,W2,b2 = update(W1,b1,W2,b2, dW1,db1,dW2,db2, learning_rate)
    # 将本次训练的成本打印出来
    if print_cost and i % 100 == 0:
    print ("在训练%i次后,成本是: %f" % (i, cost))

  return W1,b1,W2,b2

#调用训练模型

W1,b1,W2,b2 = model(train_img, train_label, 4, num_iterations=2000, learning_rate=1, print_cost=True)

#调用预测函数

predictions = predict(W1,b1,W2,b2, test_img)
print ('预测准确率是: %d' % float((np.dot(test_label, predictions.T) + np.dot(1 - test_label, 1 - predictions.T)) / float(test_label.size) * 100) + '%')

  需要注意的是这里的learning_rate=1,而单神经元时的learning_rate为0.005。

十一、总结回顾

  通过实现一个简单的二层神经网络我们发现,其实代码并没有修改很多,整体的结构也变化不大,其中最主要的变化在于第一层使用的激活函数变为tanh,由此导致反向传播的计算也有了较大的变化。

  运行后我们可以发现,预测的准确度较单神经元有了较大幅度的提升:

在训练0次后,成本是: 0.693817
在训练100次后,成本是: 0.251725
在训练200次后,成本是: 0.176756
在训练300次后,成本是: 0.110538
在训练400次后,成本是: 0.372297
在训练500次后,成本是: 0.128188
在训练600次后,成本是: 0.091792
在训练700次后,成本是: 0.075769
在训练800次后,成本是: 0.064764
在训练900次后,成本是: 0.055826
在训练1000次后,成本是: 0.132452
在训练1100次后,成本是: 0.102556
在训练1200次后,成本是: 0.131425
在训练1300次后,成本是: 0.086445
在训练1400次后,成本是: 0.178343
在训练1500次后,成本是: 0.077496
在训练1600次后,成本是: 0.093846
在训练1700次后,成本是: 0.071567
在训练1800次后,成本是: 0.070109
在训练1900次后,成本是: 0.060202
预测准确率是: 94%

  关注公众号“零基础爱学习”回复"AI5"可获得完整代码。后面我们还会继续更新“如何构建深度神经网络”,以及对目前还未明晰的问题解析。

posted @ 2019-09-23 21:23  布兰姥爷  阅读(3780)  评论(0编辑  收藏  举报