theano學習指南---棧式自編碼(源碼)

歡迎fork我的github:https://github.com/zhaoyu611/DeepLearningTutorialForChinese

最近在學習git,所以正好趁這個機會,把學習到的知識實踐一下~ 看完DeepLearning的原理,有了大體的瞭解,但是對於theano的代碼,還是自己擼一遍印象更深 所以照着deeplearning.net上的代碼,重新寫了一遍,註釋部分是原文翻譯和自己的理解。 感興趣的小夥伴可以一起完成這個工作哦~ 有問題歡迎聯繫我 Email: [email protected] QQ: 3062984605


# -*- coding: utf-8 -*-
'''
時間:2016.8.4
作者:趙雨
E-mail: [email protected]
說明:針對deepleanring.net中SdA部分的翻譯
'''
#######################################
"""
本教程使用Theano進行棧式自編碼(SdA)。
SdA的基礎是自編碼器,該理論是Bengio等人在2007年提出的。
自編碼器輸入爲x,並映射到隱含層 y = f_{\theta}(x) = s(Wx+b)
其中參數是\theta={W,b}。然後將隱層輸出y映射輸出重構向量z\in [0,1]^d
映射函數爲z = g_{\theta'}(y) = s(W'y + b')。權重矩陣 W'可以由W' = W^T
得到,W'和W稱爲tied weights。網絡的訓練目標是最小化重構誤差(x和z之間的誤差)。

對於降噪自編碼的訓練,首先將x corrupted爲\tilde{x},\tilde{x}是x的破損形式,
破損函數是隨機映射。隨後採用與之前相同方法計算y(y = s(W\tilde{x} + b)
並且 z = s(W'y + b') )。重構誤差是計算z和uncorrupted x的誤差,即:
計算兩者的交叉熵:
     - \sum_{k=1}^d[ x_k \log z_k + (1-x_k) \log( 1-z_k)]

 參考文獻:
   - P. Vincent, H. Larochelle, Y. Bengio, P.A. Manzagol: Extracting and
   Composing Robust Features with Denoising Autoencoders, ICML'08, 1096-1103,
   2008
   - Y. Bengio, P. Lamblin, D. Popovici, H. Larochelle: Greedy Layer-Wise
   Training of Deep Networks, Advances in Neural Information Processing
   Systems 19, 2007
"""

import cPickle
import gzip
import os
import sys
import time
import numpy

import theano
import theano.tensor as T
from theano.tensor.shared_randomstreams import RandomStreams

from logistic_sgd import LogisticRegression, load_data
from mlp import HiddenLayer
from dA import dA

class SdA(object):
    """棧式自編碼類(SdA)
    棧式自編碼模型是由若干dAs堆棧組成。第i層的dA的隱層變成第i+1層的輸入。
    第一層dA的輸入是SdA的輸入,最後一層dA的輸出的SdA的輸出、預訓練後,
    SdA的運行類似普通的MLP,dAs只是用來初始化權重。
    """
    def __init__(self,numpy_rng,theano_rng=None,n_ins=784,
                 hidden_layers_sizes=[500,500],n_outs=10,
                 corruption_levels=[0.1,0.1]):
        """
        該類可以構造可變層數的網絡
        numpy_rng:numpy.random.RandomState  用於初始化權重的隨機數

        theano_rng: theano.tensor.shared_randomstreams.RandomStreams
                    Theano隨機生成數,如果,默認值爲None, 則是由'rng'
                    生成的隨機種子
        n_ins: int  SdA輸入的維度
        hidden_layers_sizes: lists of ints 中間層的層數列表,最少一個元素
        n_out: int 網路輸出量的維度
        corruption_levels: list of float 每一層的corruption level
        """

        self.sigmoid_layers=[]
        self.dA_layers=[]
        self.params=[]
        self.n_layers=len(hidden_layers_sizes)

        assert self.n_layers>0 #設定隱層數量大於0

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

        #設定符號變量
        self.x=T.matrix('x') #柵格化的圖像數據
        self.y=T.ivector('y') #由[int]型標籤組成的一維向量

        #SdA是一個MLP,降噪自編碼器共享中間層的權重向量。
        #首先將SdA構造爲深層多感知器,然後構造每個sigmoid層。
        #同時,該層的降噪自編碼器也會共享權重。
        #預訓練過程是訓練這些自編碼器(同時也會改變多感知器的權重)
        #在微調過程,通過在MLP上採用隨機梯度下降法完成SdA的訓練

        #構造sigmoid層
        for i in xrange(self.n_layers):
            #輸入量的大小是下層隱層單元數量(本層不是第一層)
            #輸入量的大小是輸入量的大小(本層是第一層)
            if i==0:
                input_size=n_ins
            else:
                input_size=hidden_layers_sizes[i-1]

            #本層的輸入是下層隱層的激活(本層不是第一層);
            #本層的輸入是SdA的輸入(本層是第一層)
            if i==0:
                layer_input=self.x
            else:
                layer_input=self.sigmoid_layers[-1].output

            #定義sigmoid層
            sigmoid_layer=HiddenLayer(rng=numpy_rng,
                                      input=layer_input,
                                      n_in=input_size,
                                      n_out=hidden_layers_sizes[i],
                                      activation=T.nnet.sigmoid)
            #將sigmoid層添加到層列表
            self.sigmoid_layers.append(sigmoid_layer)
            #這是個哲學問題...
            #但是我們只想說sigmoid_layers的參數就是
            #SdA的參數
            #dA中可視偏置是dA的參數,而不是SdA的參數
            self.params.extend(sigmoid_layer.params)

            #構造降噪自編碼器與該層共享權重
            dA_layer=dA(numpy_rng=numpy_rng,
                        theano_rng=theano_rng,
                        input=layer_input,
                        n_visible=input_size,
                        n_hidden=hidden_layers_sizes[i],
                        W=sigmoid_layer.W,
                        bhid=sigmoid_layer.b
                        )
            self.dA_layers.append(dA_layer)

        #在MLP頂部加上losgistic層
        self.logLayer=LogisticRegression(
                        input=self.sigmoid_layers[-1].output,
                        n_in=hidden_layers_sizes[-1],n_out=n_outs)
        self.params.extend(self.logLayer.params)
        #建立函數,執行一步微調
        #定義第二步訓練的代價:負log函數
        self.finetune_cost=self.logLayer.negative_log_likelihood(self.y)
        #分別對模型中參數計算梯度
        #給定self.x和self.y,定義每個minibatch上的誤差的符號變量
        self.errors=self.logLayer.errors(self.y)

    def pretraining_function(self,train_set_x,batch_size):
        '''
        生成函數列表,每個函數執行一層中dA的訓練,返回預訓練的函數列表
        函數輸入是minibatch的索引,在所有的minibatch執行相同的訓練
        train_set_x: theano.tensor.TensorType   訓練dA的數據點(共享變量)
        batch_size: int  [mini]batch大小

        '''
        #[mini]batch的索引
        index=T.lscalar('index')
        corruption_level=T.scalar('corruption') #corruption百分比
        learning_rate=T.scalar('lr') #學習率
        #batch數量
        n_bathes=train_set_x.get_value(borrow=True).shape[0]/batch_size
        #給定index後,起始的
        # batch
        batch_begin=index*batch_size
        #給定index後,結束的batch
        batch_end=batch_begin+batch_size

        pretrain_fns=[]
        for dA in self.dA_layers: #遍歷dA
            #創建代價列表和更新列表
            cost,updates=dA.get_cost_updates(corruption_level,
                                            learning_rate)
            #創建theano函數
            fn=theano.function(inputs=[index,
                            theano.Param(corruption_level,default=0.2),
                            theano.Param(learning_rate,default=0.1)],
                                outputs=cost,
                                updates=updates,
                                givens={self.x:train_set_x[batch_begin:
                                                           batch_end]})
            #將fn添加到函數列表
            pretrain_fns.append(fn)

        return pretrain_fns

    def build_finetune_functions(self,datasets,batch_size,learning_rate):
        '''
        創建"train"函數執行一步微調;"validate"函數計算驗證集合中batch的誤差;
        "test"函數計算測試集合中batch誤差

        :param datasets: list of pairs of theano.tensor.TensorType
                         #包含所有datasets的列表,每3個元素爲一個組:
                         依次爲'train'、'valid'、'test'。每個元素又
                         包含兩個theano變量:數據特徵和標籤
        :param batch_size: int  minibatch的大小
        :param learning_rate:float  微調階段的learning_rate
        :return:
        '''

        (train_set_x,train_set_y)=datasets[0]
        (valid_set_x,valid_set_y)=datasets[1]
        (test_set_x,test_set_y)=datasets[2]

        #分別計算training、validation、testing的minibatch的數量
        n_valid_batches=valid_set_x.get_value(borrow=True).shape[0]
        n_valid_batches/=batch_size
        n_test_batches=test_set_x.get_value(borrow=True).shape[0]
        n_test_batches/=batch_size

        index=T.lscalar('index') #[mini]batch的索引
        #分別計算模型參數的梯度
        gparams=T.grad(self.finetune_cost,self.params)
        #計算微調更新參數列表
        updates=[]
        for param,gparam in zip(self.params,gparams):
            updates.append((param,param-gparam*learning_rate))

        train_fn=theano.function(inputs=[index],
                                 outputs=self.finetune_cost,
                                 updates=updates,
                                 givens={
                                     self.x:train_set_x[index*batch_size:
                                                        (index+1)*batch_size],
                                     self.y:train_set_y[index*batch_size:
                                                        (index+1)*batch_size]},
                                 name='train')
        test_score_i=theano.function([index],self.errors,
                                     givens={
                                         self.x:test_set_x[index*batch_size:
                                                         (index+1)*batch_size],
                                         self.y:test_set_y[index*batch_size:
                                                        (index+1)*batch_size]},
                                     name='test')
        valid_score_i=theano.function([index],self.errors,
                                      givens={
                                          self.x:valid_set_x[index*batch_size:
                                                            (index+1)*batch_size],
                                          self.y:valid_set_y[index*batch_size:
                                                            (index+1)*batch_size]},
                                      name='valid')
        #創建函數遍歷整個驗證集合
        def valid_score():
            return [valid_score_i(i) for i in xrange(n_valid_batches) ]
        #創建函數遍歷整個測試集合
        def test_score():
            return [test_score_i(i) for i in xrange(n_test_batches)]
        return train_fn, valid_score, test_score

def test_SdA(finetune_lr=0.1,pretraining_epochs=15,
             pretrain_lr=0.001,training_epochs=1000,
             dataset='./data/mnist.pkl.gz',batch_size=1):
    '''
    創建函數,訓練和測試隨機降噪自編碼器,實驗數據爲MNINST


    :param finetune_lr: float   微調階段的學習率(隨機梯度下降的影響因素)
    :param pretraining_epochs:int   預訓練的迭代次數
    :param pretrain_lr: float   預訓練階段的學習率
    :param training_epochs: int 整個訓練的最大次數
    :param dataset: string 數據集的路徑
    :param batch_size: batch大小
    :return:
    '''
    datasets=load_data(dataset)

    train_set_x,training_set_y=datasets[0]
    valid_set_x,valid_set_y=datasets[1]
    test_set_x,test_set_y=datasets[2]
    #計算訓練的minibatch個數
    n_train_batches=train_set_x.get_value(borrow=True).shape[0]
    n_train_batches/=batch_size

    #生成隨機數
    numpy_rng=numpy.random.RandomState(89677)
    print '...building the model'
    #實例化棧式自編碼類
    sda=SdA(numpy_rng=numpy_rng,n_ins=28*28,
            hidden_layers_sizes=[1000,1000,1000],n_outs=10)

    #########################
    #        預訓練過程      #
    #########################
    print '...getting the pretraining functions'
    pretraining_fns=sda.pretraining_function(train_set_x=train_set_x,
                                                 batch_size=batch_size)
    print '... pre-training the model'
    stat_time=time.clock()
    #逐層訓練
    corruption_levels=[0.1,0.2,0.3]
    #對每層進行預訓練
    for i in xrange(sda.n_layers):
        #進行n次預訓練
        for epoch in xrange(pretraining_epochs):
            #遍歷整個訓練集合
            c=[]
            for batch_index in xrange(n_train_batches):
                c.append(pretraining_fns[i](index=batch_index,
                                            corruption=corruption_levels[i],
                                            lr=pretrain_lr))
            print 'Pre-training layer %i, epoch %d, cost '%(i,epoch)
            print numpy.mean(c)
    end_time=time.clock()
    print >>sys.stderr,('The pretraining code for file'+
                        os.path.split(__file__)[1]+
                        'ran for %0.2fm')%((end_time-stat_time)/60)
    ########################
    #       微調模型        #
    ########################
    #創建模型的訓練函數、驗證函數和測試函數
    print '...getting the finetuning functions'
    train_fn,validate_model,test_model=sda.build_finetune_functions(
                                        datasets=datasets,batch_size=batch_size,
                                        learning_rate=finetune_lr)
    print '... finetuning the model'
    #提前終止的條件參數
    patience=10*n_train_batches # 通常訓練中忽略該條件
    patience_increase=2.0 #當發現新的最優值時,patience增加值爲2.0
    improvement_threshold=0.995 #提高的閾值限定
    validation_frequency=min(n_train_batches,patience/2) #在驗證集合的上檢查minibatch的次數
                                                         #在本實驗中,minibatch爲1,
                                                         # 每次迭代都檢查
    best_params=None
    best_validation_loss=numpy.inf
    test_score=0.
    start_time=time.clock()

    done_looping=False
    epoch=0

    while (epoch<training_epochs) and (not done_looping):
        epoch=epoch+1
        for minibatch_index in xrange(n_train_batches):
            minibatch_avg_cost=train_fn(minibatch_index)
            iter=(epoch-1)*n_train_batches+minibatch_index  #數據的編號
            #每個minibatch都輸出驗證集代價值,該實驗中minibatch爲1
            if (iter+1)%validation_frequency==0:
                validation_losses=validate_model()
                this_validation_loss=numpy.mean(validation_losses)
                print ('epoch %i, minibatch %i/%i, validation error %f %%' %
                       (epoch,minibatch_index+1,n_train_batches,
                        this_validation_loss*100))

                #如果我們得到當前最優驗證得分,則更新
                if this_validation_loss<best_validation_loss:
                    #增加patience,如果loss improvement足夠好
                    if (this_validation_loss<best_validation_loss*
                        improvement_threshold):
                        patience=max(patience,iter*patience_increase)

                    #保存最優驗證值和編號
                    best_validation_loss=this_validation_loss
                    best_iter=iter

                    #測試集合上進行測試
                    test_losses=test_model()
                    test_score=numpy.mean(test_losses)
                    print ('epoch %i, minibath %i/%i, test error of '
                           'best model %f %%') %(epoch,minibatch_index+1,
                            n_train_batches,test_score*100.)

            if patience<=iter: #設置終止條件
                done_looping=True
                break
    end_time=time.clock()
    print ('optimization complete with best validation score of %f %%,'
            'with test performance %f %%') %(best_validation_loss%100,test_score*100)
    print >>sys.stderr, ('The training code for file'+
                         os.path.split(__file__)[1]+
                         'ran for %0.2fm')%((end_time-start_time)/60.)


if __name__=='__main__':
    test_SdA()


實驗配置:ThunderBot筆記本,I7,1T機械硬盤+128SSD,N卡Geforce GTX 980。
實驗數據集:MNIST
實驗用時:預訓練過程:686.75minutes, 微調過程:362.87minutes。
最優訓練結果:驗證集正確率:0.0137 %,測試數據集1.2600 %

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章