Добавить информацию о классе в сеть keras

Я пытаюсь выяснить, как я буду использовать информацию метки моего набора данных с генеративными состязательными сетями. Я пытаюсь использовать следующую реализацию условных GANs, которые можно посмотреть здесь. Мой набор данных содержит два разных домена изображений (реальные объекты и эскизы) с общей информацией о классе (стул, дерево, оранжевый и т. д.). Я выбрал эту реализацию, которая рассматривает только два разных домена как разные "классы" для соответствия (образцы поездов X соответствуют реальным изображениям в то время как целевые образцы y соответствуют эскизов изображений).

есть ли способ изменить мой код и принять во внимание информацию о классе (стул, дерево и т. д. во всей моей архитектуре? На самом деле я хочу, чтобы мой дискриминатор предсказал, принадлежат ли мои сгенерированные изображения из генератора к определенному классу, а не только являются ли они реальными или нет. Как это, с текущей архитектурой, система учится создавать похожие эскизы во всех случаях.

обновление: дискриминатор возвращает тензор размера 1x7x7 тогда как y_true и y_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)

Furthremore, моя модификация модели дискриминатора для выходного 1 слоя:

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

теперь дискриминатор выводит 1 слой. Как я могу соответствующим образом изменить вышеупомянутые функции потерь? Должен ли я иметь 7 вместо 1, для n_classes = 6 + один класс для прогнозирования реальных и поддельных пар?

2 ответов


Предлагаемое Решение

повторное использование кода репозиторий вы поделились, вот некоторые предлагаемые модификации для обучения классификатора вдоль вашего генератора и дискриминатора (их архитектуры и другие потери остаются нетронутыми):

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)

Теоретические Подробности

я считаю, что есть некоторые недоразумения относительно того, как условные Ганы работают и какова роль дискриминаторов в таких схемы.

роль дискриминатора

в игре min-max, которая является тренировкой GAN [4], дискриминатор D играет против генератора G (сеть, о которой вы действительно заботитесь), так что под С G становится лучше при выводе реалистичных результатов.

для этого D обучен отличать реальные образцы от образцов из G; while G обучен дурак D создавая реалистичные результаты / результаты после целевого распределения.

Примечание: В случае условных Ганов, т. е. Ганов, отображающих входной образец из одного домена A (например, реальное изображение) в другой домен B (например, эскиз), D обычно подается с парами уложенных образцов вместе и должен различать "реальные" пары (входная выборка из A + соответствующий целевой образец из B) и "поддельные" пары (входной образец от A + соответствующий выход из G) [1, 2]

обучение условного генератора против D (в отличие от простого обучения G самостоятельно, с потерей L1/L2 только например DAE) улучшает возможность забора G, заставляя его выводить четкие, реалистичные результаты вместо того, чтобы пытаться усреднить распределение.

несмотря на то, что дискриминаторы могут иметь несколько подсетей для решения других задач (см. Следующие параграфы), D следует хранить при хотя бы одна подсеть / выход для покрытия своей основной задачи: говорить реальные образцы от произведенных одних врозь. Прошу D регрессировать дальнейшую семантическую информацию (например, классы) может помешать этой основной цели.

Примечание: D вывод часто не является простым скалярным / булевым. Обычно используется дискриминатор (например, PatchGAN [1, 2]), возвращающий матрицу вероятности, оценивая, насколько реалистичные патчи сделаны из его ввода являются.


Условный Ганс

традиционные Ганы обучаются бесконтрольно генерировать реалистичные данные (например, изображения) из случайного вектора шума в качестве входных данных. [4]

как уже упоминалось ранее, условные Ганы имеют дальнейший ввод условия. Вдоль / вместо вектора шума они берут для ввода выборку из домена A и вернуть соответствующий образец из домена B. A может быть совершенно другой модальностью, например B = sketch image пока A = discrete label ; B = volumetric data пока A = RGB image, etc. [3]

такие Ганы также могут быть обусловлены кратными входами, например A = real image + discrete label пока B = sketch image. Известная работа, вводящая такие методы, -InfoGAN [5]. Он представляет, как обусловливать Ганы на нескольких непрерывных или дискретных входах (например,A = digit class + writing type, B = handwritten digit image), используя более продвинутый дискриминатор, который имеет для 2-й задачи force G в максимизации взаимной информации между кондиционер входы и соответствующие выходы.


максимизация взаимной информации для cGANs

InfoGAN дискриминатор имеет 2 головы / подсети, чтобы покрыть свои 2 задачи [5]:

  • одна голова D1 делает традиционное реальное / произведенное различение -- G должен минимизировать этот результат, т. е. он должен обмануть D1 так что он не может отличить реальную форму, сгенерированную данные;
  • еще одна глава D2 (также названный Q network) пытается регрессировать вход A информация -- G должен максимизировать этот результат, т. е. он должен выводить данные, которые "показывают" запрошенную семантическую информацию (c.f. взаимно-информационная максимизация между G условные входы и выходы).

вы можете найти реализацию Keras здесь, например: https://github.com/eriklindernoren/Keras-GAN/tree/master/infogan.

несколько работ используют аналогичные схемы для улучшения контроля над тем, что генерирует GAN, используя предоставленные метки и максимизируя взаимную информацию между этими входами и G выходы [6, 7]. Основная идея всегда одна и та же, хотя:

  • Железнодорожный G для генерации элементов домена B, учитывая некоторые входы домена(ов) A;
  • Железнодорожный D различать "реальные" / "поддельные" результаты -- G должен свести к минимуму это:
  • Железнодорожный Q (например, классификатор; может делиться слоями с D) оценить оригинал A входы B образцы -- G нужно увеличить это).

Подводя Итоги

в вашем случае, кажется, у вас есть следующие данные обучения:

  • реальные образы Ia
  • соответствующие изображения эскиза Ib
  • соответствующий класс метки c

и вы хотите обучить генератор G Так что, учитывая образ Ia и его метка класс c, он выводит правильное изображение эскиза Ib'.

в общем, у вас много информации, и вы можете контролировать свое обучение как на обусловленных изображениях, так и на обусловленных ярлыках... Вдохновение от вышеупомянутые методы [1, 2, 5, 6, 7], вот возможный путь использования всей этой информации, чтобы тренировать свой условный G:

сеть G:
  • входные данные: Ia + c
  • выход: Ib'
  • архитектура: up-to-you (например, U-Net, ResNet,...)
  • потери: L1 / L2 потери между Ib' & Ib, -D убытков, Q потеря
сеть D:
  • входные данные: Ia + Ib (реальная пара), Ia + Ib' (поддельные пара)
  • вывод:" фальшивость " скаляр / матрица
  • архитектура: up-to-you (например, PatchGAN)
  • потеря: кросс-энтропия по оценке "фальшивости"
сеть Q:
  • входные данные: Ib (реальный образец, за обучение Q), Ib' (поддельный образец, при обратном распространении через G)
  • выход: c' (оценочная класс)
  • архитектура: up-to-you (например, LeNet, ResNet, VGG, ...)
  • потеря: кросс-энтропия между c и c'
Обучающий Этап:
  1. Железнодорожный D на партии реальных пар Ia + Ib затем на партию поддельных пар Ia + Ib';
  2. Железнодорожный Q на партии реальных образцов Ib;
  3. исправить D и Q Весов;
  4. Железнодорожный G, передавала полученные результаты Ib' to D и Q чтобы распространять через них.

Примечание: это действительно грубое описание архитектуры. Я бы рекомендовал просмотреть литературу ([1, 5, 6, 7] как хорошее начало), чтобы получить подробнее и, возможно, более подробно решение.


ссылки

  1. Isola, Phillip, et al. "Перевод изображения в изображение с условно-состязательными сетями."препринт материалам arXiv (2017). http://openaccess.thecvf.com/content_cvpr_2017/papers/Isola_Image-To-Image_Translation_With_CVPR_2017_paper.pdf
  2. Zhu, Jun-Yan, et al. "Непарный перевод изображения в изображение с использованием циклически последовательных состязательных сетей."препринт материалам arXiv arXiv: 1703.10593 (2017). http://openaccess.thecvf.com/content_ICCV_2017/papers/Zhu_Unpaired_Image-To-Image_Translation_ICCV_2017_paper.pdf
  3. Мирза Мехди, и Симон Osindero. "Условные генеративные состязательные сети. препринт arXiv arXiv: 1411.1784 (2014). https://arxiv.org/pdf/1411.1784
  4. Гудфеллоу, Ян и др. - Генеративные состязательные сети.- Достижения в нейронных системах обработки информации. 2014. http://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf
  5. Chen, Xi, et al. "Infogan: интерпретируемое представление обучение с помощью информации, максимизирующей генеративные состязательные сети.- Достижения в нейронных системах обработки информации. 2016. http://papers.nips.cc/paper/6399-infogan-interpretable-representation-learning-by-information-maximizing-generative-adversarial-nets.pdf
  6. ли, Minhyeok, и Junhee сок. "Управляемая Генеративная Состязательная Сеть. препринт arXiv arXiv: 1708.00598 (2017). https://arxiv.org/pdf/1708.00598.pdf
  7. Одена, август, Кристофер Ола, и Shlens Джонатон. "Условный синтез изображений со вспомогательным классификатором gans. препринт arXiv arXiv: 1610.09585 (2016). http://proceedings.млр.press/v70/odena17a / odena17a.pdf

вы должны изменить свою модель дискриминатора, либо иметь два выхода, либо иметь выход" n_classes + 1".

предупреждение: я не вижу в определении вашего дискриминатора вывод "true/false", я вижу, что он выводит изображение...

где-то он должен содержать GlobalMaxPooling2D или GlobalAveragePooling2D.
В конце и один или несколько Dense слои для классификации.

если говорить true / false, последний плотный должен иметь 1 блок.
В противном случае n_classes + 1 единиц.

Итак, окончание вашего дискриминатора должно быть чем-то вроде

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

дискриминатор теперь выведет n_classes плюс либо знак " true / fake "(вы не сможете использовать" категориальный "там), либо даже" поддельный класс " (тогда вы обнуляете другие классы и используете категориальный)

ваши генерируемые эскизы должны быть переданы дискриминатору вместе с целью, которая будет конкатенацией фальшивый класс с другим классом.

Вариант 1-использование знака" true/fake". (Не используйте "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_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(....)