【theano-windows】学习笔记十三——去噪自编码器
前言
上一章節學習了卷積的寫法,主要注意的是其實現在theano.tensor.nnet和theano.sandbox.cuda.dnn中都有對應函數實現, 這一節就進入到無監督或者稱為半監督的網絡構建中. 首先是自編碼器(Autoencoders)和降噪自編碼器(denoising Autoencoders)
國際慣例, 參考網址:
Denoising Autoencoders (dA)
Learning deep architectures for {AI}
Extracting and Composing Robust Features with Denoising Autoencoders
降噪自動編碼器(Denoising Autoencoder)
理論
自編碼器
其實就是先將dd維輸入數據x∈[0,1]x∈[0,1]映射到d′d′維的隱層y∈[0,1]y∈[0,1], 然后再重構回去得到dd維的zz的過程. 這兩步的數學表達式為:
依據第三個參考文獻 ,這里面 xx代表輸入數據,WW代表輸入層到隱層的 (d′×d)(d′×d)維權重, bhbh是隱層偏置, ss代表激活函數,所以編碼層參數就是θ=(W,b)θ=(W,b), 反向映射(隱層到重構層)的權重 W′W′可以被約束為 W′=WTW′=WT, 稱為綁定權重( tied weights),所以解碼層的參數就是 θ′=(W′,b′)θ′=(W′,b′)
損失函數可以是平均重構誤差
其中 LL可以是傳統的方差損失
L(x,z)=∥x?z∥2L(x,z)=∥x?z∥2
如果 xx和zz是位向量或者位(伯努利)概率向量,那么損失就可以是重構交叉熵
LH(x,z)=H(Bx∥Bz)=?∑k=1d[xklogzk+(1?xk)log(1?zk)]LH(x,z)=H(Bx∥Bz)=?∑k=1d[xklog?zk+(1?xk)log?(1?zk)]
而根據第二篇參考博客所說, 如果隱層是線性的, 且以均方誤差為損失去訓練網絡,那么 kk個隱單元學到的就是將輸入投影到數據的前kk個主分量中, 可以看成是一個PCA了.
自編碼器所期望的是隱層的分布表示能夠捕捉到數據變化的主要因素, 對于所有的輸入xx, 它會是一個很好的有損壓縮,學習使得它對訓練數據的壓縮更好,而且也希望對其它數據也更好, 當然并非是針對任意的輸入.
有一個嚴重的問題是,如果沒有其他任何的限制,具有nn維輸入的自編碼和至少nn維的隱單元(編碼層), 那么可能學習到的就是恒等函數(‘identity function ‘), 很多隱神經元就沒用了(僅僅是將輸入數據復制了一遍), 令人驚訝的是, 實際上如果我們使用隨機梯度下降訓練方法, 即使是具有比輸入層神經元數更多的隱單元(過完備overcomplete)的非線性自編碼器, 也產生了有用的特征表示, 一個簡單的原因是提前停止的隨機梯度下降算法與l2l2正則項很像. 為了達到對連續數據更好的重構效果,具有非線性隱單元層的自編碼器在第一層需要很小的權重(將隱單元的非線性引入到它們線性狀態中), 第二層需要很大的權重. 如果是二值輸入, 需要非常大的權重去完全最小化重構誤差.但是由于顯式或者隱式的正則化很難得到較大的權重解, 優化算法找到的編碼單元(隱層)僅僅對于訓練集相似的樣本效果好, 這就是我們希望的. 這也就以為這特征表示在探索訓練集的統計規律而非去學習恒等函數.
有很多方法去阻止自編碼器學習到恒等函數, 同時在隱層表示中獲取到有用的知識. 除了給自編碼器添加隱式或者顯式的權重正則, 另一個方法是給編碼層增加噪聲.這實際上是RBM所做的東東. 另一個方法是基于編碼的稀疏約束. 但是稀疏或者正則化為了避免學習到恒等函數而降低了表達能力, RBM相對來說表達能力很強, 也不會學習恒等函數, 因為它不僅編碼輸入, 也通過生成模型的極大似然估計逼近方法捕捉到了輸入的統計結構.
降噪自編碼器
有一種自編碼器就共享了RBM的這個特性,稱為降噪自編碼器(denoising auto-encoder), 它是最小化對輸入的隨機損壞變換的重構誤差, 最小化生成模型的對數似然的下界.
降噪自編碼器做了兩件事情:
- 嘗試編碼輸入
- 嘗試重做對輸入的隨機損壞處理操作
后者僅僅可以通過捕捉輸入之間的非統計依賴.實際上, 隨機損壞過程包含將輸入的某些值置零.因而降噪自編碼器嘗試對丟失值進行預測. 訓練標準就是重構對數似然
其中 xx是無損的輸入, x^x^是隨機有損輸入, c(x^)c(x^)是從 x^x^獲得的編碼.
降噪自編碼兩個有趣的屬性就是:
- 它與生成模型對應, 它的訓練準則就是生成模型的對數似然的一個界限
- 他能夠被用于修復丟失數據或者多模數據, 因為它就是在有損數據上訓練的
代碼實現
模型構建與訓練
先引入相關庫,這里要注意引入一個theano中的隨機數生成器,主要用于對數據的有損處理
import theano import theano.tensor as T import numpy as np import os import cPickle,gzip from theano.tensor.shared_randomstreams import RandomStreams然后讀數據沒啥好說的
#定義讀數據的函數,把數據丟入到共享區域 def load_data(dataset):data_dir,data_file=os.path.split(dataset)if os.path.isfile(dataset):with gzip.open(dataset,'rb') as f:train_set,valid_set,test_set=cPickle.load(f)#共享數據集def shared_dataset(data_xy,borrow=True):data_x,data_y=data_xyshared_x=theano.shared(np.asarray(data_x,dtype=theano.config.floatX),borrow=borrow)shared_y=theano.shared(np.asarray(data_y,dtype=theano.config.floatX),borrow=borrow)return shared_x,T.cast(shared_y,'int32')#定義三個元組分別存儲訓練集,驗證集,測試集train_set_x,train_set_y=shared_dataset(train_set)valid_set_x,valid_set_y=shared_dataset(valid_set)test_set_x,test_set_y=shared_dataset(test_set)rval=[(train_set_x,train_set_y),(valid_set_x,valid_set_y),(test_set_x,test_set_y)]return rval初始化網絡結構, 雖然代碼長,但是按部就班地寫就可以了, 和所有的神經網絡定義方法一樣, 先初始化參數權重偏置, , 隨后定義梯度更新方法, 只不過自編碼的提取更新涉及到編碼和解碼兩個階段, 所以再額外對這兩個操作進行定義, 而在輸入的時候, 為了切換是去噪自編碼還是普通的自編碼器, 加入一個有損函數去隨機對輸入置零
class dA(object):#初始化所需參數,隨機初始化,輸入,輸入單元數,隱單元數, 權重,偏置def __init__(self,rng,input=None,n_visible=784,n_hidden=500,W=None,h_b=None,v_b=None):self.n_visible=n_visibleself.n_hidden=n_hiddenif not W:initial_W=np.asarray(rng.uniform(low=-4*np.sqrt(6./(n_hidden+n_visible)),high=4*np.sqrt(6./(n_hidden+n_visible)),size=(n_visible,n_hidden)),dtype=theano.config.floatX)W=theano.shared(initial_W,name='W',borrow=True)if not h_b:h_b=theano.shared(np.zeros(n_hidden,dtype=theano.config.floatX),borrow=True)if not v_b:v_b=theano.shared(np.zeros(n_visible,dtype=theano.config.floatX),borrow=True)self.W=Wself.vb=v_bself.hb=h_bself.W_prime=self.W.Tif input is None:self.x=T.dmatrix(name='input')else:self.x=inputself.params=[self.W,self.vb,self.hb]#編碼階段def get_hidden_value(self,input):return T.nnet.sigmoid(T.dot(input,self.W)+self.hb)#解碼階段def get_reconstructed_input(self,hidden):return T.nnet.sigmoid(T.dot(hidden,self.W_prime)+self.vb)#是否有損輸入,如果是有損輸入就是降噪自編碼器了def get_corrupted_input(self,input,corruption_level):srng=RandomStreams(np.random.randint(2**30))return srng.binomial(size=input.shape,n=1,p=1-corruption_level,dtype=theano.config.floatX)*input#更新參數def get_cost_updates(self,corruption_level,learning_rate):tilde_x=self.get_corrupted_input(self.x,corruption_level)#有損數據y=self.get_hidden_value(tilde_x)#編碼z=self.get_reconstructed_input(y)#解碼#損失函數L=-T.sum(self.x*T.log(z)+(1-self.x)*T.log(1-z),axis=1)cost=T.mean(L)#參數梯度gparams=T.grad(cost,self.params)#更新權重偏置updates=[(param,param-learning_rate*gparam) for param,gparam in zip(self.params,gparams)]return (cost,updates)定義訓練過程,這里就不用那個提前停止算法了,直接讓他訓練15次, 訓練方法照舊, 重點注意的是模型結構的定義, 建議將前面的MLP和CNN的博客對網絡結構的定義部分與dA的網絡結構的定義對比著看. 保存模型這里直接保存權重和偏置算了.
#定義訓練過程 def test_dA(learning_rate=0.1,n_epoches=15,dataset='mnist.pkl.gz',n_visible=28*28,n_hidden=500,corruption_level=0.3,batch_size=20):#數據集datasets=load_data(dataset=dataset)train_set_x,train_set_y=datasets[0]valide_set_x,valide_set_y=datasets[1]test_set_x,test_set_y=datasets[2]#計算小批數據的批數n_train_batches=train_set_x.get_value(borrow=True).shape[0]//batch_sizen_valid_batches=valide_set_x.get_value(borrow=True).shape[0]//batch_sizen_test_batches=test_set_x.get_value(borrow=True).shape[0]//batch_sizeindex=T.iscalar()#批索引x=T.matrix('x')rng=np.random.RandomState(123)#初始化一個去噪自編碼器da=dA(rng=rng,input=x,n_visible=n_visible,n_hidden=n_hidden)#參數更新cost,updates=da.get_cost_updates(corruption_level=0,learning_rate=learning_rate)#訓練函數train_model=theano.function([index],cost,updates=updates,givens={x:train_set_x[index*batch_size:(index+1)*batch_size]})#訓練for epoch in range(n_epoches):c=[]for batch_index in range(n_train_batches):c.append(train_model(batch_index))print ('Training epoch %d, cost' % epoch,np.mean(c,dtype='float32'))save_file=open('best_model_dA.pkl','wb')model=[da.params]cPickle.dump( model,save_file)最后就可以進行模型訓練了
test_dA() #輸出 ''' ('Training epoch 0, cost', 63.23605) ('Training epoch 1, cost', 55.798237) ('Training epoch 2, cost', 54.78653) ('Training epoch 3, cost', 54.273125) ('Training epoch 4, cost', 53.922806) ('Training epoch 5, cost', 53.654221) ('Training epoch 6, cost', 53.436089) ('Training epoch 7, cost', 53.253082) ('Training epoch 8, cost', 53.096138) ('Training epoch 9, cost', 52.95927) ('Training epoch 10, cost', 52.838261) ('Training epoch 11, cost', 52.730087) ('Training epoch 12, cost', 52.632538) ('Training epoch 13, cost', 52.543911) ('Training epoch 14, cost', 52.462799) '''小插曲
期間發生了一件有趣的事情,初始化權重的時候我使用了
initial_W=np.asarray(rng.uniform(low=-4*np.sqrt(6./(n_hidden+n_visible)),high=4*np.sqrt(6./n_hidden+n_visible),size=(n_visible,n_hidden)),dtype=theano.config.floatX)而不是
initial_W=np.asarray(rng.uniform(low=-4*np.sqrt(6./(n_hidden+n_visible)),high=4*np.sqrt(6./(n_hidden+n_visible)),size=(n_visible,n_hidden)),dtype=theano.config.floatX)也就是說我的權重應該是蠻大的, 最小的權重也比28*28的值大, 結果訓練的時候出現了以下狀況
('Training epoch 0, cost', 2455178.8) ('Training epoch 1, cost', 8450.71) ('Training epoch 2, cost', 7065.3008) ('Training epoch 3, cost', 6342.0913) ('Training epoch 4, cost', 5833.1704) ('Training epoch 5, cost', 5438.9961) ... ('Training epoch 292, cost', 146.0446) ('Training epoch 293, cost', 145.98045) ('Training epoch 294, cost', 145.89928) ('Training epoch 295, cost', 145.86646) ('Training epoch 296, cost', 145.71715) ('Training epoch 297, cost', 145.62868) ('Training epoch 298, cost', 145.54076) ('Training epoch 299, cost', 145.44417) ('Training epoch 300, cost', 145.35019) ('Training epoch 301, cost', 145.3033) ('Training epoch 302, cost', 145.21727) ('Training epoch 303, cost', 145.02127) ('Training epoch 304, cost', 144.89236) ('Training epoch 305, cost', 144.8385) ('Training epoch 306, cost', 144.68234) ('Training epoch 307, cost', 144.59572) ...所以, 說實話, 權重的初始化對模型的訓練的收斂情況有很大影響啊,以后權重初始化最好還是用fan_in,fan_out準則, 前面博客有講這兩個公式
使用模型
首先想一下這個best_model_dA.pkl里面存的是什么?依據我對python的菜鳥級想法, da.params應該就是self.params里面的一個權重和兩個偏置, 然后我們去pychar中調試一波看看
果然是的, 那么我們就有譜怎么調用了.
先初始化一個測試網絡結構, 用于對輸入圖像進行編碼和解碼
#使用模型 model_params=cPickle.load(open('best_model_dA.pkl')) rng=np.random.RandomState(123) x=T.matrix('x') coder=T.matrix('coder') single_input=x.reshape((1,28*28)) da_test=dA(rng=rng,input=single_input,n_visible=28*28,n_hidden=model_params[0][0].get_value().shape[1]) #權重賦值 da_test.W=model_params[0][0].get_value() da_test.vb=model_params[0][1].get_value() da_test.hb=model_params[0][2].get_value() #重構計算 forward_compute=theano.function([single_input],da_test.get_reconstructed_input(coder),givens={coder:da_test.get_hidden_value(single_input)})然后我們使用一張圖片做做測試
#輸入一張圖片試試 from PIL import Image import pylab img=Image.open('E:\\code_test\\theano\\binarybmp\\9.bmp') img_w,img_h=img.size#圖像的寬和高 img=np.asarray(img,dtype='float32') pylab.imshow(img) pylab.show() #原始圖片是28*28,要增加兩個維度 img=img.reshape((1,28*28))重構一波試試
data_recon=forward_compute(img) data_recon=data_recon.reshape(28,28) data_recon=np.asarray(data_recon,dtype='float32') pylab.imshow(data_recon) pylab.show()我只想說”這。。。。。誕生了一個什么鬼哦”,再測試一個數字2.bmp試試
吾有一橘麻麥皮,不知當槳不當槳
不過嘛畢竟是最最最基本的自編碼器,可能效果就是這樣,畢竟上面我們也能看出來重構的分別是9和2. 雖然有點勉強。。。。。。如果是代碼錯誤,希望各位指正
更新日志2018-8-15
后面用TensorFlow寫了一下自編碼,發現數據歸一化對結果影響很大,這篇博客效果差的原因極有可能是數據未歸一化問題,有興趣的可以試試,不過我轉型TensorFlow了暫時,所以這個代碼就不折騰了o(╯□╰)o
博客code打包:鏈接: https://pan.baidu.com/s/1nvGDkTj 密碼: ykid
官方code打包:鏈接: https://pan.baidu.com/s/1eRQK4nS 密碼: yemn
總結
以上是生活随笔為你收集整理的【theano-windows】学习笔记十三——去噪自编码器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 信用卡到期换卡怎么激活?不激活有影响吗?
- 下一篇: 读写bin