TensorFlow實戰之實現自編碼器過程


        關於本文說明,已同步本人另外一個博客地址位於http://blog.csdn.net/qq_37608890,詳見http://blog.csdn.net/qq_37608890/article/details/79352212。

      本文根據最近學習TensorFlow書籍網絡文章的情況,特將一些學習心得做了總結,詳情如下.如有不當之處,請各位大拿多多指點,在此謝過。

一、相關概念

1、稀疏性(Sparsity)及稀疏編碼(Sparse Coding)

         Sparsity 是當今機器學習領域中的一個重要話題。

          Sparsity 的最重要的“客戶”大概要屬 high dimensional data 了吧。現在的機器學習問題中,具有非常高維度的數據隨處可見。例如,在文檔或圖片分類中常用的 bag of words 模型里,如果詞典的大小是一百萬,那么每個文檔將由一百萬維的向量來表示。高維度帶來的的一個問題就是計算量:在一百萬維的空間中,即使計算向量的內積這樣的基本操作也會是非常費力的。不過,如果向量是稀疏的的話(事實上在 bag of words 模型中文檔向量通常都是非常稀疏的),例如兩個向量分別只有L1 和 L2 個非零元素,那么計算內積可以只使用min(L1,L2) 次乘法完成。因此稀疏性對於解決高維度數據的計算量問題是非常有效的。

          稀疏編碼(Sparse Coding)算法是一種無監督學習方法,它用來尋找一組“超完備”基向量來更高效地表示樣本數據。稀疏編碼算法的目的就是找到一組基向量 ,使得我們能將輸入向量 表示為這些基向量的線性組合:

       雖然形如主成分分析技術(PCA)能使我們方便地找到一組“完備”基向量,但是這里我們想要做的是找到一組“超完備”基向量來表示輸入向量(也就是說,k > n)。超完備基的好處是它們能更有效地找出隱含在輸入數據內部的結構與模式。然而,對於超完備基來說,系數 ai 不再由輸入向量 唯一確定。因此,在稀疏編碼算法中,我們另加了一個評判標准“稀疏性”來解決因超完備而導致的退化(degeneracy)問題。要求系數 ai 是稀疏的意思就是說:對於一組輸入向量,我們只想有盡可能少的幾個系數遠大於零。選擇使用具有稀疏性的分量來表示我們的輸入數據是有原因的,因為絕大多數的感官數據,比如自然圖像,可以被表示成少量基本元素的疊加,在圖像中這些基本元素可以是面或者線。

         在早期,學者們研究稀疏編碼(Sparse Coding)時,搜集了大量黑白風景照片,且從中提取了16像素*16像素的圖片碎片。結果發現:幾乎所有的圖像碎片都可以用64種正交的邊組合得到,且組合出一張圖像碎片需要的邊的數量是很少的,即稀疏的。學者們同時也發現,聲音其實也存在這種情況,他們從大量的未標注的音頻中發現了20種基本結構,絕大多數聲音可以由這些基本結構線性組合得到。顯然,這就是特征的稀疏表達,使用少量的基本特征組合拼裝得到更高層抽象的特征。一般情況想,我們也需要多層的神經網絡,對每一層神經網絡而言,前一層的輸出都是未加工的像素,而這一層則是對像素進行加工組織成更高階的特征。

2、自編碼(AutoEncoder)

         顧名思義,即可以使用自身的高階特征編碼自己。自編碼器其實也是一種神經網絡,它的輸入和輸出是一致的,它借助稀疏編碼的思想,目標是使用稀疏的一些高階特征重新組合來重構自己,即 :對所有的自編碼器來講,目標都是樣本重構。

         在機器學習中,自編碼器的使用十分廣泛。自編碼器首先通過編碼層,將高維空間的向量,壓縮成低維的向量(潛在變量),然后通過解碼層將低維向量解壓重構出原始樣本

3、隱含層

        指輸入層和輸出層以外,中間的那些層。輸入層和輸出層是可見的,且層的結構是相對固定的,而隱含層結構不固定,相當於不可見。只要隱含的節點足夠多,即是只有一個隱含層的神經網絡也可以擬合任意函數。隱含層層數越多,越容易擬合復雜的函數。擬合復雜函數需要的隱含節點數目隨着層數的增多而呈指數下降。即層數越深,概念越抽象,這就是深度學習。

4、過擬合

      指模型預測准確率在訓練集上升高,但在測試集上反而下降。這是模型的泛化性不好,只記住了當前數據的特征。

5、Dropout

       Dropout:防止過擬合的一種方法。將神經網絡某一層的輸出節點數據隨機丟棄一部分。可以理解為是對特征的采樣。

6、優化函數

        優化調試網絡中的參數。一般情況下,在調參時,學習率的設置會導致最后結果差異很大。神經網絡通常不是凸優化,充滿局部最優,但是神經網絡可能有很多個局部最優都能達到良好效果,反而全局最優容易出現過擬合。

       對於SGD,通常一開始學習率大一些,可以快速收斂,但是訓練的后期,希望學習率可以小一些,可以比較穩定地落到一個局部最優解。

        除SGD之外,還有自適應調節學習率的Adagrad、Adam、Adadelta等優化函數

7、激活函數

         Sigmoid函數的輸出在(0,1),最符合概率輸出的定義,但局限性較大。
ReLU,當x<=0時,y=0;當x>0時,y=x。這非常類似於人類的閾值響應機制。ReLU的3個顯著特點:單側抑制;相對寬闊的興奮邊界;稀疏激活性。它是目前最符合實際神經元的模型。

 二、自解碼器算法原理      

1、BacsicAuto-Encoder

         Auto-Encoder(AE)是20世紀80年代晚期提出的,簡單講,AE可以被視為一個三層神經網絡結構:一個輸入層、一個隱藏層和一個輸出層,從數據規模上講,輸入層與輸出層具有相同的規模。

            

                             圖1  Auto-Encoder網絡結構示意圖

     其中,n表示輸入層(同時也是作為輸出層)的規模;m表示隱藏層的規模;分別表示輸出層、隱藏層和輸出層上的向量,也是各層上對應的數據個數,這里隱藏層h的數據低於輸入層和輸出層的數據,即x>h<y且x=y。根據輸入層x到隱藏層h的映射矩陣求出h,再根據隱藏層h到輸出層的映射矩陣求出y。分別表示隱藏層和輸出層上的偏置向量。表示輸入層與隱藏層之間的權值矩陣,即x到h的映射矩陣,是n乘m階矩陣;表示隱藏層與輸出層之間的權值矩陣,即h到y的映射矩陣,是m乘n階矩陣,也是W的逆矩陣。   

      針對AE而言,重要的是解決好矩陣W問題,一個好的矩陣W可以保證x完全等於y,可實際上很難,尤其當輸入層x的數據量大成千上百個時,x和y的差別就可以說明矩陣W的優良程度。總之,我們的工作就是要求出矩陣W,並使得x和y之間的差異盡可能小。下面給出下圖,進一步做出解釋

     

 

                                          

                         圖2 Auto-Encoder抽象結構 

       如圖2所示,AE由編碼器(Enconder)和解碼器(Decoder)兩部分構成。顯然,從輸入層到隱藏層屬於編碼過程,從隱藏層到輸出層屬於解碼過程。我們設定f和g分別表示編碼和解碼函數,結合上面的內容,我們可以給出f和g的方程,如下(1.1)和(1.2)式所示

 

                 

       其中 為編碼器的激活函數,通常情況下取Sigmoid函數,即 為解碼器的激活函數,一般取Sigmoid函數或恆等函數,即。權值矩陣一般取一些文獻資料中提及稱,這種情況下的AE具有tiedweights。本文指討論二者相等的情況。

       所以,截止目前,我們可知,AE的參數為

       

         假設目前我們有一個訓練集S=那么我們要考慮的問題是如何利用S訓練,首先是建立一個訓練目標。

       結合前面的梳理,我們可知,AE可以被看成一個普通的三層神經網絡,y也可以看成由x做的一個預測(prediction),且保證x和y盡可能接近,這種接近程度可以通過重構誤差(reconstruction error)函數L(x,y)來進行表達。

    根據解碼器激活函數的不同,這里L(x,y)一般有兩種取值方法:
             第一種,當為恆等函數時,取平方誤差(sequared error)函數
     第二種,當 為sigmoid函數(此種情況輸入)時,取交叉熵(cross-entropy)函數
    取得重構函數后,我們就可以對數據集S進行針對性的訓練,去得到一個整體的損失函數(loss function)(1.5)
                                

        將上面這個函數(1.5)進行極小化處理,我們就可以得到所需的參數

        注意,一般文獻中損失函數多數使用的是平均重構誤差(average reconstruction error),即下面函數(1.6)

                        

         對於一個給定的訓練集S而言,系數1/N並不對於上面這個損失函數最小化產生多大的影響,所以,為了簡便,這里忽略這個系數。

    最開始,AE只是作為一種降維技巧來使用的,視隱藏層h為x的一種降維表示,也要求m<n(h<x)。但是,目前來看,AE在m>=n的情況(有些文獻中稱為 over-complete setting)下應用更多,以便獲取更高維數更有意義的表達方式。當然,在次情況下,也會有一個問題不可忽略:若直接對損失函數(1.5)進行極小化的而沒有加入任何其他限制條件的話,AE在這里學到的很可能是一個恆等函數(where the auto-encoder could perfectly reconstruction the input without needing to extract any useful feature),這種結果是不符合預期的。需要解決,例如  , 在損失函數中加入稀疏性限制(Sparse Auto-Encoder),或在網絡中引入隨機性(如RBN,Denoising Auto-Encoder)等。
    下面先介紹 Regularized Auto-Encoer 和Sparse Auto-Encoder,其余的AE變種算法,后續有機會繼續梳理。

 2、 Regularized Auto-Encoder

         我們在損失函數(1.5)中加入正則項,便可以得到所謂的Regularized Auto-Encoder。 常見的正則化有L1 正則和 L2正則。以L2正則為例進行介紹,損失  函數則變為如下(2.7):

                                
      其中      為權值矩陣W的元素。
公式(2.7)中的即為L2正則項,也叫做權重衰減(weight-decay)項(下標中的 就是weight-decay的意思),lambda為權重衰減參數,用來控制公式中兩項的相對重要性。

3、Sparse Auto-Encoder
         由公式(2.7)可以看出,權重衰減項實際上是對權重提出一些要求,也就是要求它們不能過大,否則會被懲罰。這里所講的Sparse Auto-Encoder 是對隱藏層上神經元的激活度提出要求,使其滿足一定的稀疏性。
        關於隱藏層上神經元激活度如何刻畫的問題,下面展開來看:
            假設表示輸入為x時,隱藏層上第j個神經元的激活度(  是(1.1)中向量h的第j個分量),則(3.8)

         表示隱藏層上第j個神經元在訓練集 上的平均激活度。為了保證隱藏層上的神經元大部分時間被抑制為零或者接近於零(即稀疏性限制),這里要求(3.9)
其中,為稀疏性參數,一般情況下取一個很小的數(如   )。對於那些與有顯著不同的,會進行懲罰。這個限制方法的實現有多種,這里以一種基於KL divergence的方法,即引入(3.10)

其中,(3.11)

函數 具有如下性質:
      隨着和   之間的差異增大而單調遞增,尤其當 時,存在達到最小值。所以,如果將(3.10)加入到損失函數里,則最小化損失函數即可達到使得盡可能靠近 的效果。這樣來看,Sparse Auto-Encoder的損失函數就可以表達為(3.12)
                          
         這里beta為控制稀疏性懲罰項的權重系數。

        我們也可以將L2正則和稀疏性限制結合起來使用,這時的損失函數也就變為(3.13)

                       

三、實現去噪自編碼器過程

1、代碼實現過程如下

# 這里以最具代表性的去噪自編碼器為例。

#導入MNIST數據集

import numpy as np

import sklearn.preprocessing as prep

import tensorflow as tf

from tensorflow.examples.tutorials.mnist import input_data


#這里使用一種參數初始化方法xavier initialization,需要對此做好定義工作。

#Xaiver初始化器的作用就是讓權重大小正好合適。

#這里實現的是標准均勻分布的Xaiver初始化器。

def xavier_init(fan_in, fan_out, constant = 1):

"""

目的是合理初始化權重。

參數:

fan_in --行數;

fan_out -- 列數;

constant --常數權重,條件初始化范圍的倍數。

return 初始化后的權重tensor.

"""

low = -constant * np.sqrt(6.0 / (fan_in + fan_out))

high = constant * np.sqrt(6.0 / (fan_in + fan_out))

return tf.random_uniform((fan_in, fan_out),

minval = low, maxval = high,

dtype = tf.float32)


#定義一個去噪的自編碼類

class AdditiveGaussianNoiseAutoencoder(object):

"""

__init__() :構建函數;

n_input : 輸入變量數;

n_hidden : 隱含層節點數;

transfer_function: 隱含層激活函數,默認是softplus;

optimizer : 優化器,默認是Adam;

scale : 高斯噪聲系數,默認是0.1;

"""

def __init__(self, n_input, n_hidden, transfer_function =tf.nn.softplus, optimizer = tf.train.AdamOptimizer(),

scale = 0.1):

self.n_input = n_input

self.n_hidden = n_hidden

self.transfer = transfer_function

self.scale = tf.placeholder(tf.float32)

self.training_scale = scale

network_weights = self._initialize_weights()

self.weights = network_weights


# 定義網絡結構,為輸入x創建一個維度為n_input的placeholder,然后

#建立一個能提取特征的隱含層。

self.x = tf.placeholder(tf.float32, [None, self.n_input])

self.hidden = self.transfer(tf.add(tf.matmul(self.x +scale * tf.random_normal((n_input,)),

self.weights['w1']),

self.weights['b1']))

self.reconstruction = tf.add(tf.matmul(self.hidden,self.weights['w2']), self.weights['b2'])


#首先,定義自編碼器的損失函數,在此直接使用平方誤差(SquaredError)作為cost。

#然后,定義訓練操作作為優化器self.optimizer對損失self.cost進行優化。

#最后,創建Session,並初始化自編碼器全部模型參數。

self.cost = 0.5 *tf.reduce_sum(tf.pow(tf.subtract(self.reconstruction, self.x), 2.0))

self.optimizer = optimizer.minimize(self.cost)


init = tf.global_variables_initializer()

self.sess = tf.Session()

self.sess.run(init)


def _initialize_weights(self):

all_weights = dict()

all_weights['w1'] = tf.Variable(xavier_init(self.n_input,self.n_hidden))

all_weights['b1'] = tf.Variable(tf.zeros([self.n_hidden],dtype = tf.float32))

all_weights['w2'] = tf.Variable(tf.zeros([self.n_hidden,self.n_input], dtype = tf.float32))

all_weights['b2'] = tf.Variable(tf.zeros([self.n_input],dtype = tf.float32))

return all_weights


def partial_fit(self, X):

cost, opt = self.sess.run((self.cost, self.optimizer),feed_dict = {self.x: X,

self.scale: self.training_scale})

return cost


def calc_total_cost(self, X):

return self.sess.run(self.cost, feed_dict = {self.x: X,

self.scale:self.training_scale})

#定義一個transform函數,以便返回自編碼器隱含層的輸出結果,目的是提供一個接口來獲取抽象后的特征。

def transform(self, X):

return self.sess.run(self.hidden, feed_dict = {self.x: X,

self.scale:self.training_scale})


def generate(self, hidden = None):

if hidden is None:

hidden = np.random.normal(size = self.weights["b1"])

return self.sess.run(self.reconstruction, feed_dict ={self.hidden: hidden})

def reconstruct(self, X):

return self.sess.run(self.reconstruction, feed_dict ={self.x: X,

self.scale: self.training_scale})


def getWeights(self): #獲取隱含層的權重w1.

return self.sess.run(self.weights['w1'])


def getBiases(self): #獲取隱含層的偏執系數b1.

return self.sess.run(self.weights['b1'])

#利用TensorFlow提供的讀取示例數據的函數載入MNIST數據集。

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

#定義一個對訓練、測試數據進行標准化處理的函數。

def standard_scale(X_train, X_test):

preprocessor = prep.StandardScaler().fit(X_train)

X_train = preprocessor.transform(X_train)

X_test = preprocessor.transform(X_test)

return X_train, X_test


def get_random_block_from_data(data, batch_size):

start_index = np.random.randint(0, len(data) - batch_size)

return data[start_index:(start_index + batch_size)]


X_train, X_test = standard_scale(mnist.train.images,mnist.test.images)


n_samples = int(mnist.train.num_examples)

training_epochs = 20

batch_size = 128

display_step = 1

autoencoder = AdditiveGaussianNoiseAutoencoder(n_input = 784,

n_hidden = 200,

transfer_function =tf.nn.softplus,

optimizer =tf.train.AdamOptimizer(learning_rate = 0.001),

scale = 0.01)

for epoch in range(training_epochs):

avg_cost = 0.

total_batch = int(n_samples / batch_size)

# Loop over all batches

for i in range(total_batch):

batch_xs = get_random_block_from_data(X_train, batch_size)


# Fit training using batch data

cost = autoencoder.partial_fit(batch_xs)

# Compute average loss

avg_cost += cost / n_samples * batch_size

# Display logs per epoch step

if epoch % display_step == 0:

print("Epoch:", '%04d' % (epoch + 1), "cost=","{:.9f}".format(avg_cost))

#最后對訓練完的模型進行性能測試。

print("Total cost: " +str(autoencoder.calc_total_cost(X_test)))

 

2、執行結果如下

Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
Epoch: 0001 cost= 18871.253996591
Epoch: 0002 cost= 12308.673515909
Epoch: 0003 cost= 10227.495348864
Epoch: 0004 cost= 11243.596613636
Epoch: 0005 cost= 10782.029647727
Epoch: 0006 cost= 9165.328120455
Epoch: 0007 cost= 8487.490198295
Epoch: 0008 cost= 9195.667004545
Epoch: 0009 cost= 9026.087407955
Epoch: 0010 cost= 8301.502948295
Epoch: 0011 cost= 9921.268600568
Epoch: 0012 cost= 8789.966229545
Epoch: 0013 cost= 9115.636243182
Epoch: 0014 cost= 8993.681156818
Epoch: 0015 cost= 7670.030270455
Epoch: 0016 cost= 8108.834190341
Epoch: 0017 cost= 7897.135417045
Epoch: 0018 cost= 8332.914957955
Epoch: 0019 cost= 8091.132888068
Epoch: 0020 cost= 7822.976949432
Total cost: 725054.5

 

 

四、小結

    
           至此,我們可以發現,從實現的角度而言,自編碼器其實和一個單隱含層的神經網絡差不多,只是自編碼器在數據輸入時做了標准化處理,且加上了一個高斯噪聲,同時我們的輸出結果不是數字分類結果,而是復原的數據,因此不需要用標注過的數據進行監督訓練。自編碼器作為一種無監督學習方法,它與其他無監督學習的區別主要在於:它不是對數據進行聚類,而是把數據中最有用、最頻繁的高階特征提取出來,然后根據這些高階特征進行重構數據。在深度學習發展早期非常流行的DBN,也是依靠這種思想,先對數據進行無監督學習,提取到一些有用的特征,將神經網絡權重初始化到一個較好的分布,然后再使用有標注的數據進行監督訓練,即對權重進行fine-tune。

         現實生活中,大部分數據是沒有標准信息的,但人腦比較擅長處理這些數據,會提取出其中的高階抽象特征,並使用在別的地方。自編碼器作為深度學習在無監督領域的應用的確非常成功,同時無監督學習也將成為深度學習一個重要發展方向。

 

參考資料   主要參考資料《TensorFlow實戰》(黃文堅  唐源 著)(電子工業出版社)

 


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM