【深度學習】深度學習中監督優化入門(A Primer on Supervised Optimization for Deep Learning)

簡介

這個教程涵蓋了深度學習(Deep Learning)的一些重要概念,是一個快速入門的大綱教程,包含了三個部分:

  • 第一部分-數據集:介紹了MNIST數據集和使用方法;
  • 第二部分-標記法:介紹了主要概念的符號標記方法;
  • 第三部分-監督優化入門:介紹了一些深度學習的重要概念;

1.數據集

1.MNIST數據集

這個數據集(mnist.pkl.gz)可以在CSDN下載中免費下載:http://download.csdn.net/detail/ws_20100/9224993

MNIST數據集包含手寫字符圖像,其中有60000個是訓練樣本,而10000個是測試樣本。對於很多論文,包含這篇教程,60000個訓練樣本被分爲50000個樣本作訓練集,10000個樣本作驗證集(用於選擇超參數,例如學習率和模型大小)。所有的數字圖像在大小上都已經規範化並且處於28× 28像素的中心位置。所有的圖像像素點的值都處於0到255之間,其中,0代表黑色,255代表白色,中間的值爲不同等級的灰色。

這些是一些MNIST數據集樣本:
這裏寫圖片描述

爲了方便我們在python中使用MNIST數據集,我們對MNIST數據集進行處理。處理後的數據集有3個列表:訓練集,驗證集和測試集。每個列表包含多個二元組,每個二元組包含一個圖像和對應的標籤。圖像被表示爲一個1× 784(28× 28)維數組,數組中每個元素在0到1之間,0代表黑,1代表白。標籤是一個0到9的數值。加載數據集的代碼如下:

import cPickle, gzip, numpy

# Load the dataset
f = gzip.open('mnist.pkl.gz', 'rb')
train_set, valid_set, test_set = cPickle.load(f)
f.close()

當使用數據集的時候,我們通常將其分成多個minibatch(詳細的請看下面的隨機梯度下降法)。在實際使用時,最好使用共享變量,因爲共享變量和GPU相關。如果不使用共享變量,使用GPU運算可能不比CPU快多少,可能更慢。存取數據,讀取minibatch的代碼如下:

def shared_dataset(data_xy):
    """ Function that loads the dataset into shared variables

    The reason we store our dataset in shared variables is to allow
    Theano to copy it into the GPU memory (when code is run on GPU).
    Since copying data into the GPU is slow, copying a minibatch everytime
    is needed (the default behaviour if the data is not in a shared
    variable) would lead to a large decrease in performance.
    """
    data_x, data_y = data_xy
    shared_x = theano.shared(numpy.asarray(data_x, dtype=theano.config.floatX))
    shared_y = theano.shared(numpy.asarray(data_y, dtype=theano.config.floatX))
    # When storing data on the GPU it has to be stored as floats
    # therefore we will store the labels as ``floatX`` as well
    # (``shared_y`` does exactly that). But during our computations
    # we need them as ints (we use labels as index, and if they are
    # floats it doesn't make sense) therefore instead of returning
    # ``shared_y`` we will have to cast it to int. This little hack
    # lets us get around this issue
    return shared_x, T.cast(shared_y, 'int32')

test_set_x, test_set_y = shared_dataset(test_set)
valid_set_x, valid_set_y = shared_dataset(valid_set)
train_set_x, train_set_y = shared_dataset(train_set)

batch_size = 500    # size of the minibatch

# accessing the third minibatch of the training set

data  = train_set_x[2 * batch_size: 3 * batch_size]
label = train_set_y[2 * batch_size: 3 * batch_size]

If you are running your code on the GPU and the dataset you are using is too large to fit in memory the code will crash. In such a case you should store the data in a shared variable. You can however store a sufficiently small chunk of your data (several minibatches) in a shared variable and use that during training. Once you got through the chunk, update the values it stores. This way you minimize the number of data transfers between CPU memory and GPU memory.


2.標記法

1.數據集標記法

我們將數據集標記爲D ,如果需要標註不同的數據集,我們將訓練集,驗證集和測試集分別標註爲DtrainDvalidDtest 。驗證集用於完成模型選擇和超參數選擇,而測試集用於評估最終的泛化誤差,並公平的比較不同算法的性能。
這個教程多數算法都是用於處理分類問題,所以數據集D 是一個二元組序列(x(i),y(i)) ,我們使用上標來區別不同的訓練集樣本:x(i)RD ,其中第i 個訓練樣本是D 維向量。相似的,yi{0,...,L} ,其中第i 個標籤對應於x(i) 的輸入。顯然,我們可以將樣本對應的標籤yi 擴展到其他類型(例如,用於迴歸的高斯過程,或者用於預測多個符號的多項式組)。

2.數學約定

  • W :大寫符號代表一個矩陣(除非特殊指定)。
  • Wij :矩陣W 的第i 行第j 列元素。
  • Wi,Wi :矩陣W 的第i 行。
  • Wj :矩陣W 的第j 列。
  • b :小寫符號代表一個向量(除非特殊指定)。
  • bi :向量b 的第i 個元素。

3.符號和縮寫列表

  • D :輸入向量的維數。
  • D(i)h :第i 層隱節點數量。
  • fθ(x),f(x) :與模型P(Y|x,θ) 相關的分類函數,定義爲argmaxkP(Y=k|x,θ) 。注意通常省略下標θ
  • L :標籤的數量。
  • L(θ,D) :含有參數θ 關於D 的對數似然。
  • (θ,D) :在數據集D 上,含有參數θ 的預測函數f 的經驗損失。
  • NLL :負對數似然。
  • θ :一個給定模型的所有參數組成的集。

4.Python命名空間

教程的代碼通常使用以下的命名空間:

import theano
import theano.tensor as T
import numpy

3.監督優化入門

深度學習(Deep Learning)中最大的特點,就是大量使用深度網絡的無監督學習(unsupervised learning)。但是監督學習仍然扮演着非常重要的角色。非監督預學習(pre-training)的作用在於,評估(在監督精細迭代(fine-tuning)之後)網絡可以達到的性能。這節回顧了分類模型中監督學習的理論基礎,並且包含了多數模型中精細迭代所需要的小批量數據的隨機梯度下降算法(minibatch stochastic gradient descent algorithm)。

1.學習一個分類器

1.)0-1損失

在這個深度學習教程中出現的模型大多是用於分類器設計。訓練分類器的目的在於,對於未見過的樣本,最小化錯誤分類(0-1損失)的數量。如果f:RD{0,...,L} 是預測函數,那麼它的損失值可以寫爲:

0,1=i=0|D|If(x(i))y(i)
D ,要麼是(在訓練過程中的)訓練集,要麼存在DDtrainϕ (防止在評估測試誤差時出現偏差)。I 是指示函數,定義爲:
Ix={10 if x is True  otherwise 
在這個教程中,f 定義爲:
f(x)=argmaxkP(Y=k|x,θ)
在Python裏面,使用Theano可以寫爲:
# zero_one_loss is a Theano variable representing a symbolic
# expression of the zero one loss ; to get the actual value this
# symbolic expression has to be compiled into a Theano function (see
# the Theano tutorial for more details)
zero_one_loss = T.sum(T.neq(T.argmax(p_y_given_x), y))

2.)負對數似然損失

由於0-1損失是不可微的,所以在大模型中使用它,會存在成千上萬的係數,會不可避免地增加繁重的計算量。所以,對於訓練集對應的標籤,我們最大化分類器的對數似然。

L(θ,D)=i=0|D|logP(Y=y(i)|x(i),θ)
正確類別的似然和正確預測的數量並不完全一致,但是從隨機初始分類器的角度來看,它們是很相似的。但是要記住,似然函數和0-1損失具有不同的目標,你應該知道它們在驗證集上相互聯繫,但有時一個比另一個要高,而有時它們又差不多大小。
由於我們通常要最小化一個損失函數,學習過程試圖最小化負對數似然(negative log-likelihood,NLL)定義如下:
NLL(θ,D)=i=0|D|logP(Y=y(i)|x(i),θ)
我們分類器的NLL是0-1損失的一種可微的代理函數,我們使用訓練集上函數的梯度作爲深度學習分類器的監督學習信號。
可以通過以下代碼計算:
# NLL is a symbolic variable ; to get the actual value of NLL, this symbolic
# expression has to be compiled into a Theano function (see the Theano
# tutorial for more details)
NLL = -T.sum(T.log(p_y_given_x)[T.arange(y.shape[0]), y])
# note on syntax: T.arange(y.shape[0]) is a vector of integers [0,1,2,...,len(y)].
# Indexing a matrix M by the two vectors [0,1,...,K], [a,b,...,k] returns the
# elements M[0,a], M[1,b], ..., M[K,k] as a vector.  Here, we use this
# syntax to retrieve the log-probability of the correct labels, y.

2.隨機梯度下降法

什麼是普通的梯度下降法呢?它是一個簡單的算法,在含參損失函數的誤差曲面上,向着誤差更小的方向一步一步的調整。爲了達到這個目標,我們需要將訓練數據引入損失函數。該算法的僞代碼如下所示:

# GRADIENT DESCENT

while True:
    loss = f(params)
    d_loss_wrt_params = ... # compute gradient
    params -= learning_rate * d_loss_wrt_params
    if <stopping condition is met>:
        return params

隨機梯度下降法(Stochastic gradient descent, SGD)遵循着和一般梯度下降法一樣的準則。但是隨機梯度下降法具有更快的速度,它一次只對一小部分樣本估計梯度,而不是全部的樣本集。爲了更加純粹的形式,我們每次只對一個樣本進行梯度估計。

# STOCHASTIC GRADIENT DESCENT
for (x_i,y_i) in training_set:
                            # imagine an infinite generator
                            # that may repeat examples (if there is only a finite training set)
    loss = f(params, x_i, y_i)
    d_loss_wrt_params = ... # compute gradient
    params -= learning_rate * d_loss_wrt_params
    if <stopping condition is met>:
        return params

用於深度學習的梯度下降法,是一種隨機梯度下降法的變體,我們叫它”minibatches”。稱爲Minibatch SGD(MSGD),它和SGD工作原理相同,只是在一次估計中使用多個樣本,而不僅僅是一個。這種方法減少了估計梯度的方差,並且能夠在現代計算機中更好的組織內存。

for (x_batch,y_batch) in train_batches:
                            # imagine an infinite generator
                            # that may repeat examples
    loss = f(params, x_batch, y_batch)
    d_loss_wrt_params = ... # compute gradient using theano
    params -= learning_rate * d_loss_wrt_params
    if <stopping condition is met>:
        return params

在選擇minibatch大小(記爲B )時,需要一個折衷。在B 從1增加到2時,方差的減少和SIMD指令的使用起了很大的作用,但是在B 很大時,提升就不是那麼明顯了。對於更大的B 值,時間應該被更好地用在梯度的步進上,而不是減少梯度的方差上。一個最優的B 值應該是在模型上,數據集上,和硬件上都是獨立的,並且可以取1到幾百之間的任意數值,我們在這個教程中定義B=20 ,但是要記住,它的取值是任意的。

如果你的訓練的迭代次數是固定的,minibatch的大小就會變得至關重要,因爲它控制着參數更新的次數。迭代次數爲10,minibatch爲1的迭代結果顯然和迭代次數爲10,但是minibatch爲20的迭代結果不同。在調整minibatch大小時,需要謹記這點。

以上的代碼都是僞代碼格式的,真實的實用代碼如下:

# Minibatch Stochastic Gradient Descent

# assume loss is a symbolic description of the loss function given
# the symbolic variables params (shared variable), x_batch, y_batch;

# compute gradient of loss with respect to params
d_loss_wrt_params = T.grad(loss, params)

# compile the MSGD step into a theano function
updates = [(params, params - learning_rate * d_loss_wrt_params)]
MSGD = theano.function([x_batch,y_batch], loss, updates=updates)

for (x_batch, y_batch) in train_batches:
    # here x_batch and y_batch are elements of train_batches and
    # therefore numpy arrays; function MSGD also updates the params
    print('Current loss is ', MSGD(x_batch, y_batch))
    if stopping_condition_is_met:
        return params

3.正則化

最優化並不是機器學習的全部內容。在訓練中,除了我們給定的完美樣本以外,模型還會遇到它從來沒有見過的樣本。MSGD的訓練過程不會考慮到這些,因此可能會對訓練樣本過擬合。一個用於抵抗過擬合的方法就是:正則化。正則化的方法有很多,在這裏我們僅僅介紹12 正則化,以及提前退出。

1.)12 正則化

12 正則化,是在損失函數之後增加了一個額外項,用於懲罰相應的參數配置。在格式上,如果我們的損失函數定義爲:

NLL(θ,D)=i=0|D|logP(Y=y(i)|x(i),θ)
那麼正則化的損失函數定義爲:
E(θ,D)=NLL(θ,D)+λR(θ)
或者,在我們實際使用中,定義爲:
E(θ,D)=NLL(θ,D)+λ||θ||pp
其中,
||θ||p=j=0|θ||θj|p1p
這就是θp 範數。λ 是一個超參量,用於調整正則參數的重要程度。最常用的p 值是1和2。所以稱爲1 或者2 約束。如果p=2 ,那麼正則項也被稱爲權值衰減(weight decay)。
從原理上來說,在損失項之後增加正則化約束項,可以獲得更加平滑的網絡映射(因爲通過懲罰參數中的大值,可以減少網絡模型中非線性的數量)。更加直觀的是,兩項(NLLR(θ) )分別對應於“更好地擬合數據“(NLL )和“具有簡單平滑的解“(R(θ) )。通過最小化兩項的線性組合,可以尋求“擬合訓練數據“和“獲得泛化能力“的一個折衷解。根據奧卡姆剃刀定律,最小化過程應該在擬合訓練數據的基礎上尋找到最簡單的解(是否簡單,由我們定義的簡單約束測量)。
注意,我們尋找到的最“簡單“的解,並不意味着這個解具有非常好的泛化能力。從經驗上看,在神經網絡中加入這種正則化約束可以增強泛化能力,特別是在小數據集的情況下。以下代碼,闡述瞭如何在pyhton中計算具有1 (係數λ1 )和2 (係數λ2 )正則項的損失函數。
# symbolic Theano variable that represents the L1 regularization term
L1  = T.sum(abs(param))

# symbolic Theano variable that represents the squared L2 term
L2_sqr = T.sum(param ** 2)

# the loss
loss = NLL + lambda_1 * L1 + lambda_2 * L2

2.)提前退出

提前退出(Early-stopping),通過在一個驗證集(validate set)上監控模型性能,來防止過擬合的發生。驗證集的樣本,沒有用於梯度下降法(訓練階段),但也不是測試集的一部分。驗證集樣本被視爲未來測試樣本的一個典型代表。我們可以將它用於訓練,因爲它並不是測試集的一部分。如果在驗證集上,模型的性能已經沒有提高了,甚至在某些情況下,模型的性能已經隨着訓練有所下降時,那麼訓練程序會提前退出。
提前退出的條件有很多種,我們在此使用基於patience的增加數量的退出策略。

# early-stopping parameters
patience = 5000  # look as this many examples regardless
patience_increase = 2     # wait this much longer when a new best is
                              # found
improvement_threshold = 0.995  # a relative improvement of this much is
                               # considered significant
validation_frequency = min(n_train_batches, patience/2)
                              # go through this many
                              # minibatches before checking the network
                              # on the validation set; in this case we
                              # check every epoch

best_params = None
best_validation_loss = numpy.inf
test_score = 0.
start_time = time.clock()

done_looping = False
epoch = 0
while (epoch < n_epochs) and (not done_looping):
    # Report "1" for first epoch, "n_epochs" for last epoch
    epoch = epoch + 1
    for minibatch_index in xrange(n_train_batches):

        d_loss_wrt_params = ... # compute gradient
        params -= learning_rate * d_loss_wrt_params # gradient descent

        # iteration number. We want it to start at 0.
        iter = (epoch - 1) * n_train_batches + minibatch_index
        # note that if we do `iter % validation_frequency` it will be
        # true for iter = 0 which we do not want. We want it true for
        # iter = validation_frequency - 1.
        if (iter + 1) % validation_frequency == 0:

            this_validation_loss = ... # compute zero-one loss on validation set

            if this_validation_loss < best_validation_loss:

                # improve patience if loss improvement is good enough
                if this_validation_loss < best_validation_loss * improvement_threshold:

                    patience = max(patience, iter * patience_increase)
                best_params = copy.deepcopy(params)
                best_validation_loss = this_validation_loss

        if patience <= iter:
            done_looping = True
            break

# POSTCONDITION:
# best_params refers to the best out-of-sample parameters observed during the optimization

如果我們在達到終止條件(耗盡patience)之前,耗盡了所有的訓練樣本minibatch,那麼我們就從最初的訓練樣本開始,重複訓練。

注意: validation_frequency永遠都要小於patience。在耗盡patience之前,代碼需要至少兩次檢查模型性能。這就是爲什麼我們使用公式:validation_frequency = min( value, patience/2.)

注意: 在決定是否增加patience時,不使用簡單的比較,而使用統計顯著性的一個測試,有可能可以增強算法的性能。


發佈了43 篇原創文章 · 獲贊 42 · 訪問量 39萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章