模式崩潰

模式崩潰是生成對抗網絡(GANs)訓練中常見的問題。當GAN的生成器網絡無法產生多樣化的輸出,而是陷入特定的模式時,就會發生模式崩潰。這會導致生成的輸出出現重復,缺乏多樣性和細節,有時甚至與訓練數據完全無關。

GAN中發生模式崩潰有幾個原因。一個原因是生成器網絡可能對訓練數據過擬合。如果訓練數據不夠多樣化,或者生成器網絡太復雜,就會發生這種情況。另一個原因是生成器網絡可能陷入損失函數的局部最小值。如果學習率太高,或者損失函數定義不明確,就會發生這種情況。

以前有許多技術可以用來防止模式崩潰。比如使用更多樣化的訓練數據集。或者使用正則化技術,例如dropout或批處理歸一化,使用合適的學習率和損失函數也很重要。

Wassersteian損失

Wasserstein損失,也稱為Earth Mover’s Distance(EMD)或Wasserstein GAN (WGAN)損失,是一種用于生成對抗網絡(GAN)的損失函數。引入它是為了解決與傳統GAN損失函數相關的一些問題,例如Jensen-Shannon散度和Kullback-Leibler散度。

Wasserstein損失測量真實數據和生成數據的概率分布之間的差異,同時確保它具有一定的數學性質。他的思想是最小化這兩個分布之間的Wassersteian距離(也稱為地球移動者距離)。Wasserstein距離可以被認為是將一個分布轉換為另一個分布所需的最小“成本”,其中“成本”被定義為將概率質量從一個位置移動到另一個位置所需的“工作量”。

Wasserstein損失的數學定義如下:

對于生成器G和鑒別器D, Wasserstein損失(Wasserstein距離)可以表示為:

Jensen-Shannon散度(JSD): Jensen-Shannon散度是一種對稱度量,用于量化兩個概率分布之間的差異

對于概率分布P和Q, JSD定義如下:

 JSD(P∥Q)=1/2(KL(P∥M)+KL(Q∥M))

M為平均分布,KL為Kullback-Leibler散度,P∥Q為分布P與分布Q之間的JSD。

JSD總是非負的,在0和1之間有界,并且對稱(JSD(P|Q) = JSD(Q|P))。它可以被解釋為KL散度的“平滑”版本。

Kullback-Leibler散度(KL散度):Kullback-Leibler散度,通常被稱為KL散度或相對熵,通過量化“額外信息”來測量兩個概率分布之間的差異,這些“額外信息”需要使用另一個分布作為參考來編碼一個分布。

對于兩個概率分布P和Q,從Q到P的KL散度定義為:KL(P∥Q)=∑x P(x)log(Q(x)/P(x))。KL散度是非負非對稱的,即KL(P∥Q)≠KL(Q∥P)。當且僅當P和Q相等時它為零。KL散度是無界的,可以用來衡量分布之間的不相似性。

1-Lipschitz Contiunity

1- lipschitz函數是斜率的絕對值以1為界的函數。這意味著對于任意兩個輸入x和y,函數輸出之間的差不超過輸入之間的差。

數學上函數f是1-Lipschitz,如果對于f定義域內的所有x和y,以下不等式成立:

 |f(x) — f(y)| <= |x — y

生成對抗網絡(GANs)中強制Lipschitz連續性是一種用于穩定訓練和防止與傳統GANs相關的一些問題的技術,例如模式崩潰和訓練不穩定。在GAN中實現Lipschitz連續性的主要方法是通過使用Lipschitz約束或正則化,一種常用的方法是Wasserstein GAN (WGAN)。

在標準gan中,鑒別器(也稱為WGAN中的批評家)被訓練來區分真實和虛假數據。為了加強Lipschitz連續性,WGAN增加了一個約束,即鑒別器函數應該是Lipschitz連續的,這意味著函數的梯度不應該增長得太大。在數學上,它被限制為:

 ∥∣D(x)?D(y)∣≤K?∥x?y∥

其中D(x)是評論家對數據點x的輸出,D(y)是y的輸出,K是Lipschitz 常數。

WGAN的權重裁剪:在原始的WGAN中,通過在每個訓練步驟后將鑒別器網絡的權重裁剪到一個小范圍(例如,[-0.01,0.01])來強制執行該約束。權重裁剪確保了鑒別器的梯度保持在一定范圍內,并加強了利普希茨連續性。

WGAN的梯度懲罰: WGAN的一種變體,稱為WGAN-GP,它使用梯度懲罰而不是權值裁剪來強制Lipschitz約束。WGAN-GP基于鑒別器的輸出相對于真實和虛假數據之間的隨機點的梯度,在損失函數中添加了一個懲罰項。這種懲罰鼓勵了Lipschitz約束,而不需要權重裁剪。

譜范數

從符號上看矩陣??的譜范數通常表示為:對于神經網絡??矩陣表示網絡層中的一個權重矩陣。矩陣的譜范數是矩陣的最大奇異值,可以通過奇異值分解(SVD)得到。

奇異值分解是特征分解的推廣,用于將矩陣分解為

其中??,q為正交矩陣,Σ為其對角線上的奇異值矩陣。注意Σ不一定是正方形的。

其中??1和??分別為最大奇異值和最小奇異值。更大的值對應于一個矩陣可以應用于另一個向量的更大的拉伸量。依此表示,??(??)=??1.

SVD在譜歸一化中的應用

為了對權矩陣進行頻譜歸一化,將矩陣中的每個值除以它的頻譜范數。譜歸一化矩陣可以表示為

計算??is的SVD非常昂貴,所以SN-GAN論文的作者做了一些簡化。它們通過冪次迭代來近似左、右奇異向量??和??,分別為:??)≈??

代碼實現

現在我們開始使用Pytorch實現

 import torch
from torch import nn
from tqdm.auto import tqdm
from torchvision import transforms
from torchvision.datasets import MNIST
from torchvision.utils import make_grid
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
torch.manual_seed(0)

def show_tensor_images(image_tensor, num_images=25, size=(1, 28, 28)):
image_tensor = (image_tensor + 1) / 2
image_unflat = image_tensor.detach().cpu()
image_grid = make_grid(image_unflat[:num_images], nrow=5)
plt.imshow(image_grid.permute(1, 2, 0).squeeze())
plt.show()

生成器:

 class Generator(nn.Module):
def __init__(self,z_dim=10,im_chan = 1,hidden_dim = 64):
super(Generatoe,self).__init__()
self.gen = nn.Sequential(
self.make_gen_block(z_dim,hidden_dim * 4),
self.make_gen_block(hidden_dim*4,hidden_dim * 2,kernel_size = 4,stride =1),
self.make_gen_block(hidden_dim * 2,hidden_dim),
self.make_gen_block(hidden_dim,im_chan,kernel_size=4,final_layer = True),
)
def make_gen_block(self,input_channels,output_channels,kernel_size=3,stride=2,final_layer = False):
if not final_layer :
return nn.Sequential(nn.ConvTranspose2D(input_layer,output_layer,kernel_size,stride),
nn.BatchNorm2d(output_channels),
nn.ReLU(inplace = True),
)
else:
return nn.Sequential(nn.ConvTranspose2D(input_layer,output_layer,kernel_size,stride),
nn.Tanh(),)
def unsqueeze_noise():
return noise.view(len(noise), self.z_dim, 1, 1)
def forward(self,noise):
x = self.unsqueeze_noise(noise)
return self.gen(x)
def get_noise(n_samples, z_dim, device='cpu'):
return torch.randn(n_samples, z_dim, device=device)

鑒頻器

對于鑒別器,我們可以使用spectral_norm對每個Conv2D 進行處理。除了??之外,還引入了??、??、和其他的參數,這樣在運行時就可以計算出????的二進制二進制運算符:??、y、y、y、y

因為Pytorch還提供 nn.utils. spectral_norm,nn.utils. remove_spectral_norm函數,所以我們操作起來很方便。

我們只在推理期間將nn.utils. remove_spectral_norm應用于卷積層,以提高運行速度。

值得注意的是,譜范數并不能消除對批范數的需要。譜范數影響每一層的權重,批范數影響每一層的激活度。

 class Discriminator(nn.Module):
def __init__(self, im_chan=1, hidden_dim=16):
super(Discriminator, self).__init__()
self.disc = nn.Sequential(
self.make_disc_block(im_chan, hidden_dim),
self.make_disc_block(hidden_dim, hidden_dim * 2),
self.make_disc_block(hidden_dim * 2, 1, final_layer=True),
)
def make_disc_block(self, input_channels, output_channels, kernel_size=4, stride=2, final_layer=False):
if not final_layer:
return nn.Sequential(
nn.utils.spectral_norm(nn.Conv2d(input_channels, output_channels, kernel_size, stride)),
nn.BatchNorm2d(output_channels),
nn.LeakyReLU(0.2, inplace=True),
)
else:
return nn.Sequential(
nn.utils.spectral_norm(nn.Conv2d(input_channels, output_channels, kernel_size, stride)),
)
def forward(self, image):
disc_pred = self.disc(image)
return disc_pred.view(len(disc_pred), -1)

訓練

我們這里使用MNIST數據集,bcewithlogitsloss()函數計算logit和目標標簽之間的二進制交叉熵損失。二值交叉熵損失是對兩個分布差異程度的度量。在二元分類中,這兩種分布分別是邏輯的分布和目標標簽的分布。

criterion = nn.BCEWithLogitsLoss()
n_epochs = 50
z_dim = 64
display_step = 500
batch_size = 128
# A learning rate of 0.0002 works well on DCGAN
lr = 0.0002
beta_1 = 0.5
beta_2 = 0.999
device = 'cuda'
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,)),
])

dataloader = DataLoader(
MNIST(".", download=True, transform=transform),
batch_size=batch_size,
shuffle=True)

創建生成器和鑒別器

 gen = Generator(z_dim).to(device)
gen_opt = torch.optim.Adam(gen.parameters(), lr=lr, betas=(beta_1, beta_2))
disc = Discriminator().to(device)
disc_opt = torch.optim.Adam(disc.parameters(), lr=lr, betas=(beta_1, beta_2))

# initialize the weights to the normal distribution
# with mean 0 and standard deviation 0.02
def weights_init(m):
if isinstance(m, nn.Conv2d) or isinstance(m, nn.ConvTranspose2d):
torch.nn.init.normal_(m.weight, 0.0, 0.02)
if isinstance(m, nn.BatchNorm2d):
torch.nn.init.normal_(m.weight, 0.0, 0.02)
torch.nn.init.constant_(m.bias, 0)
gen = gen.apply(weights_init)
disc = disc.apply(weights_init)

下面是訓練步驟

cur_step = 0
mean_generator_loss = 0
mean_discriminator_loss = 0
for epoch in range(n_epochs):
# Dataloader returns the batches
for real, _ in tqdm(dataloader):
cur_batch_size = len(real)
real = real.to(device)

## Update Discriminator ##
disc_opt.zero_grad()
fake_noise = get_noise(cur_batch_size, z_dim, device=device)
fake = gen(fake_noise)
disc_fake_pred = disc(fake.detach())
disc_fake_loss = criterion(disc_fake_pred, torch.zeros_like(disc_fake_pred))
disc_real_pred = disc(real)
disc_real_loss = criterion(disc_real_pred, torch.ones_like(disc_real_pred))
disc_loss = (disc_fake_loss + disc_real_loss) / 2

# Keep track of the average discriminator loss
mean_discriminator_loss += disc_loss.item() / display_step
# Update gradients
disc_loss.backward(retain_graph=True)
# Update optimizer
disc_opt.step()

## Update Generator ##
gen_opt.zero_grad()
fake_noise_2 = get_noise(cur_batch_size, z_dim, device=device)
fake_2 = gen(fake_noise_2)
disc_fake_pred = disc(fake_2)
gen_loss = criterion(disc_fake_pred, torch.ones_like(disc_fake_pred))
gen_loss.backward()
gen_opt.step()

# Keep track of the average generator loss
mean_generator_loss += gen_loss.item() / display_step

## Visualization code ##
if cur_step % display_step == 0 and cur_step > 0:
print(f"Step {cur_step}: Generator loss: {mean_generator_loss}, discriminator loss: {mean_discriminator_loss}")
show_tensor_images(fake)
show_tensor_images(real)
mean_generator_loss = 0
mean_discriminator_loss = 0
cur_step += 1

訓練結果如下:

總結

本文我們介紹了SN-GAN的原理和簡單的代碼實現,SN-GAN已經被廣泛應用于圖像生成任務,包括圖像合成、風格遷移和超分辨率等領域。它在改善生成模型的性能和穩定性方面取得了顯著的成果,所以學習他的代碼對我們理解會更有幫助。

文章轉自微信公眾號@數據派THU

上一篇:

Python實現生成對抗網絡:生成逼真數據

下一篇:

R語言實現邏輯回歸(LR)以及繪制ROC曲線和混淆矩陣
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費