zoukankan      html  css  js  c++  java
  • 【零基础】深层神经网络解析

    回顾:

      【零基础】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"可获得完整代码。后面我们还会继续更新“传播函数、反向传播函数的具体含义”,以及各种激活函数的区别。

  • 相关阅读:
    费马小定理
    Big Number阶乘位数计算(斯特林公式)
    V
    矩阵快速幂求斐波那契
    奇迹
    缘分
    求导
    拓扑排序
    线段树
    单调栈
  • 原文地址:https://www.cnblogs.com/cation/p/11599435.html
Copyright © 2011-2022 走看看