ultralytics / yolov5

YOLOv5 πŸš€ in PyTorch > ONNX > CoreML > TFLite
https://docs.ultralytics.com
GNU Affero General Public License v3.0
50.4k stars 16.27k forks source link

an adversarial training questions #12808

Closed YDMYy closed 6 months ago

YDMYy commented 7 months ago

Search before asking

Question

I am using yolov5 to do a project on adversarial training, using classification model and PGD to combat attacks. Both clean-train and adv-train are OK under train.py, but clean-test is OK under val.py, while adv-test is not. My code is like this: attacker = LinfPGDAttack( model, loss_fn=None, eps=8 / 255, eps_iter=2 / 255, nb_iter=10, rand_init=True, clip_min=0.0, clip_max=1.0, targeted=False, ) Change the data to ith torch.enable_grad(): img_adv = attacker.perturb(images, labels.to(device)) Then the test will pop up Traceback (most recent call last): File "/home/kwan/project/YOLO project/yolov5-master/classify/val.py", line 274, in main(opt) File "/home/kwan/project/YOLO project/yolov5-master/classify/val.py", line 269, in main run(*vars(opt)) The File "/ home/kwan/miniconda3 / envs/yolov8 (1)/lib/python3.9 / site - packages/torch/utils / _contextlib py", line 115, in decorate_context return func(args, **kwargs) File "/home/kwan/project/YOLO project/yolov5-master/classify/val.py", line 209, in run img_adv = attacker.perturb(images, labels.to(device)) File "/ home/kwan/miniconda3 / envs/yolov8 (1)/lib/python3.9 / site - packages/advertorch/attacks/iterative_projected_gradient py", line 183, in perturb rval = perturb_iterative( File "/ home/kwan/miniconda3 / envs/yolov8 (1)/lib/python3.9 / site - packages/advertorch/attacks/iterative_projected_gradient py", line 70, in perturb_iterative loss.backward() The File "/ home/kwan/miniconda3 / envs/yolov8 (1)/lib/python3.9 / site - packages/torch / _tensor. Py", line 492, in backward torch.autograd.backward( The File "/ home/kwan/miniconda3 / envs/yolov8 (1)/lib/python3.9 / site - packages/torch/autograd/set py", line 251, in backward Variable._execution_engine.run_backward( # Calls into the C++ engine to run the backward pass RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn why?

Additional

No response

YDMYy commented 7 months ago

attacker = LinfPGDAttack( model, loss_fn=None, eps=8 / 255, eps_iter=2 / 255, nb_iter=10, rand_init=True, clip_min=0.0, clip_max=1.0, targeted=False, ) with torch.cuda.amp.autocast(enabled=device.type != "cpu"): for images, labels in bar: with dt[0]: images, labels = images.to(device, non_blocking=True), labels.to(device)

        with dt[1]:
            # with torch.enable_grad():
            #     img_adv = attacker.perturb(images, labels.to(device))
            y = model(images)
        with dt[2]:
            pred.append(y.argsort(1, descending=True)[:, :5])
            targets.append(labels)
            if criterion:
                loss += criterion(y, labels)
YDMYy commented 7 months ago

def perturb_iterative(xvar, yvar, predict, nb_iter, eps, eps_iter, loss_fn, delta_init=None, minimize=False, ord=np.inf, clip_min=0.0, clip_max=1.0, l1_sparsity=None): """ Iteratively maximize the loss over the input. It is a shared method for iterative attacks including IterativeGradientSign, LinfPGD, etc.

:param xvar: input data.
:param yvar: input labels.
:param predict: forward pass function.
:param nb_iter: number of iterations.
:param eps: maximum distortion.
:param eps_iter: attack step size.
:param loss_fn: loss function.
:param delta_init: (optional) tensor contains the random initialization.
:param minimize: (optional bool) whether to minimize or maximize the loss.
:param ord: (optional) the order of maximum distortion (inf or 2).
:param clip_min: mininum value per input dimension.
:param clip_max: maximum value per input dimension.
:param l1_sparsity: sparsity value for L1 projection.
              - if None, then perform regular L1 projection.
              - if float value, then perform sparse L1 descent from
                Algorithm 1 in https://arxiv.org/pdf/1904.13000v1.pdf
:return: tensor containing the perturbed input.
"""
if delta_init is not None:
    delta = delta_init
else:
    delta = torch.zeros_like(xvar)

delta.requires_grad_()
for ii in range(nb_iter):
    outputs = predict(xvar + delta)
    loss = loss_fn(outputs, yvar)
    if minimize:
        loss = -loss

    loss.backward()
    if ord == np.inf:
        grad_sign = delta.grad.data.sign()
        delta.data = delta.data + batch_multiply(eps_iter, grad_sign)
        delta.data = batch_clamp(eps, delta.data)
        delta.data = clamp(xvar.data + delta.data, clip_min, clip_max
                           ) - xvar.data

    elif ord == 2:
        grad = delta.grad.data
        grad = normalize_by_pnorm(grad)
        delta.data = delta.data + batch_multiply(eps_iter, grad)
        delta.data = clamp(xvar.data + delta.data, clip_min, clip_max
                           ) - xvar.data
        if eps is not None:
            delta.data = clamp_by_pnorm(delta.data, ord, eps)

    elif ord == 1:
        grad = delta.grad.data
        abs_grad = torch.abs(grad)

        batch_size = grad.size(0)
        view = abs_grad.view(batch_size, -1)
        view_size = view.size(1)
        if l1_sparsity is None:
            vals, idx = view.topk(1)
        else:
            vals, idx = view.topk(
                int(np.round((1 - l1_sparsity) * view_size)))

        out = torch.zeros_like(view).scatter_(1, idx, vals)
        out = out.view_as(grad)
        grad = grad.sign() * (out > 0).float()
        grad = normalize_by_pnorm(grad, p=1)
        delta.data = delta.data + batch_multiply(eps_iter, grad)

        delta.data = batch_l1_proj(delta.data.cpu(), eps)
        if xvar.is_cuda:
            delta.data = delta.data.cuda()
        delta.data = clamp(xvar.data + delta.data, clip_min, clip_max
                           ) - xvar.data
    else:
        error = "Only ord = inf, ord = 1 and ord = 2 have been implemented"
        raise NotImplementedError(error)
    delta.grad.data.zero_()

x_adv = clamp(xvar + delta, clip_min, clip_max)
return x_adv

class PGDAttack(Attack, LabelMixin): """ The projected gradient descent attack (Madry et al, 2017). The attack performs nb_iter steps of size eps_iter, while always staying within eps from the initial point. Paper: https://arxiv.org/pdf/1706.06083.pdf

:param predict: forward pass function.
:param loss_fn: loss function.
:param eps: maximum distortion.
:param nb_iter: number of iterations.
:param eps_iter: attack step size.
:param rand_init: (optional bool) random initialization.
:param clip_min: mininum value per input dimension.
:param clip_max: maximum value per input dimension.
:param ord: (optional) the order of maximum distortion (inf or 2).
:param targeted: if the attack is targeted.
"""

def __init__(
        self, predict, loss_fn=None, eps=0.3, nb_iter=40,
        eps_iter=0.01, rand_init=True, clip_min=0., clip_max=1.,
        ord=np.inf, l1_sparsity=None, targeted=False):
    """
    Create an instance of the PGDAttack.

    """
    super(PGDAttack, self).__init__(
        predict, loss_fn, clip_min, clip_max)
    self.eps = eps
    self.nb_iter = nb_iter
    self.eps_iter = eps_iter
    self.rand_init = rand_init
    self.ord = ord
    self.targeted = targeted
    if self.loss_fn is None:
        self.loss_fn = nn.CrossEntropyLoss(reduction="sum")
    self.l1_sparsity = l1_sparsity
    assert is_float_or_torch_tensor(self.eps_iter)
    assert is_float_or_torch_tensor(self.eps)

def perturb(self, x, y=None):
    """
    Given examples (x, y), returns their adversarial counterparts with
    an attack length of eps.

    :param x: input tensor.
    :param y: label tensor.
              - if None and self.targeted=False, compute y as predicted
                labels.
              - if self.targeted=True, then y must be the targeted labels.
    :return: tensor containing perturbed inputs.
    """
    x, y = self._verify_and_process_inputs(x, y)

    delta = torch.zeros_like(x)
    delta = nn.Parameter(delta)
    if self.rand_init:
        rand_init_delta(
            delta, x, self.ord, self.eps, self.clip_min, self.clip_max)
        delta.data = clamp(
            x + delta.data, min=self.clip_min, max=self.clip_max) - x

    rval = perturb_iterative(
        x, y, self.predict, nb_iter=self.nb_iter,
        eps=self.eps, eps_iter=self.eps_iter,
        loss_fn=self.loss_fn, minimize=self.targeted,
        ord=self.ord, clip_min=self.clip_min,
        clip_max=self.clip_max, delta_init=delta,
        l1_sparsity=self.l1_sparsity,
    )

    return rval.data

class LinfPGDAttack(PGDAttack): """ PGD Attack with order=Linf

:param predict: forward pass function.
:param loss_fn: loss function.
:param eps: maximum distortion.
:param nb_iter: number of iterations.
:param eps_iter: attack step size.
:param rand_init: (optional bool) random initialization.
:param clip_min: mininum value per input dimension.
:param clip_max: maximum value per input dimension.
:param targeted: if the attack is targeted.
"""

def __init__(
        self, predict, loss_fn=None, eps=0.3, nb_iter=40,
        eps_iter=0.01, rand_init=True, clip_min=0., clip_max=1.,
        targeted=False):
    ord = np.inf
    super(LinfPGDAttack, self).__init__(
        predict=predict, loss_fn=loss_fn, eps=eps, nb_iter=nb_iter,
        eps_iter=eps_iter, rand_init=rand_init, clip_min=clip_min,
        clip_max=clip_max, targeted=targeted,
        ord=ord)
YDMYy commented 7 months ago

My goal is to test the strength of my attack by adding a counter image to the validation set on a normally trained model, but I can't seem to succeed, so can't I do the attack test on val.py?

glenn-jocher commented 7 months ago

@YDMYy it seems like you're encountering issues with implementing adversarial attacks during the validation phase using val.py. The error you're experiencing, where tensors do not require gradients, suggests that the model or input tensors are not set to require gradients, which is essential for adversarial attack algorithms like PGD (Projected Gradient Descent).

To perform adversarial testing in val.py, ensure the following:

  1. Enable gradients for input images: Before performing the attack, make sure to enable gradients for your input images. This can be done by setting images.requires_grad = True.
  2. Model in evaluation mode with gradients: While it's common to set the model to evaluation mode (model.eval()) during validation to disable dropout and batch normalization updates, you need to ensure that the model or specific layers still allow gradients computation for the adversarial attack to work. This might involve custom handling to keep certain parts of the model in a state that allows gradient computation.
  3. Correct loss function: Ensure that the loss function you're using is appropriate for the task and is correctly computing gradients. The loss function should be differentiable with respect to the input images, as this is how adversarial examples are generated.
  4. Update val.py to handle adversarial examples: You might need to modify val.py to correctly process adversarial examples. This includes generating adversarial examples within the validation loop and ensuring that these examples are correctly passed through the model for evaluation.

Remember, the goal of adversarial testing is to evaluate the robustness of the model against perturbed inputs that are designed to mislead the model's predictions. This requires careful handling of gradients and model evaluation state.

If after these adjustments you're still facing issues, it might be helpful to review the adversarial attack implementation and ensure it's compatible with the YOLOv5 validation pipeline. Adversarial attacks and defenses are advanced topics that often require custom modifications to the standard training and validation loops.

For further assistance, consider providing more detailed information about the modifications you've made to val.py and the exact nature of the errors or unexpected behaviors you're encountering. This will help in diagnosing the issue more effectively.

github-actions[bot] commented 6 months ago

πŸ‘‹ Hello there! We wanted to give you a friendly reminder that this issue has not had any recent activity and may be closed soon, but don't worry - you can always reopen it if needed. If you still have any questions or concerns, please feel free to let us know how we can help.

For additional resources and information, please see the links below:

Feel free to inform us of any other issues you discover or feature requests that come to mind in the future. Pull Requests (PRs) are also always welcomed!

Thank you for your contributions to YOLO πŸš€ and Vision AI ⭐

zhou-huan-1 commented 5 months ago

thank

glenn-jocher commented 5 months ago

You're welcome! If you have any more questions or need further assistance, feel free to ask. Happy coding! 😊