def generate_synthetic_shadow_dataset(
        self,
        target_classifier: "CLASSIFIER_TYPE",
        dataset_size: int,
        max_features_randomized: int | None,
        member_ratio: float = 0.5,
        min_confidence: float = 0.4,
        max_retries: int = 6,
        random_record_fn: Callable[[], np.ndarray] | None = None,
        randomize_features_fn: Callable[[np.ndarray, int], np.ndarray] | None = None,
    ) -> tuple[tuple[np.ndarray, np.ndarray, np.ndarray], tuple[np.ndarray, np.ndarray, np.ndarray]]:
        """
        Generates a shadow dataset (member and nonmember samples and their corresponding model predictions) by training
        the shadow models on a synthetic dataset generated from the target classifier using the hill climbing algorithm
        from R. Shokri et al. (2017)

        Paper Link: https://arxiv.org/abs/1610.05820

        :param target_classifier: The classifier to synthesize data from.
        :param dataset_size: How many records to synthesize.
        :param max_features_randomized: The initial amount of features to randomize before fine-tuning. If None, half of
                                        record features will be used, which will not work well for one-hot encoded data.
        :param member_ratio: Percentage of the data that should be used to train the shadow models. Must be between 0
                             and 1.
        :param min_confidence: The minimum confidence the classifier assigns the target class for the record to be
                               accepted (i.e. the hill-climbing algorithm is finished).
        :param max_retries: The maximum amount of record-generation retries. The initial random pick of a record for the
                            hill-climbing algorithm might result in failing to optimize the target-class confidence, and
                            so a new random record will be retried.
        :param random_record_fn: Callback that returns a single random record (numpy array), i.e. all feature values are
                                 random. If None, random records are generated by treating each column in the input
                                 shape as a feature and choosing uniform values [0, 1) for each feature. This default
                                 behaviour is not correct for one-hot-encoded features, and a custom callback which
                                 provides a random record with random one-hot-encoded values should be used instead.
        :param randomize_features_fn: Callback that accepts an existing record (numpy array) and an int which is the
                                      number of features to randomize. The callback should return a new record, where
                                      the specified number of features have been randomized. If None, records are
                                      randomized by treating each column in the input shape as a feature, and choosing
                                      uniform values [0, 1) for each randomized feature. This default behaviour is not
                                      correct for one-hot-encoded features, and a custom callback which randomizes
                                      one-hot-encoded features should be used instead.
        :return: The shadow dataset generated. The shape is `((member_samples, true_label, model_prediction),
                 (nonmember_samples, true_label, model_prediction))`.
        """
        x = []
        y = []

        records_per_class = dataset_size // target_classifier.nb_classes

        # Generate samples for each classification class
        for target_class in range(target_classifier.nb_classes):
            one_hot_label = np.zeros(target_classifier.nb_classes)
            one_hot_label[target_class] = 1.0

            for _ in range(records_per_class):
                for tries in range(max_retries):
                    try:
                        random_record = self._hill_climbing_synthesis(
                            target_classifier,
                            target_class,
                            min_confidence,
                            max_features_randomized=max_features_randomized,
                            random_record_fn=random_record_fn,
                            randomize_features_fn=randomize_features_fn,
                        )
                        break
                    except RuntimeError as err:
                        if tries == max_retries - 1:
                            raise err

                x.append(random_record)
                y.append(one_hot_label)

        return self.generate_shadow_dataset(np.array(x), np.array(y), member_ratio)

        Generates a shadow dataset (member and nonmember samples and their corresponding model predictions) by training
        the shadow models on a synthetic dataset generated from the target classifier using the hill climbing algorithm
        from R. Shokri et al. (2017)

        Paper Link: https://arxiv.org/abs/1610.05820

        :param target_classifier: The classifier to synthesize data from.
        :param dataset_size: How many records to synthesize.
        :param max_features_randomized: The initial amount of features to randomize before fine-tuning. If None, half of
                                        record features will be used, which will not work well for one-hot encoded data.
        :param member_ratio: Percentage of the data that should be used to train the shadow models. Must be between 0
                             and 1.
        :param min_confidence: The minimum confidence the classifier assigns the target class for the record to be
                               accepted (i.e. the hill-climbing algorithm is finished).
        :param max_retries: The maximum amount of record-generation retries. The initial random pick of a record for the
                            hill-climbing algorithm might result in failing to optimize the target-class confidence, and
                            so a new random record will be retried.
        :param random_record_fn: Callback that returns a single random record (numpy array), i.e. all feature values are
                                 random. If None, random records are generated by treating each column in the input
                                 shape as a feature and choosing uniform values [0, 1) for each feature. This default
                                 behaviour is not correct for one-hot-encoded features, and a custom callback which
                                 provides a random record with random one-hot-encoded values should be used instead.
        :param randomize_features_fn: Callback that accepts an existing record (numpy array) and an int which is the
                                      number of features to randomize. The callback should return a new record, where
                                      the specified number of features have been randomized. If None, records are
                                      randomized by treating each column in the input shape as a feature, and choosing
                                      uniform values [0, 1) for each randomized feature. This default behaviour is not
                                      correct for one-hot-encoded features, and a custom callback which randomizes
                                      one-hot-encoded features should be used instead.
        :return: The shadow dataset generated. The shape is `((member_samples, true_label, model_prediction),
                 (nonmember_samples, true_label, model_prediction))`.
        
art/attacks/inference/membership_inference/shadow_models.py