StyleGAN2代码PyTorch版逐行学习(下)

前请提要: StyleGAN2代码PyTorch版逐行学习(上)

今天的代码学习就从StyleGAN2生成器的forward函数开始吧~~有什么建议/问题/修正please评论区见~

——插一个有关forward的小故事,完全不重要,可以忽视——

我之前被导师追问,为什么实例化一个class x(nn.Module)对象以后,用实例化的名字就可以调class中的forward函数。我接触python就是从pytorch开始的,一上来就把这个认识当作“知识”记住了,根本没想过原因,一下子被问住了。导师说他会产生这种疑问,会去想弄明白背后的原理,这就是我和他的区别。好吧,确实。

然后去搜索了一下,这好像就是pytorch那个nn.Module的特性,把forward函数写到__call__里面,就可以直接同名调用了酱紫...


1、必需的输入参数就是styles,然后input_is_latent选一下,=True代表输入的styles是隐层码w,=False则代表输入的styles还需经过self.style(PixelNorm+8个EqualLinear)映射为隐层码w。

在使用Generator时,会把latent code w+(维度为(batch, 18, 512)的tensor),装进列表以[w+]作为styles参数输入。

* stylegan常讲的特征空间有Z空间,W空间,W+空间,S空间。z服从高斯分布;w是512维向量;w+是18个w构成的(18,512)维tensor,18个w控制18层卷积层;s是w经仿射变换后,乘在卷积核上进行调制的量。

    def forward(
            self,
            styles,
            return_latents=False,
            return_features=False,
            inject_index=None,
            truncation=1,
            truncation_latent=None,
            input_is_latent=False,
            noise=None,
            randomize_noise=True,
    ):
        if not input_is_latent:  # 如果输入的是z
            styles = [self.style(s) for s in styles]  # z -> w

        if noise is None:
            if randomize_noise:
                noise = [None] * self.num_layers  # self.num_layers=17
                # noise是一个长度为17的空值列表
            else:
                noise = [
                    getattr(self.noises, f'noise_{i}') for i in range(self.num_layers)
                ]
                # noise取Generator初始化时17个从高斯分布采的(1,1,hi,wi)大小的噪声组成列表。

        if truncation < 1:  # 该操作使得latent code w在平均值附近,生图质量不会太糟
            style_t = []

            for style in styles:
                style_t.append(
                    truncation_latent + truncation * (style - truncation_latent)
                )

            styles = style_t

        if len(styles) < 2:  # 如果输入1个隐层码
            inject_index = self.n_latent  # 18

            if styles[0].ndim < 3:  # 如果输入的是w,ndim=2,维度为(batch,512)
                latent = styles[0].unsqueeze(1).repeat(1, inject_index, 1) 
                # 将w升维、复制为(batch,18,512)维度
            else:   # 如果输入的是w+,维度为(batch,18,512),直接用就行
                latent = styles[0]

        else: # 如果输入2组w
            if inject_index is None:
                inject_index = random.randint(1, self.n_latent - 1)  
                # 返回 [1, 17] 之间的随机整数,我叫它i吧
            latent = styles[0].unsqueeze(1).repeat(1, inject_index, 1)  
            # 第1个w升维为(batch, i, 512) 
            latent2 = styles[1].unsqueeze(1).repeat(1, self.n_latent - inject_index, 1)
            # 第2个w升维为(batch, 18-i, 512) 
            latent = torch.cat([latent, latent2], 1)
            # 将2个w concat为(batch, 18, 512)维度
        # 这里应该指的是style mixing,在第i层前用A图的latent code控制生图的低分辨率特征;
        # 在第i层后用B图的latent code控制高分辨率图特征,从而混合了A、B两张图 

Truncation trick:训练集的样本分布并不均匀,会存在低概率密度样本无法被训好的情况,从而生成质量不行的图,所以采取这个方法,计算latent code平均值\bar{w},然后如下公式进行计算。

style mixing:可视化出了不同分辨率下style控制的属性差异、并且提供了一种属性融合方式。有2组latent code w1和w2,分别生成source图A和source图B,style mixing就是在生成主支网络中选择一个交叉点,交叉点前的低分辨率合成使用w1控制,交叉点之后的高分辨率合成使用w2,这样最终得到的图像则融合了图A和图B的特征。根据交叉点位置的不同,可以得到不同的融合结果。


2、接上段:

上段操作中,得到的latent是一个(batch, 18, 512)维度的tensor。

self.input其实是获取了latent的第一个维度,也就是batch_size去得到一个(batch, 512, 4, 4)的主枝常量输入out。

而后用self.conv1对out做卷积,输入latent[:,0]也就是18个中第1个(batch, 512)维度的w,经一个EqualLinear变为s,调制在卷积核权重上。做完卷积后,out维度还是(batch, 512, 4, 4)。

用18个中的第2个w对to_rgb中的卷积核权重进行调制,经卷积将out变为一个rgb通道图,维度(batch, 3, 4, 4).

        out = self.input(latent)
        out = self.conv1(out, latent[:, 0], noise=noise[0])  
        skip = self.to_rgb1(out, latent[:, 1])  

3、接上段:

每次取出一层,取8次,分别从self.convs中取第1、3、5、7、9、11、13、15个卷积层为conv1,从self.convs中取第2、4、6、8、10、12、14、16个卷积层为conv2,从noise取第2、4、6、8、10、12、14、16个噪声图为noise1,从noise取第3、5、7、9、11、13、15、17个噪声图为noise2,self.to_rgbs的全部8层为to_rgb。

将第2~18个w分别用于调制这些层的卷积核权重,conv1、conv2先卷积再加噪。

to_rgb层中将上一层to_rgb的输出上采样加在本层输出上,最终生成的图片就是最后一层to_rgb的输出结果。

        i = 1
        for conv1, conv2, noise1, noise2, to_rgb in zip(
                self.convs[::2], self.convs[1::2], noise[1::2], noise[2::2], self.to_rgbs
        ):
            out = conv1(out, latent[:, i], noise=noise1)
            out = conv2(out, latent[:, i + 1], noise=noise2)
            skip = to_rgb(out, latent[:, i + 2], skip)

            i += 2

        image = skip

        if return_latents:
            return image, styles[0]#latent
        elif return_features:
            return image, out
        else:
            return image, None

return_latents=True,返回的是styles[0],也就是输入的东东又输出出来了,没做改变呀。

猜你喜欢

转载自blog.csdn.net/qq_43522986/article/details/125209895