def fit(
        self,
        x: np.ndarray | None = None,
        y: np.ndarray | None = None,
        test_x: np.ndarray | None = None,
        test_y: np.ndarray | None = None,
        pred: np.ndarray | None = None,
        test_pred: np.ndarray | None = None,
        **kwargs,
    ):
        """
        Train the attack model.

        :param x: Records that were used in training the target estimator. Can be None if supplying `pred`.
        :param y: True labels for `x`. If not supplied, attack will be based solely on model predictions.
        :param test_x: Records that were not used in training the target estimator. Can be None if supplying
                       `test_pred`.
        :param test_y: True labels for `test_x`. If not supplied, attack will be based solely on model predictions.
        :param pred: Estimator predictions for the records, if not supplied will be generated by calling the estimators'
                     `predict` function. Only relevant for input_type='prediction'.
        :param test_pred: Estimator predictions for the test records, if not supplied will be generated by calling the
                          estimators' `predict` function. Only relevant for input_type='prediction'.
        :return: An array holding the inferred membership status, 1 indicates a member and 0 indicates non-member.
        """
        if x is None and pred is None:
            raise ValueError("Must supply either x or pred")
        if test_x is None and test_pred is None:
            raise ValueError("Must supply either test_x or test_pred")

        if self.estimator.input_shape is not None:
            if x is not None and self.estimator.input_shape[0] != x.shape[1]:  # pragma: no cover
                raise ValueError("Shape of x does not match input_shape of estimator")
            if test_x is not None and self.estimator.input_shape[0] != test_x.shape[1]:  # pragma: no cover
                raise ValueError("Shape of test_x does not match input_shape of estimator")

        if y is not None and test_y is not None and not self._regressor_model:
            y = check_and_transform_label_format(y, nb_classes=self.estimator.nb_classes, return_one_hot=True)
            test_y = check_and_transform_label_format(test_y, nb_classes=self.estimator.nb_classes, return_one_hot=True)

        if x is not None and y is not None and y.shape[0] != x.shape[0]:  # pragma: no cover
            raise ValueError("Number of rows in x and y do not match")
        if pred is not None and y is not None and y.shape[0] != pred.shape[0]:  # pragma: no cover
            raise ValueError("Number of rows in pred and y do not match")
        if test_x is not None and test_y is not None and test_y.shape[0] != test_x.shape[0]:  # pragma: no cover
            raise ValueError("Number of rows in test_x and test_y do not match")
        if test_pred is not None and test_y is not None and test_y.shape[0] != test_pred.shape[0]:  # pragma: no cover
            raise ValueError("Number of rows in test_pred and test_y do not match")

        # Create attack dataset
        # uses final probabilities/logits
        x_len = 0
        test_len = 0
        if pred is None and x is not None:
            x_len = x.shape[0]
        elif pred is not None:
            x_len = pred.shape[0]
        if test_pred is None and test_x is not None:
            test_len = test_x.shape[0]
        elif test_pred is not None:
            test_len = test_pred.shape[0]

        if self.input_type == "prediction":
            # members
            if pred is None:
                features = self.estimator.predict(x).astype(np.float32)
            else:
                features = pred.astype(np.float32)
            # non-members
            if test_pred is None:
                test_features = self.estimator.predict(test_x).astype(np.float32)
            else:
                test_features = test_pred.astype(np.float32)
        # only for models with loss
        elif self.input_type == "loss":
            if y is None:
                raise ValueError("Cannot compute loss values without y.")
            if x is not None:
                # members
                features = self.estimator.compute_loss(x, y).astype(np.float32).reshape(-1, 1)
            else:
                try:
                    features = self.estimator.compute_loss_from_predictions(pred, y).astype(np.float32).reshape(-1, 1)
                except NotImplementedError as err:
                    raise ValueError(
                        "For loss input type and no x, the estimator must implement 'compute_loss_from_predictions' "
                        "method"
                    ) from err
            if test_x is not None:
                # non-members
                test_features = self.estimator.compute_loss(test_x, test_y).astype(np.float32).reshape(-1, 1)
            else:
                try:
                    test_features = (
                        self.estimator.compute_loss_from_predictions(test_pred, test_y)
                        .astype(np.float32)
                        .reshape(-1, 1)
                    )
                except NotImplementedError as err:
                    raise ValueError(
                        "For loss input type and no test_x, the estimator must implement "
                        "'compute_loss_from_predictions' method"
                    ) from err
        else:  # pragma: no cover
            raise ValueError("Illegal value for parameter `input_type`.")

        # members
        labels = np.ones(x_len)
        # non-members
        test_labels = np.zeros(test_len)

        x_1 = np.concatenate((features, test_features))
        x_2: np.ndarray | None = None
        if y is not None and test_y is not None:
            x_2 = np.concatenate((y, test_y))
            if self._regressor_model and x_2 is not None:
                x_2 = x_2.astype(np.float32).reshape(-1, 1)
        y_new = np.concatenate((labels, test_labels))
        if x_2 is None:
            self.use_label = False

        if self.scaler_type:
            if self.scaler_type == "standard":
                self.scaler = StandardScaler()
            elif self.scaler_type == "minmax":
                self.scaler = MinMaxScaler()
            elif self.scaler_type == "robust":
                self.scaler = RobustScaler()
            else:
                raise ValueError("Illegal scaler_type: ", self.scaler_type)

        if self.default_model and self.attack_model_type == "nn":
            import torch
            from torch import nn
            from torch import optim
            from torch.utils.data import DataLoader
            from art.utils import to_cuda

            if self.scaler:
                self.scaler.fit(x_1)
                x_1 = self.scaler.transform(x_1)

            if x_2 is not None:

                class MembershipInferenceAttackModel(nn.Module):
                    """
                    Implementation of a pytorch model for learning a membership inference attack.

                    The features used are probabilities/logits or losses for the attack training data along with
                    its true labels.
                    """

                    def __init__(self, num_classes, num_features=None):

                        self.num_classes = num_classes
                        if num_features:
                            self.num_features = num_features
                        else:
                            self.num_features = num_classes

                        super().__init__()

                        self.features = nn.Sequential(
                            nn.Linear(self.num_features, 512),
                            nn.ReLU(),
                            nn.Linear(512, 100),
                            nn.ReLU(),
                            nn.Linear(100, 64),
                            nn.ReLU(),
                        )

                        self.labels = nn.Sequential(
                            nn.Linear(self.num_classes, 256),
                            nn.ReLU(),
                            nn.Linear(256, 64),
                            nn.ReLU(),
                        )

                        self.combine = nn.Sequential(
                            nn.Linear(64 * 2, 1),
                        )

                        self.output = nn.Sigmoid()

                    def forward(self, x_1, label):
                        """Forward the model."""
                        out_x1 = self.features(x_1)
                        out_l = self.labels(label)
                        is_member = self.combine(torch.cat((out_x1, out_l), 1))
                        return self.output(is_member)

                if self.input_type == "prediction":
                    num_classes = self.estimator.nb_classes  # type: ignore
                    self.attack_model = MembershipInferenceAttackModel(num_classes)
                else:  # loss
                    if self._regressor_model:
                        self.attack_model = MembershipInferenceAttackModel(1, num_features=1)
                    else:
                        num_classes = self.estimator.nb_classes  # type: ignore
                        self.attack_model = MembershipInferenceAttackModel(num_classes, num_features=1)

                loss_fn = nn.BCELoss()
                optimizer = optim.Adam(self.attack_model.parameters(), lr=self.learning_rate)  # type: ignore

                attack_train_set = self._get_attack_dataset(f_1=x_1, f_2=x_2, label=y_new)
                train_loader = DataLoader(attack_train_set, batch_size=self.batch_size, shuffle=True, num_workers=0)

                self.attack_model = to_cuda(self.attack_model)  # type: ignore
                self.attack_model.train()  # type: ignore

                for _ in range(self.epochs):
                    for input1, input2, targets in train_loader:
                        input1, input2, targets = to_cuda(input1), to_cuda(input2), to_cuda(targets)
                        _, input2 = torch.autograd.Variable(input1), torch.autograd.Variable(input2)
                        targets = torch.autograd.Variable(targets)

                        optimizer.zero_grad()
                        outputs = self.attack_model(input1, input2)  # type: ignore
                        loss = loss_fn(outputs, targets.unsqueeze(1))

                        loss.backward()
                        optimizer.step()
            else:  # no label

                class MembershipInferenceAttackModelNoLabel(nn.Module):
                    """
                    Implementation of a pytorch model for learning a membership inference attack.

                    The features used are probabilities/logits or losses for the attack training data along with
                    its true labels.
                    """

                    def __init__(self, num_features):

                        self.num_features = num_features

                        super().__init__()

                        self.features = nn.Sequential(
                            nn.Linear(self.num_features, 512),
                            nn.ReLU(),
                            nn.Linear(512, 100),
                            nn.ReLU(),
                            nn.Linear(100, 64),
                            nn.ReLU(),
                            nn.Linear(64, 1),
                        )

                        self.output = nn.Sigmoid()

                    def forward(self, x_1):
                        """Forward the model."""
                        out_x1 = self.features(x_1)
                        return self.output(out_x1)

                num_classes = self.estimator.nb_classes  # type: ignore
                self.attack_model = MembershipInferenceAttackModelNoLabel(num_classes)

                loss_fn = nn.BCELoss()
                optimizer = optim.Adam(self.attack_model.parameters(), lr=self.learning_rate)  # type: ignore

                attack_train_set = self._get_attack_dataset_no_label(f_1=x_1, label=y_new)
                train_loader = DataLoader(attack_train_set, batch_size=self.batch_size, shuffle=True, num_workers=0)

                self.attack_model = to_cuda(self.attack_model)  # type: ignore
                self.attack_model.train()  # type: ignore

                for _ in range(self.epochs):
                    for input1, targets in train_loader:
                        input1, targets = to_cuda(input1), to_cuda(targets)
                        input1 = torch.autograd.Variable(input1)
                        targets = torch.autograd.Variable(targets)

                        optimizer.zero_grad()
                        outputs = self.attack_model(input1)  # type: ignore
                        loss = loss_fn(outputs, targets.unsqueeze(1))

                        loss.backward()
                        optimizer.step()

        else:  # not nn
            y_ready = check_and_transform_label_format(y_new, nb_classes=2, return_one_hot=False)
            if x_2 is not None:
                x = np.c_[x_1, x_2]
                if self.scaler:
                    self.scaler.fit(x)
                    x = self.scaler.transform(x)
                self.attack_model.fit(x, y_ready.ravel())  # type: ignore
            else:
                if self.scaler:
                    self.scaler.fit(x_1)
                    x_1 = self.scaler.transform(x_1)
                self.attack_model.fit(x_1, y_ready.ravel())  # type: ignore

        Train the attack model.

        :param x: Records that were used in training the target estimator. Can be None if supplying `pred`.
        :param y: True labels for `x`. If not supplied, attack will be based solely on model predictions.
        :param test_x: Records that were not used in training the target estimator. Can be None if supplying
                       `test_pred`.
        :param test_y: True labels for `test_x`. If not supplied, attack will be based solely on model predictions.
        :param pred: Estimator predictions for the records, if not supplied will be generated by calling the estimators'
                     `predict` function. Only relevant for input_type='prediction'.
        :param test_pred: Estimator predictions for the test records, if not supplied will be generated by calling the
                          estimators' `predict` function. Only relevant for input_type='prediction'.
        :return: An array holding the inferred membership status, 1 indicates a member and 0 indicates non-member.
        
art/attacks/inference/membership_inference/black_box.py