VAE變分自編碼器原理解析看這一篇就夠了!另附Python代碼實現
變分自編碼器是近些年較火的一個生成模型,我個人認為其本質上仍然是一個概率圖模型,只是在此基礎上引入了神經網絡。本文將就變分自編碼器(VAE)進行簡單的原理講解和數學推導。
論文:https://arxiv.org/abs/1312.6114
視頻:https://www.bilibili.com/video/BV1op421S7Ep/
引入
高斯混合模型
生成模型,可以簡單的理解為生成數據 (不 止 , 但 我 們 暫 且 就 這 么 理 解 它) 。假如現在我們有樣本數據,而我們發現這些樣本符合正態分布且樣本具有充分的代表性,因此我們計算出樣本的均值和方差,就能得到樣本的概率分布。然后從正態分布中抽樣,就能得到樣本。這 種 生 成 樣 本 的 過 程 就 是 生 成 過 程 。
可是,假如我們的數據長這樣
很顯然,它的數據是由兩個不同的正態分布構成。我們可以計算出這些樣本的概率分布。但是一種更為常見的方法就是將其當作是兩個正態分布。我們引入一個隱變量z。
假 設 z 的 取 值 為 0,1 ,如果z為0,我們就從藍色的概率分布中抽樣;否則為1,則從橙色的概率分布中抽樣。這就是生成過程。
但是這個隱變量z是什么?它其實就是隱藏特征 訓 練 數 據x 的 抽 象 出 來 的 特 征 ,比如,如果x偏小,我們則認為它數據藍色正太分布,否則為橙色。這個 "偏 小" 就是特征,我們把它的取值為0,1(0代表偏小,1代表偏大)。
那這種模型我們如何取訓練它呢?如何去找出這個z呢?一 種 很 直 觀 的 方 法 就 是 重 構 代 價 最 小 ,我們希望,給一個訓練數據x,由x去預測隱變量z,再由隱變量z預測回x,得到的誤差最小。比如假如我們是藍色正態分布,去提取特征z,得到的z再返回來預測x,結果得到的卻是橙色的正態分布,這是不可取的。其模型圖如下
這個模型被稱為GMM高斯混合模型
變分自編碼器(VAE)
那它和VAE有什么關聯呢?其實VAE的模型圖跟這個原理差不多。只是有些許改變, 隱 變 量Z 的 維 度 是 連 續 且 高 維 的 , 不 再 是 簡 單 的 離 散 分 布 ,因為假如我們生成的是圖片,我們需要提取出來的特征明顯是要很多的,傳統的GMM無法做到。
也就是將訓練樣本x給神經網絡,讓神經網絡計算出均值和協方差矩陣.
取log 的 原 因 是 傳 統 的 神 經 網 絡 輸 出 值 總 是 有 正 有 負 。有了這兩個值就可以在對應的高斯分布中采樣,得到隱變量z。再讓z經過神經網絡重構回樣本,得到新樣本。這就是整個VAE的大致過程了。
再次強調, 訓 練 過 程 我 們 希 望 每 次 重 構 的 時 候 , 新 樣 本 和 訓 練 樣 本 盡 可 能 的 相 似。
為什么協方差會變成0?因為采樣具有隨機性,也就是存在噪聲,噪聲是肯定會增加重構的誤差的。神經網絡為了讓誤差最小,是肯定讓這個隨機性越小越好,因為只有這樣,才能重構誤差最小。
但是我們肯定是希望有隨機性的,為什么?因為有隨機性,我們才可以生成不同的樣本??!
所以,有KL散度去衡量兩個概率分布的相似性
KL散度是大于等于0的值,越小則證明越相似
所以,我們就是兩個優化目標 ① 最 小 化 重 構 代 價 ② 最 小 化 上 述 的 散 度
依照這兩個條件,建立目標函數,直接梯度下降 其 實 還 需 要 重 參 數 化 , 后 面 會 講 到 ,刷刷刷地往下降,最終收斂。
下面,我們就對其進行簡單的數學推導,并以此推導出目標函數
原理推導
引入目標函數
以VAE的簡略圖為例
設我們有N個樣本
現在,我們先單獨看看里面某一個樣本的似然,某個樣本記為x
所以左邊等于右邊
按照上面提到的,我們可以把第一步改成
更一般地,我們把它們寫成一起
由于KL散度是大于等于0的,所以第①項,就被稱為變分下界。
好,現在我們只需要最大化其變分下界(以下省略掉參數)
細化目標函數
先 來 看 KL 散 度
可以分為三部分
如果你熟悉高斯分布的高階矩的話,式A和式C完全就是二階原點矩和中心距,是直接可以的得出答案的。
當然了,其實我們也可以不對其概率分布進行約束,歸根究底,其讓然是最小重構代價,那么我們的目標函數如果可以充分表達出“最小重構代價”,那么是什么又有何關系呢?
可以看到,這就是一個均方差
重參數化技巧
有了目標函數,理論上我們直接梯度下降就可以了。然而,別忘了,我們是從中采樣出z來??墒俏覀儏s是用的神經網絡去計算的均值和方差,得到的高斯分布再去采樣,這種情況是不可導的。中間都已經出現了一個斷層了。神經網絡是一層套一層的計算。而采樣計算了一層之后,從這一層中去采樣新的值,再計算下一層。因此,采樣本身是不可導的。
代碼實現
效果一般,不曉得論文里面用了什么手段,效果看起來比這個好。(這個結果甚至還是我加了一層隱藏層的)
import torch
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
from torchvision.transforms import transforms
from torch import nn
from tqdm import tqdm
import matplotlib.pyplot as plt
class VAE(nn.Module):
def __init__(self,input_dim,hidden_dim,gaussian_dim):
super().__init__()
#編碼器
#隱藏層
self.fc1=nn.Sequential(
nn.Linear(in_features=input_dim,out_features=hidden_dim),
nn.Tanh(),
nn.Linear(in_features=hidden_dim, out_features=256),
nn.Tanh(),
)
#μ和logσ^2
self.mu=nn.Linear(in_features=256,out_features=gaussian_dim)
self.log_sigma=nn.Linear(in_features=256,out_features=gaussian_dim)
#解碼(重構)
self.fc2=nn.Sequential(
nn.Linear(in_features=gaussian_dim,out_features=256),
nn.Tanh(),
nn.Linear(in_features=256, out_features=512),
nn.Tanh(),
nn.Linear(in_features=512,out_features=input_dim),
nn.Sigmoid() #圖片被轉為為0,1的值了,故用此函數
)
def forward(self,x):
#隱藏層
h=self.fc1(x)
#計算期望和log方差
mu=self.mu(h)
log_sigma=self.log_sigma(h)
#重參數化
h_sample=self.reparameterization(mu,log_sigma)
#重構
recnotallow=self.fc2(h_sample)
return reconsitution,mu,log_sigma
def reparameterization(self,mu,log_sigma):
#重參數化
sigma=torch.exp(log_sigma*0.5) #計算σ
e=torch.randn_like(input=sigma,device=device)
result=mu+e*sigma #依據重參數化技巧可得
return result
def predict(self,new_x): #預測
recnotallow=self.fc2(new_x)
return reconsitution
def train():
transformer = transforms.Compose([
transforms.ToTensor(),
]) #歸一化
data = MNIST("./data", transform=transformer,download=True) #載入數據
dataloader = DataLoader(data, batch_size=128, shuffle=True) #寫入加載器
model = VAE(784, 512, 20).to(device) #初始化模型
optimer = torch.optim.Adam(model.parameters(), lr=1e-3) #初始化優化器
loss_fn = nn.MSELoss(reductinotallow="sum") #均方差損失
epochs = 100 #訓練100輪
for epoch in torch.arange(epochs):
all_loss = 0
dataloader_len = len(dataloader.dataset)
for data in tqdm(dataloader, desc="第{}輪梯度下降".format(epoch)):
sample, label = data
sample = sample.to(device)
sample = sample.reshape(-1, 784) #重塑
result, mu, log_sigma = model(sample) #預測
loss_likelihood = loss_fn(sample, result) #計算似然損失
#計算KL損失
loss_KL = torch.pow(mu, 2) + torch.exp(log_sigma) - log_sigma - 1
#總損失
loss = loss_likelihood + 0.5 * torch.sum(loss_KL)
#梯度歸0并反向傳播和更新
optimer.zero_grad()
loss.backward()
optimer.step()
with torch.no_grad():
all_loss += loss.item()
print("函數損失為:{}".format(all_loss / dataloader_len))
torch.save(model, "./model/VAE.pth")
if __name__ == '__main__':
#是否有閑置GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#訓練
train()
#載入模型,預測
model=torch.load("./model/VAE (1).pth",map_locatinotallow="cpu")
#預測20個樣本
x=torch.randn(size=(20,20))
result=model.predict(x).detach().numpy()
result=result.reshape(-1,28,28)
#繪圖
for i in range(20):
plt.subplot(4,5,i+1)
plt.imshow(result[i])
plt.gray()
plt.show()
VAE有很多的變種優化,感興趣的讀者自行查閱。
結束
以上,就是VAE的原理和推導過程了。能力有限,過程并不嚴謹,如有問題,還望指出。
本文轉自 AI生成未來 ,作者:篝火者2312
