Menu iconMenu iconGenerative Deep Learning Updated Edition
Generative Deep Learning Updated Edition

Chapter 4: Project Face Generation with GANs

4.6 Enhancing Face Generation with StyleGAN

StyleGAN represents a significant advancement in the field of Generative Adversarial Networks (GANs), providing a novel architecture that enables fine-grained control over the generated images. This section will delve into the intricacies of StyleGAN, explaining its architecture, the style-based generator, and how to use it for generating high-quality face images. We will also include example code to help you implement and experiment with StyleGAN.

4.6.1 Introduction to StyleGAN

StyleGAN, introduced by NVIDIA researchers, enhances traditional GANs by incorporating a style-based generator architecture. This architecture allows for the separation of high-level attributes (styles) from low-level details, enabling more precise control over the generation process. StyleGAN uses adaptive instance normalization (AdaIN) to inject styles at different layers of the generator, resulting in images with a wide range of variations and fine-grained features.

Key Features of StyleGAN:

  • Style-based Generator: Modulates styles at various layers of the generator to control specific aspects of the image.
  • Progressive Growing: Trains the model by progressively increasing the resolution of generated images, improving stability and quality.
  • Noise Injection: Adds stochastic variation to the images by injecting noise at multiple layers, enhancing the diversity of generated samples.

4.6.2 Style-based Generator Architecture

The generator in StyleGAN consists of a mapping network and a synthesis network. The mapping network transforms the input latent vector into an intermediate latent space, which is then used by the synthesis network to generate images.

Mapping Network:

  • Consists of several fully connected layers.
  • Maps the input latent vector \( z \) to an intermediate latent space \( w \).

Synthesis Network:

  • Uses the intermediate latent vector \( w \) to control the styles at each layer.
  • Employs adaptive instance normalization (AdaIN) to modulate the feature maps based on the styles.
  • Adds noise to the feature maps at various layers to introduce stochastic variation.

Example: Style-based Generator Code

import tensorflow as tf
from tensorflow.keras.layers import Dense, Reshape, Conv2D, Conv2DTranspose, Input, LeakyReLU, Layer
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.initializers import RandomNormal

# Custom layer for Adaptive Instance Normalization (AdaIN)
class AdaIN(Layer):
    def __init__(self):
        super(AdaIN, self).__init__()

    def call(self, inputs):
        content, style = inputs
        mean, variance = tf.nn.moments(content, axes=[1, 2], keepdims=True)
        std_dev = tf.sqrt(variance + 1e-8)
        norm_content = (content - mean) / std_dev

        style_mean, style_std_dev = tf.nn.moments(style, axes=[1, 2], keepdims=True)
        return norm_content * style_std_dev + style_mean

# Mapping network to map latent vector z to intermediate latent space w
def build_mapping_network(latent_dim):
    model = Sequential()
    for _ in range(8):
        model.add(Dense(latent_dim, activation='relu'))
    return model

# Synthesis network to generate images
def build_synthesis_network(img_shape, latent_dim):
    init = RandomNormal(mean=0.0, stddev=1.0)
    inputs = Input(shape=(latent_dim,))
    x = Dense(4 * 4 * 512, activation='relu', kernel_initializer=init)(inputs)
    x = Reshape((4, 4, 512))(x)

    for filters in [512, 256, 128, 64]:
        x = Conv2DTranspose(filters, kernel_size=4, strides=2, padding='same', kernel_initializer=init)(x)
        style_input = Input(shape=(latent_dim,))
        x = AdaIN()([x, style_input])
        x = LeakyReLU(alpha=0.2)(x)
        x = Conv2D(filters, kernel_size=3, padding='same', kernel_initializer=init)(x)
        x = AdaIN()([x, style_input])
        x = LeakyReLU(alpha=0.2)(x)

    outputs = Conv2D(3, kernel_size=3, padding='same', activation='tanh')(x)
    return Model(inputs=[inputs, style_input], outputs=outputs)

# Build StyleGAN generator
latent_dim = 100
mapping_network = build_mapping_network(latent_dim)
synthesis_network = build_synthesis_network((64, 64, 3), latent_dim)
mapping_network.summary()
synthesis_network.summary()

In this example:

  1. Import required libraries and modules.
  2. Define a custom TensorFlow layer (AdaIN or Adaptive Instance Normalization) which normalizes the content features according to the style features.
  3. Define a mapping network. This is a part of the StyleGAN architecture that transforms the input latent vector to an intermediate latent space.
  4. Define a synthesis network. This takes the output of the mapping network and generates the final image. The synthesis network uses Conv2DTranspose layers for upscaling, with the AdaIN layer used to apply style features at each scale.
  5. Build the StyleGAN generator by creating instances of the mapping and synthesis networks and summarizing their architectures.

4.6.3 Training StyleGAN

Training StyleGAN involves two phases: pre-training the mapping network and then training the entire StyleGAN model using progressive growing. Progressive growing starts with low-resolution images and gradually increases the resolution, stabilizing the training process and improving the quality of the generated images.

Example: Progressive Growing Training Loop

Here’s how you can implement the progressive growing training loop:

# Define progressive growing parameters
initial_resolution = 4
final_resolution = 64
num_steps_per_resolution = 10000
batch_size = 64

# Function to train at a given resolution
def train_at_resolution(generator, discriminator, gan, resolution, steps):
    for step in range(steps):
        # Generate real and fake images
        idx = np.random.randint(0, train_images.shape[0], batch_size)
        real_images = train_images[idx]
        noise = np.random.normal(0, 1, (batch_size, latent_dim))
        fake_images = generator.predict([noise, noise])

        # Train discriminator
        d_loss_real = discriminator.train_on_batch(real_images, np.ones((batch_size, 1)))
        d_loss_fake = discriminator.train_on_batch(fake_images, np.zeros((batch_size, 1)))
        d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

        # Train generator
        noise = np.random.normal(0, 1, (batch_size, latent_dim))
        g_loss = gan.train_on_batch([noise, noise], np.ones((batch_size, 1)))

        # Print progress
        if step % 100 == 0:
            print(f"Resolution {resolution}x{resolution}, Step {step}/{steps}, D loss: {d_loss[0]}, G loss: {g_loss}")

        # Save models periodically
        if step % 1000 == 0:
            generator.save(f'generator_res_{resolution}_step_{step}.h5')
            discriminator.save(f'discriminator_res_{resolution}_step_{step}.h5')

# Initialize and compile models
generator = build_synthesis_network((initial_resolution, initial_resolution, 3), latent_dim)
discriminator = build_discriminator((initial_resolution, initial_resolution, 3))
gan_input = Input(shape=(latent_dim,))
gan_output = discriminator(generator([gan_input, gan_input]))
gan = Model(gan_input, gan_output)
generator.compile(optimizer='adam', loss='binary_crossentropy')
discriminator.compile(optimizer='adam', loss='binary_crossentropy')
gan.compile(optimizer='adam', loss='binary_crossentropy')

# Train progressively
for resolution in [4, 8, 16, 32, 64]:
    train_at_resolution(generator, discriminator, gan, resolution, num_steps_per_resolution)

The code defines parameters for the progressive growing of the GAN, such as the initial and final resolutions and the number of training steps per resolution. It also defines a function for training the GAN at a given resolution.

This function loops over a specified number of steps, generating batches of real and fake images, training the discriminator on these images, and then training the generator. Performance metrics are printed out periodically, and the models are saved at certain intervals.

The GAN models are then initialized and compiled, and the training function is called for each resolution in a specified range, allowing the network to progressively grow as it trains.

4.6.4 Fine-Tuning and Evaluating StyleGAN

After training StyleGAN, it’s essential to fine-tune and evaluate the model to ensure high-quality image generation. This involves:

  • Fine-Tuning: Adjust hyperparameters, add additional training steps, and use techniques like learning rate annealing to refine the model.
  • Evaluation: Use qualitative and quantitative methods such as visual inspection, Inception Score (IS), and Fréchet Inception Distance (FID) to assess the quality of generated images.

Example: Fine-Tuning Code

# Adjust learning rate and recompile models for fine-tuning
fine_tune_learning_rate = 0.0001
generator.compile(optimizer=tf.keras.optimizers.Adam(fine_tune_learning_rate), loss='binary_crossentropy')
discriminator.compile(optimizer=tf.keras.optimizers.Adam(fine_tune_learning_rate), loss='binary_crossentropy')
gan.compile(optimizer=tf.keras.optimizers.Adam(fine_tune_learning_rate), loss='binary_crossentropy')

# Fine-tuning loop
fine_tune_steps = 5000
train_at_resolution(generator, discriminator, gan, final_resolution, fine_tune_steps)

First, a new learning rate for fine-tuning is defined, and the models of the generator, discriminator, and GAN are recompiled with this learning rate using the Adam optimizer and binary cross-entropy loss function.

Then, a specific number of fine-tuning steps are defined, and the GAN model is trained at a specified resolution for this number of steps.

4.6.5 Generating and Evaluating Final Images

Once the model is fine-tuned, generate final images and evaluate their quality.

Example: Generating Final Images

# Generate final images using the fine-tuned model
def generate_final_images(generator, latent_dim, n_samples=10):
    noise = np.random.normal(0, 1, (n_samples, latent_dim))
    generated_images = generator.predict([noise, noise])
    generated_images = (generated_images * 127.5 + 127.5).astype(np.uint8)  # Rescale to [0, 255]

    plt.figure(figsize=(20, 2))
    for i in range(n_samples):
        plt.subplot(1, n_samples, i + 1)
        plt.imshow(generated_images[i])
        plt.axis('off')
    plt.show()

# Generate and plot final images
generate_final_images(generator, latent_dim, n_samples=10)

This function is used to generate and plot final images using a generator model that has been finely tuned. The function takes three arguments: the generator model, the latent dimension size, and the number of samples to generate (which defaults to 10 if not specified).

It first generates a random noise input for the generator model using a normal distribution. The generated images are then predicted by the generator model. These generated images are rescaled to fall within the range of [0, 255] to match the typical range of pixel values in an image.

A plot is then created with a subplot for each generated image. The images are plotted without axes. Finally, the plot is displayed.

Enhancing face generation with StyleGAN provides significant improvements in the quality and control of generated images. By leveraging the style-based generator architecture, progressive growing training, and fine-tuning techniques, you can achieve high-quality, diverse, and realistic face images. Evaluating the model using both qualitative and quantitative methods ensures that the generated images meet the desired standards.

As you continue exploring the capabilities of StyleGAN, you can experiment with different styles, resolutions, and fine-tuning strategies to further enhance your generative modeling projects. This powerful technique opens up new possibilities for creative applications, research, and practical implementations in various domains.

4.6 Enhancing Face Generation with StyleGAN

StyleGAN represents a significant advancement in the field of Generative Adversarial Networks (GANs), providing a novel architecture that enables fine-grained control over the generated images. This section will delve into the intricacies of StyleGAN, explaining its architecture, the style-based generator, and how to use it for generating high-quality face images. We will also include example code to help you implement and experiment with StyleGAN.

4.6.1 Introduction to StyleGAN

StyleGAN, introduced by NVIDIA researchers, enhances traditional GANs by incorporating a style-based generator architecture. This architecture allows for the separation of high-level attributes (styles) from low-level details, enabling more precise control over the generation process. StyleGAN uses adaptive instance normalization (AdaIN) to inject styles at different layers of the generator, resulting in images with a wide range of variations and fine-grained features.

Key Features of StyleGAN:

  • Style-based Generator: Modulates styles at various layers of the generator to control specific aspects of the image.
  • Progressive Growing: Trains the model by progressively increasing the resolution of generated images, improving stability and quality.
  • Noise Injection: Adds stochastic variation to the images by injecting noise at multiple layers, enhancing the diversity of generated samples.

4.6.2 Style-based Generator Architecture

The generator in StyleGAN consists of a mapping network and a synthesis network. The mapping network transforms the input latent vector into an intermediate latent space, which is then used by the synthesis network to generate images.

Mapping Network:

  • Consists of several fully connected layers.
  • Maps the input latent vector \( z \) to an intermediate latent space \( w \).

Synthesis Network:

  • Uses the intermediate latent vector \( w \) to control the styles at each layer.
  • Employs adaptive instance normalization (AdaIN) to modulate the feature maps based on the styles.
  • Adds noise to the feature maps at various layers to introduce stochastic variation.

Example: Style-based Generator Code

import tensorflow as tf
from tensorflow.keras.layers import Dense, Reshape, Conv2D, Conv2DTranspose, Input, LeakyReLU, Layer
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.initializers import RandomNormal

# Custom layer for Adaptive Instance Normalization (AdaIN)
class AdaIN(Layer):
    def __init__(self):
        super(AdaIN, self).__init__()

    def call(self, inputs):
        content, style = inputs
        mean, variance = tf.nn.moments(content, axes=[1, 2], keepdims=True)
        std_dev = tf.sqrt(variance + 1e-8)
        norm_content = (content - mean) / std_dev

        style_mean, style_std_dev = tf.nn.moments(style, axes=[1, 2], keepdims=True)
        return norm_content * style_std_dev + style_mean

# Mapping network to map latent vector z to intermediate latent space w
def build_mapping_network(latent_dim):
    model = Sequential()
    for _ in range(8):
        model.add(Dense(latent_dim, activation='relu'))
    return model

# Synthesis network to generate images
def build_synthesis_network(img_shape, latent_dim):
    init = RandomNormal(mean=0.0, stddev=1.0)
    inputs = Input(shape=(latent_dim,))
    x = Dense(4 * 4 * 512, activation='relu', kernel_initializer=init)(inputs)
    x = Reshape((4, 4, 512))(x)

    for filters in [512, 256, 128, 64]:
        x = Conv2DTranspose(filters, kernel_size=4, strides=2, padding='same', kernel_initializer=init)(x)
        style_input = Input(shape=(latent_dim,))
        x = AdaIN()([x, style_input])
        x = LeakyReLU(alpha=0.2)(x)
        x = Conv2D(filters, kernel_size=3, padding='same', kernel_initializer=init)(x)
        x = AdaIN()([x, style_input])
        x = LeakyReLU(alpha=0.2)(x)

    outputs = Conv2D(3, kernel_size=3, padding='same', activation='tanh')(x)
    return Model(inputs=[inputs, style_input], outputs=outputs)

# Build StyleGAN generator
latent_dim = 100
mapping_network = build_mapping_network(latent_dim)
synthesis_network = build_synthesis_network((64, 64, 3), latent_dim)
mapping_network.summary()
synthesis_network.summary()

In this example:

  1. Import required libraries and modules.
  2. Define a custom TensorFlow layer (AdaIN or Adaptive Instance Normalization) which normalizes the content features according to the style features.
  3. Define a mapping network. This is a part of the StyleGAN architecture that transforms the input latent vector to an intermediate latent space.
  4. Define a synthesis network. This takes the output of the mapping network and generates the final image. The synthesis network uses Conv2DTranspose layers for upscaling, with the AdaIN layer used to apply style features at each scale.
  5. Build the StyleGAN generator by creating instances of the mapping and synthesis networks and summarizing their architectures.

4.6.3 Training StyleGAN

Training StyleGAN involves two phases: pre-training the mapping network and then training the entire StyleGAN model using progressive growing. Progressive growing starts with low-resolution images and gradually increases the resolution, stabilizing the training process and improving the quality of the generated images.

Example: Progressive Growing Training Loop

Here’s how you can implement the progressive growing training loop:

# Define progressive growing parameters
initial_resolution = 4
final_resolution = 64
num_steps_per_resolution = 10000
batch_size = 64

# Function to train at a given resolution
def train_at_resolution(generator, discriminator, gan, resolution, steps):
    for step in range(steps):
        # Generate real and fake images
        idx = np.random.randint(0, train_images.shape[0], batch_size)
        real_images = train_images[idx]
        noise = np.random.normal(0, 1, (batch_size, latent_dim))
        fake_images = generator.predict([noise, noise])

        # Train discriminator
        d_loss_real = discriminator.train_on_batch(real_images, np.ones((batch_size, 1)))
        d_loss_fake = discriminator.train_on_batch(fake_images, np.zeros((batch_size, 1)))
        d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

        # Train generator
        noise = np.random.normal(0, 1, (batch_size, latent_dim))
        g_loss = gan.train_on_batch([noise, noise], np.ones((batch_size, 1)))

        # Print progress
        if step % 100 == 0:
            print(f"Resolution {resolution}x{resolution}, Step {step}/{steps}, D loss: {d_loss[0]}, G loss: {g_loss}")

        # Save models periodically
        if step % 1000 == 0:
            generator.save(f'generator_res_{resolution}_step_{step}.h5')
            discriminator.save(f'discriminator_res_{resolution}_step_{step}.h5')

# Initialize and compile models
generator = build_synthesis_network((initial_resolution, initial_resolution, 3), latent_dim)
discriminator = build_discriminator((initial_resolution, initial_resolution, 3))
gan_input = Input(shape=(latent_dim,))
gan_output = discriminator(generator([gan_input, gan_input]))
gan = Model(gan_input, gan_output)
generator.compile(optimizer='adam', loss='binary_crossentropy')
discriminator.compile(optimizer='adam', loss='binary_crossentropy')
gan.compile(optimizer='adam', loss='binary_crossentropy')

# Train progressively
for resolution in [4, 8, 16, 32, 64]:
    train_at_resolution(generator, discriminator, gan, resolution, num_steps_per_resolution)

The code defines parameters for the progressive growing of the GAN, such as the initial and final resolutions and the number of training steps per resolution. It also defines a function for training the GAN at a given resolution.

This function loops over a specified number of steps, generating batches of real and fake images, training the discriminator on these images, and then training the generator. Performance metrics are printed out periodically, and the models are saved at certain intervals.

The GAN models are then initialized and compiled, and the training function is called for each resolution in a specified range, allowing the network to progressively grow as it trains.

4.6.4 Fine-Tuning and Evaluating StyleGAN

After training StyleGAN, it’s essential to fine-tune and evaluate the model to ensure high-quality image generation. This involves:

  • Fine-Tuning: Adjust hyperparameters, add additional training steps, and use techniques like learning rate annealing to refine the model.
  • Evaluation: Use qualitative and quantitative methods such as visual inspection, Inception Score (IS), and Fréchet Inception Distance (FID) to assess the quality of generated images.

Example: Fine-Tuning Code

# Adjust learning rate and recompile models for fine-tuning
fine_tune_learning_rate = 0.0001
generator.compile(optimizer=tf.keras.optimizers.Adam(fine_tune_learning_rate), loss='binary_crossentropy')
discriminator.compile(optimizer=tf.keras.optimizers.Adam(fine_tune_learning_rate), loss='binary_crossentropy')
gan.compile(optimizer=tf.keras.optimizers.Adam(fine_tune_learning_rate), loss='binary_crossentropy')

# Fine-tuning loop
fine_tune_steps = 5000
train_at_resolution(generator, discriminator, gan, final_resolution, fine_tune_steps)

First, a new learning rate for fine-tuning is defined, and the models of the generator, discriminator, and GAN are recompiled with this learning rate using the Adam optimizer and binary cross-entropy loss function.

Then, a specific number of fine-tuning steps are defined, and the GAN model is trained at a specified resolution for this number of steps.

4.6.5 Generating and Evaluating Final Images

Once the model is fine-tuned, generate final images and evaluate their quality.

Example: Generating Final Images

# Generate final images using the fine-tuned model
def generate_final_images(generator, latent_dim, n_samples=10):
    noise = np.random.normal(0, 1, (n_samples, latent_dim))
    generated_images = generator.predict([noise, noise])
    generated_images = (generated_images * 127.5 + 127.5).astype(np.uint8)  # Rescale to [0, 255]

    plt.figure(figsize=(20, 2))
    for i in range(n_samples):
        plt.subplot(1, n_samples, i + 1)
        plt.imshow(generated_images[i])
        plt.axis('off')
    plt.show()

# Generate and plot final images
generate_final_images(generator, latent_dim, n_samples=10)

This function is used to generate and plot final images using a generator model that has been finely tuned. The function takes three arguments: the generator model, the latent dimension size, and the number of samples to generate (which defaults to 10 if not specified).

It first generates a random noise input for the generator model using a normal distribution. The generated images are then predicted by the generator model. These generated images are rescaled to fall within the range of [0, 255] to match the typical range of pixel values in an image.

A plot is then created with a subplot for each generated image. The images are plotted without axes. Finally, the plot is displayed.

Enhancing face generation with StyleGAN provides significant improvements in the quality and control of generated images. By leveraging the style-based generator architecture, progressive growing training, and fine-tuning techniques, you can achieve high-quality, diverse, and realistic face images. Evaluating the model using both qualitative and quantitative methods ensures that the generated images meet the desired standards.

As you continue exploring the capabilities of StyleGAN, you can experiment with different styles, resolutions, and fine-tuning strategies to further enhance your generative modeling projects. This powerful technique opens up new possibilities for creative applications, research, and practical implementations in various domains.

4.6 Enhancing Face Generation with StyleGAN

StyleGAN represents a significant advancement in the field of Generative Adversarial Networks (GANs), providing a novel architecture that enables fine-grained control over the generated images. This section will delve into the intricacies of StyleGAN, explaining its architecture, the style-based generator, and how to use it for generating high-quality face images. We will also include example code to help you implement and experiment with StyleGAN.

4.6.1 Introduction to StyleGAN

StyleGAN, introduced by NVIDIA researchers, enhances traditional GANs by incorporating a style-based generator architecture. This architecture allows for the separation of high-level attributes (styles) from low-level details, enabling more precise control over the generation process. StyleGAN uses adaptive instance normalization (AdaIN) to inject styles at different layers of the generator, resulting in images with a wide range of variations and fine-grained features.

Key Features of StyleGAN:

  • Style-based Generator: Modulates styles at various layers of the generator to control specific aspects of the image.
  • Progressive Growing: Trains the model by progressively increasing the resolution of generated images, improving stability and quality.
  • Noise Injection: Adds stochastic variation to the images by injecting noise at multiple layers, enhancing the diversity of generated samples.

4.6.2 Style-based Generator Architecture

The generator in StyleGAN consists of a mapping network and a synthesis network. The mapping network transforms the input latent vector into an intermediate latent space, which is then used by the synthesis network to generate images.

Mapping Network:

  • Consists of several fully connected layers.
  • Maps the input latent vector \( z \) to an intermediate latent space \( w \).

Synthesis Network:

  • Uses the intermediate latent vector \( w \) to control the styles at each layer.
  • Employs adaptive instance normalization (AdaIN) to modulate the feature maps based on the styles.
  • Adds noise to the feature maps at various layers to introduce stochastic variation.

Example: Style-based Generator Code

import tensorflow as tf
from tensorflow.keras.layers import Dense, Reshape, Conv2D, Conv2DTranspose, Input, LeakyReLU, Layer
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.initializers import RandomNormal

# Custom layer for Adaptive Instance Normalization (AdaIN)
class AdaIN(Layer):
    def __init__(self):
        super(AdaIN, self).__init__()

    def call(self, inputs):
        content, style = inputs
        mean, variance = tf.nn.moments(content, axes=[1, 2], keepdims=True)
        std_dev = tf.sqrt(variance + 1e-8)
        norm_content = (content - mean) / std_dev

        style_mean, style_std_dev = tf.nn.moments(style, axes=[1, 2], keepdims=True)
        return norm_content * style_std_dev + style_mean

# Mapping network to map latent vector z to intermediate latent space w
def build_mapping_network(latent_dim):
    model = Sequential()
    for _ in range(8):
        model.add(Dense(latent_dim, activation='relu'))
    return model

# Synthesis network to generate images
def build_synthesis_network(img_shape, latent_dim):
    init = RandomNormal(mean=0.0, stddev=1.0)
    inputs = Input(shape=(latent_dim,))
    x = Dense(4 * 4 * 512, activation='relu', kernel_initializer=init)(inputs)
    x = Reshape((4, 4, 512))(x)

    for filters in [512, 256, 128, 64]:
        x = Conv2DTranspose(filters, kernel_size=4, strides=2, padding='same', kernel_initializer=init)(x)
        style_input = Input(shape=(latent_dim,))
        x = AdaIN()([x, style_input])
        x = LeakyReLU(alpha=0.2)(x)
        x = Conv2D(filters, kernel_size=3, padding='same', kernel_initializer=init)(x)
        x = AdaIN()([x, style_input])
        x = LeakyReLU(alpha=0.2)(x)

    outputs = Conv2D(3, kernel_size=3, padding='same', activation='tanh')(x)
    return Model(inputs=[inputs, style_input], outputs=outputs)

# Build StyleGAN generator
latent_dim = 100
mapping_network = build_mapping_network(latent_dim)
synthesis_network = build_synthesis_network((64, 64, 3), latent_dim)
mapping_network.summary()
synthesis_network.summary()

In this example:

  1. Import required libraries and modules.
  2. Define a custom TensorFlow layer (AdaIN or Adaptive Instance Normalization) which normalizes the content features according to the style features.
  3. Define a mapping network. This is a part of the StyleGAN architecture that transforms the input latent vector to an intermediate latent space.
  4. Define a synthesis network. This takes the output of the mapping network and generates the final image. The synthesis network uses Conv2DTranspose layers for upscaling, with the AdaIN layer used to apply style features at each scale.
  5. Build the StyleGAN generator by creating instances of the mapping and synthesis networks and summarizing their architectures.

4.6.3 Training StyleGAN

Training StyleGAN involves two phases: pre-training the mapping network and then training the entire StyleGAN model using progressive growing. Progressive growing starts with low-resolution images and gradually increases the resolution, stabilizing the training process and improving the quality of the generated images.

Example: Progressive Growing Training Loop

Here’s how you can implement the progressive growing training loop:

# Define progressive growing parameters
initial_resolution = 4
final_resolution = 64
num_steps_per_resolution = 10000
batch_size = 64

# Function to train at a given resolution
def train_at_resolution(generator, discriminator, gan, resolution, steps):
    for step in range(steps):
        # Generate real and fake images
        idx = np.random.randint(0, train_images.shape[0], batch_size)
        real_images = train_images[idx]
        noise = np.random.normal(0, 1, (batch_size, latent_dim))
        fake_images = generator.predict([noise, noise])

        # Train discriminator
        d_loss_real = discriminator.train_on_batch(real_images, np.ones((batch_size, 1)))
        d_loss_fake = discriminator.train_on_batch(fake_images, np.zeros((batch_size, 1)))
        d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

        # Train generator
        noise = np.random.normal(0, 1, (batch_size, latent_dim))
        g_loss = gan.train_on_batch([noise, noise], np.ones((batch_size, 1)))

        # Print progress
        if step % 100 == 0:
            print(f"Resolution {resolution}x{resolution}, Step {step}/{steps}, D loss: {d_loss[0]}, G loss: {g_loss}")

        # Save models periodically
        if step % 1000 == 0:
            generator.save(f'generator_res_{resolution}_step_{step}.h5')
            discriminator.save(f'discriminator_res_{resolution}_step_{step}.h5')

# Initialize and compile models
generator = build_synthesis_network((initial_resolution, initial_resolution, 3), latent_dim)
discriminator = build_discriminator((initial_resolution, initial_resolution, 3))
gan_input = Input(shape=(latent_dim,))
gan_output = discriminator(generator([gan_input, gan_input]))
gan = Model(gan_input, gan_output)
generator.compile(optimizer='adam', loss='binary_crossentropy')
discriminator.compile(optimizer='adam', loss='binary_crossentropy')
gan.compile(optimizer='adam', loss='binary_crossentropy')

# Train progressively
for resolution in [4, 8, 16, 32, 64]:
    train_at_resolution(generator, discriminator, gan, resolution, num_steps_per_resolution)

The code defines parameters for the progressive growing of the GAN, such as the initial and final resolutions and the number of training steps per resolution. It also defines a function for training the GAN at a given resolution.

This function loops over a specified number of steps, generating batches of real and fake images, training the discriminator on these images, and then training the generator. Performance metrics are printed out periodically, and the models are saved at certain intervals.

The GAN models are then initialized and compiled, and the training function is called for each resolution in a specified range, allowing the network to progressively grow as it trains.

4.6.4 Fine-Tuning and Evaluating StyleGAN

After training StyleGAN, it’s essential to fine-tune and evaluate the model to ensure high-quality image generation. This involves:

  • Fine-Tuning: Adjust hyperparameters, add additional training steps, and use techniques like learning rate annealing to refine the model.
  • Evaluation: Use qualitative and quantitative methods such as visual inspection, Inception Score (IS), and Fréchet Inception Distance (FID) to assess the quality of generated images.

Example: Fine-Tuning Code

# Adjust learning rate and recompile models for fine-tuning
fine_tune_learning_rate = 0.0001
generator.compile(optimizer=tf.keras.optimizers.Adam(fine_tune_learning_rate), loss='binary_crossentropy')
discriminator.compile(optimizer=tf.keras.optimizers.Adam(fine_tune_learning_rate), loss='binary_crossentropy')
gan.compile(optimizer=tf.keras.optimizers.Adam(fine_tune_learning_rate), loss='binary_crossentropy')

# Fine-tuning loop
fine_tune_steps = 5000
train_at_resolution(generator, discriminator, gan, final_resolution, fine_tune_steps)

First, a new learning rate for fine-tuning is defined, and the models of the generator, discriminator, and GAN are recompiled with this learning rate using the Adam optimizer and binary cross-entropy loss function.

Then, a specific number of fine-tuning steps are defined, and the GAN model is trained at a specified resolution for this number of steps.

4.6.5 Generating and Evaluating Final Images

Once the model is fine-tuned, generate final images and evaluate their quality.

Example: Generating Final Images

# Generate final images using the fine-tuned model
def generate_final_images(generator, latent_dim, n_samples=10):
    noise = np.random.normal(0, 1, (n_samples, latent_dim))
    generated_images = generator.predict([noise, noise])
    generated_images = (generated_images * 127.5 + 127.5).astype(np.uint8)  # Rescale to [0, 255]

    plt.figure(figsize=(20, 2))
    for i in range(n_samples):
        plt.subplot(1, n_samples, i + 1)
        plt.imshow(generated_images[i])
        plt.axis('off')
    plt.show()

# Generate and plot final images
generate_final_images(generator, latent_dim, n_samples=10)

This function is used to generate and plot final images using a generator model that has been finely tuned. The function takes three arguments: the generator model, the latent dimension size, and the number of samples to generate (which defaults to 10 if not specified).

It first generates a random noise input for the generator model using a normal distribution. The generated images are then predicted by the generator model. These generated images are rescaled to fall within the range of [0, 255] to match the typical range of pixel values in an image.

A plot is then created with a subplot for each generated image. The images are plotted without axes. Finally, the plot is displayed.

Enhancing face generation with StyleGAN provides significant improvements in the quality and control of generated images. By leveraging the style-based generator architecture, progressive growing training, and fine-tuning techniques, you can achieve high-quality, diverse, and realistic face images. Evaluating the model using both qualitative and quantitative methods ensures that the generated images meet the desired standards.

As you continue exploring the capabilities of StyleGAN, you can experiment with different styles, resolutions, and fine-tuning strategies to further enhance your generative modeling projects. This powerful technique opens up new possibilities for creative applications, research, and practical implementations in various domains.

4.6 Enhancing Face Generation with StyleGAN

StyleGAN represents a significant advancement in the field of Generative Adversarial Networks (GANs), providing a novel architecture that enables fine-grained control over the generated images. This section will delve into the intricacies of StyleGAN, explaining its architecture, the style-based generator, and how to use it for generating high-quality face images. We will also include example code to help you implement and experiment with StyleGAN.

4.6.1 Introduction to StyleGAN

StyleGAN, introduced by NVIDIA researchers, enhances traditional GANs by incorporating a style-based generator architecture. This architecture allows for the separation of high-level attributes (styles) from low-level details, enabling more precise control over the generation process. StyleGAN uses adaptive instance normalization (AdaIN) to inject styles at different layers of the generator, resulting in images with a wide range of variations and fine-grained features.

Key Features of StyleGAN:

  • Style-based Generator: Modulates styles at various layers of the generator to control specific aspects of the image.
  • Progressive Growing: Trains the model by progressively increasing the resolution of generated images, improving stability and quality.
  • Noise Injection: Adds stochastic variation to the images by injecting noise at multiple layers, enhancing the diversity of generated samples.

4.6.2 Style-based Generator Architecture

The generator in StyleGAN consists of a mapping network and a synthesis network. The mapping network transforms the input latent vector into an intermediate latent space, which is then used by the synthesis network to generate images.

Mapping Network:

  • Consists of several fully connected layers.
  • Maps the input latent vector \( z \) to an intermediate latent space \( w \).

Synthesis Network:

  • Uses the intermediate latent vector \( w \) to control the styles at each layer.
  • Employs adaptive instance normalization (AdaIN) to modulate the feature maps based on the styles.
  • Adds noise to the feature maps at various layers to introduce stochastic variation.

Example: Style-based Generator Code

import tensorflow as tf
from tensorflow.keras.layers import Dense, Reshape, Conv2D, Conv2DTranspose, Input, LeakyReLU, Layer
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.initializers import RandomNormal

# Custom layer for Adaptive Instance Normalization (AdaIN)
class AdaIN(Layer):
    def __init__(self):
        super(AdaIN, self).__init__()

    def call(self, inputs):
        content, style = inputs
        mean, variance = tf.nn.moments(content, axes=[1, 2], keepdims=True)
        std_dev = tf.sqrt(variance + 1e-8)
        norm_content = (content - mean) / std_dev

        style_mean, style_std_dev = tf.nn.moments(style, axes=[1, 2], keepdims=True)
        return norm_content * style_std_dev + style_mean

# Mapping network to map latent vector z to intermediate latent space w
def build_mapping_network(latent_dim):
    model = Sequential()
    for _ in range(8):
        model.add(Dense(latent_dim, activation='relu'))
    return model

# Synthesis network to generate images
def build_synthesis_network(img_shape, latent_dim):
    init = RandomNormal(mean=0.0, stddev=1.0)
    inputs = Input(shape=(latent_dim,))
    x = Dense(4 * 4 * 512, activation='relu', kernel_initializer=init)(inputs)
    x = Reshape((4, 4, 512))(x)

    for filters in [512, 256, 128, 64]:
        x = Conv2DTranspose(filters, kernel_size=4, strides=2, padding='same', kernel_initializer=init)(x)
        style_input = Input(shape=(latent_dim,))
        x = AdaIN()([x, style_input])
        x = LeakyReLU(alpha=0.2)(x)
        x = Conv2D(filters, kernel_size=3, padding='same', kernel_initializer=init)(x)
        x = AdaIN()([x, style_input])
        x = LeakyReLU(alpha=0.2)(x)

    outputs = Conv2D(3, kernel_size=3, padding='same', activation='tanh')(x)
    return Model(inputs=[inputs, style_input], outputs=outputs)

# Build StyleGAN generator
latent_dim = 100
mapping_network = build_mapping_network(latent_dim)
synthesis_network = build_synthesis_network((64, 64, 3), latent_dim)
mapping_network.summary()
synthesis_network.summary()

In this example:

  1. Import required libraries and modules.
  2. Define a custom TensorFlow layer (AdaIN or Adaptive Instance Normalization) which normalizes the content features according to the style features.
  3. Define a mapping network. This is a part of the StyleGAN architecture that transforms the input latent vector to an intermediate latent space.
  4. Define a synthesis network. This takes the output of the mapping network and generates the final image. The synthesis network uses Conv2DTranspose layers for upscaling, with the AdaIN layer used to apply style features at each scale.
  5. Build the StyleGAN generator by creating instances of the mapping and synthesis networks and summarizing their architectures.

4.6.3 Training StyleGAN

Training StyleGAN involves two phases: pre-training the mapping network and then training the entire StyleGAN model using progressive growing. Progressive growing starts with low-resolution images and gradually increases the resolution, stabilizing the training process and improving the quality of the generated images.

Example: Progressive Growing Training Loop

Here’s how you can implement the progressive growing training loop:

# Define progressive growing parameters
initial_resolution = 4
final_resolution = 64
num_steps_per_resolution = 10000
batch_size = 64

# Function to train at a given resolution
def train_at_resolution(generator, discriminator, gan, resolution, steps):
    for step in range(steps):
        # Generate real and fake images
        idx = np.random.randint(0, train_images.shape[0], batch_size)
        real_images = train_images[idx]
        noise = np.random.normal(0, 1, (batch_size, latent_dim))
        fake_images = generator.predict([noise, noise])

        # Train discriminator
        d_loss_real = discriminator.train_on_batch(real_images, np.ones((batch_size, 1)))
        d_loss_fake = discriminator.train_on_batch(fake_images, np.zeros((batch_size, 1)))
        d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

        # Train generator
        noise = np.random.normal(0, 1, (batch_size, latent_dim))
        g_loss = gan.train_on_batch([noise, noise], np.ones((batch_size, 1)))

        # Print progress
        if step % 100 == 0:
            print(f"Resolution {resolution}x{resolution}, Step {step}/{steps}, D loss: {d_loss[0]}, G loss: {g_loss}")

        # Save models periodically
        if step % 1000 == 0:
            generator.save(f'generator_res_{resolution}_step_{step}.h5')
            discriminator.save(f'discriminator_res_{resolution}_step_{step}.h5')

# Initialize and compile models
generator = build_synthesis_network((initial_resolution, initial_resolution, 3), latent_dim)
discriminator = build_discriminator((initial_resolution, initial_resolution, 3))
gan_input = Input(shape=(latent_dim,))
gan_output = discriminator(generator([gan_input, gan_input]))
gan = Model(gan_input, gan_output)
generator.compile(optimizer='adam', loss='binary_crossentropy')
discriminator.compile(optimizer='adam', loss='binary_crossentropy')
gan.compile(optimizer='adam', loss='binary_crossentropy')

# Train progressively
for resolution in [4, 8, 16, 32, 64]:
    train_at_resolution(generator, discriminator, gan, resolution, num_steps_per_resolution)

The code defines parameters for the progressive growing of the GAN, such as the initial and final resolutions and the number of training steps per resolution. It also defines a function for training the GAN at a given resolution.

This function loops over a specified number of steps, generating batches of real and fake images, training the discriminator on these images, and then training the generator. Performance metrics are printed out periodically, and the models are saved at certain intervals.

The GAN models are then initialized and compiled, and the training function is called for each resolution in a specified range, allowing the network to progressively grow as it trains.

4.6.4 Fine-Tuning and Evaluating StyleGAN

After training StyleGAN, it’s essential to fine-tune and evaluate the model to ensure high-quality image generation. This involves:

  • Fine-Tuning: Adjust hyperparameters, add additional training steps, and use techniques like learning rate annealing to refine the model.
  • Evaluation: Use qualitative and quantitative methods such as visual inspection, Inception Score (IS), and Fréchet Inception Distance (FID) to assess the quality of generated images.

Example: Fine-Tuning Code

# Adjust learning rate and recompile models for fine-tuning
fine_tune_learning_rate = 0.0001
generator.compile(optimizer=tf.keras.optimizers.Adam(fine_tune_learning_rate), loss='binary_crossentropy')
discriminator.compile(optimizer=tf.keras.optimizers.Adam(fine_tune_learning_rate), loss='binary_crossentropy')
gan.compile(optimizer=tf.keras.optimizers.Adam(fine_tune_learning_rate), loss='binary_crossentropy')

# Fine-tuning loop
fine_tune_steps = 5000
train_at_resolution(generator, discriminator, gan, final_resolution, fine_tune_steps)

First, a new learning rate for fine-tuning is defined, and the models of the generator, discriminator, and GAN are recompiled with this learning rate using the Adam optimizer and binary cross-entropy loss function.

Then, a specific number of fine-tuning steps are defined, and the GAN model is trained at a specified resolution for this number of steps.

4.6.5 Generating and Evaluating Final Images

Once the model is fine-tuned, generate final images and evaluate their quality.

Example: Generating Final Images

# Generate final images using the fine-tuned model
def generate_final_images(generator, latent_dim, n_samples=10):
    noise = np.random.normal(0, 1, (n_samples, latent_dim))
    generated_images = generator.predict([noise, noise])
    generated_images = (generated_images * 127.5 + 127.5).astype(np.uint8)  # Rescale to [0, 255]

    plt.figure(figsize=(20, 2))
    for i in range(n_samples):
        plt.subplot(1, n_samples, i + 1)
        plt.imshow(generated_images[i])
        plt.axis('off')
    plt.show()

# Generate and plot final images
generate_final_images(generator, latent_dim, n_samples=10)

This function is used to generate and plot final images using a generator model that has been finely tuned. The function takes three arguments: the generator model, the latent dimension size, and the number of samples to generate (which defaults to 10 if not specified).

It first generates a random noise input for the generator model using a normal distribution. The generated images are then predicted by the generator model. These generated images are rescaled to fall within the range of [0, 255] to match the typical range of pixel values in an image.

A plot is then created with a subplot for each generated image. The images are plotted without axes. Finally, the plot is displayed.

Enhancing face generation with StyleGAN provides significant improvements in the quality and control of generated images. By leveraging the style-based generator architecture, progressive growing training, and fine-tuning techniques, you can achieve high-quality, diverse, and realistic face images. Evaluating the model using both qualitative and quantitative methods ensures that the generated images meet the desired standards.

As you continue exploring the capabilities of StyleGAN, you can experiment with different styles, resolutions, and fine-tuning strategies to further enhance your generative modeling projects. This powerful technique opens up new possibilities for creative applications, research, and practical implementations in various domains.