Вот как я создал GAN, который может генерировать английские алфавиты.

Во-первых, вам нужно знать, что такое GAN на самом деле. Ну вот краткое описание. Генеративная состязательная сеть - это комбинация двух моделей, а именно генератора и дискриминатора. Генератор пытается создать поддельные данные, имитирующие исходные данные. С другой стороны, Дискриминатор пытается определить, являются ли данные подлинными или поддельными. Благодаря состязательной настройке, в конечном итоге обе модели продолжают совершенствоваться в своих задачах. Конечно, о GAN нужно понимать гораздо больше. Если вам интересно, посмотрите это видео ...

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

Набор данных: рукописные алфавиты от А до Я

Здесь я использую набор данных рукописных английских алфавитов в стиле MNIST. Набор данных A – Z содержит 372 450 символов из 26 классов. Каждая выборка данных представляет собой изображение алфавита в оттенках серого. Как и в наборе данных MNIST, размер каждого изображения составляет 28 пикселей * 28 пикселей и представлен как 784 ( 28 * 28) размерный вектор. Давайте визуализируем некоторые из них ...

Первоначально значения пикселей находятся в диапазоне [0, 255], но мы должны нормализовать их перед подачей в любую модель машинного обучения. Обычно мы нормализуем пиксели между [0, 1] путем деления 255,0, но здесь мы нормализуем их между [-1, 1]. Это связано с тем, что позже мы будем использовать функцию tanh (диапазон tanh = [-1, 1]). .

Теперь давайте создадим нашу GAN. Мне нравится делать это за 4 шага.

1. Постройте генератор (G).

Генератор представляет собой нейронную сеть, которая принимает на вход вектор шума (100 -мерный) и выводит изображение одного английского алфавита. Поскольку мы работаем с данными изображения, имеет смысл использовать сверточную нейронную сеть. Идея состоит в том, чтобы увеличивать пространственные размеры ввода, когда он проходит через разные слои, пока не достигнет желаемой выходной формы (28px * 28px). Первые два уровня сети - это плотные слои с активацией ReLu. Я настоятельно рекомендую использовать BatchNormalization для вывода каждого слоя.

Примечание: BatchNormalization ускоряет сходимость обучения. Намного быстрее.

Обратите внимание, что первый плотный слой содержит 1024 нейронов, а второй - 6272 нейронов. После этого идет слой Reshape. Изменение формы важно, потому что мы хотим использовать свертку позже, а для применения свертки нам нужны матричные объекты, а не векторы столбцов / строк.

Примечание: чтобы найти правильные размеры, нам нужно подумать задом наперед! Сначала определите размеры матриц (7 * 7) и их количество (128), а затем умножьте их, чтобы получить размер (7 * 7 * 128 = 6272) плотного слоя.

Перед применением свертки мы повысим дискретизацию матриц. Я использовал (2, 2) повышающую дискретизацию, которая увеличила размерность с 7 * 7 до 14 * 14.

UpSampling - это своего рода обратная функция Pooling.

После этого у нас есть 2 * 2 фильтры свертки (64). Обратите внимание, что я инициализировал веса ядер в соответствии с нормальным распределением. Активация этого слоя - LeakyReLu. Опять же, у нас есть слой с повышающей дискретизацией, за которым следует сверточный слой. На этот раз слой UpSampling будет выводить размерные матрицы размером 28 * 28. Последний слой свертки содержит только фильтр 1, потому что нам нужен только один канал для нашего изображения в градациях серого. Здесь используется функция активации tanh. По этой причине мы нормализовали значения пикселей между [-1, 1].

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

Код:

Архитектура:

Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_1 (Dense)              (None, 1024)              103424    
_________________________________________________________________
batch_normalization_1 (Batch (None, 1024)              4096      
_________________________________________________________________
activation_1 (Activation)    (None, 1024)              0         
_________________________________________________________________
dense_2 (Dense)              (None, 6272)              6428800   
_________________________________________________________________
batch_normalization_2 (Batch (None, 6272)              25088     
_________________________________________________________________
activation_2 (Activation)    (None, 6272)              0         
_________________________________________________________________
reshape_1 (Reshape)          (None, 7, 7, 128)         0         
_________________________________________________________________
up_sampling2d_1 (UpSampling2 (None, 14, 14, 128)       0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 14, 14, 64)        32832     
_________________________________________________________________
batch_normalization_3 (Batch (None, 14, 14, 64)        256       
_________________________________________________________________
leaky_re_lu_1 (LeakyReLU)    (None, 14, 14, 64)        0         
_________________________________________________________________
up_sampling2d_2 (UpSampling2 (None, 28, 28, 64)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 28, 28, 1)         577       
=================================================================
Total params: 6,595,073
Trainable params: 6,580,353
Non-trainable params: 14,720
_________________________________________________________________

Вы заметили, что я не скомпилировал генератор здесь? Это будет сделано на 3-м шаге.

2. Создайте дискриминатор (D)

Наш дискриминатор - это просто двоичный классификатор, который принимает в качестве входных данных изображение в градациях серого и предсказывает, оригинальное ли это изображение или поддельное, то есть созданное генератором. Первые два слоя являются сверточными. Обратите внимание, что я использовал шаг 2, что означает, что выходной размер будет меньше входного. Итак, нам не нужны слои объединения. Размер фильтра составляет 5 * 5 для обоих слоев, но количество фильтров больше во втором слое.

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

После слоев свертки нам нужно сгладить результат, чтобы мы могли передать его на плотный слой. Размер плотного слоя составляет 256 с пропуском 50%. Наконец, у нас есть сигмовидный слой, как и любой другой двоичный классификатор. Теперь нам нужно скомпилировать дискриминатор. Потеря должна быть двоичной кросс-энтропией, и я использовал специальный оптимизатор Adam (скорость обучения = 0,0002).

Примечание. Скорость обучения Adam по умолчанию (0,001) слишком высока для GAN, поэтому всегда настраивайте оптимизатор Adam.

Код:

Архитектура:

Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_3 (Conv2D)            (None, 14, 14, 64)        1664      
_________________________________________________________________
leaky_re_lu_2 (LeakyReLU)    (None, 14, 14, 64)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 5, 5, 128)         204928    
_________________________________________________________________
leaky_re_lu_3 (LeakyReLU)    (None, 5, 5, 128)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 3200)              0         
_________________________________________________________________
dense_3 (Dense)              (None, 256)               819456    
_________________________________________________________________
leaky_re_lu_4 (LeakyReLU)    (None, 256)               0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 256)               0         
_________________________________________________________________
dense_4 (Dense)              (None, 1)                 257       
=================================================================
Total params: 1,026,305
Trainable params: 1,026,305
Non-trainable params: 0
_________________________________________________________________

3. Объедините G и D

Согласно оригинальной статье GAN, мы должны обучать генератор и дискриминатор отдельно. Тогда зачем этот шаг?

Дискриминатор может быть обучен напрямую путем обратного распространения потерь, вычисленных на последнем сигмовидном слое. Но для обучения генератора нам нужно отправить эту потерю обратно в генератор, не влияя на веса дискриминатора!

Один из способов добиться этого - создать новую модель, объединив генератор и дискриминатор. Вот почему я раньше не компилировал генератор. Назовем новую модель gan. Он принимает вектор шума в качестве входных данных, а затем передает его через генератор для создания поддельного изображения. Затем изображение проходит через дискриминатор, который вычисляет вероятность того, что это исходное изображение. Когда мы будем обучать этот ган, дискриминатор ничего не должен узнавать. Следовательно, ‘discinator.trainable = False’. Будут изменены только веса генератора.

Код:

Архитектура:

Model: "model_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 100)               0         
_________________________________________________________________
sequential_1 (Sequential)    (None, 28, 28, 1)         6595073   
_________________________________________________________________
sequential_2 (Sequential)    (None, 1)                 1026305   
=================================================================
Total params: 7,621,378
Trainable params: 6,580,353
Non-trainable params: 1,041,025
_________________________________________________________________

4. Поезд

Наконец-то мы готовы обучать наш GAN! Код вам кажется странным? Не волнуйтесь, я объясню каждый шаг.

Код:

Внешний предназначен для перехода по эпохам, а внутренний - для партий. Я обучил модели для 80 эпох, а batch_size равен 128. Итак, в одну эпоху у нас будет 2909 (steps_per_epoch = кол-во образцов данных / batch_size⌋ = ⌊372 450 / 128⌋ = 2909) шагов.

Тренируйте D, пока G зафиксировано:

Во-первых, количество векторов шума batch_size формируется путем случайного извлечения чисел из стандартного нормального распределения. Затем эти векторы передаются генератору для создания поддельных изображений. Теперь мы рисуем количество реальных изображений batch_size из обучающих данных. Чтобы получить входные данные для дискриминатора, нам нужно объединить поддельные и реальные данные. Соответственно, нам нужно упомянуть вектор метки (0: поддельные данные, 1: реальные данные). Но подождите, вместо этого в коде написано 0,1 и 0,9! WTH происходит?

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

Затем мы вызываем функцию train_on_batch для дискриминатора и передаем пары данных-меток.

Тренируйте D, пока G зафиксировано:

Здесь нам нужны только векторы шума и метки. Вектор метки содержит единицы. Подождите, генератор создает фальшивые данные, разве метки не должны быть 0?

да. Но здесь мы намеренно даем неправильные ярлыки, чтобы дискриминатор ошибался. Причина в том, что мы хотим, чтобы генератор превосходил дискриминатор. Сделав это, G будет знать, как ведет себя D, когда ему присвоены настоящие метки, и (G) изменит свои веса соответственно, чтобы обмануть D. Помните, что на этом этапе мы не меняем веса дискриминатора, поэтому дискриминатор не отучиться чему-либо.

Теперь мы вызываем функцию train_on_batch для генератора и передаем пары данных-меток. А вот друзья, как обучается GAN!

Позвольте мне показать вам некоторые из лучших (подобранных вручную) результатов моей модели ...

Вот полный код этого проекта. Вуаля! Теперь вы знаете, как обучать GAN!

Если вы хотите узнать больше, посмотрите это…

Надеюсь, вам понравилось чтение. До следующего раза… Удачного обучения!