人們平時看一幅圖片時,並不是像計算機那樣逐個像素去讀,一般是掃一眼物體,大致能得到需要的信息,如形狀、顏色和特征等,那么怎么讓機器也具有這項能力呢?這里就介紹一下自編碼網絡。
自編碼網絡是非監督學習領域中的一種,可以自動從無標注的數據中學習特征,是一種以重構輸入信息為目標的神經網絡,它可以給出比原始數據更好的特征描述,具有較強的特征學習能力,在深度學習中常用自編碼網絡生成的特征來取代原始數據,以取得更好的效果。
那么這種網絡有什么實際應用呢?自編碼器是當前深度學習研究的熱點之一,有很多重要的應用領域,這里僅舉一個有趣的例子,大家還記得前一段時間百度推出的上傳你的照片,系統幫你找到與你像的明星嗎?其實這個功能就可以用自編碼器來實現,首先,我們將已經訓練好的自編碼器的輸入層和中間層組成的網絡拿出來,將所有明星的臉進行壓縮,得到一個人臉向量,保存起來,然后,當普通用戶上傳了自己的照片后,系統將用戶照片輸入自編碼器的輸入層,從中間層得到該用戶的人臉向量,最后拿這個用戶的人臉向量與明星們的人臉向量進行比較,找出向量距離最近的一個或幾個明星,將明星的照片作為與用戶最像的明星照片,返回給用戶。由於百度這項服務推出的時間較早,應該不是基於自動編碼器實現的,但是使用自動編碼器,完全可以實現這個功能。
上一節我們介紹到了受限玻爾茲曼機模型,也可以重構輸入信息,提取特征信息。他們具體有什么差異呢?下面我將會詳細介紹一下自編碼器,以及和受限玻爾茲曼機的區別。
一 自編碼網絡和受限玻爾茲曼機
自編碼器(AE)和受限玻爾茲曼機(RBM)是在深度神經網絡領域廣泛使用的兩種常見的基礎性結構。它們都可以作為無監督學習的框架,通過最小化重構誤差,提取系統的重要特征;更重要的是,通過多層的堆疊和逐層的預訓練,層疊式自動編碼器和深度信念網絡都可以在后續監督學習的過程中,幫助整個神經網絡更好更快地收斂到最小值點。
1.最簡單的自編碼器網絡
什么是自編碼?所謂自編碼就是自己給自己編碼,再簡單點就是令輸出等於輸入自己。以一個簡單三層網絡為例如下: 、

自編碼器通過隱藏層對輸入進行了壓縮,並在輸出層中解壓縮,整個過程肯定會丟失信息,但是通過訓練我們能夠使丟失的信息盡量減少,最大化的保留其主要特征。
這里我們假設輸出等於輸入來訓練這個網絡參數(可能訓練好的網絡參數不可能讓輸出百分百等於輸入,至少會非常接近吧)。那么這個網絡在輸入確定了以后(這時輸出也就確定了吧),唯一需要確定的就是隱含層的個數了吧,以上述這個圖為例,我們可以看到,網絡的輸入與輸出都是一個6維的向量,而隱含層是3個,也就是3維。
那么這種自編碼有什么意義呢?它又有什么用呢?可以看到,自編碼可以使得網絡通過學習轉化成一組另外的量,這組量又可以通過解碼恢復成原始的量,這樣一來一回的過程看上去沒什么用,但是你把分開着來看就會發現很有用,原始的量可以通過編碼映射成另一組量吧,這一組量既然可以通過譯碼恢復成原始的量,說明了什么?中間這一層的輸出是不是就是原始輸入的另外一種表達了?是的。這就好比一個人,你看得到時候直接看就是一個人,當你看不到人的時候,比如說你聽到了他的名字,你也知道這個名字表示的就是這個人。所以這個名字就是這個人的另一種條件下的新特征,而往往這種新特征更能去分這個人是誰。
好了再看看上面這個圖,輸入6維,隱含層以后變成了3維,輸出還是6維,我們單看到隱含層發現了什么?是不是輸入從6維降到了3維?但是這3為在某種意義上還是原始數據的典型特征吧,言外之意是不是相當於降維了。如果知道主成分分析法(PCA)的人應該了解,PCA方法其實就是實現數據降維的,如果自編碼網絡激活函數不使用Sigmoid函數,而使用心形函數,那么便是PCA模型了。在這里我們通過這種自編碼,規定隱含層神經元的個數以后,通過自編碼的訓練,讓網絡的輸出盡可能的等於輸入,待自編碼完成后,那么輸入通過隱含層的輸出就相當於降維了吧(前提是隱含層的神經元個數要小於輸入維數,這樣才叫降維,否則的話叫升維),說到升維,了解PCA的朋友你們有沒有試過PCA升維呢?PCA能不能升維呢?哈哈,貌似不能,沒試過。但是理論上是可以的。那么升維相當於將信息復雜化,這種操作有沒有用呢?可能還是有用的吧,了解SVM的朋友知道,SVM里面就有將非線性數據通過升維以后可以在線性范圍內可分,那么這里的升維是不是也能將原始非線性的數據變到線性呢?可以去試試看,應該有那么個意思。
好了說了這么多,我們還是來看降維的情況,通過自編碼實現數據的降維思想最初是2006年深度學習領域大牛Hinton想出來的,並且發表在頂級期刊Science上,文章的出處在這里:“reducing the dimensionality of data with neural networks”
該篇經典之作也被視為深度學習的開山之作,自此以后深度學習火了起來,並且逐漸打敗傳統模式識別領域的淺層學習算法。我們知道,機器學習或者模式識別,對數據的主要工作在做什么?無非提取數據的主要特征,那么以前可能所有的特征要么是人為設計出來,要么是淺層學習出來的,像PCA,他們雖然一定程度上有用,但是相對於深度學習這種將數據的各個層次的特征都學習出來了的相比自然弱了不少,這也是深度學習的最強大之處。
2.受限玻爾茲曼機
上一節已經詳細介紹了RBM網絡,這里不再具體介紹,只做一個簡單的總結。
RBM是實現深度學習的另一種神經網絡結構。RBM的基本功能與AE類似,RBM同樣也可以對數據進行編碼,多層RBM也可以用於對神經網絡進行初始化的預訓練。
RBM是一種基於能量的神經網絡模型,它具有深刻的統計物理背景。RBM訓練的目標即為讓RBM網絡表示的概率分布與輸入樣本的分布盡可能地接近,這一訓練同樣是無監督式的。在實踐中,常常用對比散度(CD)的方法來對網絡進行訓練,CD算法較好地解決了RBM學習效率的問題。在用CD算法開始進行訓練時,所有可見神經元的初始狀態被設置成某一個訓練樣本,將這些初始參數代入到激活函數中,可以算出所有隱藏層神經元的狀態,進而用激活函數產生可見層的一個重構。通過比照重構結果和初始狀態,RBM的各參數可以得以更新,從這一點來看,用CD算法對RBM進行訓練與AE的訓練是非常相似的。近年來,CD算法有許多改進,例如:持續性對比散度(PCD)和快速持續性對比散度(FPCD)等;而在訓練RBM時,也可以利用非CD式的算法,例如比率匹配等。
3.自編碼器與受限玻爾茲曼機的區別
AE與RBM兩種算法之間也有着重要的區別,這種區別的核心在於:AE希望通過非線性變換找到輸入數據的特征表示,它是某種確定論性的模型;而RBM的訓練則是圍繞概率分布進行的,它通過輸入數據的概率分布(能量函數)來提取高層表示,它是某種概率論性的模型。從結構的角度看,AE的編碼器和解碼器都可以是多層的神經網絡,而通常我們所說的RBM只是一種兩層的神經網絡。在訓練AE的過程中,當輸出的結果可以完全重構輸入數據時,損失函數 L 被最小化,而損失函數常常被定義為輸出與輸出之間的某種偏差(例如均方差等),它的偏導數便於直接計算,因此可以用傳統的BP算法進行優化。RBM最顯著的特點在於其用物理學中的能量概念重新描述了概率分布,它的學習算法基於最大似然,網絡能量函數的偏導不能直接計算,而需要用統計的方法進行估計,因此需要用CD算法等來對 RBM 進行訓練。
二 自編碼網絡的代碼實現
1.提取圖片的特征,並利用特征還原圖片
通過構建一個量程的自編碼網絡,將MNIST數據集的數據特征提取處來,並通過這些特征重建一個MNIST數據集 ,下面以MNIST數據集為例,將其像素點組成的數據(28x28)從784維降維到256,然后再降到128,最后再以同樣的方式經過256,最終還原到原來的圖片。
import tensorflow as tf import numpy as np import matplotlib.pyplot as plt from tensorflow.examples.tutorials.mnist import input_data def two_layer_auto_encoder(): ''' 通過構建一個量程的自編碼網絡,將MNIST數據集的數據特征提取處來,並通過這些特征重建一個MNIST數據集 下面以MNIST數據集為例,將其像素點組成的數據(28x28)從784維降維到256,然后再降到128,最后再以同樣的 方式經過256,最終還原到原來的圖片。 ''' ''' 導入MNIST數據集 ''' #mnist是一個輕量級的類,它以numpy數組的形式存儲着訓練,校驗,測試數據集 one_hot表示輸出二值化后的10維 mnist = input_data.read_data_sets('MNIST-data',one_hot=True) print(type(mnist)) #<class 'tensorflow.contrib.learn.python.learn.datasets.base.Datasets'> print('Training data shape:',mnist.train.images.shape) #Training data shape: (55000, 784) print('Test data shape:',mnist.test.images.shape) #Test data shape: (10000, 784) print('Validation data shape:',mnist.validation.images.shape) #Validation data shape: (5000, 784) print('Training label shape:',mnist.train.labels.shape) #Training label shape: (55000, 10) ''' 定義參數,以及網絡結構 ''' n_input = 784 #輸入節點 n_hidden_1 = 256 #第一次256個節點 n_hidden_2 = 128 #第二層128個節點 batch_size = 256 #小批量大小 training_epochs = 20 #迭代輪數 display_epoch = 5 #迭代2輪輸出5次信息 learning_rate = 1e-2 #學習率 show_num = 10 #顯示的圖片個數 #定義占位符 input_x = tf.placeholder(dtype=tf.float32,shape=[None,n_input]) #輸入 input_y = input_x #輸出 #學習參數 weights = { 'encoder_h1':tf.Variable(tf.random_normal(shape=[n_input,n_hidden_1])), 'encoder_h2':tf.Variable(tf.random_normal(shape=[n_hidden_1,n_hidden_2])), 'decoder_h1':tf.Variable(tf.random_normal(shape=[n_hidden_2,n_hidden_1])), 'decoder_h2':tf.Variable(tf.random_normal(shape=[n_hidden_1,n_input])) } biases = { 'encoder_b1':tf.Variable(tf.random_normal(shape=[n_hidden_1])), 'encoder_b2':tf.Variable(tf.random_normal(shape=[n_hidden_2])), 'decoder_b1':tf.Variable(tf.random_normal(shape=[n_hidden_1])), 'decoder_b2':tf.Variable(tf.random_normal(shape=[n_input])) } #編碼 當我們對最終提取的特征節點采用sigmoid函數時,就相當於對輸入限制或者縮放,使其位於[0,1]范圍中 encoder_h1 = tf.nn.sigmoid(tf.add(tf.matmul(input_x,weights['encoder_h1']),biases['encoder_b1'])) encoder_h2 = tf.nn.sigmoid(tf.add(tf.matmul(encoder_h1,weights['encoder_h2']),biases['encoder_b2'])) #解碼 decoder_h1 = tf.nn.sigmoid(tf.add(tf.matmul(encoder_h2,weights['decoder_h1']),biases['decoder_b1'])) pred = tf.nn.sigmoid(tf.add(tf.matmul(decoder_h1,weights['decoder_h2']),biases['decoder_b2'])) ''' 設置代價函數 ''' #對所有元素求和求平均 cost = tf.reduce_mean((input_y - pred)**2) ''' 求解,開始訓練 ''' #train = tf.train.RMSPropOptimizer(learning_rate).minimize(cost) train = tf.train.AdamOptimizer(learning_rate).minimize(cost) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) #計算一輪跌倒多少次 num_batch = int(np.ceil(mnist.train.num_examples/batch_size)) #迭代 for epoch in range(training_epochs): sum_loss = 0.0 for i in range(num_batch): batch_x,batch_y = mnist.train.next_batch(batch_size) _,loss = sess.run([train,cost],feed_dict={input_x:batch_x}) sum_loss += loss #打印信息 if epoch % display_epoch == 0: print('Epoch {} cost = {:.9f}'.format(epoch+1,sum_loss/num_batch)) print('訓練完成') #輸出圖像數據最大值和最小值 print('最大值:',np.max(mnist.train.images[0]),'最小值:',np.min(mnist.train.images[0])) ''' 可視化結果 ''' reconstruction = sess.run(pred,feed_dict = {input_x:mnist.test.images[:show_num]}) plt.figure(figsize=(1.0*show_num,1*2)) for i in range(show_num): plt.subplot(2,show_num,i+1) plt.imshow(np.reshape(mnist.test.images[i],(28,28)),cmap='gray') plt.axis('off') plt.subplot(2,show_num,i+show_num+1) plt.imshow(np.reshape(reconstruction[i],(28,28)),cmap='gray') plt.axis('off') plt.show()
下圖為程序運行的結果,圖片分為上下兩行,第一行顯示的是輸入圖片,第二行顯示的是重構圖片。

在上面的代碼中,我們使用的激活函數為sigmoid激活函數,輸出范圍是[0,1],當我們對最終提取的特征節點采用激勵函數時,竟相當於對輸入限制或縮放,使其位於[0,1]范圍中。有一些數據集,比如MNIST,能方便地將輸出縮放到[0,1]中,但是很難滿足對輸入值的要求。例如,PCA白化處理的輸入並不滿足[0,1]的范圍要求,頁不清楚是否有最好的辦法將數據縮放到特定范圍中。
如果利用一個恆等式來作為激勵函數,就可以很好的解決這個問題,即f(z)=z作為激勵函數。
由多個帶有sigmois激活函數的隱藏層以及一個線性輸出層構成的自編碼器,稱為線性解碼器。
2.提取圖片的二維特征,並利用二維特征還原圖片
通過構建一個2維的自編碼網絡,將MNIST數據集的數據特征提取處來,並通過這些特征重建一個MNIST數據集 ,這里使用4層逐漸壓縮將785維度分別壓縮成256,64,16,2這4個特征向量,最后再還原。並且在這里我們使用了線性解碼器,在編碼的最后一層,沒有進行sigmoid變化,這是因為生成的二維特征數據其特征已經標的極為主要,所有我們希望讓它傳到解碼器中,少一些變化可以最大化地保存原有的主要特征,當然這一切是通過分析之后實際測試得來的結果。
def four_layer_auto_encoder(): ''' 通過構建一個2維的自編碼網絡,將MNIST數據集的數據特征提取處來,並通過這些特征重建一個MNIST數據集 這里使用4層逐漸壓縮將785維度分別壓縮成256,64,16,2這4個特征向量,最后再還原 ''' ''' 導入MNIST數據集 ''' #mnist是一個輕量級的類,它以numpy數組的形式存儲着訓練,校驗,測試數據集 one_hot表示輸出二值化后的10維 mnist = input_data.read_data_sets('MNIST-data',one_hot=True) print(type(mnist)) #<class 'tensorflow.contrib.learn.python.learn.datasets.base.Datasets'> print('Training data shape:',mnist.train.images.shape) #Training data shape: (55000, 784) print('Test data shape:',mnist.test.images.shape) #Test data shape: (10000, 784) print('Validation data shape:',mnist.validation.images.shape) #Validation data shape: (5000, 784) print('Training label shape:',mnist.train.labels.shape) #Training label shape: (55000, 10) ''' 定義參數,以及網絡結構 ''' n_input = 784 #輸入節點 n_hidden_1 = 256 n_hidden_2 = 64 n_hidden_3 = 16 n_hidden_4 = 2 batch_size = 256 #小批量大小 training_epochs = 20 #迭代輪數 display_epoch = 5 #迭代1輪輸出5次信息 learning_rate = 1e-2 #學習率 show_num = 10 #顯示的圖片個數 #定義占位符 input_x = tf.placeholder(dtype=tf.float32,shape=[None,n_input]) #輸入 input_y = input_x #輸出 #學習參數 weights = { 'encoder_h1':tf.Variable(tf.random_normal(shape=[n_input,n_hidden_1])), 'encoder_h2':tf.Variable(tf.random_normal(shape=[n_hidden_1,n_hidden_2])), 'encoder_h3':tf.Variable(tf.random_normal(shape=[n_hidden_2,n_hidden_3])), 'encoder_h4':tf.Variable(tf.random_normal(shape=[n_hidden_3,n_hidden_4])), 'decoder_h1':tf.Variable(tf.random_normal(shape=[n_hidden_4,n_hidden_3])), 'decoder_h2':tf.Variable(tf.random_normal(shape=[n_hidden_3,n_hidden_2])), 'decoder_h3':tf.Variable(tf.random_normal(shape=[n_hidden_2,n_hidden_1])), 'decoder_h4':tf.Variable(tf.random_normal(shape=[n_hidden_1,n_input])) } biases = { 'encoder_b1':tf.Variable(tf.random_normal(shape=[n_hidden_1])), 'encoder_b2':tf.Variable(tf.random_normal(shape=[n_hidden_2])), 'encoder_b3':tf.Variable(tf.random_normal(shape=[n_hidden_3])), 'encoder_b4':tf.Variable(tf.random_normal(shape=[n_hidden_4])), 'decoder_b1':tf.Variable(tf.random_normal(shape=[n_hidden_3])), 'decoder_b2':tf.Variable(tf.random_normal(shape=[n_hidden_2])), 'decoder_b3':tf.Variable(tf.random_normal(shape=[n_hidden_1])), 'decoder_b4':tf.Variable(tf.random_normal(shape=[n_input])) } #編碼 encoder_h1 = tf.nn.sigmoid(tf.add(tf.matmul(input_x,weights['encoder_h1']),biases['encoder_b1'])) encoder_h2 = tf.nn.sigmoid(tf.add(tf.matmul(encoder_h1,weights['encoder_h2']),biases['encoder_b2'])) encoder_h3 = tf.nn.sigmoid(tf.add(tf.matmul(encoder_h2,weights['encoder_h3']),biases['encoder_b3'])) #在編碼的最后一層,沒有進行sigmoid變化,這是因為生成的二維特征數據其特征已經標的極為主要,所有我們希望讓它 #傳到解碼器中,少一些變化可以最大化地保存原有的主要特征 #encoder_h4 = tf.nn.sigmoid(tf.add(tf.matmul(encoder_h3,weights['encoder_h4']),biases['encoder_b4'])) encoder_h4 = tf.add(tf.matmul(encoder_h3,weights['encoder_h4']),biases['encoder_b4']) #解碼 decoder_h1 = tf.nn.sigmoid(tf.add(tf.matmul(encoder_h4,weights['decoder_h1']),biases['decoder_b1'])) decoder_h2 = tf.nn.sigmoid(tf.add(tf.matmul(decoder_h1,weights['decoder_h2']),biases['decoder_b2'])) decoder_h3 = tf.nn.sigmoid(tf.add(tf.matmul(decoder_h2,weights['decoder_h3']),biases['decoder_b3'])) pred = tf.nn.sigmoid(tf.add(tf.matmul(decoder_h3,weights['decoder_h4']),biases['decoder_b4'])) ''' 設置代價函數 ''' #求平均 cost = tf.reduce_mean((input_y - pred)**2) ''' 求解,開始訓練 ''' train = tf.train.AdamOptimizer(learning_rate).minimize(cost) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) #計算一輪跌倒多少次 num_batch = int(np.ceil(mnist.train.num_examples/batch_size)) #迭代 for epoch in range(training_epochs): sum_loss = 0.0 for i in range(num_batch): batch_x,batch_y = mnist.train.next_batch(batch_size) _,loss = sess.run([train,cost],feed_dict={input_x:batch_x}) sum_loss += loss #打印信息 if epoch % display_epoch == 0: print('Epoch {} cost = {:.9f}'.format(epoch+1,sum_loss/num_batch)) print('訓練完成') #輸出圖像數據最大值和最小值 print('最大值:',np.max(mnist.train.images[0]),'最小值:',np.min(mnist.train.images[0])) ''' 可視化結果 ''' reconstruction = sess.run(pred,feed_dict = {input_x:mnist.test.images[:show_num]}) plt.figure(figsize=(1.0*show_num,1*2)) for i in range(show_num): plt.subplot(2,show_num,i+1) plt.imshow(np.reshape(mnist.test.images[i],(28,28)),cmap='gray') plt.axis('off') plt.subplot(2,show_num,i+show_num+1) plt.imshow(np.reshape(reconstruction[i],(28,28)),cmap='gray') plt.axis('off') plt.show() ''' 顯示二維的特征數據 有點聚類的感覺,一般來說通過自編碼網絡將數據降維之后更有利於進行分類處理 ''' plt.figure(figsize=(10,8)) #將onehot轉為一維編碼 labels = [np.argmax(y) for y in mnist.test.labels] encoder_result = sess.run(encoder_h4,feed_dict={input_x:mnist.test.images}) plt.scatter(encoder_result[:,0],encoder_result[:,1],c=labels) plt.colorbar() plt.show()
下圖為線性解碼器運行后的結果:

然后我們再把編碼的最后一層,改為simoid函數測試,結果如下:

對比上面兩張圖,我們可以看到重構出來的效果並不理想,和實際輸入有誤差,可能是因為層數變多,存在現實的梯度問題,我們可以通過調參來嘗試一下會有什么效果。我們對比這兩張圖的第二行,我們會發現第一張圖的噪聲比較少,這說明線性編碼器效果更好。

我們再來看一下這張圖,是不是有一種聚類的感覺,一般來說通過自編碼網絡將數據降維之后更有利於進行分類處理。
3.實現卷積網絡的自編碼
自編碼結構不盡只用在全網絡連接上,還可以用在卷積網絡上。我們在原有代碼的基礎上把全連接改成卷積,把解碼改成反卷積,反池化,代碼如下:(GPU環境才可以運行)
def max_pool_with_argmax(net,stride): ''' 重定義一個最大池化函數,返回最大池化結果以及每個最大值的位置(是個索引,形狀和池化結果一致) args: net:輸入數據 形狀為[batch,in_height,in_width,in_channels] stride:步長,是一個int32類型,注意在最大池化操作中我們設置窗口大小和步長大小是一樣的 ''' #使用mask保存每個最大值的位置 這個函數只支持GPU操作 _, mask = tf.nn.max_pool_with_argmax( net,ksize=[1, stride, stride, 1], strides=[1, stride, stride, 1],padding='SAME') #將反向傳播的mask梯度計算停止 mask = tf.stop_gradient(mask) #計算最大池化操作 net = tf.nn.max_pool(net, ksize=[1, stride, stride, 1],strides=[1, stride, stride, 1], padding='SAME') #將池化結果和mask返回 return net,mask def un_max_pool(net,mask,stride): ''' 定義一個反最大池化的函數,找到mask最大的索引,將max的值填到指定位置 args: net:最大池化后的輸出,形狀為[batch, height, width, in_channels] mask:位置索引組數組,形狀和net一樣 stride:步長,是一個int32類型,這里就是max_pool_with_argmax傳入的stride參數 ''' ksize = [1, stride, stride, 1] input_shape = net.get_shape().as_list() # calculation new shape output_shape = (input_shape[0], input_shape[1] * ksize[1], input_shape[2] * ksize[2], input_shape[3]) # calculation indices for batch, height, width and feature maps one_like_mask = tf.ones_like(mask) batch_range = tf.reshape(tf.range(output_shape[0], dtype=tf.int64), shape=[input_shape[0], 1, 1, 1]) b = one_like_mask * batch_range y = mask // (output_shape[2] * output_shape[3]) x = mask % (output_shape[2] * output_shape[3]) // output_shape[3] feature_range = tf.range(output_shape[3], dtype=tf.int64) f = one_like_mask * feature_range # transpose indices & reshape update values to one dimension updates_size = tf.size(net) indices = tf.transpose(tf.reshape(tf.stack([b, y, x, f]), [4, updates_size])) values = tf.reshape(net, [updates_size]) ret = tf.scatter_nd(indices, values, output_shape) return ret def cnn_auto_encoder(): tf.reset_default_graph() ''' 通過構建一個卷積網絡的自編碼,將MNIST數據集的數據特征提取處來,並通過這些特征重建一個MNIST數據集 ''' ''' 導入MNIST數據集 ''' #mnist是一個輕量級的類,它以numpy數組的形式存儲着訓練,校驗,測試數據集 one_hot表示輸出二值化后的10維 mnist = input_data.read_data_sets('MNIST-data',one_hot=True) print(type(mnist)) #<class 'tensorflow.contrib.learn.python.learn.datasets.base.Datasets'> print('Training data shape:',mnist.train.images.shape) #Training data shape: (55000, 784) print('Test data shape:',mnist.test.images.shape) #Test data shape: (10000, 784) print('Validation data shape:',mnist.validation.images.shape) #Validation data shape: (5000, 784) print('Training label shape:',mnist.train.labels.shape) #Training label shape: (55000, 10) ''' 定義參數,以及網絡結構 ''' n_input = 784 batch_size = 256 #小批量大小 n_conv_1 = 16 #第一層16個ch n_conv_2 = 32 #第二層32個ch training_epochs = 8 #迭代輪數 display_epoch = 5 #迭代2輪輸出5次信息 learning_rate = 1e-2 #學習率 show_num = 10 #顯示的圖片個數 #定義占位符 使用反卷積的時候,這個形狀中不能帶有None,不然會報錯 input_x = tf.placeholder(dtype=tf.float32,shape=[batch_size,n_input]) #輸入 #input_y = input_x #輸出 #學習參數 weights = { 'encoder_conv1': tf.Variable(tf.truncated_normal([5, 5, 1, n_conv_1],stddev=0.1)), 'encoder_conv2': tf.Variable(tf.random_normal([3, 3, n_conv_1, n_conv_2],stddev=0.1)), 'decoder_conv1': tf.Variable(tf.random_normal([5, 5, 1, n_conv_1],stddev=0.1)), 'decoder_conv2': tf.Variable(tf.random_normal([3, 3, n_conv_1, n_conv_2],stddev=0.1)) } biases = { 'encoder_conv1': tf.Variable(tf.zeros([n_conv_1])), 'encoder_conv2': tf.Variable(tf.zeros([n_conv_2])), 'decoder_conv1': tf.Variable(tf.zeros([n_conv_1])), 'decoder_conv2': tf.Variable(tf.zeros([n_conv_2])), } image_x = tf.reshape(input_x,[-1,28,28,1]) #編碼 當我們對最終提取的特征節點采用sigmoid函數時,就相當於對輸入限制或者縮放,使其位於[0,1]范圍中 encoder_conv1 = tf.nn.relu(tf.nn.conv2d(image_x, weights['encoder_conv1'],strides=[1,1,1,1],padding = 'SAME') + biases['encoder_conv1']) print('encoder_conv1:',encoder_conv1.shape) encoder_conv2 = tf.nn.relu(tf.nn.conv2d(encoder_conv1, weights['encoder_conv2'],strides=[1,1,1,1],padding = 'SAME') + biases['encoder_conv2']) print('encoder_conv2:',encoder_conv2.shape) encoder_pool2, mask = max_pool_with_argmax(encoder_conv2, 2) #池化 print('encoder_pool2:',encoder_pool2.shape) #解碼 decoder_upool = un_max_pool(encoder_pool2,mask,2) #反池化 decoder_conv1 = tf.nn.conv2d_transpose(decoder_upool - biases['decoder_conv2'], weights['decoder_conv2'],encoder_conv1.shape,strides=[1,1,1,1],padding='SAME') pred = tf.nn.conv2d_transpose(decoder_conv1 - biases['decoder_conv1'], weights['decoder_conv1'], image_x.shape,strides=[1,1,1,1],padding='SAME') print('pred:',pred.shape) ''' 設置代價函數 ''' #求平均 cost = tf.reduce_mean((image_x - pred)**2) ''' 求解,開始訓練 ''' #train = tf.train.RMSPropOptimizer(learning_rate).minimize(cost) train = tf.train.AdamOptimizer(learning_rate).minimize(cost) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) #計算一輪跌倒多少次 num_batch = int(np.ceil(mnist.train.num_examples/batch_size)) #迭代 for epoch in range(training_epochs): sum_loss = 0.0 for i in range(num_batch): batch_x,batch_y = mnist.train.next_batch(batch_size) _,loss = sess.run([train,cost],feed_dict={input_x:batch_x}) sum_loss += loss #打印信息 if epoch % display_epoch == 0: print('Epoch {} cost = {:.9f}'.format(epoch+1,sum_loss/num_batch)) print('訓練完成') #輸出圖像數據最大值和最小值 print('最大值:',np.max(mnist.train.images[0]),'最小值:',np.min(mnist.train.images[0])) ''' 可視化結果 ''' reconstruction = sess.run(pred,feed_dict = {input_x:batch_x}) plt.figure(figsize=(1.0*show_num,1*2)) for i in range(show_num): plt.subplot(2,show_num,i+1) plt.imshow(np.reshape(batch_x[i],(28,28)),cmap='gray') plt.axis('off') plt.subplot(2,show_num,i+show_num+1) plt.imshow(np.reshape(reconstruction[i],(28,28)),cmap='gray') plt.axis('off') plt.show()
完整代碼如下:
# -*- coding: utf-8 -*- """ Created on Tue May 22 15:30:46 2018 @author: zy """ ''' 利用自編碼網絡提取圖片的特征,並利用特征還原圖片 ''' import tensorflow as tf import numpy as np import matplotlib.pyplot as plt from tensorflow.examples.tutorials.mnist import input_data def two_layer_auto_encoder(): ''' 通過構建一個量程的自編碼網絡,將MNIST數據集的數據特征提取處來,並通過這些特征重建一個MNIST數據集 下面以MNIST數據集為例,將其像素點組成的數據(28x28)從784維降維到256,然后再降到128,最后再以同樣的 方式經過128,再經過256,最終還原到原來的圖片。 ''' ''' 導入MNIST數據集 ''' #mnist是一個輕量級的類,它以numpy數組的形式存儲着訓練,校驗,測試數據集 one_hot表示輸出二值化后的10維 mnist = input_data.read_data_sets('MNIST-data',one_hot=True) print(type(mnist)) #<class 'tensorflow.contrib.learn.python.learn.datasets.base.Datasets'> print('Training data shape:',mnist.train.images.shape) #Training data shape: (55000, 784) print('Test data shape:',mnist.test.images.shape) #Test data shape: (10000, 784) print('Validation data shape:',mnist.validation.images.shape) #Validation data shape: (5000, 784) print('Training label shape:',mnist.train.labels.shape) #Training label shape: (55000, 10) ''' 定義參數,以及網絡結構 ''' n_input = 784 #輸入節點 n_hidden_1 = 256 #第一次256個節點 n_hidden_2 = 128 #第二層128個節點 batch_size = 256 #小批量大小 training_epochs = 20 #迭代輪數 display_epoch = 5 #迭代2輪輸出5次信息 learning_rate = 1e-2 #學習率 show_num = 10 #顯示的圖片個數 #定義占位符 input_x = tf.placeholder(dtype=tf.float32,shape=[None,n_input]) #輸入 input_y = input_x #輸出 #學習參數 weights = { 'encoder_h1':tf.Variable(tf.random_normal(shape=[n_input,n_hidden_1])), 'encoder_h2':tf.Variable(tf.random_normal(shape=[n_hidden_1,n_hidden_2])), 'decoder_h1':tf.Variable(tf.random_normal(shape=[n_hidden_2,n_hidden_1])), 'decoder_h2':tf.Variable(tf.random_normal(shape=[n_hidden_1,n_input])) } biases = { 'encoder_b1':tf.Variable(tf.random_normal(shape=[n_hidden_1])), 'encoder_b2':tf.Variable(tf.random_normal(shape=[n_hidden_2])), 'decoder_b1':tf.Variable(tf.random_normal(shape=[n_hidden_1])), 'decoder_b2':tf.Variable(tf.random_normal(shape=[n_input])) } #編碼 當我們對最終提取的特征節點采用sigmoid函數時,就相當於對輸入限制或者縮放,使其位於[0,1]范圍中 encoder_h1 = tf.nn.sigmoid(tf.add(tf.matmul(input_x,weights['encoder_h1']),biases['encoder_b1'])) encoder_h2 = tf.nn.sigmoid(tf.add(tf.matmul(encoder_h1,weights['encoder_h2']),biases['encoder_b2'])) #解碼 decoder_h1 = tf.nn.sigmoid(tf.add(tf.matmul(encoder_h2,weights['decoder_h1']),biases['decoder_b1'])) pred = tf.nn.sigmoid(tf.add(tf.matmul(decoder_h1,weights['decoder_h2']),biases['decoder_b2'])) ''' 設置代價函數 ''' #對一維的ndarray求平均 cost = tf.reduce_mean((input_y - pred)**2) ''' 求解,開始訓練 ''' #train = tf.train.RMSPropOptimizer(learning_rate).minimize(cost) train = tf.train.AdamOptimizer(learning_rate).minimize(cost) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) #計算一輪跌倒多少次 num_batch = int(np.ceil(mnist.train.num_examples/batch_size)) #迭代 for epoch in range(training_epochs): sum_loss = 0.0 for i in range(num_batch): batch_x,batch_y = mnist.train.next_batch(batch_size) _,loss = sess.run([train,cost],feed_dict={input_x:batch_x}) sum_loss += loss #打印信息 if epoch % display_epoch == 0: print('Epoch {} cost = {:.9f}'.format(epoch+1,sum_loss/num_batch)) print('訓練完成') #輸出圖像數據最大值和最小值 print('最大值:',np.max(mnist.train.images[0]),'最小值:',np.min(mnist.train.images[0])) ''' 可視化結果 ''' reconstruction = sess.run(pred,feed_dict = {input_x:mnist.test.images[:show_num]}) plt.figure(figsize=(1.0*show_num,1*2)) for i in range(show_num): plt.subplot(2,show_num,i+1) plt.imshow(np.reshape(mnist.test.images[i],(28,28)),cmap='gray') plt.axis('off') plt.subplot(2,show_num,i+show_num+1) plt.imshow(np.reshape(reconstruction[i],(28,28)),cmap='gray') plt.axis('off') plt.show() def four_layer_auto_encoder(): ''' 通過構建一個2維的自編碼網絡,將MNIST數據集的數據特征提取處來,並通過這些特征重建一個MNIST數據集 這里使用4層逐漸壓縮將785維度分別壓縮成256,64,16,2這4個特征向量,最后再還原 ''' ''' 導入MNIST數據集 ''' #mnist是一個輕量級的類,它以numpy數組的形式存儲着訓練,校驗,測試數據集 one_hot表示輸出二值化后的10維 mnist = input_data.read_data_sets('MNIST-data',one_hot=True) print(type(mnist)) #<class 'tensorflow.contrib.learn.python.learn.datasets.base.Datasets'> print('Training data shape:',mnist.train.images.shape) #Training data shape: (55000, 784) print('Test data shape:',mnist.test.images.shape) #Test data shape: (10000, 784) print('Validation data shape:',mnist.validation.images.shape) #Validation data shape: (5000, 784) print('Training label shape:',mnist.train.labels.shape) #Training label shape: (55000, 10) ''' 定義參數,以及網絡結構 ''' n_input = 784 #輸入節點 n_hidden_1 = 256 n_hidden_2 = 64 n_hidden_3 = 16 n_hidden_4 = 2 batch_size = 256 #小批量大小 training_epochs = 20 #迭代輪數 display_epoch = 5 #迭代1輪輸出5次信息 learning_rate = 1e-2 #學習率 show_num = 10 #顯示的圖片個數 #定義占位符 input_x = tf.placeholder(dtype=tf.float32,shape=[None,n_input]) #輸入 input_y = input_x #輸出 #學習參數 weights = { 'encoder_h1':tf.Variable(tf.random_normal(shape=[n_input,n_hidden_1])), 'encoder_h2':tf.Variable(tf.random_normal(shape=[n_hidden_1,n_hidden_2])), 'encoder_h3':tf.Variable(tf.random_normal(shape=[n_hidden_2,n_hidden_3])), 'encoder_h4':tf.Variable(tf.random_normal(shape=[n_hidden_3,n_hidden_4])), 'decoder_h1':tf.Variable(tf.random_normal(shape=[n_hidden_4,n_hidden_3])), 'decoder_h2':tf.Variable(tf.random_normal(shape=[n_hidden_3,n_hidden_2])), 'decoder_h3':tf.Variable(tf.random_normal(shape=[n_hidden_2,n_hidden_1])), 'decoder_h4':tf.Variable(tf.random_normal(shape=[n_hidden_1,n_input])) } biases = { 'encoder_b1':tf.Variable(tf.random_normal(shape=[n_hidden_1])), 'encoder_b2':tf.Variable(tf.random_normal(shape=[n_hidden_2])), 'encoder_b3':tf.Variable(tf.random_normal(shape=[n_hidden_3])), 'encoder_b4':tf.Variable(tf.random_normal(shape=[n_hidden_4])), 'decoder_b1':tf.Variable(tf.random_normal(shape=[n_hidden_3])), 'decoder_b2':tf.Variable(tf.random_normal(shape=[n_hidden_2])), 'decoder_b3':tf.Variable(tf.random_normal(shape=[n_hidden_1])), 'decoder_b4':tf.Variable(tf.random_normal(shape=[n_input])) } #編碼 encoder_h1 = tf.nn.sigmoid(tf.add(tf.matmul(input_x,weights['encoder_h1']),biases['encoder_b1'])) encoder_h2 = tf.nn.sigmoid(tf.add(tf.matmul(encoder_h1,weights['encoder_h2']),biases['encoder_b2'])) encoder_h3 = tf.nn.sigmoid(tf.add(tf.matmul(encoder_h2,weights['encoder_h3']),biases['encoder_b3'])) #在編碼的最后一層,沒有進行sigmoid變化,這是因為生成的二維特征數據其特征已經標的極為主要,所有我們希望讓它 #傳到解碼器中,少一些變化可以最大化地保存原有的主要特征 #encoder_h4 = tf.nn.sigmoid(tf.add(tf.matmul(encoder_h3,weights['encoder_h4']),biases['encoder_b4'])) encoder_h4 = tf.add(tf.matmul(encoder_h3,weights['encoder_h4']),biases['encoder_b4']) #解碼 decoder_h1 = tf.nn.sigmoid(tf.add(tf.matmul(encoder_h4,weights['decoder_h1']),biases['decoder_b1'])) decoder_h2 = tf.nn.sigmoid(tf.add(tf.matmul(decoder_h1,weights['decoder_h2']),biases['decoder_b2'])) decoder_h3 = tf.nn.sigmoid(tf.add(tf.matmul(decoder_h2,weights['decoder_h3']),biases['decoder_b3'])) pred = tf.nn.sigmoid(tf.add(tf.matmul(decoder_h3,weights['decoder_h4']),biases['decoder_b4'])) ''' 設置代價函數 ''' #對一維的ndarray求平均 cost = tf.reduce_mean((input_y - pred)**2) ''' 求解,開始訓練 ''' train = tf.train.AdamOptimizer(learning_rate).minimize(cost) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) #計算一輪跌倒多少次 num_batch = int(np.ceil(mnist.train.num_examples/batch_size)) #迭代 for epoch in range(training_epochs): sum_loss = 0.0 for i in range(num_batch): batch_x,batch_y = mnist.train.next_batch(batch_size) _,loss = sess.run([train,cost],feed_dict={input_x:batch_x}) sum_loss += loss #打印信息 if epoch % display_epoch == 0: print('Epoch {} cost = {:.9f}'.format(epoch+1,sum_loss/num_batch)) print('訓練完成') #輸出圖像數據最大值和最小值 print('最大值:',np.max(mnist.train.images[0]),'最小值:',np.min(mnist.train.images[0])) ''' 可視化結果 ''' reconstruction = sess.run(pred,feed_dict = {input_x:mnist.test.images[:show_num]}) plt.figure(figsize=(1.0*show_num,1*2)) for i in range(show_num): plt.subplot(2,show_num,i+1) plt.imshow(np.reshape(mnist.test.images[i],(28,28)),cmap='gray') plt.axis('off') plt.subplot(2,show_num,i+show_num+1) plt.imshow(np.reshape(reconstruction[i],(28,28)),cmap='gray') plt.axis('off') plt.show() ''' 顯示二維的特征數據 有點聚類的感覺,一般來說通過自編碼網絡將數據降維之后更有利於進行分類處理 ''' plt.figure(figsize=(10,8)) #將onehot轉為一維編碼 labels = [np.argmax(y) for y in mnist.test.labels] encoder_result = sess.run(encoder_h4,feed_dict={input_x:mnist.test.images}) plt.scatter(encoder_result[:,0],encoder_result[:,1],c=labels) plt.colorbar() plt.show() def max_pool_with_argmax(net,stride): ''' 重定義一個最大池化函數,返回最大池化結果以及每個最大值的位置(是個索引,形狀和池化結果一致) args: net:輸入數據 形狀為[batch,in_height,in_width,in_channels] stride:步長,是一個int32類型,注意在最大池化操作中我們設置窗口大小和步長大小是一樣的 ''' #使用mask保存每個最大值的位置 這個函數只支持GPU操作 _, mask = tf.nn.max_pool_with_argmax( net,ksize=[1, stride, stride, 1], strides=[1, stride, stride, 1],padding='SAME') #將反向傳播的mask梯度計算停止 mask = tf.stop_gradient(mask) #計算最大池化操作 net = tf.nn.max_pool(net, ksize=[1, stride, stride, 1],strides=[1, stride, stride, 1], padding='SAME') #將池化結果和mask返回 return net,mask def un_max_pool(net,mask,stride): ''' 定義一個反最大池化的函數,找到mask最大的索引,將max的值填到指定位置 args: net:最大池化后的輸出,形狀為[batch, height, width, in_channels] mask:位置索引組數組,形狀和net一樣 stride:步長,是一個int32類型,這里就是max_pool_with_argmax傳入的stride參數 ''' ksize = [1, stride, stride, 1] input_shape = net.get_shape().as_list() # calculation new shape output_shape = (input_shape[0], input_shape[1] * ksize[1], input_shape[2] * ksize[2], input_shape[3]) # calculation indices for batch, height, width and feature maps one_like_mask = tf.ones_like(mask) batch_range = tf.reshape(tf.range(output_shape[0], dtype=tf.int64), shape=[input_shape[0], 1, 1, 1]) b = one_like_mask * batch_range y = mask // (output_shape[2] * output_shape[3]) x = mask % (output_shape[2] * output_shape[3]) // output_shape[3] feature_range = tf.range(output_shape[3], dtype=tf.int64) f = one_like_mask * feature_range # transpose indices & reshape update values to one dimension updates_size = tf.size(net) indices = tf.transpose(tf.reshape(tf.stack([b, y, x, f]), [4, updates_size])) values = tf.reshape(net, [updates_size]) ret = tf.scatter_nd(indices, values, output_shape) return ret def cnn_auto_encoder(): tf.reset_default_graph() ''' 通過構建一個卷積網絡的自編碼,將MNIST數據集的數據特征提取處來,並通過這些特征重建一個MNIST數據集 ''' ''' 導入MNIST數據集 ''' #mnist是一個輕量級的類,它以numpy數組的形式存儲着訓練,校驗,測試數據集 one_hot表示輸出二值化后的10維 mnist = input_data.read_data_sets('MNIST-data',one_hot=True) print(type(mnist)) #<class 'tensorflow.contrib.learn.python.learn.datasets.base.Datasets'> print('Training data shape:',mnist.train.images.shape) #Training data shape: (55000, 784) print('Test data shape:',mnist.test.images.shape) #Test data shape: (10000, 784) print('Validation data shape:',mnist.validation.images.shape) #Validation data shape: (5000, 784) print('Training label shape:',mnist.train.labels.shape) #Training label shape: (55000, 10) ''' 定義參數,以及網絡結構 ''' n_input = 784 batch_size = 256 #小批量大小 n_conv_1 = 16 #第一層16個ch n_conv_2 = 32 #第二層32個ch training_epochs = 8 #迭代輪數 display_epoch = 5 #迭代2輪輸出5次信息 learning_rate = 1e-2 #學習率 show_num = 10 #顯示的圖片個數 #定義占位符 使用反卷積的時候,這個形狀中不能帶有None,不然會報錯 input_x = tf.placeholder(dtype=tf.float32,shape=[batch_size,n_input]) #輸入 #input_y = input_x #輸出 #學習參數 weights = { 'encoder_conv1': tf.Variable(tf.truncated_normal([5, 5, 1, n_conv_1],stddev=0.1)), 'encoder_conv2': tf.Variable(tf.random_normal([3, 3, n_conv_1, n_conv_2],stddev=0.1)), 'decoder_conv1': tf.Variable(tf.random_normal([5, 5, 1, n_conv_1],stddev=0.1)), 'decoder_conv2': tf.Variable(tf.random_normal([3, 3, n_conv_1, n_conv_2],stddev=0.1)) } biases = { 'encoder_conv1': tf.Variable(tf.zeros([n_conv_1])), 'encoder_conv2': tf.Variable(tf.zeros([n_conv_2])), 'decoder_conv1': tf.Variable(tf.zeros([n_conv_1])), 'decoder_conv2': tf.Variable(tf.zeros([n_conv_2])), } image_x = tf.reshape(input_x,[-1,28,28,1]) #編碼 當我們對最終提取的特征節點采用sigmoid函數時,就相當於對輸入限制或者縮放,使其位於[0,1]范圍中 encoder_conv1 = tf.nn.relu(tf.nn.conv2d(image_x, weights['encoder_conv1'],strides=[1,1,1,1],padding = 'SAME') + biases['encoder_conv1']) print('encoder_conv1:',encoder_conv1.shape) encoder_conv2 = tf.nn.relu(tf.nn.conv2d(encoder_conv1, weights['encoder_conv2'],strides=[1,1,1,1],padding = 'SAME') + biases['encoder_conv2']) print('encoder_conv2:',encoder_conv2.shape) encoder_pool2, mask = max_pool_with_argmax(encoder_conv2, 2) #池化 print('encoder_pool2:',encoder_pool2.shape) #解碼 decoder_upool = un_max_pool(encoder_pool2,mask,2) #反池化 decoder_conv1 = tf.nn.conv2d_transpose(decoder_upool - biases['decoder_conv2'], weights['decoder_conv2'],encoder_conv1.shape,strides=[1,1,1,1],padding='SAME') pred = tf.nn.conv2d_transpose(decoder_conv1 - biases['decoder_conv1'], weights['decoder_conv1'], image_x.shape,strides=[1,1,1,1],padding='SAME') print('pred:',pred.shape) ''' 設置代價函數 ''' #對一維的ndarray求平均 cost = tf.reduce_mean((image_x - pred)**2) ''' 求解,開始訓練 ''' #train = tf.train.RMSPropOptimizer(learning_rate).minimize(cost) train = tf.train.AdamOptimizer(learning_rate).minimize(cost) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) #計算一輪跌倒多少次 num_batch = int(np.ceil(mnist.train.num_examples/batch_size)) #迭代 for epoch in range(training_epochs): sum_loss = 0.0 for i in range(num_batch): batch_x,batch_y = mnist.train.next_batch(batch_size) _,loss = sess.run([train,cost],feed_dict={input_x:batch_x}) sum_loss += loss #打印信息 if epoch % display_epoch == 0: print('Epoch {} cost = {:.9f}'.format(epoch+1,sum_loss/num_batch)) print('訓練完成') #輸出圖像數據最大值和最小值 print('最大值:',np.max(mnist.train.images[0]),'最小值:',np.min(mnist.train.images[0])) ''' 可視化結果 ''' reconstruction = sess.run(pred,feed_dict = {input_x:batch_x}) plt.figure(figsize=(1.0*show_num,1*2)) for i in range(show_num): plt.subplot(2,show_num,i+1) plt.imshow(np.reshape(batch_x[i],(28,28)),cmap='gray') plt.axis('off') plt.subplot(2,show_num,i+show_num+1) plt.imshow(np.reshape(reconstruction[i],(28,28)),cmap='gray') plt.axis('off') plt.show() if __name__ == '__main__': #two_layer_auto_encoder() four_layer_auto_encoder() #cnn_auto_encoder()
參考文獻
