本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!

作者简介:秃头小苏,致力于用最浅显的语言描绘问题

往期回忆:对立生成网络GAN系列——GAN原理及手写数字生成小事例   对立生成网络GAN系列——DCGAN简介及人脸图画生成事例   对立生成网络GAN系列——AnoGAN原理及缺点检测实战   对立生成网络GAN系列——EGBAD原理及缺点检测实战   对立生成网络GAN系列——WGAN原理及实战演练

近期目标:写好专栏的每一篇文章

支撑小苏:点赞、收藏⭐、留言

对立生成网络GAN系列——GANomaly原理及源码解析

写在前面

​  在前面,我现已介绍过好几篇有关GAN的文章,链接如下:

  • [1]对立生成网络GAN系列——GAN原理及手写数字生成小事例
  • [2]对立生成网络GAN系列——DCGAN简介及人脸图画生成事例
  • [3]对立生成网络GAN系列——CycleGAN原理
  • [4] 对立生成网络GAN系列——AnoGAN原理及缺点检测实战
  • [5]对立生成网络GAN系列——EGBAD原理及缺点检测实战
  • [6]对立生成网络GAN系列——WGAN原理及实战演练

​  这篇文章我将来为咱们介绍GANomaly,论文名为:Semi-Supervised Anomaly Detection via Adversarial Training。这篇文章同样是完成缺点检测的,因此在阅览本文之前主张你对运用GAN网络完成缺点检测有一定的了解,能够参阅上文链接中的[4]和[5]。

​  准备好了吗,嘟嘟嘟,开始发车。

GANomaly原了解析


【阅览此部分前主张对GAN的原理及GAN在缺点检测上的运用有所了解,概况点击写在前面中的链接查看,本篇文章我不会再介绍GAN的一些先验常识。】


GANomaly结构

​  这部分为咱们介绍GANomaly的原理,其实咱们一起来看下图就足够了:

对抗生成网络GAN系列——GANomaly原理及源码解析

              图1 GANomaly结构图

​  咱们仍是先来对上图中的结构做一些解释。从直观的颜色上来看,咱们能够分红两类,一类是赤色的Encoder结构,一类是蓝色的Decoder结构。Encoder主要便是降维的作用啦,如将一张张图片数据压缩成一个个潜在向量;相反,Decoder便是升维的作用,如将一个个潜在向量重建成一张张图片。按照论文描绘的结构来分,能够分红三个子结构,分别为生成器网络G,编码器网络E和判别器网络D。下面分别来介绍介绍这三个子结构:

  • 生成器网络G

    ​  生成器网络G由两个部分组成,分别为编码器GE(x))G_E(x))和解码器GD(z)G_D(z),其实这便是一个自动编码器结构,主要用来学习输入x的数据散布并重建图画x{\hat x}。咱们一个个来看,先看GE(x)结构G_E(x)结构,假设咱们的输入x维度为RCHW \mathbb{R}^{CHW},经过GE(x)结构G_E(x)结构后,变成一个向量zz,其维度为Rd\mathbb{R}^d。【GE(x)G_E(x)详细结构很简单啦,这儿就不详细介绍了。我会在源码解析部分给出,咱们必定一看就会。】接着咱们来看GD(z)G_D(z)结构,它会将刚刚得到的向量z上采样成x\hat xx\hat x的维度和xx共同,都为RCHW \mathbb{R}^{CHW}。关于GD(Z)G_D(Z)结构也很简单,其主要用到了转置卷积,关于转置卷积不了解的能够看博客[2]了解概况。生成器网络G就为咱们介绍完了,是不是发现很简单呢。总结下来就两步,榜首步让输入x经过GE(x)G_E(x)得到z,第二步让z经过GD(Z)G_D(Z)变成x\hat x。这两步也能够用一步表明,即x=G(x)\hat x=G(x)

    ​  思来想去我仍是想在这儿给咱们抛出一个问题,咱们传统的GAN是怎么经过生成器来构建假图画的呢?和GANomaly有差异吗?其实这个问题的答案很简单,咱们都稍稍思考一下,我就不给答案了,不明白的评论区见吧!!!

  • 编码器网络E

    ​  编码器网络E的作用是将生成器得到的x\hat x压缩成一个向量z\hat z,是不是发现和生成器网络中的GE(x)G_E(x)很像呢,其实呀,它俩的结构便是完全相同的,生成的z\hat zzz 的维度共同,这是便利后面的丢失比较。

  • 判别器网络D

    ​  判别器网络D和咱们之前介绍DCGAN时的结构是相同的,都是将真实数据xx和生成数据x\hat x输入网络,然后得出一个分数。

GANomaly丢失函数

​  GANomaly的丢失函数分为两部分,榜首部分是生成器丢失,第二部分为判别器丢失,下面咱们分别来进行介绍:

  • 生成器丢失函数

    ​  生成器丢失函数又由三个部分组成,分别如下:

    • Adversari Loss

      我仍是直接上公式吧,如下:

      ​        Ladv=Ex∼px∣∣f(x)−Ex∼pxf(G(x))∣∣2L_{adv}=E_{x \sim px}||f(x)-E_{x \sim px}f(G(x))||_2

        这个公式对应图一中的Ladv=∣∣f(x)−f(x)∣∣2L_{adv}=||f(x)-f(\hat x)||_2这个丢失函数应该很好了解,在前面介绍的GAN网络都有提及,f(∗)f(*)表明判别器网络某个中间层的输出。这个丢失函数的作用便是让两张图画x和xx和\hat x尽或许挨近,也便是让生成器生成的图片更加传神。


    • Contextual Loss

      同样的,直接来上公式,如下:

      ​        Lcon=Ex∼px∣∣x−G(x)∣∣1L_{con}=E_{x \sim px}||x-G(x)||_1

        这个公式对应图一中的Lcon=∣∣x−x∣∣1L_{con}=||x-\hat x||_1这个函数其实也是要让两张图画x和xx和\hat x尽或许挨近。至于这儿为什么用的是L1范数而不是L2范数,作者在论文中说这儿运用L1范数的作用要比运用L2范数的作用好,这归于试验得到的定论,咱们也不用过于纠结。

    • Encoder Loss

      话不多说,上公式,如下:

      ​        Lenc=Ex∼px∣∣GE(x)−E(G(x))∣∣2L_{enc}=E_{x \sim px}||G_E(x)-E(G(x))||_2

        这个公式对应图一中的Lenc=∣∣z−z∣∣2L_{enc}=||z-\hat z||_2这儿的丢失函数在我看来主要作用便是让咱们在推理过程中的作用更好,这儿就像AnoGAN中不断搜索最优的那个z的作用。


         假如咱们这儿读过cycleGAN的论文的话,或许会觉得这个丢失函数有点类似cycleGAN中的循环共同性丢失。我觉得这篇文章的思想或许借鉴了cycleGAN中的思想,感兴趣的能够去阅览一下,非常有意思的一篇文章!!!


    生成器总的丢失是上述三种丢失的加权和,如下:

         L=wadvLadv+wconLcon+wencLencL=w_{adv}L_{adv}+w_{con}L_{con}+w_{enc}L_{enc}

  在论文供给的源码中,默许wcon=50,wadv=wenc=1w_{con}=50,w_{adv}=w_{enc}=1

  • 判别器丢失函数

      判别器的丢失函数就和原始GAN相同,如下:【不清楚的点击☞☞☞了解概况】

    对抗生成网络GAN系列——GANomaly原理及源码解析

    这部分我直接先放上代码吧,不多,也很容易了解,如下:

    self.l_bce = nn.BCELoss()
    # Real - Fake Loss
    self.err_d_real = self.l_bce(self.pred_real, self.real_label)
    self.err_d_fake = self.l_bce(self.pred_fake, self.fake_label)
    # NetD Loss & Backward-Pass
    self.err_d = (self.err_d_real + self.err_d_fake) * 0.5
    

GANomaly测验阶段

​  在上一小节,为咱们介绍了GANomaly的丢失函数,这是在测验阶段运用的。GANomaly针对的是反常检测使命,在测验阶段咱们会对输入的数据进行评分,依据评分的成果来断定输入是否反常。在GANomaly中运用的评分函数便是咱们上一小节介绍的Encoder Loss,关于一个测验数据x,用A(x)A(x)表明其反常得分,则:

​          A(x)=∣∣GE(x)−E(G(x))∣∣2A(x)=||G_E(x)-E(G(x))||_2

​  这儿咱们需要注意以下,论文中A(x)A(x)的表达式运用的是L1范数,可是从我阅览论文供给的源码来看,代码中运用的是L2范数。这儿保持和源码共同,运用L2范数。代码中关于此部分的描绘如下:

# latent_i表明G_E(x),latent_o表明E(G(x))。torch.pow(m,2)=m^2
error = torch.mean(torch.pow((latent_i-latent_o), 2), dim=1)

GANomaly源码解析

​  这儿直接运用论文中供给的源码地址:GANomaly源码

GANomaly模型建立

​  其实经过我前文的解说,不知道咱们能否感受到GANomaly模型其实是不复杂的。需要注意的是在介绍GANomaly结构时咱们将模型分为了三个子结构,分别为生成器网络G、编码器网络E、判别器网络D。可是在代码中咱们将生成器网络G和编码器网络E合并在一块儿了,也称为生成器网络G。

​  下面我给出这部分的代码,咱们注意一下这儿面的超参数比较多,为了便利咱们阅览,我把这儿用到超参数的整理出来,如下图所示:

对抗生成网络GAN系列——GANomaly原理及源码解析

""" Network architectures.
"""
# pylint: disable=W0221,W0622,C0103,R0913
##
import torch
import torch.nn as nn
import torch.nn.parallel
from options import Options
##
def weights_init(mod):
    """
    Custom weights initialization called on netG, netD and netE
    :param m:
    :return:
    """
    classname = mod.__class__.__name__
    if classname.find('Conv') != -1:
        mod.weight.data.normal_(0.0, 0.02)
    elif classname.find('BatchNorm') != -1:
        mod.weight.data.normal_(1.0, 0.02)
        mod.bias.data.fill_(0)
###
class Encoder(nn.Module):
    """
    DCGAN ENCODER NETWORK
    """
    def __init__(self, isize, nz, nc, ndf, ngpu, n_extra_layers=0, add_final_conv=True):
        super(Encoder, self).__init__()
        self.ngpu = ngpu
        assert isize % 16 == 0, "isize has to be a multiple of 16"
        main = nn.Sequential()
        # input is nc x isize x isize
        main.add_module('initial-conv-{0}-{1}'.format(nc, ndf),
                        nn.Conv2d(nc, ndf, 4, 2, 1, bias=False))
        main.add_module('initial-relu-{0}'.format(ndf),
                        nn.LeakyReLU(0.2, inplace=True))
        csize, cndf = isize / 2, ndf     # csize=16,cndf=64
        # Extra layers
        for t in range(n_extra_layers):
            main.add_module('extra-layers-{0}-{1}-conv'.format(t, cndf),
                            nn.Conv2d(cndf, cndf, 3, 1, 1, bias=False))
            main.add_module('extra-layers-{0}-{1}-batchnorm'.format(t, cndf),
                            nn.BatchNorm2d(cndf))
            main.add_module('extra-layers-{0}-{1}-relu'.format(t, cndf),
                            nn.LeakyReLU(0.2, inplace=True))
        while csize > 4:
            in_feat = cndf
            out_feat = cndf * 2
            main.add_module('pyramid-{0}-{1}-conv'.format(in_feat, out_feat),
                            nn.Conv2d(in_feat, out_feat, 4, 2, 1, bias=False))
            main.add_module('pyramid-{0}-batchnorm'.format(out_feat),
                            nn.BatchNorm2d(out_feat))
            main.add_module('pyramid-{0}-relu'.format(out_feat),
                            nn.LeakyReLU(0.2, inplace=True))
            cndf = cndf * 2
            csize = csize / 2
        # state size. K x 4 x 4
        if add_final_conv:
            main.add_module('final-{0}-{1}-conv'.format(cndf, 1),
                            nn.Conv2d(cndf, nz, 4, 1, 0, bias=False))
        self.main = main
    def forward(self, input):
        if self.ngpu > 1:
            output = nn.parallel.data_parallel(self.main, input, range(self.ngpu))
        else:
            output = self.main(input)
        return output
##
class Decoder(nn.Module):
    """
    DCGAN DECODER NETWORK
    """
    def __init__(self, isize, nz, nc, ngf, ngpu, n_extra_layers=0):
        super(Decoder, self).__init__()
        self.ngpu = ngpu
        assert isize % 16 == 0, "isize has to be a multiple of 16"
        cngf, tisize = ngf // 2, 4    #cngf=32 ,tisize=4
        while tisize != isize:
            cngf = cngf * 2
            tisize = tisize * 2
        main = nn.Sequential()
        # input is Z, going into a convolution
        main.add_module('initial-{0}-{1}-convt'.format(nz, cngf),
                        nn.ConvTranspose2d(nz, cngf, 4, 1, 0, bias=False))
        main.add_module('initial-{0}-batchnorm'.format(cngf),
                        nn.BatchNorm2d(cngf))
        main.add_module('initial-{0}-relu'.format(cngf),
                        nn.ReLU(True))
        csize, _ = 4, cngf
        while csize < isize // 2:
            main.add_module('pyramid-{0}-{1}-convt'.format(cngf, cngf // 2),
                            nn.ConvTranspose2d(cngf, cngf // 2, 4, 2, 1, bias=False))
            main.add_module('pyramid-{0}-batchnorm'.format(cngf // 2),
                            nn.BatchNorm2d(cngf // 2))
            main.add_module('pyramid-{0}-relu'.format(cngf // 2),
                            nn.ReLU(True))
            cngf = cngf // 2
            csize = csize * 2
        # Extra layers
        for t in range(n_extra_layers):
            main.add_module('extra-layers-{0}-{1}-conv'.format(t, cngf),
                            nn.Conv2d(cngf, cngf, 3, 1, 1, bias=False))
            main.add_module('extra-layers-{0}-{1}-batchnorm'.format(t, cngf),
                            nn.BatchNorm2d(cngf))
            main.add_module('extra-layers-{0}-{1}-relu'.format(t, cngf),
                            nn.ReLU(True))
        main.add_module('final-{0}-{1}-convt'.format(cngf, nc),
                        nn.ConvTranspose2d(cngf, nc, 4, 2, 1, bias=False))
        main.add_module('final-{0}-tanh'.format(nc),
                        nn.Tanh())
        self.main = main
    def forward(self, input):
        if self.ngpu > 1:
            output = nn.parallel.data_parallel(self.main, input, range(self.ngpu))
        else:
            output = self.main(input)
        return output
## 判别器网络结构
class NetD(nn.Module):
    """
    DISCRIMINATOR NETWORK
    """
    def __init__(self, opt):
        super(NetD, self).__init__()
        model = Encoder(opt.isize, 1, opt.nc, opt.ngf, opt.ngpu, opt.extralayers)
        layers = list(model.main.children())
        self.features = nn.Sequential(*layers[:-1])
        self.classifier = nn.Sequential(layers[-1])
        self.classifier.add_module('Sigmoid', nn.Sigmoid())
    def forward(self, x):
        features = self.features(x)
        features = features
        classifier = self.classifier(features)
        classifier = classifier.view(-1, 1).squeeze(1)
        return classifier, features
## 生成器网络结构
class NetG(nn.Module):
    """
    GENERATOR NETWORK
    """
    def __init__(self, opt):
        super(NetG, self).__init__()
        self.encoder1 = Encoder(opt.isize, opt.nz, opt.nc, opt.ngf, opt.ngpu, opt.extralayers)
        self.decoder = Decoder(opt.isize, opt.nz, opt.nc, opt.ngf, opt.ngpu, opt.extralayers)
        self.encoder2 = Encoder(opt.isize, opt.nz, opt.nc, opt.ngf, opt.ngpu, opt.extralayers)
    def forward(self, x):
        latent_i = self.encoder1(x)
        gen_imag = self.decoder(latent_i)
        latent_o = self.encoder2(gen_imag)
        return gen_imag, latent_i, latent_o

GANomaly丢失函数

​  咱们在理论部分现已介绍了GANomaly的丢失函数,那么在代码上它们都是一一对应的,完成起来也很简单,如下:

## 界说L1 Loss
def l1_loss(input, target):
    return torch.mean(torch.abs(input - target))
## 界说L2 Loss
def l2_loss(input, target, size_average=True):
    if size_average:
        return torch.mean(torch.pow((input-target), 2))
    else:
        return torch.pow((input-target), 2)
self.l_adv = l2_loss
self.l_con = nn.L1Loss()
self.l_enc = l2_loss
self.err_g_adv = self.l_adv(self.netd(self.input)[1], self.netd(self.fake)[1])
self.err_g_con = self.l_con(self.fake, self.input)
self.err_g_enc = self.l_enc(self.latent_o, self.latent_i)
self.err_g = self.err_g_adv * self.opt.w_adv + \
             self.err_g_con * self.opt.w_con + \
             self.err_g_enc * self.opt.w_enc

​  上述代码为GANomaly生成器丢失函数代码,判别器的丢失函数代码现已在理论部分为咱们介绍了,这儿就不在赘述了。

小结

​  这儿我并没有很详细的为咱们解读代码,可是把一些要害的部分都给咱们介绍了。会了这些其实你完全能够自己完成一个GANomaly网络,或者对我之前在Anogan中的代码稍加改造也能够到达相同的作用。论文中供给的源码感兴趣的咱们能够自己去调试一下,代码量也不算多,但有的地方了解起来也有一定的困难,总归咱们加油吧!!!

参阅链接

GANomaly: Semi-Supervised Anomaly Detection via Adversarial Training

GANomaly 反常检测的经典之作|ACCV 2018

如若文章对你有所协助,那就

        

对抗生成网络GAN系列——GANomaly原理及源码解析