1. 程式人生 > >Tensorflow——去噪自編碼器

Tensorflow——去噪自編碼器

TensorFlow實現自編碼器

  • 在深度學習中,自編碼器是一種非常有用的無監督學習模型。自編碼器(AutoEncoder),即可以用自身的高階特徵編碼自己。自編碼器也是一種神經網路,但它的輸入與輸出是一致的。自編碼器的思想就是藉助稀疏編碼,使用稀疏的一些高階特徵重新組合來重構自己。
  • 因此,自編碼器的特點就是期望輸入與輸出一致,再就是希望使用高階特徵來重構自己,而不是複製畫素點。
  • 自編碼器的輸入節點和輸出節點的數量是一致的,所以,為了使用少量稀疏的高階特徵來重構輸入,可以加入幾種限制。
    1. 如果限制中間隱含層節點的數量,就相當於一個降維的過程。如果再給中間隱含層的權重加一個L的正則,則可以根據懲罰係數控制隱含係數的稀疏程度,懲罰係數越大,學到的特徵組合越稀疏,實際使用的特徵數量越少。
    2. 如果給資料加入噪聲,那麼就是Denoising AutoEncoder(去噪自編碼器),將從噪聲中學習出資料的特徵。因為,完全複製不能去掉新增的噪聲,無法完全復原資料,只有學習資料的模式與結構,將無律的噪聲略去,才可以復原資料。去噪編碼器最常使用的噪聲就是加性高斯噪聲(Additive Gaussian Noise,AGN),也可以使用Masking Noise,即隨機遮擋的噪聲。

這次主要是根據《tensorflow實戰》一書的指導,自己實現一下去噪自編碼器。無噪聲的自編碼器只需要去掉噪聲,並保證隱含層節點小於輸入層節點;Masking Noise的自編碼器只需要將高斯噪聲改為隨機遮擋噪聲;VAE(Variational AutoEncoder)則相對複雜,關於VAE,接下來肯定是要學習的,到時候具體的過程也會記錄下來。

import numpy as np
import sklearn.preprocessing as prep
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

Numpy是常用的庫,是Python科學計算的基礎包。它提供了多維陣列物件、基於陣列的各種派生物件(例如,masked Array, 矩陣)。除此之外,還提供了各種各樣的加快陣列操作的例程,包括數學基本計算、邏輯、圖形操作、排序、選擇、輸入輸出,離散傅立葉變換、基礎線性代數、基礎統計操作、隨機模擬等等。還有Scikit-learn中的preprocessing模組,這是一個對資料進行預處理的常用模組,會使用其中的資料標準化的功能。Scikit-learn是一個功能強大的python包,這個星期剛好有模式識別的一個實驗,需要用到這個包,到時候進行學習總結。實現中依然使用的MNIST資料集。程式碼主要來自於TensorFlow的開源實現。

def xavier_init(fan_in,fan_out,constant = 1):
    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)

這裡使用了一種引數初始化的方法,xavier initialization,fan_in為輸入節點的數量,fan_out是輸出節點的數量。Xavier初始化器在Caffe的早期版本中被頻繁使用,特點是會根據某一層網路的輸入,輸出節點數量自動調整最合適的分佈。在Caffe的學習過程中,如果遇到,會進行記錄與學習。從數學的角度,Xavier就是讓權重滿足0均值,同時方差為2/in+out,分佈可以用均勻分佈或者高斯分佈。這裡使用的是均勻分佈,根據方差公式可以得出方差為所要求的。因此,這裡實現的就是標準的均勻分佈的Xavier初始化器。

  • 下面定義去噪自編碼的class。
class AdditiveGaussianNoiseAutoencoder(object):
    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) #將scale引數做成一個placeholder
        self.training_scale = scale
        network_weights = self._initialize_weights() #接下來會定義_initialize_weights()函式
        self.weights = network_weights

這裡定義了一個構建函式 _ init _(),包含這樣幾個輸入:n_input輸入變數數,n_hidden隱含層節點數,transfer_function隱含層啟用函式(預設為softplus),optimizer優化器(預設為Adam),scale高斯噪聲係數(預設為0.1)。

  • 接下來定義網路結構
        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']) #在輸出層進行資料復原,重建操作,即建立reconstruction層

在隱含層,現將輸入x加上了噪聲,然後用tf.matmul將加入噪聲的輸入與隱含層的權重w1相乘,並使用tf.add加上隱含層的偏置b1,最後使用self.transfer(即softpuls)對結果進行啟用函式處理。在reconstruction層不需要啟用函式。

        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)

定義自編碼器的損失函式,這裡使用的是平方誤差(squared error)作為cost,tf.subtract是計算輸出與輸入之差。最後建立了Session,初始化自編碼器的全部模型引數。

  • 定義引數初始化函式_initialize_weights
    def _initialize_weights(self):
        all_weights = dict() #建立一個字典,存入w1,b1,w2,b2
        all_weights['w1'] = tf.Variable(xavier_init(self.n_input,self.n_hidden)) #使用xavier_init函式初始化,返回一個比較適合於softplus等啟用函式的權重初始分佈
        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
  • 定義計算損失cost和執行一步訓練的函式partial_fit
    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

函式讓Session執行了兩個計算圖的節點,分別是損失cost,訓練過程optimizer。函式partial_fit做的就是用一個batch資料進行訓練並返回當前的損失cost。

  • 定義只求cost的函式calc_total_cost
    def calc_total_cost(self,X):
        return self.sess.run(self.cost,feed_ditc = {self.x:X,self.scale:self.training_scale})

這裡只讓Session執行一個計算圖節點self.cost。這個函式實在自編碼器訓練完畢後,在測試集上對模型效能進行評測時會用到的,不會像partial_fit那樣觸發訓練操作。

  • 定義transform函式,返回自編碼器隱含層的輸出結果
    def transform(self,X):
    return self.sess.run(self.hidden)

transform函式的目的是提供一個介面來獲取抽象後的特徵,自編碼器的隱含層的最主要功能就是學習資料中的高階特徵。

  • 定義generate函式
    def genreate(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})

generate函式將隱含層的輸出結果作為輸入,通過之後的重建層將提取到的高階特徵復原為原始資料。這個介面和前面的transform正好將整個自編碼器拆分為兩部分,這裡的generate介面是後半部分,將高階特徵復原為初始資料。

  • 定義reconstruct函式
    def reconstruct(self,X):
        return self.sess.run(self.reconstruction,feed_dict = {self.x:X,self.scale:self.training_scale})

reconstruct函式整體運行了一遍復原過程,即包括了transform和generate兩塊。

  • getWeights函式
    def getWeights(self):
        return self.sess.run(self.weights['w1']) #作用是獲取隱含層的權重w1
  • getBiases函式
    def getBiases(self):
        return self.sess.run(sefl.weights['b1']) #作用是獲取隱含層的偏執b1

到這裡,去噪自編碼器的class就定義完了,回顧一下,包括了構建函式,神經網路結構,損失函式,權重的初始化,以及一些成員函式。
下面就是用定義好的AGN自編碼器在MNIST資料集上的一些測試。

mnist = input_data.read_data_sets('MNIST_data',ont_hot = True)
#載入MNIST資料集
  • 定義一個對訓練,測試資料進行標準化處理的函式
def standard_scale(X_train,X_test):
    preprocessor = prep.StandarScaler().fit(X_train) #StandarScaler是sklearn.preprossing工具包裡面的類,先在訓練集上fit
    X_train = preprocessor.transform(X_train) #transform是返回隱含層的輸出結果,所以X_train就完成了標準化處理
    X_test = preprocessor.transform(X_test)
    return X_train,X_test
  • 再定義一個獲取隨機block資料的函式
def get_random_block_from_data(data,batch_size):
    start_index = np.random.randint(o,len(data) - batch_size)#從0到len(data) - batch_size之間取一個隨機數
    return data[start_index:(start_index + batch_size)]#從隨機數的位置開始,順序取一個 batch size的資料。這裡是不放回的抽樣
X_train,X_test = standard_scale(mnist.train.images,mnist.test.images) #使用定義好的函式,對訓練集和測試集進行標準化變換
  • 定義一些常用引數
n_samples = int(mnist.train.num_samples) #總訓練樣本數
training_epochs = 20 #最大訓練的輪數
batch_size = 128
display_step = 1 #每次顯示一次損失cost的輪數
  • 建立一個AGN自編碼器的例項
autoencoder  = AdditiveGaussianNoiseAutoencoder(n_input = 784,n_hidden = 200,transfer_function = tf.nn.softplus,optimizer = tf.train.AdamOptimizer(learining_rate = 0.001),scale = 0.01)
  • 下面開始訓練過程
for epoch in range(training_epochs):
    avg_cost = 0.
    total_batch = int(n_samples/batch_size)
    for i in range(total_batch):
        batch_xs = get_random_block_from_data(X_train,batch_size)
        cost = autoencoder.partial_fit(batch_xs)
        avg_cost += cost/n_samples * batch_size
    if epoch % display_step == 0:
        print("Epoch:",'%04d' % (epoch+1),"cost = ","{:.9f}".format(avg_cost))
  • 最後對訓練完的模型進行效能測試,對測試集X_test進行測試
print("Total cost:" + str(autoencoder.cal_total_cost(X_test))) #使用成員函式cal_total_cost計算平方誤差

可以發現,實現自編碼器和實現一個單隱層的神經網路差不多,只不過是在資料輸入時做了標準化,並加上了一個高斯噪聲,同時我們的輸出結果不是數字分類結果,而是復原的資料,不需要標註過的資料進行監督訓練。
自編碼器作為一種無監督學習的方法,它與其他無監督學習的主要不同在於,它不是對資料進行聚類,而是提取其中最有用,最頻繁出現的高階特徵,根據這些高階特徵重構資料。