def _hill_climbing_synthesis(
        self,
        target_classifier: "CLASSIFIER_TYPE",
        target_class: int,
        min_confidence: float,
        max_features_randomized: int | None,
        max_iterations: int = 40,
        max_rejections: int = 3,
        min_features_randomized: int = 1,
        random_record_fn: Callable[[], np.ndarray] | None = None,
        randomize_features_fn: Callable[[np.ndarray, int], np.ndarray] | None = None,
    ) -> np.ndarray:
        """
        This method implements 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 target_class: The class the synthesized record will have.
        :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_features_randomized: The initial amount of features to randomize in each climbing step. A good
                                        default value is one half of the number of features.
        :param max_iterations: The maximum amount of iterations to try and improve the classifier's confidence in the
                               generated record. This is essentially the maximum number of hill-climbing steps.
        :param max_rejections: The maximum amount of rejections (i.e. a step which did not improve the confidence)
                               before starting to fine-tune the record (i.e. making smaller steps).
        :param min_features_randomized: The minimum amount of features to randomize when fine-tuning.
        :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: Synthesized record.
        """

        if random_record_fn is None:
            random_record_fn = self._default_random_record
        if randomize_features_fn is None:
            randomize_features_fn = self._default_randomize_features

        best_x = None
        best_class_confidence = 0
        num_rejections = 0

        x = random_record_fn()

        if max_features_randomized is None:
            k_features_randomized = x.reshape(1, -1).shape[1] // 2
        else:
            k_features_randomized = max_features_randomized

        for _ in range(max_iterations):
            y = target_classifier.predict(x.reshape(1, -1))[0]
            class_confidence = y[target_class]

            if class_confidence >= best_class_confidence:
                # Record accepted, sample randomly
                if class_confidence > min_confidence and np.argmax(y) == target_class:
                    if self._rng.random() < class_confidence:
                        return x

                best_x = x
                best_class_confidence = class_confidence
                num_rejections = 0
            else:
                num_rejections += 1
                if num_rejections > max_rejections:
                    # Rejected too many times, we are probably making changes which are too large
                    half_current_features = math.ceil(k_features_randomized / 2)
                    k_features_randomized = max(min_features_randomized, half_current_features)  # type: ignore
                    num_rejections = 0

            x = randomize_features_fn(best_x, k_features_randomized)  # type: ignore

        raise RuntimeError("Failed to synthesize data record")

        This method implements 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 target_class: The class the synthesized record will have.
        :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_features_randomized: The initial amount of features to randomize in each climbing step. A good
                                        default value is one half of the number of features.
        :param max_iterations: The maximum amount of iterations to try and improve the classifier's confidence in the
                               generated record. This is essentially the maximum number of hill-climbing steps.
        :param max_rejections: The maximum amount of rejections (i.e. a step which did not improve the confidence)
                               before starting to fine-tune the record (i.e. making smaller steps).
        :param min_features_randomized: The minimum amount of features to randomize when fine-tuning.
        :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: Synthesized record.
        
art/attacks/inference/membership_inference/shadow_models.py