def fit(self, X, y=None):
        """Fit detector. y is ignored in unsupervised methods.

        Parameters
        ----------
        X : numpy array of shape (n_samples, n_features)
            The input samples.

        y : Ignored
            Not used, present for API consistency by convention.

        Returns
        -------
        self : object
            Fitted estimator.
        """
        # validate inputs X and y (optional)
        X = check_array(X)
        self._set_n_classes(y)

        # Verify and construct the hidden units
        self.n_samples_, self.n_features_ = X.shape

        # Standardize data for better performance
        if self.preprocessing:
            self.scaler_ = StandardScaler()
            X_norm = self.scaler_.fit_transform(X)
        else:
            X_norm = np.copy(X)
        X_norm = torch.tensor(X_norm, dtype=torch.float32)
        # train the discriminator and generator
        self.generator = Generator(latent_dim_G=self.latent_dim_G,
                                   n_features=self.n_features_,
                                   G_layers=self.G_layers,
                                   dropout_rate=self.dropout_rate,
                                   activation_hidden=self.activation_hidden,
                                   output_activation=self.output_activation)
        self.discriminator = Discriminator(n_features=self.n_features_,
                                           D_layers=self.D_layers,
                                           dropout_rate=self.dropout_rate,
                                           activation_hidden=self.activation_hidden)

        self.generator.to(self.device)
        self.discriminator.to(self.device)

        optimizer_g = optim.Adam(
            self.generator.parameters(), lr=self.learning_rate)
        optimizer_d = optim.Adam(
            self.discriminator.parameters(), lr=self.learning_rate)

        dataset = TensorDataset(X_norm)
        dataloader = DataLoader(
            dataset, batch_size=self.batch_size, shuffle=True)

        for n in range(self.epochs):
            if n % 100 == 0 and n != 0 and self.verbose == 1:
                print(f'Train iter: {n}')

            self.generator.train()
            self.discriminator.train()
            for X_train_ in dataloader:
                X_train_sel = X_train_[0].to(self.device)
                latent_noise = torch.rand(X_train_sel.size(
                    0), self.latent_dim_G, dtype=torch.float32).to(self.device)

                generated_data = self.generator(latent_noise)
                real_output = self.discriminator(X_train_sel)
                fake_output = self.discriminator(generated_data.detach())

                loss_D_real = nn.BCELoss()(real_output, torch.ones_like(
                    real_output) * 0.9).to(self.device)
                loss_D_fake = nn.BCELoss()(fake_output,
                                           torch.zeros_like(fake_output)).to(
                    self.device)
                loss_D = loss_D_real + loss_D_fake
                optimizer_d.zero_grad()
                loss_D.backward()
                optimizer_d.step()

                fake_output = self.discriminator(generated_data)
                loss_G = nn.BCELoss()(fake_output,
                                      torch.ones_like(fake_output)).to(
                    self.device)
                optimizer_g.zero_grad()
                loss_G.backward()
                optimizer_g.step()

                self.hist_loss_discriminator.append(loss_D.item())
                self.hist_loss_generator.append(loss_G.item())

        # Instantiate and train the query model
        self.generator.eval()
        self.discriminator.eval()
        self.query_model = QueryModel(
            self.generator, self.discriminator, self.latent_dim_G).to(
            self.device)
        optimizer_query = optim.Adam(
            self.query_model.parameters(), lr=self.learning_rate_query)
        scores = []
        # For each sample, use a few backpropagation steps to obtain a point in the latent space that best resembles the query sample
        self.query_model.train()
        for i in range(X_norm.shape[0]):
            if self.verbose == 1:
                print('query sample {} / {}'.format(i + 1, X_norm.shape[0]))

            query_sample = X_norm[[i],].to(self.device)
            assert (query_sample.shape[0] == 1)
            assert (query_sample.shape[1] == self.n_features_)

            # Make pseudo input (just zeros)
            zeros = torch.zeros((1, self.latent_dim_G)).to(self.device)

            # build model for back-propagating a approximate latent space where
            # reconstruction with query sample is optimal
            for i in range(self.epochs_query):
                if i % 25 == 0 and self.verbose == 1:
                    print('iter:', i)

                z, sample_gen, sample_disc_latent = self.query_model(zeros)
                with torch.no_grad():
                    sample_disc_latent_original = self.discriminator(
                        query_sample)
                # Reconstruction loss generator
                loss_recon_gen = torch.mean(torch.mean(
                    torch.abs(query_sample - sample_gen), axis=-1))
                # Reconstruction loss latent space of discrimator
                loss_recon_disc = torch.mean(torch.mean(
                    torch.abs(
                        sample_disc_latent_original - sample_disc_latent),
                    axis=-1))
                total_loss = loss_recon_gen + loss_recon_disc

                optimizer_query.zero_grad()
                total_loss.backward()
                optimizer_query.step()
            # Predict on X itself and calculate the reconstruction error as
            # the outlier scores.
            scores.append(total_loss.item())

        self.decision_scores_ = np.array(scores)
        self._process_decision_scores()
        return self
Fit detector. y is ignored in unsupervised methods.

        Parameters
        ----------
        X : numpy array of shape (n_samples, n_features)
            The input samples.

        y : Ignored
            Not used, present for API consistency by convention.

        Returns
        -------
        self : object
            Fitted estimator.
        
pyod/models/anogan.py