Module likelihood.models.deep.gan

Classes

class GANRegressor (input_shape_parm,
output_shape_parm,
num_neurons=128,
activation='linear',
depth=5,
dropout=0.2,
l2_reg=0.0,
**kwargs)
Expand source code
@tf.keras.utils.register_keras_serializable(package="Custom", name="GANRegressor")
class GANRegressor(tf.keras.Model):
    """
    GANRegressor is a custom Keras model that combines a generator and a discriminator
    """

    def __init__(
        self,
        input_shape_parm,
        output_shape_parm,
        num_neurons=128,
        activation="linear",
        depth=5,
        dropout=0.2,
        l2_reg=0.0,
        **kwargs,
    ):
        super(GANRegressor, self).__init__()
        self.input_shape_parm = input_shape_parm
        self.output_shape_parm = output_shape_parm
        self.num_neurons = num_neurons
        self.activation = activation
        self.depth = depth
        self.dropout = dropout
        self.l2_reg = l2_reg
        self.optimizer = kwargs.get("optimizer", "adam")

        self.generator = self._build_generator()
        self.discriminator = self._build_discriminator()
        dummy_input = tf.convert_to_tensor(tf.random.normal([1, self.input_shape_parm]))
        self.build(dummy_input.shape)

    def build(self, input_shape):
        self.gan = tf.keras.models.Sequential([self.generator, self.discriminator], name="gan")

        self.generator.compile(
            optimizer=self.optimizer,
            loss=tf.keras.losses.MeanAbsolutePercentageError(),
            metrics=[tf.keras.metrics.MeanAbsolutePercentageError()],
        )

        self.discriminator.compile(
            optimizer=self.optimizer, loss="binary_crossentropy", metrics=["accuracy"]
        )

        self.gan.compile(optimizer=self.optimizer, loss="binary_crossentropy")
        super(GANRegressor, self).build(input_shape)

    def _build_generator(self):
        generator = tf.keras.Sequential(name="generator")
        generator.add(
            tf.keras.layers.Dense(
                self.num_neurons,
                activation="selu",
                input_shape=[self.input_shape_parm],
                kernel_regularizer=l2(self.l2_reg),
            )
        )
        generator.add(tf.keras.layers.Dropout(self.dropout))
        for _ in range(self.depth - 1):
            generator.add(
                tf.keras.layers.Dense(
                    self.num_neurons, activation="selu", kernel_regularizer=l2(self.l2_reg)
                ),
            )
            generator.add(tf.keras.layers.Dropout(self.dropout))
        generator.add(tf.keras.layers.Dense(2 * self.output_shape_parm, activation=self.activation))
        return generator

    def _build_discriminator(self):
        discriminator = tf.keras.Sequential(name="discriminator")
        for _ in range(self.depth):
            discriminator.add(
                tf.keras.layers.Dense(
                    self.num_neurons, activation="selu", kernel_regularizer=l2(self.l2_reg)
                ),
            )
            discriminator.add(tf.keras.layers.Dropout(self.dropout))
        discriminator.add(tf.keras.layers.Dense(2, activation="softmax"))
        return discriminator

    def train_gan(
        self,
        X,
        y,
        batch_size,
        n_epochs,
        validation_split=0.2,
        verbose=1,
    ):
        """
        Train the GAN model.

        Parameters
        --------
        X : array-like
            Input data.
        y : array-like
            Target data.
        batch_size : int
            Number of samples in each batch.
        n_epochs : int
            Number of training epochs.
        validation_split : float, optional
            Fraction of the data to be used for validation.
        verbose : int, optional
            Verbosity level. Default is 1.

        Returns
        --------
        history : pd.DataFrame
            Training history.
        """
        loss_history = []
        for epoch in tqdm(range(n_epochs)):
            batch_starts = np.arange(0, len(X), batch_size)
            for start in batch_starts:
                np.random.shuffle(batch_starts)
                end = start + batch_size
                X_batch = X[start:end]
                y_batch = y[start:end].reshape(-1, self.output_shape_parm)
                y_batch = np.concatenate((y_batch, y_batch**2), axis=1)
                X_batch = tf.cast(X_batch, "float32")
                noise = tf.random.normal(
                    shape=X_batch.shape, stddev=tf.math.reduce_std(X_batch, keepdims=False)
                )

                # Phase 1 - training the generator
                self.generator.train_on_batch(X_batch, y_batch)

                # Phase 2 - training the discriminator
                generated_y_fake = self.generator(noise)
                generated_y_real = self.generator(X_batch)
                fake_and_real = tf.concat([generated_y_fake, generated_y_real], axis=0)
                X_fake_and_real = tf.concat([noise, X_batch], axis=0)
                batch_size = int(fake_and_real.shape[0] / 2)
                indices_ = tf.constant([[0.0]] * batch_size + [[1.0]] * batch_size)[:, 0]
                indices_ = tf.cast(indices_, "int32")
                y1 = tf.one_hot(indices_, 2)
                self.gan.train_on_batch(X_fake_and_real, y1)

            loss = self._cal_loss(generated_y_real, y_batch)
            loss_history.append([epoch, loss])

        if verbose:
            X_batch, y_batch, X_batch_val, y_batch_val = self._train_and_val(
                X_batch, y_batch, validation_split=validation_split
            )
            generated_y = self.generator(X_batch)
            generated_y_val = self.generator(X_batch_val)
            y_pred = self.discriminator.predict(fake_and_real, verbose=0)
            y_pred = list(np.argmax(y_pred, axis=1))

            metrics = get_metrics(self._get_frame(indices_.numpy().tolist(), y_pred), "y", "y_pred")
            loss = self._cal_loss(generated_y, y_batch)
            loss_val = self._cal_loss(generated_y_val, y_batch_val)
            clear_output(wait=True)
            metrics_list = [
                ("Epoch", f"{epoch}"),
                ("Loss", f"{loss:.2f} / {loss_val:.2f}"),
                ("Accuracy", f"{metrics['accuracy']:.2f} / {metrics['accuracy']:.2f}"),
                ("Precision", f"{metrics['precision']:.2f} / {metrics['precision']:.2f}"),
                ("Recall", f"{metrics['recall']:.2f} / {metrics['recall']:.2f}"),
                ("F1 Score", f"{metrics['f1_score']:.2f} / {metrics['f1_score']:.2f}"),
                ("Kappa", f"{metrics['kappa']:.2f} / {metrics['kappa']:.2f}"),
            ]

            metric_width = 15
            value_width = 30

            header = f"| {'Metric':<{metric_width}} | {'Value':<{value_width}} |"
            separator = "+" + "-" * (len(header) - 2) + "+"

            print(separator)
            print(header)
            print(separator)

            for metric_name, metric_values in metrics_list:
                data_row = f"| {metric_name:<{metric_width}} | {metric_values:<{value_width}} |"
                print(data_row)

            print(separator)

        return pd.DataFrame(loss_history, columns=["epoch", "loss"])

    def _get_frame(self, y, y_pred):
        df = pd.DataFrame()
        df["y"] = y
        df["y_pred"] = y_pred
        return df

    def _train_and_val(self, X, y, validation_split):
        split = int((1 - validation_split) * len(X))

        if len(X) > split and split > 0:
            X_train = X[:split]
            y_train = y[:split]
            X_val = X[split:]
            y_val = y[split:]
        else:
            X_train = X
            y_train = y
            X_val = X
            y_val = y

        X_train = tf.cast(X_train, "float32")
        X_val = tf.cast(X_val, "float32")

        return X_train, y_train, X_val, y_val

    def _cal_loss(self, generated, y):
        return tf.math.reduce_mean(100 * abs((y - generated) / y), keepdims=False).numpy()

    def train_gen(
        self,
        X_train,
        y_train,
        batch_size,
        n_epochs,
        validation_split=0.2,
        patience=3,
    ):
        """
        Train the generator model.

        Parameters
        --------
        X_train : array-like
            Training data.
        y_train : array-like
            Training target data.
        batch_size : int
            Batch size for training.
        n_epochs : int
            Number of epochs for training.
        validation_split : float, optional
            Fraction of data to use for validation. Default is 0.2.
        patience : int, optional
            Number of epochs to wait before early stopping. Default is 3.

        Returns
        --------
        history : pd.DataFrame
            Training history.
        """
        callback = tf.keras.callbacks.EarlyStopping(monitor="val_loss", patience=patience)
        # Prepare the target by extending it with its square
        self.discriminator.trainable = False
        y_train_extended = np.concatenate(
            (
                y_train.reshape(-1, self.output_shape_parm),
                y_train.reshape(-1, self.output_shape_parm) ** 2,
            ),
            axis=1,
        )

        history = self.generator.fit(
            X_train,
            y_train_extended,
            epochs=n_epochs,
            batch_size=batch_size,
            verbose=0,
            validation_split=validation_split,
            callbacks=[callback],
        )

        return pd.DataFrame(history.history)

    def call(self, inputs):
        return self.generator(inputs)[:, 0]

    def get_config(self):
        config = {
            "input_shape_parm": self.input_shape_parm,
            "output_shape_parm": self.output_shape_parm,
            "num_neurons": self.num_neurons,
            "activation": self.activation,
            "depth": self.depth,
            "dropout": self.dropout,
            "generator": self.generator,
            "discriminator": self.discriminator,
            "gan": self.gan,
            "l2_reg": self.l2_reg,
            "optimizer": self.optimizer,
        }
        base_config = super(GANRegressor, self).get_config()
        return dict(list(base_config.items()) + list(config.items()))

    @classmethod
    def from_config(cls, config):
        return cls(
            input_shape_parm=config["input_shape_parm"],
            output_shape_parm=config["output_shape_parm"],
            num_neurons=config["num_neurons"],
            activation=config["activation"],
            depth=config["depth"],
            dropout=config["dropout"],
            generator=config["generator"],
            discriminator=config["discriminator"],
            gan=config["gan"],
            l2_reg=config["l2_reg"],
            optimizer=config["optimizer"],
        )

GANRegressor is a custom Keras model that combines a generator and a discriminator

Ancestors

  • keras.src.models.model.Model
  • keras.src.backend.tensorflow.trainer.TensorFlowTrainer
  • keras.src.trainers.trainer.Trainer
  • keras.src.layers.layer.Layer
  • keras.src.backend.tensorflow.layer.TFLayer
  • keras.src.backend.tensorflow.trackable.KerasAutoTrackable
  • tensorflow.python.trackable.autotrackable.AutoTrackable
  • tensorflow.python.trackable.base.Trackable
  • keras.src.ops.operation.Operation
  • keras.src.saving.keras_saveable.KerasSaveable

Static methods

def from_config(config)

Creates an operation from its config.

This method is the reverse of get_config, capable of instantiating the same operation from the config dictionary.

Note: If you override this method, you might receive a serialized dtype config, which is a dict. You can deserialize it as follows:

if "dtype" in config and isinstance(config["dtype"], dict):
    policy = dtype_policies.deserialize(config["dtype"])

Args

config
A Python dictionary, typically the output of get_config.

Returns

An operation instance.

Methods

def build(self, input_shape)
Expand source code
def build(self, input_shape):
    self.gan = tf.keras.models.Sequential([self.generator, self.discriminator], name="gan")

    self.generator.compile(
        optimizer=self.optimizer,
        loss=tf.keras.losses.MeanAbsolutePercentageError(),
        metrics=[tf.keras.metrics.MeanAbsolutePercentageError()],
    )

    self.discriminator.compile(
        optimizer=self.optimizer, loss="binary_crossentropy", metrics=["accuracy"]
    )

    self.gan.compile(optimizer=self.optimizer, loss="binary_crossentropy")
    super(GANRegressor, self).build(input_shape)
def call(self, inputs)
Expand source code
def call(self, inputs):
    return self.generator(inputs)[:, 0]
def get_config(self)
Expand source code
def get_config(self):
    config = {
        "input_shape_parm": self.input_shape_parm,
        "output_shape_parm": self.output_shape_parm,
        "num_neurons": self.num_neurons,
        "activation": self.activation,
        "depth": self.depth,
        "dropout": self.dropout,
        "generator": self.generator,
        "discriminator": self.discriminator,
        "gan": self.gan,
        "l2_reg": self.l2_reg,
        "optimizer": self.optimizer,
    }
    base_config = super(GANRegressor, self).get_config()
    return dict(list(base_config.items()) + list(config.items()))

Returns the config of the object.

An object config is a Python dictionary (serializable) containing the information needed to re-instantiate it.

def train_gan(self, X, y, batch_size, n_epochs, validation_split=0.2, verbose=1)
Expand source code
def train_gan(
    self,
    X,
    y,
    batch_size,
    n_epochs,
    validation_split=0.2,
    verbose=1,
):
    """
    Train the GAN model.

    Parameters
    --------
    X : array-like
        Input data.
    y : array-like
        Target data.
    batch_size : int
        Number of samples in each batch.
    n_epochs : int
        Number of training epochs.
    validation_split : float, optional
        Fraction of the data to be used for validation.
    verbose : int, optional
        Verbosity level. Default is 1.

    Returns
    --------
    history : pd.DataFrame
        Training history.
    """
    loss_history = []
    for epoch in tqdm(range(n_epochs)):
        batch_starts = np.arange(0, len(X), batch_size)
        for start in batch_starts:
            np.random.shuffle(batch_starts)
            end = start + batch_size
            X_batch = X[start:end]
            y_batch = y[start:end].reshape(-1, self.output_shape_parm)
            y_batch = np.concatenate((y_batch, y_batch**2), axis=1)
            X_batch = tf.cast(X_batch, "float32")
            noise = tf.random.normal(
                shape=X_batch.shape, stddev=tf.math.reduce_std(X_batch, keepdims=False)
            )

            # Phase 1 - training the generator
            self.generator.train_on_batch(X_batch, y_batch)

            # Phase 2 - training the discriminator
            generated_y_fake = self.generator(noise)
            generated_y_real = self.generator(X_batch)
            fake_and_real = tf.concat([generated_y_fake, generated_y_real], axis=0)
            X_fake_and_real = tf.concat([noise, X_batch], axis=0)
            batch_size = int(fake_and_real.shape[0] / 2)
            indices_ = tf.constant([[0.0]] * batch_size + [[1.0]] * batch_size)[:, 0]
            indices_ = tf.cast(indices_, "int32")
            y1 = tf.one_hot(indices_, 2)
            self.gan.train_on_batch(X_fake_and_real, y1)

        loss = self._cal_loss(generated_y_real, y_batch)
        loss_history.append([epoch, loss])

    if verbose:
        X_batch, y_batch, X_batch_val, y_batch_val = self._train_and_val(
            X_batch, y_batch, validation_split=validation_split
        )
        generated_y = self.generator(X_batch)
        generated_y_val = self.generator(X_batch_val)
        y_pred = self.discriminator.predict(fake_and_real, verbose=0)
        y_pred = list(np.argmax(y_pred, axis=1))

        metrics = get_metrics(self._get_frame(indices_.numpy().tolist(), y_pred), "y", "y_pred")
        loss = self._cal_loss(generated_y, y_batch)
        loss_val = self._cal_loss(generated_y_val, y_batch_val)
        clear_output(wait=True)
        metrics_list = [
            ("Epoch", f"{epoch}"),
            ("Loss", f"{loss:.2f} / {loss_val:.2f}"),
            ("Accuracy", f"{metrics['accuracy']:.2f} / {metrics['accuracy']:.2f}"),
            ("Precision", f"{metrics['precision']:.2f} / {metrics['precision']:.2f}"),
            ("Recall", f"{metrics['recall']:.2f} / {metrics['recall']:.2f}"),
            ("F1 Score", f"{metrics['f1_score']:.2f} / {metrics['f1_score']:.2f}"),
            ("Kappa", f"{metrics['kappa']:.2f} / {metrics['kappa']:.2f}"),
        ]

        metric_width = 15
        value_width = 30

        header = f"| {'Metric':<{metric_width}} | {'Value':<{value_width}} |"
        separator = "+" + "-" * (len(header) - 2) + "+"

        print(separator)
        print(header)
        print(separator)

        for metric_name, metric_values in metrics_list:
            data_row = f"| {metric_name:<{metric_width}} | {metric_values:<{value_width}} |"
            print(data_row)

        print(separator)

    return pd.DataFrame(loss_history, columns=["epoch", "loss"])

Train the GAN model.

Parameters

X : array-like
Input data.
y : array-like
Target data.
batch_size : int
Number of samples in each batch.
n_epochs : int
Number of training epochs.
validation_split : float, optional
Fraction of the data to be used for validation.
verbose : int, optional
Verbosity level. Default is 1.

Returns

history : pd.DataFrame
Training history.
def train_gen(self, X_train, y_train, batch_size, n_epochs, validation_split=0.2, patience=3)
Expand source code
def train_gen(
    self,
    X_train,
    y_train,
    batch_size,
    n_epochs,
    validation_split=0.2,
    patience=3,
):
    """
    Train the generator model.

    Parameters
    --------
    X_train : array-like
        Training data.
    y_train : array-like
        Training target data.
    batch_size : int
        Batch size for training.
    n_epochs : int
        Number of epochs for training.
    validation_split : float, optional
        Fraction of data to use for validation. Default is 0.2.
    patience : int, optional
        Number of epochs to wait before early stopping. Default is 3.

    Returns
    --------
    history : pd.DataFrame
        Training history.
    """
    callback = tf.keras.callbacks.EarlyStopping(monitor="val_loss", patience=patience)
    # Prepare the target by extending it with its square
    self.discriminator.trainable = False
    y_train_extended = np.concatenate(
        (
            y_train.reshape(-1, self.output_shape_parm),
            y_train.reshape(-1, self.output_shape_parm) ** 2,
        ),
        axis=1,
    )

    history = self.generator.fit(
        X_train,
        y_train_extended,
        epochs=n_epochs,
        batch_size=batch_size,
        verbose=0,
        validation_split=validation_split,
        callbacks=[callback],
    )

    return pd.DataFrame(history.history)

Train the generator model.

Parameters

X_train : array-like
Training data.
y_train : array-like
Training target data.
batch_size : int
Batch size for training.
n_epochs : int
Number of epochs for training.
validation_split : float, optional
Fraction of data to use for validation. Default is 0.2.
patience : int, optional
Number of epochs to wait before early stopping. Default is 3.

Returns

history : pd.DataFrame
Training history.