zoukankan      html  css  js  c++  java
  • 关于gan的流程理解

    关于gan的流程的理解

    最近再看cyclegan所以慢慢来看,最后了解了原理来跑代码就好

    -----------------------------------------------------

      关于gan学习的三个重要的点:1 生成器(generator)  2 分辨器(discriminator)   3 训练手段(training strategy)  

      生成器的作用就是生成假的图片

      分辨器的作用就是在给一个正确的图片和一个生成的假的图片之后,他可以把正确的找出来

      训练手段,这个最为重要,因为很多博文没有给出来,所以大家读的也是云里雾里

    ------------------------------------------------------

     由此,其实我这里提出好多问题:

    1 如何生成假的图片:反卷积

    2 如何判断,好像是一个二分类,两个图片都给过去,<0.5就是假的,>0.5就是真的,当然这个0.x就是sigmoid(wx+b)(也就是距离的sigmoid值,)可是还是有问题,同时给两个图片吗?应该是给一个图片,然后算距离?不是,给两个图片,都算标定值的距离,两个物体只能二分类?

    3 如何梯度下降?

    4  训练手段

    带着这几个问题去读源码

    面对第一个问题:如何生成图片,源码给出的解决方案是反卷积,

    那么如何反卷积呢?

    就是这样:

    首先的操作是:

    1 进行卷积上图第一行  feature:4*4 filter: 2*2  stride: 2   得到结果:2*2

    2      进行反卷积:首先第一步:

          1 插值补0:让卷积后的结果,每一个元素后面都补(stride-1)个0 成为了 下左2

          2 padding补0:对整体再补0,这个整体补0的个数是取决于补0之后,把卷积核完全颠倒过来,按照stride=1进行卷积,卷积之后要得到原始大小(上左一)的结果

    具体函数就一个:nn.ConvTranspose2d(ngf * mult, int(ngf * mult / 2),
                                             kernel_size=3, stride=2,
                                             padding=1, output_padding=1,
                                             bias=use_bias), 

    这个函数是pytorch的函数,那么具体怎么用呢?

    第一个参数:int_channels

    第二个参数:out_channels

    第三个参数:卷积核大小

    第四个参数:步长

    第五个参数:输入每一条边补充padding

    第六个参数:输出每一条边补充padding

    具体步骤:

    >>> input = autograd.Variable(torch.randn(1, 16, 12, 12))
    >>> downsample = nn.Conv2d(16, 16, 3, stride=2, padding=1)
    >>> upsample = nn.ConvTranspose2d(16, 16, 3, stride=2, padding=1)
    >>> h = downsample(input)
    >>> h.size()
    torch.Size([1, 16, 6, 6])
    >>> output = upsample(h, output_size=input.size())
    >>> output.size()
    torch.Size([1, 16, 12, 12])

    对于downsample,那么是正常的卷积,nn.conv2d
    而对于upsample,那么是反卷积,nn.ConvTranspose2d
    具体的问题就是如何把6*6的变成12*12的

    思路:按照上面的思路来
    1 补0:6*6补0,补的0是stride-1的个数,此时也就是(2-1)个0,也就是每一个元素后面补1个0,变成12*12
    2 补0:首先stride此时固定为1,然后:(12-k+2*p)/1 +1 =(12-3+2*1)/1 +1 =12
    3 此时的结果变为1*16*12*12

    为证明此理解正确性:
    >>> input = autograd.Variable(torch.randn(1, 16, 12, 12))
    >>> downsample = nn.Conv2d(16, 16, 3, stride=2, padding=1)
    >>> upsample = nn.ConvTranspose2d(16, 16, 2, stride=3, padding=1)
    >>> h = downsample(input)
    >>> h.size()
    torch.Size([1, 16, 6, 6])
    >>> output = upsample(h, output_size=input.size())
    >>> output.size()
    结果应该是
    1 内部补0:变为18*18
    2 padding补0:(18-2+2*1)/1 +1 =19

    结果应该为1*16*19*19
    然而结果错了



    -----------------------------------下划线-------------------------------------
    以上都是错误的,那么正确的应该是什么样
    1 补0:每个元素之间补0
    比如:6*6 补stride-1个0   如果stride=2, 6+(stride-1)*(6-1)=11 s=3 6+(s-1)*(5)=16 也就是补产生边框,只产生间隔的元素
    2 反卷积:利用卷积的逆,x=(m-k+2*p)/1 +1
    x为补0的结果,求m
    这个部分为正解
    ---------------------------分析----------------------------------------------------
    经试验证明是对的,但是分析一下上面的模型错在哪里?
    其实没错,是我的理解有问题
    因为步骤就是两步,
    1 补0 (补充stride-1个0,且补充元素之间的,不是每个元素之后的)
    2 根据我想要变成的样子进行反卷积,也就是1的结果是我想要模型进行卷积之后的结果


    一句话总结,
    反卷积就是先进行0的插值,因为在卷积的计算公式里面,stride动不动就除以2,除以3,所以,补充stride的这个插值可以先放大到差不多想要的结果,然后再进行反卷

    --------------------------------------------------------以上为反卷积的部分----------------------------------------------------------------------
    接下来是

    2 个loss
    也就是生成器的loss和分辨器的loss
    首先来说分辨器的loss,这个loss的主要作用是,把generator输入的图片判别为0(也就是假的),把真实输入来的图片判别为1(也就是真的)
    所以,答案很明显,这里就是一个二分类bceloss,当输入为真实图片,label=1,当输入为gennerator图片时,为0
    注:这里看到有些代码是把这里变为两个Loss,这个具体还要再看

    接下来就是这个generator的loss


    ---------------------------------------------------------以下为discriminator部分-----------------------------------------
    这里没有什么特别多说的,主要就是一个正常的卷积,没有很特殊的,但是具体结构可以研究下

    -------------------------------------传统Gan训练策略---------------------------
    训练策略
    1 首先,初始化generator and discriminator
    2 从generator得到一些fake图片,
    3 训练discriminator,并且把generator参数固定住
    4 训练discriminator,把fake图片和real图片都放到discriminator,用bceloss来让分类器清晰的分辨出real和fake
    5 此时discriminator已经可以把real和fake分清,固定discriminator参数,训练generator,这里再次让生成的fake图片
      进入刚刚的bceloss,并且图片是fake图片,但是给他打上标签为1的label,目的是让generator生成的图片尽量为真实的
      (注:这里之所以能让generator生成更真实的fake图片是因为discriminator已经能很好的认知什么是真,什么是假,
        由于此时fake的图片已经给了label为1,那么generator的图片也能训练更加逼真的fake图片)



    --------------------------------------------------传统gan的原理------------------------------------------------------------



    这个图解释了最基本的gan的样子
    最基本的gan的样子是:
    1 一堆噪点去生成一个图片,当然这个图片是假的图片(generator)
    2 这个假的图片与真的图片放入discriminator ,从而训练分类器,
    3 策略是上面讲的
    4 注意loss是两个bceloss,一个是discriminator的,另一个是generator的,

    最后的结果是得到这个假的生成图片,,,这个生成图片的特点是与真实图片比较相似

    ------------------------------------------------------------------------------------
    gan生成图像主要就是两种方式:

    pair unpair
    这里的pair指的是pixle 2 pixle 级别的gan
    而cycle gan在这里进行了改进,使得不需要pixle级别对应的(unpair),也可以进行风格迁移

    ----------------------------------------------------------------------------------


    --------------------------------pixle 2 pixle--------------------------------------------------


    对于pixle2pixle gan,必须使用左面的pair图片,从而进行风格迁移
    对于cycle gan,使用右边的unpair就可以进行风格的迁移,这个大大减少了工作量

    下面简单说一下这个pixle 2 pixle 的,


    这个图是我截取cycle gan的,因为没有找到pixle gan的图片(急于码字,所以没时间找图了,很是抱歉),
    在这里,pixle gan的左上角和右下角的应该是一个pair,也就是再往上图片的(铅笔画的鞋,实物拍的鞋),
    流程就是素描鞋generator出一个yhat,这个yhat应该是风格和实物拍的鞋是一样的,这个是pixle级别的学习
    loss既有l2(像素级别对应),又有bce的(做)

    所以,所谓的pair就是如果你想把一个简单的向日葵变成梵高的向日葵,那么
    你需要一个简单的向日葵,还有一个梵高风格的并且是与之前的pixle对应的向日葵作品
    这样才能实现简单向日葵到梵高向日葵的迁移




    --------------------------------------------------cycle gan 具体流程--------------------------------------------------------







    这个图片解释了cycle gan的原理
    先是一个真实图片(不是噪声,这个区别于传统的gan)进入生成器,产生了一个假的图片,
    这个假的图片有两个目的地:第一个是去骗鉴别器,第二个是保持自我的模样,也就是pixle级别要对称,意味着要使中间的假的图片的形状要与最左边的一样
    那么 
      一:去骗鉴别器,就往下走,bceloss,鉴别器的目的是风格迁移,把domainb的风格迁移给生成的fake图片
      二:自我进化,往右走,n1loss,要让生成器去生成pixle级别要对应的图片,也就是生成的fake图片要和最左边的图片保持像素级对应,

    但是为什么会这样呢?
    首先既能满足n1loss,又要满足bceloss,那么最终的结果就是二者会融合,
    但是怎么样融合呢?
    因为n1loss是像素级的,也就是pixle级别要相互对应,所以产生的形状是一样的,颜色都是一样的,也就是上图最左和最右的样子。
    但是中间为什么不会形成real image in domain b 的样子呢?
    但是这样做的目的是为什么?目的是为了生成fake image in domain b





    具体的流程:

    与上面的区别在于给




    这个结构与之前的结构的区别在于,这个相当于加了约束,可以这么理解:
    上一张图片中,进行bce的时候是放入真实的图片,可是这个约束不够,不足以让学习到的这个

       self.netG_A = networks.define_G(opt.input_nc, opt.output_nc, opt.ngf, opt.netG, opt.norm,
                                            not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids)
       self.netG_B = networks.define_G(opt.output_nc, opt.input_nc, opt.ngf, opt.netG, opt.norm,
                                            not opt.no_dropout, opt.init_type, opt.init_gain, self.gpu_ids)


       def backward_D_basic(self, netD, real, fake):
            """Calculate GAN loss for the discriminator

            Parameters:
                netD (network)      -- the discriminator D
                real (tensor array) -- real images
                fake (tensor array) -- images generated by a generator

            Return the discriminator loss.
            We also call loss_D.backward() to calculate the gradients.
            """
            # Real
            pred_real = netD(real)
            loss_D_real = self.criterionGAN(pred_real, True)
            # Fake
            pred_fake = netD(fake.detach())
            loss_D_fake = self.criterionGAN(pred_fake, False)
            # Combined loss and calculate gradients
            loss_D = (loss_D_real + loss_D_fake) * 0.5
            loss_D.backward()
            return loss_D

        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_G(self):
            """Calculate the loss for generators G_A and G_B"""
            lambda_idt = self.opt.lambda_identity
            lambda_A = self.opt.lambda_A
            lambda_B = self.opt.lambda_B
            # Identity loss
            if lambda_idt > 0:
                # G_A should be identity if real_B is fed: ||G_A(B) - B||
                self.idt_A = self.netG_A(self.real_B)
                self.loss_idt_A = self.criterionIdt(self.idt_A, self.real_B) * lambda_B * lambda_idt
                # G_B should be identity if real_A is fed: ||G_B(A) - A||
                self.idt_B = self.netG_B(self.real_A)
                self.loss_idt_B = self.criterionIdt(self.idt_B, self.real_A) * lambda_A * lambda_idt
            else:
                self.loss_idt_A = 0
                self.loss_idt_B = 0

            # GAN loss D_A(G_A(A))
            self.loss_G_A = self.criterionGAN(self.netD_A(self.fake_B), True)
            # GAN loss D_B(G_B(B))
            self.loss_G_B = self.criterionGAN(self.netD_B(self.fake_A), True)
            # Forward cycle loss || G_B(G_A(A)) - A||
            self.loss_cycle_A = self.criterionCycle(self.rec_A, self.real_A) * lambda_A
            # Backward cycle loss || G_A(G_B(B)) - B||
            self.loss_cycle_B = self.criterionCycle(self.rec_B, self.real_B) * lambda_B
            # combined loss and calculate gradients
            self.loss_G = self.loss_G_A + self.loss_G_B + self.loss_cycle_A + self.loss_cycle_B + self.loss_idt_A + self.loss_idt_B
            self.loss_G.backward()


    --------------------------------------分拆理解------------------------------------------------------
    cyclegan 里面loss很多,搞懂他的loss,就能理解了
    1 discriminator loss:
      分清真的图,假的图
    两个discriminator的图
    self.loss_D_A = self.backward_D_basic(self.netD_A, self.real_B, fake_B)
    self.loss_D_B = self.backward_D_basic(self.netD_B, self.real_A, fake_A)

    2 generator loss:

    self.criterionIdt = nn.L1loss()

    self.criterionGAN = nn.bcewithlogits()

    self.criterionCycle = torch.nn.L1Loss()

    self.idt_A = self.netG_A(self.real_B)
    self.loss_idt_A = self.criterionIdt(self.idt_A, self.real_B) * lambda_B * lambda_idt
               
    self.idt_B = self.netG_B(self.real_A)
    self.loss_idt_B = self.criterionIdt(self.idt_B, self.real_A) * lambda_A * lambda_idt

    loss_idt代表什么?
    这里也是约束,是让fakeimage和之前的image,做mseloss,以保证pixle级别的对应

    self.loss_G_A = self.criterionGAN(self.netD_A(self.fake_B), True)
    # GAN loss D_B(G_B(B))
    self.loss_G_B = self.criterionGAN(self.netD_B(self.fake_A), True)
    # Forward cycle loss || G_B(G_A(A)) - A||
    loss_G_A(B)这里的工作是属于第二次训generator:即让产生的generator生成更逼真的图片

    self.loss_cycle_A = self.criterionCycle(self.rec_A, self.real_A) * lambda_A
    # Backward cycle loss || G_A(G_B(B)) - B||
    self.loss_cycle_B = self.criterionCycle(self.rec_B, self.real_B) * lambda_B
    # combined loss and calculate gradients
    loss_cycle_A(B)这里的工作是什么意思?
    这里是让生成的图片和之前的保持一样

    self.loss_G = self.loss_G_A + self.loss_G_B + self.loss_cycle_A + self.loss_cycle_B + self.loss_idt_A + self.loss_idt_B


    -----------------总结一下cyclegan-------------------------
     

     1 generator

      这里可以看cycle gan里面的第一张图片就好,因为第二张图片和第一张原理一样,只是多了些约束

            a 得到real image in domain a

      b 如图,生成fake图片,

            c  fake图片重建(reconstructed image)

    2 discriminator

           d 如图,生成的fake image和real image in domain b 放入鉴别器,以得到一个很好的鉴别器(知道什么是真,什么是假),

    3 训练流程(loss)

      1首先生成fake图片(b过程)

       2把fake图片和真实图片放在一起训练分类器(d过程)  bceloss1                  目的是能够清晰地分辨真实图片和假图片

       3固定分类器,把generator生成的图片打上label=1,进入鉴别器训练 bceloss1 ,以求得到更好的类似于domianb的图片

            4 restructure 与image in domain a进行                            mseloss       保证pixle级别对应,也就是保证风格迁移而内容不变

             5 fake in domian a 和 real image in domain a 进行     mseloss      保证pixle级别对应,也就是保证风格迁移而内容不变

           

    --------广义理解----------------------------------------

    最重要的是想起generator discriminator的精髓,也就是训练方式,即互相驳的训练方式
    面对传统gan:

    面对风格迁移gan:
    任何只要有两个类别,都可以变成gan
    比如有一个类是橘猫,有一个类是土狗,那么想要生成一个土狗样子,但是皮肤是橘猫的新物种,橘猫这边bce 土狗那边l2/l1
    所以可以理解为任何unpair的数据都可以用作gan,只要他们是两个类,那么这两个类就可以类比,
    精髓:
    首先要有一个三角形(三个顶点(1 real image in domain a 2 fake image 3 real image in domain b)
    虽然是一个三角形,但是归根结底是两个类
    其次就要在这三个做相似,loss可以选为l1/l2/bce
      1可以像猫,也可以像狗
      2可以像猫,并且远离狗
      3可以像狗,并且远离猫

    ----------结尾附送李宏毅老师的例子---------------------------------
    最近写paper,这个后期补

    ----------接下来是dcgan的原理,这个很有用-----------------------------------
    dcgan是什么,首先他遵从传统gan的模型


    1即噪声生成,产生一个fake图片,
    2fake :0与 real :1图片组合生成一个discriminator
    3fake :1来训练generator

    这是传统的原理
    dcgan通过一些调试,使得最后真的可以生成一张图片,
    dcgan之前只是一种思想,并不是真的能够生成一张栩栩如生的图片,但是dcgan可以了,
    这个paper偏工程,主要还是一些调参的trick

    -----------------------------------------------------------------------------------------------------
    半监督:semi-supervised
    无监督:unsupervisied
    接下来考虑这两个点
    其实这两个做分类的关键(用gan的方法)都是在于生成很多带有标签的图片
    只不过半监督可以生成更加多样的图片
    --------------------------无监督---------------------------------
    首选gan本来就是一个无监督的网络
    gan的generator生成图片,主要是依靠两个点,
    1 一些随机噪声
    2 一些real image
    所以,他实际上就算是无监督的,因为没有图片和与之对应的标签
    所以,最后生成的可能是一坨

    但是dcgan生成了有模有样的图片,也就是无监督的东西生成了一堆带有label的图片

    -----------这里没有解释充分----------------
    ------------------------------------------------------------------------------------------------------
    接下来所谓的半监督,我们可以看到









  • 相关阅读:
    调整JVM占用内存空间方法
    java基础1.5版后新特性 自动装箱拆箱 Date SimpleDateFormat Calendar.getInstance()获得一个日历对象 抽象不要生成对象 get set add System.arrayCopy()用于集合等的扩容
    java异常处理 throw RuntimeException时不需要同时方法中声明抛出throws 异常等待调用者catch进行捕获 子父类异常问题
    java自定义泛型 面试题:接收任意数组进行反转 泛型通配符
    javabeans 内省 introspector BeanUtils
    面试题:私有构造方法类外部能访问吗,用什么方法?反射
    java基础 java中枚举的应用 抽象方法问题
    【进阶修炼】——改善C#程序质量(7)
    【进阶修炼】——改善C#程序质量(6)
    【进阶修炼】——改善C#程序质量(5)
  • 原文地址:https://www.cnblogs.com/lllcccddd/p/10621014.html
Copyright © 2011-2022 走看看