InfoGAN 非监督条件生成

作者: 莫烦 编辑: 莫烦 2021-04-09

学习资料:

怎么了

上文我们介绍过 ACGAN 在训练Generator的时候可以自定义标签, 按照标签来输出生成的图片。这次介绍的 InfoGAN 也是做这回事,不过它又有稍稍不同。一句话来介绍 InfoGAN: 模型自己给生成器创造出适用的标签或调控开关,来控制生成的结果。 我再详细阐述一下这句话,也就是说,如果 ACGAN 是人为给定一些数据标签, 让模型学着使用这些标签来生成对应的图片,那么 InfoGAN 是完全的非监督学习,预设你想要分多少个类, 用多少个调控器,InfoGAN 自动给你将这些类别,调控器配置好。所以在一开始的时候,这些类别和调控器都是一种空瓶,模型训练好以后, 自己给空瓶填上了富有意义的液体,这些类别和调控器才能被有意义地使用。

在最后应用的时候,同样是按条件生成,它又和 ACGAN 有什么不同呢?

acgan

infogan

上面第一张是 ACGAN 的训练模式,第二张是 InfoGAN 的模式, InfoGAN 采取的是非监督模式,自己赋予条件以意义。

他们的不同点具体如下:

类比 ACGAN InfoGAN
标签 真实 虚拟
学习模式 监督学习 非监督
条件输入在训练 有意义 无意义
条件输入在训练 有意义 有意义

results

仔细观察训练的步骤就不难发现,每一行都有比较固定的非监督-自学习标签。而且从左到右比较明显,也学习出了一种斜度的特征。

怎么训练

再看到这张网络结构,我们发现它将 Discriminator 的原始功能(判断真假)交还给了 Discriminator,而且 Discriminator 就只负责判断真假。 另外我们额外加一个 Recognition(R)网络,这个网络的作用就是让 InfoGAN 可以做非监督学习,自学出来一些可作为条件的特征。

infogan

乍一看的确可以这么做,但是这种设计是存在资源浪费的,D 网络和 R 网络对于图片的理解能否通用呢?或者说CNN的图片理解,是否可以被 D R 两个网络共用? 这样我们就能节省计算开销了。的确没错,像下面这样稍作修改,让 D 和 R 共用一部分网络参数,共用一个对图片特征的高层抽象理解,然后再做不同的任务。

infogan

ACGAN 不同的是,我们不需要提供额外的标签信息,条件信息在 InfoGAN 中也是随机采样的, 只需要在 R 网络那边将刚刚随机采样的 X 当成 Y,做一个非监督,就能让 G 网络掌握这个随机条件分布可以发挥的作用了。

秀代码

如果想直接看全部代码, 请点击这里去往我的Github. 我在这里说一下代码当中的重点部分。

Training 的步骤和之前其他的GAN并没有太大差别。重点是在 InfoGAN 的结构上。我们先看 Generator,我分别给了三个 Input

  1. noise 通常 GAN 中的随机噪点
  2. style 封闭空间的连续值随机数
  3. label 随机类别

通过这三种不同的随机方式来控制生成器的结果。style 就像一个数值调控器,调节大小,label 是类别调控器,调节种类。 我使用了自己封装的 mnist_uni_gen_cnn 来标准化这个教程中的一些其他 GAN 网络设定,这样可以做横向对比。

from tensorflow.keras.layers import Dense, Input, BatchNormalization, LeakyReLU, Dropout
from gan_cnn import mnist_uni_disc_cnn, mnist_uni_gen_cnn

class InfoGAN(keras.Model):
    def _get_generator(self):
        latent_dim = self.rand_dim + self.label_dim + self.style_dim
        # 三种随机数控制
        noise = Input(shape=(self.rand_dim,))
        style = Input(shape=(self.style_dim, ))
        label = Input(shape=(), dtype=tf.int32)

        label_onehot = tf.one_hot(label, depth=self.label_dim)
        model_in = tf.concat((noise, label_onehot, style), axis=1)
        s = mnist_uni_gen_cnn((latent_dim,))
        o = s(model_in)
        model = keras.Model([noise, label, style], o, name="generator")
        return model

接下来看看稍稍复杂一点的 Discriminator,里面包含了一个共用网络 s, 这个 s 是用来抽取图片高层特征的,这个高层特征将同时被 D 和 R 网络使用。 D 网络好说,直接输出真假判断。但是 R 网络可能同时包含了连续值特征和类别特征,而且连续值特征还可以是一个正太分布。所以这里我们分情况处理了一下。

class InfoGAN(keras.Model):
    def _get_discriminator(self):
        img = Input(shape=self.img_shape)
        # 共用特征抽取网络
        s = keras.Sequential([
            mnist_uni_disc_cnn(self.img_shape),
            Dense(32),
            BatchNormalization(),
            LeakyReLU(),
            Dropout(0.5),
        ])
        style_dim = self.style_dim if self.fix_std else self.style_dim * 2
        
        # R 网络
        q = keras.Sequential([
            Dense(16, input_shape=(32,)),
            BatchNormalization(),
            LeakyReLU(),
            Dense(style_dim+self.label_dim)
        ], name="recognition")
        o = s(img)
        
        # D 网络输出
        o_bool = Dense(1)(o)
        # R 网络输出
        o_q = q(o)
        # R 网络输出切分,1 连续值输出
        if self.fix_std:
            q_style = self.style_scale*tf.tanh(o_q[:, :style_dim])
        else:
            q_style = tf.concat(
                (self.style_scale * tf.tanh(o_q[:, :style_dim//2]), tf.nn.relu(o_q[:, style_dim//2:style_dim])),
                axis=1)
        # R 网络输出切分,2 类别值输出
        q_label = o_q[:, -self.label_dim:]

        model = keras.Model(img, [o_bool, q_style, q_label], name="discriminator")
        return model

接下来,我们在看看loss的结算模式吧,除了正常的 真假 loss,论文中还引入了一个叫 info loss 的 loss,这个 loss 就是用来表示通过随机条件生成的图片, 还能不能从生成图片中重新提取出来这些随机条件?如果G网络生成了带有条件信息的图片,而R网络也能从照片里提取出这样的条件, 那么就从侧面说明了这个条件被合理的使用到了生成的图片上,也就是说,G真的按条件生成了图片。 举个例子,如果G按照大眼睛,高鼻子生成了大眼睛高鼻子的图片,但R也从图片里没有发现了大眼高鼻特征,这就说明G不理解条件,生成得不好。

在计算这个 info loss 时,可以分:

  • 类别误差:crossentropy loss
  • 连续值概率误差:这也可以通过正太分布的信息误差来计算
class InfoGAN(keras.Model):
    def loss_mutual_info(self, style, pred_style, label, pred_label):
        # 标签误差
        categorical_loss = keras.losses.sparse_categorical_crossentropy(label, pred_label, from_logits=True)
    
        # 正太分布误差
        if self.fix_std:
            # 如果固定正态分布的标准差
            style_mean = pred_style
            style_std = tf.ones_like(pred_style)   # fixed std
        else:
            # 如果标准差也是一个随机数
            split = pred_style.shape[1]//2
            style_mean, style_std = pred_style[:split], pred_style[split:]
            style_std = tf.sqrt(tf.exp(style_std))
        epsilon = (style - style_mean) / (style_std + 1e-5)
        ll_continuous = tf.reduce_sum(
            - 0.5 * tf.math.log(2 * np.pi) - tf.math.log(style_std + 1e-5) - 0.5 * tf.square(epsilon),
            axis=1,
        )
        
        # 总 Mutual Information loss 
        loss = categorical_loss - ll_continuous
        return loss

其他的代码我就不细说了, 如果理解了上面的重点部分,其他代码其实也还比较好理解。我们看看最后一个 epoch 生成的图片吧,我加了两个style连续随机,一个label类别误差。 这里的 style 模型学出来的结果看起来是去控制字体的斜度和横向的宽窄了,而 label 学出来尝试去区分 1234 这样的数字,但是还没有学好分类。 你可以看到在3这个类别里,还冒出了一个5,说明网络觉得 3 和 5 还是有些接近的。

res

问题

所以 InfoGAN 的标签是在训练后才变得有意义的,而且也有一个非监督学习的通病,和 Kmeans 算法一样,我要在训练之后,手动归类一下模型学出来的类别。 比如我要模型生成 1234 类别,但是我并不知道它依据的是什么在分类,只有当模型训练好,我在把它的 1234 类别挑出来溜溜, 我才知道它认为的 1234 是我认知中的哪些类别。

res

从这张图就可以看出,虽然我的生成的标签排序是按照 0~9 从上到下排列的,但是模型并不会按照我给的 0~9 标签生成数字 0~9,而是按照他自己随机学习到的标签映射。 从左到右我也没有告诉它先 / 后 \ 。这都是他自己的安排。如果要使用这些非监督学出来的条件规则,那这就是非监督的一种不太好用的地方。

总结

InfoGAN 是一种非监督学习,他可以自己设计条件属性,我们可以在生成的时候,调控这些自己生成的条件属性。通过它的自发性学习,得到这些调控器, 我自己玩调控器的时候,感觉还是挺有意思的。


降低知识传递的门槛

莫烦很常从互联网上学习知识,开源分享的人是我学习的榜样。 他们的行为也改变了我对教育的态度: 降低知识传递的门槛免费 奉献我的所学正是受这种态度的影响。 通过 【赞助莫烦】 能让我感到认同,我也更有理由坚持下去。