【TensorFlow-windows】学习笔记六——变分自编码器
#前言
對理論沒興趣的直接看代碼吧,理論一堆,而且還有點復雜,我自己的描述也不一定準確,但是代碼就兩三句話搞定了。
國際慣例,參考博文
論文:Tutorial on Variational Autoencoders
【干貨】一文讀懂什么是變分自編碼器
CS598LAZ - Variational Autoencoders
MusicVAE: Creating a palette for musical scores with machine learning
【Learning Notes】變分自編碼器(Variational Auto-Encoder,VAE)
花式解釋AutoEncoder與VAE
#理論
##基礎知識
似然函數(引自百度百科)
似然函數是關于統計模型中的參數的函數,表示模型參數的似然性。在給定輸出xxx時,關于參數θ\thetaθ的似然函數L(θ∣x)L(\theta|x)L(θ∣x)在數值上等于給定參數θ\thetaθ后變量XXX的概率:
L(θ∣x)=P(X=x∣θ)L(\theta|x)=P(X=x|\theta) L(θ∣x)=P(X=x∣θ)
有兩個比較有趣的說法來區分概率與似然的關系,比如拋硬幣的例子:
- 概率說法:對于“一枚正反對稱的硬幣上拋十次”這種事件,問硬幣落地時十次都是正面向上的“概率”是多少
- 似然說法:對于“一枚硬幣上拋十次”,問這枚硬幣正反面對稱的“似然”程度是多少。
極大似然估計
(摘自西瓜書)兩大學派:
- 頻率主義學:參數是固定的,通過優化似然函數來確定參數
- 貝葉斯學派:參數是變化的,且本身具有某種分布,先假設參數服從某個先驗分布,然后基于觀測到的數據來計算參數的后驗分布
極大似然估計(Maximum Likelihood Estimation,MLE)源自頻率主義學。
假設DDD是第ccc類樣本的集合,比如所有的數字333的圖片集合,假設它們是獨立同分布的,則參數θ\thetaθ對于數據集DDD的似然是:
P(D∣θ)=∏x∈DP(x∣θ)P(D|\theta)=\prod_{x\in D}P(x|\theta) P(D∣θ)=x∈D∏?P(x∣θ)
極大似然估計就是尋找一個θ\thetaθ使得樣本xxx出現的概率最大
但是上面的連乘比較難算,這就出現了對數似然:
L(θ)=log?P(D∣θ)=∑x∈Dlog?P(x∣D)L(\theta)=\log P(D|\theta)=\sum_{x\in D}\log P(x|D) L(θ)=logP(D∣θ)=x∈D∑?logP(x∣D)
我們的目標就是求參數θ\thetaθ的極大似然估計θ^\hat{\theta}θ^
θ^=arg?max?θL(θ)\hat{\theta}=\arg \max_{\theta}L(\theta) θ^=argθmax?L(θ)
例子:在連續屬性情況下,如果樣本集合概率密度函數p(x∣c)~N(μ,σ2)p(x|c)\sim N(\mu,\sigma^2)p(x∣c)~N(μ,σ2),那么參數μ,σ2\mu,\sigma^2μ,σ2的極大似然估計就是
μ^=1∣D∣∑x∈Dxσ^2=1∣D∣∑x∈D(x?μ^)(x?μ^)T\begin{aligned} \hat{\mu}&=\frac{1}{|D|}\sum_{x\in D}x\\ \hat{\sigma}^2&=\frac{1}{|D|}\sum_{x \in D}(x-\hat{\mu})(x-\hat{\mu})^T \end{aligned} μ^?σ^2?=∣D∣1?x∈D∑?x=∣D∣1?x∈D∑?(x?μ^?)(x?μ^?)T?
其實就是計算均值和方差了。這樣想,這些樣本就服從這個高斯分布,那么把高斯分布直接當做參數,一定能夠大概率得到此類樣本,也就是說用333的樣本所服從的高斯分布作為模型參數一定能使333出現的概率P(x∣θ)P(x|\theta)P(x∣θ)最大。
###期望值最大化算法(EM)
這一部分簡單說一下即可,詳細的在我前面的博客HMM——前向后向算法中有介紹,主要有兩步:
-
E步:求Q函數Q(θ,θ(i))Q(\theta,\theta^{(i)})Q(θ,θ(i)),這個θ(i)\theta^{(i)}θ(i)就是當前迭代次數iii對應的參數值,Q函數實際就是對數聯合似然函數log?P(X,Z∣θ)\log P(X,Z|\theta)logP(X,Z∣θ)在分布P(Z∣X,θ(i))P(Z|X,\theta^{(i)})P(Z∣X,θ(i))下的期望
Q(θ,θ(i))=EZ∣X,θ(i)L(θ∣X,Z)=Ez[log?P(X,Z∣θ)∣X,θ(i)]=∑ZP(Z∣X,θ(i))log?P(X,Z∣θ)\begin{aligned} Q(\theta,\theta^{(i)})&=E_{Z|X,\theta^{(i)}}L(\theta|X,Z)\\ &=E_z\left[\log P(X,Z|\theta)|X,\theta^{(i)}\right]\\ &=\sum_Z P(Z|X,\theta^{(i)})\log P(X,Z|\theta) \end{aligned} Q(θ,θ(i))?=EZ∣X,θ(i)?L(θ∣X,Z)=Ez?[logP(X,Z∣θ)∣X,θ(i)]=Z∑?P(Z∣X,θ(i))logP(X,Z∣θ)? -
M步:求使得Q函數最大化的參數θ?\theta?θ?,并將其作為下一步的θ(i)?\theta^{(i)}?θ(i)?
θ(i+1)=arg?max?θQ(θ,θ(i))\theta^{(i+1)}=\arg\max_\theta Q(\theta,\theta^{(i)}) θ(i+1)=argθmax?Q(θ,θ(i))
從西瓜書上再摘點主要內容過來:
有時候樣本的一些屬性可以觀測到,而另一些屬性觀測不到,所以就定義未觀測變量為隱變量,設XXX為可觀測變量,ZZZ為隱變量,θ\thetaθ為模型參數,則可寫出對數似然:
L(θ∣X,Z)=ln?P(X,Z∣θ)L(\theta|X,Z)=\ln P(X,Z|\theta) L(θ∣X,Z)=lnP(X,Z∣θ)
但是ZZZ又不知道,所以采用邊緣化(marginal)方法消除它
L(θ∣X)=ln?P(X∣θ)=ln?∑ZP(X,Z∣θ)=∑i=1Nln?{∑ZP(xi,Z∣θ)}L(\theta|X)=\ln P(X|\theta)=\ln\sum_Z P(X,Z|\theta)=\sum_{i=1}^N\ln\left\{\sum_Z P(x_i,Z|\theta)\right\} L(θ∣X)=lnP(X∣θ)=lnZ∑?P(X,Z∣θ)=i=1∑N?ln{Z∑?P(xi?,Z∣θ)}
使用EM算法求解參數的方法是:
- 基于θ(i)\theta^{(i)}θ(i)推斷隱變量ZZZ的期望,記為ZtZ^tZt
- 基于已觀測變量XXX和ZtZ^tZt對參數θ\thetaθ做極大似然估計,求得θ(i+1)\theta^{(i+1)}θ(i+1)
【注】是不是感覺很像坐標下降法
變分推斷
(摘自西瓜書)
變分推斷是通過使用已知簡單分布來逼近需推斷的復雜分布,并通過限制近似分布的類型,從而得到一種局部最優,但具有確定解的近似后驗分布。
繼續看上面的EM算法的M步,我們得到了:
θ(i+1)=arg?max?θQ(θ,θ(i))=arg?max?θ∑ZP(Z∣x,θ(i))ln?P(x,Z∣θ)\begin{aligned} \theta^{(i+1)}&=\arg\max_\theta Q(\theta,\theta^{(i)})\\ &=\arg\max_\theta \sum_ZP\left(Z|x,\theta^{(i)}\right)\ln P(x,Z|\theta) \end{aligned} θ(i+1)?=argθmax?Q(θ,θ(i))=argθmax?Z∑?P(Z∣x,θ(i))lnP(x,Z∣θ)?
還記得QQQ函數的意義吧,對數聯合似然函數ln?P(X,Z∣θ)\ln P(X,Z|\theta)lnP(X,Z∣θ)在分布P(Z∣X,θ(i))P(Z|X,\theta^{(i)})P(Z∣X,θ(i))下的期望。當分布P(Z∣X,θ(i))P(Z|X,\theta^{(i)})P(Z∣X,θ(i))與變量ZZZ的真實后驗分布相等的時候,QQQ函數就近似于對數似然函數,因而EM算法能夠獲得穩定的參數θ\thetaθ,且隱變量ZZZ的分布也能通過該參數獲得。
但是通常情況下,P(Z∣X,θ(i))P(Z|X,\theta^{(i)})P(Z∣X,θ(i))只是隱變量ZZZ所服從的真實分布的近似,若用Q(Z)Q(Z)Q(Z)表示,則
ln?P(X)=L(Q)+KL(Q∣∣P)\ln P(X)=L(Q)+KL(Q||P) lnP(X)=L(Q)+KL(Q∣∣P)
其中
L(Q)=∫Q(Z)ln?{P(X,Z)Q(Z)}dZKL(Q∣∣P)=?∫Q(Z)ln?P(Z∣X)Q(Z)dZL(Q)=\int Q(Z)\ln\left\{\frac{P(X,Z)}{Q(Z)}\right\}dZ\\ KL(Q||P)=-\int Q(Z)\ln \frac{P(Z|X)}{Q(Z)}dZ L(Q)=∫Q(Z)ln{Q(Z)P(X,Z)?}dZKL(Q∣∣P)=?∫Q(Z)lnQ(Z)P(Z∣X)?dZ
但是,這個ZZZ模型可能很復雜,導致E步的P(Z∣X,θ)P(Z|X,\theta)P(Z∣X,θ)比較難推斷,這時候就借用變分推斷了,假設ZZZ服從分布
Q(Z)=∏i=1MQi(Zi)Q(Z)=\prod_{i=1}^MQ_i(Z_i) Q(Z)=i=1∏M?Qi?(Zi?)
也就是說多變量ZZZ可拆解為一系列相互獨立的多變量ZiZ_iZi?,可以另QiQ_iQi?是非常簡單的分布。
【PS】淺嘗輒止了,經過層層理論已經引出了變分自編碼的主要思想,變分推斷,使用簡單分布逼近復雜分布,實際上,變分自編碼所使用的簡單分布就是高斯分布,用多個高斯分布來逼近隱變量,隨后利用服從這些分布的隱變量重構我們想要的數據。
變分自編碼
先看優化目標ELBO(Evidence Lower Bound):
ELBO=log?p(x)?KL[q(z∣x)∣∣p(z∣x)]ELBO=\log p(x)-KL\left[q(z|x)||p(z|x)\right] ELBO=logp(x)?KL[q(z∣x)∣∣p(z∣x)]
其中qqq是假設分布,ppp是真實分布,我們希望最大化第一項而最小化KL距離,所以整個規則就是最大化ELBOELBOELBO,但是這里面有個p(z∣x)p(z|x)p(z∣x)代表隱變量的真實分布,這個是無法求解的,所以需要簡化:
簡化結果是:
log?p(x)?KL(q(z)∣∣p(z∣x))=Ez~q[log?P(x∣z)]?KL(q(z)∣∣p(z))\log p(x)-KL(q(z)||p(z|x))=E_{z\sim q}\left[\log P(x|z)\right]-KL(q(z)||p(z)) logp(x)?KL(q(z)∣∣p(z∣x))=Ez~q?[logP(x∣z)]?KL(q(z)∣∣p(z))
證明:
假設KL距離為KL(q(z)∣∣p(z∣x))=Ez~q[log?q(z)?log?p(z∣x)]KL(q(z)||p(z|x))=E_{z\sim q}\left[\log q(z)-\log p(z|x)\right]KL(q(z)∣∣p(z∣x))=Ez~q?[logq(z)?logp(z∣x)]
那么直接使用貝葉斯準則:
- p(z∣x)=p(x∣z)p(z)p(x)p(z|x)=\frac{p(x|z)p(z)}{p(x)}p(z∣x)=p(x)p(x∣z)p(z)?
- log?p(z∣x)=log?p(x∣z)+log?p(z)?log?p(x)\log p(z|x)=\log p(x|z)+\log p(z)-\log p(x)logp(z∣x)=logp(x∣z)+logp(z)?logp(x)
- p(x)p(x)p(x)不依賴于z
可以得到:
KL(q(z)∣∣p(z∣x))=Ez~q(log?q(z)?log?p(x∣z)?log?p(z))+log?p(x)KL(q(z)||p(z|x))=E_{z\sim q}(\log q(z)-\log p(x|z)-\log p(z))+\log p(x) KL(q(z)∣∣p(z∣x))=Ez~q?(logq(z)?logp(x∣z)?logp(z))+logp(x)
其中Ez~q(log?q(z)?log?p(z))=KL(q(z)∣∣p(z))E_{z\sim q}(\log q(z)-\log p(z))=KL(q(z)||p(z))Ez~q?(logq(z)?logp(z))=KL(q(z)∣∣p(z))
所以就能繼續簡化那個KL(q(z)∣∣p(z∣x))KL(q(z)||p(z|x))KL(q(z)∣∣p(z∣x))了:
log?p(x)?KL(q(z)∣p(z∣x))=Ez~q[log?p(x∣z)]?KL(q(z)∣∣p(z))\log p(x)-KL(q(z)|p(z|x))=E_{z\sim q}[\log p(x|z)]-KL(q(z)||p(z)) logp(x)?KL(q(z)∣p(z∣x))=Ez~q?[logp(x∣z)]?KL(q(z)∣∣p(z))
證畢
但是我們的優化目標是
ELBO=log?p(x)?KL[q(z∣x)∣∣p(z∣x)]ELBO=\log p(x)-KL\left[q(z|x)||p(z|x)\right] ELBO=logp(x)?KL[q(z∣x)∣∣p(z∣x)]
發現一個是q(z)q(z)q(z)一個是q(z∣x)q(z|x)q(z∣x),怎么辦呢?看論文第8頁有這樣一句話:
Note that X is fixed, and Q can be any distribution, not just a distribution which does a good job mapping X to the z’s that can produce X. Since we’re interested in inferring P(X), it makes sense to construct a Q which does depend on X, and in particular, one which makes D [Q(z)k|P(z|X)] small
翻譯一下意思就是:XXX是固定的(因為它是樣本集),Q也可是任意分布,并非僅是能夠生成XXX的分布,因為我們想推斷P(X)P(X)P(X),那么構建一個依賴于XXX的QQQ分布是可行的,還能讓KL(Q(z)∣∣P(z∣X))KL(Q(z)||P(z|X))KL(Q(z)∣∣P(z∣X))較小:
log?p(x)?KL(q(z∣x)∣p(z∣x))=Ez~q[log?p(x∣z)]?KL(q(z∣x)∣∣p(z))\log p(x)-KL(q(z|x)|p(z|x))=E_{z\sim q}[\log p(x|z)]-KL(q(z|x)||p(z)) logp(x)?KL(q(z∣x)∣p(z∣x))=Ez~q?[logp(x∣z)]?KL(q(z∣x)∣∣p(z))
這個式子就是變分自編碼的核心了
這樣我們就知道了優化目標(等號右邊的時候),我們看看變換后的式子為什么能夠計算?
首先沒了p(z∣x)p(z|x)p(z∣x),其次每一項都能計算,我們挨個來看:
-
如何計算q(z∣x)q(z|x)q(z∣x)?
我們可以使用神經網絡逼近q(z∣x)q(z|x)q(z∣x),假設q(z∣x)q(z|x)q(z∣x)服從高斯分布N(μ,σ)N(\mu,\sigma)N(μ,σ)- 神經網絡的輸出就是均值μ\muμ和方差σ\sigmaσ
- 輸入是圖片,輸出是分布
計算q(z∣x)q(z|x)q(z∣x)就是編碼過程了
-
如果計算p(x∣z)?用一個神經網絡去逼近p(x|z)? 用一個神經網絡去逼近p(x∣z)?用一個神經網絡去逼近p(x|z),假設神經網絡輸出是,假設神經網絡輸出是,假設神經網絡輸出是f(z)$
假設p(x∣z)p(x|z)p(x∣z)服從另一種高斯分布- x=f(z)+ηx=f(z)+\etax=f(z)+η,其中η~N(0,I)\eta\sim N(0,I)η~N(0,I)
- 簡化成l2l_2l2?損失:∣∣X?f(z)∣∣2||X-f(z)||^2∣∣X?f(z)∣∣2
計算p(x∣z)p(x|z)p(x∣z)就是解碼過程了
最終損失就是
L=∣∣X?f(z)∣∣2?λ?KL(q(z∣x)∣∣p(z))L=||X-f(z)||^2-\lambda\cdot KL(q(z|x)||p(z)) L=∣∣X?f(z)∣∣2?λ?KL(q(z∣x)∣∣p(z))
在這里,我們先不看這個最終損失的式子,我們去瞅瞅未經過l2l_2l2?簡化的的優化目標
ELBO=Ez~q[log?p(x∣z)]?KL(q(z∣x)∣∣p(z))ELBO=E_{z\sim q}[\log p(x|z)]-KL(q(z|x)||p(z)) ELBO=Ez~q?[logp(x∣z)]?KL(q(z∣x)∣∣p(z))
-
計算第二項的KL散度
我們經常選擇q(z∣x)=N(z∣μ(x;θ),Σ(x;θ))q(z|x)=N(z|\mu(x;\theta),\Sigma(x;\theta))q(z∣x)=N(z∣μ(x;θ),Σ(x;θ)),這里面μ,Σ\mu,\Sigmaμ,Σ通常是任意確定的函數,且其參數θ\thetaθ能夠從數據中學習。通常通過神經網絡獲取,并且Σ\SigmaΣ被限制為一個對角陣。這樣選擇的好處是便于計算,僅此而已,那么右邊的KL(q(z∣x)∣∣p(z))KL(q(z|x)||p(z))KL(q(z∣x)∣∣p(z))就編程了兩個多元高斯分布的KL距離,有閉式解為:
D(N(μ0,Σ0)∣∣N(μ1,Σ1))=12(tr(Σ1?1Σ0)+(μ1?μ0)TΣ1?1(μ1?μ0)?k+log?(det?Σ1det?Σ0)D(N(\mu_0,\Sigma_0)||N(\mu_1,\Sigma_1))=\\ \frac{1}{2}\left(tr(\Sigma^{-1}_1\Sigma_0\right)+(\mu_1-\mu_0)^T\Sigma_1^{-1}(\mu_1-\mu_0)-k+\log(\frac{\det \Sigma_1}{\det \Sigma_0}) D(N(μ0?,Σ0?)∣∣N(μ1?,Σ1?))=21?(tr(Σ1?1?Σ0?)+(μ1??μ0?)TΣ1?1?(μ1??μ0?)?k+log(detΣ0?detΣ1??)
其中kkk是分布的維數,而在變分推斷中,經常又被簡化成
D(N(μ(x),Σ(x))∣∣N(0,I))=12(tr(Σ(x))+(μ(x))T(μ(x))?k+log?det?(Σ(x))D(N(\mu(x),\Sigma(x))||N(0,I))=\\ \frac{1}{2}\left(tr(\Sigma(x)\right)+(\mu(x))^T(\mu(x))-k+\log\det(\Sigma(x)) D(N(μ(x),Σ(x))∣∣N(0,I))=21?(tr(Σ(x))+(μ(x))T(μ(x))?k+logdet(Σ(x)) -
計算第一項Ez~qE_{z\sim q}Ez~q?
論文中說這一項的計算有點小技巧(tricky),本來是可以通過采樣的方法估計Ez~q(log?p(x∣z))E_{z\sim q}(\log p(x|z))Ez~q?(logp(x∣z)),但是只有將很多的zzz通過fff式子(解碼部分)輸出以后才能得到較好的估計結果,這個計算量很大,因此想到了隨機梯度下降,我們可以拿一個樣本zzz,將p(x∣z)p(x|z)p(x∣z)作為Ez~q(log?p(x∣z))E_{z\sim q}(\log p(x|z))Ez~q?(logp(x∣z))的估計,所以式子又變成了:
Ex~D(log?p(x)?KL(q(z∣x)∣∣p(z∣x)))=Ex~D[Ez~q[log?p(x∣z)]]?KL(q(z∣x)∣∣p(z))E_{x\sim D}(\log p(x)-KL(q(z|x)||p(z|x)))=\\ E_{x\sim D}\left[E_{z\sim q}\left[\log p(x|z)\right]\right]-KL(q(z|x)||p(z)) Ex~D?(logp(x)?KL(q(z∣x)∣∣p(z∣x)))=Ex~D?[Ez~q?[logp(x∣z)]]?KL(q(z∣x)∣∣p(z))
意思就是我們從樣本集合DDD中取一個樣本xxx來計算,所以對于單個,可以計算下式梯度:
log?p(x∣z)?KL(q(z∣x)∣∣p(z))\log p(x|z)-KL(q(z|x)||p(z)) logp(x∣z)?KL(q(z∣x)∣∣p(z))
這樣消除了Ez~qE_{z\sim q}Ez~q?中對qqq的依賴。論文中有個圖很好
其實log?p(x∣z)?KL(q(z∣x)∣∣p(z))\log p(x|z)-KL(q(z|x)||p(z))logp(x∣z)?KL(q(z∣x)∣∣p(z))剛好就是左圖,主要就是反傳的時候沒法計算梯度,看左圖紅框部分,這一部分是隨機采樣,是無法計算梯度的,那么文中就說了一個技巧:重新參數化(reparameterization trick),給定了μ(x),Σ(x)\mu(x),\Sigma(x)μ(x),Σ(x)也就是Q(z∣x)Q(z|x)Q(z∣x)的均值和方差,我們先從N(0,I)N(0,I)N(0,I)中采樣,然后計算z=μ(x)+Σ12??z=\mu(x)+\Sigma^{\frac{1}{2}}*\epsilonz=μ(x)+Σ21???,所以我們又可以計算下式的梯度了:
Ex~D[E?~N(0,I)[log?p(x∣z=μ(x)+Σ1/2(x)??)]?KL(q(z∣x)∣∣p(z))]E_{x\sim D}\left[E_{\epsilon\sim N(0,I)}\left[\log p(x|z=\mu(x)+\Sigma^{1/2}(x)*\epsilon)\right]-KL(q(z|x)||p(z)) \right] Ex~D?[E?~N(0,I)?[logp(x∣z=μ(x)+Σ1/2(x)??)]?KL(q(z∣x)∣∣p(z))]
這就完成了從左圖到右圖的轉變。
代碼實現-模型訓練及保存
理論很復雜,但是我們看著右圖就能實現,無需看理論,理論只是讓我們知道為什么會有右圖這種網絡結構。按照標準流程來書寫代碼:
- 讀數據
- 初始化相關參數
- 定義數據接收接口以便測試使用
- 初始化權重和偏置
- 定義基本模塊:編碼器、采樣器、解碼器
- 構建模型
- 定義預測函數、損失函數、優化器
- 訓練
整個代碼很簡單,我就只貼部分重點的:
初始化權重偏置
#初始化權重、偏置 def glorot_init(shape):return tf.random_normal(shape=shape,stddev=1./tf.sqrt(shape[0]/2.0)) #權重 weights={'encoder_h1':tf.Variable(glorot_init([num_input,hidden_dim])),'z_mean':tf.Variable(glorot_init([hidden_dim,latent_dim])),'z_std':tf.Variable(glorot_init([hidden_dim,latent_dim])),'decoder_h1':tf.Variable(glorot_init([latent_dim,hidden_dim])),'decoder_out':tf.Variable(glorot_init([hidden_dim,num_input])) } #偏置 biases={'encoder_b1':tf.Variable(glorot_init([hidden_dim])),'z_mean':tf.Variable(glorot_init([latent_dim])),'z_std':tf.Variable(glorot_init([latent_dim])),'decoder_b1':tf.Variable(glorot_init([hidden_dim])),'decoder_out':tf.Variable(glorot_init([num_input])) }注意這里使用了另一種初始化方法,說是Xavier初始化方法,因為直接使用上一篇博客的方法
tf.Variable(tf.random_normal([num_input,num_hidden1])),訓練時候一直給我彈出loss:nan,我也是醉了,以后還是用之前學theano時候采用的fan_in-fan_out方法初始化權重算了。
定義基本模塊
注意需要定義編碼器、采樣器、解碼器
#定義編碼器 def encoder(x):encoder=tf.matmul(x,weights['encoder_h1'])+biases['encoder_b1']encoder=tf.nn.tanh(encoder)z_mean=tf.matmul(encoder,weights['z_mean'])+biases['z_mean']z_std=tf.matmul(encoder,weights['z_std'])+biases['z_std']return z_mean,z_std#定義采樣器 def sampler(z_mean,z_std):eps=tf.random_normal(tf.shape(z_std),dtype=tf.float32,mean=0,stddev=1.0,name='epsilon')z=z_mean+tf.exp(z_std/2)*epsreturn z#定義解碼器 def decoder(x):decoder=tf.matmul(x,weights['decoder_h1'])+biases['decoder_b1']decoder=tf.nn.tanh(decoder)decoder=tf.matmul(decoder,weights['decoder_out'])+biases['decoder_out']decoder=tf.nn.sigmoid(decoder)return decoder構建模型
#構建模型 [z_mean,z_std]=encoder(X)#計算均值方差 sample_latent=sampler(z_mean,z_std)#采樣隱空間 decoder_op=decoder(sample_latent)#重構輸出預測函數和損失
#預測函數 y_pred=decoder_op y_true=X tf.add_to_collection('recon',y_pred) #定義損失函數和優化器 def vae_loss(x_reconstructed,x_true,z_mean,zstd):#重構損失encode_decode_loss=x_true*tf.log(1e-10+x_reconstructed)\+(1-x_true)*tf.log(1e-10+1-x_reconstructed)encode_decode_loss=-tf.reduce_sum(encode_decode_loss,1)#KL損失kl_div_loss=1+z_std-tf.square(z_mean)-tf.exp(z_std)kl_div_loss=-0.5*tf.reduce_sum(kl_div_loss,1)return tf.reduce_mean(encode_decode_loss+kl_div_loss) loss_op=vae_loss(decoder_op,y_true,z_mean,z_std) optimizer=tf.train.RMSPropOptimizer(learning_rate=learning_rate) train_op=optimizer.minimize(loss_op)注意這里損失的第一項類似于交叉熵損失:y×log?(y^)+(1?y)×(1?log?y^)y\times \log(\hat{y})+(1-y)\times(1-\log \hat y)y×log(y^?)+(1?y)×(1?logy^?)
關于交叉熵損失和均方差損失的區別,可以看我前面的博客:損失函數梯度對比-均方差和交叉熵
訓練和保存模型
#參數初始化 init=tf.global_variables_initializer() input_image,input_label=read_images('./mnist/train_labels.txt',batch_size) #訓練和保存模型 saver=tf.train.Saver() with tf.Session() as sess:sess.run(init)coord=tf.train.Coordinator()tf.train.start_queue_runners(sess=sess,coord=coord)for step in range(1,num_steps):batch_x,batch_y=sess.run([input_image,tf.one_hot(input_label,1,0)])sess.run(train_op,feed_dict={X:batch_x})if step%disp_step==0 or step==1:loss=sess.run(loss_op,feed_dict={X:batch_x})print('step '+str(step)+' ,loss '+'{:.4f}'.format(loss))coord.request_stop()coord.join()print('optimization finished')saver.save(sess,'./VAE_mnist_model/VAE_mnist')常規的保存方法,沒什么說的,訓練日志:
step 1 ,loss 616.3002 step 1000 ,loss 169.6044 step 2000 ,loss 163.5006 step 3000 ,loss 166.1648 step 4000 ,loss 161.2366 step 5000 ,loss 155.1714 step 6000 ,loss 153.2840 step 7000 ,loss 161.5571 step 8000 ,loss 152.0021 step 9000 ,loss 159.5550 step 10000 ,loss 154.6315 step 11000 ,loss 153.8298 step 12000 ,loss 141.5825 step 13000 ,loss 149.7792 step 14000 ,loss 150.9575 step 15000 ,loss 151.2249 step 16000 ,loss 159.3878 step 17000 ,loss 148.7136 step 18000 ,loss 148.5801 step 19000 ,loss 150.6678 step 20000 ,loss 146.3471 step 21000 ,loss 156.4142 step 22000 ,loss 148.7607 step 23000 ,loss 145.4101 step 24000 ,loss 153.3523 step 25000 ,loss 157.8997 step 26000 ,loss 136.9668 step 27000 ,loss 155.7835 step 28000 ,loss 137.7291 step 29000 ,loss 153.1723 optimization finished【很尷尬的事情】偷偷說一句,上面保存錯東東了,別打我,但是也不必重新訓練,看接下來的蛇皮操作。
#代碼實現-模型加載及測試
老樣子,先載入模型
sess=tf.Session() new_saver=tf.train.import_meta_graph('./VAE_mnist_model/VAE_mnist.meta') new_saver.restore(sess,'./VAE_mnist_model/VAE_mnist')獲取計算圖:
graph=tf.get_default_graph()看看保存了啥
print (graph.get_all_collection_keys()) #['queue_runners', 'recon', 'summaries', 'train_op', 'trainable_variables', 'variables']準備調用recon函數重構數據。
等等,“重構”?搞錯了,這里應該是依據噪聲來生成數據的,不是輸入一個數據然后重構它,這是AE的做法,我們在VAE中應該稱之為生成了,然而不幸的是,我們保存的recon函數接收的是圖片輸入,無法指定decoder部分所需的分布參數,也就是均值方差,怎么辦?手動選擇性載入模型,這篇博客有介紹怎么在測試階段定義網絡權重和載入訓練好的權重,但是我好像沒成功,懶得試了,按照我自己的想法來做。
眼尖的童鞋會發現,我們之前一直只關注recon函數了,忽視了其它keys,很快發現最后兩個trainable_variables和variables貌似與我們想要的模型參數有關,我們來輸出一下這兩個東東里面都保存了啥:
第一個trainable_variables
for i in tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES):print(i)輸出
<tf.Variable 'Variable:0' shape=(784, 512) dtype=float32_ref> <tf.Variable 'Variable_1:0' shape=(512, 2) dtype=float32_ref> <tf.Variable 'Variable_2:0' shape=(512, 2) dtype=float32_ref> <tf.Variable 'Variable_3:0' shape=(2, 512) dtype=float32_ref> <tf.Variable 'Variable_4:0' shape=(512, 784) dtype=float32_ref> <tf.Variable 'Variable_5:0' shape=(512,) dtype=float32_ref> <tf.Variable 'Variable_6:0' shape=(2,) dtype=float32_ref> <tf.Variable 'Variable_7:0' shape=(2,) dtype=float32_ref> <tf.Variable 'Variable_8:0' shape=(512,) dtype=float32_ref> <tf.Variable 'Variable_9:0' shape=(784,) dtype=float32_ref>第二個:GLOBAL_VARIABLES
for i in tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES):print(i) <tf.Variable 'Variable:0' shape=(784, 512) dtype=float32_ref> <tf.Variable 'Variable_1:0' shape=(512, 2) dtype=float32_ref> <tf.Variable 'Variable_2:0' shape=(512, 2) dtype=float32_ref> <tf.Variable 'Variable_3:0' shape=(2, 512) dtype=float32_ref> <tf.Variable 'Variable_4:0' shape=(512, 784) dtype=float32_ref> <tf.Variable 'Variable_5:0' shape=(512,) dtype=float32_ref> <tf.Variable 'Variable_6:0' shape=(2,) dtype=float32_ref> <tf.Variable 'Variable_7:0' shape=(2,) dtype=float32_ref> <tf.Variable 'Variable_8:0' shape=(512,) dtype=float32_ref> <tf.Variable 'Variable_9:0' shape=(784,) dtype=float32_ref> <tf.Variable 'Variable/RMSProp:0' shape=(784, 512) dtype=float32_ref> <tf.Variable 'Variable/RMSProp_1:0' shape=(784, 512) dtype=float32_ref> <tf.Variable 'Variable_1/RMSProp:0' shape=(512, 2) dtype=float32_ref> <tf.Variable 'Variable_1/RMSProp_1:0' shape=(512, 2) dtype=float32_ref> <tf.Variable 'Variable_2/RMSProp:0' shape=(512, 2) dtype=float32_ref> <tf.Variable 'Variable_2/RMSProp_1:0' shape=(512, 2) dtype=float32_ref> <tf.Variable 'Variable_3/RMSProp:0' shape=(2, 512) dtype=float32_ref> <tf.Variable 'Variable_3/RMSProp_1:0' shape=(2, 512) dtype=float32_ref> <tf.Variable 'Variable_4/RMSProp:0' shape=(512, 784) dtype=float32_ref> <tf.Variable 'Variable_4/RMSProp_1:0' shape=(512, 784) dtype=float32_ref> <tf.Variable 'Variable_5/RMSProp:0' shape=(512,) dtype=float32_ref> <tf.Variable 'Variable_5/RMSProp_1:0' shape=(512,) dtype=float32_ref> <tf.Variable 'Variable_6/RMSProp:0' shape=(2,) dtype=float32_ref> <tf.Variable 'Variable_6/RMSProp_1:0' shape=(2,) dtype=float32_ref> <tf.Variable 'Variable_7/RMSProp:0' shape=(2,) dtype=float32_ref> <tf.Variable 'Variable_7/RMSProp_1:0' shape=(2,) dtype=float32_ref> <tf.Variable 'Variable_8/RMSProp:0' shape=(512,) dtype=float32_ref> <tf.Variable 'Variable_8/RMSProp_1:0' shape=(512,) dtype=float32_ref> <tf.Variable 'Variable_9/RMSProp:0' shape=(784,) dtype=float32_ref> <tf.Variable 'Variable_9/RMSProp_1:0' shape=(784,) dtype=float32_ref>很容易發現我們只需要從可訓練的參數集中獲取權重,還記得之前說過的么,我們啥都往sess.run中丟試試,看看能不能取出來值:
a=sess.run(graph.get_collection('trainable_variables')) for i in a:print(i.shape) (784, 512) (512, 2) (512, 2) (2, 512) (512, 784) (512,) (2,) (2,) (512,) (784,)可以看出參數可以從a中通過索引取出來了。
接下來簡單,重新定義一下模型的decoder計算:
latent_dim=2 noise_input=tf.placeholder(tf.float32,shape=[None,latent_dim]) decoder=tf.matmul(noise_input,a[3])+a[8] decoder=tf.nn.tanh(decoder) decoder=tf.matmul(decoder,a[4])+a[9] decoder=tf.nn.sigmoid(decoder)然后就可以嘗試丟進去一個均值和方差去預測了:
generate=sess.run(decoder,feed_dict={noise_input:[[3,3]]})可視化
generate=generate*255.0 gen_img=generate.reshape(28,28) plt.imshow(gen_img) plt.show()
效果還不錯,再測試幾個,丟[0,0][0,0][0,0]試試:
丟[0,5][0,5][0,5]試試:
好了,不玩了,隨機性太大了,我都不知道啥噪聲能輸出啥數字,只有輸出的時候才知道。
后記
好玩是好玩,但是我不知道哪個數字對應哪種噪聲輸入,還是有點郁悶,下一篇我們就去看看有名的搞基網GAN
本文訓練代碼:鏈接:https://pan.baidu.com/s/19QSNfT7fgWrU68CV7lXSZA 密碼:6c7l
本文測試代碼:鏈接:https://pan.baidu.com/s/1CAxPGnmCTg-OT8TqnXQdVw 密碼:tmep
總結
以上是生活随笔為你收集整理的【TensorFlow-windows】学习笔记六——变分自编码器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《武动乾坤》武祖讲道之黑暗之殿技能搭配小
- 下一篇: 女子宿舍禁爱令声优介绍 美少女角色介绍