cleverhans-lab / cleverhans

An adversarial example library for constructing attacks, building defenses, and benchmarking both
MIT License
6.12k stars 1.39k forks source link

Is there any problem in my code of FGSM and I-FGSM? plz HELP #1213

Closed RSMung closed 2 years ago

RSMung commented 3 years ago

Describe I implements the I-FGSM modified from the your FGSM code in https://github.com/cleverhans-lab/cleverhans/blob/1115738a3f31368d73898c5bdd561a85a7c1c741/cleverhans/torch/attacks/fast_gradient_method.py But I find out that when the disturbance intensity (it is e in my code ) is greater than 0.01, I can see the attack in my eyes, which is not reasonable in my opinions. The picture as follows: disturbance intensity is 0.001 0 01 disturbance intensity is 0.1 0 1

But in paper (https://ojs.aaai.org//index.php/AAAI/article/view/4857), we can't observe the attack even when its adversarial intensity set to 16. The picture as follows: 1

Then I want to know if it is normal or not? I will very appreciate it if you could help me. My code as follows:

"""
    cnl_criterion is the nn.CrossEntropyLoss()
    Args: 
        x: the input img
        y: the target mask
        e: the Disturbance intensity
    Return:
        perturbed_x: the input with disturbance
        d: disturbance
    """
    def fgsm(self, x, y, e):
        x = x.clone().detach().requires_grad_(True).to(Config.device)
        seg_x = self.model(x)
        # print(seg_x.shape)
        # print(torch.squeeze(y, 0).shape)
        loss = self.cnl_criterion(seg_x, torch.squeeze(y, 0).long())
        loss.backward()
        d = e * torch.sign(x.grad)
        perturbed_x = x + d
        perturbed_x = torch.clamp(perturbed_x, 0, 1)
        return perturbed_x, d
    """
    Return:
        input+r: the input with disturbance
        r: disturbance
    """
    def i_fgsm(self, input, target, e, max_iter=10):
        input2 = input.clone().detach().requires_grad_(True).to(Config.device)
        r = torch.zeros_like(input2)
        for _ in range(max_iter):
            _, ri = self.fgsm(input2, target, e)
            r = r + ri
            input2 = input2 + ri
        return input+r, r
tejuafonja commented 3 years ago

Hi @RSMung,

Thanks for reporting this. Just had a quick look at the paper. Your implementation needs slight modification for the i-FGSM case. Epsilon (e), becomes alpha (as seen in equation 6 of the paper), then you also need to make sure your adv example is within certain clipping criteria, epsilon (e). We currently don't have i-FGSM implemented in our library but you can check out this implementation to better understand how to modify your current implementation. Once you have this fixed, feel free to contribute it to the library : )

Let me know if this helps.

Screenshot 2021-06-10 at 11 27 13
tejuafonja commented 3 years ago

Also, the link I shared with you implemented the Iterative least-likely class method where x = x-alpha*x.grad, but with slight modification, you should be able to make it work for the basic iterative method.

Screenshot 2021-06-10 at 11 51 33
RSMung commented 3 years ago

Thank you for your reply! You are so nice. I have read your reply carefully, but I still have some confusion about the I-FGSM. The question: When I want to change the disturbance intensity of attack in I-FGSM, what should I going to change? It's the step size ahpha or the eps? On the other hand, in the paper of I-FGSM , the author only show the change of eps. The picture as follow. 捕获 So I change the eps in my code from 0.5 to 32 and set the alpha to 1 , but the results are still poor, I could observe the attack using my eyes when the eps is greater than 0.01 .
Is my result normal or nor? If my result is not normal, I will feel confused because I can't find out the error in my code. My code as follow:

"""
    Args:
        input: the input img
        target: the label img
        eps: the difference between each pixel value and the amplitude
        max_iter: the number of iteration
        alpha: the step size
        x_val_min: the min value of each pixel
        x_val_max: the max value of each pixel
    Return:
        x_adv: the input with disturbance
        x_adv - input: the disturbance
    """
    def i_fgsm(self, input, target, eps, max_iter=10, alpha=1, x_val_min=0, x_val_max=1):
        x_adv = Variable(input.data, requires_grad=True)
        for i in range(max_iter):
            y_adv = self.model(x_adv)
            loss = self.cnl_criterion(y_adv, torch.squeeze(target, 0).long())
            self.model.zero_grad()
            if x_adv.grad is not None:
                x_adv.grad.data.fill_(0)
            loss.backward()
            x_adv.grad.sign_()
            x_adv = x_adv + alpha * x_adv.grad
            x_adv = where(x_adv > input + eps, input + eps, x_adv)
            x_adv = where(x_adv < input - eps, input - eps, x_adv)
            x_adv = torch.clamp(x_adv, x_val_min, x_val_max)
            x_adv = Variable(x_adv.data, requires_grad=True)
        return x_adv, x_adv - input

BEST WISHES TO YOU. I am looking forward to your reply.

tejuafonja commented 3 years ago

Hi @RSMung

The paper set alpha=1 and the disturbance intensity (eps) is controlled in condition i.e

loss.backward()
optimal_perturbation = torch.sign(x_adv.grad)
x_adv = x_adv + alpha * optimal_perturbation
x_adv = torch.where(x_adv > x+eps, x+eps, x_adv)
x_adv = torch.where(x_adv < x-eps, x-eps, x_adv)
Screenshot 2021-06-12 at 12 55 27

A few observations:

  1. According to the paper, max_iter is dependent on epsilon i.e num_iterations=min(eps+4, floor(1.25*eps)). So if eps=32, max_iter should be 36 and not 10. This might not solve your problem but it's worth checking.
  2. The Biomedical image paper set the target as the inverse of the ground-truth masks
Screenshot 2021-06-12 at 12 53 57

A small note, x_adv.grad.sign_() will implicitly replace your x_adv.grad with the sign which is okay in this case but might not be desirable in most cases. Instead, I'll suggest that you do optimial_pertubation = torch.sign(x_adv.grad). This way, you're not changing your x_adv.grad result.

I hope this helps.

tejuafonja commented 3 years ago

@RSMung I just realized that the basic iterative method is implemented in our repository. I deeply apologies for this. It goes by the name Projected Gradient Descent

https://github.com/cleverhans-lab/cleverhans/blob/1115738a3f31368d73898c5bdd561a85a7c1c741/cleverhans/torch/attacks/projected_gradient_descent.py

Can you try the implementation instead and let me know if this works as expected.

RSMung commented 3 years ago

Thank you for your reply. After reading your reply, I know more about this algorithm. Then I temporarily abandon realizing the I-FGSM by myself, I use your implemention to test my model. After that, I find out that it still don't work well. eps = 0.5 image

My Questions:

  1. In your opinions, max_iter is dependent on epsilon i.e *num_iterations=min(eps+4, floor(1.25eps)). But I find out that if the eps is smaller than 0.8, the num_iterations will be 0. There will be no disturbance.** That's unreasonable, so I set the num_iterations to 10 in the previous code. Is it reasonable for me to do so? On the other hand, I modified the code following your opinions, it still don't work well. We could see the result in the top picture. 2

  2. I agree with you about seting the target as the inverse of the ground-truth masks. So I modified my code. But I don't know if it is reasonable. I set the y to torch.ones_like(y) - y, so the 0 pixels will be 1, 1 pixels will be 0.

My code as following:

def attack(self):
    evaluation_list = []
    # start computing all the eva value
    for e in self.epsilon:
        x, y = loadDataForAttack()  # load my data, x is (3, h, w) and y is (1, h, w)
        # --------let the data into the device-----
        x, y = x.to(device).unsqueeze(dim=0), y.to(device)
        # ---------seg the original img----------
        seg_x = self.model(x)  # seg_x is (1, 2, h, w)
        if e == 0:
            eva = 1 - self.cnl_criterion(seg_x, y.long())
            # lel the channel to 1
            # if channel 0 is the max value, the result value is 0
            # if channel 1 is the max value, the result value is 1
            seg_x = self.get_refine_mask(seg_x)  # seg_x is 1, h, w
            evaluation_list.append(eva.item())
            print("e={} --- :{:f}".format(e, eva.item()))
            continue  # Skip the code below
        # ----------get the x_adv---------
        # because of An attack sets the target as the inverse of ground-truth masks
        # I let y=(torch.ones_like(y) - y)
        if Config.attack_algorithm_type == 0:
            # fgsm algorithm
            x_adv = fast_gradient_method(self.model, x, e, np.inf, clip_min=0, clip_max=1,
                                         y=(torch.ones_like(y) - y).long(), targeted=True)
        else:
            # i-fgsm
            x_adv = projected_gradient_descent(self.model, x, e, 0.01, min(e + 4, math.floor(1.25 * e)), np.inf,
                                               clip_min=0, clip_max=1, y=(torch.ones_like(y) - y).long(), targeted=True)
        # ------seg the x_adv------
        seg_x_adv = self.model(x_adv)
        # ------compute the eva value-----
        eva = self.cnl_criterion(seg_x_adv, y.long())
        evaluation_list.append(eva)
        print("e= {} --- {:f}".format(e, eva.item()))

def get_refine_mask(self, x):
    # x is 1, 2, h, w
    x = x.squeeze()  # 2, h, w
    x = x[1] - x[0]  # h, w
    x = torch.where(x > 0, torch.ones_like(x), torch.zeros_like(x))  # h, w
    return x.unsqueeze(dim=0)  # 1, h, w
RSMung commented 3 years ago

I'm so careless that *the num_iterations should not be min(eps+4, floor(1.25eps))*. The usage of API math.floor is wrong. Because the formula requires a larger integer for 1.25eps. I think there will be something difference. Let me try, then I will tell you the result.

RSMung commented 3 years ago

On the other hand, I feel confused to this sentence: the L∞ norm of adversarial perturbation to in- tensity.. If it means that Could you help me understand it? I will very appreciate it. I am looking forward to your reply.

RSMung commented 3 years ago

I find out that the norm is np.inf in your examples of projected_gradient_descent. But I don't understand what it means. The another question: How to choose eps_iter when the eps is changed from 0.5 to 32? If I set the eps_iter to 1 following the paper, the pycharm will give me some error caused by sanity_checks.

tejuafonja commented 3 years ago

Hi @RSMung,

I'm sorry to hear that you haven't been able to solve this problem. I'm glad you found the math.floor bug. Does it work now? I agree, the distinction between alpha and epsilon seem confusing. You might need to test different implementations to see which works best for the paper you're trying to re-implement. It's might also be the case that the paper is lacking in reproducibility and you might need to use a different parameter settings to get the reported results.

np.inf (l-infinity) norm measures the distance between input x and the adversarial example x_adv. The l-infinity norm has the interpretation of minimizing the maximum element of the perturbation. It does so by taking the sign of the gradient i.e torch.sign(x_adv.grad), you might find this material on adversarial attacks useful, geometrical interpretation of the norms are presented on page 5. In the paper you shared, they used l-inf. Our implementation only supports (l-inf, l1 and l2), check our implementation for more descriptions of the remaining norms.

I have also found this material useful which gives an overview of adversarial example and how to solve the inner maximization problem, you can skip to the Projected gradient descent as it's the most relevant to your question.

I hope this helps.

RSMung commented 3 years ago

I think I might have solved this problem.

According to this paper, I find out that the values of the pixels are integer numbers in the range(0, 255), the they use the eps from 0 to 128, including 16, 32 etc, as same as the paper I want to implemente.

But when we train our model, the image will be normalized, so the values of pixels are float numbers in the range (0, 1). As far as I'm concerned, we should let the eps divide 255 before we deliver the parameter eps to the attack function.

Do you think my idea is reasonable? Looking forward to your reply.

RSMung commented 3 years ago

image

tejuafonja commented 2 years ago

Hi @RSMung, were you able to resolve this issue? Your idea does sound reasonable, did it work? Apologies for my late reply - the past few months was a little hectic.

RSMung commented 2 years ago

It doesn't matter. I could understand that you may be busy. On the other hand, I think my last comments are reasonable. When we set the eps, we should pay attention to the range. As a result, if I set the eps=8 or more large( I use the range [0, 255] here), the result would be very strange when my data is in [0, 1]. What's your opinion?