深度學習基礎(CNN詳解以及訓練過程1)

深度學習是一個框架,包含多個重要算法: python

  • Convolutional Neural Networks(CNN)卷積神經網絡
  • AutoEncoder自動編碼器
  • Sparse Coding稀疏編碼
  • Restricted Boltzmann Machine(RBM)限制波爾茲曼機
  • Deep Belief Networks(DBN)深信度網絡
  • Recurrent neural Network(RNN)多層反饋循環神經網絡神經網絡

對於不一樣問題(圖像,語音,文本),須要選用不一樣網絡模型好比CNN RESNET等才能達到更好效果。git

今天來說最基礎的CNN網絡。算法

能夠不能夠模仿人類大腦的這個特色,構造多層的神經網絡,較低層的識別初級的圖像特徵,若干底層特徵組成更上一層特徵,最終經過多個層級的組合,最終在頂層作出分類呢?答案是確定的,這也是許多深度學習算法(包括CNN)的靈感來源。數組

CNN網絡介紹

卷積神經網絡是一種多層神經網絡,擅長處理圖像特別是大圖像的相關機器學習問題。網絡

卷積網絡經過一系列方法,成功將數據量龐大的圖像識別問題不斷降維,最終使其可以被訓練。CNN最先由Yann LeCun提出並應用在手寫字體識別上(MINST)。LeCun提出的網絡稱爲LeNet,其網絡結構以下:app

這是一個最典型的卷積網絡,由卷積層、池化層、全鏈接層組成。其中卷積層與池化層配合,組成多個卷積組,逐層提取特徵,最終經過若干個全鏈接層完成分類。框架

卷積層完成的操做,能夠認爲是受局部感覺野概念的啓發,而池化層,主要是爲了下降數據維度。dom

綜合起來講,CNN經過卷積來模擬特徵區分,而且經過卷積的權值共享及池化,來下降網絡參數的數量級,最後經過傳統神經網絡完成分類等任務。機器學習

 

下降參數量級

爲何要下降參數量級?從下面的例子就能夠很容易理解了。分佈式

若是咱們使用傳統神經網絡方式,對一張圖片進行分類,那麼,咱們把圖片的每一個像素都鏈接到隱藏層節點上,那麼對於一張1000x1000像素的圖片,若是咱們有1M隱藏層單元,那麼一共有10^12個參數,這顯然是不能接受的。(以下圖所示)

可是咱們在CNN裏,能夠大大減小參數個數,咱們基於如下兩個假設:

1)最底層特徵都是局部性的,也就是說,咱們用10x10這樣大小的過濾器就能表示邊緣等底層特徵

2)圖像上不一樣小片斷,以及不一樣圖像上的小片斷的特徵是相似的,也就是說,咱們能用一樣的一組分類器來描述各類各樣不一樣的圖像

基於以上兩個,假設,咱們就能把第一層網絡結構簡化以下:

咱們用100個10x10的小過濾器,就可以描述整幅圖片上的底層特徵。

 

卷積(Convolution)

卷積運算的定義以下圖所示:

如圖所示,咱們有一個5x5的圖像,咱們用一個3x3的卷積核:

1  0  1

0  1  0

1  0  1

來對圖像進行卷積操做(能夠理解爲有一個滑動窗口,把卷積核與對應的圖像像素作乘積而後求和),獲得了3x3的卷積結果。

這個過程咱們能夠理解爲咱們使用一個過濾器(卷積核)來過濾圖像的各個小區域,從而獲得這些小區域的特徵值。

在實際訓練過程當中,卷積核的值是在學習過程當中學到的。

在具體應用中,每每有多個卷積核,能夠認爲,每一個卷積核表明了一種圖像模式,若是某個圖像塊與此卷積核卷積出的值大,則認爲此圖像塊十分接近於此卷積核。若是咱們設計了6個卷積核,能夠理解:咱們認爲這個圖像上有6種底層紋理模式,也就是咱們用6中基礎模式就能描繪出一副圖像。如下就是24種不一樣的卷積核的示例:

 

池化(Pooling)

池化聽起來很高深,其實簡單的說就是下采樣。池化的過程以下圖所示:

上圖中,咱們能夠看到,原始圖片是20x20的,咱們對其進行下采樣,採樣窗口爲10x10,最終將其下采樣成爲一個2x2大小的特徵圖。

之因此這麼作的緣由,是由於即便作完了卷積,圖像仍然很大(由於卷積核比較小),因此爲了下降數據維度,就進行下采樣。

之因此能這麼作,是由於即便減小了許多數據,特徵的統計屬性仍可以描述圖像,並且因爲下降了數據維度,有效地避免了過擬合。

在實際應用中,池化根據下采樣的方法,分爲最大值下采樣(Max-Pooling)與平均值下采樣(Mean-Pooling)。

 全鏈接層(fully connected layers,FC)

      在整個卷積神經網絡中起到「分類器」的做用。若是說卷積層、池化層和激活函數層等操做是將原始數據映射到隱層特徵空間的話,全鏈接層則起到將學到的「分佈式特徵表示」映射到樣本標       記空間的做用。在實際使用中,全鏈接層可由卷積操做實現:對前層是全鏈接的全鏈接層能夠轉化爲卷積核爲1x1的卷積;而前層是卷積層的全鏈接層能夠轉化爲卷積核爲hxw的全局卷積,h和w分別爲前層卷積結果的高和寬。

全鏈接層的實現

 

LeNet介紹

下面再回到LeNet網絡結構:

這回咱們就比較好理解了,原始圖像進來之後,先進入一個卷積層C1,由6個5x5的卷積核組成,卷積出28x28的圖像,而後下采樣到14x14(S2)。

接下來,再進一個卷積層C3,由16個5x5的卷積核組成,以後再下采樣到5x5(S4)。

注意,這裏S2與C3的鏈接方式並非全鏈接,而是部分鏈接,以下圖所示:

其中行表明S2層的某個節點,列表明C3層的某個節點。

咱們能夠看出,C3-0跟S2-0,1,2鏈接,C3-1跟S2-1,2,3鏈接,後面依次類推,仔細觀察能夠發現,其實就是排列組合:

 

0 0 0 1 1 1

0 0 1 1 1 0

0 1 1 1 0 0

...

1 1 1 1 1 1

 

咱們能夠領悟做者的意圖,即用不一樣特徵的底層組合,能夠獲得進一步的高級特徵,例如:/ + \ = ^ (比較抽象O(∩_∩)O~),再好比好多個斜線段連成一個圓等等。

最後,經過全鏈接層C五、F6獲得10個輸出,對應10個數字的機率。

 

最後說一點我的的想法哈,我認爲第一個卷積層選6個卷積核是有緣由的,大概也許多是由於0~9其實能用如下6個邊緣來表明:

是否是有點道理呢,哈哈

而後C3層的數量選擇上面也說了,是從選3個開始的排列組合,因此也是能夠理解的。

其實這些都是針對特定問題的trick,如今更加通用的網絡的結構都會複雜得多,至於這些網絡的參數如何選擇,那就須要咱們好好學習了。

 

-----------------------------------------------------------------------------------------------------------------------

 

訓練過程

   卷積網絡在本質上是一種輸入到輸出的映射,它可以學習大量的輸入與輸出之間的映射關係,而不須要任何輸入和輸出之間的精確的數學表達式,只要用已知的模式對卷積網絡加以訓練,網絡就具備輸入輸出對之間的映射能力。卷積網絡執行的是有監督訓練,因此其樣本集是由形如:(輸入向量,理想輸出向量)的向量對構成的。全部這些向量對,都應該是來源於網絡即將模擬的系統的實際「運行」結果。它們能夠是從實際運行系統中採集來的。在開始訓練前,全部的權都應該用一些不一樣的小隨機數進行初始化。「小隨機數」用來保證網絡不會因權值過大而進入飽和狀態,從而致使訓練失敗;「不一樣」用來保證網絡能夠正常地學習。實際上,若是用相同的數去初始化權矩陣,則網絡無能力學習。

卷積神經網絡的訓練過程與傳統神經網絡相似,也是參照了反向傳播算法。

第一階段,向前傳播階段:

a)從樣本集中取一個樣本(X,Yp),將X輸入網絡;

b)計算相應的實際輸出Op

      在此階段,信息從輸入層通過逐級的變換,傳送到輸出層。這個過程也是網絡在完成訓練後正常運行時執行的過程。在此過程當中,網絡執行的是計算(實際上就是輸入與每層的權值矩陣相點乘,獲得最後的輸出結果):

          Op=Fn(…(F2(F1(XpW(1))W(2))…)W(n)

第二階段,向後傳播階段

a)算實際輸出Op與相應的理想輸出Yp的差;

b)按極小化偏差的方法反向傳播調整權矩陣。

以上內容摘自其餘博客,因爲我也沒有仔細瞭解這一塊,建議直接參考原博客

 

手寫數字分類的例子,基於tensorflow

引自:使用TensorFlow編寫識別數字的CNN訓練程序詳解

CNN的結構


從網上借用一張圖片來表示一下,是一個有2層hidden layer的CNN。

程序中設置的一些參數是: 
卷積層1:kernel_size [5, 5], stride=1, 4個卷積窗口 
卷積層2:kernel_size [5, 5], stride=1, 6個卷積窗口 
池化層: pool_size [2, 2], stride = 2 
全鏈接層1: 1024個特徵

MNIST數據的獲取


以往咱們獲取MINIST的方式是:

 

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

 

如今能夠: 

from tensorflow.contrib import learn
mnist = learn.datasets.load_dataset('mnist')

經過mnist.train, mnist.test, mnist.validation來得到3個數據集,每一個數據集裏面的方法有(已train爲例): 

  • train.images 圖片數據,二維數組 (55000, 784) dtype=float32 
  • train.labels 圖片的分類, 一維數組,每一個數值表示圖片對應的數字 
    array([7, 3, 4, …, 5, 6, 8], dtype=uint8)
  • train.num_examples 圖片數量 55000
  • train.next_batch 下一批數據 

    n = train.next_batch 
    n[0] 是images n[1]是labels 

 第一次load MNIST數據的時候,會自動從網上下載,放到當前目錄的MNIST-data目錄下

  1. 第一種加載方式,有一個one-hot參數,此時每一個樣本的label,返回的是一個長度10的vector,其中只有一個位置是1,其餘都是0。 第二種方式,沒有這個參數,若是須要的話,得直接調用datasets.mnist.read_data_sets

定義卷積層


在tf.contrib.layers裏面有convolution2d,conv2d等方法,其實都是convolution方法的別名

convolution(inputs, num_outputs, kernel_size, stride=1, padding='SAME', data_format=None, rate=1, activation_fn=nn.relu, normalizer_fn=None, normalizer_params=None, weights_initializer=initializers.xavier_initializer(), weights_regularizer=None, biases_initializer=init_ops.zeros_initializer, biases_regularizer=None, reuse=None, variables_collections=None, outputs_collections=None, trainable=True, scope=None) 

這個函數很強大,1到3維的卷積都支持。(我暫時只用過2維)

  • inputs: 輸入變量,是一個N+2維的Tensor

    • 類型要求是一個Tensor,而咱們通常訓練的數據都是常量(好比mnist,load之後獲得是python的數據類型,不是tf的),因此須要把用tf的方法作一下轉換,好比tf.reshape
    • 爲何是N+2維呢,好比圖像,除了寬度和高度,實際上還有樣本數量和通道數量(如RGB3通道),因此多了2維。
    • inputs的格式,由date_format這個參數來以爲,好比2維,有NHWC和NCHW兩種。N是樣本數量,H高度,W寬度,C通道數。
  • num_outputs: 卷積filter的數量,或者說提取的特徵數量,好比5,10

  • kernel_size: 卷積核的大小,是N個參數的list,好比二維圖像,能夠時候[10,10],若是參數值相同,用一個整數來表示也能夠;
  • stride: 卷積步長,一樣是N個參數的序列,或者都相等的話,用一個整數來表示,默認是1.
  • padding: 字符串格式,默認SAME,可選’VALID’。(想問:這兩個效果上有多大差別?)
  • data_format: 字符串,指定inputs的格式 
    • 一維數據:」NWC」 (default) and 「NCW」
    • 二維數據:」NHWC」 (default) and 「NCHW」
    • 三維數據:」NDHWC」
    • 也就是,不指定的話,通道數都是最後一個參數。
  • rate: a sequence of N positive integers specifying the dilation rate to use for a’trous convolution. Can be a single integer to specify the same value for all spatial dimensions. (暫時沒看到其做用)
  • activation_fn: 激活函數,默認relu
  • normalizer_fn: normalization function to use instead of biases.(沒用過,不知道起做用)
  • normalizer_params: normalization function parameters.
  • weights_initializer: 這不用說了,有默認值,估計用默認的就能夠了。
  • weights_regularizer: Optional regularizer for the weights.(沒明白爲何須要這個)
  • biases_initializer: 有默認值,通常也就不用指定。
  • biases_regularizer: …
  • reuse: whether or not the layer and its variables should be reused. To be able to reuse the layer scope must be given. 應該都須要reuse吧,因此這個參數默認爲True更好,如今是None。
  • variables_collections: 怎麼用暫時不太明白,但應該不用指定也能夠;
  • outputs_collections: 同上;
  • trainable: If True also add variables to the graph collection GraphKeys.TRAINABLE_VARIABLES,默認是True。 (這個是否是說在fit的時候須要設爲True,evaluate和predict的時候爲false?)
  • scope: 也便是variable_scope, 若是用多個卷積層的話,須要設置這個參數,以便把每一次的weight和bias區別出來。

咱們在對MNIST作卷積的時候,只要指定inputs, num_outputs, kernel_size, scope這幾個參數就能夠了,好比:

conv1 = tf.contrib.layers.conv2d(inputs, 4, [5, 5], 'conv_layer1')
#stride默認1,weights和biases也都是默認的

定義池化層


能夠用 tf.contrib.layers.max_pool2d或者tf.contrib.layers.avg_pool2d 

max_pool2d(inputs, kernel_size, stride=2, padding=’VALID’, data_format=DATA_FORMAT_NHWC, outputs_collections=None, scope=None)
  • inputs: 就是卷積的輸出了;
  • kernel_size: 是否是叫pool_size更貼切。[kernel_height, kernel_width]或者是一個整數;
  • stride: [stride_height, stride_width],不過文檔上說目前這兩個值必須同樣
  • padding: 這裏默認是VALID,和卷積默認不同,爲何要這樣呢?
  • data_format: 注意和卷積用的同樣哦;
  • outputs_collections: …
  • scope: pooling的時候沒有參數,須要scope嗎?
pool1 = tf.contrib.layers.max_pool2d(conv1, [2, 2], padding='SAME')

定義全鏈接層


tf.contrib.layers下有可用的全鏈接方法:

fully_connected(inputs, num_outputs, activation_fn=nn.relu, normalizer_fn=None, normalizer_params=None, weights_initializer=initializers.xavier_initializer(), weights_regularizer=None, biases_initializer=init_ops.zeros_initializer, biases_regularizer=None, reuse=None, variables_collections=None, outputs_collections=None, trainable=True, scope=None)

看這個函數,參數和卷積不少地方是同樣的, 咱們能夠這樣用:

fc = tf.contrib.layers.fully_connected(inputs, 1024, scope='fc_layer')

惟一須要注意的是這裏的inputs參數,通常是二維的形式[batch_size, depth],而前面卷積的結果,通常是[batch_size, height, width, channels]的形式,因此須要作一個flatten操做後再傳給fully_connected。

通常在fc以後還會作dropout,能夠用以下方法:

dropout(inputs, keep_prob=0.5, noise_shape=None, is_training=True, outputs_collections=None, scope=None)

參數的意義很明顯,其中is_training須要注意一下,在訓練的時候傳True,其餘狀況下傳False。 

dropout是指在深度學習網絡的訓練過程當中,對於神經網絡單元,按照必定的機率將其暫時從網絡中丟棄。注意是暫時,對於隨機梯度降低來講,因爲是隨機丟棄,故而每個mini-batch都在訓練不一樣的網絡。

dropout是CNN中防止過擬合提升效果的一個大殺器。

定義logits


全鏈接以後,通常就是用softmax作分類,而後定義loss,就能夠訓練了。可是看官方的例子,softmax前還加了一步,計算叫logits的東西,代碼裏面的說明是:

We don’t apply softmax here because 
tf.nn.softmax_cross_entropy_with_logits accepts the unscaled logits 
and performs the softmax internally for efficiency.

爲何要這樣暫時不太明白,可是依樣畫葫蘆,定義logtis自己很簡單,作一個線性變換,把FC的結果映射到分類的數量上:

def inference(x, num_class):
  with tf.variable_scope('softmax'):
    dtype = x.dtype.base_dtype
    # Set up the requested initialization.
    init_mean = 0.0
    init_stddev = 0.0
    weights = tf.get_variable('weights',
                                [x.get_shape()[1], num_class], initializer=init_ops.random_normal_initializer(init_mean, init_stddev, dtype=dtype), dtype=dtype)
    biases = tf.get_variable('bias', [num_class], initializer=init_ops.random_normal_initializer(init_mean, init_stddev, dtype=dtype), dtype=dtype)

    logits = tf.nn.xw_plus_b(x, weights, biases)
    return logits

定義loss


在tf.contrib.losses下有一些預約義的loss函數,好比直接用

softmax_cross_entropy(logits, onehot_labels, weights=_WEIGHT_SENTINEL, label_smoothing=0, scope=None)

注意這裏的label是onehot格式的, 咱們從mnist獲取的label要轉換成這個格式。

定義train_op


能夠用tf.contrib.layers.optimize_loss,經過傳遞不一樣的參數,就能夠調用不一樣的優化方法。

optimize_loss(loss,
              global_step,
              learning_rate,
              optimizer,
              gradient_noise_scale=None,
              gradient_multipliers=None,
              clip_gradients=None,
              learning_rate_decay_fn=None,
              update_ops=None,
              variables=None,
              name=None,
              summaries=None,
              colocate_gradients_with_ops=False):

預約義的optimizer有:

OPTIMIZER_CLS_NAMES = {
    "Adagrad": train.AdagradOptimizer,
    "Adam": train.AdamOptimizer,
    "Ftrl": train.FtrlOptimizer,
    "Momentum": train.MomentumOptimizer,
    "RMSProp": train.RMSPropOptimizer,
    "SGD": train.GradientDescentOptimizer,
}
或者這麼寫
train_op = tf.contrib.layers.optimize_loss(
            loss, tf.contrib.framework.get_global_step(), optimizer='Adagrad', learning_rate=0.1)

model和Estimator


結合上面的內容,就能夠定義出model, 從而用Estimator完成訓練,預測等功能,完整的程序以下:

import numpy as np

import sklearn.metrics as metrics
import tensorflow as tf
from PIL import Image
from tensorflow.contrib import learn
from tensorflow.contrib.learn import SKCompat
from tensorflow.contrib.learn.python.learn.estimators import model_fn as model_fn_lib
from tensorflow.python.ops import init_ops

IMAGE_SIZE = 28
LOG_DIR = './ops_logs'

mnist = learn.datasets.load_dataset('mnist')

def inference(x, num_class):
  with tf.variable_scope('softmax'):
    dtype = x.dtype.base_dtype
    init_mean = 0.0
    init_stddev = 0.0
    weight = tf.get_variable('weights',
                                [x.get_shape()[1], num_class], initializer=init_ops.random_normal_initializer(init_mean, init_stddev, dtype=dtype), dtype=dtype)
    biases = tf.get_variable('bias', [num_class], initializer=init_ops.random_normal_initializer(init_mean, init_stddev, dtype=dtype), dtype=dtype)

    logits = tf.nn.xw_plus_b(x, weight, biases)
    return logits

def model(features, labels, mode):
    if mode != model_fn_lib.ModeKeys.INFER:
        labels = tf.one_hot(labels, 10, 1, 0)
    else:
        labels = None

    inputs = tf.reshape(features, (-1, IMAGE_SIZE, IMAGE_SIZE, 1))

    #conv1
    conv1 = tf.contrib.layers.conv2d(inputs, 4, [5, 5], scope='conv_layer1', activation_fn=tf.nn.tanh);
    pool1 = tf.contrib.layers.max_pool2d(conv1, [2, 2], padding='SAME')
    #conv2
    conv2 = tf.contrib.layers.conv2d(pool1, 6, [5, 5], scope='conv_layer2', activation_fn=tf.nn.tanh);
    pool2 = tf.contrib.layers.max_pool2d(conv2, [2, 2], padding='SAME')
    pool2_shape = pool2.get_shape()
    pool2_in_flat = tf.reshape(pool2, [pool2_shape[0].value or -1, np.prod(pool2_shape[1:]).value])
    #fc
    fc1 = tf.contrib.layers.fully_connected(pool2_in_flat, 1024, scope='fc_layer1', activation_fn=tf.nn.tanh)
    #dropout
    is_training = False
    if mode == model_fn_lib.ModeKeys.TRAIN:
        is_training = True

    dropout = tf.contrib.layers.dropout(fc1, keep_prob=0.5, is_training=is_training, scope='dropout')

    logits = inference(dropout, 10)
    prediction = tf.nn.softmax(logits)
    if mode != model_fn_lib.ModeKeys.INFER:
        loss = tf.contrib.losses.softmax_cross_entropy(logits, labels)
        train_op = tf.contrib.layers.optimize_loss(
            loss, tf.contrib.framework.get_global_step(), optimizer='Adagrad',
            learning_rate=0.1)
    else:
        train_op = None
        loss = None

    return {'class': tf.argmax(prediction, 1), 'prob': prediction}, loss, train_op


classifier = SKCompat(learn.Estimator(model_fn=model, model_dir=LOG_DIR))

classifier.fit(mnist.train.images, mnist.train.labels, steps=1000, batch_size=300)

predictions = classifier.predict(mnist.test.images)
score = metrics.accuracy_score(mnist.test.labels, predictions['class'])
print('Accuracy: {0:f}'.format(score))
相關文章
相關標籤/搜索