Generating Cartoon Avatars with Deep Convolutional Generative Adversarial Networks

Generating Cartoon Avatars with Deep Convolutional Generative Adversarial Networks

Understanding DCGAN Architecture

Deep Convolutional Generative Adversarial Networks (DCGAN) represent an evolution of the original GAN architecture. The primary distinction lies in the incorporation of convolutional layers in both the discriminator and generator networks. This architectural enhancement enables more effective learning of hierarchical representations from image data.

The DCGAN framework was first introduced by Radford et al. in their paper "Unsupervised Representation Learning With Deep Convolutional Generative Adversarial Networks." In this implementation, the discriminator employs a series of convolutional layers, each followed by batch normalization and LeakyReLU activation functions. The network accepts 3x64x64 images as input and outputs a probability score indicating whether the input image is real or generated. Conversely, the generator utilizes transposed convolutional layers, batch normalization, and ReLU activations to transform a latent vector z into a 3x64x64 RGB image.

Dataset Preparation and Processing

First, we'll download the dataset required for training our DCGAN model:

from download import download

dataset_url = "https://download.mindspore.cn/dataset/Faces/faces.zip"
dataset_path = download(dataset_url, "./faces", kind="zip", replace=True)

The downloaded dataset has the following directory structure:

./faces/faces
├── 0.jpg
├── 1.jpg
├── 2.jpg
├── 3.jpg
├── 4.jpg
    ...
├── 70169.jpg
└── 70170.jpg

Data Processing Pipeline

Let's define the necessary hyperparameters for our training process:

batch_size = 128          # Batch size for training
image_dimension = 64       # Spatial size of training images
color_channels = 3         # Number of color channels in images
latent_vector_size = 100   # Dimensionality of the latent vector
generator_feature_maps = 64  # Size of feature maps in generator
discriminator_feature_maps = 64  # Size of feature maps in discriminator
training_epochs = 3       # Number of training epochs
learning_rate = 0.0002    # Learning rate for optimization
beta1_param = 0.5         # Beta1 hyperparameter for Adam optimizer

Next, we'll implement the data loading and preprocessing pipeline:

import numpy as np
import mindspore.dataset as ds
import mindspore.dataset.vision as vision

def prepare_dataset(dataset_path):
    """Load and preprocess the dataset"""
    dataset = ds.ImageFolderDataset(dataset_path,
                                    num_parallel_workers=4,
                                    shuffle=True,
                                    decode=True)

    # Define data augmentation transformations
    transformations = [
        vision.Resize(image_dimension),
        vision.CenterCrop(image_dimension),
        vision.HWC2CHW(),
        lambda x: ((x / 255).astype("float32"))
    ]

    # Apply transformations
    dataset = dataset.project('image')
    dataset = dataset.map(transformations, 'image')

    # Create batches
    dataset = dataset.batch(batch_size)
    return dataset

dataset = prepare_dataset('./faces')

import matplotlib.pyplot as plt

def visualize_sample_data(data):
    """Display a subset of training samples"""
    plt.figure(figsize=(10, 3), dpi=140)
    for i, image in enumerate(data[0][:30], 1):
        plt.subplot(3, 10, i)
        plt.axis("off")
        plt.imshow(image.transpose(1, 2, 0))
    plt.show()

sample_batch = next(dataset.create_tuple_iterator(output_numpy=True))
visualize_sample_data(sample_batch)

Network Architecture Implementation

After preparing the data, we can construct our DCGAN network. According to the DCGAN paper, all model weights should be randomly initialized from a normal distribution with mean=0 and standard deviation=0.02.

Generator Network

The generator network's function is to map a latent vector z to the data space, creating RGB images of the same dimensions as the real images. This is achieved through a series of transposed convolutional layers, each paired with batch normalization and ReLU activation functions. The final output passes through a tanh function to constrain values to the range [-1, 1].

The generator structure is influenced by the hyperparameters defined earlier: latent_vector_size determines the dimensionality of the input vector, generator_feature_maps affects the size of feature maps propagated through the network, and color_channels specifies the number of channels in the output image.

Here's the implementation of our generator network:

import mindspore as ms
from mindspore import nn, ops
from mindspore.common.initializer import Normal

# Initialize weights with normal distribution
weight_initializer = Normal(mean=0, sigma=0.02)
gamma_initializer = Normal(mean=1, sigma=0.02)

class Generator(nn.Cell):
    """DCGAN Generator Network"""

    def __init__(self):
        super(Generator, self).__init__()
        self.network = nn.SequentialCell(
            nn.Conv2dTranspose(latent_vector_size, generator_feature_maps * 8, 4, 1, 'valid', weight_init=weight_initializer),
            nn.BatchNorm2d(generator_feature_maps * 8, gamma_init=gamma_initializer),
            nn.ReLU(),
            nn.Conv2dTranspose(generator_feature_maps * 8, generator_feature_maps * 4, 4, 2, 'pad', 1, weight_init=weight_initializer),
            nn.BatchNorm2d(generator_feature_maps * 4, gamma_init=gamma_initializer),
            nn.ReLU(),
            nn.Conv2dTranspose(generator_feature_maps * 4, generator_feature_maps * 2, 4, 2, 'pad', 1, weight_init=weight_initializer),
            nn.BatchNorm2d(generator_feature_maps * 2, gamma_init=gamma_initializer),
            nn.ReLU(),
            nn.Conv2dTranspose(generator_feature_maps * 2, generator_feature_maps, 4, 2, 'pad', 1, weight_init=weight_initializer),
            nn.BatchNorm2d(generator_feature_maps, gamma_init=gamma_initializer),
            nn.ReLU(),
            nn.Conv2dTranspose(generator_feature_maps, color_channels, 4, 2, 'pad', 1, weight_init=weight_initializer),
            nn.Tanh()
        )

    def construct(self, input_vector):
        return self.network(input_vector)

generator_model = Generator()

Discriminator Network

As previously mentioned, the discriminator is a binary classification network that outputs the probability of an image being real. It processes input through a series of convolutional layers, batch normalization, and LeakyReLU activations, with a final Sigmoid activation function to produce the probability score.

The DCGAN paper suggests using convolutional layers rather than pooling operations for downsampling, as this allows the network to learn its own pooling features.

Here's the implementation of our discriminator network:

class Discriminator(nn.Cell):
    """DCGAN Discriminator Network"""

    def __init__(self):
        super(Discriminator, self).__init__()
        self.network = nn.SequentialCell(
            nn.Conv2d(color_channels, discriminator_feature_maps, 4, 2, 'pad', 1, weight_init=weight_initializer),
            nn.LeakyReLU(0.2),
            nn.Conv2d(discriminator_feature_maps, discriminator_feature_maps * 2, 4, 2, 'pad', 1, weight_init=weight_initializer),
            nn.BatchNorm2d(discriminator_feature_maps * 2, gamma_init=gamma_initializer),
            nn.LeakyReLU(0.2),
            nn.Conv2d(discriminator_feature_maps * 2, discriminator_feature_maps * 4, 4, 2, 'pad', 1, weight_init=weight_initializer),
            nn.BatchNorm2d(discriminator_feature_maps * 4, gamma_init=gamma_initializer),
            nn.LeakyReLU(0.2),
            nn.Conv2d(discriminator_feature_maps * 4, discriminator_feature_maps * 8, 4, 2, 'pad', 1, weight_init=weight_initializer),
            nn.BatchNorm2d(discriminator_feature_maps * 8, gamma_init=gamma_initializer),
            nn.LeakyReLU(0.2),
            nn.Conv2d(discriminator_feature_maps * 8, 1, 4, 1, 'valid', weight_init=weight_initializer),
        )
        self.output_layer = nn.Sigmoid()

    def construct(self, input_image):
        output = self.network(input_image)
        output = output.reshape(output.shape[0], -1)
        return self.output_layer(output)

discriminator_model = Discriminator()

Model Training Process

Loss Function Definition

After defining the generator and discriminator networks, we'll use the binary cross-entropy loss function (BCELoss) from MindSpore for our training process:

# Define the loss function
adversarial_loss = nn.BCELoss(reduction='mean')

Optimizer Configuration

We'll set up two separate optimizers, one for the discriminator and one for the generator. Both will use the Adam optimizer with a learning rate of 0.0002 and beta1 parameter of 0.5:

# Configure optimizers for generator and discriminator
discriminator_optimizer = nn.Adam(discriminator_model.trainable_params(), learning_rate=learning_rate, beta1=beta1_param)
generator_optimizer = nn.Adam(generator_model.trainable_params(), learning_rate=learning_rate, beta1=beta1_param)
generator_optimizer.update_parameters_name('optim_g.')
discriminator_optimizer.update_parameters_name('optim_d.')

Training Implementation

The training process consists of two main parts: training the discriminator and training the generator.

  • Discriminator Training: The objective is to maximize the probability of correctly classifying real images as real and generated images as fake. This involves maximizing log(D(x)) + log(1 - D(G(z))).
  • Generator Training: As described in the DCGAN paper, we aim to train the generator to produce better fake images by minimizing log(1 - D(G(z))).

Here's the implementation of our training logic:

def generator_step(real_images, real_labels):
    # Sample noise as input to the generator
    noise_vector = ops.standard_normal((real_images.shape[0], latent_vector_size, 1, 1))

    # Generate a batch of images
    generated_images = generator_model(noise_vector)

    # Calculate generator loss based on its ability to fool the discriminator
    generator_loss = adversarial_loss(discriminator_model(generated_images), real_labels)

    return generator_loss, generated_images

def discriminator_step(real_images, generated_images, real_labels, fake_labels):
    # Calculate discriminator's ability to classify real samples correctly
    real_loss = adversarial_loss(discriminator_model(real_images), real_labels)
    fake_loss = adversarial_loss(discriminator_model(generated_images), fake_labels)
    discriminator_loss = (real_loss + fake_loss) / 2
    return discriminator_loss

# Create gradient functions
generator_gradient_fn = ms.value_and_grad(generator_step, None,
                                        generator_optimizer.parameters,
                                        has_aux=True)
discriminator_gradient_fn = ms.value_and_grad(discriminator_step, None,
                                            discriminator_optimizer.parameters)

@ms.jit
def training_step(images):
    real_labels = ops.ones((images.shape[0], 1), mindspore.float32)
    fake_labels = ops.zeros((images.shape[0], 1), mindspore.float32)

    (generator_loss, generated_images), generator_gradients = generator_gradient_fn(images, real_labels)
    generator_optimizer(generator_gradients)
    discriminator_loss, discriminator_gradients = discriminator_gradient_fn(images, generated_images, real_labels, fake_labels)
    discriminator_optimizer(discriminator_gradients)

    return generator_loss, discriminator_loss, generated_images

We'll now train our network by iterating through the dataset for the specified number of epochs. We'll collect the losses from both the generator and discriminator every 50 iterations to visualize the training progress:

import mindspore

# Lists to store losses for visualization
generator_losses = []
discriminator_losses = []
generated_images_list = []

dataset_size = dataset.get_dataset_size()
for epoch in range(training_epochs):
    generator_model.set_train()
    discriminator_model.set_train()
    
    # Process data for each epoch
    for i, (images, ) in enumerate(dataset.create_tuple_iterator()):
        g_loss, d_loss, gen_imgs = training_step(images)
        if i % 100 == 0 or i == dataset_size - 1:
            # Print training progress
            print('[%2d/%d][%3d/%d]   Loss_D:%7.4f  Loss_G:%7.4f' % (
                epoch + 1, training_epochs, i + 1, dataset_size, d_loss.asnumpy(), g_loss.asnumpy()))
        discriminator_losses.append(d_loss.asnumpy())
        generator_losses.append(g_loss.asnumpy())

    # Generate images after each epoch for visualization
    generator_model.set_train(False)
    fixed_noise = ops.standard_normal((batch_size, latent_vector_size, 1, 1))
    generated_batch = generator_model(fixed_noise)
    generated_images_list.append(generated_batch.transpose(0, 2, 3, 1).asnumpy())

    # Save model parameters
    mindspore.save_checkpoint(generator_model, "./generator_model.ckpt")
    mindspore.save_checkpoint(discriminator_model, "./discriminator_model.ckpt")

Training Results Visualization

Let's plot the loss functions of both the generator and discriminator during training:

plt.figure(figsize=(10, 5))
plt.title("Generator and Discriminator Loss During Training")
plt.plot(generator_losses, label="Generator", color='blue')
plt.plot(discriminator_losses, label="Discriminator", color='orange')
plt.xlabel("Iterations")
plt.ylabel("Loss")
plt.legend()
plt.show()

Next, we'll create an animation to visualize the progression of generated images throughout training:

import matplotlib.pyplot as plt
import matplotlib.animation as animation

def create_animation(image_sequence):
    animation_frames = []
    figure = plt.figure(figsize=(8, 3), dpi=120)
    for epoch in range(len(image_sequence)):
        images = []
        for i in range(3):
            row = np.concatenate((image_sequence[epoch][i * 8:(i + 1) * 8]), axis=1)
            images.append(row)
        img = np.clip(np.concatenate((images[:]), axis=0), 0, 1)
        plt.axis("off")
        animation_frames.append([plt.imshow(img)])

    animation_obj = animation.ArtistAnimation(figure, animation_frames, interval=1000, repeat_delay=1000, blit=True)
    animation_obj.save('./dcgan_animation.gif', writer='pillow', fps=1)

create_animation(generated_images_list)

As shown in the animation, the quality of generated images improves as training progresses. If we increase the number of training epochs to 50 or more, the generated cartoon avatars become increasingly similar to those in the training dataset.

Finally, let's load the trained generator model to generate new images:

# Load pre-trained model parameters
mindspore.load_checkpoint("./generator_model.ckpt", generator_model)

# Generate images using the trained model
fixed_noise = ops.standard_normal((batch_size, latent_vector_size, 1, 1))
generated_images = generator_model(fixed_noise).transpose(0, 2, 3, 1).asnumpy()

# Display generated images
figure = plt.figure(figsize=(8, 3), dpi=120)
images = []
for i in range(3):
    images.append(np.concatenate((generated_images[i * 8:(i + 1) * 8]), axis=1))
img = np.clip(np.concatenate((images[:]), axis=0), 0, 1)
plt.axis("off")
plt.imshow(img)
plt.show()

Tags: DCGAN Generative Adversarial Networks Deep Learning image generation Cartoon Avatars

Posted on Thu, 09 Jul 2026 16:24:32 +0000 by geek_girl_2020