傳統機器學習任務很大程度上依賴於好的特徵工程,好比對數值型,日期時間型,種類型等特徵的提取。特徵工程每每是很是耗時耗力的,在圖像,語音和視頻中提取到有效的特徵就更難了,工程師必須在這些領域有很是深刻的理解,而且使用專業算法提取這些數據的特徵。深度學習則能夠解決人工難以提取有效特徵的問題,它能夠大大緩解機器學習模型對特徵工程的依賴。深度學習在早期一度被認爲是一種無監督的特徵學習(Unsuperbised Feature Learning),模仿了人腦的對特徵逐層抽象提取的過程。這其中有兩點很重要:一是無監督學習,即咱們不須要標註數據就能夠對數據進行必定程度的學習,這種學習是對數據內容的組織形式的學習,提取的時頻繁出現的特徵;二是逐層抽象,特徵是須要不斷抽象的,就像人老是從簡單基礎的概念開始學習,再到複雜的概念。學生們要從加減乘除開始學起,再到簡單函數,而後到微積分,深度學習也是同樣的,它從簡單的微觀的特徵開始,不斷抽象特徵的層級,逐漸往復雜的宏觀特徵轉變。html
例如在圖像識別問題中,假定咱們有許多汽車的圖片,要如何斷定這些圖片是汽車呢?若是咱們從像素級特徵開始進行訓練分類器,那麼絕大多數算法很難有效的工做。若是咱們提取出高階的特徵,好比汽車的車輪,汽車的車窗,汽車的車身,那麼使用這些高階特徵即可以很是準確地對圖片進行分類,這就是高階特徵的效果。不過任何高階特徵都是由更小單位的特徵組合而成的。好比車輪是由橡膠輪胎,車軸,輪輻等組成。而其中每個組件都是由更小單位的特徵組合而成的,好比橡膠輪胎由許多黑色的同心圓組成的,而這些同心圓也都由許多圓弧曲線組成,圓弧曲線都由像素組成。咱們將漆面的過程逆過來,將一張圖片的原始像素慢慢抽象,從像素組成點,線,再講點,線組合成小零件,再將小零件組成車輪,車窗,車身等高階特徵,這邊是深度學習在訓練過程當中所做的特徵學習。git
早年由學者們研究稀疏編碼(Sparse Coding)時,他們收集了大量黑白風景照,並從中提取了許多16像素*16像素的圖像碎片。他們發現幾乎全部的圖像碎片均可以由64種正交的邊組合獲得,以下圖所示,而且組合出一張圖像碎片須要的邊的數量是不多的,即稀疏的。學者同時發現聲音也存在這種狀況,他們從大量的未標註音頻中發現了20種基本結構,絕大多數聲音能夠由這些基本結構線性組合獲得。這其實就是特徵的稀疏表達,使用少許的基本特徵組合拼裝獲得更高層抽象的特徵,一般咱們也須要多層的神經網絡,對每一層神經網絡來講,前一層的輸出都是未加工的像素,而這一層則是對象素進行加工組織成更高階的特徵(即前面提到的將邊組合成圖像碎片)。github
咱們來看一下實際的例子。假如咱們有許多基本結構,好比指向各個方向的邊,白塊,黑塊等。以下圖所示,咱們能夠經過不一樣方式組合出不一樣的高階特徵,並最終拼出不一樣的目標物體。這些基本結構就是basis,在人臉識別任務中,咱們可使用他們拼出人臉的不一樣器官,好比鼻子,嘴,眼睛,眉毛,臉頰等,這些器官又能夠向上一層拼出不一樣樣式的人臉,最後模型經過在圖片中匹配這些不一樣樣式 的人臉(即高階特徵)來進行識別。一樣,basis能夠拼出汽車上不一樣的組件,最終拼出各式各樣的車型;也能夠拼出大象身體的不一樣部位,最後組成各類尺寸,品種,顏色的大象;還能夠拼出椅子的凳,座,靠背凳,最後組成各類尺寸,品種,顏色的大象;還能夠拼出椅子的凳,座,靠背凳,最後組成不一樣款式的椅子,特徵是能夠不斷抽象轉爲高一級的特徵的,那咱們如何找到這些基本結構,而後如何抽象呢?若是咱們有不少標註的數據,則能夠訓練一個深層的神經網絡。若是沒有標註的數據呢?在這種狀況下,咱們依然可使用無監督的自編碼器來提取特徵。自編碼器(AutoEncoder),顧名思義,便可以使用自身的高階特徵編碼本身。自編碼器其實也是一種神經網絡,它的輸入和輸出是一致的,它藉助稀疏編碼的思想,目標是使用稀疏的一些高階特徵從新組合來重構本身。所以,它的特色很是明顯:第一,指望輸入/輸出一致;第二,但願使用高階特徵來重構本身,而不是複製像素點。算法
Hinton 教授在Science發表文章 Reducing the dimensionality of data with neural networks,講解了使用自編碼器對數據進行降維的方法。Hinton還提出了基於深度信念網絡(Deep Belief Networks, DBN,由多層RBM堆疊而成)可以使用無監督的逐層訓練的貪心算法,爲訓練很深的網絡提供一個可行方案:咱們可能很難直接訓練極深的網絡,可是能夠用無監督的逐層訓練提取特徵,將網絡的權重初始化到一個比較好的位置,輔助後面的監督訓練。無監督的逐層訓練,其思想和自編碼器(AutoEncoder)很是類似,後者的目標是讓神經網絡的輸出能和原始輸入一致,至關於學習一個恆等式 y=x, 以下圖所示,自編碼器的輸入節點和輸出節點的數量是一致的,但若是隻是單純的逐個複製輸入節點則沒有意義,像前面提到的,自編碼器一般但願使用少許稀疏的高階特徵來重構輸入,因此咱們能夠加入幾種限制。網絡
(1)若是限制中間隱含層節點的數量,好比讓中間隱含層節點的數量小於輸入/輸出節點的數量,就至關於一個降維的過程。此時已經不可能出現複製全部節點的狀況,由於中間節點數小於輸入節點數,那隻能學習數據中最重要的特徵復原,將可能不太相關的內容去掉。此時,若是再給中間隱含層的權重加一個L1的正則,則能夠根據懲罰係數控制隱含節點的稀疏程度,懲罰係數越大,學到的特徵組合越稀疏,實際使用(非零權重)的特徵數量越少。session
(2)若是給數據加入噪音,那麼就是Denoising AutoEncoder(去噪自編碼器),咱們將從噪聲中學習出數據的特徵。一樣,咱們也不可能徹底複製節點,徹底複製並不能去除咱們添加的噪聲,沒法徹底復原數據,因此惟有學習數據頻繁出現的模式和結構,將無規律的噪聲略去,才能夠復原數據。app
去噪自編碼器中最常使用的噪聲是加性高斯噪聲,其結構以下,固然也可使用Masking Noise,即有隨機遮擋的噪聲,這種狀況下,圖像中的一部分像素被置爲0,模型須要從其餘像素的結構推測出這些被遮擋的像素是什麼,所以模型依然須要學習圖像中抽象的高階特徵。dom
若是自編碼器的隱含層只有一層,那麼其原理相似於主成分分析(PCA)。Hinton提出的DBN模型有多個隱含層,每一個隱含層都是限制性玻爾茲曼機RBM(Restricted Boltzman Machine,一種具備特殊鏈接分佈的神經網絡)。DBN訓練時,須要先對每兩層間進行無監督的預訓練(per-training),這個過程其實就至關於一個多層的自編碼器,能夠將整個網絡的權重初始化到一個理想的分佈。最後,經過反向傳播算法調整模型權重,這個步驟會使用通過標註的信息來作監督性的分類訓練。當年DBN 給訓練深層的神經網絡提供了可能性,它能解決網絡過深帶來的梯度瀰漫(Gradient Vanishment)的問題,讓訓練變得容易。簡單來講,Hinton的思路就是先用自編碼器的方法進行無監督的預訓練,提取特徵並初始化權重,而後使用標註信息進行監督式的訓練。固然自編碼器的做用不只侷限於給監督訓練作預訓練,直接使用自編碼器進行特徵提取和分析也是能夠的。現實中數據最多的仍是未標註的數據,所以自編碼器擁有許多用武之地。機器學習
下面咱們開始實現最具表明性的去噪自編碼器。去噪自編碼器的使用範圍最廣也最通用。而其餘幾種自編碼器,你們能夠本身對代碼加以修改自行實現,其中無噪聲的自編碼器只須要去掉噪聲,並保證隱含層節點小於輸入層節點;Masking Noise 的自編碼器只須要將高斯噪聲改成隨機遮擋噪聲;Variational AutoEncoder(VAE)則相對複雜,VAE則相對複雜,VAE對中間節點的分佈有強假設,擁有額外的損失項,且會使用特殊的SGVB(Stochastic Gradient Variational Bayes)算法進行訓練。目前VAE還在生成模型中發揮了很大的做用。ide
這裏咱們主要來自TensorFlow的開源實現,使用的數據爲MNIST數據。咱們的自編碼器會使用到一種參數初始化方法 xavier initialization,須要先定義好它。Xavier初始化器在Caffe的早期版本中被頻繁使用,它的特色是會根據某一層網絡的輸入,輸出節點數量自動調整最適合的分佈。Xaiver Glorot 和深度學習三巨頭之一的 Yoshua Bengio 在一篇論文中指出,若是深度學習模型的權重初始化的過小,那信號將在每層間傳遞時逐漸縮小而難以產生做用,但若是權重初始化的太大,那信號將在每層間傳遞時逐漸放大並致使發散和實效。而 Xavier 初始化器作的事情就是讓權重被初始化的不大不小,正好合適。從數學的角度分析, Xavier 初始化器作的事情就是讓權重被初始化的不大不小,正好合適。從數學的角度分析, Xavier 初始化器就是讓權重知足0均值,同時方差爲 2/(Nin + Nout),分佈能夠用均勻分佈或者高斯分佈。以下代碼:
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)
其中 fan_in 是輸入節點的數量,fan_out 是輸出節點的數量。咱們使用了 tf.random_uniform建立了一個範圍內的均勻分佈,而它的方差根據公式 D(x) = (max - min) **2 /12恰好等於 2/(Nin + Nout)。所以這時實現的就是標準的均勻分佈的 Xaiver 初始化器。
代碼以下:
#_*_coding:utf-8_*_ import numpy as np import sklearn.preprocessing as prep import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data 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) 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) #高斯噪聲稀疏 self.training_scale = scale network_weights = self._initialize_weights() # 咱們只用了一個隱含層 self.weights = network_weights 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']) 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 # 計算損失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執行一個計算圖節點self.cost,在測試會用到 def calc_total_cost(self, X): return self.sess.run(self.cost, feed_dict={self.x: X, self.scale: self.training_scale}) # 返回自編碼器隱含層的輸出結果,目的是提供一個接口來獲取抽象後的特徵 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}) # 總體運行一遍復原過程,包括提取高階特徵和經過高階特徵復原數據 def reconstruct(self, X): return self.sess.run(self.reconstruction, feed_dict={self.x: X, self.scale: self.training_scale}) # 獲取隱含層的權重 W1 def getWeights(self): return self.sess.run(self.weights['w1']) # 獲取隱含層的偏置係數 b1 def getBiases(self): return self.sess.run(self.weights['b1']) # read data to load mnist dataset mnist = input_data.read_data_sets('MNIST_data', one_hot=True) def standard_scale(X_train, X_test): # 標準化即讓數據變成0均值,且標準差爲1的分佈 preprocessor = prep.StandardScaler().fit(X_train) X_train = preprocessor.transform(X_train) X_test = preprocessor.transform(X_test) return X_train, X_test # 定義一個獲取隨機block數據的函數,取一個從0到len(data)-batch_size之間的隨機整數 # 這裏是不放回抽樣,能夠提升數據的利益效率 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)] # 使用以前定義的standard_scale 函數對訓練集,測試集進行標準化變換 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) 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)) print("Total cost: " + str(autoencoder.calc_total_cost(X_test)))
結果以下:
Epoch: 0001 cost= 19994.269430682 Epoch: 0002 cost= 12152.192460227 Epoch: 0003 cost= 10875.503042045 Epoch: 0004 cost= 10308.418541477 Epoch: 0005 cost= 9187.305378977 Epoch: 0006 cost= 9217.541369886 Epoch: 0007 cost= 9685.120126136 Epoch: 0008 cost= 9682.280556250 Epoch: 0009 cost= 8886.662347727 Epoch: 0010 cost= 8706.033726705 Epoch: 0011 cost= 7810.424784091 Epoch: 0012 cost= 8669.721145455 Epoch: 0013 cost= 8534.551150568 Epoch: 0014 cost= 7741.216421591 Epoch: 0015 cost= 7924.397942045 Epoch: 0016 cost= 7475.140959091 Epoch: 0017 cost= 8064.848693750 Epoch: 0018 cost= 8351.518777273 Epoch: 0019 cost= 7844.633439773 Epoch: 0020 cost= 8505.549214773 Total cost: 634369.4
上面爲訓練結果,咱們將平均損失 avg_cost 設爲0,並計算總共須要的 batch 數(一般樣本總數除以batch大小),注意這裏使用的時不放回抽樣,因此並不能保證每一個樣本都被抽樣並參與訓練。而後在每個 batch 的循環中,先使用 get_random_block_from_data 函數隨機抽取一個 block 的數據,而後使用成員函數 partial_fit 訓練這個 batch 的數據並計算當前的 cost,最後將當前的 cost 整合到 avg_cost 中,在每一輪迭代後,顯示當前的迭代數和這一輪迭代的平均 cost。咱們在第一輪迭代時,cost 大約爲19999,在最後一輪迭代時, cost大約爲7000,再接着訓練 cost 也很難繼續下降了。固然能夠繼續調整 batch_size,epoch數,優化器,自編碼器的隱含層數,隱含節點數等,來嘗試獲取更低的 cost 。
最後對訓練完的模型進行性能測試,這裏使用以前定義的成員函數 cal_total_cost 對測試集 X_test 進行測試,評價指標依然是平方偏差,若是使用示例中的參數,咱們能夠看到損失值爲 60萬。
至此,去噪自編碼器的 TensorFlow徹底實現。咱們能夠發現實現自編碼器和實現一個單隱含層的神經網絡差很少,只不過是在數據輸入時作了標準化,並加上了一個高斯噪聲,同時咱們在輸出結果不是數字分類結果,而是復原的數據,所以不須要用標註過的數據進行監督訓練。自編碼器做爲一種無監督學習的方法,它與其餘無監督學習的主要不一樣在於,它不是對數據進行聚類,而是提取其中最有用,最頻繁出現的高階特徵,根據這些高階特徵重構數據。在深度學習發展早期很是流行的 DBN,也是依靠這種思想,先對數據進行無監督的學習,提取到一些有用的特徵,將神經網絡權重初始化到一個較好的分佈,而後再使用有標註的數據進行監督訓練,即對權重進行 fine-tune。
如今,無監督式預訓練的使用場景比之前少了許多,訓練全鏈接的 MLP 或者 CNN,RNN 時,咱們都不須要先使用無監督訓練提取特徵。可是無監督學習乃至 AutoEncoder 依然是很是有用的。現實生活中,大部分的數據都是沒有標註信息的,但人腦就很擅長處理這些數據,咱們會提取其中的高階抽象特徵,並使用在其餘地方。自編碼器做爲深度學習在無監督領域的嘗試是很是成功的,同時無監督學習也將是深度學習接下來的一個重要發展方向。
自動編碼器(AutoEncoder)最開始做爲一種數據的壓縮方法,其特色有:
(1) 跟數據相關程序很高,這意味着自動編碼器只能壓縮與訓練數據類似的數據,這個其實比較顯然,由於使用神經網絡提取的特徵通常是高度相關於原始的訓練集,使用人臉訓練出來的自動編碼器在壓縮天然界動物的圖片是表現就會比較差,由於它只學習到了人臉的特徵,而沒有可以學習到天然界圖片的特徵;
(2)壓縮後的數據是有損的,這是由於在降維過程當中不可避免的要丟掉信息
Autoencoder 簡單來講就是將有不少Feature的數據進行壓縮,以後再進行解壓的過程。好比有一個神經網絡,它在作的事情是接收一張圖片,而後給他打碼,最後,再從打碼後的圖片中還原,以下圖:
由於有時候神經網絡要接受大量的輸入信息,好比輸入信息是高清圖片時,輸入信息量可能達到上千萬,讓神經網絡直接從上千萬個信息源中學習是一件很吃力的工做。因此,壓縮一下,提取出原圖片中的最具備表明性的信息,縮減輸入信息量,再將縮減事後的信息放入神經網絡學習,這樣學習就簡單輕鬆了。
對於此問題來講,自編碼在這時候就能發揮做用,經過將原數據白色的X壓縮,解壓成黑色的X,而後經過對比黑白X,求出預測偏差,進行反向傳遞,逐步提高自編碼的準確性。訓練好的自編碼中間這一部分就是能總結元數據的精髓。
對於自編碼,咱們只用了輸入數據X,並沒用到X對應的數據標籤,因此也能夠說自編碼是一種非監督學習。若是你們知道PCA(Principal component analysis),與Autoencoder相相似,它的主要功能即便對數據進行非監督學習,並將壓縮站會後獲得的「特徵值」,這一中間結果正相似於PCA的結果。以後再將壓縮過的「特徵值」進行解壓,獲得的最終結果與原始數據進行比較,對此進行非監督學習。其大概過程以下:
這是一個經過自編碼整理出來的數據,它能從元數據中總結出每種類型數據的特徵,若是把這些特徵類型都放在一張二維的圖片上 ,每種類型都已經很好地用原數據的精髓區分來。若是你瞭解PCA主成分,再提取主要特徵時,自編碼和他同樣,甚至超越了PCA,換句話說,自編碼能夠像PCA同樣給特徵屬性降維。
這部分叫作encoder編碼器,編碼器能獲得元數據的精髓,而後咱們只須要再建立一個小的神經網絡學習這個精髓的數據,不只減小了神經網絡的負擔,一樣也能達到很好的效果。
至於解碼器Decoder,咱們知道,它在訓練的時候是要將精髓信息解壓成原始信息,那麼這就提供了一個解壓器的做用,甚至咱們能夠認爲是一個生成器(相似於GAN),那作這件事的一種特殊自編碼叫作variational autoencoders。
有一個例子就是讓它能模仿並生成手寫數字:
今天的代碼,咱們會運用兩個類型:
神經網絡也能進行非監督學習,只須要訓練數據,不須要標籤數據。自編碼就是這樣一種形式。自編碼能自動分類數據,並且也能嵌套在半監督學習的上面,用少許的有標籤樣本和大量的無標籤樣本學習。
此次學習用MNIST手寫數據來壓縮再解壓圖片。
而後用壓縮的特徵進行非(無)監督分類。
自編碼只能用訓練集就行了,並且只須要訓練training data 的 image, 不用訓練labels。
import torch import torch.nn as nn import torch.utils.data as Data import torchvision # 超參數 EPOCH = 10 BATCH_SIZE = 64 LR = 0.005 # 下過數據的話, 就能夠設置成 False DOWNLOAD_MNIST = True # 到時候顯示 5張圖片看效果, 如上圖一 N_TEST_IMG = 5 # Mnist digits dataset train_data = torchvision.datasets.MNIST( root='./mnist/', # this is training data train=True, # Converts a PIL.Image or numpy.ndarray to # torch.FloatTensor of shape (C x H x W) and normalize in the range [0.0, 1.0] transform=torchvision.transforms.ToTensor(), # download it if you don't have it download=DOWNLOAD_MNIST, )
AutoEncoder 的形式很監督,分別是encoder 和 decoder,壓縮和解壓,壓縮後獲得壓縮的特徵值,再從壓縮的特徵值解壓成原圖片。
class AutoEncoder(nn.Module): def __init__(self): super(AutoEncoder, self).__init__() # 壓縮 self.encoder = nn.Sequential( nn.Linear(28*28, 128), nn.Tanh(), nn.Linear(128, 64), nn.Tanh(), nn.Linear(64, 12), nn.Tanh(), nn.Linear(12, 3), # 壓縮成3個特徵, 進行 3D 圖像可視化 ) # 解壓 self.decoder = nn.Sequential( nn.Linear(3, 12), nn.Tanh(), nn.Linear(12, 64), nn.Tanh(), nn.Linear(64, 128), nn.Tanh(), nn.Linear(128, 28*28), nn.Sigmoid(), # 激勵函數讓輸出值在 (0, 1) ) def forward(self, x): encoded = self.encoder(x) decoded = self.decoder(encoded) return encoded, decoded autoencoder = AutoEncoder()
這裏咱們定義了一個簡單的四層網絡做爲編碼器,中間使用ReLU激活函數,最後輸出的是三維的。輸出一個28 * 28 的圖像數據,特別要注意的是最後使用的激活函數是Tanh,這個激活函數可以將最後的輸出轉換到 -1 和 1 之間,這是由於咱們輸入的圖片已經變換到 -1 和 1 之間了,這是的輸出必須和其對應。
咱們也能夠將多層感知器換成卷積神經網絡,這樣對圖片的特徵提取有着更好的效果。
class autoencoder(nn.Module): def __init__(self): super(autoencoder, self).__init__() self.encoder = nn.Sequential( nn.Conv2d(1, 16, 3, stride=3, padding=1), # b, 16, 10, 10 nn.ReLU(True), nn.MaxPool2d(2, stride=2), # b, 16, 5, 5 nn.Conv2d(16, 8, 3, stride=2, padding=1), # b, 8, 3, 3 nn.ReLU(True), nn.MaxPool2d(2, stride=1) # b, 8, 2, 2 ) self.decoder = nn.Sequential( nn.ConvTranspose2d(8, 16, 3, stride=2), # b, 16, 5, 5 nn.ReLU(True), nn.ConvTranspose2d(16, 8, 5, stride=3, padding=1), # b, 8, 15, 15 nn.ReLU(True), nn.ConvTranspose2d(8, 1, 2, stride=2, padding=1), # b, 1, 28, 28 nn.Tanh() ) def forward(self, x): x = self.encoder(x) x = self.decoder(x) return x
這裏使用了 nn.ConvTranspose2d(),這能夠看作是卷積的反操做,能夠在某種意義上看作是反捲積。
訓練,並可視化訓練的過程,咱們能夠有效的利用encoder 和decoder來作不少事情,好比這裏咱們用decoder的信息輸出看和原圖片的對比,還能用encoder來看通過壓縮後,神經網絡對原圖片的理解。encoder能將不一樣圖片數據大概的分離開來,這樣就是一個無監督學習的過程。
而訓練過程也比較簡單,咱們使用最小均方偏差來做爲損失函數,比較生成的圖片與原始圖片的每一個像素點的差別。
optimizer = torch.optim.Adam(autoencoder.parameters(), lr=LR) loss_func = nn.MSELoss() for epoch in range(EPOCH): for step, (x, b_label) in enumerate(train_loader): b_x = x.view(-1, 28*28) # batch x, shape (batch, 28*28) b_y = x.view(-1, 28*28) # batch y, shape (batch, 28*28) encoded, decoded = autoencoder(b_x) loss = loss_func(decoded, b_y) # mean square error optimizer.zero_grad() # clear gradients for this training step loss.backward() # backpropagation, compute gradients optimizer.step() # apply gradients
全部代碼以下:
import torch import torch.nn as nn import torch.utils.data as Data import torchvision import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from matplotlib import cm import numpy as np # torch.manual_seed(1) # reproducible # Hyper Parameters EPOCH = 10 BATCH_SIZE = 64 LR = 0.005 # learning rate DOWNLOAD_MNIST = False N_TEST_IMG = 5 # Mnist digits dataset train_data = torchvision.datasets.MNIST( root='./mnist/', train=True, # this is training data transform=torchvision.transforms.ToTensor(), # Converts a PIL.Image or numpy.ndarray to # torch.FloatTensor of shape (C x H x W) and normalize in the range [0.0, 1.0] download=DOWNLOAD_MNIST, # download it if you don't have it ) # plot one example print(train_data.train_data.size()) # (60000, 28, 28) print(train_data.train_labels.size()) # (60000) plt.imshow(train_data.train_data[2].numpy(), cmap='gray') plt.title('%i' % train_data.train_labels[2]) plt.show() # Data Loader for easy mini-batch return in training, the image batch shape will be (50, 1, 28, 28) train_loader = Data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True) class AutoEncoder(nn.Module): def __init__(self): super(AutoEncoder, self).__init__() self.encoder = nn.Sequential( nn.Linear(28*28, 128), nn.Tanh(), nn.Linear(128, 64), nn.Tanh(), nn.Linear(64, 12), nn.Tanh(), nn.Linear(12, 3), # compress to 3 features which can be visualized in plt ) self.decoder = nn.Sequential( nn.Linear(3, 12), nn.Tanh(), nn.Linear(12, 64), nn.Tanh(), nn.Linear(64, 128), nn.Tanh(), nn.Linear(128, 28*28), nn.Sigmoid(), # compress to a range (0, 1) ) def forward(self, x): encoded = self.encoder(x) decoded = self.decoder(encoded) return encoded, decoded autoencoder = AutoEncoder() optimizer = torch.optim.Adam(autoencoder.parameters(), lr=LR) loss_func = nn.MSELoss() # initialize figure f, a = plt.subplots(2, N_TEST_IMG, figsize=(5, 2)) plt.ion() # continuously plot # original data (first row) for viewing view_data = train_data.train_data[:N_TEST_IMG].view(-1, 28*28).type(torch.FloatTensor)/255. for i in range(N_TEST_IMG): a[0][i].imshow(np.reshape(view_data.data.numpy()[i], (28, 28)), cmap='gray'); a[0][i].set_xticks(()); a[0][i].set_yticks(()) for epoch in range(EPOCH): for step, (x, b_label) in enumerate(train_loader): b_x = x.view(-1, 28*28) # batch x, shape (batch, 28*28) b_y = x.view(-1, 28*28) # batch y, shape (batch, 28*28) encoded, decoded = autoencoder(b_x) loss = loss_func(decoded, b_y) # mean square error optimizer.zero_grad() # clear gradients for this training step loss.backward() # backpropagation, compute gradients optimizer.step() # apply gradients if step % 100 == 0: print('Epoch: ', epoch, '| train loss: %.4f' % loss.data.numpy()) # plotting decoded image (second row) _, decoded_data = autoencoder(view_data) for i in range(N_TEST_IMG): a[1][i].clear() a[1][i].imshow(np.reshape(decoded_data.data.numpy()[i], (28, 28)), cmap='gray') a[1][i].set_xticks(()); a[1][i].set_yticks(()) plt.draw(); plt.pause(0.05) plt.ioff() plt.show() # visualize in 3D plot view_data = train_data.train_data[:200].view(-1, 28*28).type(torch.FloatTensor)/255. encoded_data, _ = autoencoder(view_data) fig = plt.figure(2); ax = Axes3D(fig) X, Y, Z = encoded_data.data[:, 0].numpy(), encoded_data.data[:, 1].numpy(), encoded_data.data[:, 2].numpy() values = train_data.train_labels[:200].numpy() for x, y, z, s in zip(X, Y, Z, values): c = cm.rainbow(int(255*s/9)); ax.text(x, y, z, s, backgroundcolor=c) ax.set_xlim(X.min(), X.max()); ax.set_ylim(Y.min(), Y.max()); ax.set_zlim(Z.min(), Z.max()) plt.show()
變分編碼器是自動編碼器的升級版本,其結構根自動編碼器是相似的,也由編碼器和解碼器構成。
自動編碼器就是須要輸入一張圖片,而後將一張圖片編碼以後獲得一個隱含向量,這比咱們隨機取一個隨機噪聲更好,由於這包含着原圖片的信息,而後咱們隱含向量解碼獲得與原圖片對應的照片。
可是這樣咱們其實並不能任意生成圖片,由於咱們沒有辦法本身去構造隱藏向量,咱們須要經過一張圖片輸入編碼咱們才知道獲得的隱含向量是什麼,這時咱們就能夠經過變分自動編碼器來解決這個問題。
其實原理特別簡單,只須要在編碼過程當中給它增長一些限制,迫使其生產的隱含向量可以粗略的遵循一個標準正態分佈,這就是與通常的自動編碼器最大的不一樣。
這樣咱們生產一張新的圖片就很簡單了,咱們只須要給它一個標準正態分佈的隨機隱含向量,這樣經過解碼器就能生成咱們想要的圖片,而不須要給他一張原始圖片先編碼。
在實際狀況中,咱們須要在模型的準確率上與隱含向量服從標準正態分佈之間作一個權衡,所謂模型的準確率就是指解碼器生成的圖片與原圖片的類似程度。咱們可讓網絡本身來作這個決定,很是簡單,咱們只須要將這二者都作一個loss,而後在將他們求和做爲總的loss,這樣網絡就可以本身選擇如何纔可以使得這個總的loss降低。另外咱們要衡量兩種分佈的類似程度,如何看過以前一片GAN的數學推導,你就知道會有一個東西叫KL divergence來衡量兩種分佈的類似程度,這裏咱們就是用KL divergence來表示隱含向量與標準正態分佈之間差別的loss,另一個loss仍然使用生成圖片與原圖片的均方偏差來表示。
reconstruction_function = nn.BCELoss(size_average=False) # mse loss def loss_function(recon_x, x, mu, logvar): """ recon_x: generating images x: origin images mu: latent mean logvar: latent log variance """ BCE = reconstruction_function(recon_x, x) # loss = 0.5 * sum(1 + log(sigma^2) - mu^2 - sigma^2) KLD_element = mu.pow(2).add_(logvar.exp()).mul_(-1).add_(1).add_(logvar) KLD = torch.sum(KLD_element).mul_(-0.5) # KL divergence return BCE + KLD
另外變分編碼器除了可讓咱們隨機生成隱含變量,還可以提升網絡的泛化能力、
最後是VAE的代碼實現:
class VAE(nn.Module): def __init__(self): super(VAE, self).__init__() self.fc1 = nn.Linear(784, 400) self.fc21 = nn.Linear(400, 20) self.fc22 = nn.Linear(400, 20) self.fc3 = nn.Linear(20, 400) self.fc4 = nn.Linear(400, 784) def encode(self, x): h1 = F.relu(self.fc1(x)) return self.fc21(h1), self.fc22(h1) def reparametrize(self, mu, logvar): std = logvar.mul(0.5).exp_() if torch.cuda.is_available(): eps = torch.cuda.FloatTensor(std.size()).normal_() else: eps = torch.FloatTensor(std.size()).normal_() eps = Variable(eps) return eps.mul(std).add_(mu) def decode(self, z): h3 = F.relu(self.fc3(z)) return F.sigmoid(self.fc4(h3)) def forward(self, x): mu, logvar = self.encode(x) z = self.reparametrize(mu, logvar) return self.decode(z), mu, logvar
VAE 的結果比普通的自動編碼器要好不少,下面是結果(左邊是原圖 ,右邊是自動編碼):
VAE的缺點也很明顯,他是直接計算生成圖片和原始圖片的均方偏差而不是像GAN那樣去對抗來學習,這就使得生成的圖片會有點模糊。如今已經有一些工做是將VAE和GAN結合起來,使用VAE的結構,可是使用對抗網絡來進行訓練,具體能夠參考一下這篇論文。
在以前學習的 TensorFlow學習筆記——使用TensorFlow操做MNIST數據(1)。咱們實現了一個簡單的 Softmax Regression 模型,這個線性模型最大的特色是簡單易用,可是擬合能力不強。Softmax Regression 能夠算是多分類問題 logistic regression ,它進而傳統意義上的神經網絡的最大區別是哪些沒有隱含層。隱含層是神經網絡一個重要概念。它是指除輸出,輸出層外,中間的哪些層。輸入層和輸出層是對外可見的,所以也能夠被稱做可視層,而中間層不直接暴露出來,是模型的黑箱部分,一般也比較難具備可解釋性,因此通常被稱做隱含層。有了隱含層,神經網絡就具備了一些特殊的屬性,好比引入非線性的隱含層後,理論上只要隱含層節點足夠多,即便只有一個隱含層的神經網絡也能夠擬合任意函數。同時隱含層越多,越容易擬合複雜函數。有理論研究代表,爲了擬合複雜函數須要的隱含節點的數目,基本上隨着隱含層的數量增多呈指數降低趨勢。也就是說層數越多,神經網絡所須要的隱含節點能夠越少。這也是深度學習的特色之一,層數越深,概念越抽象,須要背誦的知識點(神經網絡隱含節點)就越少。不過實際使用中,使用層數較深的神經網絡會遇到許多困難,好比容易過擬合,參數難以調試,梯度彌散等等。對這些問題咱們須要不少 Trick 來解決,在最近幾年的研究中心,愈來愈多的方法,好比Dropout,Adagrad,ReLUdeng ,逐漸幫助咱們解決了一部分問題。
過擬合是機器學習中一個常見的問題,它是指模型預測準確率在訓練集上升高,可是在測試集上反而降低了,這一般意味着泛化性很差,模型只是記憶了當前數據的特徵,不具有推廣能力,尤爲是在神經網絡中,由於參數衆多,常常出現參數比數據還要多的狀況,這就很是容易出現只是記憶了訓練集特徵的狀況。爲了解決這個問題,Hinton教授團隊提出了一個思路簡單可是很是有效的方法,Dropout。在使用複雜的卷積神經網絡訓練圖像數據時尤爲有效,它的大體思路是在訓練時,將神經網絡某一層的輸出節點數據隨機丟棄一部分。咱們能夠理解爲隨機把一張圖片50%的點刪除掉(即隨機將50%的點變成黑點),此時人仍是極可能識別出這種圖片的類型,當時機器也是能夠的。這種作法實質上等於創造出了許多新的隨機樣本,經過增大樣本量,減小特徵數量來防止過擬合。Dropout 其實也算一種 bagging 方法,咱們能夠理解成每次丟棄節點數據是對特徵的一種採樣。至關於咱們訓練一個 ensemble 的神經網絡模型,對每一個樣本都作特徵採樣,只不過沒有訓練多個神經網絡模型,只要一個融合的神經網絡。
參數難以調試時神經網絡的另外一大痛點,尤爲是SGD的參數,對SGD設置不一樣的學習速率,最後獲得的結果可能差距巨大。神經網絡一般不是一個凸優化的問題,它到處充滿了局部最優。SGD自己也不是一個比較穩定的算法,結果可能會在最優解附近波動,而不一樣的學習速率可能致使神經網絡落入大相徑庭的局部最優之中。不過,一般咱們也並不期望能達到全局最優,有理論表示,神經網絡可能有不少個局部最優解均可以達到比較好的分類效果,而全局最優反而是比較容易過擬合的解。咱們也能夠從人來類推,不一樣的人有各自迥異的腦神經鏈接,沒有兩我的的神經鏈接方式能徹底一致,就像沒有兩我的的看法能徹底相同,可是每一個人的腦神經網絡(局部最優解)對識別圖片中物體類別都有很不錯的效果。對SGD,一開始咱們可能但願學習速率大一些,能夠加速收斂,可是訓練的後期又但願學習速率能夠小一些,這樣能夠比較穩定的落入一個局部最優解。不一樣的機器學習問題所須要的學習速率也不太好設置,須要反覆調試,所以就像有 Adagrad, Adam, Adadelta 等自適應的方法能夠減輕調試參數的負擔。對於這些優化算法,一般咱們使用它默認的參數設置就能夠取得一個比較好的效果。而SGD則須要對學習速率,Momentum, Nesterov 等參數進行比較複雜的調試,當調試的參數較爲適合問題時,才能達到比較好的效果。
梯度彌散(Gradient Vanishment)是另外一個影響深層神經網絡訓練的問題,在ReLU激活函數出現以前,神經網絡訓練所有都是用 Sigmoid 做爲激活函數。這多是由於 Sigmoid 函數具備侷限性,輸出數值在0~1 ,最符合機率輸出的定義。非線性的Sigmoid 函數在信號的特徵空間映射上,對中央區的信息增益較大,對兩側區的信號增益小。從生物神經科學的角度來看,中央區酷似神經元的興奮態,兩側區酷似神經元的抑制態。於是在神經網絡訓練時,能夠將重要特徵置於中央區,將非中央特徵置於兩側區。能夠說,Sigmoid比最初期的線性激活函數 y=x ,階梯激活函數 y=x ,階梯激活函數 和 好了很多。可是當神經網絡層數較多時,Sigmoid函數在反向傳播中梯度值會逐漸減小,通過多層的傳遞後會呈現指數級急劇降低,所以梯度值在傳遞到前面幾層時就變得很是小了。這種狀況下,根據訓練數據的反饋來更新神經網絡的參數將會很是緩慢,基本起不到訓練的做用。直到ReLU 的出現,才比較完美的解決了梯度彌散的問題。ReLU是很是簡單的非線性函數 Y=max(0, x),它在座標軸上式一條折線,當x<=0時,y=0;當x>0時, y=x,很是相似於人腦閾值響應機制。信號在超過某個閾值時,神經元纔會進入興奮和激活的狀態,平時則處於抑制狀態。ReLU能夠很好地傳遞梯度,通過多層的反向傳播,梯度依舊不會大幅縮小,所以很是適合訓練很深的神經網絡。ReLU從正面解決了梯度彌散的問題,而不須要經過無監督的逐層訓練初始化權重來繞行。
ReLU對比 Sigmoid 的主要變化有以下三點:
神經科學家在進行大腦能量消耗的研究中發現,神經元編碼的工做方式具備稀疏性,推測大腦同時被激活的神經元只有1%~4%。神經元只會對輸入信號有少部分的選擇性響應,大量不相關的信號被屏蔽,這樣能夠更高效的提取重要特徵。傳統的Sigmoid函數則有接近一半的神經元被激活,不符合神經科學的研究。Softplus雖然有單側抑制,卻沒有稀疏激活性,於是ReLU函數 max(0, x) 成了最符合實際神經元的模型。目前,ReLU及其變種(EIU34,PReLU35,RReLU36)已經成爲了最主流的激活函數。實踐中大部分狀況下(包含MLP和CNN,RNN內部主要仍是使用 Sigmoid,Tanh,Hard Sigmoid)將隱含層的激活函數從 Sigmoid 替換爲ReLU均可以帶來訓練速度及模型準確率的提高。固然神經網絡的輸出層通常都仍是Sigmoid函數,由於他最接近機率輸出分佈。
上面三段分別提到了能夠解決多層神經網絡問題的Dropout,Adagrad,ReLU等,那麼多層神經網絡到底有什麼顯著的能力值得你們探索呢?或者說神經網絡的隱含層到底有什麼用呢?隱含層的一個表明性的功能是能夠解決XOR問題。在早期神經網絡的研究中,有學者提出一個尖銳的問題,當時(沒有隱含層)的神經網絡沒法解決XOR的問題。以下圖所示,假設咱們有兩個維度的特徵,而且有兩類樣本,(0, 0),(1, 1)是灰色,(0, 1), (1, 0)是黑色,在這個特徵空間中這兩類樣本時線性不可分的,也就是說,咱們沒法用一條直線把灰,黑兩類分卡。沒有隱含層的神經網絡時線性的,因此不可能對着兩類樣本進行正確的區分。這是早期神經網絡的致命弱點,也直接致使了當時神經網絡研究的低谷。當引入了隱含層並使用非線性的激活函數(如Sigmoid,ReLU)後,咱們可使用曲線劃分兩類樣本,能夠輕鬆的解決XOR異或函數的分類問題。這就是多層神經網絡(或多層感知器,Multi-Layer Perceptron,MLP)的功能所在。
接下來,咱們一般例子展現在僅加入一個隱含層的狀況下,神經網絡對MNIST數據集的分類項能就有顯著的提高,能夠達到98%的準確率。固然這裏使用了Dropout,Adagrad,ReLU等輔助性組件。
在以前使用TensorFlow實現一個完整的Softmax Regression(無隱含層)並在MNIST數據集上取得了大約92%的正確率。下面實現多層感知機依然使用 MNIST數據集,如今要給審計網絡加上隱含層,並使用減輕過擬合的Dropout,自適應學習速率的Adagrad,以及能夠解決梯度彌散的激活函數ReLU。
代碼:
#_*_coding:utf-8_*_ from tensorflow.examples.tutorials.mnist import input_data import tensorflow as tf mnist = input_data.read_data_sets('MNITS_data/', one_hot=True) # 建立一個默認的InteractiveSession,這樣後面執行各項操做就無須指定Session sess = tf.InteractiveSession() in_uints = 784 # 輸入節點數 # 在此模型中隱含層的節點數設置在200~1000範圍內結果區別都不大 h1_uints = 300 # 隱含層的輸出節點數設爲300 # W1 b1是隱含層的權重和偏置,將偏置所有賦值爲0 # 並將權重初始化爲截斷的正態分佈,其標註差爲0.1 W1 = tf.Variable(tf.truncated_normal([in_uints, h1_uints], stddev=0.1)) b1 = tf.Variable(tf.zeros([h1_uints])) W2 = tf.Variable(tf.zeros([h1_uints, 10])) b2 = tf.Variable(tf.zeros([10])) # 因爲在訓練和預測時,Dropout的比率keep_prob(即保留節點的機率)是不同的 x = tf.placeholder(tf.float32, [None, in_uints]) keep_prob = tf.placeholder(tf.float32) # 定義模型結構,神經網絡forward時的計算 hidden1 = tf.nn.relu(tf.matmul(x, W1) + b1) hidden1_drop = tf.nn.dropout(hidden1, keep_prob) y = tf.nn.softmax(tf.matmul(hidden1_drop, W2) + b2) y_ = tf.placeholder(tf.float32, [None, 10]) cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1])) train_step = tf.train.AdagradOptimizer(0.3).minimize(cross_entropy) # 訓練模型 tf.global_variables_initializer().run() for i in range(3000): batch_xs, batch_ys = mnist.train.next_batch(100) # 咱們加入了keep_prob做爲計算圖的輸入,並在訓練時候設爲0.75,其他的0.25置爲0 train_step.run({x: batch_xs, y_: batch_ys, keep_prob: 0.75}) # 對模型進行準確率評測, 加入一個keep_prob做爲輸入 # 由於預測部分,因此咱們直接令keep_prob=1便可,這樣能夠達到模型最好的預測效果 correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) print(accuracy.eval({x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
訓練的時候,這裏加入了keep_prob做爲計算圖的輸入,而且在訓練時設爲0.75,即保留75%的節點,其他的25%置爲0。通常來講,對越複雜越大規模的神經網絡,Dropout的效果越顯著。另外,由於加入了隱含層,咱們須要更多的訓練迭代來優化模型參數以達到一個比較好的效果。因此一共採用了3000個 batch,每一個 batch包含100個樣本,一共30萬的樣本,至關於對全數據集進行了5輪(epoch)迭代。若是增長循環次數,準確率也會略有提升。
結果以下:
0.9778
最終,咱們在測試集上能夠達到97.78%的準確率。相比以前的Softmax,咱們的偏差率由8%降低到2%,對識別銀行帳單這種精確度要求很高的場景,能夠說是飛躍性的提升。而這個提高僅增長一個隱含層就實現了,可見多層神經網絡的效果有多顯著。固然,其實咱們也使用了一些Trick 進行輔助,好比 Dropout,Adagrad,ReLU等,可是起決定性做用的仍是隱含層自己,它能對特徵進行抽象和轉化。
沒有隱含層的Softmax Regression 只能直接從圖像的像素點推斷是哪一個數字,而沒有特徵抽象的過程。多層神經網絡依靠隱含層,則能夠組合出高階特徵,好比橫線,豎線,圓圈等,以後能夠將這些高階特徵或者說組件再組合成數字,就能實現精準的匹配和分類。隱含層輸出的高階特徵(組件)常常是能夠複用的,因此每一類的判別,機率輸出都共享這些高階特徵,而不是各自鏈接獨立的高階特徵。
同時咱們能夠發現,新加一個隱含層,並使用了Dropout,Adagrad和ReLU,而代碼沒有增長不少,這就是TensorFlow的優點之一。它的代碼很是簡潔,沒有太多的冗餘,能夠方便地將有用的模塊拼裝在一塊兒。
參考文獻: