1. 程式人生 > 其它 >利用Theano理解深度學習——Auto Encoder

利用Theano理解深度學習——Auto Encoder

注:本系列是基於參考文獻中的內容,並對其進行整理,註釋形成的一系列關於深度學習的基本理論與實踐的材料,基本內容與參考文獻保持一致,並對這個專題起名為“利用Theano理解深度學習”系列,若文中有任何問題歡迎諮詢。本文提供PDF版本,歡迎索取。

“利用Theano理解深度學習”系列分為44個部分,這是第二部分,在第一部分中的演算法主要是監督學習演算法,在這部分中主要是無監督學習演算法和半監督學習演算法,主要包括:

  • 利用Theano理解深度學習——Auto Encoder
  • 利用Theano理解深度學習——Denoising Autoencoder
  • 利用Theano理解深度學習——Stacked Denoising Auto Encoder
  • 利用Theano理解深度學習——Restricted Boltzmann Machine
  • 利用Theano理解深度學習——Deep Belief Network

一、自編碼器(Autoencoders)的原理

自編碼器是典型的無監督學習演算法,其結構如下所示:

二、Autoencoder的損失函式

定義重構誤差的方法取決於對輸入的合適的假設。我們希望的是y mathbf{y}是一種分散式的表示,可以捕獲在資料中的主要變化因素的座標。這與對映到主要成分的方式相似,可以捕獲資料中變化的主要因素。實際上,如果對於存在一個線性的隱含層,並且使用均方誤差作為標準訓練網路,則第kk個隱含層節點學到的是將輸入對映到前kk個主要成分張成的空間。如果隱含層是非線性的,則auto-encoder與PCA不同,具有捕獲輸入分佈中的多模態的能力。這種與PCA不一樣的性質對於我們構建層疊式多個編碼器(stacking multiply encoders)是非常重要的,如構建一個深度自編碼器。

因為y mathbf{y}是被看成是x mathbf{x}的有失真壓縮,不可能對所有的x mathbf{x}都具有較好的壓縮效果。優化的過程使得這樣的y mathbf{y}對所有的訓練樣本來說是較好的,同時,希望對其他的輸入同樣也具有較好的壓縮效果,但是不是對任意的輸入都適用。在Auto Encoder演算法中有如下的結論:

當測試樣本與輸入樣本具有同樣的分佈時,auto-encoder具有較小的重構誤差,但是對於從輸入空間中隨機選取的樣本,通常具有較大的重構誤差。

三、利用Theano實現Autoencoder

在Autoencoder的構建中主要包括以下幾個部分:

3.1、匯入資料集

在匯入資料集中,主要使用的是在logistic_sgd中定義的load_data函式,其具體的匯入形式如下所示:

#1、匯入資料集#
datasets = load_data(dataset)#匯入資料集,函式在logistic_sgd中定義
train_set_x, train_set_y = datasets[0]#得到訓練資料

3.2、構建模型

構建模型的程式碼如下所示:

#2、構建模型
rng = numpy.random.RandomState(123)
theano_rng = RandomStreams(rng.randint(2 ** 30))

#初始化模型的引數
da = auto_encoder(
    numpy_rng=rng,
    theano_rng=theano_rng,
    input=x,
    n_visible=28 * 28, 
    n_hidden=500
)   

#定義模型的損失函式和更新規則
cost, updates = da.get_cost_updates(
    corruption_level=0.,
    learning_rate=learning_rate
)   

#定義訓練函式
train_da = theano.function(
    [index],
    cost,
    updates=updates,
    givens={
        x: train_set_x[index * batch_size: (index + 1) * batch_size]
    }   
)   

start_time = timeit.default_timer()#定義訓練的初始時間

3.2.1、類的定義

首先是對模型中的引數進行初始化,這部分主要是一個auto_encoder的類,類的定義如下所示:

class auto_encoder(object):
def __init__(
    self,
    numpy_rng,
    theano_rng=None,
    input=None,
    n_visible=784,#輸入層節點的個數
    n_hidden=500,#隱含層節點的個數
    W=None,
    bhid=None,
    bvis=None
):
    """
    :type numpy_rng: numpy.random.RandomState
    :param numpy_rng: 用於隨機產生權重和偏置

    :type theano_rng: theano.tensor.shared_randomstreams.RandomStreams
    :param theano_rng: Theano的隨機數生成

    :type input: theano.tensor.TensorType
    :param input: 描述輸入的引數,None表示的是單獨的autoencoder

    :type n_visible: int
    :param n_visible: 輸入層節點的個數

    :type n_hidden: int
    :param n_hidden: 隱含層節點的個數

    :type W: theano.tensor.TensorType
    :param W: 指示權重的引數,如果是None表示的是單獨的Autoencoder

    :type bhid: theano.tensor.TensorType
    :param bhid: 指示隱含層偏置的引數,如果是None表示的是單獨的Autoencoder

    :type bvis: theano.tensor.TensorType
    :param bvis: 指示輸出層偏置的引數,如果是None表示的是單獨的Autoencoder

    """
    self.n_visible = n_visible#設定輸入層的節點個數
    self.n_hidden = n_hidden#設定隱含層的節點個數

    if not theano_rng:
        theano_rng = RandomStreams(numpy_rng.randint(2 ** 30))

    if not W:#初始化權重
        initial_W = numpy.asarray(
            numpy_rng.uniform(
                low=-4 * numpy.sqrt(6. / (n_hidden + n_visible)),#下界
                high=4 * numpy.sqrt(6. / (n_hidden + n_visible)),#上界
                size=(n_visible, n_hidden)
            ),
            dtype=theano.config.floatX
        )
        W = theano.shared(value=initial_W, name='W', borrow=True)

    if not bvis:#初始化偏置
        bvis = theano.shared(
            value=numpy.zeros(#初始化為0
                n_visible,
                dtype=theano.config.floatX
            ),
            borrow=True
        )

    if not bhid:#初始化偏置
        bhid = theano.shared(#初始化為0
            value=numpy.zeros(
                n_hidden,
                dtype=theano.config.floatX
            ),
            name='b',
            borrow=True
        )

    self.W = W#輸入層到隱含層的權重
    self.b = bhid#輸入層到隱含層的偏置
    self.b_prime = bvis#隱含層到輸出層的偏置
    self.W_prime = self.W.T#隱含層到輸出層的偏置
    self.theano_rng = theano_rng

    #將輸入作為引數傳入,可以方便後面堆疊成深層的網路
    if input is None:
        self.x = T.dmatrix(name='input')
    else:
        self.x = input

    self.params = [self.W, self.b, self.b_prime]#宣告引數



def get_corrupted_input(self, input, corruption_level):
    return self.theano_rng.binomial(size=input.shape, n=1,
                                    p=1 - corruption_level,
                                    dtype=theano.config.floatX) * input


def get_hidden_values(self, input):#計算隱含層的輸出
    return T.nnet.sigmoid(T.dot(input, self.W) + self.b)

def get_reconstructed_input(self, hidden):#計算輸出層的輸出
    return T.nnet.sigmoid(T.dot(hidden, self.W_prime) + self.b_prime)

def get_cost_updates(self, corruption_level, learning_rate):#計算損失函式和更新

    tilde_x = self.get_corrupted_input(self.x, corruption_level)
    y = self.get_hidden_values(tilde_x)
    z = self.get_reconstructed_input(y)

    #損失函式
    L = - T.sum(self.x * T.log(z) + (1 - self.x) * T.log(1 - z), axis=1)
    cost = T.mean(L)#求出平均誤差

    # 對需要求解的引數求其梯度
    gparams = T.grad(cost, self.params)

    #基於梯度下降更新每個引數
    updates = [
        (param, param - learning_rate * gparam)
        for param, gparam in zip(self.params, gparams)
    ]

    return (cost, updates)

3.3、訓練模型

在模型的訓練階段,是根據每一批樣本,利用梯度下降對引數進行求解,程式程式碼如下:

#3、訓練模型
# go through training epochs
for epoch in xrange(training_epochs):
    # go through trainng set
    c = []
    for batch_index in xrange(n_train_batches):
        c.append(train_da(batch_index))

    print 'Training epoch %d, cost ' % epoch, numpy.mean(c)

end_time = timeit.default_timer()

training_time = (end_time - start_time)

'''
print >> sys.stderr, ('The no corruption code for file ' +
                      os.path.split(__file__)[1] +
                      ' ran for %.2fm' % ((training_time) / 60.))
image = Image.fromarray(
    tile_raster_images(X=da.W.get_value(borrow=True).T,
                       img_shape=(28, 28), tile_shape=(10, 10),
                       tile_spacing=(1, 1)))
image.save('filters_corruption_0.png')

os.chdir('../')
'''

其中,在迴圈的過程中,對模型進行訓練,最終的影象輸出功能被註釋了。我們的目的是求出模型的權重和偏置,利用輸入層到隱含層的權重和偏置,在後面的堆疊自編碼其中,可以將這兩層通過堆疊的方式構建成深度的網路。

四、執行結果

在程式中,本人註釋了影象的輸出功能,訓練的過程如下所示:

五、關於隱含層節點個數的幾點論述

對於隱含層節點的個數,對於非線性的自編碼器,如果隱含層的節點個數大於輸入層的節點個數,通過隨機梯度下降法訓練得到的模型通常具有更好的表示能力,這裡的表示能力是指模型具有較小的分類誤差。

隱含層節點個數大於輸入層節點個數,這樣的自編碼器具有更小的分類誤差。

以上的現象可以解釋為:隨機梯度下降法加上early stopping策略相當於對模型中的引數進行L2L2正則約束。對於一個隱含層節點個數大於輸入層節點個數的自編碼器,還有一些其他的策略可以使其在隱含層學習到輸入資料的更多有用的資訊,包括:

  • 增加稀疏性(Sparsity):使得隱含層節點為00或者趨近於00。
  • 在輸入到重構的過程中增加隨機性(Randomness)。在Restricted Boltzmann Machines和Denoising Auto-Encoders中會涉及。

若需要PDF版本,請關注我的新浪部落格@趙_志_勇,私信你的郵箱地址給我。

參考文獻

Deep Learning Tutorialshttp://www.deeplearning.net/tutorial/