Подробное объяснение и реализация InfoGAN (реализовано с помощью TensorFlow2.x)

искусственный интеллект глубокое обучение
Подробное объяснение и реализация InfoGAN (реализовано с помощью TensorFlow2.x)

«Это 17-й день моего участия в ноябрьском испытании обновлений. Подробную информацию об этом событии см.:Вызов последнего обновления 2021 г."

Принцип ИнфоГАН

Первоначальный GAN мог выдавать значимые выходные данные, но недостатком было то, что его свойства нельзя было контролировать. Например, генератор не может быть явно предложен для создания лица знаменитости, темноволосой, светлокожей, кареглазой и улыбающейся. Основная причина этого заключается в том, что используемый 100-мерный вектор шума включает в себя все существенные свойства выходного сигнала генератора.

Если исходный GAN можно изменить так, чтобы представление было разделено на объединенные и разделенные интерпретируемые векторы скрытого кодирования, то генератору можно указать, что синтезировать. Слияние и разделение кодировок можно выразить следующим образом:

合并编码与分离编码对比

GAN с разделенными представлениями также можно оптимизировать так же, как и обычные GAN. Выход генератора можно представить в виде:

G(z,c)=G(z)G(z,c)=G(z)

кодированиеz=(z,c)z = (z,c)содержит два элемента,zzпредставляет собой комбинированное представление,c=c1,c2,...,cLc=c_1,c_2,...,c_LПредставляет отдельное закодированное представление. Чтобы усилить разделение кодировок, InfoGAN предлагает функцию регуляризации для исходной функции потерь, которая объединяет скрытое кодированиеccиG(z,c)G(z,c)Максимально увеличить взаимную информацию между:

I(c;G(z,c))=IG(c;z)I(c;G(z,c))=IG(c;z)

Регулятор заставляет генератор учитывать скрытые кодировки. В области теории информации скрытое кодированиеccиG(z,c)G(z,c)Взаимная информация между ними определяется как:

I(G(c;z)=H(c)H(cG(z,c))I(G(c;z)=H(c)-H(c|G(z,c))

вH(c)H(c)это скрытый кодccэнтропия иH(cG(z,c))H(c|G(z,c))это выход генератораG(z,c)G(z,c)ЗаднийccУсловная энтропия . Максимизация взаимной информации означает, что при создании сгенерированного выводаH(cG(z,c))H(c|G(z,c))Минимизируйте или уменьшите неопределенность в базовом кодировании.

Но из-за предполагаемогоH(cG(z,c))H(c|G(z,c))Требуется апостериорное распределениеp(cG(z,c))=p(cx)p(c|G(z,c))=p(c|x), так что трудно оценитьH(cG(z,c))H(c|G(z,c)).

Обходной путь - использовать вспомогательный дистрибутивQ(cx)Q(c|x)Апостериорная вероятность оценивается для оценки нижней границы взаимной информации, которая оценивается как:

I(c;G(z,c))LI(G,Q)=Ecp(c),xG(z,c)[logQ(cx)]+H(c)I(c;G(z,c)) \ge L_I(G,Q)=E_{c \sim p(c),x \sim G(z,c)}[logQ(c|x)]+H(c)

В InfoGAN предполагается, чтоH(c)H(c)является константой. Следовательно, максимизация взаимной информации — это вопрос максимизации ожиданий. Генератор должен быть уверен, что он произвел выходные данные с определенными свойствами. Это желаемое максимальное значение равно нулю. Следовательно, максимальное значение нижней границы взаимной информации равноH(c)H(c). В InfoGAN дискретные скрытые кодыQ(cx)Q(c|x)может быть представлен softmax. Ожидается отрицательная потеря categorical_crossentropy в tf.keras.

Ожидается, что для непрерывного кодирования 1DccиxxДвойной интеграл из-за ожидания того, что выборки поступают как из распределения разделительного кодирования, так и из распределения генератора. Один из способов оценить ожидаемое значение — предположить, что выборка является хорошей мерой непрерывных данных. Таким образом, потери оцениваются какclogQ(cx)clogQ(c|x).

Чтобы завершить сеть InfoGAN, должен бытьlogQ(cx)logQ(c|x)реализация. Для простоты сеть Q является вспомогательной сетью, присоединенной к дискриминатору.

InfoGAN网络架构

Функция потери дискриминатора:

L(D)=ExpdatalogD(x)Ez,clog[1D(G(z,c))]λI(c;G(z,c))\mathcal L^{(D)} = -\mathbb E_{x\sim p_{data}}logD(x)-\mathbb E_{z,c}log[1 - D(G(z,c))] - \ лямбда I (с; G (г, с))

Функция потерь генератора:

L(G)=Ez,clogD(G(z,c))λI(c;G(z,c))\mathcal L^{(G)} = -\mathbb E_{z,c}logD(G(z,c))-\lambda I(c;G(z,c))

вλ\lambdaположительная константа

Реализация ИнфоГАН

Применительно к набору данных MNIST InfoGAN может изучать отдельные дискретные и непрерывные кодировки для изменения выходных свойств генератора. Например, подобно CGAN и ACGAN, дискретные кодировки в виде 10-мерных однократных меток будут использоваться для указания генерируемых чисел. Однако можно добавить два последовательных кода: один для управления углом наклона письма, а другой — для регулировки ширины штриха. Кодировки меньшего размера зарезервированы для представления всех остальных свойств:

MNIST数据集编码形式

Строитель

def generator(inputs,image_size,activation='sigmoid',labels=None,codes=None):

    image_resize = image_size // 4
    kernel_size = 5
    layer_filters = [128,64,32,1]
    inputs = [inputs,labels] + codes
    x = keras.layers.concatenate(inputs,axis=1)
    
    x = keras.layers.Dense(image_resize*image_resize*layer_filters[0])(x)
    x = keras.layers.Reshape((image_resize,image_resize,layer_filters[0]))(x)
    for filters in layer_filters:
        if filters > layer_filters[-2]:
            strides = 2
        else:
            strides = 1
        x = keras.layers.BatchNormalization()(x)
        x = keras.layers.Activation('relu')(x)
        x = keras.layers.Conv2DTranspose(filters=filters,
                kernel_size=kernel_size,
                strides=strides,
                padding='same')(x)
    if activation is not None:
        x = keras.layers.Activation(activation)(x)
    return keras.Model(inputs,x,name='generator')

Дискриминатор

def discriminator(inputs,activation='sigmoid',num_labels=None,num_codes=None):
    kernel_size = 5
    layer_filters = [32,64,128,256]
    x = inputs
    for filters in layer_filters:
        if filters == layer_filters[-1]:
            strides = 1
        else:
            strides = 2
        x = keras.layers.LeakyReLU(0.2)(x)
        x = keras.layers.Conv2D(filters=filters,
                kernel_size=kernel_size,
                strides=strides,
                padding='same')(x)
    x = keras.layers.Flatten()(x)
    outputs = keras.layers.Dense(1)(x)
    if activation is not None:
        print(activation)
        outputs = keras.layers.Activation(activation)(outputs)
    if num_labels:
        layer = keras.layers.Dense(layer_filters[-2])(x)
        labels = keras.layers.Dense(num_labels)(layer)
        labels = keras.layers.Activation('softmax',name='label')(labels)
        # 1-dim continous Q of 1st c given x
        code1 = keras.layers.Dense(1)(layer)
        code1 = keras.layers.Activation('sigmoid',name='code1')(code1)
        # 1-dim continous Q of 2nd c given x
        code2 = keras.layers.Dense(1)(layer)
        code2 = keras.layers.Activation('sigmoid',name='code2')(code2)
        outputs = [outputs,labels,code1,code2]
    return keras.Model(inputs,outputs,name='discriminator')

Построение модели

#mi_loss
def mi_loss(c,q_of_c_give_x):
    """mi_loss = -c * log(Q(c|x))
    """
    return K.mean(-K.sum(K.log(q_of_c_give_x + K.epsilon()) * c,axis=1))
    
def build_and_train_models(latent_size=100):
    """Load the dataset, build InfoGAN models,
    Call the InfoGAN train routine.
    """
    (x_train,y_train),_ = keras.datasets.mnist.load_data()
    image_size = x_train.shape[1]
    x_train = np.reshape(x_train,[-1,image_size,image_size,1])
    x_train = x_train.astype('float32') / 255.
    num_labels = len(np.unique(y_train))
    y_train = keras.utils.to_categorical(y_train)
    
    #超参数
    model_name = 'infogan_mnist'
    batch_size = 64
    train_steps = 40000
    lr = 2e-4
    decay = 6e-8
    input_shape = (image_size,image_size,1)
    label_shape = (num_labels,)
    code_shape = (1,)

    #discriminator model
    inputs = keras.layers.Input(shape=input_shape,name='discriminator_input')
    #discriminator with 4 outputs
    discriminator_model = discriminator(inputs,num_labels=num_labels,num_codes=2)
    optimizer = keras.optimizers.RMSprop(lr=lr,decay=decay)
    loss = ['binary_crossentropy','categorical_crossentropy',mi_loss,mi_loss]
    loss_weights = [1.0,1.0,0.5,0.5]
    discriminator_model.compile(loss=loss,
            loss_weights=loss_weights,
            optimizer=optimizer,
            metrics=['acc'])
    discriminator_model.summary()
    input_shape = (latent_size,)
    inputs = keras.layers.Input(shape=input_shape,name='z_input')
    labels = keras.layers.Input(shape=label_shape,name='labels')
    code1 = keras.layers.Input(shape=code_shape,name='code1')
    code2 = keras.layers.Input(shape=code_shape,name='code2')
    generator_model = generator(inputs,image_size,labels=labels,codes=[code1,code2])
    generator_model.summary()
    optimizer = keras.optimizers.RMSprop(lr=lr*0.5,decay=decay*0.5)
    discriminator_model.trainable = False
    inputs = [inputs,labels,code1,code2]
    adversarial_model = keras.Model(inputs,
            discriminator_model(generator_model(inputs)),
            name=model_name)
    adversarial_model.compile(loss=loss,loss_weights=loss_weights,
            optimizer=optimizer,
            metrics=['acc'])
    adversarial_model.summary()

    models = (generator_model,discriminator_model,adversarial_model)
    data = (x_train,y_train)
    params = (batch_size,latent_size,train_steps,num_labels,model_name)
    train(models,data,params)

обучение модели

def train(models,data,params):
    generator,discriminator,adversarial = models
    x_train,y_train = data
    batch_size,latent_size,train_steps,num_labels,model_name = params

    save_interval = 500
    code_std = 0.5
    noise_input = np.random.uniform(-1.0,1.,size=[16,latent_size])
    noise_label = np.eye(num_labels)[np.arange(0,16) % num_labels]
    noise_code1 = np.random.normal(scale=code_std,size=[16,1])
    noise_code2 = np.random.normal(scale=code_std,size=[16,1])
    train_size = x_train.shape[0]
    for i in range(train_steps):
        rand_indexes = np.random.randint(0,train_size,size=batch_size)
        real_images = x_train[rand_indexes]
        real_labels = y_train[rand_indexes]
        #random codes for real images
        real_code1 = np.random.normal(scale=code_std,size=[batch_size,1])
        real_code2 = np.random.normal(scale=code_std,size=[batch_size,1])
        #生成假图片,标签和编码
        noise = np.random.uniform(-1.,1.,size=[batch_size,latent_size])
        fake_labels = np.eye(num_labels)[np.random.choice(num_labels,batch_size)]
        fake_code1 = np.random.normal(scale=code_std,size=[batch_size,1])
        fake_code2 = np.random.normal(scale=code_std,size=[batch_size,1])
        inputs = [noise,fake_labels,fake_code1,fake_code2]
        fake_images = generator.predict(inputs)
        x = np.concatenate((real_images,fake_images))
        labels = np.concatenate((real_labels,fake_labels))
        codes1 = np.concatenate((real_code1,fake_code1))
        codes2 = np.concatenate((real_code2,fake_code2))
        y = np.ones([2 * batch_size,1])
        y[batch_size:,:] = 0
        #train discriminator network
        outputs = [y,labels,codes1,codes2]
        # metrics = ['loss', 'activation_1_loss', 'label_loss',
        # 'code1_loss', 'code2_loss', 'activation_1_acc',
        # 'label_acc', 'code1_acc', 'code2_acc']
        metrics = discriminator.train_on_batch(x, outputs)
        fmt = "%d: [dis: %f, bce: %f, ce: %f, mi: %f, mi:%f, acc: %f]"
        log = fmt % (i, metrics[0], metrics[1], metrics[2], metrics[3], metrics[4], metrics[6])
        #train the adversarial network
        noise = np.random.uniform(-1.,1.,size=[batch_size,latent_size])
        fake_labels = np.eye(num_labels)[np.random.choice(num_labels,batch_size)]
        fake_code1 = np.random.normal(scale=code_std,size=[batch_size,1])
        fake_code2 = np.random.normal(scale=code_std,size=[batch_size,1])
        y = np.ones([batch_size,1])
        inputs = [noise,fake_labels,fake_code1,fake_code2]
        outputs = [y,fake_labels,fake_code1,fake_code2]
        metrics = adversarial.train_on_batch(inputs,outputs)

Показать результаты

steps = 500

steps = 500

steps = 16000

steps = 16000

修改书写角度的分离编码

修改书写角度的分离编码