tensoflow實現自編碼器
- 自編碼器介紹
- 自編碼器可以用自身的高階特徵編碼自己,實際上是一種神經網路,輸入輸出一致,藉助了稀疏編碼的思想,用稀疏的一些高階特徵重新組合來重構自己。
- 圖中,虛線藍色框內就是一個自編碼器的基本模型,它由編碼器(Encoder)和解碼器(Decoder)兩部分組成,本質上兩者都是對輸入信號做某種變換。編碼器將輸入信號x變換成編碼信號y,而解碼器是將編碼信號y轉換成輸出信號,即y=f(x)=g(y)=g(f(x))。
- 而整個自編碼器的目的就是,讓最終的輸出信號儘可能的復現輸入x。但是,這裡問題就來了——如果f和g都是恆等映射,那不有就恆等式=x了?確實如此,但這樣的變化沒有任何意義。因此,我們需要對中間的信號y(也叫做「編碼」)做一定的約束,這樣系統往往能學出很有趣的編碼變換f和編碼y。而這個約束,一般使編碼y的數據複雜程度遠小於x,並且能對原始x的數據信號做接近全部的保留。
- 這裡強調一點,對於自編碼器,我們往往並不關心輸出的是什麼(反正只是復現輸入),我們真正關心的是中間層的編碼,或者說是從輸入到編碼的映射f。可以這麼想,在我們強迫編碼y和輸入x不同的情況下,系統還能夠去復原原始信號x,那麼說明編碼y已經承載了原始數據的所有信息,但以一種不同的形式!這就是特徵提取啊,而且是自動學出來的!其實,自動學習原始數據的特徵表達也是神經網路和深度學習的核心目的之一。
2.自編碼器加入限制的問題
- 如果限制中間隱含層節點的數量,可視作一個降維的過程,則不可能複製所有的節點出來,只能學習數據中最重要的特徵復原,將可能不太相關的內容去除掉。如果再給權重加上一個L1的正則,則可以根據懲罰係數控制隱含節點的稀疏程度,懲罰係數越大,學到的特徵數量越少。
- (關於L1正則化的補充)
- (從上面這張圖可以看出,損失函數的主體是一個凸函數,它的等高線均勻地向外擴散。因此正方形的L1正則更容達到參數的稀疏性,而圓形的L2正則則不太容易達到這個效果。所以多年以來,大家一直在心中默默記住:L1可以達到參數稀疏化的效果。)
- 如果給數據加入雜訊的限制,那麼就是去噪自編碼器(Denoising AutoEncoder),將從雜訊中學習到數據的特徵。同樣也不能完全複製節點,完全複製並不能去除添加的雜訊,無法復原數據,所以只能隵數據頻繁出現的模式,將無規律的雜訊略去才能復原。
3.去噪自編碼器最長使用的雜訊是加性高斯雜訊,也可使用Masking Noise,即有隨機遮擋的雜訊(和後面提到的dropout類似的方法)。Hinton的思路是先用自編碼器的方法進行無監督的預訓練,提取特徵並且初始化權重,然後用標註的信息進行監督式的訓練。
4.下面開始用tensorflow實現最具代表性的去噪自編碼器
#首先導入常用的庫import numpy as npimport sklearn.preprocessing as prepimport tensorflow as tffrom tensorflow.examples.tutorials.mnist import input_data#自編碼器中會用到一種xavier initialization的初始化方法,它會根據某一層網路的輸入,輸出節點調整最合適的分布。見附錄1def 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)#tf.random_uniform見附錄2class 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 #隱含層激活函數,默認是softplus,見附錄4 self.scale=tf.placeholder(tf.float32) #高斯雜訊係數,默認為0.1 self.training_scale=scale network_weights=self._initialize_weights() #後面會定義該函數,函數的下劃線問題件附錄3 self.weights=network_weights#接下來開始定義網路結構 self.x=tf.placeholder(tf.float32,[None,self.n_input]) #x為 任意長度xn_input的矩陣 self.hidden=self.transfer(tf.add(tf.matmul(self.x+scale*tf.random_normal((n_input,)), self.weights[w1]),self.weights[b1])) #self.x+scale*...是為x加入雜訊,用self.transfer進行結果的激活處理 self.reconstruction = tf.add(tf.matmul(self.hidden, self.weights[w2]), self.weights[b2]) #經過隱含層後將在輸出層進行數據復原和重建操作,此處不需要激活函數。 #以上出現的self.weights[]各類將會在後面給出具體定義。#接下來定義自編碼器的損失函數,使用平方誤差進行計算 self.cost=0.5*tf.reduce_sum(tf.pow(tf.subtract(self.reconstruction,self.x),20))#self.x輸入,self.reconstruction輸出,tf.pow指數函數,tf.subtract減法#公式見附錄5 self.optimizer=optimizer.minimize(self.cost)#optimizer為類中預先定義的optimizer,設定優化目標為cost init=tf.global_variables_initializer()#初始化所有的模型參數,注意此處有tensorflow版本跟新 self.sess=tf.Session() self.sess.run(init)#sess.run()在被調用時才會啟動一個計算#接下來實現之前的初始化參數initialize_weights def _initialize_weights(self): #私有函數只用於標明 all_weights=dict()#先創建一個字典 all_weights[w1]=tf.Variable(xavier_init(self.n_input,self.n_hidden)) #採用xavier初始化參數,得到較好的權重分布 all_weights[b1]=tf.Variable(tf.zeros([self.n_hidden],dtype=tf.float32)) #偏置無需初始化,全部置0 all_weights[w2]=tf.Variable(tf.zeros([self.n_hidden,self.n_input],dtype=tf.float32)) #輸出層沒有使用激活函數所以直接初始化為0 all_weights[b2]=tf.Variable(tf.zeros([self.n_input],dtype=tf.float32)) #關於tf.Variable見附錄6#接下來定義一個計算cost和執行一步計算的函數 #用一個batc h數據進行訓練並返回當前損失 def partial_fit(self,x): cost,opt=self.sess.run((self.cost,self.optimizer),feed_dict={self.x:x, self.scale:training_scale}) return cost#下面的函數只求損失,傳入參數與partial_fit一致,是在訓練完畢後評估性能 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 })#generate函數將隱含層輸出作為輸入,將高階特徵復原為原始數據 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}) #resconstruction的公式見上文有提及,見附錄7@#reconstruct 整體運行一遍復原過程,包括提取高階特徵和通過高階特徵復原 def reconstruct(self, X): return self.sess.run(self.reconstruction, feed_dict = {self.x: X, self.scale: self.training_scale }) def getWeights(self): return self.sess.run(self.weights[w1]) def getBiases(self): return self.sess.run(self.weights[b1]) 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_testdef get_random_block_from_data(data, batch_size): start_index = np.random.randint(0, len(data) - batch_size) return data[start_indexstart_index + batch_size)]X_train, X_test = standard_scale(mnist.train.images, mnist.test.images)n_samples = int(mnist.train.num_examples)training_epochs = 20batch_size = 128display_step = 1autoencoder = 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)))
附錄
1:
Xavier初始化的實現就是下面的均勻分布:(其中j為輸入節點,j+1為輸出節點。)
————————————————————————————————————
——————————————————————————————————————
2:
tf.random_normal(shape,mean=0.0,stddev=1.0,dtype=tf.float32,seed=None,name=None)
tf.truncated_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
tf.random_uniform(shape,minval=0,maxval=None,dtype=tf.float32,seed=None,name=None)
這幾個都是用於生成隨機數tensor的。尺寸是shape
random_normal: 正太分布隨機數,均值mean,標準差stddev
truncated_normal:截斷正態分布隨機數,均值mean,標準差stddev,不過只保留[mean-2*stddev,mean+2*stddev]範圍內的隨機數
random_uniform:均勻分布隨機數,範圍為[minval,maxval]
tf.random_normal(shape[1,5],mean=0.0,stddev=1.0,dtype=tf.float32,seed=None,name=None)
3:
變數:
前帶_的變數: 標明是一個私有變數, 只用於標明, 外部類還是可以訪問到這個變數
前帶兩個_ ,後帶兩個_ 的變數: 標明是內置變數,
大寫加下劃線的變數: 標明是 不會發生改變的全局變數
函數:
前帶_的變數: 標明是一個私有函數, 只用於標明,
前帶兩個_ ,後帶兩個_ 的函數: 標明是特殊函數
4:
激活函數是用來加入非線性因素的,因為線性模型的表達能力不夠
傳統神經網路中最常用的兩個激活函數,Sigmoid系(Logistic-Sigmoid、Tanh-Sigmoid)被視為神經網路的核心所在。
從數學上來看,非線性的Sigmoid函數對中央區的信號增益較大,對兩側區的信號增益小,在信號的特徵空間映射上,有很好的效果。
從神經科學上來看,中央區酷似神經元的興奮態,兩側區酷似神經元的抑制態,因而在神經網路學習方面,可以將重點特徵推向中央區,將非重點特徵推向兩側區。
無論是哪種解釋,看起來都比早期的線性激活函數(y=x),階躍激活函數(-1/1,0/1)高明了不少。
這個模型對比Sigmoid系主要變化有三點:①單側抑制 ②相對寬闊的興奮邊界 ③稀疏激活性(重點,可以看到紅框里前端狀態完全沒有激活)
同年,Charles Dugas等人在做正數回歸預經元激活頻率函數有神似的地方,這促成了新的激活函數的研究。測論文中偶然使用了Softplus函數,Softplus函數是Logistic-Sigmoid函數原函數。
Softplus(x)=log(1+ex)
按照論文的說法,一開始想要使用一個指數函數(天然正數)作為激活函數來回歸,但是到後期梯度實在太大,難以訓練,於是加了一個log來減緩上升趨勢。
加了1是為了保證非負性。同年,Charles Dugas等人在NIPS會議論文中又調侃了一句,Softplus可以看作是強制非負校正函數
max(0,x)平滑版本。
偶然的是,同是2001年,ML領域的Softplus/Rectifier激活函數與神經科學領域的提出腦神經元激活頻率函數有神似的地方,這促成了新的激活函數的研究。
5:
cost=
6:
變數維護圖執行過程中的狀態信息。下面的例子演示了如何使用變數實現一個簡單的計數器。
# 創建一個變數, 初始化為標量 0state = tf.Variable(0, name="counter")# 創建一個operation, 其作用是使state 增加 1one = tf.constant(1)new_value = tf.add(sate,one)update = tf.assign(state, new_value)# 啟動圖後, 變數必須先經過初始化 (init) op 初始化,# 首先必須增加一個初始化 op 到圖中.init_op = tf.initialize_all_variables()with tf.Session() as sess: sess.run(init_op) # 運行 init_op print sess.run(state) # 列印出事狀態 for _ in range(3): sess.run(update) print sess.run(state)# 輸出:# 0# 1# 2# 3
7:
重建操作的公式
最新版本請移步我的文章Udacity tutorial 2
推薦閱讀:
※AutoML是如何打敗AI工程師的?
※物聯網:Facebook人工智慧正在教機器人「討價還價」
※人工智慧在不同產業的應用
※看似光鮮實則曲折,搜狗跟風人工智慧的問題出在哪?
※突圍機器寫詩紅海,360這款黑科技讓全民皆「詩人」
TAG:人工智慧 |