1. 程式人生 > >深度學習之卷積神經網路原理詳解(一)

深度學習之卷積神經網路原理詳解(一)

初探CNN卷積神經網路

1、概述

  • 典型的深度學習模型就是很深層的神經網路,包含多個隱含層,多隱層的神經網路很難直接使用BP演算法進行直接訓練,因為反向傳播誤差時往往會發散,很難收斂
  • CNN節省訓練開銷的方式是權共享weight sharing,讓一組神經元使用相同的權值
  • 主要用於影象識別領域

2、卷積(Convolution)特徵提取

  • 卷積核(Convolution Kernel),也叫過濾器filter,由對應的權值W和偏置b體現
  • 下圖是3x3的卷積核在5x5的影象上做卷積的過程,就是矩陣做點乘之後的和
    這裡寫圖片描述
    i個隱含單元的輸入就是:$${W_{\rm{i}}}{x_{small}} + {b_i}$$,其中$${x_{small}}$$就是與過濾器filter過濾到的圖片
  • 另外上圖的步長stride1,就是每個filter每次移動的距離
  • 卷積特徵提取的原理

    • 卷積特徵提取利用了自然影象的統計平穩性,這一部分學習的特徵也能用在另一部分上,所以對於這個影象上的所有位置,我們都能使用同樣的學習特徵。
    • 當有多個filter時,我們就可以學到多個特徵,例如:輪廓、顏色等
  • 多個過濾器filter(卷積核)

    • 例子如下

這裡寫圖片描述
- 一張圖片有RGB三個顏色通道,則對應的filter過濾器也是三維的,影象經過每個filter做卷積運算後都會得到對應提取特徵的影象,途中兩個filter:W0和W1,輸出的就是兩個影象
- 這裡的步長stride2(一般就取2,3)
- 在原圖上新增zero-padding

,它是超引數,主要用於控制輸出的大小
- 同樣也是做卷積操作,以下圖的一步卷積操作為例:
與w0[:,:,0]卷積:0x(-1)+0x0+0x1+0x1+0x0+1x(-1)+1x0+1x(-1)+2x0=-2
與w0[:,:,1]卷積:2x1+1x(-1)+1x1=2
與w0[:,:,2]卷積:1x(-1)+1x(-1)=-2
最終結果:-2+2+(-2)+1=-1 (1為偏置)
這裡寫圖片描述

3、池化(Pooling)

  • 也叫做下采樣
  • Pooling過程

    • 把提取之後的特徵看做一個矩陣,並在這個矩陣上劃分出幾個不重合的區域,
    • 然後在每個區域上計算該區域內特徵的均值最大值,然後用這些均值或最大值參與後續的訓練
      這裡寫圖片描述

    -下圖是使用最大Pooling的方法之後的結果
    這裡寫圖片描述

  • Pooling的好處
    • 很明顯就是減少引數
    • Pooling就有平移不變性((translation invariant)
      如圖feature map12x12大小的圖片,Pooling區域為6x6,所以池化後得到的feature map2x2,假設白色畫素值為1,灰色畫素值為0,若採用max pooling之後,左上角視窗值為**
      這裡寫圖片描述
      將影象右移一個畫素,左上角視窗值仍然為1
      這裡寫圖片描述
      將影象縮放之後,左上角視窗值仍然為1
      這裡寫圖片描述
  • Pooling的方法中average方法對背景保留更好,max對紋理提取更好
  • 深度學習可以進行多次卷積、池化操作

4、啟用層

  • 在每次卷積操作之後一般都會經過一個非線性層,也是啟用層
  • 現在一般選擇是ReLu,層次越深,相對於其他的函式效果較好,還有Sigmod,tanh函式等

這裡寫圖片描述
- sigmodtanh都存在飽和的問題,如上圖所示,當x軸上的值較大時,對應的梯度幾乎為0,若是利用BP反向傳播演算法, 可能造成梯度消失的情況,也就學不到東西了

5、全連線層 Fully connected layer

  • 將多次卷積和池化後的影象展開進行全連線,如下圖所示。
    這裡寫圖片描述
  • 接下來就可以通過BP反向傳播進行訓練了
  • 所以總結起來,結構可以是這樣的

這裡寫圖片描述

6、CNN是如何工作的

  • 每個過濾器可以被看成是特徵識別符號( feature identifiers)
  • 如下圖一個曲線檢測器對應的值
    這裡寫圖片描述
  • 我們有一張圖片,當過濾器移動到左上角時,進行卷積運算

這裡寫圖片描述
- 當與我們的過濾器的形狀很相似時,得到的值會很大
這裡寫圖片描述
- 若是滑動到其他的部分,可以看出很不一樣,對應的值就會很小,然後進行啟用層的對映。
這裡寫圖片描述
- 過濾器filter的值怎麼求到,就是我們通過BP訓練得到的。

CNN的Tensorflow實現

import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data     # 匯入mnist資料集


'''計算準確度函式'''
def compute_accuracy(xs,ys,X,y,keep_prob,sess,prediction):
    y_pre = sess.run(prediction,feed_dict={xs:X,keep_prob:1.0})       # 預測,這裡的keep_prob是dropout時用的,防止過擬合
    correct_prediction = tf.equal(tf.argmax(y_pre,1),tf.argmax(y,1))  #tf.argmax 給出某個tensor物件在某一維上的其資料最大值所在的索引值,即為對應的數字,tf.equal 來檢測我們的預測是否真實標籤匹配
    accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32)) # 平均值即為準確度
    result = sess.run(accuracy,feed_dict={xs:X,ys:y,keep_prob:1.0})
    return result  

'''權重初始化函式'''
def weight_variable(shape):
    inital = tf.truncated_normal(shape, stddev=0.1)  # 使用truncated_normal進行初始化
    return tf.Variable(inital)

'''偏置初始化函式'''
def bias_variable(shape):
    inital = tf.constant(0.1,shape=shape)  # 偏置定義為常量
    return tf.Variable(inital)

'''卷積函式'''
def conv2d(x,W):#x是圖片的所有引數,W是此卷積層的權重
    return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding='SAME')#strides[0]和strides[3]的兩個1是預設值,中間兩個1代表padding時在x方向運動1步,y方向運動1步

'''池化函式'''
def max_pool_2x2(x):
    return tf.nn.max_pool(x,ksize=[1,2,2,1],
                          strides=[1,2,2,1],
                          padding='SAME')#池化的核函式大小為2x2,因此ksize=[1,2,2,1],步長為2,因此strides=[1,2,2,1]
'''執行主函式'''
def cnn():
    mnist = input_data.read_data_sets('MNIST_data', one_hot=True)  # 下載資料

    xs = tf.placeholder(tf.float32,[None,784])  # 輸入圖片的大小,28x28=784
    ys = tf.placeholder(tf.float32,[None,10])   # 輸出0-9共10個數字
    keep_prob = tf.placeholder(tf.float32)      # 用於接收dropout操作的值,dropout為了防止過擬合
    x_image = tf.reshape(xs,[-1,28,28,1])       #-1代表先不考慮輸入的圖片例子多少這個維度,後面的1是channel的數量,因為我們輸入的圖片是黑白的,因此channel是1,例如如果是RGB影象,那麼channel就是3

    '''第一層卷積,池化'''
    W_conv1 = weight_variable([5,5,1,32])  # 卷積核定義為5x5,1是輸入的通道數目,32是輸出的通道數目
    b_conv1 = bias_variable([32])          # 每個輸出通道對應一個偏置
    h_conv1 = tf.nn.relu(conv2d(x_image,W_conv1)+b_conv1) # 卷積運算,並使用ReLu啟用函式啟用
    h_pool1 = max_pool_2x2(h_conv1)        # pooling操作 
    '''第二層卷積,池化'''
    W_conv2 = weight_variable([5,5,32,64]) # 卷積核還是5x5,32個輸入通道,64個輸出通道
    b_conv2 = bias_variable([64])          # 與輸出通道一致
    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2)+b_conv2)
    h_pool2 = max_pool_2x2(h_conv2)

    '''全連線層'''
    h_pool2_flat = tf.reshape(h_pool2, [-1,7*7*64])   # 將最後操作的資料展開
    W_fc1 = weight_variable([7*7*64,1024])            # 下面就是定義一般神經網路的操作了,繼續擴大為1024
    b_fc1 = bias_variable([1024])                     # 對應的偏置
    h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat,W_fc1)+b_fc1)  # 運算、啟用(這裡不是卷積運算了,就是對應相乘)
    '''dropout'''
    h_fc1_drop = tf.nn.dropout(h_fc1,keep_prob)       # dropout操作
    '''最後一層全連線'''
    W_fc2 = weight_variable([1024,10])                # 最後一層權重初始化
    b_fc2 = bias_variable([10])                       # 對應偏置

    prediction = tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2)+b_fc2)  # 使用softmax分類器
    cross_entropy = tf.reduce_mean(-tf.reduce_sum(ys*tf.log(prediction),reduction_indices=[1]))  # 交叉熵損失函式來定義cost function
    train_step = tf.train.AdamOptimizer(1e-3).minimize(cross_entropy)  # 呼叫梯度下降

    '''下面就是tf的一般操作,定義Session,初始化所有變數,placeholder傳入值訓練'''
    sess = tf.Session()
    sess.run(tf.initialize_all_variables())

    for i in range(1000):
        batch_xs, batch_ys = mnist.train.next_batch(100)  # 使用SGD,每次選取100個數據訓練
        sess.run(train_step, feed_dict={xs: batch_xs, ys: batch_ys, keep_prob: 0.5})  # dropout值定義為0.5
        if i % 50 == 0:
            print(compute_accuracy(xs,ys,mnist.test.images, mnist.test.labels,keep_prob,sess,prediction))  # 每50次輸出一下準確度


if __name__ == '__main__':
    cnn()
參考文章