sintel-dev / Orion

Library for detecting anomalies in signals
https://sintel.dev/Orion/
MIT License
1.05k stars 162 forks source link

TadGAN with Tensorflow 2.3 support #143

Closed blaskowitz100 closed 1 year ago

blaskowitz100 commented 3 years ago

Hello everyone,

thank you for this nice module and the good examples for the TadGAN. I wonder if anyone had success so far get the TadGAN model running under Tensorflow 2 instead of 1.15?

I tried a lot so far but had no luck.

Best regards, Sebastian

sarahmish commented 3 years ago

Hi @blaskowitz100

Thanks for the comment, right now we only support running on tensorflow>=1.11.0,<2 we’re planning to release a new version soon that supports tensorflow>=2.

blaskowitz100 commented 3 years ago

Hello @sarahmish ,

thank you for your feedback. If I made some progress on my implementation I will let you know.

AKJHK2021 commented 3 years ago

Hello @sarahmish ,

thank you for your feedback. If I made some progress on my implementation I will let you know.

Hello @blaskowitz100, can you please share your version for Tensorflow 2.3... Thanks

blaskowitz100 commented 3 years ago

Hello @sarahmish and @A2015Albasir ,

here are my current version I was able to run in TF2.3 and TF2.4. It uses the Subclassing API and can handle multivariate time series data. This means the model can be trained using the fit/predict function like a simple Keras model. The rest of the architecture should be the same as in the TadGAN paper.

import os
from typing import Tuple
import numpy as np
import tensorflow as tf

def _wasserstein_loss(y_true, y_pred):
    return tf.keras.backend.mean(y_true * y_pred)

class TadGAN(tf.keras.Model):
    """
    TadGAN based on the idea of https://arxiv.org/pdf/2009.07769.pdf

    The model can be used for anomaly detection in univariate & multivariate time series based on a GAN network architecture.

    This GAN architecture uses a encoder-generator network in combination with two discriminator networks (critics).
    One for the encoding of the incoming time series and the other to discriminate the learned embedding.

    This implementation uses the new TensorFlow 2 / Keras Subclassing API instead of a simple Python class.
    """
    def __init__(
        self,
        # Time series hyper parameters
        ts_input_shape: Tuple[int] = (100, 1),
        latent_dim: int = 20,
        gradient_penelty_weight: int = 10,
        n_iterations_critic: int = 5,

        # sub network hyper parameters
        encoder_lstm_units: int = 100,
        generator_lstm_units: int = 100,
        generator_output_activation: str = "tanh",
        critic_x_cnn_blocks: int = 4,
        critic_x_cnn_filters: int = 64,
        critic_z_dense_units: int = 100,

        log_all_losses: bool = True,
        print_model_summaries: bool = False
    ):
        super(TadGAN, self).__init__()

        # Parse default variables
        self.latent_dim = latent_dim
        self.ts_input_shape = ts_input_shape
        self.signal_length = ts_input_shape[0]
        self.n_channels = ts_input_shape[1]
        self.gradient_penelty_weight = gradient_penelty_weight
        self.n_iterations_critic = n_iterations_critic
        self.log_all_losses = log_all_losses

        # TadGAN encoder
        self.encoder_lstm_units = encoder_lstm_units
        self.encoder = self._build_encoder(lstm_units=self.encoder_lstm_units)

        # TadGAN generator
        self.generator_lstm_units = generator_lstm_units
        self.generator_act_fn = generator_output_activation
        self.generator = self._build_generator(generator_lstm_units=self.generator_lstm_units, output_activation=generator_output_activation)

        # TadGAN critic x
        self.critic_x_cnn_filters = critic_x_cnn_filters
        self.critic_x_cnn_blocks = critic_x_cnn_blocks
        self.critic_x = self._build_critic_x(n_cnn_filters=self.critic_x_cnn_filters, n_cnn_blocks=self.critic_x_cnn_blocks)

        # TadGAN critic z
        self.critic_z_dense_units = critic_z_dense_units
        self.critic_z = self._build_critic_z(critic_z_dense_units=self.critic_z_dense_units)

        if print_model_summaries:
            print(self.encoder.summary())
            print(self.generator.summary())
            print(self.critic_x.summary())
            print(self.critic_z.summary())

        self.build(input_shape=(None, ts_input_shape[0], ts_input_shape[1]))

    def get_config(self) -> dict:
        """
        Build a config dict for the custom attributes of this subclassing Keras model

        :return: Config as Python dict
        """
        return dict(
            ts_input_shape=self.ts_input_shape,
            latent_dim=self.latent_dim,
            gradient_penelty_weight=self.gradient_penelty_weight,
            n_iterations_critic=self.n_iterations_critic,
            log_all_losses=self.log_all_losses,

            # Hyperparameters
            encoder_lstm_units=self.encoder_lstm_units,
            generator_lstm_units=self.generator_lstm_units,
            critic_x_cnn_filters=self.critic_x_cnn_filters,
            critic_x_cnn_blocks=self.critic_x_cnn_blocks,
            critic_z_dense_units=self.critic_z_dense_units
        )

    def compile(
        self,
        critic_x_optimizer,
        critic_z_optimizer,
        encoder_generator_optimizer,
        critic_x_loss_fn,
        critic_z_loss_fn,
        encoder_generator_loss_fn,
        **kwargs
    ):
        """
        Customized Keras model compilation based on the idea of https://keras.io/examples/generative/wgan_gp/

        :param critic_x_optimizer: Keras optimizer for the critic x model
        :param critic_z_optimizer: Keras optimizer for the critic z model
        :param encoder_generator_optimizer: Keras optimizer for the encoder & generator model
        :param critic_x_loss_fn: Loss function for the critic x model
        :param critic_z_loss_fn: Loss function for the critic z model
        :param encoder_generator_loss_fn: Loss function for the generator & encoder model

        :param kwargs: Additional kwargs forwarded to the super class

        :return: None
        """
        super(TadGAN, self).compile(**kwargs)

        self.critic_x_optimizer = critic_x_optimizer
        self.critic_z_optimizer = critic_z_optimizer
        self.encoder_generator_optimizer = encoder_generator_optimizer

        self.critic_x_loss_fn = critic_x_loss_fn
        self.critic_z_loss_fn = critic_z_loss_fn
        self.encoder_generator_loss_fn = encoder_generator_loss_fn

    def _build_encoder(self, lstm_units: int = 100):
        """
        Build the Encoder subnetwork for the GAN. This model learns the compressed representation of the input
        time series.
        The encoder uses a single layer BI-LSTM network to learn the compressed representation.
        The number of LSTM units can be adjusted.

        :param lstm_units: Number of LSTM units that could be used for the time series encoding

        :return: Encoder model
        """
        x = tf.keras.layers.Input(shape=self.ts_input_shape, name="encoder_input")

        # Encode the sequence and extend its dimensions
        encoded = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(units=lstm_units, return_sequences=True))(x)
        encoded = tf.keras.layers.Flatten()(encoded)
        encoded = tf.keras.layers.Dense(units=self.latent_dim, name="latent_encoding")(encoded)
        encoded = tf.keras.layers.Reshape(target_shape=(self.latent_dim, 1), name='output_encoder')(encoded)

        model = tf.keras.Model(inputs=x, outputs=encoded, name="encoder_model")

        return model

    def _build_generator(self, generator_lstm_units: int = 100, output_activation: str = "tanh") -> tf.keras.Model:
        """
        Build the Generator model for the GAN. This model uses the compressed representation of the encoder and
        tries to reconstruct the original time series from it.
        At the moment a two-layer Bi-LSTM network is used for the reconstruction.

        :param generator_lstm_units: Number of LSTM units that should be used for the reconstruction.
        :param output_activation: The final activation of the generator. Choose with respect to the preprocesssing (scaling/normalizing)
                       of the input time series

        :return: Generator model
        """
        x = tf.keras.layers.Input(shape=(self.latent_dim, 1), name="generator_input")

        # Remove additional dimensions from the latent embedding
        decoded = tf.keras.layers.Flatten()(x)

        # Check if the sequence length is a even number (this is required for this model architecture)
        if self.signal_length % 2 == 1:
            raise ValueError(f"The signal length needs to be even (current signal length: {self.signal_length})")

        # Build the first layer of the generator that should be half the size of the sequence length
        half_seq_length = self.signal_length // 2
        decoded = tf.keras.layers.Dense(units=half_seq_length)(decoded)
        decoded = tf.keras.layers.Reshape(target_shape=(half_seq_length, 1))(decoded)

        # Generation of a new time series using LSTM in combination with up sampling
        decoded = tf.keras.layers.Bidirectional(
            tf.keras.layers.LSTM(units=generator_lstm_units, return_sequences=True, dropout=0.2, recurrent_dropout=0.2),
            merge_mode='concat'
        )(decoded)
        decoded = tf.keras.layers.UpSampling1D(2)(decoded)
        decoded = tf.keras.layers.Bidirectional(
            tf.keras.layers.LSTM(units=generator_lstm_units, return_sequences=True, dropout=0.2, recurrent_dropout=0.2),
            merge_mode='concat'
        )(decoded)

        # Rebuild the original time series signal for all channels
        decoded = tf.keras.layers.TimeDistributed(tf.keras.layers.Dense(self.n_channels))(decoded)
        decoded = tf.keras.layers.Activation(activation=output_activation)(decoded)

        model = tf.keras.Model(inputs=x, outputs=decoded, name="generator_model")

        return model

    def _build_critic_x(self, n_cnn_filters: int = 64, n_cnn_blocks: int = 4) -> tf.keras.Model:
        """
        Build the critic x network that learns to differ between a fake and real input sequence. The classifier uses a
        stack of 1D CNN block (CNN + leaky relu + dropout) and a final fully connected classifier network.

        :return: Critic x model
        """

        x = tf.keras.layers.Input(shape=self.ts_input_shape, name="critic_x_input")

        if n_cnn_blocks < 1:
            raise ValueError(f"The number of CNN blocks needs to be greater than 1 (current value: {n_cnn_blocks})")

        y = tf.keras.layers.Conv1D(filters=n_cnn_filters, kernel_size=5)(x)
        y = tf.keras.layers.LeakyReLU(alpha=0.2)(y)
        y = tf.keras.layers.Dropout(rate=0.25)(y)

        if n_cnn_blocks > 1:
            for i in range(n_cnn_blocks - 1):
                y = tf.keras.layers.Conv1D(filters=n_cnn_filters, kernel_size=5)(y)
                y = tf.keras.layers.LeakyReLU(alpha=0.2)(y)
                y = tf.keras.layers.Dropout(rate=0.25)(y)

        y = tf.keras.layers.Flatten()(y)
        y = tf.keras.layers.Dense(1)(y)

        model = tf.keras.Model(inputs=x, outputs=y, name="critic_x_model")

        return model

    def _build_critic_z(self, critic_z_dense_units: int = 100) -> tf.keras.Model:
        """
        Build the critic z model that learns to differ between a real and a fake encoding coming from the encoder.
        The network works with a two-layer fully connected network in combination with a leaky RELU activation
        and dropout regularization.

        :param critic_z_dense_units: Number of units for each of the fully connected layers

        :return: Critic z model
        """

        x = tf.keras.layers.Input(shape=(self.latent_dim, 1), name="critic_z_input")
        y = tf.keras.layers.Flatten()(x)

        y = tf.keras.layers.Dense(units=critic_z_dense_units)(y)
        y = tf.keras.layers.LeakyReLU(alpha=0.2)(y)
        y = tf.keras.layers.Dropout(rate=0.2)(y)

        y = tf.keras.layers.Dense(units=critic_z_dense_units)(y)
        y = tf.keras.layers.LeakyReLU(alpha=0.2)(y)
        y = tf.keras.layers.Dropout(rate=0.2)(y)

        y = tf.keras.layers.Dense(1)(y)

        model = tf.keras.Model(inputs=x, outputs=y, name="critic_z_model")

        return model

    @tf.function
    def critic_x_gradient_penalty(self, batch_size, y_true, y_pred):
        """
        Calculates the gradient penalty.
        """
        alpha = tf.keras.backend.random_uniform((batch_size, 1, 1))
        interpolated = (alpha * y_true) + ((1 - alpha) * y_pred)

        with tf.GradientTape() as gp_tape:
            gp_tape.watch(interpolated)
            # 1. Get the discriminator output for this interpolated image.
            pred = self.critic_x(interpolated)

        grads = gp_tape.gradient(pred, [interpolated])[0]
        norm = tf.sqrt(tf.reduce_sum(tf.square(grads), axis=[1, 2]))
        gp = tf.reduce_mean((norm - 1.0) ** 2)

        return gp

    @tf.function
    def critic_z_gradient_penalty(self, batch_size, y_true, y_pred):
        """
        Calculates the gradient penalty.
        """
        alpha = tf.keras.backend.random_uniform((batch_size, 1, 1))
        interpolated = (alpha * y_true) + ((1 - alpha) * y_pred)

        with tf.GradientTape() as gp_tape:
            gp_tape.watch(interpolated)
            # 1. Get the discriminator output for this interpolated image.
            pred = self.critic_z(interpolated)

        # 2. Calculate the gradients w.r.t to this interpolated image.
        grads = gp_tape.gradient(pred, [interpolated])[0]
        norm = tf.sqrt(tf.reduce_sum(tf.square(grads), axis=[1, 2]))
        gp = tf.reduce_mean((1.0 - norm) ** 2)

        return gp

    @tf.function
    def _critic_x_loss(self, x_mb, z, valid, fake, mini_batch_size):
        """
        Do a step forward and calculate the loss on the critic x model

        :param x_mb: Minibatch of input data
        :param z: Minibatch of random noise
        :param valid: Ground truth vector for valid samples
        :param fake: Ground truth vector for fake samples
        :param mini_batch_size:

        :return: A tuple containing the total loss and the three single losses
        """
        # Do a step forward on critic x model and collect gradients
        x_ = self.generator(z)
        fake_x = self.critic_x(x_)
        valid_x = self.critic_x(x_mb)

        # Calculate critic x loss
        critic_x_valid_cost = self.critic_x_loss_fn(y_true=valid, y_pred=valid_x)
        critic_x_fake_cost = self.critic_x_loss_fn(y_true=fake, y_pred=fake_x)
        # TODO: [SMe] Is the mini_batch size still required?
        critic_x_gradient_penalty = self.critic_x_gradient_penalty(mini_batch_size, x_mb, x_)
        critic_x_total_loss = critic_x_valid_cost + critic_x_fake_cost + (critic_x_gradient_penalty * self.gradient_penelty_weight)

        return critic_x_total_loss, critic_x_valid_cost, critic_x_fake_cost, critic_x_gradient_penalty

    @tf.function
    def _critic_z_loss(self, x_mb, z, valid, fake, mini_batch_size):
        """
        Do a step forward and calculate the loss on the critic z model

        :param x_mb: Minibatch of input data
        :param z: Minibatch of random noise
        :param valid: Ground truth vector for valid samples
        :param fake: Ground truth vector for fake samples
        :param mini_batch_size:

        :return: A tuple containing the total loss and the three single losses
        """
        # Do a step forward on critic z model and collect gradients
        z_ = self.encoder(x_mb)
        fake_z = self.critic_z(z_)
        valid_z = self.critic_z(z)

        # Calculate critic z loss
        critic_z_valid_cost = self.critic_z_loss_fn(y_true=valid, y_pred=valid_z)
        critic_z_fake_cost = self.critic_z_loss_fn(y_true=fake, y_pred=fake_z)
        critic_z_gradient_penalty = self.critic_z_gradient_penalty(mini_batch_size, z, z_)
        critic_z_total_loss = critic_z_valid_cost + critic_z_fake_cost + (critic_z_gradient_penalty * self.gradient_penelty_weight)

        return critic_z_total_loss, critic_z_valid_cost, critic_z_fake_cost, critic_z_gradient_penalty

    @tf.function
    def _encoder_generator_loss(self, x_mb, z, valid):
        """
        Do a step forward and calculate the loss on the encoder-generator model

        :param x_mb: Minibatch of input data
        :param z: Minibatch of random noise
        :param valid: Ground truth vector for valid samples

        :return: A tuple containing the total loss and the three single losses
        """
        # Do a step forward on the encoder generator model
        x_gen_ = self.generator(z)
        fake_gen_x = self.critic_x(x_gen_)

        z_gen_ = self.encoder(x_mb)
        x_gen_rec = self.generator(z_gen_)
        fake_gen_z = self.critic_z(z_gen_)

        # Calculate encoder generator loss
        encoder_generator_fake_gen_x_cost = self.encoder_generator_loss_fn(y_true=valid, y_pred=fake_gen_x)
        encoder_generator_fake_gen_z_cost = self.encoder_generator_loss_fn(y_true=valid, y_pred=fake_gen_z)

        # Use simple MSE as reconstruction error
        general_reconstruction_cost = tf.reduce_mean(tf.square((x_mb - x_gen_rec)))
        encoder_generator_total_loss = encoder_generator_fake_gen_x_cost + encoder_generator_fake_gen_z_cost + (10.0 * general_reconstruction_cost)

        return encoder_generator_total_loss, encoder_generator_fake_gen_x_cost, encoder_generator_fake_gen_z_cost, general_reconstruction_cost

    @tf.function
    def train_step(self, X) -> dict:
        """
        Custom training step for this Subclassing API Keras model.
        The shape should be (n_iterations_critic * batch size, n_channels) because the critic networks are trained
        multiple times over the encoder-generator network.

        :param X: Group of mini batches that are used to train the critics and the encoder-generator network
                  Shape: (batch_size, signal_length, n_channels)

        :return: Sub-model losses as los dict
        """
        if isinstance(X, tuple):
            X = X[0]

        batch_size = X.shape[0]
        mini_batch_size = batch_size // self.n_iterations_critic

        # Prepare the ground truth data
        fake = tf.ones((mini_batch_size, 1))
        valid = -tf.ones((mini_batch_size, 1))

        critic_x_loss_steps = []
        critic_z_loss_steps = []

        # Train the critics multiple steps more then the encoder-generator model
        for critic_train_step in range(self.n_iterations_critic):
            z = tf.random.normal(shape=(mini_batch_size, self.latent_dim, 1))
            x_mb = X[critic_train_step * mini_batch_size: (critic_train_step + 1) * mini_batch_size]

            # Optimize step on critic x
            with tf.GradientTape() as tape:
                # Do a step forward on critic x model and collect gradients
                _critic_x_losses = self._critic_x_loss(x_mb, z, valid, fake, mini_batch_size)

            # Backward step with updating critic x weights
            critic_x_gradient = tape.gradient(_critic_x_losses[0], self.critic_x.trainable_variables)
            self.critic_x_optimizer.apply_gradients(zip(critic_x_gradient, self.critic_x.trainable_variables))

            # Collect summaries for logging
            _critic_x_losses = np.array(_critic_x_losses)
            critic_x_loss_steps.append(_critic_x_losses)

            # Optimize step on critic z
            with tf.GradientTape() as tape:
                # Do a step forward on critic z model and collect gradients
                _critic_z_losses = self._critic_z_loss(x_mb, z, valid, fake, mini_batch_size)

            # Backward step with updating critic z weights
            critic_z_gradient = tape.gradient(_critic_z_losses[0], self.critic_z.trainable_variables)
            self.critic_z_optimizer.apply_gradients(zip(critic_z_gradient, self.critic_z.trainable_variables))

            # Collect summaries for logging
            _critic_z_losses = np.array(_critic_z_losses)
            critic_z_loss_steps.append(_critic_z_losses)

        # Optimize step on encoder & generator and collect gradients
        with tf.GradientTape() as tape:
            # Do a step forward on the encoder generator model
            _encoder_generator_losses = self._encoder_generator_loss(x_mb, z, valid)

        # Backward step with updating encoder generator weights
        encoder_generator_gradient = tape.gradient(_encoder_generator_losses, self.encoder.trainable_variables + self.generator.trainable_variables)
        self.encoder_generator_optimizer.apply_gradients(zip(encoder_generator_gradient, self.encoder.trainable_variables + self.generator.trainable_variables))

        # Collect summaries for logging
        critic_x_losses = np.mean(np.array(critic_x_loss_steps), axis=0)
        critic_z_losses = np.mean(np.array(critic_z_loss_steps), axis=0)
        encoder_generator_losses = np.array(_encoder_generator_losses)

        if self.log_all_losses:
            loss_dict = {
                "Cx_total": critic_x_losses[0],
                "Cx_valid": critic_x_losses[1],
                "Cx_fake": critic_x_losses[2],
                "Cx_gp_penalty": critic_x_losses[3],

                "Cz_total": critic_z_losses[0],
                "Cz_valid": critic_z_losses[1],
                "Cz_fake": critic_z_losses[2],
                "Cz_gp_penalty": critic_z_losses[3],

                "EG_total": encoder_generator_losses[0],
                "EG_fake_gen_x": encoder_generator_losses[1],
                "EG_fake_gen_z": encoder_generator_losses[2],
                "G_rec": encoder_generator_losses[3],
            }
        else:
            loss_dict = {
                "Cx_total": critic_x_losses[0],
                "Cz_total": critic_z_losses[0],
                "EG_total": encoder_generator_losses[0]
            }

        return loss_dict

    @tf.function
    def test_step(self, X):
        """
        Custom test step for this Subclassing API Keras model.
        This overrides the default behavior of the model.evaluate() function.

        :param X: Minibatch of time series signals (batch_size, signal_length, n_channels)

        :return: Sub-model losses as los dict
        """

        if isinstance(X, tuple):
            X = X[0]

        batch_size = X.shape[0]

        # Prepare the ground truth data
        fake = tf.ones((batch_size, 1))
        valid = -tf.ones((batch_size, 1))

        z = tf.random.normal(shape=(batch_size, self.latent_dim, 1))

        critic_x_losses = self._critic_x_loss(X, z, valid, fake, batch_size)
        critic_z_losses = self._critic_z_loss(X, z, valid, fake, batch_size)
        encoder_generator_losses = self._encoder_generator_loss(X, z, valid)

        if self.log_all_losses:
            loss_dict = {
                "Cx_total": critic_x_losses[0],
                "Cx_valid": critic_x_losses[1],
                "Cx_fake": critic_x_losses[2],
                "Cx_gp_penalty": critic_x_losses[3],

                "Cz_total": critic_z_losses[0],
                "Cz_valid": critic_z_losses[1],
                "Cz_fake": critic_z_losses[2],
                "Cz_gp_penalty": critic_z_losses[3],

                "EG_total": encoder_generator_losses[0],
                "EG_fake_gen_x": encoder_generator_losses[1],
                "EG_fake_gen_z": encoder_generator_losses[2],
                "G_rec": encoder_generator_losses[3],
            }
        else:
            loss_dict = {
                "Cx_total": critic_x_losses[0],
                "Cz_total": critic_z_losses[0],
                "EG_total": encoder_generator_losses[0]
            }

        return loss_dict

    @tf.function
    def call(self, X, **kwargs) -> Tuple[np.array, np.array, np.array, np.array]:
        """
        Default forward step during the inference step of the model (for training and evaluation see train_step() and test_step()).
        This function will be called in the model.predict() function to use the trained sub networks for anomaly detection.

        :param X: Batch of signals that should be analyzed by the model (batch_size, signal_length, n_channels)

        :param kwargs: Additional kwargs forwarded to the super class fit method

        :return: Tuple containing the outputs of the sub networks as numpy arrays:
                 - The reconstructed signals from the generator
                 - The compressed embedding of the time series (latent_dim, 1) from the encoder
                 - The fake/real classification result for the reconstructed time series from the critic x network
                 - The fake/real classification result for the learned embedding from the critic z network
        """
        latent_encoding = self.encoder(X)
        y_hat = self.generator(latent_encoding)
        critic_x = self.critic_x(X)
        critic_z = self.critic_z(latent_encoding)
        return y_hat, latent_encoding, critic_x, critic_z

    def fit(
        self,
        x=None, y=None, batch_size=None, epochs=1, verbose=1, callbacks=None,
        validation_split=0., validation_data=None, shuffle=True,
        class_weight=None, sample_weight=None,
        initial_epoch=0, steps_per_epoch=None, validation_steps=None, validation_batch_size=None, validation_freq=1,
        max_queue_size=10, workers=1, use_multiprocessing=False
    ):
        """
        Extends the orignal fit method of the keras.Model API with some checks for the train_step batch size requirements.
        See for https://www.tensorflow.org/api_docs/python/tf/keras/Model#fit more details

        :return: Keras model training history dict
        """
        # Adjust batch size to support multiple training steps on the critics
        if not isinstance(validation_data, tf.data.Dataset):
            if (validation_data is not None) and (validation_batch_size is None):
                validation_batch_size = batch_size

        if not isinstance(x, tf.data.Dataset):
            batch_size = batch_size * self.n_iterations_critic

        return super().fit(x, y, batch_size, epochs, verbose, callbacks, validation_split, validation_data, shuffle,
                           class_weight, sample_weight, initial_epoch, steps_per_epoch, validation_steps,
                           validation_batch_size, validation_freq, max_queue_size, workers, use_multiprocessing)

    @staticmethod
    def export_as_keras_model(tadgan_model, export_path: str):
        # Rebuild model graph
        x = tf.keras.layers.Input(shape=tadgan_model.ts_input_shape, name="ts_input")
        latent_encoding = tadgan_model.encoder(x)
        y_hat = tadgan_model.generator(latent_encoding)
        critic_x = tadgan_model.critic_x(x)
        critic_z = tadgan_model.critic_z(latent_encoding)

        # Export model
        standalone_model = tf.keras.models.Model(inputs=x, outputs=[y_hat, latent_encoding, critic_x, critic_z])
        standalone_model.save(export_path)
AKJHK2021 commented 3 years ago

@blaskowitz100 thanks man!

AKJHK2021 commented 3 years ago

@blaskowitz100 I run into some errors while training, can you share how you use the class please? Thanks

blaskowitz100 commented 3 years ago

@AKJHK2021 You can fit the model with a numpy array that has a shape of (n_samples, subsequence_length, features) or a similar prepared tf.data.Dataset. One thing is very important. The n_samples needs to be a multiple of the class attribute "n_iterations_critic".

Here is a small example for a tf.data.Dataset:

x_train_normal = ... # Numpy array with training data
x_val_normal = ... # Numpy array with validation data
batch_size = 32 # Model batch size that should be used to fit the model
predict_batch_size = 1024 # Bigger batch size to speed up model predictions

train_normal_ds = tf.data.Dataset.from_tensor_slices(x_train_normal).shuffle(x_train_normal.shape[0])
train_normal_ds = train_normal_ds.batch(batch_size * n_critic_train_iter, drop_remainder=True)

val_normal_ds = tf.data.Dataset.from_tensor_slices(x_val_normal).shuffle(x_val_normal.shape[0])
val_normal_ds = val_normal_ds.batch(predict_batch_size, drop_remainder=True)

You can then use this in the fit() function:

...
model.fit(
   train_normal_ds,
   epochs=100,
   validation_data=val_normal_ds
)
...

The prediction later on is very similar.

halkkuma commented 3 years ago

@blaskowitz100 The source code for this issue has not yet been merged, can I use it as an MIT license like any other Orion source code? Thanks

GiacomoSintoni commented 3 years ago

@blaskowitz100 I can't train the model with this line of code:

model.fit ( train_normal_ds, epochs = 100, validation_data = val_normal_ds )

Some error appears when I run it. Could you give me a complete example of training and signal prediction?

blaskowitz100 commented 3 years ago

@halkkuma For me it is not a problem and you can use the code in the same way like the other sources of this repository.

@GiacomoSintoni To run the code snippet from above it is important that the input numpy arrays have the shape [n_samples, n_features, n_timesteps] otherwise the training will fail. If you have still problems more information/details about the failure would be helpful.

ExtinctionEvent commented 2 years ago

I love this model, and greatly admire your work here. Can you comment on the compile step, in particular the loss functions for the critics and encoder/generators, but also suggestions for best optimisers, to go with Adam? as I note that the WassersteinGAN paper suggests RMSProp? Thanks

My-MacE commented 1 year ago

@blaskowitz100 Thank you for sharing your code. I have one question. I imported TadGAN class and set my custom data(4200000,20,1).

and I tried to fit the model.

But It doesn't work caues "name 'model' is not defined"

how can I make model using TadGAN class?

blaskowitz100 commented 1 year ago

Hi @My-MacE, do you have some more details on how you set up your model? Usally there is no need to define a separate model, because TadGAN is a subclass of a Keras model:

model = TadGAN(ts_input_shape=(20,1), ...)

My-MacE commented 1 year ago

@blaskowitz100 Thank you for your replying. Could you write more details when fit the model? I typed the code that you write

model input

model = TadGAN(ts_input_shape = (100, 1), latent_dim= 20, gradient_penelty_weight= 10, n_iterations_critic= 5,

sub network hyper parameters

    encoder_lstm_units= 100,
    generator_lstm_units= 100,
    generator_output_activation= "tanh",
    critic_x_cnn_blocks= 4,
    critic_x_cnn_filters= 64,
    critic_z_dense_units= 100,
    log_all_losses = True,
    print_model_summaries= False)

Here is my code

  1. I imported TadGAN class that you written in commented [on Jan 28, 2021]
  2. I made traning data set(x_train) using my custom data: Training input shape: (4749330, 100, 1))
  3. After making traning set, I typed your code to fit the model

x_train_normal = x_train[0:4274370] # Numpy array with training data x_val_normal = x_train[4274370:4749300] # Numpy array with validation data batch_size = 128 # Model batch size that should be used to fit the model predict_batch_size = 1024 # Bigger batch size to speed up model predictions

train_normal_ds = tf.data.Dataset.from_tensor_slices(x_train_normal).shuffle(x_train_normal.shape[0]) train_normal_ds = train_normal_ds.batch(batch_size 33394, drop_remainder=True) # I replaced n_critic_train_iter with 33394 cause I think my tranning size is 4274370(12833394) Am I right?

val_normal_ds = tf.data.Dataset.from_tensor_slices(x_val_normal).shuffle(x_val_normal.shape[0]) val_normal_ds = val_normal_ds.batch(predict_batch_size, drop_remainder=True)

and I ran this code

model.fit( train_normal_ds, epochs=1, validation_data=val_normal_ds )

but compile error came out. ->

You must compile your model before training/testing. Use model.compile(optimizer, loss).

Is there any solution about this error?

My-MacE commented 1 year ago

Hi @blaskowitz100 , I tried to use this model NewYork Taxi Data,

I found error

model.fit(x_train,batch_size=256,epochs=1)

TypeError                                 Traceback (most recent call last)
Cell In[49], line 1
----> 1 model.fit(x_train,batch_size=256,epochs=1)

Cell In[44], line 567, in TadGAN.fit(self, x, y, batch_size, epochs, verbose, callbacks, validation_split, validation_data, shuffle, class_weight, sample_weight, initial_epoch, steps_per_epoch, validation_steps, validation_batch_size, validation_freq, max_queue_size, workers, use_multiprocessing)
    564 if not isinstance(x, tf.data.Dataset):
    565     batch_size = batch_size * self.n_iterations_critic
--> 567 return super().fit(x, y, batch_size, epochs, verbose, callbacks, validation_split, validation_data, shuffle,
    568                    class_weight, sample_weight, initial_epoch, steps_per_epoch, validation_steps,
    569                    validation_batch_size, validation_freq, max_queue_size, workers, use_multiprocessing)

TypeError: fit() takes from 1 to 19 positional arguments but 20 were given

I don't know why fit error happen. My data is (Number of sample(8060), Time_step(100), Feature(1))

blaskowitz100 commented 1 year ago

Hello @My-MacE,

what version of TensorFlow are you using to train the model?

I see that I only used positional arguments to call the parent fit function. Maybe the order or the number of arguments has changed between the different Tensorflow versions. To check this, we have to take a look at the standard fit function of the Temsorflow version that you are using in your environment.

The last time I trained the model, I used TF2.3.

By the way, what is the type of your input data? Is it a tf.data.Dataset or a numpy array?

My-MacE commented 1 year ago

@blaskowitz100 I used TF2.3. But It didn't work

my data type is numpy.ndarray. Does it have difference?

shimonymittal commented 9 months ago

Hi @blaskowitz100,

I ran the code provided by you in comment 'https://github.com/sintel-dev/Orion/issues/143#issuecomment-768640950'. The code runs fine after rectifying few errors as per my data. But I have 2 questions:

  1. I want to know the significance of 'n_iterations_critic'
  2. Where in the code of train step have we used epoch to train the model for the specified number of epochs?
  3. I capture history while fitting the model, the history results are as below, whereas I was expecting all the loss values in each n_iterations_critic step, instead of one: history.history.keys() dict_keys(['Cx_total', 'Cx_valid', 'Cx_fake', 'Cx_gp_penalty', 'Cz_total', 'Cz_valid', 'Cz_fake', 'Cz_gp_penalty', 'EG_total', 'EG_fake_gen_x', 'EG_fake_gen_z', 'G_rec']) [0.02138495445251465] [4.742159366607666] [-4.880722522735596] [0.015994776040315628] [-3.5925278663635254] [-2.8535757064819336] [-1.146549105644226] [0.040759697556495667] [2.1441867351531982] [-0.054472602903842926] [1.587338924407959] [0.061132051050662994]
  4. Also. I don't know whether above vlues are satisfactoy, what values to improve and what to expect in each loss (I did refer issue 'https://github.com/sintel-dev/Orion/issues/441' but I am still not able to comprehend)