def fit_generator(self, generator: "DataGenerator", nb_epochs: int = 20, **kwargs) -> None:
        """
        Train a model adversarially using a data generator.
        See class documentation for more information on the exact procedure.

        :param generator: Data generator.
        :param nb_epochs: Number of epochs to use for trainings.
        :param kwargs: Dictionary of framework-specific arguments. These will be passed as such to the `fit` function of
               the target classifier.
        """
        logger.info("Performing adversarial training using %i attacks.", len(self.attacks))
        if generator.size is None:
            raise ValueError("Generator size is required and cannot be None.")
        batch_size = generator.batch_size
        nb_batches = int(np.ceil(generator.size / batch_size))  # type: ignore
        ind = np.arange(generator.size)
        attack_id = 0

        # Precompute adversarial samples for transferred attacks
        logged = False
        self._precomputed_adv_samples = []
        for attack in tqdm(self.attacks, desc="Precompute adversarial examples."):
            if "verbose" in attack.attack_params:
                attack.set_params(verbose=False)
            if "targeted" in attack.attack_params and attack.targeted:  # type: ignore
                raise NotImplementedError("Adversarial training with targeted attacks is currently not implemented")

            if attack.estimator != self._classifier:
                if not logged:
                    logger.info("Precomputing transferred adversarial samples.")
                    logged = True

                for batch_id in range(nb_batches):
                    # Create batch data
                    x_batch, y_batch = generator.get_batch()
                    x_adv_batch = attack.generate(x_batch, y=y_batch)
                    if batch_id == 0:
                        next_precomputed_adv_samples = x_adv_batch
                    else:
                        next_precomputed_adv_samples = np.append(next_precomputed_adv_samples, x_adv_batch, axis=0)
                self._precomputed_adv_samples.append(next_precomputed_adv_samples)
            else:
                self._precomputed_adv_samples.append(None)

        for _ in trange(nb_epochs, desc="Adversarial training epochs"):
            # Shuffle the indices of precomputed examples
            np.random.shuffle(ind)

            for batch_id in range(nb_batches):
                # Create batch data
                x_batch, y_batch = generator.get_batch()
                x_batch = x_batch.copy()

                # Choose indices to replace with adversarial samples
                attack = self.attacks[attack_id]
                if "verbose" in attack.attack_params:
                    attack.set_params(verbose=False)

                # If source and target models are the same, craft fresh adversarial samples
                if attack.estimator == self._classifier:
                    nb_adv = int(np.ceil(self.ratio * x_batch.shape[0]))

                    if self.ratio < 1:
                        adv_ids = np.random.choice(x_batch.shape[0], size=nb_adv, replace=False)
                    else:
                        adv_ids = np.array(list(range(x_batch.shape[0])))
                        np.random.shuffle(adv_ids)

                    x_batch[adv_ids] = attack.generate(x_batch[adv_ids], y=y_batch[adv_ids])

                # Otherwise, use precomputed adversarial samples
                else:
                    batch_size_current = min(batch_size, generator.size - batch_id * batch_size)
                    nb_adv = int(np.ceil(self.ratio * batch_size_current))
                    if self.ratio < 1:
                        adv_ids = np.random.choice(batch_size_current, size=nb_adv, replace=False)
                    else:
                        adv_ids = np.array(list(range(batch_size_current)))
                        np.random.shuffle(adv_ids)

                    x_adv = self._precomputed_adv_samples[attack_id]
                    if x_adv is not None:
                        x_adv = x_adv[ind[batch_id * batch_size : min((batch_id + 1) * batch_size, generator.size)]][
                            adv_ids
                        ]
                    x_batch[adv_ids] = x_adv

                # Fit batch
                self._classifier.fit(
                    x_batch, y_batch, nb_epochs=1, batch_size=x_batch.shape[0], verbose=False, **kwargs
                )
                attack_id = (attack_id + 1) % len(self.attacks)

        Train a model adversarially using a data generator.
        See class documentation for more information on the exact procedure.

        :param generator: Data generator.
        :param nb_epochs: Number of epochs to use for trainings.
        :param kwargs: Dictionary of framework-specific arguments. These will be passed as such to the `fit` function of
               the target classifier.
        
art/defences/trainer/adversarial_trainer.py