-
1 # 北航秦曾昌
-
2 # 讀芯術
全文共5718字,預計學習時長20分鐘或更長變分自動編碼器的厲害之處
假設你正在開發一款開放性世界端遊,且遊戲裡的景觀設定相當複雜。
你聘用了一個圖形設計團隊來製作一些植物和樹木以裝飾遊戲世界,但是將這些裝飾植物放進遊戲中之後,你發現它們看起來很不自然,因為同種植物的外觀看起來一模一樣,這時你該怎麼辦呢?
首先,你可能會建議使用一些引數化來嘗試隨機地改變影象,但是多少改變才足夠呢?又需要多大的改變呢?還有一個重要的問題:實現這種改變的計算強度如何?
這是使用變分自動編碼器的理想情況。我們可以訓練一個神經網路,使其學習植物的潛在特徵,每當我們將一個植物放入遊戲世界中,就可以從“已學習”的特徵中隨機抽取一個樣本,生成獨特的植物。事實上,很多開放性世界遊戲正在透過這種方法構建他們的遊戲世界設定。
再看一個更圖形化的例子。假設我們是一個建築師,想要為任意形狀的建築生成平面圖。可以讓一個自動編碼器網路基於任意建築形狀來學習資料生成分佈,它將從資料生成分佈中提取樣本來生成一個平面圖。詳見下方的動畫。
對於設計師來說,這些技術的潛力無疑是最突出的。
再假設我們為一個時裝公司工作,需要設計一種新的服裝風格,可以基於“時尚”的服裝來訓練自動編碼器,使其學習時裝的資料生成分佈。隨後,從這個低維潛在分佈中提取樣本,並以此來創造新的風格。
在該節中我們將研究fashion MNIST資料集。
自動編碼器傳統自動編碼器
自動編碼器其實就是非常簡單的神經結構。它們大體上是一種壓縮形式,類似於使用MP3壓縮音訊檔案或使用jpeg壓縮影象檔案。
自動編碼器與主成分分析(PCA)密切相關。事實上,如果自動編碼器使用的啟用函式在每一層中都是線性的,那麼瓶頸處存在的潛在變數(網路中最小的層,即程式碼)將直接對應(PCA/主成分分析)的主要元件。通常,自動編碼器中使用的啟用函式是非線性的,典型的啟用函式是ReLU(整流線性函式)和sigmoid/S函式。
網路背後的數學原理理解起來相對容易。從本質上看,可以把網路分成兩個部分:編碼器和解碼器。
編碼器函式用ϕ表示,該函式將原始資料X對映到潛在空間F中(潛在空間F位於瓶頸處)。解碼器函式用ψ表示,該函式將瓶頸處的潛在空間F對映到輸出函式。此處的輸出函式與輸入函式相同。因此,我們基本上是在一些概括的非線性壓縮之後重建原始影象。
編碼網路可以用啟用函式傳遞的標準神經網路函式表示,其中z是潛在維度。
相似地,解碼網路可以用相同的方式表示,但需要使用不同的權重、偏差和潛在的啟用函式。
隨後就可以利用這些網路函式來編寫損失函式,我們會利用這個損失函式透過標準的反向傳播程式來訓練神經網路。
由於輸入和輸出的是相同的影象,神經網路的訓練過程並不是監督學習或無監督學習,我們通常將這個過程稱為自我監督學習。自動編碼器的目的是選擇編碼器和解碼器函式,這樣就可以用最少的資訊來編碼影象,使其可以在另一側重新生成。
如果在瓶頸層中使用的節點太少,重新建立影象的能力將受到限制,導致重新生成的影象模糊或者和原影象差別很大。如果使用的節點太多,那麼就沒必要壓縮了。
壓縮背後的理論其實很簡單,例如,每當你在Netflix下載某些內容時,傳送給你的資料都會被壓縮。一旦這個內容傳輸到電腦上就會通解壓演算法在電腦螢幕顯示出來。這類似於zip檔案的執行方式,只是這裡說的壓縮是在後臺透過流處理演算法完成的。
去噪自動編碼器
有幾種其它型別的自動編碼器。其中最常用的是去噪自動編碼器,本教程稍後會和Keras一起進行分析。這些自動編碼器在訓練前給資料新增一些白噪聲,但在訓練時會將誤差與原始影象進行比較。這就使得網路不會過度擬合影象中出現的任意噪聲。稍後,將使用它來清除文件掃描影象中的摺痕和暗黑區域。
稀疏自動編碼器
與其字義相反的是,稀疏自動編碼器具有比輸入或輸出維度更大的潛在維度。然而,每次網路執行時,只有很小一部分神經元會觸發,這意味著網路本質上是“稀疏”的。稀疏自動編碼器也是透過一種規則化的形式來減少網路過度擬合的傾向,這一點與去噪自動編碼器相似。
收縮自動編碼器
收縮編碼器與前兩個自動編碼器的執行過程基本相同,但是在收縮自動編碼器中,我們不改變結構,只是在丟失函式中新增一個正則化器。這可以被看作是嶺迴歸的一種神經形式。
現在瞭解了自動編碼器是如何執行的,接下來看看自動編碼器的弱項。一些最顯著的挑戰包括:
· 潛在空間中的間隙
· 潛在空間中的可分性
· 離散潛在空間
這些問題都在以下圖中體現。
MNIST資料集的潛在空間表示
這張圖顯示了潛在空間中不同標記數字的位置。可以看到潛在空間中存在間隙,我們不知道字元在這些空間中是長什麼樣的。這相當於在監督學習中缺乏資料,因為網路並沒有針對這些潛在空間的情況進行過訓練。另一個問題就是空間的可分性,上圖中有幾個數字被很好地分離,但也有一些區域被標籤字元是隨機分佈的,這讓我們很難區分字元的獨特特徵(在這個圖中就是數字0-9)。還有一個問題是無法研究連續的潛在空間。例如,我們沒有針對任意輸入而訓練的統計模型(即使我們填補了潛在空間中的所有間隙也無法做到)。
這些傳統自動編碼器的問題意味著我們還要做出更多努力來學習資料生成分佈並生成新的資料與影象。
變分自動編碼器變分自動編碼器延續了傳統自動編碼器的結構,並利用這一結構來學習資料生成分佈,這讓我們可以從潛在空間中隨機抽取樣本。然後,可以使用解碼器網路對這些隨機樣本進行解碼,以生成獨特的影象,這些影象與網路所訓練的影象具有相似的特徵。
對於熟悉貝葉斯統計的人來說,編碼器正在學習後驗分佈的近似值。這種分佈通常很難分析,因為它沒有封閉式的解。這意味著我們要麼執行計算上覆雜的取樣程式,如馬爾可夫鏈蒙特卡羅(MCMC)演算法,要麼採用變分方法。正如你可能猜測的那樣,變分自動編碼器使用變分推理來生成其後驗分佈的近似值。
若對背後的數學理論不感興趣,也可以選擇跳過這篇變分自動編碼器(VAE)編碼教程。
首先需要理解的是後驗分佈以及它無法被計算的原因。先看看下面的方程式:貝葉斯定理。這裡的前提是要知道如何從潛變數“z”生成資料“x”。這意味著要搞清p(z|x)。然而,該分佈值是未知的,不過這並不重要,因為貝葉斯定理可以重新表達這個機率。但是這還沒有解決所有的問題,因為分母(證據)通常很難解。但也不是就此束手無辭了,還有一個挺有意思的辦法可以近似這個後驗分佈值。那就是將這個推理問題轉化為一個最佳化問題。
要近似後驗分佈值,就必須找出一個辦法來評估提議分佈與真實後驗分佈相比是否更好。而要這麼做,就需要貝葉斯統計員的最佳夥伴:KL散度。KL散度是兩個機率分佈相似度的度量。如果它們相等,那散度為零;而如果散度是正值,就代表這兩個分佈不相等。KL散度的值為非負數,但實際上它不是一個距離,因為該函式不具有對稱性。可以採用下面的方式使用KL散度:
這個方程式看起來可能有點複雜,但是概念相對簡單。那就是先猜測可能生成資料的方式,並提出一系列潛在分佈Q,然後再找出最佳分佈q*,從將提議分佈和真實分佈的距離最小化,然後因其難解性將其近似。但這個公式還是有一個問題,那就是p(z|x)的未知值,所以也無法計算KL散度。那麼,應該怎麼解決這個問題呢?
這裡就需要一些內行知識了。可以先進行一些計算上的修改並針對證據下界(ELBO)和p(x)重寫KL散度:
有趣的是ELBO是這個方程中唯一取決於所選分佈的變數。而後者由於不取決於q,則不受所選分佈的影響。因此,可以在上述方程中透過將ELBO(負值)最大化來使KL散度最小化。這裡的重點是ELBO可以被計算,也就是說現在可以進行一個最佳化流程。
所以現在要做的就是給Q做一個好的選擇,再微分ELBO,將其設為零,然後就大功告成了。可是開始的時候就會面臨一些障礙,即必須選擇最好的分佈系列。
一般來說,為了簡化定義q的過程,會進行平均場變分推理。每個變分引數實質上是相互獨立的。因此,每個資料點都有一個單獨的q,可被相稱以得到一個聯合機率,從而獲得一個“平均場”q。
實際上,可以選用任意多的場或者叢集。比如在MINIST資料集中,可以選擇10個叢集,因為可能有10個數字存在。
要做的第二件事通常被稱為再引數化技巧,透過把隨機變數帶離導數完成,因為從隨機變數求導數的話會由於它的內在隨機性而產生較大的誤差。
再引數化技巧較為深奧,但簡單來說就是可以將一個正態分佈寫成均值加標準差,再乘以誤差。這樣在微分時,我們不是從隨機變數本身求導數,而是從它的引數求得。
這個程式沒有一個通用的閉型解,所以近似後驗分佈的能力仍然受到一定限制。然而,指數分佈族確實有一個閉型解。這意味著標準分佈,如正態分佈、二項分佈、泊松分佈、貝塔分佈等。所以,就算真正的後驗分佈值無法被查出,依然可以利用指數分佈族得出最接近的近似值。
變分推理的奧秘在於選擇分佈區Q,使其足夠大以求得後驗分佈的近似值,但又不需要很長時間來計算。
既然已經大致瞭解如何訓練網路學習資料的潛在分佈,那麼現在可以探討如何使用這個分佈生成資料。
資料生成過程觀察下圖,可以看出對資料生成過程的近似認為應生成數字‘2’,所以它從潛在變數質心生成數值2。但是也許不希望每次都生成一摸一樣的數字‘2’,就好像上述端遊例子所提的植物,所以我們根據一個隨機數和“已學”的數值‘2’分佈範圍,在潛在空間給這一過程添加了一些隨機噪聲。該過程透過解碼器網路後,我們得到了一個和原型看起來不一樣的‘2’。
這是一個非常簡化的例子,抽象描述了實際自動編碼器網路的體系結構。下圖表示了一個真實變分自動編碼器在其編碼器和解碼器網路使用卷積層的結構體系。從這裡可以觀察到,我們正在分別學習潛在空間中生成資料分佈的中心和範圍,然後從這些分佈“抽樣”生成本質上“虛假”的資料。
該學習過程的固有性代表所有看起來很相似的引數(刺激相同的網路神經元放電)都聚集到潛在空間中,而不是隨意的分散。如下圖所示,可以看到數值2都聚集在一起,而數值3都逐漸地被推開。這一過程很有幫助,因為這代表網路並不會在潛在空間隨意擺放字元,從而使數值之間的轉換更有真實性。
整個網路體系結構的概述如下圖所示。希望讀者看到這裡,可以比較清晰地理解整個過程。我們使用一組影象訓練自動編碼器,讓它學習潛在空間裡均值和標準值的差,從而形成我們的資料生成分佈。接下來,當我們要生成一個類似的影象,就從潛在空間的一個質心取樣,利用標準差和一些隨機誤差對它進行輕微的改變,然後使其透過解碼器網路。從這個例子可以明顯看出,最終的輸出看起來與輸入影象相似,但卻是不一樣的。
變分自動編碼器編碼指南去噪自編碼器
Fashion MNIST
在第一個練習中,在Fashion MNIST資料集新增一些隨機噪聲(椒鹽噪聲),然後使用去噪自編碼器嘗試移除噪聲。首先進行預處理:下載資料,調整資料大小,然後新增噪聲。
## Download the data(x_train, y_train), (x_test, y_test) = datasets.fashion_mnist.load_data()## normalize and reshapex_train = x_train/255.x_test = x_test/255.x_train = x_train.reshape(-1, 28, 28, 1)x_test = x_test.reshape(-1, 28, 28, 1)# Lets add sample noise - Salt and Peppernoise = augmenters.SaltAndPepper(0.1)seq_object = augmenters.Sequential([noise])train_x_n = seq_object.augment_images(x_train * 255) / 255val_x_n = seq_object.augment_images(x_test * 255) / 255接著,給自編碼器網路建立結構。這包括多層卷積神經網路、編碼器網路的最大池化層和解碼器網路上的升級層。
# input layerinput_layer =Input(shape=(28, 28, 1))# encodingarchitectureencoded_layer1= Conv2D(64, (3, 3), activation="relu", padding="same")(input_layer)encoded_layer1= MaxPool2D( (2, 2), padding="same")(encoded_layer1)encoded_layer2= Conv2D(32, (3, 3), activation="relu", padding="same")(encoded_layer1)encoded_layer2= MaxPool2D( (2, 2), padding="same")(encoded_layer2)encoded_layer3= Conv2D(16, (3, 3), activation="relu", padding="same")(encoded_layer2)latent_view = MaxPool2D( (2, 2),padding="same")(encoded_layer3)# decodingarchitecturedecoded_layer1= Conv2D(16, (3, 3), activation="relu", padding="same")(latent_view)decoded_layer1= UpSampling2D((2, 2))(decoded_layer1)decoded_layer2= Conv2D(32, (3, 3), activation="relu", padding="same")(decoded_layer1)decoded_layer2= UpSampling2D((2, 2))(decoded_layer2)decoded_layer3= Conv2D(64, (3, 3), activation="relu")(decoded_layer2)decoded_layer3= UpSampling2D((2, 2))(decoded_layer3)output_layer = Conv2D(1, (3, 3), padding="same",activation="sigmoid")(decoded_layer3)# compile themodelmodel =Model(input_layer, output_layer)model.compile(optimizer="adam",loss="mse")# run themodelearly_stopping= EarlyStopping(monitor="val_loss", min_delta=0, patience=10, verbose=5,mode="auto")history =model.fit(train_x_n, x_train, epochs=20, batch_size=2048,validation_data=(val_x_n, x_test), callbacks=[early_stopping])所輸入的影象,新增噪聲的影象,和輸出影象。
從時尚MNIST輸入的影象。
新增椒鹽噪聲的輸入影象。
從去噪網路輸出的影象。
從這裡可以看到,我們成功從噪聲影象去除相當的噪聲,但同時也失去了一定量的服裝細節的解析度。這是使用穩健網路所需付出的代價之一。可以對該網路進行調優,使最終的輸出更能代表所輸入的影象。
文字清理
去噪自編碼器的第二個例子包括清理掃描影象的摺痕和暗黑區域。這是最終獲得的輸入和輸出影象。
輸入的有噪聲文字資料影象。
經清理的文字影象。
為此進行的資料預處理稍微複雜一些,因此就不在這裡進行介紹,預處理過程和相關資料可在GitHub庫裡獲取。網路結構如下:
input_layer= Input(shape=(258, 540, 1))#encoderencoder= Conv2D(64, (3, 3), activation="relu", padding="same")(input_layer)encoder= MaxPooling2D((2, 2), padding="same")(encoder)#decoderdecoder= Conv2D(64, (3, 3), activation="relu", padding="same")(encoder)decoder= UpSampling2D((2, 2))(decoder)output_layer= Conv2D(1, (3, 3), activation="sigmoid", padding="same")(decoder)ae =Model(input_layer, output_layer)ae.compile(loss="mse",optimizer=Adam(lr=0.001))batch_size= 16epochs= 200early_stopping= EarlyStopping(monitor="val_loss",min_delta=0,patience=5,verbose=1,mode="auto")history= ae.fit(x_train, y_train, batch_size=batch_size, epochs=epochs,validation_data=(x_val, y_val), callbacks=[early_stopping])變分自編碼器
最後的壓軸戲,是嘗試從FashionMNIST資料集現有的服裝中生成新影象。
其中的神經結構較為複雜,包含了一個稱‘Lambda’層的取樣層。
batch_size = 16latent_dim = 2 # Number of latent dimension parameters# ENCODER ARCHITECTURE: Input -> Conv2D*4 -> Flatten -> Denseinput_img = Input(shape=(28, 28, 1))x = Conv2D(32, 3,padding="same", activation="relu")(input_img)x = Conv2D(64, 3,padding="same", activation="relu",strides=(2, 2))(x)x = Conv2D(64, 3,padding="same", activation="relu")(x)x = Conv2D(64, 3,padding="same", activation="relu")(x)# need to know the shape of the network here for the decodershape_before_flattening = K.int_shape(x)x = Flatten()(x)x = Dense(32, activation="relu")(x)# Two outputs, latent mean and (log)variancez_mu = Dense(latent_dim)(x)z_log_sigma = Dense(latent_dim)(x)## SAMPLING FUNCTIONdef sampling(args):z_mu, z_log_sigma = args epsilon = K.random_normal(shape=(K.shape(z_mu)[0], latent_dim),mean=0., stddev=1.)return z_mu + K.exp(z_log_sigma) * epsilon# sample vector from the latent distributionz = Lambda(sampling)([z_mu, z_log_sigma])## DECODER ARCHITECTURE# decoder takes the latent distribution sample as inputdecoder_input = Input(K.int_shape(z)[1:])# Expand to 784 total pixelsx = Dense(np.prod(shape_before_flattening[1:]),activation="relu")(decoder_input)# reshapex = Reshape(shape_before_flattening[1:])(x)# use Conv2DTranspose to reverse the conv layers from the encoderx = Conv2DTranspose(32, 3,padding="same", activation="relu",strides=(2, 2))(x)x = Conv2D(1, 3,padding="same", activation="sigmoid")(x)# decoder model statementdecoder = Model(decoder_input, x)# apply the decoder to the sample from the latent distributionz_decoded = decoder(z)這就是體系結構,但還是需要插入損失函式再合併KL散度。# construct a custom layer to calculate the lossclass CustomVariationalLayer(Layer):def vae_loss(self, x, z_decoded):x = K.flatten(x)z_decoded = K.flatten(z_decoded)# Reconstruction lossxent_loss = binary_crossentropy(x, z_decoded)# KL divergencekl_loss = -5e-4 * K.mean(1 + z_log_sigma - K.square(z_mu) - K.exp(z_log_sigma), axis=-1)return K.mean(xent_loss + kl_loss)# adds the custom loss to the classdef call(self, inputs):x = inputs[0]z_decoded = inputs[1]loss = self.vae_loss(x, z_decoded)self.add_loss(loss, inputs=inputs)return x# apply the custom loss to the input images and the decoded latent distribution sampley = CustomVariationalLayer()([input_img, z_decoded])# VAE model statementvae = Model(input_img, y)vae.compile(optimizer="rmsprop", loss=None)vae.fit(x=train_x, y=None,shuffle=True,epochs=20,batch_size=batch_size,validation_data=(val_x, None))現在,可以檢視重構的樣本,看看網路能夠學習到什麼。
從這裡可以清楚看到鞋子、手袋和服裝之間的過渡。在此並沒有標出所有使畫面更清晰的潛在空間。也可以觀察到Fashion MNIST資料集現有的10件服裝的潛在空間和顏色程式碼。
可看出這些服飾分成了不同的叢集。
我們一起分享AI學習與發展的乾貨
回覆列表
VAE,即Auto-encoding variational bayes是13年由Kingma, Diederik P., and Max Welling. 提出的。
論文地址:https://arxiv.org/abs/1312.6114
想要深刻理解VAE背後的原理需要一定的數學基礎,其中涉及到變分推理和貝葉斯等。本文的重點放在對VAE的思想的理解上,重點並不是怎麼去做公式的推導和解釋。
首先,VAE是結合了神經網路和貝葉斯思想做的變分推理。那神經網路,在這其中起到什麼作用呢?
我們知道我們可以透過增加神經網路的複雜結構,來增強神經網路的非線性擬合功能。因此可以用神經網路去逼近一些比較複雜的函式,這些函式如果不用神經網路逼近的話,可能非常複雜,甚至無法以非常顯形的方式建模出來(要知道,只有一部分自然界中的規律能讓我們用數學公式抽象地表達出來)。
我們再來繼續看,VAE中哪一步需要神經網路的幫助?
VAE是生成模型,先不看VAE。簡單地說,如果我們想要生成一個數據,可以透過什麼樣的方式做到呢?如下圖所示,比如,我們輸入一個向量[1, 0, 0, 0],想讓它經過神經網路後生成一張貓的圖片,我們不斷訓練這個網路去減小生成的影象和原始影象的平均平方誤差。那麼訓練完後,這個影象的資訊就被保留在了網路的引數中。
按照這個想法,我們再向網路輸入[0, 1, 0, 0]代表狗,讓網路能生成一個狗的圖片。基於這個思想,我們可以上升一個層次擴充套件下去,我們可以不輸入獨熱編碼,而是輸入實數值向量,從而能用更低維度的向量,編碼更多的圖片。例如可以輸入 [3.3, 4.5, 2.1, 9.8]代表貓,輸入[3.4, 2.1, 6.7, 4.2] 代表狗。這個已知的初始向量就對應了VAE中提到的概念latent variable。而上述得到的網路叫做解碼器,因為給網路輸入一個指定的向量,就能把這個向量透過網路解碼得到與編碼向量對應的一張圖片。但這麼做的前提是,你已經知道在latent variable這個集合中,怎樣的向量輸給網路能生成貓,怎樣的向量輸給網路能生成狗。如果你不知道這個latent variable,你會怎麼辦,你只能去試,比如隨機選一個latent variable向量輸進網路,看結果會不會是你想要的貓或者狗,但這種做法實在是一言難盡,一點不像是搞科研的人乾的事情。
因此,我們需要一個編碼器,能夠把輸入的影象進行編碼,而這個編碼的結果不再是規律不可循的了,而是服從我們指定的簡單的分佈,這個編碼的結果服從的分佈,一方面是基於輸入資料的,因此它也具備了能被解碼器解碼回去的能力。另一方面,它服從一種簡單的我們能夠掌握的分佈,因此我們能夠有規律可循的生成一個latent variable,把這個latent variable輸入給解碼器,就能生成一張圖片。我們把latent variable記作Z(Z是可以有很多分量,同時它的分量也可以是一個多維向量,總之Z可以是一個多維的向量),而Z的這個分佈我們可以記作q(Z)。
實際上,真實世界的過程應該是這樣的,比如對於一張動物圖片,決定它是一個貓還是一個狗,應該是由很多隱含變數的,比如圖片中的動物眼睛、鼻子、嘴、耳朵都是什麼樣的、而且這個變數之間往往不是相互獨立的,是錯綜複雜的關係,這個真實世界中的latent variable的分佈我們記作p(Z)。真實世界生成一種張圖片的過程應該是:
我們先在p(Z)中選在一個編碼向量,在根據這個編碼向量生成圖片。就是因為真實的先驗機率p(Z)實在不好捕捉和發現,我們才利用VAE中的encoder去編碼一個q(Z),這個q(Z)的意義就是為了去逼近近似p(Z),能完成p(Z)的功能,即取樣之後能生成一張圖片的功能。
因此,我們就能理解VAE中的loss函式的組成了,一方面,是圖片的重構誤差,我們可以用平均平方誤差來度量,另一方面。我們可以用KL散度來度量我們的q(Z)分佈和p(Z)分佈的差異。
最後,我們給出利用VAE生成手寫數字的結果:
感興趣的同學也可以看一下,下面的有關VAE的更詳細的論文:
http://lanl.arxiv.org/pdf/1606.05908v2