卷積神經網絡cnn的實現

  • 2019 年 10 月 3 日
  • 筆記

卷積神經網絡

代碼:https://github.com/TimVerion/cat

卷積層

卷積層:通過在原始圖像上平移來提取特徵,每一個特徵就是一個特徵映射

原理:基於人腦的圖片識別過程,我們可以認為圖像的空間聯繫也是局部的像素聯繫比較緊密,而較遠的像素相關性比較弱,所以每個神經元沒有必要對全局圖像進行感知,只要對局部進行感知,而在更高層次對局部的信息進行綜合操作得出全局信息;即局部感知。

卷積分的知識

 

過程:

作用:

局部感知:在進行計算的時候,將圖片劃分為一個個的區域進行計算/考慮; 參數共享機制:假設每個神經元連接數據窗的權重是固定的 滑動窗口重疊:降低窗口與窗口之間的邊緣不平滑的特性。

不同的過濾器產生不同的效果:

 

真實做了什麼?

一步有一步的濃縮,產生更加靠譜更加準確的特徵

 

一張圖片卷積後高和寬如何變化?

# 卷積函數
def conv_fun(cache):
   x, w, b = cache["a"], cache["w"], cache["b"]
   pad, stride = cache["pad"], cache["stride"]
   N, C, H, W = x.shape
   F, C, HH, WW = w.shape
   # numpy提供的可以填充0的api,constant代表用一樣的值填充前兩維不填,後兩維各自填充pad行
   x_padded = np.pad(x, ((0, 0), (0, 0), (pad, pad), (pad, pad)), mode='constant')
   H_new = int((H + 2 * pad - HH) / stride) + 1
   W_new = int((W + 2 * pad - WW) / stride) + 1
   s = stride
   out = np.zeros((N, F, H_new, W_new))
   for i in range(N):  # ith image
       for f in range(F):  # fth filter
           for j in range(H_new):
               for k in range(W_new):
                   out[i, f, j, k] = np.sum
                  (x_padded[i, :, j * s:(HH + j * s), k * s:(WW + k * s)] * w[f]) +b[f]
   return out

 

池化層

池化層:通過特徵後稀疏參數來減少學習的參數,降低網絡的複雜度,(最大池化和平均池化)

# 前向池化
def max_pool_forward(cache):
  x, HH, WW, s = cache["net"], cache["HH"], cache["WW"], cache["s"]
  N, C, H, W = x.shape
  H_new = 1 + int((H - HH) / s)
  W_new = 1 + int((W - WW) / s)
  out = np.zeros((N, C, H_new, W_new))
  for i in range(N):
      for j in range(C):
          for k in range(H_new):
              for l in range(W_new):
                  # 定位到某個窗口
                  window = x[i, j, k * s:HH + k * s, l * s:WW + l * s]
                  # 找到該窗口的最大值,然後賦值
                  out[i, j, k, l] = np.max(window)
  return out

ReLU層

http://playground.tensorflow.org/

作用:增加網絡非線性的分割能力

# Relu函數
def Relu(x):
   return np.maximum(0, x)

全連接層

# 全連接
def fc(net, w, b):
   N = net.shape[0]
   # 把每個像素提取出來
   x_row = net.reshape(N, -1)
   out = np.dot(x_row, w) + b
   return out

CNN反向傳播的不同之處

首先要注意的是,一般神經網絡中每一層輸入輸出a,z都只是一個向量,而CNN中的a,z是一個三維張量,即由若干個輸入的子矩陣組成。其次:

  1. 池化層沒有激活函數。這個問題倒比較好解決,我們可以令池化層的激活函數為σ(z)=z,即激活後就是自己本身。這樣池化層激活函數的導數為1。

  2. 池化層在前向傳播的時候,對輸入進行了壓縮,那麼我們向前反向推導上一層的誤差時,需要做upsample處理

  3. 卷積層是通過張量卷積,或者說若干個矩陣卷積求和而得到當前層的輸出,這和一般的網絡直接進行矩陣乘法得到當前層的輸出不同。這樣在卷積層反向傳播的時候,上一層誤差的遞推計算方法肯定有所不同。

  4. 對於卷積層,由於W使用的運算是卷積,那麼由該層誤差推導出該層的所有卷積核的W,b的方式也不同。

池化層的反向傳播

這時候假如前向的時候是最大化池化:

這時候要用到前向傳播的時候最大值位置進行還原:

如果是平均:

 

卷積層的反向傳播

吳恩達筆記中的推導:

卷積神經網絡中的反向傳播(可選/非梯度)

在現代深度學習框架中,你只需要執行正向傳遞,這個框架負責向後傳遞,所以大多數深度學習工程師不會這樣做

需要麻煩的細節向後傳遞。卷積的向後傳遞網絡是複雜的。但是,如果您願意,您可以完成這個可選部分

來了解卷積網絡中的backprop是什麼樣子的。

在之前的課程中,你實現了一個簡單的(完全連接的)神經網絡,使用反向傳播來計算關於更新成本的導數

參數。類似地,在卷積神經網絡中你可以計算為了更新參數對代價求導。backprop方程不是平常那樣的,我們在課堂上沒有推導出來,但是我們簡單地介紹了一下:

Convolutional layer backward pass

讓我們從實現CONV層的向後傳遞開始。

 
  1. 計算dA:

這是計算dA對於一定的過濾器Wc的代價的公式以及一個給定的訓練例子:

 

其中Wc是一個過濾器,dZhw是上一層傳遞過來的梯度,每次我們都將相同的過濾器Wc乘以不同的dZ更新的。我們這樣做主要是因為當計算正向傳播時,每個過濾器都是由不同的a_slice點乘求和。因此,當計算dA的反向傳播的時候,我們也是把所有a_slice的梯度相加。

在代碼中,在合適的for循環中,這個公式可以轉化為:

da_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :] 
+=,W[:,:,:,c] * dZ[i, h, w, c]
  1. 計算dW: 這是計算dWc (dWc是一個過濾器的導數)關於損失的公式:

     

    其中aslice是原樣本的一個窗口。因此,這就得到了W關於這個窗口的梯度。這是相同的W,我們將所有這些梯度相加得到dW。

    在代碼中,在合適的for循環中,這個公式可以轉化為:

    dW[:,:,:,c] += a_slice * dZ[i, h, w, c]
  2. 計算db: 這是計算db對於一個過濾器Wc的代價的公式:

     

    正如您之前在基本神經網絡中看到的,db是通過求和dZ來計算的。在本例中只需對conv輸出(Z)的所有梯度求和。

    在代碼中,在合適的for循環中,這個公式可以轉化為:

    db[:,:,:,c] += dZ[i, h, w, c]

真實代碼:

def conv_backward(dout, cache):      x, w, b = cache["a"], cache["w"], cache["b"]      pad, stride = cache["pad"], cache["stride"]      F, C, HH, WW = w.shape      N, C, H, W = x.shape      H_new = 1 + int((H + 2 * pad - HH) / stride)      W_new = 1 + int((W + 2 * pad - WW) / stride)  ​      dx = np.zeros_like(x)      dw = np.zeros_like(w)      db = np.zeros_like(b)  ​      s = stride      x_padded = np.pad(x, ((0, 0), (0, 0), (pad, pad), (pad, pad)), 'constant')      dx_padded = np.pad(dx, ((0, 0), (0, 0), (pad, pad), (pad, pad)), 'constant')  ​      for i in range(N):  # ith image          for f in range(F):  # fth filter              for j in range(H_new):                  for k in range(W_new):                      window = x_padded[i, :, j * s:HH + j * s, k * s:WW + k * s]                      # db = dout //  dw=dout*x // dx = dout*w                      db[f] += dout[i, f, j, k]                      dw[f] += window * dout[i, f, j, k]                      dx_padded[i, :, j * s:HH + j * s, k * s:WW + k * s]                      += w[f] * dout[i, f, j, k]