Добавить информацию о классе в сеть 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'
- Железнодорожный
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(....)