def generate_shadow_dataset(
        self,
        x: np.ndarray,
        y: np.ndarray,
        member_ratio: float = 0.5,
    ) -> 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 splitting
        the dataset into training and testing samples, and then training the shadow models on the result.

        :param x: The samples used to train the shadow models.
        :param y: True labels for the dataset samples (as expected by the estimator's fit method).
        :param member_ratio: Percentage of the data that should be used to train the shadow models. Must be between 0
                             and 1.
        :return: The shadow dataset generated. The shape is `((member_samples, true_label, model_prediction),
                 (nonmember_samples, true_label, model_prediction))`.
        """

        if len(x) != len(y):
            raise ValueError("Number of samples in dataset does not match number of labels")

        # Shuffle data set
        random_indices = self._rng.permutation(len(x))
        x, y = x[random_indices], y[random_indices]

        if self._disjoint_datasets:
            shadow_dataset_size = len(x) // len(self._shadow_models)
        else:
            shadow_dataset_size = len(x)

        member_samples = []
        member_true_label = []
        member_prediction = []
        nonmember_samples = []
        nonmember_true_label = []
        nonmember_prediction = []

        # Train and create predictions for every model
        for i, shadow_model in enumerate(self._shadow_models):
            if self._disjoint_datasets:
                shadow_x = x[shadow_dataset_size * i : shadow_dataset_size * (i + 1)]
                shadow_y = y[shadow_dataset_size * i : shadow_dataset_size * (i + 1)]

                shadow_x_train = shadow_x[: int(member_ratio * shadow_dataset_size)]
                shadow_y_train = shadow_y[: int(member_ratio * shadow_dataset_size)]
                shadow_x_test = shadow_x[int(member_ratio * shadow_dataset_size) :]
                shadow_y_test = shadow_y[int(member_ratio * shadow_dataset_size) :]
            else:
                member_indexes = self._rng.choice(len(x) - 1, int(len(x) * member_ratio), replace=False)
                non_member_indexes = np.setdiff1d(range(len(x) - 1), member_indexes, assume_unique=True)
                shadow_x_train = x[member_indexes]
                shadow_y_train = y[member_indexes]
                shadow_x_test = x[non_member_indexes]
                shadow_y_test = y[non_member_indexes]

            self._shadow_models_train_sets[i] = (shadow_x_train, shadow_y_train)

            shadow_model.fit(shadow_x_train, shadow_y_train)

            member_samples.append(shadow_x_train)
            member_true_label.append(shadow_y_train)
            member_prediction.append(shadow_model.predict(shadow_x_train))

            nonmember_samples.append(shadow_x_test)
            nonmember_true_label.append(shadow_y_test)
            nonmember_prediction.append(shadow_model.predict(shadow_x_test))

        def concat(first: np.ndarray, second: np.ndarray) -> np.ndarray:
            return np.concatenate((first, second))

        # Concatenate the results of all the shadow models
        all_member_samples = reduce(concat, member_samples)
        all_member_true_label = reduce(concat, member_true_label)
        all_member_prediction = reduce(concat, member_prediction)
        all_nonmember_samples = reduce(concat, nonmember_samples)
        all_nonmember_true_label = reduce(concat, nonmember_true_label)
        all_nonmember_prediction = reduce(concat, nonmember_prediction)

        return (
            (all_member_samples, all_member_true_label, all_member_prediction),
            (all_nonmember_samples, all_nonmember_true_label, all_nonmember_prediction),
        )

        Generates a shadow dataset (member and nonmember samples and their corresponding model predictions) by splitting
        the dataset into training and testing samples, and then training the shadow models on the result.

        :param x: The samples used to train the shadow models.
        :param y: True labels for the dataset samples (as expected by the estimator's fit method).
        :param member_ratio: Percentage of the data that should be used to train the shadow models. Must be between 0
                             and 1.
        :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