[论文理解] 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()
            

原文地址:https://www.cnblogs.com/aoru45/p/12262507.html