Here's the complete class code with an example of its implementation.
import tensorflow as tf
import numpy as np
import time
from tensorflow.keras.applications.inception_v3 import InceptionV3
from tensorflow.keras.applications.inception_v3 import preprocess_input
from scipy.linalg import sqrtm
class ImageGAN:
"""
Classe pour construire un GAN (Generative Adversarial Network) pour la génération d'images en utilisant TensorFlow.
Paramètres :
- generator : Modèle TensorFlow représentant le générateur du GAN.
- discriminator : Modèle TensorFlow représentant le discriminateur du GAN.
- noise_dim : Dimension du vecteur de bruit d'entrée du générateur.
- batch_size : Taille des mini-lots pour l'entraînement.
- epochs : Nombre d'epochs pour l'entraînement.
- learning_rate : Taux d'apprentissage pour l'optimiseur.
- verbose : Booléen indiquant s'il faut afficher les informations de progression à chaque epoch.
- fid_samples : Nombre d'échantillons pour le calcul de la FID.
- spectral_regularization : Booléen indiquant s'il faut appliquer la régularisation spectrale.
- metric : Métrique d'évaluation à utiliser (parmi 'fid', 'inception_score', 'kid', 'mse').
- conditional : Booléen indiquant si l'architecture doit être conditionnelle.
- use_adamw : Booléen indiquant si l'optimiseur AdamW doit être utilisé.
Méthodes :
- fit : Entraîne le GAN sur les données fournies.
- generate_images : Génère des images à l'aide du générateur entraîné.
- calculate_metric : Calcule la métrique d'évaluation spécifiée entre les images réelles et générées.
Exemple d'utilisation :
# Création du générateur et du discriminateur basés sur StyleGAN2
generator = build_stylegan2_generator(...)
discriminator = build_stylegan2_discriminator(...)
# Création de l'instance de la classe ImageGAN avec des options avancées
gan = ImageGAN(generator, discriminator, noise_dim=512, batch_size=32, epochs=100, learning_rate=0.0002,
verbose=True, fid_samples=1000, spectral_regularization=True, metric='fid', conditional=False,
use_adamw=True)
# Entraînement du GAN sur des données réelles
gan.fit(real_images)
# Génération d'images à l'aide du générateur entraîné
generated_images = gan.generate_images(10)
# Calcul de la métrique d'évaluation entre les images réelles et générées
metric_score = gan.calculate_metric(real_images, num_samples=1000)
"""
def __init__(self, generator, discriminator, noise_dim, batch_size=64, epochs=100, learning_rate=0.0002,
verbose=True, fid_samples=1000, spectral_regularization=True, metric='fid', conditional=False,
use_adamw=True):
self.generator = generator
self.discriminator = discriminator
self.noise_dim = noise_dim
self.batch_size = batch_size
self.epochs = epochs
self.learning_rate = learning_rate
self.verbose = verbose
self.fid_samples = fid_samples
self.spectral_regularization = spectral_regularization
self.metric = metric
self.conditional = conditional
self.use_adamw = use_adamw
def generate_latent_noise(batch_size, noise_dim):
return np.random.normal(size=(batch_size, noise_dim))
def train_step(real_images, generator_optimizer, discriminator_optimizer, loss_fn, batch_size, noise_dim, spectral_regularization, discriminator):
noise = generate_latent_noise(batch_size, noise_dim)
with tf.GradientTape(persistent=True) as tape:
generated_images = generator(noise, training=True)
real_output = discriminator(real_images, training=True)
generated_output = discriminator(generated_images, training=True)
gen_loss = loss_fn(tf.ones_like(generated_output), generated_output)
disc_loss_real = loss_fn(tf.ones_like(real_output), real_output)
disc_loss_generated = loss_fn(tf.zeros_like(generated_output), generated_output)
disc_loss = disc_loss_real + disc_loss_generated
if spectral_regularization:
disc_grads = tape.gradient(disc_loss, discriminator.trainable_variables)
disc_grads_norm = [tf.norm(grad) for grad in disc_grads]
disc_penalty = 10.0 * tf.reduce_mean(tf.square(disc_grads_norm))
disc_loss += disc_penalty
gen_gradients = tape.gradient(gen_loss, generator.trainable_variables)
disc_gradients = tape.gradient(disc_loss, discriminator.trainable_variables)
generator_optimizer.apply_gradients(zip(gen_gradients, generator.trainable_variables))
discriminator_optimizer.apply_gradients(zip(disc_gradients, discriminator.trainable_variables))
return gen_loss, disc_loss
def fit(self, real_images):
"""
Entraîne le GAN sur des données réelles (images réelles).
Paramètres :
- real_images : Tensor ou tableau numpy contenant les images réelles pour l'entraînement.
Retourne :
Rien.
"""
# Préparation des données
real_images = tf.data.Dataset.from_tensor_slices(real_images).shuffle(len(real_images)).batch(self.batch_size)
# Fonction de perte pour le générateur et le discriminateur
if self.conditional:
loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
else:
loss_fn = tf.keras.losses.BinaryCrossentropy(from_logits=True)
# Optimiseurs pour le générateur et le discriminateur
if self.use_adamw:
generator_optimizer = tf.keras.optimizers.AdamW(self.learning_rate)
discriminator_optimizer = tf.keras.optimizers.AdamW(self.learning_rate)
else:
generator_optimizer = tf.keras.optimizers.Adam(self.learning_rate)
discriminator_optimizer = tf.keras.optimizers.Adam(self.learning_rate)
# Entraînement du GAN sur plusieurs epochs
for epoch in range(self.epochs):
start_time = time.time()
gen_loss_avg = tf.keras.metrics.Mean()
disc_loss_avg = tf.keras.metrics.Mean()
for real_batch in real_images:
gen_loss, disc_loss = train_step(real_batch, generator_optimizer, discriminator_optimizer, loss_fn,
self.batch_size, self.noise_dim, self.spectral_regularization, self.discriminator)
gen_loss_avg.update_state(gen_loss)
disc_loss_avg.update_state(disc_loss)
duration = time.time() - start_time
if self.verbose:
print(f"Epoch {epoch+1}/{self.epochs}, Gen Loss: {gen_loss_avg.result():.4f}, Disc Loss: {disc_loss_avg.result():.4f}, Duration: {duration:.2f} seconds")
def generate_images(self, num_images):
"""
Génère des images à l'aide du générateur entraîné.
Paramètres :
- num_images : Nombre d'images à générer.
Retourne :
- Tensor : Images générées.
"""
noise = generate_latent_noise(num_images, self.noise_dim)
generated_images = self.generator.predict(noise)
return generated_images
def calculate_metric(self, real_images, num_samples=None, metric=None):
"""
Calcule la métrique d'évaluation spécifiée entre les images réelles et générées.
Paramètres :
- real_images : Tensor ou tableau numpy contenant les images réelles.
- num_samples : Nombre d'échantillons à utiliser pour le calcul de la métrique.
- metric : Métrique d'évaluation à utiliser (parmi 'fid', 'inception_score', 'kid', 'mse').
Retourne :
- float : Score de la métrique.
"""
if num_samples is None:
num_samples = self.fid_samples
if metric is None:
metric = self.metric
if self.metric == 'fid':
return self.calculate_fid(self.generator, real_images, num_samples)
elif self.metric == 'inception_score':
return self.calculate_inception_score(self.generator, num_samples)
elif self.metric == 'kid':
return self.calculate_kid(self.generator, real_images, num_samples)
elif self.metric == 'mse':
return self.calculate_mse(self.generator, real_images, num_samples)
else:
raise ValueError("Metric not supported. Available metrics: 'fid', 'inception_score', 'kid', 'mse'")
def calculate_fid(generator, real_images, num_samples):
"""
Calcule la Fréchet Inception Distance (FID) entre les images réelles et générées.
Paramètres :
- real_images : Tensor ou tableau numpy contenant les images réelles.
- num_samples : Nombre d'échantillons à utiliser pour le calcul de la FID.
Retourne :
- float : Score FID.
"""
# Générer des images à l'aide du générateur entraîné
noise = np.random.normal(size=(num_samples, generator.noise_dim))
generated_images = generator.generator.predict(noise)
# Chargement du modèle InceptionV3 pré-entraîné pour l'extraction des features
inception_model = InceptionV3(include_top=False, pooling='avg', input_shape=(299, 299, 3))
real_features = inception_model.predict(preprocess_input(real_images))
generated_features = inception_model.predict(preprocess_input(generated_images))
# Calcul des moyennes et matrices de covariance des features
real_mean, real_cov = np.mean(real_features, axis=0), np.cov(real_features, rowvar=False)
generated_mean, generated_cov = np.mean(generated_features, axis=0), np.cov(generated_features, rowvar=False)
# Calcul de la distance FID
diff = real_mean - generated_mean
cov_sqrt, _ = sqrtm(real_cov.dot(generated_cov), disp=False)
if np.iscomplexobj(cov_sqrt):
cov_sqrt = cov_sqrt.real
fid_score = np.dot(diff, diff) + np.trace(real_cov + generated_cov - 2 * cov_sqrt)
return fid_score
def calculate_inception_score(generator, num_samples):
# Générer des images à l'aide du générateur entraîné
noise = np.random.normal(size=(num_samples, generator.noise_dim))
generated_images = generator.generator.predict(noise)
# Charger le modèle InceptionV3 pré-entraîné
inception_model = InceptionV3(include_top=False, pooling='avg', input_shape=(299, 299, 3))
# Prétraitement des images pour le modèle InceptionV3
generated_images = tf.image.resize(generated_images, (299, 299))
generated_images = tf.image.grayscale_to_rgb(generated_images)
generated_images = preprocess_input(generated_images)
# Calculer les probabilités de chaque classe pour les images générées
inception_scores = inception_model.predict(generated_images)
inception_scores = tf.nn.softmax(inception_scores)
# Calculer l'entropie croisée pour obtenir l'Inception Score
entropy = -tf.reduce_sum(inception_scores * tf.math.log(inception_scores), axis=1)
inception_score = tf.exp(tf.reduce_mean(entropy))
return inception_score.numpy()
def calculate_kid(generator, real_images, num_samples):
# Générer des images à l'aide du générateur entraîné
noise = np.random.normal(size=(num_samples, generator.noise_dim))
generated_images = generator.generator.predict(noise)
# Charger le modèle InceptionV3 pré-entraîné pour les features
inception_model = InceptionV3(include_top=False, pooling='avg', input_shape=(299, 299, 3))
# Prétraitement des images pour le modèle InceptionV3
real_images = tf.image.resize(real_images, (299, 299))
real_images = tf.image.grayscale_to_rgb(real_images)
real_images = preprocess_input(real_images)
generated_images = tf.image.resize(generated_images, (299, 299))
generated_images = tf.image.grayscale_to_rgb(generated_images)
generated_images = preprocess_input(generated_images)
# Extraire les features pour les images réelles et générées
real_features = inception_model.predict(real_images)
generated_features = inception_model.predict(generated_images)
# Calculer la distance euclidienne moyenne entre les features réelles et générées
distances = tf.norm(real_features[:, np.newaxis] - generated_features, axis=2)
kid = tf.reduce_mean(distances).numpy()
return kid
def calculate_mse(generator, real_images, num_samples):
# Générer des images à l'aide du générateur entraîné
noise = np.random.normal(size=(num_samples, generator.noise_dim))
generated_images = generator.generator.predict(noise)
# Calculer la Mean Squared Error (MSE) entre les images réelles et générées
mse = tf.reduce_mean(tf.square(real_images - generated_images)).numpy()
return mse
from tensorflow.keras.layers import Input, Dense, Reshape, Flatten, Conv2D, Conv2DTranspose, LeakyReLU, BatchNormalization
from tensorflow.keras.models import Model
def build_stylegan2_generator(noise_dim):
# Ici, nous construisons une architecture simple de générateur basée sur StyleGAN2
# pour générer des images de 28x28 en noir et blanc (pour MNIST).
# (Ceci est une implémentation fictive et n'est pas une vraie architecture StyleGAN2).
inputs = Input(shape=(noise_dim,))
x = Dense(7*7*128)(inputs)
x = LeakyReLU(alpha=0.2)(x)
x = Reshape((7, 7, 128))(x)
x = Conv2DTranspose(64, kernel_size=4, strides=2, padding='same')(x)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2DTranspose(1, kernel_size=4, strides=2, padding='same', activation='sigmoid')(x)
generator = Model(inputs, x)
return generator
def build_stylegan2_discriminator():
# Ici, nous construisons une architecture simple de discriminateur basée sur StyleGAN2
# pour discriminer les images de 28x28 en noir et blanc (pour MNIST).
# (Ceci est une implémentation fictive et n'est pas une vraie architecture StyleGAN2).
inputs = Input(shape=(28, 28, 1))
x = Conv2D(64, kernel_size=4, strides=2, padding='same')(inputs)
x = LeakyReLU(alpha=0.2)(x)
x = Conv2D(128, kernel_size=4, strides=2, padding='same')(x)
x = LeakyReLU(alpha=0.2)(x)
x = Flatten()(x)
x = Dense(1)(x)
discriminator = Model(inputs, x)
return discriminator
# Charger le jeu de données MNIST
(train_images, _), (_, _) = tf.keras.datasets.mnist.load_data()
# Prétraitement des images
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
train_images = (train_images - 127.5) / 127.5 # Mise à l'échelle des pixels entre -1 et 1
# Créer le générateur et le discriminateur
noise_dim = 100
generator = build_stylegan2_generator(noise_dim)
discriminator = build_stylegan2_discriminator()
# Créer l'instance de la classe ImageGAN avec des options avancées
gan = ImageGAN(generator, discriminator, noise_dim=noise_dim, batch_size=64, epochs=50, learning_rate=0.0002,
verbose=True, fid_samples=1000, spectral_regularization=True, metric='inception_score',
conditional=False, use_adamw=True)
# Entraîner le GAN sur le jeu de données MNIST
gan.fit(train_images)
# Générer des images à l'aide du générateur entraîné
n_images_to_generate = 10
generated_images = gan.generate_images(n_images_to_generate)
# Calcule la Fréchet Inception Distance (FID) entre les images réelles et générées.
fid = gan.calculate_metric(generated_images, num_samples=1000, metric='fid')
# Calculer l'Inception Score entre les images réelles et générées
inception_score = gan.calculate_metric(generated_images, num_samples=1000, metric='inception_score')
# Calculer le Kernel Inception Distance (KID) entre les images réelles et générées
kid = gan.calculate_metric(generated_images, num_samples=1000, metric='kid')
# Calculer la Mean Squared Error (MSE) entre les images réelles et générées
mse = gan.calculate_metric(generated_images, num_samples=1000, metric='mse')
# Affichage des images générées
plt.figure(figsize=(10, 5))
for i in range(n_images_to_generate):
plt.subplot(1, n_images_to_generate, i + 1)
plt.imshow(generated_images[i].reshape(28, 28), cmap='gray')
plt.axis('off')
plt.suptitle("Exemples d'images générées", fontsize=16)
plt.show()
# Afficher les résultats des métriques d'évaluation entre les images réelles et générées
print(f"Fréchet Inception Distance (FID): {fid}")
print(f"Inception Score: {inception_score}")
print(f"Kernel Inception Distance (KID): {kid}")
print(f"Mean Squared Error (MSE): {mse}")
here you'll find the error displayed by google Colab during execution
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-3-fcaa091262ee> in <cell line: 19>()
17
18 # Entraîner le GAN sur le jeu de données MNIST
---> 19 gan.fit(train_images)
20
21 # Générer des images à l'aide du générateur entraîné
<ipython-input-1-d5bbfe93e45e> in fit(self, real_images)
137
138 for real_batch in real_images:
--> 139 gen_loss, disc_loss = train_step(real_batch, generator_optimizer, discriminator_optimizer, loss_fn,
140 self.batch_size, self.noise_dim, self.spectral_regularization, self.discriminator)
141 gen_loss_avg.update_state(gen_loss)
NameError: name 'train_step' is not defined
thank you in advance for your help. i apologize in advance for the fact that the comments are in french !! because it's my mother tongue but you can translate !?
to solve the problem, I reintroduced the code from the "generate_latent_noise" and "train_step" functions into the "fit" function - it worked! but I'm not happy with it!
You need to pass the self
argument to call a function
from another function
when both are under the same class
. You can find out more about this from this thread.
However, to solve your specific case, replace this:
def train_step(real_images, generator_optimizer, discriminator_optimizer, loss_fn, batch_size, noise_dim, spectral_regularization, discriminator):
with the following (I just included the self
argument here):
def train_step(self, real_images, generator_optimizer, discriminator_optimizer, loss_fn, batch_size, noise_dim, spectral_regularization, discriminator):
And then in your fit
function, use self.train_step
instead of train_step
here:
gen_loss, disc_loss = self.train_step(real_batch, generator_optimizer, discriminator_optimizer, loss_fn,
self.batch_size, self.noise_dim, self.spectral_regularization, self.discriminator)
Additional: You need to make the same changes for your generate_latent_noise
function. And change these lines under train_step
function:
line 78: generated_images = generator(noise, training=True)
line 94: gen_gradients = tape.gradient(gen_loss, generator.trainable_variables)
line 97: generator_optimizer.apply_gradients(zip(gen_gradients, generator.trainable_variables))
with:
line 78: generated_images = self.generator(noise, training=True)
line 94: gen_gradients = tape.gradient(gen_loss, self.generator.trainable_variables)
line 97: generator_optimizer.apply_gradients(zip(gen_gradients, self.generator.trainable_variables))