Добавить информацию о классе в сеть 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(также названныйQnetwork) пытается регрессировать вход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'
- Железнодорожный
Dна партии реальных парIa+Ibзатем на партию поддельных парIa+Ib'; - Железнодорожный
Qна партии реальных образцовIb; - исправить
DиQВесов; - Железнодорожный
G, передавала полученные результатыIb'toDиQчтобы распространять через них.
Примечание: это действительно грубое описание архитектуры. Я бы рекомендовал просмотреть литературу ([1, 5, 6, 7] как хорошее начало), чтобы получить подробнее и, возможно, более подробно решение.
ссылки
- Isola, Phillip, et al. "Перевод изображения в изображение с условно-состязательными сетями."препринт материалам arXiv (2017). http://openaccess.thecvf.com/content_cvpr_2017/papers/Isola_Image-To-Image_Translation_With_CVPR_2017_paper.pdf
- 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
- Мирза Мехди, и Симон Osindero. "Условные генеративные состязательные сети. препринт arXiv arXiv: 1411.1784 (2014). https://arxiv.org/pdf/1411.1784
- Гудфеллоу, Ян и др. - Генеративные состязательные сети.- Достижения в нейронных системах обработки информации. 2014. http://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf
- Chen, Xi, et al. "Infogan: интерпретируемое представление обучение с помощью информации, максимизирующей генеративные состязательные сети.- Достижения в нейронных системах обработки информации. 2016. http://papers.nips.cc/paper/6399-infogan-interpretable-representation-learning-by-information-maximizing-generative-adversarial-nets.pdf
- ли, Minhyeok, и Junhee сок. "Управляемая Генеративная Состязательная Сеть. препринт arXiv arXiv: 1708.00598 (2017). https://arxiv.org/pdf/1708.00598.pdf
- Одена, август, Кристофер Ола, и 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(....)