AcGan
译文下载地址
链接:https://pan.baidu.com/s/1OEHITJ0f9SwJrc_XyGOvEQ
提取码:az2o
译文一瞥:
介绍
本文中主要是提出了一种新的网络结构,可量化辨识度和多样性的方法及一些实验论证。
新的网络结构
本文主要介绍了一种CGan的变种,其带有额外的辅助分类器,称之为AcGan。主体网络结构仍然是生成器G和判别器D,只是G的输入除了随机噪声z之外还有类别信息c,D的输出除了是否为真是图像的概率外还有其相应的类别概率。其目标函数也发生了变化,如下如图所示: 其中D的目标是最大化Ls+Lc,而G的目标是最大化Lc-Ls。
新的可量化的测量方法
本中创新性的提出了通过使用Inception准确率来量化辨识度及MS-SSIM分值来量化样本的多样性。通过使用Inception-V3预训练模型对样本的预测类别及其自身的标签间的准确度来作为样本辨识性的指标。通过使用MS-SSIM分值作为样本多样的指标,其更符合人类在感官上的认识,MS-SSIM值越高,则代表两者更相似,相反则说明样本间的相似度更高而多样性更低。而且MS-SSIM值不能够表示生成器像素分布的熵,熵对像素间的改变比较敏感,而对图像语义上的变化不明显。MS-SSIM更适合作为样本多样性的评价指标。
实验结果
1:高分辨率图像的辨识度更高
注意此处的高分辨率不是通过类似线性插值resize得到的,而是模型自身输出的具备更多信息的高分辨率图像。文中以生成的斑马图像的Inception准确率为例说明了高分辨率图像的辨识度也就是Inception准确率越高,而通过resize得到的高分辨率不会增加辨识度。如下所示:
下面两图也更进一步说明了生成的高分辨率图像(128X128)的准确率比低分辨率图像(64X64)更高,但也说明了跟真实样本间的准确率差别还是很大的。右下图中可以看出84.4%的点(每个点代表ImageNet中的一个类别)都在平均线(蓝线,位于此线上则意味着两种分辨率图像的准确率一样)以下,这也说明了84.4%的类别都在高分辨率时的准确率更高。文中还指明了128X128的准确率为10.1% ± 2.0%,64X64的准确率为 7.0% ± 2.0%,而32X32的准确率仅为ei5.0% ± 2.0%。
2:生成图像的多样性
文中使用了MS-SSIM来测量样本的多样性,要注意MS-SSIM值越高则代表样本的多样性越低,值越低却代表样本的多样性越高。文中测量了通过AC-GAN模型生成的ImageNet中的1000各类别的平均MS-SSIM值。文中指出ImageNet训练数据各类别中中MS-SSIM值最高的为0.25,而AC-GAN模型生成的各类别样本中有847种类别样本的平均MS-SSIM值都在0.25之下,意味着有84.7%类别样本的多样性都比ImageNet训练数据中的多样性最差的类别要好。但是训练数据和模型生产样本数据的平均MS-SSIM值分别为0.05和0.18.这也说明模型生成样本的多样性相比于原始数据还有很大的提升空间。
具体请看下图:[图中各点都代表了ImageNet中的一种类别,x值为模型生成样本的平均MS-SSIM值,y值为训练样本的MS-SSIM值,蓝线为平均线,落在这上面的点则代表了两者的多样性是一样的。]
文中还跟踪了随着训练进行的MS-SSIM均值的变化。可以看下,成功训练的都是类似黑线那样随着训练的进行MS-SSIM值越低,而红线则代表训练发生了崩溃。下图中不同的线为不同的类别。(文中最终将ImageNet的1000个类别训练了100个模型,每个模型包含10个类别)
3:生成的图像是多样且可辨识
下图联合了Inception准确率和MS-SSIM值进行了展示。这两者是反相关的。实际上,在低多样性(MS-SSIM值高于0.25)的类别中74%的Inception准确率都低于1%。相反,在高多样性的类别中78%的类别的Inception准确率都超过了1%。而Inception-V3在ImageNet的1000个类别中的平均准确率为78.8%。而模型生成样本中只有一小部分类别的准确率比之高。这也是未来有待改进的地方。在所有ImageNet的1000类别上的Inception准确率和MS-SSIM均值。
4:模型没有过拟合
文中在训练完成后通过测量像素空间的L1
距离来寻找跟合成图像最相近的训练图像。下图为选出一些类别的合成图像跟与其最相似的训练图像,这也证实了模型不是通过记忆样本来合成图像的。
5:隐藏空间Laten Space
文中指出AC-GAN将其表现划分为了类别信息和与类别无关的隐藏表示z。下图上方三行展示了可以通过改变隐藏空间来改变合成的图像但是不改变其类别,也可以从中看得出中间是如何进行过渡的。图像下方三行每行的隐藏编码z都是一样的,各列都是一种不同类别的鸟儿。从每行都可以看出虽然鸟儿的类比不同,但是其整体布局(位置,布局,背景)都是很相近的,这也直观的说明了z来负责生成图像的全局结构,但是与类别无关。
6:合成图像质量跟划分类别数量的关系
文中指出数据的多样性会影响合成图像的质量,而训练数据的多样性主要指的就是类别,使用越少类别的数据则其整体的多样性也越低。文中使用了不同划分类别数量进行了模型训练及验证,结果如下:可以看出划分的数据中包含的类别越多则生成样本的MS-SSIM值越高,即生产样本的相似度越高多样性越低。文中最后选取了10为训练各组中包含的类别数量,从而得到了100个训练模型。
7:训练的超参数
文中最后分别给出了在ImageNet及CIFAR-10训练时的超参数,其中ImageNet的训练超参数如下所示:
训练结果图
可以看出随着训练的进行,生成图像的效果越来越好。
基于MNIST的Keras实现
from __future__ import print_function, division
from keras.datasets import mnist
from keras.layers import Input, Dense, Reshape, Flatten, Dropout, multiply
from keras.layers import BatchNormalization, Activation, Embedding, ZeroPadding2D
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.convolutional import UpSampling2D, Conv2D
from keras.models import Sequential, Model
from keras.optimizers import Adam
import matplotlib.pyplot as plt
import numpy as np
class ACGAN():
def __init__(self):
# Input shape
self.img_rows = 28
self.img_cols = 28
self.channels = 1
self.img_shape = (self.img_rows, self.img_cols, self.channels)
self.num_classes = 10
self.latent_dim = 100
optimizer = Adam(0.0002, 0.5)
# 可以通过关键字参数loss_weights或loss来为不同的输出设置不同的损失函数或权值。这两个参数均可为Python的列表或字典。这里我们给loss传递单个损失函数,这个损失函数会被应用于所有输出上
losses = ['binary_crossentropy', 'sparse_categorical_crossentropy']
# Build and compile the discriminator
self.discriminator = self.build_discriminator()
self.discriminator.compile(loss=losses,
optimizer=optimizer,
metrics=['accuracy'])
# Build the generator
self.generator = self.build_generator()
# The generator takes noise and the target label as input
# and generates the corresponding digit of that label
noise = Input(shape=(self.latent_dim,))
label = Input(shape=(1,))
img = self.generator([noise, label])
# For the combined model we will only train the generator
self.discriminator.trainable = False
# The discriminator takes generated image as input and determines validity
# and the label of that image
valid, target_label = self.discriminator(img)
# The combined model (stacked generator and discriminator)
# Trains the generator to fool the discriminator
self.combined = Model([noise, label], [valid, target_label])
self.combined.compile(loss=losses,
optimizer=optimizer)
def build_generator(self):
model = Sequential()
model.add(Dense(128 * 7 * 7, activation="relu", input_dim=self.latent_dim))
model.add(Reshape((7, 7, 128)))
model.add(BatchNormalization(momentum=0.8))
model.add(UpSampling2D())
model.add(Conv2D(128, kernel_size=3, padding="same"))
model.add(Activation("relu"))
model.add(BatchNormalization(momentum=0.8))
model.add(UpSampling2D())
model.add(Conv2D(64, kernel_size=3, padding="same"))
model.add(Activation("relu"))
model.add(BatchNormalization(momentum=0.8))
model.add(Conv2D(self.channels, kernel_size=3, padding='same'))
model.add(Activation("tanh"))
model.summary()
noise = Input(shape=(self.latent_dim,))
label = Input(shape=(1,), dtype='int32')
label_embedding = Flatten()(Embedding(self.num_classes, 100)(label))
model_input = multiply([noise, label_embedding])
img = model(model_input)
return Model([noise, label], img)
def build_discriminator(self):
model = Sequential()
model.add(Conv2D(16, kernel_size=3, strides=2, input_shape=self.img_shape, padding="same"))
model.add(LeakyReLU(alpha=0.2))
model.add(Dropout(0.25))
model.add(Conv2D(32, kernel_size=3, strides=2, padding="same"))
model.add(ZeroPadding2D(padding=((0,1),(0,1))))
model.add(LeakyReLU(alpha=0.2))
model.add(Dropout(0.25))
model.add(BatchNormalization(momentum=0.8))
model.add(Conv2D(64, kernel_size=3, strides=2, padding="same"))
model.add(LeakyReLU(alpha=0.2))
model.add(Dropout(0.25))
model.add(BatchNormalization(momentum=0.8))
model.add(Conv2D(128, kernel_size=3, strides=1, padding="same"))
model.add(LeakyReLU(alpha=0.2))
model.add(Dropout(0.25))
model.add(Flatten())
model.summary()
img = Input(shape=self.img_shape)
# Extract feature representation
features = model(img)
# Determine validity and label of the image
validity = Dense(1, activation="sigmoid")(features)
label = Dense(self.num_classes+1, activation="softmax")(features)
return Model(img, [validity, label])
def train(self, epochs, batch_size=128, sample_interval=50):
# Load the dataset
(X_train, y_train), (_, _) = mnist.load_data()
# Configure inputs
X_train = (X_train.astype(np.float32) - 127.5) / 127.5
X_train = np.expand_dims(X_train, axis=3)
y_train = y_train.reshape(-1, 1)
# Adversarial ground truths
valid = np.ones((batch_size, 1))
fake = np.zeros((batch_size, 1))
for epoch in range(epochs):
# ---------------------
# Train Discriminator
# ---------------------
# Select a random batch of images
idx = np.random.randint(0, X_train.shape[0], batch_size)
imgs = X_train[idx]
# Sample noise as generator input
noise = np.random.normal(0, 1, (batch_size, 100))
# The labels of the digits that the generator tries to create an
# image representation of
sampled_labels = np.random.randint(0, 10, (batch_size, 1))
# Generate a half batch of new images
gen_imgs = self.generator.predict([noise, sampled_labels])
# Image labels. 0-9 if image is valid or 10 if it is generated (fake)
img_labels = y_train[idx]
fake_labels = 10 * np.ones(img_labels.shape)
# Train the discriminator
d_loss_real = self.discriminator.train_on_batch(imgs, [valid, img_labels])
d_loss_fake = self.discriminator.train_on_batch(gen_imgs, [fake, fake_labels])
d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
# ---------------------
# Train Generator
# ---------------------
# Train the generator
g_loss = self.combined.train_on_batch([noise, sampled_labels], [valid, sampled_labels])
# Plot the progress
print ("%d [D loss: %f, acc.: %.2f%%, op_acc: %.2f%%] [G loss: %f]" % (epoch, d_loss[0], 100*d_loss[3], 100*d_loss[4], g_loss[0]))
# If at save interval => save generated image samples
if epoch % sample_interval == 0:
self.save_model()
self.sample_images(epoch)
def sample_images(self, epoch):
r, c = 10, 10
noise = np.random.normal(0, 1, (r * c, 100))
sampled_labels = np.array([num for _ in range(r) for num in range(c)])
gen_imgs = self.generator.predict([noise, sampled_labels])
# Rescale images 0 - 1
gen_imgs = 0.5 * gen_imgs + 0.5
fig, axs = plt.subplots(r, c)
cnt = 0
for i in range(r):
for j in range(c):
axs[i,j].imshow(gen_imgs[cnt,:,:,0], cmap='gray')
axs[i,j].axis('off')
cnt += 1
fig.savefig("images/%d.png" % epoch)
plt.close()
def save_model(self):
def save(model, model_name):
model_path = "saved_model/%s.json" % model_name
weights_path = "saved_model/%s_weights.hdf5" % model_name
options = {"file_arch": model_path,
"file_weight": weights_path}
json_string = model.to_json()
open(options['file_arch'], 'w').write(json_string)
model.save_weights(options['file_weight'])
save(self.generator, "generator")
save(self.discriminator, "discriminator")
if __name__ == '__main__':
acgan = ACGAN()
acgan.train(epochs=14000, batch_size=32, sample_interval=200)