StyleGAN閱讀筆記和源碼閱讀

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的訓練過程需要非常細心,所以訓練代碼有空也要好好讀一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章