1. 程式人生 > >深度學習:自動編碼器基礎和型別

深度學習:自動編碼器基礎和型別

本文轉載自《機器之心》,原文連結:https://mp.weixin.qq.com/s/QuDa__mi1NX1wOxo5Ki94A ,如有侵權請聯絡刪除。

很顯然,深度學習即將對我們的社會產生重大顯著的影響。Mobibit 創始人兼 CEO Pramod Chandrayan 近日在 codeburst.io 上發文對自動編碼器的基礎知識和型別進行了介紹並給出了程式碼例項。機器之心對本文進行了編譯。
在這裡插入圖片描述

繼續我之前的文章《深度學習:什麼&為什麼?》(https://goo.gl/Ka3YoF),今天我們將進一步瞭解深度學習的架構型別,並詳細討論自動編碼器。

當人類大腦與深度學習機器合作時:
在這裡插入圖片描述

在我們開始揭祕深度網路之前,讓我們先定義一下深度學習。根據我的理解:

深度學習是一種先進的機器學習技術,其中存在多個彼此通訊的抽象層,每一層都與前一層深度相連,並根據前一層饋送的輸出進行決策。」

Investopedia 將深度學習定義成:

深度學習是人工智慧(AI)領域中機器學習中的一個子集,其有網路狀的結構,可以從非結構化或無標記的資料中以無監督的方式學習。也被稱為深度神經學習或深度神經網路。」

今天我們將深入解讀無監督預訓練網路(Unsupervised Pertained Networks)的工作方式。

一、UPN:無監督預訓練網路

這種無監督學習網路可以進一步分類成

  • 自動編碼器
  • 深度信念網路(DBN)
  • 生成對抗網路(GAN)

自動編碼器是一種有三層的神經網路:輸入層、隱藏層(編碼層)和解碼層。該網路的目的是重構其輸入,使其隱藏層學習到該輸入的良好表徵。

自動編碼器神經網路是一種無監督機器學習演算法,其應用了反向傳播,可將目標值設定成與輸入值相等。自動編碼器的訓練目標是將輸入複製到輸出。在內部,它有一個描述用於表徵其輸入的程式碼的隱藏層。
在這裡插入圖片描述

自動編碼器的目標是學習函式 h(x)≈x。換句話說,它要學習一個近似的恆等函式,使得輸出 x^ 近似等於輸入 x。自動編碼器屬於神經網路家族,但它們也和 PCA(主成分分析)緊密相關。

關於自動編碼器的一些關鍵事實:

  • 它是一種類似於 PCA 的無監督機器學習演算法
  • 它要最小化和 PCA 一樣的目標函式
  • 它是一種神經網路
  • 這種神經網路的目標輸出就是其輸入

儘管自動編碼器與 PCA 很相似,但自動編碼器比 PCA 靈活得多。在編碼過程中,自動編碼器既能表徵線性變換,也能表徵非線性變換;而 PCA 只能執行線性變換。因為自動編碼器的網路表徵形式,所以可將其作為層用於構建深度學習網路。

自動編碼器的型別:

  1. 去噪自動編碼器
  2. 稀疏自動編碼器
  3. 變分自動編碼器(VAE)
  4. 收縮自動編碼器(CAE/contractive autoencoder)

1.1、A. 去噪自動編碼器

這是最基本的一種自動編碼器,它會隨機地部分採用受損的輸入來解決恆等函式風險,使得自動編碼器必須進行恢復或去噪。

這項技術可用於得到輸入的良好表徵。良好的表徵是指可以從受損的輸入穩健地獲得的表徵,該表徵可被用於恢復其對應的無噪聲輸入。

去噪自動編碼器背後的思想很簡單。為了迫使隱藏層發現更加穩健的特徵並且為了防止其只是學習其中的恆等關係,我們在訓練自動編碼器時會讓其從受損的版本中重建輸入。

應用在輸入上的噪聲量以百分比的形式呈現。一般來說,30% 或 0.3 就很好,但如果你的資料非常少,你可能就需要考慮增加更多噪聲。

1.2、堆疊的去噪自動編碼器(SDA):
在這裡插入圖片描述

這是一種在層上使用了無監督預訓練機制的去噪自編碼器,其中當一層被預訓練用於在之前層的輸入上執行特徵選擇和特徵提取後,後面會跟上一個監督式的微調(fine-tuning)階段。SDA 只是將很多個去噪自動編碼器融合在了一起。一旦前面 k 層訓練完成,我們就可以訓練第 k+1 層,因為我們現在可以根據下面的層計算程式碼或隱含表徵。

一旦所有層都預訓練完成,網路就會進入一個被稱為微調的階段。在這裡我們會為微調使用監督學習機制,以最小化被監督任務上的預測誤差。然後,我們以訓練多層感知器的方式訓練整個網路。在這個階段,我們僅考慮每個自動編碼器的編碼部分。這個階段是有監督的,自此以後我們就在訓練中使用目標類別了。

1.3、使用程式碼示例解釋 SDA

這一節源自 deeplearning.net(對於想要理解深度學習的人來說,這個網站提供了很好的參考),其中使用案例對堆疊的去噪自動編碼器進行了很好的解釋。

我們可以以兩種方式看待堆疊的去噪自動編碼器:一是自動編碼器列表,二是多層感知器(MLP)。在預訓練過程中,我們使用了第一種方式,即我們將我們的模型看作是一組自動編碼器列表,並分開訓練每個自動編碼器。在第二個訓練階段,我們使用第二種方式。這兩種方式是有聯絡的,因為:

  • 自動編碼器和 MLP 的 sigmoid 層共享引數;
  • MLP 的中間層計算出的隱含表徵被用作自動編碼器的輸入。
class SdA(object):
   """Stacked denoising auto-encoder class (SdA)
   A stacked denoising autoencoder model is obtained by stacking several
   dAs. The hidden layer of the dA at layer `i` becomes the input of
   the dA at layer `i+1`. The first layer dA gets as input the input of
   the SdA, and the hidden layer of the last dA represents the output.
   Note that after pretraining, the SdA is dealt with as a normal MLP,
   the dAs are only used to initialize the weights.
   """
   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]
   ):
       """ This class is made to support a variable number of layers.
       :type numpy_rng: numpy.random.RandomState
       :param numpy_rng: numpy random number generator used to draw initial
                   weights
       :type theano_rng: theano.tensor.shared_randomstreams.RandomStreams
       :param theano_rng: Theano random generator; if None is given one is
                          generated based on a seed drawn from `rng`
       :type n_ins: int
       :param n_ins: dimension of the input to the sdA
       :type hidden_layers_sizes: list of ints
       :param hidden_layers_sizes: intermediate layers size, must contain
                              at least one value
       :type n_outs: int
       :param n_outs: dimension of the output of the network
       :type corruption_levels: list of float
       :param corruption_levels: amount of corruption to use for each
                                layer
         """
       self.sigmoid_layers = []
       self.dA_layers = []
       self.params = []
       self.n_layers = len(hidden_layers_sizes)
       assert self.n_layers > 0
       if not theano_rng:
       theano_rng = RandomStreams(numpy_rng.randint(2 ** 30))
       # allocate symbolic variables for the data
       self.x = T.matrix('x')   # the data is presented as rasterized images
       self.y = T.ivector('y')  # the labels are presented as 1D vector of
                                # [int] labels

self.sigmoid_layers 將會儲存 MLP 形式的 sigmoid 層,而 self.dA_layers 將會儲存與該 MLP 層關聯的去噪自動編碼器。接下來,我們構建 n_layers sigmoid 層和 n_layers 去噪自動編碼器,其中 n_layers 是我們的模型的深度。我們使用了多層感知器中引入的 HiddenLayer 類,但有一項修改:我們將 tanh 非線性替換成了 logistic 函式

在這裡插入圖片描述

我們連結了 sigmoid 層來構建一個 MLP,而且我們在構建自動編碼器時使得每個自動編碼器的編碼部分都與其對應的 sigmoid 層共享權重矩陣和偏置。


for i in range(self.n_layers):
           # construct the sigmoidal layer
           # the size of the input is either the number of hidden units of
           # the layer below or the input size if we are on the first layer
           if i == 0:
               input_size = n_ins
           else:
               input_size = hidden_layers_sizes[i - 1]
           # the input to this layer is either the activation of the hidden
           # layer below or the input of the SdA if you are on the first
           # layer
           if i == 0:
               layer_input = self.x
           else:
               layer_input = self.sigmoid_layers[-1].output
           sigmoid_layer = HiddenLayer(rng=numpy_rng,input=layer_input,n_in=input_size,n_out=hidden_layers_sizes[i],activation=T.nnet.sigmoid)
           # add the layer to our list of layers
           self.sigmoid_layers.append(sigmoid_layer)
           # its arguably a philosophical question...
           # but we are going to only declare that the parameters of the
           # sigmoid_layers are parameters of the StackedDAA
           # the visible biases in the dA are parameters of those
           # dA, but not the SdA
           self.params.extend(sigmoid_layer.params)
           # Construct a denoising autoencoder that shared weights with this
           # layer
           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)

現在我們只需要在這個 sigmoid 層上新增一個 logistic 層即可,這樣我們就有了一個 MLP。我們將使用 LogisticRegression 類,這個類是在使用 logistic 迴歸分類 MNIST 數字時引入的。

# We now need to add a logistic layer on top of the MLP
       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)
       # construct a function that implements one step of finetunining
       # compute the cost for second phase of training,
       # defined as the negative log likelihood
       self.finetune_cost = self.logLayer.negative_log_likelihood(self.y)
       # compute the gradients with respect to the model parameters
       # symbolic variable that points to the number of errors made on the
       # minibatch given by self.x and self.y
       self.errors = self.logLayer.errors(self.y)

SdA 類也提供了一種為其層中的去噪自動編碼器生成訓練函式的方法。它們會作為一個列表返回,其中元素 i 是一個函式——該函式實現了訓練對應於第 i 層的 dA 的步驟。

def pretraining_functions(self, train_set_x, batch_size):
       ''' Generates a list of functions, each of them implementing one
       step in trainnig the dA corresponding to the layer with same index.
       The function will require as input the minibatch index, and to train
       a dA you just need to iterate, calling the corresponding function on
       all minibatch indexes.
       :type train_set_x: theano.tensor.TensorType
       :param train_set_x: Shared variable that contains all datapoints used
                           for training the dA
       :type batch_size: int
       :param batch_size: size of a [mini]batch
       :type learning_rate: float
       :param learning_rate: learning rate used during training for any of
                             the dA layers
       '''
       # index to a [mini]batch
       index = T.lscalar('index')  # index to a minibatch

為了修改訓練過程中的受損水平或學習率,我們將它們與 Theano 變數聯絡了起來。

corruption_level = T.scalar('corruption')  # % of corruption to use
       learning_rate = T.scalar('lr')  # learning rate to use
       # begining of a batch, given `index`
       batch_begin = index * batch_size
       # ending of a batch given `index`
       batch_end = batch_begin + batch_size
       pretrain_fns = []
       for dA in self.dA_layers:
           # get the cost and the updates list
           cost, updates = dA.get_cost_updates(corruption_level,learning_rate)
           # compile the theano function
           fn = theano.function(
               inputs=[
                   index,
                   theano.In(corruption_level, value=0.2),
                   theano.In(learning_rate, value=0.1)
               ],
               outputs=cost,
               updates=updates,
               givens={
                   self.x: train_set_x[batch_begin: batch_end]
               }
           )
           # append `fn` to the list of functions
           pretrain_fns.append(fn)
       return pretrain_fns

現在任意 pretrain_fns[i] 函式都可以使用索引引數了,可選的有 corruption(受損水平)或 lr(學習率)。注意這些引數名是在它們被構建時賦予 Theano 變數的名字,而不是 Python 變數(learning_rate 或 corruption_level)的名字。在使用 Theano 時一定要記住這一點。我們用同樣的方式構建了用於構建微調過程中所需函式的方法(train_fn、valid_score 和 test_score)。

def build_finetune_functions(self, datasets, batch_size, learning_rate):
       '''Generates a function `train` that implements one step of
       finetuning, a function `validate` that computes the error on
       a batch from the validation set, and a function `test` that
       computes the error on a batch from the testing set
       :type datasets: list of pairs of theano.tensor.TensorType
       :param datasets: It is a list that contain all the datasets;
                        the has to contain three pairs, `train`,
                        `valid`, `test` in this order, where each pair
                        is formed of two Theano variables, one for the
                        datapoints, the other for the labels
       :type batch_size: int
       :param batch_size: size of a minibatch
       :type learning_rate: float
       :param learning_rate: learning rate used during finetune stage
       '''
       (train_set_x, train_set_y) = datasets[0]
       (valid_set_x, valid_set_y) = datasets[1]
       (test_set_x, test_set_y) = datasets[2]
       # compute number of minibatches for training, validation and testing
       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')  # index to a [mini]batch
       # compute the gradients with respect to the model parameters
       gparams = T.grad(self.finetune_cost, self.params)
       # compute list of fine-tuning updates
       updates = [
           (param, param - gparam * learning_rate)
           for param, gparam in zip(self.params, gparams)
       ]
       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'
       )
       # Create a function that scans the entire validation set
       def valid_score():
           return [valid_score_i(i) for i in range(n_valid_batches)]
       # Create a function that scans the entire test set
       def test_score():
           return [test_score_i(i) for i in range(n_test_batches)]
       return train_fn, valid_score, test_score

注意,valid_score 和 test_score 並不是 Theano 函式,而是分別在整個驗證集和整個測試集上迴圈的 Python 函式,可以在這些集合上產生一個損失列表。

總結

下面給出的幾行程式碼就構建了一個堆疊的去噪自動編碼器:

numpy_rng = numpy.random.RandomState(89677)
   print('... building the model')
   # construct the stacked denoising autoencoder class
   sda = SdA(
       numpy_rng=numpy_rng,
       n_ins=28 * 28,
       hidden_layers_sizes=[1000, 1000, 1000],
       n_outs=10
   )

該網路的訓練分兩個階段:逐層的預訓練,之後是微調。

對於預訓練階段,我們將在網路的所有層上進行迴圈。對於每個層,我們都將使用編譯過的實現 SGD 步驟的函式,以優化權重,從而降低該層的重構成本。這個函式將根據 pretraining_epochs 在訓練集上執行固定數量的 epoch。

#########################
   # PRETRAINING THE MODEL #
   #########################
   print('... getting the pretraining functions')
   pretraining_fns = sda.pretraining_functions(train_set_x=train_set_x,batch_size=batch_size)
   print('... pre-training the model')
   start_time = timeit.default_timer()
   ## Pre-train layer-wise
   corruption_levels = [.1, .2, .3]
   for i in range(sda.n_layers):
       # go through pretraining epochs
       for epoch in range(pretraining_epochs):
           # go through the training set
           c = []
           for batch_index in range(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 %f' % (i, epoch, numpy.mean(c, dtype='float64')))
   end_time = timeit.default_timer()
   print(('The pretraining code for file ' +
          os.path.split(__file__)[1] +
          ' ran for %.2fm' % ((end_time - start_time) / 60.)), file=sys.stderr)

這裡的微調迴圈和多層感知器中的微調過程很相似。唯一的區別是它使用了 build_finetune_functions 給出的函式。

執行程式碼

使用者可以通過呼叫以下 Python CLI 來執行該程式碼:

python code/SdA.py

預設情況下,該程式碼會為每一層執行 15 次預訓練 epoch,其批大小為 1。第一層的受損水平為 0.1,第二層為 0.2,第三層為 0.3。預訓練的學習率為 0.001,微調學習率為 0.1。預訓練耗時 585.01 分鐘,每 epoch 平均 13 分鐘。微調經歷了 36 epoch,耗時 444.2 分鐘,每 epoch 平均 12.34 分鐘。最後的驗證分數是 1.39%,測試分數是 1.3%。這些結果是在配置了 Intel Xeon E5430 @ 2.66GHz CPU 的機器上得到的,它有單執行緒的 GotoBLAS