StyleGAN以Style爲名,有兩個含義:1. 借鑑在風格遷移中常用到的adain操作。2. 通過adain 把不同層次的樣式(style)embed到不同分辨率的特徵圖上,能夠影響不同層次(level)的生成圖像的樣式。高級層次有人臉的姿態,臉型,髮型等宏觀的樣式,低級點的有細小的細節風格(臉的細節,像源A還是源B,),眼鏡。
StyleGAN以漸進式GAN爲baseline,通過改進了一些方面,來生成高清(1024*1024)逼真的人臉圖像。
這些改進主要有:
- 改進漸進GAN的上採樣層和超參數
- 添加了maping學習W空間,用於產生樣式
- 移除其他GAN的輸入方式
- 添加noise input
- 採用mixing regularization
其中後四項(黑體)是比較重要的改進。我就按照這幾個部分分別講述。
獨立的mapping network
傳統的GAN,把噪聲Z送到generator中,一步一步上採樣得到生成圖像。然後Z的不同控制生成圖像的多樣性。同時如果想控制生成圖像,就在中間卷積層或者輸入層上concat一個屬性的編碼。作者認爲這種結構沒有讓latent space之間的不同屬性解糾纏(解耦)。意思就是如果一個人是否是長髮,和這個人是否是女性無關,但因爲數據集的關係,傳統的gan,生成女性的人臉圖,基本是長髮的。但stylegan,可以做到精準打擊,可控生成圖像的每個樣式。
左圖是傳統的生成器結構,右邊是stylegan的生成器。具體來說,stylegan把z輸入到一個maping結構中,得到另一個隱空間W,是一個512維向量。而mapping網絡是一個8層的MLP。z要經過L2 norm才能送到這個MLP中。
然後W空間的特徵向量,經過一個A(affine transform,另一個FC層)得到樣式(gamma, beta)。把該樣式用adain,embed到卷積特徵圖中(用gamma作爲新的均值,用beta作爲方差)
**adain最開始應該是ECCV18那篇任意風格遷移的論文提出的,之前看過。**然後Adain還在AdaptIS(實例分割)中採用過。AdaptIS我也介紹過。
移除傳統GAN的輸入層
Z一般是均值分佈的噪聲輸入,傳統的gan是把z送到生成器中。而styleGAN發現,當已經把z獨立出來之後,生成器的輸入不在依賴於噪聲輸入,故換成一個constant tensor會更加好。 在論文的附加材料中, 寫到他們用了一個learned constant tensor,初始化爲1,參與訓練。
添加噪聲
作者在Adain之前,引入了輸入噪聲。這個噪聲是每個通道上都有一個噪聲,然後在空間維度上expand。但噪聲加到特徵圖之間,要先經過一個訓練參數scale。
採用mixing regularization
爲了讓styleGAN更進一步解耦樣式之間的聯繫,作者還使用了混合正則化。具體來說,先送入z1,z2進入mapping,得到w1, w2。 然後交叉使用w1和w2,依次送入不同的adain中。這樣的話避免了一個問題,即,只用w1提供的樣式,會不會網絡認爲相鄰的adain所控制的風格是有關係的。
何爲樣式的層次性(層級)
作者用了10個z得到10張生成圖像,然後其中5個組成源A, 剩下5個組成源B。 然後用源B對應的w,輸入到低分辨率的特徵圖上使用的admin中。發現生成的圖像,髮型這些高級風格採用的是來自B的樣式。 如果只在中等大小的特徵圖上用源B的樣式,用adain嵌入到特徵圖中,則改變的是是否帶眼睛,鼻子,嘴型,等較細的樣式,同時發現高級樣式已經保留下來了(採用的還是源A),比如臉的姿態,性別等。如果再更高的特徵圖上採用源B的樣式,則改變的僅僅是細節的東西。比附膚色,頭髮的顏色(髮型沒有被改變)
其他部分
作者還有一系列更加詳細的實驗和分析,同時給出了一個新的人像數據集FFHQ。這裏就不多介紹。
源碼剖析
我閱讀的是第三方實現,基於Pytorch的,源作者在readme裏面給出了訓練結果,是比較接近官方實現的,反響也比較好,應該是目前最好的pytorch實現了吧
pytorch實現
mapping網絡結構, 8層MLP
layers = [PixelNorm()]
for i in range(n_mlp):
layers.append(EqualLinear(code_dim, code_dim))
layers.append(nn.LeakyReLU(0.2))
self.style = nn.Sequential(*layers)
AdaIN
class AdaptiveInstanceNorm(nn.Module):
def __init__(self, in_channel, style_dim):
super().__init__()
self.norm = nn.InstanceNorm2d(in_channel) # affine 默認爲False
self.style = EqualLinear(style_dim, in_channel * 2) # 論文中的Affine
self.style.linear.bias.data[:in_channel] = 1 #分兩半
self.style.linear.bias.data[in_channel:] = 0
def forward(self, input, style):
style = self.style(style).unsqueeze(2).unsqueeze(3) # W的特徵向量經過Affine
gamma, beta = style.chunk(2, 1) # 方差,均值 split
out = self.norm(input) # 歸一化
out = gamma * out + beta # 反歸一化
return out
class NoiseInjection(nn.Module):
def __init__(self, channel):
super().__init__()
# 論文中的scale,初始化爲0
self.weight = nn.Parameter(torch.zeros(1, channel, 1, 1))
def forward(self, image, noise):
return image + self.weight * noise
輸入層
class ConstantInput(nn.Module):
def __init__(self, channel, size=4):
super().__init__()
# 我記得論文中是初始化爲1 是一個learned constant input 所以用parameter封裝,是要參與訓練的。
self.input = nn.Parameter(torch.randn(1, channel, size, size))
def forward(self, input):
batch = input.shape[0] # 只需要batchsize這個信息
out = self.input.repeat(batch, 1, 1, 1)
return out
還有一些細節我也沒看懂,等用的時候在細究一下。比如還有訓練方式,GAN的訓練過程需要非常細心,所以訓練代碼有空也要好好讀一下。