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

回顾:

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

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

 

一、序言

  前面我们已经完成了单神经元、浅层神经网络(2层)的解析,其中有很多没有讲透的地方我们就暂时不要理会了,比如反向传播的原理我也没看明白呢。这里我们继续下一步,解析深层的神经网络(N层),也就是真的要“深度学习”了。

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

二、深层神经网络的构成

  我们再回顾一下前面做的工作,一个典型的神经元构成如下:

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

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

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

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

  在浅层神经网络中,其实主要也是实现了这四个函数,区别只是在输入与输出间多了一层计算。以反向传播函数为例:

  单神经元反向传播函数:

 

  具有两层的浅层神经网络:

 

   可以看到整体形式上并没有太大差别,而对于神经元层数更多的深层神经网络,其实大体上也是一样,只是我们需要用一个“循环”来处理一下可自定义的网络层数。

  对于深层神经网络,我们在构建模型时不限制其网络层数、神经元个数,只是在使用时根据需要写上网络结构即可。下面依然是直接上代码,文末附完整代码文件下载。

  这里我们依旧是处理前面“浅层神经网络”的问题“从图片中识别出偶数和奇数“。

三、随机初始化参数

#初始化所有层的w和b
def initialize_parameters(layer_dims):
  np.random.seed(1)
  wGroup = {}
  bGroup = {}
  layer = len(layer_dims)#神经网络总共有几层
  #逐层初始化w、b
  for i in range(1,layer): #i的值是1 2 3 到layer-1 的标号是从0开始的,其中layer_dims[0]是输入层
    wGroup[str(i)] = np.random.randn(layer_dims[i],layer_dims[i-1])/np.sqrt(layer_dims[i-1])
    bGroup[str(i)] = np.zeros((layer_dims[i],1))
  return wGroup,bGroup

  layer_dims包含了每层神经元个数,如[10,5,4,1]表示是一个三层的网络,第一层5个神经元、第二层4个神经元、第三层1个神经元。注意输入层是不算一层的,但是输入层的个数又直接影响了第一层w的初始化。

  返回值wGroup,bGroup包含了每一层初始化后的w和b

四、传播函数

#传播函数
def forward(img, wGroup, bGroup):
  #神经网络的实际层数与参数的层数一致
  layer = len(wGroup)
  #除第一层神经网络的输入是img,其他层的输入都是上一层的输出
  #除最后一层的激活函数是sigmoid,其他层的激活函数都是relu
  caches = []
  Y = []
  #前L-1层使用relu作为激活函数,最后一层使用sigmoid做激活函数
  for i in range(1,layer+1):
    if i == 1:
      IN = img
    else:
      IN = Y#IN 就是Y_prev
      w = wGroup[str(i)]
      b = bGroup[str(i)]
      A = np.dot(w, IN) + b
      #将变量数据保存起来便于后面反向计算
      caches.append((IN,w,b,A))
      #下一层的输入使用激活函数转化一下
      if i != layer:
        Y = relu(A)
      else:
        Y = sigmoid(A)

  return Y, caches

  这里,除第一层的输入是img之外,后面都是用激活函数转化后的Y值作为输入。其中最后一层使用sigmoid作为激活函数,其他层使用relu函数(又换了个激活函数)。

  这里的IN其实就是上一层的Y,caches里存储了IN:上一层的Y(其实就是本层的输入,第一层是img)、w:本层的权重、b:本层的偏置、A:激活前的乘积。将这些数据保存起来后面计算反向传播时用。最后还返回了最终的Y,其实就是神经网络最后的输出值。

五、反向传播函数

#反向传播
def backward(YLast,label,caches):
  layer = len(caches)
  label = label.reshape(YLast.shape)
  dWGroup = {}
  dbGroup = {}

  Y = YLast
  (Y_prev, W, b, A) = caches[-1]#取最后一个值
  m = Y_prev.shape[1]

  #直接计算最后一层dY
  dY = - (np.divide(label, Y) - np.divide(1 - label, 1 - Y+0.000001))#这里加上一个0.000001是为了防止1-Y=0
  #最后一层dA dW db
  dA = sigmoid_backward(dY, A)
  dW = np.dot(dA, Y_prev.T) / m
  db = np.sum(dA, axis=1, keepdims=True) / m
  dWGroup[str(layer)] = dW
  dbGroup[str(layer)] = db
  #上一层的dY
  dY_prev = np.dot(W.T, dA)
  for c in reversed(range(1,layer)):#若layer=4则C =3 2 1
    dY = dY_prev
    (Y_prev, W, b, A) = caches[c-1]#c-1 = 2 1 0,这里不好理解的是c是从1开始的,而caches是从0开始的
    m = Y_prev.shape[1]

    dA = relu_backward(dY, A)
    dW = np.dot(dA, Y_prev.T) / m
    db = np.sum(dA, axis=1, keepdims=True) / m
    #上一层的dA
    dY_prev = np.dot(W.T, dA)
    dWGroup[str(c)] = dW
    dbGroup[str(c)] = db
  return dWGroup,dbGroup

  这里的反向传播其实本质上跟浅层神经网络差不多,只是我们将最后一层的反向传播单独拿出来计算(因为激活函数不同),而且又涉及到历史参数获取(之前直接作为参数传)只是看起来复杂。你可以自己尝试结合前面浅层网络做一个指定层数(比如3层)的代码编写,只有动起手来才好理解。下面是我自己写的一段帮助理解的伪代码。另外需要注意的是,这里计算dA时分别针对relu和sigmoid函数是不一样的,所以分别有relu_backward和sigmoid_backward函数。

 

六、梯度下降(更新w、b)

#更新w、b参数
def update(wGroup, bGroup, dWGroup, dbGroup, learning_rate):
  L = len(wGroup)
  for i in range(1,L+1): #1-3
    wGroup[str(i)] = wGroup[str(i)] - learning_rate * dWGroup[str(i)]
    bGroup[str(i)] = bGroup[str(i)] - learning_rate * dbGroup[str(i)]

  return wGroup,bGroup

七、损失函数

#损失函数
def costCAL(Y, label):
  m = label.shape[1]
  cost = np.multiply(label,np.log(Y))+np.multiply(1-label, np.log(1-Y+0.000001))#这里加个很小的数是为了防止1-Y=0的情况
  cost = -np.sum(cost)/m
  cost = np.squeeze(cost)
  return cost

八、预测函数

#预测函数
def predict(img,wGroup,bGroup):
  m = img.shape[1]
  L = len(wGroup)
  p = np.zeros((1,m))
  #向前传播做预测
  probas,caches = forward(img, wGroup, bGroup)
  # 将预测结果转化成0和1的形式,即大于0.5的就是1,否则就是0
  for i in range(0, probas.shape[1]):
    if probas[0,i] > 0.5:
    p[0,i] = 1
  else:
    p[0,i] = 0
  return p

九、训练模型并预测

#组成训练model
def model(img, label, layers_dims, learning_rate=0.0075, num_iterations=3000, print_cost=False):
  np.random.seed(1)
  costs = []
  wGroup,bGroup = initialize_parameters(layers_dims)
  #训练若干次数
  for i in range(0, num_iterations):
    #向前传播
    Y,caches = forward(img, wGroup, bGroup)
    # 计算成本
    cost = costCAL(Y, label)
    # 进行反向传播
    dWGroup,dbGroup = backward(Y,label,caches)
    # 更新参数,好用这些参数进行下一轮的前向传播
    wGroup,bGroup = update(wGroup, bGroup, dWGroup, dbGroup, learning_rate)


    # 打印出成本
    if i % 100 == 0:
      if print_cost and i > 0:
        print ("训练%i次后成本是: %f" % (i, cost))
  return wGroup,bGroup

#训练并预测

layers_dims = [784, 20, 7, 5, 1]
wGroup,bGroup = model(train_img, train_label, layers_dims,learning_rate=0.1, num_iterations=2000, print_cost=True)

# 对训练数据集进行预测
pred_train = predict(train_img,wGroup,bGroup)
print("预测准确率是: " + str(np.sum((pred_train == train_label) / train_img.shape[1])))

# 对测试数据集进行预测
pred_test = predict(test_img,wGroup,bGroup)
print("预测准确率是: " + str(np.sum((pred_test == test_label) / test_img.shape[1])))

运行最后结果:

十、总结回顾

  预测准确率较之前浅层神经网络又有了大幅的提升(之前是94%)。

  其实从浅层神经网络开始,大部分都只是贴上代码了,有点”只可意会不可言传“的感觉,其实整体框架一直都没变,所以也不知道有啥可说的。后面我会继续花时间搞明白传播函数、反向传播函数的具体原理,到时再写深度解析的文章。

  关注公众号“零基础爱学习”回复"AI6"可获得完整代码。后面我们还会继续更新“传播函数、反向传播函数的具体含义”,以及各种激活函数的区别。

 

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