從鍋爐工到AI專家(6)

欠擬合和過擬合

幾乎全部的複雜方程都存在結果跟預期差別的狀況,越複雜的方程,這種狀況就越嚴重。這裏面一般都是算法形成的,固然也存在數據集的個體差別問題。
因此」欠擬合「和」過擬合「是機器學習過程當中重要的調優指標之一。
如圖所示:

以篇(2)中房價的程序爲例,上圖中間的那幅圖,是比較滿意的一種結果。對於咱們給出的全部樣本,模型的預測結果同實際房價比較貼切的「擬合」。
左圖則是「欠擬合」,有些樣本和房價能對應的比較好,有些預測出來的值同事實則差距較大,「預測不大準」。
右側圖是「過擬合」,過擬合是很尷尬的一種狀況,對於全部的訓練樣本都擬合很是好,但投產實際的數據就差異巨大。過擬合也很嚴重,在不少角度上比欠擬合更嚴重。若是在之前手工編寫算法的年代,出現這種狀況,模型的調優將會很是麻煩。而且在一個研發週期比較長的項目中,由於每每是到了投產階段才發現過擬合問題,形成的損失也會比較大。
在分類問題中,同樣會出現這兩個問題,如圖:

一樣,中間的圖表示擬合較好。左側圖表示欠擬合,右側圖表示過擬合。
欠擬合和過擬合狀況發生以後,傳統的辦法有如下幾種手段解決:python

  1. 減小咱們的參數數量,下降方程的維度。這個方法對於圖像識別這種狀況顯然不適用,由於維度是固定的。
  2. 樣本自己歸一化作的很差。樣本歸一化咱們前面說了,正常狀況都應當先作歸一化再進行訓練。
  3. 一般增長樣本的數量對改善「欠擬合」和「過擬合」都有效果。可是在工程中,樣本就是錢啊。
  4. 手工實現的算法中,能夠添加歸一化參數(Regularization Parameter),一般稱爲λ。在TensorFlow這種成熟框架中,則使用了Dropout機制。

Dropout

Dropout能夠當作神經網絡中的一層,串聯於神經網絡中。接收上一層的輸入,根據參數值拋棄一部分,把輸出再接入到下一層輸入,後面仍是原來的神經網絡。
這種方法對於總體算法幾乎沒有改變,代碼變更最小,效果優秀。具體實現的數學公式在下面參考連接中有論文可供研究。TensorFlow中則已經有了內置的函數。
拋棄率也是一個學問,有論文表示,對於隱藏層來說,50%的拋棄率有最優的效果,因此一般這部分就不用動腦子了,直接用0.5做爲參數調用就好,不過我實際實踐中,大多仍是要嘗試不一樣值看看效果。
最後同歸一化參數λ的使用同樣,在訓練時,啓用Dropout機制,也就是使用50%保留率(固然也是50%的拋棄率)調用tf.nn.dropout()。
等到實際生產的時候,由於模型用於真正預測,則無需再使用dropout。大多狀況下咱們訓練的模型跟生產的模型,一般是同一個,這時候能夠用100%保留率爲參數調用dropout,等於全部數據都會輸出,也就等於取消dropout層的效果。
注意,雖然函數名叫dropout,其中的參數確是表示數據保留下來的比例(固然不是簡單的保留的意思,請參考後面的例子),其他的數據會用0替代。看一個小程序來驗證dropout的效果:算法

#!/usr/bin/env python
# -*- coding=UTF-8 -*-

import tensorflow as tf

dropout = tf.placeholder(tf.float32)
x = tf.Variable(tf.ones([10, 10]))
y = tf.nn.dropout(x, dropout)

init = tf.initialize_all_variables()
sess = tf.Session()
sess.run(init)

print sess.run(y, feed_dict = {dropout: 1})
print sess.run(y, feed_dict = {dropout: 0.1})
print sess.run(y, feed_dict = {dropout: 0.2})

上面例子分別使用參數1/0.1/0.2調用dropout,運行輸出將是:小程序

[[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]

[[ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0. 10.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [10.  0.  0.  0.  0. 10.  0.  0.  0.  0.]
 [ 0.  0. 10.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0. 10.  0.  0.  0.]
 [ 0. 10.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0. 10.  0.  0. 10.  0.  0.  0.  0.  0.]]

[[0. 0. 0. 0. 0. 0. 5. 0. 0. 0.]
 [0. 0. 5. 5. 0. 0. 5. 5. 0. 5.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 5. 0.]
 [0. 0. 5. 0. 5. 0. 0. 5. 5. 0.]
 [0. 0. 0. 5. 0. 0. 0. 0. 0. 0.]
 [5. 0. 0. 0. 0. 0. 0. 0. 0. 5.]
 [0. 5. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 5. 0. 0. 0. 0. 5. 0.]
 [5. 0. 0. 5. 0. 0. 0. 0. 0. 0.]]

dropout並非簡單的拋棄,每一個保留下來的結果,實際上彙總了其它被設置爲0的值。但願你對dropout完全理解了。數組

卷積

一般講,一項新技術的引入,每每是一般採用的技術發現了問題。爲了解決這個問題經歷大量研究和嘗試以後,誕生了新的技術來對原有技術進行改進。卷積偏偏就是如此。
將DNN用於圖像識別,識別率仍然不夠高是表象。
一個很容易想到的根源則是一副二維的圖片,變成了一維的浮點數組,其中點與相鄰點之間的關係,本應當是這樣圖像識別中「圖形」的部分理應關注的重點,而在DNN中反而徹底把這種關係信息拋棄了。
爲了改善這種狀況,很早就曾經出現過名爲「Edge filter」的算法,在神經網絡的中間尋找這種可能出現的「邊緣」的濾鏡層,這其實就是卷積的雛形。
所採用的原理,你能夠理解成用一副放大鏡近距離、細緻的觀察一幅畫面的局部,注意是二維的畫面,不是一維的數組。
由於是局部,因此這個「卷積」核的維度不會很大,至少要遠遠小於整個要識別的圖像。具體採用多大的尺寸,取決於你關注的那些邊緣、或者筆畫、或者微小的圖形,是什麼樣的尺寸。在本案例中,將會採用5x5的卷積尺寸。
同時應當很容易理解,用放大鏡觀看圖像嘛,看到的局部,雖然是一副小圖像,但色深等特徵,跟原圖是如出一轍的。因此卷積的深度,同原圖必然是相同的,本例中是灰度圖,1個數據的深度。若是是RGB彩色圖,則是3個數據的深度。

這個用於近距離、細緻觀察局部的小窗口,叫作「卷積核」,也有被稱爲「觀察視野」的,一樣都應當是轉折的外來詞,你理解含義,閱讀文檔的時候能看懂就行了。
「觀察」這個小窗口所獲得的信息,由於附加了邊緣、筆畫、圖形等更精細的信息,這些信息顯然不是原來的點陣圖的概念。因此雖然這個窗口小了,但輸出結果,必然要遠遠大於原有的深度。好比在本例中,咱們使用5x5的窗口。而卷積輸出的數據,原有圖形僅是1維,這裏將達到32維(這個值是咱們根據本案例的需求,自行設置的)的深度。
這一點必定要注意,國內不少的譯文中,不知道是哪一個環節出的問題,竟然不少人將卷積理解爲「降維」的算法。這顯然是沒有真正理解卷積的內涵,而且把深度這麼多倍的增加忽略掉了。
如同放大鏡的邊緣會稍有變形,卷積算法重點關注在卷積核中心,周圍的數據也會有細微變形,這是由算法的數學模型決定的。

此外也如同放大鏡在畫面上一點點移動,逐漸觀察整個畫面同樣,「觀察窗」也將在屏幕上滾動,最終覆蓋整個要識別的圖像。這個滾動過程會有移動的步長設置,對於一些重點稀疏的圖像,咱們可能會增長步長來減小數據及提升效率。
由於上面這兩個緣由,卷積最終所得圖像的輸出尺寸,是可能小於原圖像長寬尺寸的。這可能也是一部分人理解爲降維的緣由之一。但算上增長的深度,一般輸出的數據,會幾十倍於原有的輸入數據量。
在本案例中,咱們設置TensorFlow卷積的步長爲1,padding參數爲「SAME」,表示輸出圖像的尺寸跟原圖徹底相同。padding參數還可能取值爲「VALID」,表示僅保留有效數據,這種狀況下,即使步長仍然是1,由於剛纔說過的邊緣變形等緣由,輸出的尺寸也會縮小。
這裏不斷強調這一點,是由於多層神經網絡之間,用於計算的矩陣維度,是互相對應的,你必須能清晰的知道你最終輸入下一層的數據維度是什麼。

如同DNN同樣,卷積也能夠逐層關聯,去深刻挖掘信息與信息之間的細微關係。而卷積這種特色也逐層傳遞,成爲尺寸愈來愈小,但深度愈來愈深的形狀。用示意圖來看,很像一個金字塔,因此也稱爲「卷積金字塔」。
問題來了,在咱們這樣的例子中,咱們每一層都沒有縮小圖像尺寸,僅增長了深度。數據量逐層大幅增長,總會達到沒法承受的境地啊?
並且,即使不考慮承受能力,假設咱們所用的計算機無比強大,這些不是問題。但咱們增長的數據,不可能憑空出現。從數學的推導來看,這些數據其實都是使用不一樣係數相乘而來的結果,原圖中可能出現很小的一點偏差,這樣多倍的放大以後,必將也對結果產生很大的影響。這又如何解決呢?bash

池化

一般說,卷積跟池化都是聯合使用的。

如圖所示,上面部分,假設咱們使用卷積的步長爲2,那麼會獲得一個縮小一倍的數據量。
下面的部分,則是假設咱們使用了步長爲1,實際上獲得的數據長寬尺寸,跟原圖是相同的,而深度大大增長了。
這時候能夠在卷積以後附加一層「池化」,以池化設置爲2x2爲例。池化算法會在輸入的圖像中,以2x2爲視窗,提取其中的重點,造成一組數據。
在咱們的案例中,等於輸入2x2x32,輸出1x1x32的數據。
在池化的「提取」中,有不一樣算法供選擇,咱們這裏會採用max,就是取大者,這個大的部分不論在2x2的點陣中在哪一點,都會被提取出來。
因此池化算法對於消除數據抖動、增長系統魯棒性也頗有幫助。降維數據,則是自己就具備的能力。
TensorFlow中還提供了池化的「平均」提取算法,須要的時候能夠查看TensorFlow相關資料。
總之在本例中,輸入的圖像通過2x2的池化以後,圖像的深度不變,尺寸會長、寬各縮減一倍,數據總量將減小4倍。網絡

網絡模型構建

同DNN同樣,CNN的構建也沒有什麼必須的規則。可是TensorFlow手寫數字識別這個例子中所採用的構建方式被認爲是比較典型的一種手段,在Google的教學視頻中有介紹,相關的論文資料沒有查到。
這裏列出來供參考吧:
卷積層1->relu->池化層1->卷積層2->relu->池化層2->全鏈接神經網絡層->relu->dropout層->神經網絡輸出層->softmax
總計11層,也算一個複雜的網絡了。這個模式能夠記下來,之後碰到新項目在本身還吃不許的時候,漫無目的大量實驗以前套上這個模型試試,極可能會有驚喜的收穫。框架

源碼

提早說一下,每一次新的源碼,其中跟之前例子重複的部分,我會減小注釋或者取消註釋,避免過多打斷閱讀程序的連貫性。機器學習

#!/usr/bin/env python
# -*- coding=UTF-8 -*-

import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

import tensorflow as tf
sess = tf.InteractiveSession()

def weight_variable(shape):
  initial = tf.truncated_normal(shape, stddev=0.1)
  return tf.Variable(initial)
def bias_variable(shape):
  initial = tf.constant(0.1, shape=shape)
  return tf.Variable(initial)

#定義卷積,設定步長(stride size)爲1,
#邊距(padding size)爲0,SAME就是指邊距爲0
def conv2d(x, W):
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
#池化是2x2
def max_pool_2x2(x):
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
                        strides=[1, 2, 2, 1], padding='SAME')

x = tf.placeholder("float", shape=[None, 784])
y_ = tf.placeholder("float", shape=[None, 10])

#第一層卷積
#在每一個5x5的patch中算出32個特徵。卷積的權重張量形狀是[5, 5, 1, 32],
#前兩個維度是patch的大小,接着是輸入的通道數目,這裏灰度圖是1個數據,
#最後是輸出的通道數目。 而對於每個輸出通道都有一個對應的偏置量
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
#卷積是在平面2D圖的方向上進行,
#因此爲了使用這一層卷積,咱們先要把x恢復成一組圖,
#2D加上第一維是樣本數量,以及圖的色深,是一個4d向量,
#這裏第二、第3維對應圖片的寬、高,
#最後一維表明圖片的顏色通道數(由於是灰度圖因此這裏的通道數爲1,若是是rgb彩色圖,則爲3)。
x_image = tf.reshape(x, [-1,28,28,1])

#卷積計算,跟上個例子同樣,使用relu激活函數
#本層卷積結果由於padding是SAME,
#因此輸出一樣是28x28,只是變成了32層深
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
#池化是2x2,因此輸出結果是28/2=14,14x14x32的圖
#max池化算法是指在2x2的空間中取最大值
h_pool1 = max_pool_2x2(h_conv1)

#第二層卷積:
#爲了構建一個更深的網絡,咱們會把幾個相似的層堆疊起來。
#第二層中,每一個5x5的patch會獲得64個特徵。
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])

#再次卷積,輸出同輸入同樣是14x14,深度變成了64
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
#再次2x2池化,14/2=7,最後結果是7x7x64的圖
h_pool2 = max_pool_2x2(h_conv2)

#密集鏈接層,也就是一般的神經網絡層:
#如今,圖片尺寸減少到7x7,接入到1024個神經元的全鏈接層
#由於傳統神經網絡層工做在一維模式,
#因此咱們把池化層輸出的張量reshape成一維向量
#算法跟上例中徹底相同
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

#爲了減小過擬合,咱們在輸出層以前加入dropout。
#咱們用一個placeholder來表明一個神經元的輸出在dropout中保持不變的機率。
#這樣咱們能夠在訓練過程當中啓用dropout,在測試過程當中關閉dropout。 
#TensorFlow的tf.nn.dropout操做除了能夠屏蔽神經元的輸出外,
#還會自動處理神經元輸出的比例。
#因此用dropout的時候能夠不用考慮比例。
#此外keep_prob更像是邏輯運算中的if then 邏輯,
#在機器學習中,利用規範的數學運算替代須要程序的邏輯運算是很經常使用的模式
keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

#輸出層:最後,咱們添加一個softmax分類層
#對於這種分類型的深度學習,無論前面多麼複雜的算法,
#最後每每仍然須要softmax進行分類
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])

y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

#訓練和評估模型:
#請參考前一個源碼中的註釋
#在feed_dict中加入額外的參數keep_prob來控制dropout比例。而後每100次迭代輸出一第二天志。
cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
sess.run(tf.global_variables_initializer())
for i in range(20000):
  batch = mnist.train.next_batch(50)
  if i%100 == 0:
    train_accuracy = accuracy.eval(feed_dict={
        #測試過程同生產過程相同關閉dropout.  
        #1表明dropout的保留比例是100%,至關於沒起做用
        x:batch[0], y_: batch[1], keep_prob: 1.0})  
    print "step %d, training accuracy %g"%(i, train_accuracy)
  #訓練過程,啓用dropout,保留50%
  train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5}) 

print "test accuracy %g"%accuracy.eval(feed_dict={
    x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})

這個真的是當前比較先進的識別算法了,最終的正確率大約是超過98%,絕對達到了商用的標準。只是在運行的時候你可能注意到了,三個不一樣的實現,模型訓練的速度一個比一個慢。爲了提升這2%的識別率,可說無比艱辛。ide

從視覺上看卷積的本質

咱們說過了卷積在這裏主要用於提取細節中相鄰點之間的關係。這裏「視覺」就是指肉眼能看到的線、邊、筆畫、小圖形。
那麼若是把這些卷積的結果圖形化出來是什麼樣子呢?下圖就是一個展現:

是否是看到這種直觀的圖示使人更印象更深入?
因此因爲這些特色,CNN大量的用於圖像識別、人臉識別、文字識別、語音識別等很普遍的領域,涵蓋當前比較火爆的機器學習中很大一部分行業。到了這裏,你也能夠自豪的喊一聲:真的入行了。函數

(待續...)

引文及參考

如何理解卷積神經網絡(CNN)中的卷積和池化?
理解dropout
歸一化參數

相關文章
相關標籤/搜索