Вот как я создал 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!
Если вы хотите узнать больше, посмотрите это…
Надеюсь, вам понравилось чтение. До следующего раза… Удачного обучения!