【问题标题】:Add class information to keras network向keras网络添加类信息
【发布时间】:2018-11-27 06:32:24
【问题描述】:

我试图弄清楚如何将我的数据集的标签信息与生成对抗网络一起使用。我正在尝试使用can be found here 的以下条件 GAN 实现。我的数据集包含两个不同的图像域(真实对象和草图),具有共同的类信息(椅子、树、橙色等)。我选择了这种实现,它只将两个不同的域视为对应的不同“类”(训练样本 X 对应于真实图像,而目标样本 y 对应于草图图像)。

有没有办法修改我的代码并在我的整个架构中考虑类信息(椅子、树等)?实际上,我希望我的鉴别器预测我从生成器生成的图像是否属于特定类别,而不仅仅是它们是否真实。事实上,使用当前架构,系统学习在所有情况下创建类似的草图。

更新:判别器返回大小为1x7x7 的张量,然后y_truey_pred 在计算损失之前都通过一个展平层:

def discriminator_loss(y_true, y_pred):
     BATCH_SIZE=100
     return K.mean(K.binary_crossentropy(K.flatten(y_pred), K.concatenate([K.ones_like(K.flatten(y_pred[:BATCH_SIZE,:,:,:])),K.zeros_like(K.flatten(y_pred[:BATCH_SIZE,:,:,:])) ]) ), axis=-1)

以及鉴别器对生成器的损失函数:

def discriminator_on_generator_loss(y_true,y_pred):
     BATCH_SIZE=100
     return K.mean(K.binary_crossentropy(K.flatten(y_pred), K.ones_like(K.flatten(y_pred))), axis=-1)

此外,我对输出 1 层的判别器模型的修改:

model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
#model.add(Activation('sigmoid'))

现在判别器输出 1 层。如何相应修改上述损失函数?对于n_classes = 6 + 一个用于预测真假配对的类,我应该使用 7 而不是 1?

【问题讨论】:

  • 对不起,我在最后 2-3 行中丢失了你。您希望您的代码应该包含有关架构和类的详细信息吗?这是什么意思?
  • 代码使用目标域(草图图像)作为目标信息,并使用真实的样本作为输入。损失函数是生成的草图图像与数据集中的图像之间的 L1 距离。鉴别器是否有可能也预测类别,而不仅仅是生成的图像是否真实。
  • 您的生成器生成什么?草图?判别器的输入是什么?假 x 真草图?我几乎完全迷失在你的解释中。
  • 你想如何使用这些类?干什么用的?
  • 生成器生成草图。我想要类信息的原因是将它用于鉴别器损失函数,这样做我希望生成器能够更好地学习为每个类生成草图。我也希望通过这样做也许我会提高我的结果的质量。

标签: python keras conv-neural-network loss-function generative-adversarial-network


【解决方案1】:

建议的解决方案

重用来自repository you shared 的代码,这里有一些建议的修改,以沿着你的生成器和鉴别器训练分类器(它们的架构和其他损失保持不变):

from keras import backend as K
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation, Flatten
from keras.layers.convolutional import Convolution2D, MaxPooling2D

def lenet_classifier_model(nb_classes):
    # Snipped by Fabien Tanc - https://www.kaggle.com/ftence/keras-cnn-inspired-by-lenet-5
    # Replace with your favorite classifier...
    model = Sequential()
    model.add(Convolution2D(12, 5, 5, activation='relu', input_shape=in_shape, init='he_normal'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Convolution2D(25, 5, 5, activation='relu', init='he_normal'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Flatten())
    model.add(Dense(180, activation='relu', init='he_normal'))
    model.add(Dropout(0.5))
    model.add(Dense(100, activation='relu', init='he_normal'))
    model.add(Dropout(0.5))
    model.add(Dense(nb_classes, activation='softmax', init='he_normal'))

def generator_containing_discriminator_and_classifier(generator, discriminator, classifier):
    inputs = Input((IN_CH, img_cols, img_rows))
    x_generator = generator(inputs)

    merged = merge([inputs, x_generator], mode='concat', concat_axis=1)
    discriminator.trainable = False
    x_discriminator = discriminator(merged)

    classifier.trainable = False
    x_classifier = classifier(x_generator)

    model = Model(input=inputs, output=[x_generator, x_discriminator, x_classifier])

    return model


def train(BATCH_SIZE):
    (X_train, Y_train, LABEL_train) = get_data('train')  # replace with your data here
    X_train = (X_train.astype(np.float32) - 127.5) / 127.5
    Y_train = (Y_train.astype(np.float32) - 127.5) / 127.5
    discriminator = discriminator_model()
    generator = generator_model()
    classifier = lenet_classifier_model(6)
    generator.summary()
    discriminator_and_classifier_on_generator = generator_containing_discriminator_and_classifier(
        generator, discriminator, classifier)
    d_optim = Adagrad(lr=0.005)
    g_optim = Adagrad(lr=0.005)
    generator.compile(loss='mse', optimizer="rmsprop")
    discriminator_and_classifier_on_generator.compile(
        loss=[generator_l1_loss, discriminator_on_generator_loss, "categorical_crossentropy"],
        optimizer="rmsprop")
    discriminator.trainable = True
    discriminator.compile(loss=discriminator_loss, optimizer="rmsprop")
    classifier.trainable = True
    classifier.compile(loss="categorical_crossentropy", optimizer="rmsprop")

    for epoch in range(100):
        print("Epoch is", epoch)
        print("Number of batches", int(X_train.shape[0] / BATCH_SIZE))
        for index in range(int(X_train.shape[0] / BATCH_SIZE)):
            image_batch = Y_train[index * BATCH_SIZE:(index + 1) * BATCH_SIZE]
            label_batch = LABEL_train[index * BATCH_SIZE:(index + 1) * BATCH_SIZE]  # replace with your data here

            generated_images = generator.predict(X_train[index * BATCH_SIZE:(index + 1) * BATCH_SIZE])
            if index % 20 == 0:
                image = combine_images(generated_images)
                image = image * 127.5 + 127.5
                image = np.swapaxes(image, 0, 2)
                cv2.imwrite(str(epoch) + "_" + str(index) + ".png", image)
                # Image.fromarray(image.astype(np.uint8)).save(str(epoch)+"_"+str(index)+".png")

            # Training D:
            real_pairs = np.concatenate((X_train[index * BATCH_SIZE:(index + 1) * BATCH_SIZE, :, :, :], image_batch),
                                        axis=1)
            fake_pairs = np.concatenate(
                (X_train[index * BATCH_SIZE:(index + 1) * BATCH_SIZE, :, :, :], generated_images), axis=1)
            X = np.concatenate((real_pairs, fake_pairs))
            y = np.zeros((20, 1, 64, 64))  # [1] * BATCH_SIZE + [0] * BATCH_SIZE
            d_loss = discriminator.train_on_batch(X, y)
            print("batch %d d_loss : %f" % (index, d_loss))
            discriminator.trainable = False

            # Training C:
            c_loss = classifier.train_on_batch(image_batch, label_batch)
            print("batch %d c_loss : %f" % (index, c_loss))
            classifier.trainable = False

            # Train G:
            g_loss = discriminator_and_classifier_on_generator.train_on_batch(
                X_train[index * BATCH_SIZE:(index + 1) * BATCH_SIZE, :, :, :], 
                [image_batch, np.ones((10, 1, 64, 64)), label_batch])
            discriminator.trainable = True
            classifier.trainable = True
            print("batch %d g_loss : %f" % (index, g_loss[1]))
            if index % 20 == 0:
                generator.save_weights('generator', True)
                discriminator.save_weights('discriminator', True)

理论细节

我认为对于条件 GAN 的工作原理以及鉴别器在此类方案中的作用存在一些误解。

鉴别器的作用

在 GAN 训练 [4] 的 min-max 游戏中,判别器 D 与生成器 G(您真正关心的网络)对战,因此在 D 的审查下,@987654334 @ 更擅长输出真实的结果。

为此,D 被训练来区分来自G 的样本中的真实样本;而G 被训练来愚弄D,方法是根据目标分布生成真实的结果/结果。

注意:在条件 GAN 的情况下,即 GAN 将输入样本从一个域 A(例如真实图片)映射到另一个域 B (例如草图),D 通常由堆叠的样本对馈送 在一起并且必须区分“真实”对(来自A的输入样本+ 来自B的相应目标样本)和“假”对(输入样本 来自A + 来自G 的相应输出) [1, 2]

针对D 训练条件生成器(而不是简单地单独训练G,仅使用 L1/L2 损失,例如 DAE)提高了G 的采样能力,迫使其输出清晰、逼真的结果试图平均分布。

即使鉴别器可以有多个子网络来覆盖其他任务(见下一段),D 应该至少保留一个子网络/输出来覆盖其主要任务:从生成的样本中分辨出真实样本分开。要求D 回归更多语义信息(例如类)可能会干扰这个主要目的。

注意:D 输出通常不是简单的标量/布尔值。通常有一个判别器(例如 PatchGAN [1, 2])返回一个矩阵 概率,评估从其输入中生成的补丁的真实性 是。


条件 GANs

传统的 GAN 以无监督方式进行训练,以从作为输入的随机噪声向量生成真实数据(例如图像)。 [4]

如前所述,条件 GAN 具有进一步的输入条件。沿着/而不是噪声向量,它们将来自域A 的样本作为输入,并从域B 返回相应的样本。 A 可以是完全不同的方式,例如B = sketch imageA = discrete label ; B = volumetric dataA = RGB image 等[3]

这样的 GAN 也可以通过多个输入来调节,例如A = real image + discrete labelB = sketch imageInfoGAN [5] 是介绍此类方法的著名作品。它介绍了如何在多个连续或离散输入(例如 A = digit class + writing typeB = handwritten digit image)上调节 GAN,使用更高级的鉴别器,该鉴别器在第二个任务中强制 G 最大化其调节之间的互信息输入及其对应的输出


最大化 cGAN 的互信息

InfoGAN 判别器有 2 个头/子网络来覆盖其 2 个任务 [5]:

  • 一个头 D1 进行传统的真实/生成的区分 -- G 必须最小化这个结果,即它必须欺骗 D1 以便它无法区分真实形式的生成数据;
  • 另一个头D2(也称为Q网络)试图回归输入A信息——G必须最大化这个结果,即它必须输出“显示”请求的语义信息的数据(参见G 条件输入与其输出之间的互信息最大化)。

您可以在此处找到 Keras 实现,例如:https://github.com/eriklindernoren/Keras-GAN/tree/master/infogan

一些工作正在使用类似的方案来改进对 GAN 生成内容的控制,方法是使用提供的标签并最大化这些输入和G 输出之间的互信息 [6, 7]。基本思想总是一样的:

  • 在给定域A 的一些输入的情况下,训练G 以生成域B 的元素;
  • 训练 D 区分“真实”/“假”结果 -- G 必须尽量减少这种情况;
  • 训练Q(例如分类器;可以与D 共享层)以估计来自B 样本的原始A 输入——G 必须最大化这一点。

总结

在您的情况下,您似乎有以下训练数据:

  • 实图Ia
  • 对应的素描图片Ib
  • 对应的类标签c

并且您想训练一个生成器G,以便给定图像Ia 及其类标签c,它会输出正确的草图图像Ib'

总而言之,您拥有大量信息,并且您可以监督您对条件图像和条件标签的训练...... 受上述方法 [1, 2, 5, 6, 7] 的启发,这是一种使用所有这些信息来训练您的条件 G 的可能方法:

网络G:
  • 输入:Ia + c
  • 输出:Ib'
  • 架构:由您决定(例如 U-Net、ResNet...)
  • 损失:Ib' & Ib-D 损失、Q 损失之间的 L1/L2 损失
网络D:
  • 输入:Ia + Ib(真对),Ia + Ib'(假对)
  • 输出:“假”标量/矩阵
  • 架构:由您决定(例如 PatchGAN)
  • 损失:“虚假”估计的交叉熵
网络Q:
  • 输入:Ib(真实样本,用于训练 Q),Ib'(假样本,通过 G 反向传播时)
  • 输出:c'(估计类)
  • 架构:由您决定(例如 LeNet、ResNet、VGG 等)
  • 损失:cc' 之间的交叉熵
训练阶段:
  1. 训练 D 对一批真实对 Ia + Ib 然后对一批假对 Ia + Ib';
  2. 在一批真实样本上训练Q Ib;
  3. 修复 DQ 权重;
  4. 训练 G,将其生成的输出 Ib' 传递给 DQ 以通过它们进行反向传播。

注意:这是一个非常粗略的架构描述。我建议您阅读文献([1, 5, 6, 7] 作为一个好的开始)以获得 更多细节,或许还有更详尽的解决方案。


参考文献

  1. 伊索拉、菲利普等人。 “使用条件对抗网络进行图像到图像的翻译。” arXiv 预印本(2017 年)。 http://openaccess.thecvf.com/content_cvpr_2017/papers/Isola_Image-To-Image_Translation_With_CVPR_2017_paper.pdf
  2. 朱军燕等人。 “使用循环一致的对抗网络进行未配对的图像到图像转换。” arXiv 预印本 arXiv:1703.10593 (2017)。 http://openaccess.thecvf.com/content_ICCV_2017/papers/Zhu_Unpaired_Image-To-Image_Translation_ICCV_2017_paper.pdf
  3. 米尔扎、迈赫迪和西蒙·奥辛德罗。 “有条件的生成对抗网络。” arXiv 预印本 arXiv:1411.1784 (2014)。 https://arxiv.org/pdf/1411.1784
  4. Goodfellow、Ian 等人。 “生成对抗网络。”神经信息处理系统的进展。 2014.http://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf
  5. 陈曦等人。 “Infogan:通过信息最大化生成对抗网络的可解释表示学习。”神经信息处理系统的进展。 2016.http://papers.nips.cc/paper/6399-infogan-interpretable-representation-learning-by-information-maximizing-generative-adversarial-nets.pdf
  6. Lee、Minhyeok 和 Junhee Seok。 “可控生成对抗网络”。 arXiv 预印本 arXiv:1708.00598 (2017)。 https://arxiv.org/pdf/1708.00598.pdf
  7. Odena、Augustus、Christopher Olah 和 Jonathon Shlens。 “使用辅助分类器甘斯的条件图像合成。” arXiv 预印本 arXiv:1610.09585 (2016)。 http://proceedings.mlr.press/v70/odena17a/odena17a.pdf

【讨论】:

  • 所以你只需添加另一个分类器并在其他模型之上使用它的损失并使用该模型的错误来修改其他模型?
  • 作为一个简单的方案,是的。它只是在您的生成器模型之上,并且与鉴别器并行。
  • 嗯,好吧,那么 i 就像一个额外的鉴别器(比如说),鉴别器和分类器都会为生成器提供一些错误。如果我弄错了,请纠正我?
  • 我将使用 generator_ contains_discriminator_and_classifier 而不是 discriminator_on_generator,对吧?
  • 通过检查 infoGan 的代码,我注意到他们没有任何判别器模型,他们只是有一个生成器模型,并且在它之上还有:build_disk_and_q_net。
【解决方案2】:

您应该修改判别器模型,使其具有两个输出,或者具有“n_classes + 1”输出。

警告:我在您的鉴别器的定义中没有看到它输出“真/假”,我看到它输出图像...

它应该包含GlobalMaxPooling2DGlobalAveragePooling2D
最后和一个或多个Dense层进行分类。

如果判断真/假,最后一个 Dense 应该有 1 个单位。
否则n_classes + 1 单位。

所以,你的鉴别器的结尾应该是这样的

...GlobalMaxPooling2D()...
...Dense(someHidden,...)...
...Dense(n_classes+1,...)...

鉴别器现在将输出n_classes 加上“真/假”符号(您将无法在此处使用“分类”)甚至“假类”(然后将其他类归零并使用分类)

您生成的草图应该与一个目标一起传递给鉴别器,该目标将是假类与另一个类的连接。

选项 1 - 使用“真/假”标志。 (不要使用“categorical_crossentropy”)

#true sketches into discriminator:
fakeClass = np.zeros((total_samples,))
sketchClass = originalClasses

targetClassTrue = np.concatenate([fakeClass,sketchClass], axis=-1)

#fake sketches into discriminator:
fakeClass = np.ones((total_fake_sketches))
sketchClass = originalClasses

targetClassFake = np.concatenate([fakeClass,sketchClass], axis=-1)

选项 2 - 使用“假类”(可以使用“categorical_crossentropy”):

#true sketches into discriminator:
fakeClass = np.zeros((total_samples,))
sketchClass = originalClasses

targetClassTrue = np.concatenate([fakeClass,sketchClass], axis=-1)

#fake sketches into discriminator:
fakeClass = np.ones((total_fake_sketches))
sketchClass = np.zeros((total_fake_sketches, n_classes))

targetClassFake = np.concatenate([fakeClass,sketchClass], axis=-1)

现在将所有内容连接到一个目标数组中(相对于输入草图)

更新训练方法

对于这种训练方法,您的损失函数应该是以下之一:

  • discriminator.compile(loss='binary_crossentropy', optimizer=....)
  • discriminator.compile(loss='categorical_crossentropy', optimizer=...)

代码:

for epoch in range(100):
    print("Epoch is", epoch)
    print("Number of batches", int(X_train.shape[0]/BATCH_SIZE))

    for index in range(int(X_train.shape[0]/BATCH_SIZE)):

        #names:
            #images -> initial images, not changed    
            #sketches -> generated + true sketches    
            #classes -> your classification for the images    
            #isGenerated -> the output of your discriminator telling whether the passed sketches are fake

        batchSlice = slice(index*BATCH_SIZE,(index+1)*BATCH_SIZE)
        trueImages = X_train[batchSlice]

        trueSketches = Y_train[batchSlice] 
        trueClasses = originalClasses[batchSlice]
        trueIsGenerated = np.zeros((len(trueImages),)) #discriminator telling whether the sketch is fake or true (generated images = 1)
        trueEndTargets = np.concatenate([trueIsGenerated,trueClasses],axis=1)

        fakeSketches = generator.predict(trueImages)
        fakeClasses = originalClasses[batchSlize]             #if option 1 -> telling class + isGenerated - use "binary_crossentropy"
        fakeClasses = np.zeros((len(fakeSketches),n_classes)) #if option 2 -> telling if generated is an individual class - use "categorical_crossentropy"    
        fakeIsGenerated = np.ones((len(fakeSketches),))
        fakeEndTargets = np.concatenate([fakeIsGenerated, fakeClasses], axis=1)

        allSketches = np.concatenate([trueSketches,fakeSketches],axis=0)            
        allEndTargets = np.concatenate([trueEndTargets,fakeEndTargets],axis=0)

        d_loss = discriminator.train_on_batch(allSketches, allEndTargets)

        pred_temp = discriminator.predict(allSketches)
        #print(np.shape(pred_temp))
        print("batch %d d_loss : %f" % (index, d_loss))

        ##WARNING## In previous keras versions, "trainable" only takes effect if you compile the models. 
            #you should have the "discriminator" and the "discriminator_on_generator" with these set at the creation of the models and never change it again   

        discriminator.trainable = False
        g_loss = discriminator_on_generator.train_on_batch(trueImages, trueEndTargets)
        discriminator.trainable = True


        print("batch %d g_loss : %f" % (index, g_loss[1]))
        if index % 20 == 0:
            generator.save_weights('generator', True)
            discriminator.save_weights('discriminator', True)

正确编译模型

当你创建“discriminator”和“discriminator_on_generator”时:

discriminator.trainable = True
for l in discriminator.layers:
    l.trainable = True


discriminator.compile(.....)

for l in discriminator_on_generator.layer[firstDiscriminatorLayer:]:
    l.trainable = False

discriminator_on_generator.compile(....)

【讨论】:

  • 鉴别器输出一个大小为 1x7x7 的张量,然后在鉴别器损失函数中将其展平,就像我帖子中更新的示例一样。
  • 如果它是一个真/假分类器,为什么不让它预测类呢?
  • 在我看来是一样的。它不是在模型中执行展平,而是在损失函数的计算中执行。
  • 我个人认为使用 DCGAN 鉴别器D 来回归类标签并不是一个好主意。当D 接收输入的真实/草图图像对时,D 很可能只依赖真实的、固定的图像进行分类,而忽略目标/生成的草图,因此不会改善两者之间的互信息输入和生成的输出。正如我在回答中初步解释的那样,我将使用第三个网络 Q 仅针对此任务训练/应用于草图(而不是对)。
  • 任何丢失都可以,详情见答案。每个选项都会使用不同的损失,但在 keras 现有损失中。
猜你喜欢
  • 2011-01-02
  • 1970-01-01
  • 2019-01-30
  • 2016-11-06
  • 1970-01-01
  • 2019-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多