Deep learning / CNN and computer vision
Generative Adversarial Networks in Python:
Contents
- Introduction to Generative Adversarial Networks (GANs)
- The Generator Network
- The Discriminator Network
- GAN Training Process
- Loss Functions in GANs
- Challenges in Training GANs
- Conditional GANs
- Wasserstein GAN (WGAN)
- Progressive Growing of GANs
- CycleGAN for Image-to-Image Translation
- StyleGAN for High-Quality Image Generation
- Evaluation Metrics for GANs
- Applications of GANs
- Advanced GAN Architectures
- Response:
- Advanced GAN Architectures
- Ethical Considerations and Future Directions
- Additional Resources
Introduction to Generative Adversarial Networks (GANs)
Generative Adversarial Networks are a class of deep learning models consisting of two neural networks that compete against each other. The generator network creates synthetic data, while the discriminator network attempts to distinguish between real and fake data. This adversarial process leads to the generation of highly realistic synthetic data.
import tensorflow as tf
from tensorflow.keras.layers import Dense, Reshape, Conv2D, Conv2DTranspose
from tensorflow.keras.models import Sequential
# Simple GAN architecture
def build_generator():
model = Sequential([
Dense(7*7*256, use_bias=False, input_shape=(100,)),
Reshape((7, 7, 256)),
Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False),
Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False),
Conv2D(1, (5, 5), activation='tanh', padding='same')
])
return model
def build_discriminator():
model = Sequential([
Conv2D(64, (5, 5), strides=(2, 2), padding='same', input_shape=[28, 28, 1]),
Conv2D(128, (5, 5), strides=(2, 2), padding='same'),
Dense(1)
])
return model
The Generator Network
The generator network takes random noise as input and produces synthetic data. Its goal is to create data that is indistinguishable from real data. The generator learns to map from a latent space to the data distribution of interest.
import numpy as np
import matplotlib.pyplot as plt
def generate_random_noise(batch_size, latent_dim):
return np.random.normal(0, 1, (batch_size, latent_dim))
generator = build_generator()
noise = generate_random_noise(1, 100)
generated_image = generator(noise, training=False)
plt.imshow(generated_image[0, :, :, 0], cmap='gray')
plt.axis('off')
plt.show()
The Discriminator Network
The discriminator network acts as a binary classifier, attempting to distinguish between real and generated data. It takes both real and fake data as input and outputs a probability indicating whether the input is real or fake.
discriminator = build_discriminator()
def discriminator_loss(real_output, fake_output):
real_loss = tf.keras.losses.BinaryCrossentropy(from_logits=True)(tf.ones_like(real_output), real_output)
fake_loss = tf.keras.losses.BinaryCrossentropy(from_logits=True)(tf.zeros_like(fake_output), fake_output)
total_loss = real_loss + fake_loss
return total_loss
GAN Training Process
The GAN training process involves alternating between training the discriminator and the generator. The discriminator is trained to correctly classify real and fake data, while the generator is trained to produce data that fools the discriminator.
@tf.function
def train_step(images):
noise = generate_random_noise(batch_size, latent_dim)
with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
generated_images = generator(noise, training=True)
real_output = discriminator(images, training=True)
fake_output = discriminator(generated_images, training=True)
gen_loss = generator_loss(fake_output)
disc_loss = discriminator_loss(real_output, fake_output)
gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)
gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)
generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))
discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))
Loss Functions in GANs
GANs use specialized loss functions for both the generator and discriminator. The generator aims to minimize the probability that the discriminator correctly identifies generated samples, while the discriminator tries to maximize its accuracy in distinguishing real and fake data.
def generator_loss(fake_output):
return tf.keras.losses.BinaryCrossentropy(from_logits=True)(tf.ones_like(fake_output), fake_output)
def discriminator_loss(real_output, fake_output):
real_loss = tf.keras.losses.BinaryCrossentropy(from_logits=True)(tf.ones_like(real_output), real_output)
fake_loss = tf.keras.losses.BinaryCrossentropy(from_logits=True)(tf.zeros_like(fake_output), fake_output)
total_loss = real_loss + fake_loss
return total_loss
Challenges in Training GANs
Training GANs can be challenging due to issues like mode collapse, where the generator produces limited varieties of outputs, and vanishing gradients. Careful tuning of hyperparameters and network architectures is often necessary to achieve stable training.
# Example of a technique to help stabilize GAN training: label smoothing
def smooth_positive_labels(y):
return y - 0.3 + (np.random.random(y.shape) * 0.5)
def smooth_negative_labels(y):
return y + np.random.random(y.shape) * 0.3
# Usage in discriminator training
real_labels = smooth_positive_labels(np.ones((batch_size, 1)))
fake_labels = smooth_negative_labels(np.zeros((batch_size, 1)))
Conditional GANs
Conditional GANs (cGANs) allow for the generation of data with specific attributes by providing additional information to both the generator and discriminator. This enables more controlled generation of synthetic data.
def build_conditional_generator(latent_dim, num_classes):
noise_input = tf.keras.Input(shape=(latent_dim,))
label_input = tf.keras.Input(shape=(1,))
label_embedding = tf.keras.layers.Embedding(num_classes, 50)(label_input)
label_embedding = tf.keras.layers.Flatten()(label_embedding)
combined_input = tf.keras.layers.Concatenate()([noise_input, label_embedding])
x = Dense(7*7*256, use_bias=False)(combined_input)
x = Reshape((7, 7, 256))(x)
x = Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False)(x)
x = Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False)(x)
output = Conv2D(1, (5, 5), activation='tanh', padding='same')(x)
return tf.keras.Model([noise_input, label_input], output)
Wasserstein GAN (WGAN)
Wasserstein GAN is a variant that uses the Wasserstein distance as a measure of the difference between the real and generated data distributions. This approach can lead to more stable training and better quality results.
def wasserstein_loss(y_true, y_pred):
return tf.reduce_mean(y_true * y_pred)
def build_critic():
model = Sequential([
Conv2D(64, (5, 5), strides=(2, 2), padding='same', input_shape=[28, 28, 1]),
tf.keras.layers.LeakyReLU(),
Conv2D(128, (5, 5), strides=(2, 2), padding='same'),
tf.keras.layers.LeakyReLU(),
Dense(1)
])
return model
critic = build_critic()
critic_optimizer = tf.keras.optimizers.RMSprop(lr=0.00005)
Progressive Growing of GANs
Progressive Growing of GANs is a technique where the generator and discriminator start with low-resolution images and gradually increase the resolution during training. This approach can lead to more stable training and higher quality results, especially for high-resolution image generation.
def build_progressive_generator(latent_dim, target_resolution):
model = Sequential()
model.add(Dense(4*4*512, use_bias=False, input_shape=(latent_dim,)))
model.add(Reshape((4, 4, 512)))
current_resolution = 4
while current_resolution < target_resolution:
model.add(Conv2DTranspose(512//2, (3, 3), strides=(2, 2), padding='same', use_bias=False))
model.add(tf.keras.layers.LeakyReLU())
current_resolution *= 2
model.add(Conv2D(3, (3, 3), activation='tanh', padding='same'))
return model
progressive_generator = build_progressive_generator(100, 128)
CycleGAN for Image-to-Image Translation
CycleGAN is a type of GAN used for unpaired image-to-image translation. It learns to translate an image from one domain to another without paired examples, using cycle consistency loss.
def build_generator_resnet():
input_layer = tf.keras.layers.Input(shape=(256, 256, 3))
x = tf.keras.layers.Conv2D(64, 7, strides=1, padding='same')(input_layer)
x = tf.keras.layers.BatchNormalization()(x)
x = tf.keras.layers.ReLU()(x)
# Downsampling
x = tf.keras.layers.Conv2D(128, 3, strides=2, padding='same')(x)
x = tf.keras.layers.BatchNormalization()(x)
x = tf.keras.layers.ReLU()(x)
# Residual blocks
for _ in range(9):
residual = x
x = tf.keras.layers.Conv2D(128, 3, strides=1, padding='same')(x)
x = tf.keras.layers.BatchNormalization()(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.Conv2D(128, 3, strides=1, padding='same')(x)
x = tf.keras.layers.BatchNormalization()(x)
x = tf.keras.layers.Add()([x, residual])
# Upsampling
x = tf.keras.layers.Conv2DTranspose(64, 3, strides=2, padding='same')(x)
x = tf.keras.layers.BatchNormalization()(x)
x = tf.keras.layers.ReLU()(x)
x = tf.keras.layers.Conv2D(3, 7, strides=1, padding='same', activation='tanh')(x)
return tf.keras.Model(inputs=input_layer, outputs=x)
generator_G = build_generator_resnet()
generator_F = build_generator_resnet()
StyleGAN for High-Quality Image Generation
StyleGAN is an advanced GAN architecture that introduces a style-based generator, allowing for fine-grained control over the generated images and producing state-of-the-art results in terms of image quality and diversity.
def build_mapping_network(latent_dim, dlatent_dim, mapping_layers=8):
inputs = tf.keras.Input(shape=(latent_dim,))
x = inputs
for _ in range(mapping_layers):
x = Dense(dlatent_dim, activation='leaky_relu')(x)
return tf.keras.Model(inputs, x)
def build_synthesis_network(dlatent_dim, resolution=1024):
dlatents_in = tf.keras.Input(shape=[dlatent_dim])
x = Dense(512 * 4 * 4, activation='leaky_relu')(dlatents_in)
x = Reshape((4, 4, 512))(x)
resolution_log2 = int(np.log2(resolution))
for i in range(2, resolution_log2 + 1):
x = Conv2DTranspose(512 // 2**(i-1), 3, strides=2, padding='same')(x)
x = tf.keras.layers.LeakyReLU()(x)
x = Conv2D(3, 1, activation='tanh')(x)
return tf.keras.Model(dlatents_in, x)
latent_dim = 512
dlatent_dim = 512
mapping_network = build_mapping_network(latent_dim, dlatent_dim)
synthesis_network = build_synthesis_network(dlatent_dim)
Evaluation Metrics for GANs
Evaluating GANs can be challenging due to the lack of a single objective function. Common metrics include Inception Score (IS), Fréchet Inception Distance (FID), and human evaluation. These metrics help assess the quality and diversity of generated samples.
from tensorflow.keras.applications.inception_v3 import InceptionV3
import numpy as np
def calculate_fid(real_images, generated_images):
inception_model = InceptionV3(include_top=False, pooling='avg', input_shape=(299, 299, 3))
def preprocess_images(images):
images = tf.image.resize(images, (299, 299))
return tf.keras.applications.inception_v3.preprocess_input(images)
real_features = inception_model.predict(preprocess_images(real_images))
generated_features = inception_model.predict(preprocess_images(generated_images))
mu1, sigma1 = real_features.mean(axis=0), np.cov(real_features, rowvar=False)
mu2, sigma2 = generated_features.mean(axis=0), np.cov(generated_features, rowvar=False)
diff = mu1 - mu2
covmean = np.sqrt(sigma1.dot(sigma2))
fid = np.sum(diff**2) + np.trace(sigma1 + sigma2 - 2*covmean)
return fid
Applications of GANs
GANs have found numerous applications across various domains, including image generation, style transfer, text-to-image synthesis, and even in scientific fields like drug discovery. Their ability to generate realistic synthetic data makes them valuable in scenarios where data is scarce or privacy is a concern.
# Example: Using a pre-trained StyleGAN2 model for face generation
import dnnlib
import dnnlib.tflib as tflib
import pickle
def load_stylegan2(model_path):
with dnnlib.util.open_url(model_path) as f:
_G, _D, Gs = pickle.load(f)
return Gs
def generate_faces(Gs, num_samples=1):
latents = np.random.randn(num_samples, Gs.input_shape[1])
images = Gs.run(latents, None, truncation_psi=0.7, randomize_noise=True, output_transform=dict(func=tflib.convert_images_to_uint8, nchw_to_nhwc=True))
return images
# Usage:
# Gs = load_stylegan2('path/to/stylegan2-ffhq-config-f.pkl')
# generated_faces = generate_faces(Gs, num_samples=4)
Advanced GAN Architectures
Recent advancements in GAN architectures have led to improved performance and capabilities. Some notable examples include BigGAN for high
Response:
Advanced GAN Architectures
Recent advancements in GAN architectures have led to improved performance and capabilities. Some notable examples include BigGAN for high-fidelity image generation, Self-Attention GAN (SAGAN) for long-range dependency modeling, and StyleGAN3 for alias-free image synthesis. These architectures incorporate novel techniques to address challenges in training stability, output quality, and control over generated content.
# Example: Self-Attention module for GANs
class SelfAttention(tf.keras.layers.Layer):
def __init__(self, channels):
super(SelfAttention, self).__init__()
self.channels = channels
self.query_conv = tf.keras.layers.Conv2D(channels // 8, 1)
self.key_conv = tf.keras.layers.Conv2D(channels // 8, 1)
self.value_conv = tf.keras.layers.Conv2D(channels, 1)
self.gamma = self.add_weight(name='gamma', shape=[1], initializer='zeros', trainable=True)
def call(self, x):
batch_size, height, width, channels = x.shape
query = self.query_conv(x)
key = self.key_conv(x)
value = self.value_conv(x)
query = tf.reshape(query, [-1, height * width, self.channels // 8])
key = tf.reshape(key, [-1, height * width, self.channels // 8])
value = tf.reshape(value, [-1, height * width, self.channels])
attention = tf.matmul(query, key, transpose_b=True)
attention = tf.nn.softmax(attention)
out = tf.matmul(attention, value)
out = tf.reshape(out, [-1, height, width, self.channels])
return self.gamma * out + x
# Usage in a generator or discriminator
x = Conv2D(64, 3, padding='same')(input_layer)
x = SelfAttention(64)(x)
Ethical Considerations and Future Directions
As GANs become more powerful, ethical considerations surrounding their use become increasingly important. Issues such as deepfakes, potential misuse for misinformation, and privacy concerns need to be addressed. Future research directions include improving GAN stability, enhancing interpretability, and developing robust defense mechanisms against malicious use of GANs.
# Example: Simple watermarking technique for generated images
def add_watermark(image, watermark_text):
from PIL import Image, ImageDraw, ImageFont
pil_image = Image.fromarray((image * 255).astype(np.uint8))
draw = ImageDraw.Draw(pil_image)
font = ImageFont.truetype("arial.ttf", 20)
text_width, text_height = draw.textsize(watermark_text, font)
x = pil_image.width - text_width - 10
y = pil_image.height - text_height - 10
draw.text((x, y), watermark_text, font=font, fill=(255, 255, 255, 128))
return np.array(pil_image) / 255.0
# Usage
generated_image = generator(noise, training=False)
watermarked_image = add_watermark(generated_image[0], "AI Generated")
Additional Resources
For those interested in diving deeper into GANs, here are some valuable resources:
- "Generative Adversarial Networks" by Ian Goodfellow et al. (2014) ArXiv: https://arxiv.org/abs/1406.2661
- "Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks" by Radford et al. (2015) ArXiv: https://arxiv.org/abs/1511.06434
- "Improved Techniques for Training GANs" by Salimans et al. (2016) ArXiv: https://arxiv.org/abs/1606.03498
- "Progressive Growing of GANs for Improved Quality, Stability, and Variation" by Karras et al. (2017) ArXiv: https://arxiv.org/abs/1710.10196
- "A Style-Based Generator Architecture for Generative Adversarial Networks" by Karras et al. (2018) ArXiv: https://arxiv.org/abs/1812.04948
These papers provide a solid foundation for understanding GANs and their evolution over time.