如前所述,infogan的提出的目标在于非监督生成模型,然而我们人类总是对数据有一些确定的知识(如图片表达得数字为几之类的),如果将这些确定的知识结合infogan进行半监督的学习,生成的数据的效果必然会有所提高。
和传统生成模型相似,infogan的结构如图1所示,实质上infogan在结构上和gan相比就是输入上增加了几维潜在变量,而在分类网络后增加了一个识别网络Q用以估计这些潜在变量,其中潜在变量可以是离散的,也可以是连续的。
图1:infogan和gan的结构,图源自Katrina Evtimova和Andrew Drozdov
而infogan的训练也和gan训练类似,采用的是对抗训练的方式,先固定D训练G尽量的去生成符合加入变量C属性的数据来愚弄D,然后训练D和Q来识别图像的真伪和C。而对于如mnist这样的数据库,我们是明显知道数据表达得数字是0到9中的某一类的,我们在输入真实图像时可以加入其标签作为潜在变量C中离散变量的一部分。
这里我们解析的代码基于由Namju Kim开发的sugartensor,代码可在github上下载。
import sugartensor as tf
import numpy as np
sugartensor包有一个好处就在于其可直接插入sugartensor不用再import tensorflow了。
batch_size = 32 # batch size
num_category = 10 # total categorical factor
num_cont = 2 # total continuous factor
num_dim = 50 # total latent dimension
#
# inputs
#
# MNIST input tensor ( with QueueRunner )
data = tf.sg_data.Mnist(batch_size=batch_size)
# input images
x = data.train.image
# generator labels ( all ones )
y = tf.ones(batch_size, dtype=tf.sg_floatx)
# discriminator labels ( half 1s, half 0s )
y_disc = tf.concat(0, [y, y * 0])
这里作者的输入数据维度选择和infogan文章中是一模一样的。对于产生的数据,g的label和d的label是相反的。
z_cat = tf.multinomial(tf.ones((batch_size, num_category), dtype=tf.sg_floatx) / num_category, 1).sg_squeeze().sg_int()
# random seed = random categorical variable + random uniform
z = z_cat.sg_one_hot(depth=num_category).sg_concat(target=tf.random_uniform((batch_size, num_dim-num_category)))
# random continuous variable
z_cont = z[:, num_category:num_category+num_cont]
# category label
label = tf.concat(0, [data.train.label, z_cat])
产生噪声z和隐含变量c,c中含有含有离散变量,离散变量z_cat用于规定产生的数据属于0到9之间的某个数,这里由于输入的数据我们已知其表达的是哪个数了,所以可以给输入数据加入标签并和z_cat拼接在一起。
with tf.sg_context(name='generator', size=4, stride=2, act='relu', bn=True):
gen = (z.sg_dense(dim=1024)
.sg_dense(dim=7*7*128)
.sg_reshape(shape=(-1, 7, 7, 128))
.sg_upconv(dim=64)
.sg_upconv(dim=1, act='sigmoid', bn=False))
# add image summary
tf.sg_summary_image(gen)
#
# create discriminator & recognizer
#
# create real + fake image input
xx = tf.concat(0, [x, gen])
with tf.sg_context(name='discriminator', size=4, stride=2, act='leaky_relu'):
# shared part
shared = (xx.sg_conv(dim=64)
.sg_conv(dim=128)
.sg_flatten()
.sg_dense(dim=1024))
# shared recognizer part
recog_shared = shared.sg_dense(dim=128)
# discriminator end
disc = shared.sg_dense(dim=1, act='linear').sg_squeeze()
# categorical recognizer end
recog_cat = recog_shared.sg_dense(dim=num_category, act='linear')
# continuous recognizer end
recog_cont = recog_shared[batch_size:, :].sg_dense(dim=num_cont, act='sigmoid')
recog_conts = recog_shared[batch_size:, :].sg_dense(dim=num_cont, act='sigmoid')
这里短短几行代码,作者构建了infogan的网络结构,与原作稍稍不同,我们对于潜在变量C多加入了两维的输出用于估计连续变量的方差。,损失函数如下。
loss_disc = tf.reduce_mean(disc.sg_bce(target=y_disc)) # discriminator loss
loss_gen = tf.reduce_mean(disc.sg_reuse(input=gen).sg_bce(target=y)) # generator loss
loss_recog = tf.reduce_mean(recog_cat.sg_ce(target=label)) \
+ tf.reduce_mean(0.5*tf.identity(tf.square(recog_cont-z_cont)/recog_conts))) # recognizer loss
train_disc = tf.sg_optim(loss_disc + loss_recog, lr=0.0001, category='discriminator') # discriminator train ops
train_gen = tf.sg_optim(loss_gen + loss_recog, lr=0.001, category='generator') # generator train ops
对于离散潜在变量,Q的识别损失为其输出与潜在变量之间的交叉熵。而对于连续变量,Q的识别误差我们改为估计方差为recog_conts时的带权重的l_2范数,训练过程
def alt_train(sess, opt):
l_disc = sess.run([loss_disc, train_disc])[0] # training discriminator
l_gen = sess.run([loss_gen, train_gen])[0] # training generator
return np.mean(l_disc) + np.mean(l_gen)
# do training
alt_train(log_interval=10, max_ep=30, ep_size=data.train.num_batch, early_stop=False)
结果