一分钟教会你五种CycleGAN的优质创新思路(附代码)

news2024/11/29 10:44:19

专栏导读 

🔥🔥本文已收录于专栏:《风格迁移之从入门到成功魔改》,欢迎免费订阅
此专栏用于带你从零基础学会什么是风格迁移,风格迁移有什么作用,传统做法和Cyclegan的原理,及其优缺点,以及最重要的CycleGAN的成功魔改(附代码)。

1)环境部署搭建,资源配置
2)风格迁移传统做法,GAN,CycleGAN的原理及其优缺点。
3)代码详细解析
4)根据缺点进行全方面成功魔改的原理。

本文导读

 🔥🔥本文的创新点部分,是来自我即将发表的一篇核心文章!!!。 只有淋过雨的孩子才会懂得给别人打伞,所以我开源的目的很简单,希望可以帮助到有缘相遇的初学者快速了解并掌握该方向内容。 有能力的同学可以进行二次改进创新。我的最终模型在定性和定量的评估中效果均有提升!!!。后续代码将更新到GitHub上,如果对大家有帮助,希望可以得到您的免费star✨本人万分感谢!!!

本文的结构图均为本人绘制,如有需可以私信或评论区留言

既然开源也不求回报,创新部分内附代码。如果对你们的科研创新提供了新思路,劳烦点赞收藏一下,赞满100,我将update所有消融实验数据,谢谢!

🔥🔥注:我的论文中将新模型命名为SWLAGAN。(你要问为什么叫这个,其实是我一时兴起瞎起的。哈哈哈)

🔥生成器网络

注意力机制

        自注意力机制在自然语言处理领域被广泛应用,他可以将输入到全连接神经网络中的多个词语建立语义联系。比如在神经网络中输入多个大小不一的向量,向量之间具有关联,自注意力机制便可以完成多数神经网络都难以实现的向量之间建立关联工作,自注意力机制的算法下图所示。

        算法流程如下:图中Q,K,V均是由一组词语表示的输入矩阵X经过线性变化得到,Q和K负责建立词语之间的语义关联,通过自注意力的核心算法计算出表现语义关联的距离矩阵。将距离矩阵和矩阵V进行矩阵相乘得到了具有全局关联的输出矩阵。

        Q,K,V矩阵大小则是由输入矩阵X(X,Q,K,V各行都代表一个词语)经过线性变换而决定的,线性变换矩阵分别为WQ,WK,WV。Q,K,V计算过程图如下图所示。

 再计算出矩阵Q,K,V后通过公式得到输出,计算公式如下:

\mathrm{Attention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})V}

其中d_{k}是矩阵Q,K的矩阵列数。

        通过公式softmax(\frac{QK^T}{\sqrt{d_k}})建立词语之间的语义关联,即距离矩阵。其中{\sqrt{d_k}}的作用是防止Q和K各行内积后,数值过大,而影响语义关联性。Softmax的作用是计算得到每一个词语对应其他词语的关联系数。经过softmax得到的距离矩阵中第i行代表词语i对其他所有词语的关联系数。最后将距离矩阵与V作矩阵相乘得到具有全局关联的输出矩阵。

🔥🔥🔥以下是创新部分的自注意力机制搭建代码:

class Self_Attention(nn.Module):

    def __init__(self, in_dim, activation):
        super(Self_Attention, self).__init__()
        self.chanel_in = in_dim
        self.activation = activation
        ##  下面的query_conv,key_conv,value_conv即对应Wg,Wf,Wh
        self.query_conv = nn.Conv2d(in_channels=in_dim, out_channels=in_dim // 8, kernel_size=1)  # 即得到C^ X C
        self.key_conv = nn.Conv2d(in_channels=in_dim, out_channels=in_dim // 8, kernel_size=1)  # 即得到C^ X C
        self.value_conv = nn.Conv2d(in_channels=in_dim, out_channels=in_dim, kernel_size=1)  # 即得到C X C
        self.gamma = nn.Parameter(torch.zeros(1))  # 这里即是计算最终输出的时候的伽马值,初始化为0

        self.softmax = nn.Softmax(dim=-1)

    def forward(self, x):
        m_batchsize, C, width, height = x.size()
        ##  下面的proj_query,proj_key都是C^ X C X C X N= C^ X N
        proj_query = self.query_conv(x).view(m_batchsize, -1, width * height).permute(0, 2, 1)  # B X CX(N),permute即为转置
        proj_key = self.key_conv(x).view(m_batchsize, -1, width * height)  # B X C x (*W*H)
        energy = torch.bmm(proj_query, proj_key)  # transpose check,进行点乘操作
        attention = self.softmax(energy)  # BX (N) X (N)
        proj_value = self.value_conv(x).view(m_batchsize, -1, width * height)  # B X C X N

        out = torch.bmm(proj_value, attention.permute(0, 2, 1))
        out = out.view(m_batchsize, C, width, height)

        out = self.gamma * out + x
        return out

🔥可全局连接的残差网路(SA_Blocks)

        本人提出的残差网络结构将自注意力机制中全局链接的特性与残差网络可以防止网络退化的特性完美结合。改进了原始残差网络被局限在小窗口提取特征的缺陷,增大了特征提取的全局视野的同时,也增加了多尺度不变的特性。SA_Blocks网络结构图如下所示:

         SA_Blocks网络结构在继承了原有残差网络的两个3x3卷积层(Conv)外,又增加了一个自注意力层(SEAT),其工作原理是将特征图X(X\epsilon [C,W,H],C为通道数,H为高度,W为宽度)分别通过三个1×1卷积层,得到特征图Q,K,V,其中Q,K的通道数减半,V保持不变(Q,K\epsilon [\frac{1}{2},W,H],V\epsilon [C,W,H])。由于自注意力机制公式中具有矩阵计算,因此将Q,K,V进行特征转换(Q,K\epsilon [\frac{1}{2},N]V\epsilon [C,H]N=W*N)。将转换后的Q和K的转置进行特征矩阵相乘,得到了不同通道之间像素点的关联矩阵β(\beta \epsilon [N,N])。将关联矩阵β归一化后与V进行特征矩阵相乘,得到具有全局连接的特征图X’,为了增加多尺度空间表达能力,又将X’通过一个3×3卷积层得到输出特征图Y,其表达式为:

\mathrm{Y=X+Conv(SEAT(X))+Conv(Conv(X))}

🔥🔥🔥以下是创新部分的SA_Blocks网络搭建代码:

​​​​​​class SEA_ResnetBlock_1(nn.Module):

    def __init__(self, dim, padding_type, norm_layer, use_dropout, use_bias):
        super(SEA_ResnetBlock_1, self).__init__()
        self.conv_block = self.build_conv_block(dim, padding_type, norm_layer, use_dropout, use_bias)
        self.self_attention=Self_Attention(dim,'relu')
    def build_conv_block(self, dim, padding_type, norm_layer, use_dropout, use_bias):
        conv_block = []
        p = 0
        if padding_type == 'reflect':
            conv_block += [nn.ReflectionPad2d(1)]
        elif padding_type == 'replicate':
            conv_block += [nn.ReplicationPad2d(1)]
        elif padding_type == 'zero':
            p = 1
        else:
            raise NotImplementedError('padding [%s] is not implemented' % padding_type)

        conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim), nn.ReLU(True)]
        if use_dropout:
            conv_block += [nn.Dropout(0.5)]

        p = 0
        if padding_type == 'reflect':
            conv_block += [nn.ReflectionPad2d(1)]
        elif padding_type == 'replicate':
            conv_block += [nn.ReplicationPad2d(1)]
        elif padding_type == 'zero':
            p = 1
        else:
            raise NotImplementedError('padding [%s] is not implemented' % padding_type)
        conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim)]

        return nn.Sequential(*conv_block)

    def forward(self, x):
        out = self.self_attention(x) + self.conv_block(x)+x  # add skip connections
        return out

🔥🔥生成器网络结构

        生成器模型采用Auto-Encoder+Skip-connection的网络结构,残差网络模块摒弃了原始残差网络结构,使用本文提出的SA_Blocks结构,其特有的全局连接性弥补了原始残差只能局部提取特征的不足,让模型不仅可以在全局上提取特征还具有了多尺度不变性,使生成的图片质量得到提升,SWLAGAN的生成器网络结构图如下图所示。

        其中蓝色模块是镜像填充层,将对图片四周填充三个像素矩阵的镜像内容;Ci:W×H×C表示目前是第i层卷积,通过该层卷积输出特征图的宽度为W,高度为H,通道数为C;粉色模块是卷积核大小为3,步长为2的卷积操作。褐色模块是卷积核大小为3,步长为1/2的反卷积操作,用来将卷积后的特征图发大。橙色模块是3个连续的SA_Blocks结构,让提取的特征具有全局视野。黄色模块是IN层,将特征图标准化,防止过拟合;虚线部分是将模型中卷积后得到的低级特征与同分辨率下的高级特征进行融合,使模型中各层的特征信息利用率得到提升。

🔥🔥🔥以下是创新部分的生成器构建代码:

class Unet_SEA_ResnetGenerator(nn.Module):

    def __init__(self, input_nc, output_nc, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False, n_blocks=6, padding_type='reflect'):
        assert(n_blocks >= 0)
        super(Unet_SEA_ResnetGenerator, self).__init__()
        if type(norm_layer) == functools.partial:
            use_bias = norm_layer.func == nn.InstanceNorm2d
        else:
            use_bias = norm_layer == nn.InstanceNorm2d
        self.pad=nn.ReflectionPad2d(3)
        self.Down_conv1=nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0, bias=use_bias)#下采样第一层
        self.conv_norm=norm_layer(input_nc)
        self.relu=nn.ReLU(True)
        self.Down_conv2=nn.Conv2d(ngf , ngf * 2, kernel_size=3, stride=2, padding=1, bias=use_bias) #下采样第二层
        self.SA=Self_Attention_no_connect(ngf*2,'relu')
        self.Down_conv3=nn.Conv2d(ngf*2 , ngf * 4, kernel_size=3, stride=2, padding=1, bias=use_bias) #下采样第三层
        self.Sa_block_3=SEA_Block_3(ngf * 4, padding_type=padding_type, norm_layer=norm_layer, use_dropout=use_dropout,use_bias=use_bias)
        self.Sa_resnetblock_1=SEA_ResnetBlock_1(ngf * 4, padding_type=padding_type, norm_layer=norm_layer, use_dropout=use_dropout,use_bias=use_bias)
        self.resnet=ResnetBlock(ngf * 4, padding_type=padding_type, norm_layer=norm_layer, use_dropout=use_dropout, use_bias=use_bias)
        self.Up_conv1=nn.ConvTranspose2d(ngf * 4*2, ngf * 2 , kernel_size=3, stride=2,padding=1, output_padding=1,bias=use_bias)
        self.Up_conv2=nn.ConvTranspose2d(ngf * 2*2, ngf, kernel_size=3, stride=2,padding=1, output_padding=1,bias=use_bias)
        self.Up_conv3=nn.Conv2d(ngf*2, output_nc, kernel_size=7, padding=0)
        self.tan=nn.Tanh()


    def forward(self, x):
        x1=self.relu(self.conv_norm(self.Down_conv1(self.pad(x))))
        x2=self.relu(self.conv_norm(self.Down_conv2(x1)))
        x3=self.relu(self.conv_norm(self.Down_conv3(x2)))
        x4=self.resnet(x3)
        x=torch.cat([x4,x3],1)
        x=self.relu(self.conv_norm(self.Up_conv1(x)))
        x=torch.cat([x,x2],1)
        x=self.relu(self.conv_norm(self.Up_conv2(x)))
        x=torch.cat([x,x1],1)
        x=self.tan(self.Up_conv3(self.pad(x)))
        return x

🔥🔥判别器网络

        判别器网络,使用Auto-Encoder的网络结构代替原始结构,其核心优势在于判别器的训练不再受到生成器的约束,可以先训练判别器,通过判别器的优化来刺激生成器的训练。进而解决了原始模型会产生的训练不平衡问题,SWLAGAN的判别器网络结构图如下图所示。

        判别器在上采样过程中使用扩充加卷积操作来代替反卷积。其中黄色模块表示卷积层,卷积核大小为w,d=(a,b)表示特征图的通道数从a变化为b。Full connected表示全连接层。Subsampling表示平均池化操作。NN Upsampling表示临近点填充层,将图片的四周填充图片对应位置的像素内容。

        新判别器可以不受生成器的约束而提前训练,并且还可以带动生成器训练的原理为:假设现在有三张图片,一张是输入x,一张是经过D编码解码后的图片D(x),还有一张是先经过G生成,又经过D编码解码后得到的图片D(G(y))。随着网络的训练,G和D逐渐达到纳什平衡,即(\|D(x)-x\|-\|D(x)-D(G(y))\|)\to0,D是通过真实数据分布训练,导致D(x)分布会无限接近x分布,即\|D(x)-x\|\to0。通过极限的思想可得\|D(x)-D(G(y))\|\to0。此时的x分布将无限接近G(y)分布,此时G便学习到了y源分布到x源分布的映射关系,G和D达到纳什平衡。

🔥🔥🔥以下是创新部分的判别器构建代码:

class Discriminator(nn.Module):
    def __init__(self,input_nc,ndf=64,n_layers=3, norm_layer=nn.BatchNorm2d):
        super(Discriminator,self).__init__()
        # 256 x 256
        self.conv1 = nn.Sequential(nn.Conv2d(input_nc,ndf,kernel_size=3,stride=1,padding=1),
                                    nn.ELU(True),
                                    conv_block(ndf,ndf))
        # 128 x 128
        self.conv2 = conv_block(ndf, ndf*2)
        # 64 x 64
        self.conv3 = conv_block(ndf*2, ndf*3)
        # 32 x 32
        self.conv4 = conv_block(ndf*3, ndf*4)
        # 16 x 16
        self.conv5=conv_block(ndf*4,ndf*5)
        # 8 x 8
        self.conv6 = nn.Sequential(nn.Conv2d(ndf*5,ndf*5,kernel_size=3,stride=1,padding=1),
                             nn.ELU(True),
                             nn.Conv2d(ndf*5,ndf*5,kernel_size=3,stride=1,padding=1),
                             nn.ELU(True))
        self.embed1 = nn.Linear(ndf*5*8*8, 64)
        self.embed2 = nn.Linear(64, ndf*8*8)

        # 8 x 8
        self.deconv1 = deconv_block(ndf, ndf)
        # 16 x 16
        self.deconv2 = deconv_block(ndf, ndf)
        # 32 x 32
        self.deconv3 = deconv_block(ndf, ndf)
        # 64 x 64
        self.deconv4 = deconv_block(ndf, ndf)
        # 128 x 128
        self.deconv5 = deconv_block(ndf, ndf)
        # 256 x 256
        self.deconv6 = nn.Sequential(nn.Conv2d(ndf,ndf,kernel_size=3,stride=1,padding=1),
                             nn.ELU(True),
                             nn.Conv2d(ndf,ndf,kernel_size=3,stride=1,padding=1),
                             nn.ELU(True),
                             nn.Conv2d(ndf, input_nc, kernel_size=3, stride=1, padding=1),
                             nn.Tanh())
        self.ndf = ndf

    def forward(self,x):
        out = self.conv1(x)
        out = self.conv2(out)
        out = self.conv3(out)
        out = self.conv4(out)
        out = self.conv5(out)
        out=self.conv6(out)
        out = out.view(out.size(0), self.ndf*5 * 8 * 8)

        out = self.embed1(out)
        out = self.embed2(out)
        out = out.view(out.size(0), self.ndf, 8, 8)
        out = self.deconv1(out)
        out = self.deconv2(out)
        out = self.deconv3(out)
        out = self.deconv4(out)
        out = self.deconv5(out)
        out = self.deconv6(out)
        return out

🔥🔥损失函数部分

🔥🔥循环一致性损失LPIPS

        对于我们人类来说,评估两张图片的感知相似度是一件极其简单的事情,但其中的原理却极其复杂。以前很多学者喜欢使用L2范数,PSNR,SSIM等感知指标函数来复现人类感知图片这种行为。直到LPIPS的出现打破了这种现状,其在无监督模型上学习到的特征在模型低层次感知相似性上比L2范数等损失函数要强很多,在2018年PIRM感知图像超分辨率挑战赛中众多学者联合比较了各损失函数的性能,通过把多个感知模型的计算结果与真实结果进行拟合画线,得到了8张对比图如下图所示。

         图中Corr代表模型评估的分数和人的主观审美的相似度。其范围在[-1,1],越接近1代表越接近人的主观审美。从图可知,只有LPIPS的评估是正相关,其余都是负相关,所以在感知相似度函数中LPIPS的计算误差是最小的。LPIPS网络结构图如下图所示。

         基本原理是将真实图片x与待测图片x0分别带入网络F中进行特征提取,在不同的通道中计算x和x0的特征之间的距离;在不同的卷积层中(这里举例为L层)提取特征堆栈,将不同的通道中特征堆栈进行归一化,此时的结果记为\hat{y}^L,\hat{y}_0^L(\hat{y}^L,\hat{y}_0^L\in\mathbb{R}^{\mathrm{H_L\times W_L\times C_L}}),又通过向量w^{L}\quad(w^{L}\epsilon\mathbb{R}^{\mathsf{C}_{L}})进行缩放激活通道,使用L2范数计算的距离,并在空间中求平均值,在通道上求和计算出d0。公式如下所示:

\mathrm{d(x,x0)=\sum\limits_L\frac1{H_LW_L}\sum\limits_{h,w}\left\|wl\Theta(\hat{y}_{hw}^L-\hat{y}_{0hw}^L)\right\|_2^2}

其中wl等同于通过余弦距离公式计算的结果。最后把d0和真实的d1传到含有两个32个通道的RELU全连接层、一个单通道全连接层和一个sigmoid层的模型中训练,其相似性损失函数为:

\begin{aligned}\mathrm{L_{LPIPS}(x,x0,x1,h)=-~hlogG(d(x,x0),d(x,x1))}-(1-\mathrm{h})\mathrm{log(1-G(d(x,x0),d(x,x1)))}\end{aligned}

🔥🔥🔥以下是创新部分的循环一致损失函数LPIPS的构建代码:

self.criterionCycle=lpips.LPIPS(net='alex').to(self.device)
self.loss_cycle_A = self.criterionCycle(self.rec_A, self.real_A) * 2
self.loss_cycle_B = self.criterionCycle(self.rec_B, self.real_B) * 2

​​​​​​​​​​​​​​🔥🔥​​​​​​​对抗损失WGAN-GP

        Wasserstein距离也叫做Earth-Mover(推土机)距离,其公式如下:

\mathrm{W(P_r,P_g)=\inf\limits_{r\sim\prod(P_r,P_g)}E_{(x,y)\sim r}[\|x-y\|]}

        其中P_r,P_g代表两种数据分布,并且都是\prod(P_r,P_g)中的任何一个边缘分布。当r属于联合分布时,真实数据x和生成数据y将从r中采样,\|x-y\|代表真实数据和生成数据的误差,因此当x和y属于所有联合分布时,求得\|x-y\|的期望下界值便是Wasserstein距离。

        Lipschitz连续是指在一个连续函数f中,存在一个常数K,且K≥0。使得定义域内任意的x1和x2都满足不等式|f(x1)-f(x2)|\leq K|x1-x2|。且K称为f的Lipschitz常数。

        WGAN的目标函数便是基于Wasserstein距离得来的。由于Wasserstein距离公式中有\inf\limits_{r\sim\prod(P_r,P_g)}因此无法直接求导,经证明,可将上式转换为下述公式:

\mathsf{W(P_r,P_g)}=\frac{1}{K}{\underset{​{\left\|f\right\|_L\leq K}}{\operatorname*{\sup}}\operatorname{E_{x\sim P_r}[f(x)]-E_{x\sim P_g}[f(x)]}}

        又使用参数w定义函数f_{w}来重新定义公式,近似的公式如下:

\mathrm{K\cdot W(P_r,P_g)\approx\max\limits_{w:\left\|f_w\right\|_L\leq K}E_{x\sim P_r}[f_w(x)]-E_{x\sim P_g}[f_w(x)]}

        f_{w}可以通过训练含有参数w的神经网络来表示,随着训练次数不不断增加,网络拟合能力增强,最终网络会拟合出\|f_w\|_L\leq K的情况。又防止K值多大而控制网络的所有参数w在[-c,c]之间,使得输入数据x的偏导数\frac{\alpha f_{w}}{\alpha x}也可以被控制在固定范围内,使模型梯度变化相对稳定。由于Lipschitz连续的条件得到了满足,此时Wasserstein距离便可以用下面公式近似表示:

\mathrm L=\mathrm E_{\mathrm x\sim\mathrm P_r}[\mathrm f_w(\mathrm x)]-\mathrm E_{\mathrm x\sim\mathrm P_g}[\mathrm f_w(\mathrm x)]

        同GAN原理一样,WGAN中的生成器也是MIN~G,判别器也是MAX~D。所以WGAN的生成器公式如下:

-\mathrm E_{\mathrm x\sim\mathrm P_g}[\mathrm f_w(\mathrm x)]

        WGAN的判别器公式如下:

\mathrm E_{\mathrm x\sim\mathrm P_r}[\mathrm f_w(\mathrm x)]-\mathrm E_{\mathrm x\sim\mathrm P_g}[\mathrm f_w(\mathrm x)]

        如果只进行权重裁剪(c的赋值)会出现两个问题。第一个问题是,神经网络的参数被控制在某个范围内,从而会使全权重分布严重不均,会出现很多极端的参数值。如下图所示,如果权重都集中徘徊在0.01和-0.01两个点,神经网络拟合的能力将会被削弱。

        第二个问题在于模型会产生很强的梯度爆炸或者梯度消失,如果权重裁剪过小会出现梯度消失,过大则又会出现梯度爆炸,这两种情况都会导致网络训练极不稳定。

        为了解决这些问题,WGAN-GP在原有WGAN的基础上增加梯度惩罚机制,在满足Lipschitz连续条件的同时,通过对梯度变化的约束,使梯度和K之间建立起联系,下面是限制判别器的梯度不能大于K的公式:

\text{RELU[||}\nabla_{\mathrm{x}}\mathrm{D(x)}\|_{\mathfrak{p}}-\mathbb{K}]

        随着判别器的判别能力增强,其梯度也会增加,训练结束后,梯度会无限接近K,因此公式可以优化为:

[\left\|\nabla_\mathrm{x}\mathrm{D(x)}\right\|_\mathrm{p}-\mathrm{K}]^2

        将K=1的梯度惩罚机制与WGAN的损失加权求和得到WGAN-GP的损失函数:

\mathrm L(\mathrm D)=-\mathrm E_{\mathrm x\sim\mathrm P_r}[\mathrm D(\mathrm x)]+\mathrm E_{\mathrm x\sim\mathrm P_g}[\mathrm D(\mathrm x)]+\lambda\mathrm E_{\mathrm x\sim\mathrm X}[\left\|\mathrm\nabla_x\mathrm D(\mathrm x)\right\|_{\mathrm p}-1]^2

        又结合CycleGAN的双生成对抗网络原理,建立两个生成对抗损失,一个是通过G实现X源域转为Y源域的损失公式:

L_{GAN}(G,D_Y,X,Y)=\mathrm{E_{y\sim P_{data}(y)}[D_Y(y)]-E_{x\sim P_{data}(x)}[D_Y(G(x))]+\lambda E_{y\sim Y}[\left\|\nabla_{y}D_{Y}(y)\right\|_2-1]^2}

        另一个是通过F实现Y源域转为X源域的损失公式:

L_{GAN}(F,D_X,X,Y)=\mathrm{E_{x\sim P_{data}(x)}[D_X(x)]-E_{y\sim P_{data}(y)}[D_X(F(y))]+\lambda E_{x\sim X}[\left\|\nabla_{x}D_{X}(x)\right\|_2-1]^2}

        因此总的对抗损失公式为:

L_{GAN}(G,F,D_X,D_Y)=L_{GAN}(G,D_Y,X,Y)+L_{GAN}(F,D_X,X,Y)

        SWLAGAN的总体损失函数为:

L_{\mathrm{SWLAG}AN}(G,F,D_X,D_Y)=L_{GAN}(G,D_Y,X,Y)+L_{GAN}(F,D_X,X,Y)+\mathrm{L_{LPIPS}(X,F(G(X)),G(F(Y)),h})

🔥🔥🔥以下是创新部分的对抗损失函数的构建代码:

 def gradient_penalty(self,netD,real,fake):
        BATCH_SIZE,C,H,W=real.shape
        epsilon = torch.rand((BATCH_SIZE,1,1,1)).repeat(1,C,H,W).to(self.device)
        interpolated_images=real*epsilon*fake*(1-epsilon)
        mixed_scores=netD(interpolated_images)
        gradient=torch.autograd.grad(
            inputs=interpolated_images,
            outputs=mixed_scores,
            grad_outputs=torch.ones_like(mixed_scores),
            create_graph=True,
            retain_graph=True,
        )[0]
        gradient=gradient.view(gradient.shape[0],-1)
        gradient_penalty=((gradient.norm(2,dim=1)-1)**2).mean()
        return gradient_penalty

🔥🔥🔥SWLAGAN模型训练结构图

🔥🔥训练模型的Trick

Trick1:Label平滑

        如果有两个目标label:Real=1 和 Fake=0,那么对于每个新样本,如果是real,那么把label替换为0.7~1.2之间的随机值;如果样本是fake,那么把label替换为0.0~0.3之间的随机值。

在models/networks.py中的GANLoss类中的__init__函数中进行修改:

原代码:

    def __init__(self, gan_mode, target_real_label=1.0, target_fake_label=0.0):
        super(GANLoss, self).__init__()
        self.register_buffer('real_label', torch.tensor(target_real_label))
        self.register_buffer('fake_label', torch.tensor(target_fake_label))
        self.gan_mode = gan_mode
        if gan_mode == 'lsgan':
            self.loss = nn.MSELoss()
        elif gan_mode == 'vanilla':
            self.loss = nn.BCEWithLogitsLoss()
        elif gan_mode in ['wgangp']:
            self.loss = None
        else:
            raise NotImplementedError('gan mode %s not implemented' % gan_mode)

修改后的代码:

    def __init__(self, gan_mode):
        super(GANLoss, self).__init__()
        target_real_label = random.randint(7, 12) * 0.1
        target_fake_label = random.randint(0, 3) * 0.1
        self.register_buffer('real_label', torch.tensor(target_real_label))
        self.register_buffer('fake_label', torch.tensor(target_fake_label))
        self.gan_mode = gan_mode
        if gan_mode == 'lsgan':
            self.loss = nn.MSELoss()
        elif gan_mode == 'vanilla':
            self.loss = nn.BCEWithLogitsLoss()
        elif gan_mode in ['wgangp']:
            self.loss = None
        else:
            raise NotImplementedError('gan mode %s not implemented' % gan_mode)

Trick2:将图像输入鉴别器之前,将噪声添加到实际图像和生成的图像中

        在models/cycle_gan_model.py中的CycleGANModle类中的backward_D_A函数和backward_D_B函数修改

原代码:

    def backward_D_A(self):
        """Calculate GAN loss for discriminator D_A"""
        fake_B = self.fake_B_pool.query(self.fake_B)
        self.loss_D_A = self.backward_D_basic(self.netD_A, self.real_B, fake_B)

    def backward_D_B(self):
        """Calculate GAN loss for discriminator D_B"""
        fake_A = self.fake_A_pool.query(self.fake_A)
        self.loss_D_B = self.backward_D_basic(self.netD_B, self.real_A, fake_A)

修改后代码:

    def backward_D_A(self):
        """Calculate GAN loss for discriminator D_A"""
        real_B=self.real_B #(B C H W)
        fake_B = self.fake_B_pool.query(self.fake_B) #(B C H W)
        ###给fake_B添加噪点
        BatchSize_fake,C_fake,H_fake,W_fake=fake_B.size()
        img_fake=fake_B.view(H_fake,W_fake,C_fake) #(H W C)
        img_fake_np=img_fake.numpy() #将(H W C)的Tensor转为(H W C)的numpy
        h_fake,w_fake,c_fake=img_fake_np.shape
        Nd = 0.1
        Sd = 1 - Nd
        mask_fake = np.random.choice((0, 1, 2), size=(h_fake, w_fake, 1), p=[Nd / 2.0, Nd / 2.0, Sd])  # 生成一个通道的mask
        mask_fake = np.repeat(mask_fake, c_fake, axis=2)  # 在通道的维度复制,生成彩色的mask
        img_fake_np[mask_fake==0]=0
        img_fake_np[mask_fake==1]=255
        img_fake_Tensor=torch.from_numpy(img_fake_np)   #(H W C)numpy转为(H W C)的Tensor
        H1_fake,W1_fake,C1_fake=img_fake_Tensor.size()
        fake_B=img_fake_Tensor.view(BatchSize_fake,C1_fake,H1_fake,W1_fake) #将(H W C)的Tensor转为(B C H W)的Tensor
        ###给real_B添加噪点
        BatchSize_real, C_real, H_real, W_real = real_B.size()
        img_real = real_B.view(H_real, W_real, C_real)
        img_real_np = img_real.numpy()
        h_real, w_real, c_real = img_real_np.shape
        mask_real = np.random.choice((0, 1, 2), size=(h_real, w_real, 1), p=[Nd / 2.0, Nd / 2.0, Sd])  # 生成一个通道的mask
        mask_real = np.repeat(mask_real, c_real, axis=2)  # 在通道的维度复制,生成彩色的mask
        img_real_np[mask_real == 0] = 0
        img_real_np[mask_real == 1] = 255
        img_real_Tensor=torch.from_numpy(img_real_np)
        H1_real,W1_real,C1_real=img_real_Tensor.size()
        real_B=img_real_Tensor.view(BatchSize_real,C1_real,H1_real,W1_real)
        self.loss_D_A = self.backward_D_basic(self.netD_A, real_B, fake_B)

    def backward_D_B(self):
        """Calculate GAN loss for discriminator D_B"""
        real_A=self.real_A
        fake_A = self.fake_A_pool.query(self.fake_A)
        ###给fake_A添加噪点
        BatchSize_fake, C_fake, H_fake, W_fake = fake_A.size()
        img_fake = fake_A.view(H_fake, W_fake, C_fake)
        img_fake_np = img_fake.numpy()
        h_fake, w_fake, c_fake = img_fake_np.shape
        Nd = 0.1
        Sd = 1 - Nd
        mask_fake = np.random.choice((0, 1, 2), size=(h_fake, w_fake, 1), p=[Nd / 2.0, Nd / 2.0, Sd])  # 生成一个通道的mask
        mask_fake = np.repeat(mask_fake, c_fake, axis=2)  # 在通道的维度复制,生成彩色的mask
        img_fake_np[mask_fake == 0] = 0
        img_fake_np[mask_fake == 1] = 255
        img_fake_Tensor = torch.from_numpy(img_fake_np)
        H1_fake, W1_fake, C1_fake = img_fake_Tensor.size()
        fake_B = img_fake_Tensor.view(BatchSize_fake, C1_fake, H1_fake, W1_fake)
        ###给real_A添加噪点
        BatchSize_real, C_real, H_real, W_real = real_A.size()
        img_real = real_A.view(H_real, W_real, C_real)
        img_real_np = img_real.numpy()
        h_real, w_real, c_real = img_real_np.shape
        mask_real = np.random.choice((0, 1, 2), size=(h_real, w_real, 1), p=[Nd / 2.0, Nd / 2.0, Sd])  # 生成一个通道的mask
        mask_real = np.repeat(mask_real, c_real, axis=2)  # 在通道的维度复制,生成彩色的mask
        img_real_np[mask_real == 0] = 0
        img_real_np[mask_real == 1] = 255
        img_real_Tensor = torch.from_numpy(img_real_np)
        H1_real, W1_real, C1_real = img_real_Tensor.size()
        real_B = img_real_Tensor.view(BatchSize_real, C1_real, H1_real, W1_real)
        self.loss_D_B = self.backward_D_basic(self.netD_B, real_A, fake_A)

加噪点思路:参考了该文章的椒盐噪点

        并且由于fake_A等变量是(B,C,H,W)的Tensor,而文章中的算法是基于numpy,因此只需要将fake_A等变量先从Tensor->numpy->算法->Tensor

Trick3: 判别器的优化频率高于生成器。

        原代码中判别器的训练次数和生成器的训练次数的比例是1:1,即每一个epoch的时候只训练一次Generator和Discriminator。现在我尝试一个epoch的时候,训练一次Generator,然后用其生成的fake图片训练3次Discriminator。

原理:1)Trick2的噪点会使Discriminator难以训练。

           2)多训练Discriminator,能够刺激Generator模型的训练从而生成好的效果图。

在models/cycle_gan_model.py中的CycleGANModel类中的optimize_parameters函数中进行修改

原代码:

  def optimize_parameters(self):
        # forward
        self.forward()      # compute fake images and reconstruction images.
        # G_A and G_B
        self.set_requires_grad([self.netD_A, self.netD_B], False)  # Ds require no gradients when optimizing Gs
        self.optimizer_G.zero_grad()  # set G_A and G_B's gradients to zero
        self.backward_G()             # calculate gradients for G_A and G_B
        self.optimizer_G.step()       # update G_A and G_B's weights
        # D_A and D_B
        self.set_requires_grad([self.netD_A, self.netD_B], True)
        self.optimizer_D.zero_grad()   # set D_A and D_B's gradients to zero
        self.backward_D_A()      # calculate gradients for D_A
        self.backward_D_B()      # calculate graidents for D_B
        self.optimizer_D.step()  # update D_A and D_B's weights

修改后的代码:

 def optimize_parameters(self):
        # forward
        self.forward()      # compute fake images and reconstruction images.
        # G_A and G_B
        self.set_requires_grad([self.netD_A, self.netD_B], False)  # Ds require no gradients when optimizing Gs
        self.optimizer_G.zero_grad()  # set G_A and G_B's gradients to zero
        self.backward_G()             # calculate gradients for G_A and G_B
        self.optimizer_G.step()       # update G_A and G_B's weights
        # D_A and D_B
        self.set_requires_grad([self.netD_A, self.netD_B], True)
        for i in range(3):
            self.optimizer_D.zero_grad()   # set D_A and D_B's gradients to zero
            self.backward_D_A()      # calculate gradients for D_A
            self.backward_D_B()      # calculate graidents for D_B
            self.optimizer_D.step()  # update D_A and D_B's weights

🔥🔥🔥🔥成果展示

🔥🔥由于要忙一些其他事情,这篇论文其实还未发表,一拖再拖。但很多小伙伴在后台私信问我细节。我就把绝大多数的创新细节都开源分享给大家了。实验数据这部分因为论文原因暂不公布。给大家看几张效果图。等后续我再update。

🔥🔥这是其中的一个实验春天和秋天的风格转换:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/763502.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

领域驱动中的Android开发

领域驱动的Android开发 构建有意义的模型 在Android开发世界中,最佳架构的竞争一直存在。MVC、MVP、MVVM、MVI是互联网上许多文章讨论的热门话题。 我们知道View不应包含任何复杂逻辑。我们知道Controller、Presenter和ViewModel之间的区别。但是,我们…

数据结构复习(一)到循环队列

第一章 数据结构绪论 数据结构:是相互之间存在一种或多种特定关系的数据元素的集合。 第2章 算法 算法:解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示为一个或多个操作。 算法具有五个基本特性…

2023.7.17-用*来画一个直角在左(右)下方的等腰直角三角形

功能&#xff1a;如题&#xff0c;直角边长度为5。 代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() {int a, b, i,j;printf("直角边的长度为&#xff1a;");scanf("%d",&a);for (i 1; i < a;i){for (j…

JMeter和Postman:哪一个更适合性能测试?

Postman 和 JMeter 都可以用来做 API 测试&#xff0c;但是它们之间有一些区别。 测试类型 Postman 主要用于功能测试和集成测试&#xff0c;而 JMeter 主要用于性能测试和负载测试&#xff0c;例如压力测试和并发测试。因此&#xff0c;如果你需要测试应用程序的性能和可伸缩…

​python接口自动化(四十一)- 发xml格式参数的post请求(超详解)​

简介 最近在工作中&#xff0c;遇到一种奇葩的接口&#xff0c;它的参数数据是通过xml&#xff0c;进行传递的&#xff0c;不要大惊小怪的&#xff0c;林子大了什么鸟都有&#xff0c;每个人的思路想法不一样&#xff0c;开发的接口也是各式各样的&#xff0c;如果想要统一的话…

2023年NOC决赛-加码未来编程赛项决赛模拟题-Python模拟题--卷3

第一题、是古罗马恺撒大帝用来对军事情报进行加解密的算法&#xff0c;它采用了替换方法对信息中的每一个英文字符循环替换为字母表序列中该字符后面的第三个字符&#xff0c;即&#xff0c;字母表的对应关系如下&#xff1a; 原文&#xff1a;A B C D E F G H I J K L M N O …

(转载)BP 神经网络的数据分类(matlab实现)

1案例背景 1.1 BP神经网络概述 BP神经网络是一种多层前馈神经网络,该网络的主要特点是信号前向传递,误差反向传播。在前向传递中,输入信号从输人层经隐含层逐层处理,直至输出层。每一层的神经元状态只影响下一层神经元状态。如果输出层得不到期望输出,则转入反向传播,根据预测…

微服务负载均衡器RibbonLoadBalancer实战

1.负载均衡介绍 负载均衡&#xff08;Load Balance&#xff09;&#xff0c;其含义就是指将负载&#xff08;工作任务&#xff09;进行平衡、分摊到多个操作单元上进行运行&#xff0c;例如FTP服务器、Web服务器、企业核心应用服务器和其它主要任务服务器等&#xff0c;从而协同…

支付通道及系统设计

支付渠道&#xff0c;也可以叫支付通道&#xff0c;是指能够提供资金流转功能的通道&#xff0c;包括但不限于银行、第三方支付机构。我们常见的借记卡&#xff08;储蓄卡&#xff09;、贷记卡&#xff08;信用卡&#xff09;、微信、支付宝、云闪付等支付方式&#xff0c;都是…

re学习(18)[ACTF新生赛2020]rome1(Z3库+window远程调试)

参考视频: Jamiexu793的个人空间-Jamiexu793个人主页-哔哩哔哩视频 代码分析&#xff1a; 其主要内容在两个while循环中&#xff08;从定义中可知flag位16个字符&#xff09;。 看第二个循环&#xff0c;可知是比较result和经过第一个循环得到的v1比较&#xff08;就是flag…

VXLAN在Linux上的实践

在笔记本上使用VMWare安装两台Ubunutu 22.04虚拟机&#xff0c;这两台虚拟机都桥接在一个物理网卡上&#xff0c;IP地址分别为192.168.31.113和192.168.31.131。 HOST1上的VXLAN配置如下&#xff1a; # ip link add vxlan0 type vxlan id 42 dstport 4789 remote 192.168.31.1…

什么是核心层?汇聚层?接入层

什么是核心层?汇聚层?接入层-百度经验 核心层&#xff1a; 核心层的功能主要是实现骨干网络之间的优化传输&#xff0c;骨干层设计任务的重点通常是冗余能力、可靠性和高速的传输。核心层一直被认为是所有流量的最终承受者和汇聚者&#xff0c;所以对核心层的设计以及网络设备…

STM32(HAL库)驱动GY30光照传感器通过串口进行打印

目录 1、简介 2、CubeMX初始化配置 2.1 基础配置 2.1.1 SYS配置 2.1.2 RCC配置 2.2 软件IIC引脚配置 2.3 串口外设配置 2.4 项目生成 3、KEIL端程序整合 3.1 串口重映射 3.2 GY30驱动添加 3.3 主函数代 3.4 效果展示 1、简介 本文通过STM32F103C8T6单片机通过HAL库方…

gma 2.0.0a3 (2023.07.17) 更新日志

安装 gma 2.0.0a3 pip install gma2.0.0a3新增 1、为矢量要素&#xff08;Feature&#xff09;添加 【Difference】&#xff08;差集&#xff09;方法   取第一个矢量要素与第二个矢量要素的几何差集。  2、为矢量要素&#xff08;Feature&#xff09;添加几种几何形状测试…

springCloud通过两种方式配置热更新

该热更新实际就是通过改动nacos官网里面的配置管理的妹纸内容实现 定义一个config包&#xff0c;在该包下面复制该代码 package cn.itcast.user.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.spring…

如何实现以图搜图

一、前言 在许多搜索引擎中&#xff0c;都内置了以图搜图的功能。以图搜图功能&#xff0c;可以极大简化搜索工作。今天要做的就是实现一个以图搜图引擎。 我们先来讨论一下以图搜图的难点&#xff0c;首当其冲的就是如何对比图片的相似度&#xff1f;怎么样的图片才叫相似&a…

每天一道C语言编程:排队买票

题目描述 有M个小孩到公园玩&#xff0c;门票是1元。其中N个小孩带的钱为1元&#xff0c;K个小孩带的钱为2元。售票员没有零钱&#xff0c;问这些小孩共有多少种排队方法&#xff0c;使得售票员总能找得开零钱。注意&#xff1a;两个拿一元零钱的小孩&#xff0c;他们的位置互…

Windows 10快速启动怎么关闭?

有的用户电脑在开启快速启动后&#xff0c;发现电脑的开机速度确实变快了&#xff0c;但有的用户开启快速启动后开机速度反而变慢了&#xff0c;所以想关闭快速启动。那电脑快速启动怎么关闭&#xff1f; 快速启动与休眠 快速启动与电脑的休眠功能相似&#xff0c;但又有所不同…

获取网络包的硬件时间戳

转自&#xff1a;如何获取网络包的硬件时间戳_飞行的精灵的博客-CSDN博客 在一些应用中我们需要获取网路报文进出MAC的精准的时间戳。相比较于软件时间戳&#xff0c;硬件时间戳排除了系统软件引起的延时和抖动。如下图所示意&#xff1a; 下面我们使用北京飞灵科技有限公司开…

在命令行执行命令后出现 Permission denied 的问题解决

解决在项目目录安装一个有 “bin” 配置的依赖包后&#xff0c;执行 “bin” 命令&#xff0c;出现了 Permission denied 的问题。 问题 比如有这样一个包 json2playwright &#xff0c;它的 package.json 中 “bin” 是&#xff1a; "bin": {"pince": &q…