Реализован код для увеличения разрешения изображений 25x25 в 4 раза.

Предыдущие знания

  • Нейронные сети
  • Python
  • Керас (лучше иметь)

Генеративные состязательные сети (GAN)

GAN - это технология в области нейронных сетей, разработанная Яном Гудфеллоу и его друзьями. SRGAN - это метод, с помощью которого мы можем увеличить разрешение любого изображения.

Он состоит из двух основных частей: Генератор и Дискриминатор. Генератор производит уточненные выходные данные из заданного входного шума. Дискриминатор получает два типа данных: один - это данные реального мира, а другой - сгенерированный вывод из генератора. Для дискриминатора реальные данные имеют метку «1», а сгенерированные данные - метку «0». Мы можем провести аналогию с генератором как художником и дискриминатором как критиком. Художники создают вид искусства, который оценивается критиком.

По мере того, как генератор улучшается с обучением, производительность дискриминатора ухудшается, потому что дискриминатор не может легко отличить реальное от фальшивого. Теоретически дискриминатор наконец-то будет иметь точность 50%, как подбрасывание монеты.

Наш девиз - снизить точность оценки людей, которые нас оценивают, и сосредоточиться на наших произведениях искусства.

Структура SRGAN

Альтернативное обучение

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

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

Набор данных

Набор данных, который я использовал для обучения моей модели, находится здесь.

Код

Импортируйте необходимые зависимости

import numpy as np
from keras import Model
from keras.layers import Conv2D, PReLU,BatchNormalization, Flatten
from keras.layers import UpSampling2D, LeakyReLU, Dense, Input, add

Некоторые из необходимых переменных

lr_ip = Input(shape=(25,25,3))
hr_ip = Input(shape=(100,100,3))
train_lr,train_hr = #training images arrays normalized between 0 & 1
test_lr, test_hr = # testing images arrays normalized between 0 & 1

Определить генератор

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

# Residual block
def res_block(ip):
    
    res_model = Conv2D(64, (3,3), padding = "same")(ip)
    res_model = BatchNormalization(momentum = 0.5)(res_model)
    res_model = PReLU(shared_axes = [1,2])(res_model)
    
    res_model = Conv2D(64, (3,3), padding = "same")(res_model)
    res_model = BatchNormalization(momentum = 0.5)(res_model)
    
    return add([ip,res_model])
# Upscale the image 2x
def upscale_block(ip):
    
    up_model = Conv2D(256, (3,3), padding="same")(ip)
    up_model = UpSampling2D( size = 2 )(up_model)
    up_model = PReLU(shared_axes=[1,2])(up_model)
    
    return up_model
num_res_block = 16
# Generator Model
def create_gen(gen_ip):
    layers = Conv2D(64, (9,9), padding="same")(gen_ip)
    layers = PReLU(shared_axes=[1,2])(layers)
    temp = layers
    for i in range(num_res_block):
        layers = res_block(layers)
    layers = Conv2D(64, (3,3), padding="same")(layers)
    layers = BatchNormalization(momentum=0.5)(layers)
    layers = add([layers,temp])
    layers = upscale_block(layers)
    layers = upscale_block(layers)
    op = Conv2D(3, (9,9), padding="same")(layers)
    return Model(inputs=gen_ip, outputs=op)

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

Этот блок кода определяет структуру модели дискриминатора и все уровни, участвующие в различении реального и сгенерированного изображения. По мере углубления после каждых 2 слоев количество фильтров увеличивается вдвое.

# Small block inside the discriminator
def discriminator_block(ip, filters, strides=1, bn=True):
    
    disc_model = Conv2D(filters, (3,3), strides, padding="same")(ip)
    disc_model = LeakyReLU( alpha=0.2 )(disc_model)
    if bn:
        disc_model = BatchNormalization( momentum=0.8 )(disc_model)
    return disc_model
# Discriminator Model
def create_disc(disc_ip):
    df = 64
    
    d1 = discriminator_block(disc_ip, df, bn=False)
    d2 = discriminator_block(d1, df, strides=2)
    d3 = discriminator_block(d2, df*2)
    d4 = discriminator_block(d3, df*2, strides=2)
    d5 = discriminator_block(d4, df*4)
    d6 = discriminator_block(d5, df*4, strides=2)
    d7 = discriminator_block(d6, df*8)
    d8 = discriminator_block(d7, df*8, strides=2)
    
    d8_5 = Flatten()(d8)
    d9 = Dense(df*16)(d8_5)
    d10 = LeakyReLU(alpha=0.2)(d9)
    validity = Dense(1, activation='sigmoid')(d10)
    return Model(disc_ip, validity)

Модель VGG19

В этом блоке кода мы используем модель VGG19, обученную с помощью базы данных image-net, для извлечения функций, эта модель замораживается позже, чтобы параметры не обновлялись.

from keras.applications import VGG19
# Build the VGG19 model upto 10th layer 
# Used to extract the features of high res imgaes
def build_vgg():
    vgg = VGG19(weights="imagenet")
    vgg.outputs = [vgg.layers[9].output]
    img = Input(shape=hr_shape)
    img_features = vgg(img)
    return Model(img, img_features)

Комбинированная модель

Теперь мы присоединяем модель генератора и дискриминатора. Модель, полученная в результате этого, используется только для обучения модели генератора. При обучении этой комбинированной модели мы должны «заморозить» дискриминатор в каждую эпоху.

# Attach the generator and discriminator
def create_comb(gen_model, disc_model, vgg, lr_ip, hr_ip):
    gen_img = gen_model(lr_ip)
    gen_features = vgg(gen_img)
    disc_model.trainable = False
    validity = disc_model(gen_img)
    return Model([lr_ip, hr_ip],[validity,gen_features])

Объявить модели

Затем мы объявляем модели генератора, дискриминатора и vgg. Эта модель будет использоваться в качестве аргументов для комбинированной модели.

Любые изменения меньших моделей внутри объединенной модели также влияют на модель снаружи. Например: обновление веса, замораживание модели и т. Д.

generator = create_gen(lr_ip)
discriminator = create_disc(hr_ip)
discriminator.compile(loss="binary_crossentropy", optimizer="adam",      
  metrics=['accuracy'])
vgg = build_vgg()
vgg.trainable = False
gan_model = create_comb(generator, discriminator, vgg, lr_ip, hr_ip)
gan_model.compile(loss=["binary_crossentropy","mse"], loss_weights=
  [1e-3, 1], optimizer="adam")

Сэмплируйте обучающие данные в небольшие партии

Поскольку обучающий набор слишком велик, нам нужно разбивать изображения на небольшие партии, чтобы избежать ошибки исчерпания ресурсов. Ресурса типа ОЗУ не хватит для обучения всех изображений сразу.

batch_size = 20
train_lr_batches = []
train_hr_batches = []
for it in range(int(train_hr.shape[0] / batch_size)):
    start_idx = it * batch_size
    end_idx = start_idx + batch_size
    train_hr_batches.append(train_hr[start_idx:end_idx])
    train_lr_batches.append(train_lr[start_idx:end_idx])
train_lr_batches = np.array(train_lr_batches)
train_hr_batches = np.array(train_hr_batches)

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

Этот блок является ядром всей программы. Здесь мы обучаем дискриминатор и генератор переменному методу, как указано выше. На данный момент дискриминатор заморожен, не забудьте разморозить до и заморозить после обучения дискриминатора, что приведено в приведенном ниже коде.

epochs = 100
for e in range(epochs):
    gen_label = np.zeros((batch_size, 1))
    real_label = np.ones((batch_size,1))
    g_losses = []
    d_losses = []
    for b in range(len(train_hr_batches)):
        lr_imgs = train_lr_batches[b]
        hr_imgs = train_hr_batches[b]
        gen_imgs = generator.predict_on_batch(lr_imgs)
        #Dont forget to make the discriminator trainable
        discriminator.trainable = True
        
        #Train the discriminator
        d_loss_gen = discriminator.train_on_batch(gen_imgs,
          gen_label)
        d_loss_real = discriminator.train_on_batch(hr_imgs,
          real_label)
        discriminator.trainable = False
        d_loss = 0.5 * np.add(d_loss_gen, d_loss_real)
        image_features = vgg.predict(hr_imgs)
        
        #Train the generator
        g_loss, _, _ = gan_model.train_on_batch([lr_imgs, hr_imgs], 
          [real_label, image_features])
        
        d_losses.append(d_loss)
        g_losses.append(g_loss)
    g_losses = np.array(g_losses)
    d_losses = np.array(d_losses)
    
    g_loss = np.sum(g_losses, axis=0) / len(g_losses)
    d_loss = np.sum(d_losses, axis=0) / len(d_losses)
    print("epoch:", e+1 ,"g_loss:", g_loss, "d_loss:", d_loss)

Оцените модель

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

label = np.ones((len(test_lr),1))
test_features = vgg.predict(test_hr)
eval,_,_ = gan_model.evaluate([test_lr, test_hr], [label,test_features])

Предсказать результат

Мы можем генерировать изображения с высоким разрешением с помощью модели генератора.

test_prediction = generator.predict_on_batch(test_lr)

Результат просто потрясающий ...

Вы можете найти мою реализацию, обученную на google colab.

подсказки

  • Всегда помните, какую модель сделать обучаемой или нет.
  • При обучении генератора используйте значение метки как единое целое.
  • Лучше использовать изображения размером больше 25x25, поскольку они содержат больше деталей для сгенерированных изображений.
  • Не забудьте нормализовать набор данных numpy между 0 и 1.

использованная литература

Джейсон Браунли. 2019. Генеративные состязательные сети с Python

Бумага на СРГАН