• [论文理解] Unsupervised Anomaly Detection with Generative Adversarial Networks to Guide Marker Discovery


    Unsupervised Anomaly Detection with Generative Adversarial Networks to Guide Marker Discovery

    Intro

    本文提出利用GAN来做异常检测,大致思想为,先使用正常方式训练生成器G,训练完成后固定G的参数,给定一张图片x,在latent space里去查询与之最匹配的z,认为如果能找到一个合适的匹配,那么输入x就应该服从训练生成图片的分布(近似认为是训练GAN时正常图片的分布),即为正常图片,否则为异常。基于此思想,作者设计了用于衡量匹配度的标准,用于区分正常样本和异常和样本。

    Unsupervised Manifold Learning of Normal Anatomical Variability

    这一部分就是正常的GAN的训练过程,与之前其他异常检测的工作一样,这里采用分patch训练的方式,将原图划分为k个patch,通过对抗训练将latent space采样的z映射为image space的图像。

    常规的GAN训练loss:

    [mathop{min}_G mathop{max}_D (D,G) = mathbb{E}_{x acksim p_{data}(x)}[logD(x)] + mathbb{E_{z acksim p_z(z)}}[log(1-D(G(z)))] ]

    Mapping new Images to the Latent Space

    GAN学习到的是如何将latent space中的采样结果z映射到image space的图像,但是反过的映射却并没有学习到,因此一个简单的想法就是去查询与输入图像最接近的对应z。

    也即给定输入x和一个已经经过训练的生成器G,找到一个z,使得G(z)和x的差异最小。

    写成loss形式就是:

    [L_R(z_gamma) = sum |x - G(z_gamma)| ]

    显然整个loss里只有一个参数z,可以通过对z求梯度进行迭代优化,不再赘述。上式在文章中称之为Residual Loss,因为是残差形式。

    然而作者并没有只使用这一个loss去优化z,因为上式只使用了GAN在训练时的生成器,并没将判别器加以利用,因此添加了判别器部分的discrimination loss,认为之前训练的判别器也具备查询能力,显然如果判别器的输出概率值在输入图片x和G(z)之间的差异较小,也可以认为这个z就是我们要找的z。文章觉得直接匹配输出层的特征要更加合适,于是并不是直接使用的判别器的概率输出,而是输出特征。

    [L_G(z_gamma) = sum |f(x) - f(G(z_gamma))| ]

    这里f表示判别器特征提取函数。

    因此,总的loss就可以写为:

    [L(z_gamma) = (1-lambda) cdot L_R(z_gamma) + lambda cdot L_D(z_gamma) ]

    对z求梯度优化即可。

    Detection of Anomalies

    上面的loss可以作为评判样本是否异常的标准,显然loss越小,说明在训练中见过相似的图片,即输入与训练样本同分布,所以判定为正常样本,否则判定为异常样本。

    即将loss改写为下式:

    [A(x) = (1-lambda)cdot R(x) + lambda cdot D(x) ]

    通过残差部分(R(x) = |x - G(z_T)|)可以得到异常区域,即差异的亮度可以表示异常区域和异常成都,而判别部分D(x)则可以反映置信水平。

    效果见图:

    Coding

    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    from torch.autograd import Variable
    class G(nn.Module):
        def __init__(self):
            super(G,self).__init__()
            self.layer1 = nn.Sequential(
                 nn.Linear(100,7*7*512),
                 nn.BatchNorm1d(7*7*512),
                 nn.ReLU(),
            )
            self.layer2 = nn.Sequential(
                            nn.ConvTranspose2d(512,256,3,2,1,1),
                            nn.BatchNorm2d(256),
                            nn.LeakyReLU(),
                            nn.ConvTranspose2d(256,128,3,1,1),
                            nn.BatchNorm2d(128),    
                            nn.LeakyReLU(),
                )
            self.layer3 = nn.Sequential(
                            nn.ConvTranspose2d(128,64,3,1,1),
                            nn.BatchNorm2d(64),    
                            nn.LeakyReLU(),
                            nn.ConvTranspose2d(64,1,3,2,1,1),
                            nn.Tanh()
                )
        def forward(self,z):
            out = self.layer1(z)
            out = out.view(out.size()[0],512,7,7)
            out = self.layer2(out)
            out = self.layer3(out)
            return out   
    
    class D(nn.Module):
        def __init__(self):
            super(D,self).__init__()
            self.layer1 = nn.Sequential(
                            nn.Conv2d(1,8,3,padding=1),   # batch x 16 x 28 x 28
                            nn.BatchNorm2d(8),    
                            nn.LeakyReLU(),
                            nn.Conv2d(8,16,3,stride=2,padding=1),  # batch x 32 x 28 x 28
                            nn.BatchNorm2d(16),    
                            nn.LeakyReLU(),
                            #('max1',nn.MaxPool2d(2,2))   # batch x 32 x 14 x 14
            )
            self.layer2 = nn.Sequential(
                            nn.Conv2d(16,32,3,stride=2,padding=1),  # batch x 64 x 14 x 14
                            nn.BatchNorm2d(32),
                            nn.LeakyReLU(),
                            #nn.MaxPool2d(2,2),
                            nn.Conv2d(32,64,3,padding=1),  # batch x 128 x 7 x 7
                            nn.BatchNorm2d(64),
                            nn.LeakyReLU()
            )
            self.fc = nn.Sequential(
                            nn.Linear(64*7*7,1),
                            nn.Sigmoid()
            )
        def forward(self,x):
            out = self.layer1(x)
            out = self.layer2(out)
            out = out.view(out.size()[0], -1)
            feature = out
            out = self.fc(out)
            return out,feature
    class AnoGAN(nn.Module):
        def __init__(self,la = 0.1):
            super(AnoGAN,self).__init__()
            self.la = la
            self.G = G()
            self.D = D()
        def forward(self,x,z):
            fake_x = self.G(z)
            L_R = F.l1_loss(fake_x,x)
            _,f_x = self.D(x)
            _,f_fake_x = self.D(fake_x)
            L_D = F.l1_loss(f_fake_x,f_x)
            return (1-self.la) * L_R + self.la * L_D
    
    
    if __name__ == "__main__":
        anogan = AnoGAN()
        #x = torch.randn(2,1,28,28)
        z = Variable(torch.nn.init.normal(torch.zeros(2,100),mean=0,std=0.1),requires_grad=True)
        #print(anogan(x,z))
        optimizer = torch.optim.Adam([z],lr=1e-3)
        datas = [torch.randn(2,1,28,28)]
        for epoch in range(2):
            for x in datas:
                optimizer.zero_grad()
                loss = anogan(x,z)
                print(loss)
                loss.backward()
                
                optimizer.step()
                
    
    
  • 相关阅读:
    mysql 数据库集群连接配置
    tomcat server.xml 配置优化
    使用PowerDesigner16.5 逆向数据库生产表结构或导出word文档
    SpringBoot、thymeleaf 国际化配置
    解决 SpringBoot 跨域请求问题
    在 centos7 系统中用 docker 创建 tomcat 服务镜像
    JAVA 调用 com.google.protobuf
    登录后获取token,作为参数传入下一个操作
    通过二进制数据流方式上传图片及性能测试脚本编写
    信息服务(ISS)管理器之报错【"/"应用程序中的服务器错误。】
  • 原文地址:https://www.cnblogs.com/aoru45/p/12262507.html
Copyright © 2020-2023  润新知