adv-lib 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- adv_lib/__init__.py +1 -0
- adv_lib/attacks/__init__.py +13 -0
- adv_lib/attacks/augmented_lagrangian.py +243 -0
- adv_lib/attacks/auto_pgd.py +523 -0
- adv_lib/attacks/boundary_projection_tf.py +170 -0
- adv_lib/attacks/carlini_wagner/__init__.py +2 -0
- adv_lib/attacks/carlini_wagner/l2.py +151 -0
- adv_lib/attacks/carlini_wagner/linf.py +158 -0
- adv_lib/attacks/decoupled_direction_norm.py +113 -0
- adv_lib/attacks/fast_adaptive_boundary/__init__.py +1 -0
- adv_lib/attacks/fast_adaptive_boundary/fast_adaptive_boundary.py +215 -0
- adv_lib/attacks/fast_adaptive_boundary/projections.py +164 -0
- adv_lib/attacks/fast_minimum_norm.py +218 -0
- adv_lib/attacks/perceptual_color_attacks/__init__.py +1 -0
- adv_lib/attacks/perceptual_color_attacks/differential_color_functions.py +181 -0
- adv_lib/attacks/perceptual_color_attacks/perceptual_color_distance_al.py +128 -0
- adv_lib/attacks/primal_dual_gradient_descent.py +379 -0
- adv_lib/attacks/projected_gradient_descent.py +109 -0
- adv_lib/attacks/segmentation/__init__.py +4 -0
- adv_lib/attacks/segmentation/alma_prox.py +283 -0
- adv_lib/attacks/segmentation/asma.py +92 -0
- adv_lib/attacks/segmentation/dense_adversary.py +83 -0
- adv_lib/attacks/segmentation/primal_dual_gradient_descent.py +349 -0
- adv_lib/attacks/self_adaptive_norm_update.py +127 -0
- adv_lib/attacks/sigma_zero.py +119 -0
- adv_lib/attacks/stochastic_sparse_attacks.py +237 -0
- adv_lib/attacks/structured_adversarial_attack.py +289 -0
- adv_lib/attacks/trust_region.py +153 -0
- adv_lib/distances/__init__.py +0 -0
- adv_lib/distances/color_difference.py +212 -0
- adv_lib/distances/lp_norms.py +18 -0
- adv_lib/distances/lpips.py +99 -0
- adv_lib/distances/structural_similarity.py +147 -0
- adv_lib/utils/__init__.py +1 -0
- adv_lib/utils/attack_utils.py +226 -0
- adv_lib/utils/color_conversions.py +71 -0
- adv_lib/utils/image_selection.py +27 -0
- adv_lib/utils/lagrangian_penalties/__init__.py +1 -0
- adv_lib/utils/lagrangian_penalties/all_penalties.py +67 -0
- adv_lib/utils/lagrangian_penalties/penalty_functions.py +79 -0
- adv_lib/utils/lagrangian_penalties/scripts/plot_penalties.py +42 -0
- adv_lib/utils/lagrangian_penalties/scripts/plot_univariates.py +32 -0
- adv_lib/utils/lagrangian_penalties/univariate_functions.py +299 -0
- adv_lib/utils/losses.py +29 -0
- adv_lib/utils/projections.py +100 -0
- adv_lib/utils/utils.py +58 -0
- adv_lib/utils/visdom_logger.py +109 -0
- adv_lib-0.2.2.dist-info/LICENSE +29 -0
- adv_lib-0.2.2.dist-info/METADATA +170 -0
- adv_lib-0.2.2.dist-info/RECORD +52 -0
- adv_lib-0.2.2.dist-info/WHEEL +5 -0
- adv_lib-0.2.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
from torch import Tensor
|
|
5
|
+
from torch.nn import functional as F
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def projection_l1(points_to_project: Tensor, w_hyperplane: Tensor, b_hyperplane: Tensor) -> Tensor:
|
|
9
|
+
device = points_to_project.device
|
|
10
|
+
t, w, b = points_to_project, w_hyperplane.clone(), b_hyperplane
|
|
11
|
+
|
|
12
|
+
c = (w * t).sum(1).sub_(b)
|
|
13
|
+
ind2 = (c >= 0).float().mul_(2).sub_(1)
|
|
14
|
+
w.mul_(ind2.unsqueeze(1))
|
|
15
|
+
c.mul_(ind2)
|
|
16
|
+
|
|
17
|
+
w_abs = w.abs()
|
|
18
|
+
r = (1 / w_abs).clamp_(max=1e12)
|
|
19
|
+
indr = torch.argsort(r, dim=1)
|
|
20
|
+
indr_rev = torch.argsort(indr)
|
|
21
|
+
|
|
22
|
+
d = (w < 0).float().sub_(t).mul_(w != 0)
|
|
23
|
+
ds = torch.min(-w * t, (1 - t).mul_(w)).gather(1, indr)
|
|
24
|
+
ds2 = torch.cat((c.unsqueeze(-1), ds), 1)
|
|
25
|
+
s = torch.cumsum(ds2, dim=1)
|
|
26
|
+
|
|
27
|
+
c2 = s[:, -1] < 0
|
|
28
|
+
|
|
29
|
+
lb = torch.zeros(c2.sum(), device=device)
|
|
30
|
+
ub = torch.full_like(lb, s.shape[1])
|
|
31
|
+
nitermax = math.ceil(math.log2(w.shape[1]))
|
|
32
|
+
|
|
33
|
+
s_ = s[c2]
|
|
34
|
+
for counter in range(nitermax):
|
|
35
|
+
counter4 = (lb + ub).mul_(0.5).floor_()
|
|
36
|
+
counter2 = counter4.long().unsqueeze(1)
|
|
37
|
+
c3 = s_.gather(1, counter2).squeeze(1) > 0
|
|
38
|
+
lb = torch.where(c3, counter4, lb)
|
|
39
|
+
ub = torch.where(c3, ub, counter4)
|
|
40
|
+
|
|
41
|
+
lb2 = lb.long()
|
|
42
|
+
|
|
43
|
+
if c2.any():
|
|
44
|
+
indr = indr[c2].gather(1, lb2.unsqueeze(1)).squeeze(1)
|
|
45
|
+
u = torch.arange(0, w.shape[0], device=device).unsqueeze(1)
|
|
46
|
+
u2 = torch.arange(0, w.shape[1], device=device, dtype=torch.float).unsqueeze(0)
|
|
47
|
+
alpha = s[c2, lb2].neg().div_(w[c2, indr])
|
|
48
|
+
c5 = u2 < lb.unsqueeze(-1)
|
|
49
|
+
u3 = c5[u[:c5.shape[0]], indr_rev[c2]]
|
|
50
|
+
d[c2] *= u3
|
|
51
|
+
d[c2, indr] = alpha
|
|
52
|
+
|
|
53
|
+
return d.mul_(w_abs > 1e-8)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def projection_l2(points_to_project: Tensor, w_hyperplane: Tensor, b_hyperplane: Tensor) -> Tensor:
|
|
57
|
+
device = points_to_project.device
|
|
58
|
+
t, w, b = points_to_project, w_hyperplane.clone(), b_hyperplane
|
|
59
|
+
|
|
60
|
+
c = (w * t).sum(1).sub_(b)
|
|
61
|
+
ind2 = (c >= 0).float().mul_(2).sub_(1)
|
|
62
|
+
w.mul_(ind2.unsqueeze(1))
|
|
63
|
+
w_nonzero = w.abs() > 1e-8
|
|
64
|
+
c.mul_(ind2)
|
|
65
|
+
|
|
66
|
+
r = torch.maximum(t / w, (t - 1).div_(w)).clamp_(min=-1e12, max=1e12)
|
|
67
|
+
r.masked_fill_(~w_nonzero, 1e12)
|
|
68
|
+
r[r == -1e12] *= -1
|
|
69
|
+
rs, indr = torch.sort(r, dim=1)
|
|
70
|
+
rs2 = F.pad(rs[:, 1:], (0, 1))
|
|
71
|
+
rs.masked_fill_(rs == 1e12, 0)
|
|
72
|
+
rs2.masked_fill_(rs2 == 1e12, 0)
|
|
73
|
+
|
|
74
|
+
w3s = w.square().gather(1, indr)
|
|
75
|
+
w5 = w3s.sum(dim=1, keepdim=True)
|
|
76
|
+
ws = w5 - torch.cumsum(w3s, dim=1)
|
|
77
|
+
d = (r * w).neg_()
|
|
78
|
+
d.mul_(w_nonzero)
|
|
79
|
+
s = torch.cat((-w5 * rs[:, 0:1], torch.cumsum((rs - rs2).mul_(ws), dim=1).sub_(w5 * rs[:, 0:1])), 1)
|
|
80
|
+
|
|
81
|
+
c4 = s[:, 0] + c < 0
|
|
82
|
+
c3 = (d * w).sum(dim=1).add_(c) > 0
|
|
83
|
+
c2 = ~(c4 | c3)
|
|
84
|
+
|
|
85
|
+
lb = torch.zeros(c2.sum(), device=device)
|
|
86
|
+
ub = torch.full_like(lb, w.shape[1] - 1)
|
|
87
|
+
nitermax = math.ceil(math.log2(w.shape[1]))
|
|
88
|
+
|
|
89
|
+
s_, c_ = s[c2], c[c2]
|
|
90
|
+
for counter in range(nitermax):
|
|
91
|
+
counter4 = (lb + ub).mul_(0.5).floor_()
|
|
92
|
+
counter2 = counter4.long().unsqueeze(1)
|
|
93
|
+
c3 = s_.gather(1, counter2).squeeze(1).add_(c_) > 0
|
|
94
|
+
lb = torch.where(c3, counter4, lb)
|
|
95
|
+
ub = torch.where(c3, ub, counter4)
|
|
96
|
+
|
|
97
|
+
lb = lb.long()
|
|
98
|
+
|
|
99
|
+
if c4.any():
|
|
100
|
+
alpha = c[c4] / w5[c4].squeeze(-1)
|
|
101
|
+
d[c4] = -alpha.unsqueeze(-1) * w[c4]
|
|
102
|
+
|
|
103
|
+
if c2.any():
|
|
104
|
+
alpha = (s[c2, lb] + c[c2]).div_(ws[c2, lb]).add_(rs[c2, lb])
|
|
105
|
+
alpha[ws[c2, lb] == 0] = 0
|
|
106
|
+
c5 = alpha.unsqueeze(-1) > r[c2]
|
|
107
|
+
d[c2] = (d[c2] * c5).sub_((~c5).float().mul_(alpha.unsqueeze(-1)).mul_(w[c2]))
|
|
108
|
+
|
|
109
|
+
return d.mul_(w_nonzero)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def projection_linf(points_to_project: Tensor, w_hyperplane: Tensor, b_hyperplane: Tensor) -> Tensor:
|
|
113
|
+
device = points_to_project.device
|
|
114
|
+
t, w, b = points_to_project, w_hyperplane.clone(), b_hyperplane.clone()
|
|
115
|
+
|
|
116
|
+
sign = ((w * t).sum(1).sub_(b) >= 0).float().mul_(2).sub_(1)
|
|
117
|
+
w.mul_(sign.unsqueeze(1))
|
|
118
|
+
b.mul_(sign)
|
|
119
|
+
|
|
120
|
+
a = (w < 0).float()
|
|
121
|
+
d = (a - t).mul_(w != 0)
|
|
122
|
+
|
|
123
|
+
p = (2 * a).sub_(1).mul_(t).neg_().add_(a)
|
|
124
|
+
indp = torch.argsort(p, dim=1)
|
|
125
|
+
|
|
126
|
+
b.sub_((w * t).sum(1))
|
|
127
|
+
b0 = (w * d).sum(1)
|
|
128
|
+
|
|
129
|
+
indp2 = indp.flip((1,))
|
|
130
|
+
ws = w.gather(1, indp2)
|
|
131
|
+
bs2 = -ws * d.gather(1, indp2)
|
|
132
|
+
|
|
133
|
+
s = torch.cumsum(ws.abs_(), dim=1)
|
|
134
|
+
sb = torch.cumsum(bs2, dim=1).add_(b0.unsqueeze(1))
|
|
135
|
+
|
|
136
|
+
b2 = sb[:, -1] - s[:, -1] * p.gather(1, indp[:, 0:1]).squeeze(1)
|
|
137
|
+
c_l = b - b2 > 0
|
|
138
|
+
c2 = (b - b0 > 0) & (~c_l)
|
|
139
|
+
lb = torch.zeros(c2.sum(), device=device)
|
|
140
|
+
ub = torch.full_like(lb, w.shape[1] - 1)
|
|
141
|
+
nitermax = math.ceil(math.log2(w.shape[1]))
|
|
142
|
+
|
|
143
|
+
indp_, sb_, s_, p_, b_ = indp[c2], sb[c2], s[c2], p[c2], b[c2]
|
|
144
|
+
for counter in range(nitermax):
|
|
145
|
+
counter4 = (lb + ub).mul_(0.5).floor_()
|
|
146
|
+
|
|
147
|
+
counter2 = counter4.long().unsqueeze(1)
|
|
148
|
+
indcurr = indp_.gather(1, indp_.size(1) - 1 - counter2)
|
|
149
|
+
b2 = sb_.gather(1, counter2).sub_(s_.gather(1, counter2).mul_(p_.gather(1, indcurr))).squeeze(1)
|
|
150
|
+
c = b_ - b2 > 0
|
|
151
|
+
|
|
152
|
+
lb = torch.where(c, counter4, lb)
|
|
153
|
+
ub = torch.where(c, ub, counter4)
|
|
154
|
+
|
|
155
|
+
lb = lb.long()
|
|
156
|
+
|
|
157
|
+
if c_l.any():
|
|
158
|
+
lmbd_opt = (b[c_l] - sb[c_l, -1]).div_(-s[c_l, -1]).clamp_(min=0).unsqueeze_(-1)
|
|
159
|
+
d[c_l] = (2 * a[c_l]).sub_(1).mul_(lmbd_opt)
|
|
160
|
+
|
|
161
|
+
lmbd_opt = (b[c2] - sb[c2, lb]).div_(-s[c2, lb]).clamp_(min=0).unsqueeze_(-1)
|
|
162
|
+
d[c2] = torch.minimum(lmbd_opt, d[c2]).mul_(a[c2]).add_(torch.maximum(-lmbd_opt, d[c2]).mul_(1 - a[c2]))
|
|
163
|
+
|
|
164
|
+
return d.mul_(w != 0)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Adapted from https://github.com/maurapintor/Fast-Minimum-Norm-FMN-Attack
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from functools import partial
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import torch
|
|
8
|
+
from torch import Tensor, nn
|
|
9
|
+
from torch.autograd import grad
|
|
10
|
+
|
|
11
|
+
from adv_lib.utils.losses import difference_of_logits
|
|
12
|
+
from adv_lib.utils.projections import clamp_, l1_ball_euclidean_projection
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def l0_projection_(δ: Tensor, ε: Tensor) -> Tensor:
|
|
16
|
+
"""In-place l0 projection"""
|
|
17
|
+
δ = δ.flatten(1)
|
|
18
|
+
δ_abs = δ.abs()
|
|
19
|
+
thresholds = δ_abs.topk(k=ε.long().max(), dim=1).values.gather(1, (ε.long().unsqueeze(1) - 1).clamp_(min=0))
|
|
20
|
+
δ[δ_abs < thresholds] = 0
|
|
21
|
+
return δ
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def l1_projection_(δ: Tensor, ε: Tensor) -> Tensor:
|
|
25
|
+
"""In-place l1 projection"""
|
|
26
|
+
δ = l1_ball_euclidean_projection(x=δ.flatten(1), ε=ε, inplace=True)
|
|
27
|
+
return δ
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def l2_projection_(δ: Tensor, ε: Tensor) -> Tensor:
|
|
31
|
+
"""In-place l2 projection"""
|
|
32
|
+
δ = δ.flatten(1)
|
|
33
|
+
l2_norms = δ.norm(p=2, dim=1, keepdim=True).clamp_(min=1e-12)
|
|
34
|
+
δ.mul_(ε.unsqueeze(1) / l2_norms).clamp_(max=1)
|
|
35
|
+
return δ
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def linf_projection_(δ: Tensor, ε: Tensor) -> Tensor:
|
|
39
|
+
"""In-place linf projection"""
|
|
40
|
+
δ, ε = δ.flatten(1), ε.unsqueeze(1)
|
|
41
|
+
δ = clamp_(δ, lower=-ε, upper=ε)
|
|
42
|
+
return δ
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def l0_mid_points(x0: Tensor, x1: Tensor, ε: Tensor) -> Tensor:
|
|
46
|
+
n_features = x0[0].numel()
|
|
47
|
+
δ = l0_projection_(δ=x1 - x0, ε=n_features * ε)
|
|
48
|
+
return δ.view_as(x0).add_(x0)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def l1_mid_points(x0: Tensor, x1: Tensor, ε: Tensor) -> Tensor:
|
|
52
|
+
threshold = (1 - ε).unsqueeze(1)
|
|
53
|
+
δ = (x1 - x0).flatten(1)
|
|
54
|
+
δ_abs = δ.abs()
|
|
55
|
+
mask = δ_abs <= threshold
|
|
56
|
+
mid_points = δ_abs.sub_(threshold).copysign_(δ)
|
|
57
|
+
mid_points[mask] = 0
|
|
58
|
+
return mid_points.view_as(x0).add_(x0)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def l2_mid_points(x0: Tensor, x1: Tensor, ε: Tensor) -> Tensor:
|
|
62
|
+
ε = ε.unsqueeze(1)
|
|
63
|
+
return x0.flatten(1).mul(1 - ε).addcmul_(ε, x1.flatten(1)).view_as(x0)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def linf_mid_points(x0: Tensor, x1: Tensor, ε: Tensor) -> Tensor:
|
|
67
|
+
ε = ε.unsqueeze(1)
|
|
68
|
+
δ = clamp_((x1 - x0).flatten(1), lower=-ε, upper=ε)
|
|
69
|
+
return δ.view_as(x0).add_(x0)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def fmn(model: nn.Module,
|
|
73
|
+
inputs: Tensor,
|
|
74
|
+
labels: Tensor,
|
|
75
|
+
norm: float,
|
|
76
|
+
targeted: bool = False,
|
|
77
|
+
steps: int = 10,
|
|
78
|
+
α_init: float = 1.0,
|
|
79
|
+
α_final: Optional[float] = None,
|
|
80
|
+
γ_init: float = 0.05,
|
|
81
|
+
γ_final: float = 0.001,
|
|
82
|
+
starting_points: Optional[Tensor] = None,
|
|
83
|
+
binary_search_steps: int = 10) -> Tensor:
|
|
84
|
+
"""
|
|
85
|
+
Fast Minimum-Norm attack from https://arxiv.org/abs/2102.12827.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
model : nn.Module
|
|
90
|
+
Model to attack.
|
|
91
|
+
inputs : Tensor
|
|
92
|
+
Inputs to attack. Should be in [0, 1].
|
|
93
|
+
labels : Tensor
|
|
94
|
+
Labels corresponding to the inputs if untargeted, else target labels.
|
|
95
|
+
norm : float
|
|
96
|
+
Norm to minimize in {0, 1, 2 ,float('inf')}.
|
|
97
|
+
targeted : bool
|
|
98
|
+
Whether to perform a targeted attack or not.
|
|
99
|
+
steps : int
|
|
100
|
+
Number of optimization steps.
|
|
101
|
+
α_init : float
|
|
102
|
+
Initial step size.
|
|
103
|
+
α_final : float
|
|
104
|
+
Final step size after cosine annealing.
|
|
105
|
+
γ_init : float
|
|
106
|
+
Initial factor by which ε is modified: ε = ε * (1 + or - γ).
|
|
107
|
+
γ_final : float
|
|
108
|
+
Final factor, after cosine annealing, by which ε is modified.
|
|
109
|
+
starting_points : Tensor
|
|
110
|
+
Optional warm-start for the attack.
|
|
111
|
+
binary_search_steps : int
|
|
112
|
+
Number of binary search steps to find the decision boundary between inputs and starting_points.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
adv_inputs : Tensor
|
|
117
|
+
Modified inputs to be adversarial to the model.
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
_dual_projection_mid_points = {
|
|
121
|
+
0: (None, l0_projection_, l0_mid_points),
|
|
122
|
+
1: (float('inf'), l1_projection_, l1_mid_points),
|
|
123
|
+
2: (2, l2_projection_, l2_mid_points),
|
|
124
|
+
float('inf'): (1, linf_projection_, linf_mid_points),
|
|
125
|
+
}
|
|
126
|
+
if inputs.min() < 0 or inputs.max() > 1: raise ValueError('Input values should be in the [0, 1] range.')
|
|
127
|
+
device = inputs.device
|
|
128
|
+
batch_size = len(inputs)
|
|
129
|
+
batch_view = lambda tensor: tensor.view(batch_size, *[1] * (inputs.ndim - 1))
|
|
130
|
+
dual, projection, mid_point = _dual_projection_mid_points[norm]
|
|
131
|
+
α_final = α_init / 100 if α_final is None else α_final
|
|
132
|
+
multiplier = 1 if targeted else -1
|
|
133
|
+
|
|
134
|
+
# If starting_points is provided, search for the boundary
|
|
135
|
+
if starting_points is not None:
|
|
136
|
+
start_preds = model(starting_points).argmax(dim=1)
|
|
137
|
+
is_adv = (start_preds == labels) if targeted else (start_preds != labels)
|
|
138
|
+
if not is_adv.all():
|
|
139
|
+
raise ValueError('Starting points are not all adversarial.')
|
|
140
|
+
lower_bound = torch.zeros(batch_size, device=device)
|
|
141
|
+
upper_bound = torch.ones(batch_size, device=device)
|
|
142
|
+
for _ in range(binary_search_steps):
|
|
143
|
+
ε = (lower_bound + upper_bound) / 2
|
|
144
|
+
mid_points = mid_point(x0=inputs, x1=starting_points, ε=ε)
|
|
145
|
+
pred_labels = model(mid_points).argmax(dim=1)
|
|
146
|
+
is_adv = (pred_labels == labels) if targeted else (pred_labels != labels)
|
|
147
|
+
lower_bound = torch.where(is_adv, lower_bound, ε)
|
|
148
|
+
upper_bound = torch.where(is_adv, ε, upper_bound)
|
|
149
|
+
|
|
150
|
+
δ = mid_point(x0=inputs, x1=starting_points, ε=upper_bound).sub_(inputs)
|
|
151
|
+
else:
|
|
152
|
+
δ = torch.zeros_like(inputs)
|
|
153
|
+
δ.requires_grad_(True)
|
|
154
|
+
|
|
155
|
+
if norm == 0:
|
|
156
|
+
ε = torch.ones(batch_size, device=device) if starting_points is None else δ.flatten(1).norm(p=0, dim=1)
|
|
157
|
+
else:
|
|
158
|
+
ε = torch.full((batch_size,), float('inf'), device=device)
|
|
159
|
+
|
|
160
|
+
# Init trackers
|
|
161
|
+
worst_norm = torch.maximum(inputs, 1 - inputs).flatten(1).norm(p=norm, dim=1)
|
|
162
|
+
best_norm = worst_norm.clone()
|
|
163
|
+
best_adv = inputs.clone()
|
|
164
|
+
adv_found = torch.zeros(batch_size, dtype=torch.bool, device=device)
|
|
165
|
+
|
|
166
|
+
for i in range(steps):
|
|
167
|
+
cosine = (1 + math.cos(math.pi * i / steps)) / 2
|
|
168
|
+
α = α_final + (α_init - α_final) * cosine
|
|
169
|
+
γ = γ_final + (γ_init - γ_final) * cosine
|
|
170
|
+
|
|
171
|
+
δ_norm = δ.data.flatten(1).norm(p=norm, dim=1)
|
|
172
|
+
adv_inputs = inputs + δ
|
|
173
|
+
logits = model(adv_inputs)
|
|
174
|
+
pred_labels = logits.argmax(dim=1)
|
|
175
|
+
|
|
176
|
+
if i == 0:
|
|
177
|
+
labels_infhot = torch.zeros_like(logits).scatter_(1, labels.unsqueeze(1), float('inf'))
|
|
178
|
+
logit_diff_func = partial(difference_of_logits, labels=labels, labels_infhot=labels_infhot)
|
|
179
|
+
|
|
180
|
+
logit_diffs = logit_diff_func(logits=logits)
|
|
181
|
+
loss = multiplier * logit_diffs
|
|
182
|
+
δ_grad = grad(loss.sum(), δ, only_inputs=True)[0]
|
|
183
|
+
|
|
184
|
+
is_adv = (pred_labels == labels) if targeted else (pred_labels != labels)
|
|
185
|
+
is_smaller = δ_norm < best_norm
|
|
186
|
+
is_both = is_adv & is_smaller
|
|
187
|
+
adv_found.logical_or_(is_adv)
|
|
188
|
+
best_norm = torch.where(is_both, δ_norm, best_norm)
|
|
189
|
+
best_adv = torch.where(batch_view(is_both), adv_inputs.detach(), best_adv)
|
|
190
|
+
|
|
191
|
+
if norm == 0:
|
|
192
|
+
ε = torch.where(is_adv,
|
|
193
|
+
torch.minimum(torch.minimum(ε - 1, (ε * (1 - γ)).floor_()), best_norm),
|
|
194
|
+
torch.maximum(ε + 1, (ε * (1 + γ)).floor_()))
|
|
195
|
+
ε.clamp_(min=0)
|
|
196
|
+
else:
|
|
197
|
+
distance_to_boundary = loss.detach().abs() / δ_grad.flatten(1).norm(p=dual, dim=1).clamp_(min=1e-12)
|
|
198
|
+
ε = torch.where(is_adv,
|
|
199
|
+
torch.minimum(ε * (1 - γ), best_norm),
|
|
200
|
+
torch.where(adv_found, ε * (1 + γ), δ_norm + distance_to_boundary))
|
|
201
|
+
|
|
202
|
+
# clip ε
|
|
203
|
+
ε = torch.minimum(ε, worst_norm)
|
|
204
|
+
|
|
205
|
+
# normalize gradient
|
|
206
|
+
grad_l2_norms = δ_grad.flatten(1).norm(p=2, dim=1).clamp_(min=1e-12)
|
|
207
|
+
δ_grad.div_(batch_view(grad_l2_norms))
|
|
208
|
+
|
|
209
|
+
# gradient ascent step
|
|
210
|
+
δ.data.add_(δ_grad, alpha=α)
|
|
211
|
+
|
|
212
|
+
# project in place
|
|
213
|
+
projection(δ=δ.data, ε=ε)
|
|
214
|
+
|
|
215
|
+
# clamp
|
|
216
|
+
δ.data.add_(inputs).clamp_(min=0, max=1).sub_(inputs)
|
|
217
|
+
|
|
218
|
+
return best_adv
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .perceptual_color_distance_al import perc_al
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Adapted from https://github.com/ZhengyuZhao/PerC-Adversarial
|
|
2
|
+
import numpy as np
|
|
3
|
+
import torch
|
|
4
|
+
from torch import Tensor
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def rgb2xyz(rgb_image):
|
|
8
|
+
device = rgb_image.device
|
|
9
|
+
mt = torch.tensor([[0.4124, 0.3576, 0.1805],
|
|
10
|
+
[0.2126, 0.7152, 0.0722],
|
|
11
|
+
[0.0193, 0.1192, 0.9504]], device=device)
|
|
12
|
+
mask1 = (rgb_image > 0.0405).float()
|
|
13
|
+
mask1_no = 1 - mask1
|
|
14
|
+
temp_img = mask1 * (((rgb_image + 0.055) / 1.055) ** 2.4)
|
|
15
|
+
temp_img = temp_img + mask1_no * (rgb_image / 12.92)
|
|
16
|
+
temp_img = 100 * temp_img
|
|
17
|
+
|
|
18
|
+
res = torch.matmul(mt, temp_img.permute(1, 0, 2, 3).contiguous().view(3, -1)).view(
|
|
19
|
+
3, rgb_image.size(0), rgb_image.size(2), rgb_image.size(3)).permute(1, 0, 2, 3)
|
|
20
|
+
return res
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def xyz_lab(xyz_image):
|
|
24
|
+
mask_value_0 = (xyz_image == 0).float()
|
|
25
|
+
mask_value_0_no = 1 - mask_value_0
|
|
26
|
+
xyz_image = xyz_image + 0.0001 * mask_value_0
|
|
27
|
+
mask1 = (xyz_image > 0.008856).float()
|
|
28
|
+
mask1_no = 1 - mask1
|
|
29
|
+
res = mask1 * (xyz_image) ** (1 / 3)
|
|
30
|
+
res = res + mask1_no * ((7.787 * xyz_image) + (16 / 116))
|
|
31
|
+
res = res * mask_value_0_no
|
|
32
|
+
return res
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def rgb2lab_diff(rgb_image):
|
|
36
|
+
"""
|
|
37
|
+
Function to convert a batch of image tensors from RGB space to CIELAB space.
|
|
38
|
+
parameters: xn, yn, zn are the CIE XYZ tristimulus values of the reference white point.
|
|
39
|
+
Here use the standard Illuminant D65 with normalization Y = 100.
|
|
40
|
+
"""
|
|
41
|
+
rgb_image = rgb_image
|
|
42
|
+
res = torch.zeros_like(rgb_image)
|
|
43
|
+
xyz_image = rgb2xyz(rgb_image)
|
|
44
|
+
|
|
45
|
+
xn = 95.0489
|
|
46
|
+
yn = 100
|
|
47
|
+
zn = 108.8840
|
|
48
|
+
|
|
49
|
+
x = xyz_image[:, 0, :, :]
|
|
50
|
+
y = xyz_image[:, 1, :, :]
|
|
51
|
+
z = xyz_image[:, 2, :, :]
|
|
52
|
+
|
|
53
|
+
L = 116 * xyz_lab(y / yn) - 16
|
|
54
|
+
a = 500 * (xyz_lab(x / xn) - xyz_lab(y / yn))
|
|
55
|
+
b = 200 * (xyz_lab(y / yn) - xyz_lab(z / zn))
|
|
56
|
+
res[:, 0, :, :] = L
|
|
57
|
+
res[:, 1, :, :] = a
|
|
58
|
+
res[:, 2, :, :] = b
|
|
59
|
+
|
|
60
|
+
return res
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def degrees(n): return n * (180. / np.pi)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def radians(n): return n * (np.pi / 180.)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def hpf_diff(x, y):
|
|
70
|
+
mask1 = ((x == 0) * (y == 0)).float()
|
|
71
|
+
mask1_no = 1 - mask1
|
|
72
|
+
|
|
73
|
+
tmphp = degrees(torch.atan2(x * mask1_no, y * mask1_no))
|
|
74
|
+
tmphp1 = tmphp * (tmphp >= 0).float()
|
|
75
|
+
tmphp2 = (360 + tmphp) * (tmphp < 0).float()
|
|
76
|
+
|
|
77
|
+
return tmphp1 + tmphp2
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def dhpf_diff(c1, c2, h1p, h2p):
|
|
81
|
+
mask1 = ((c1 * c2) == 0).float()
|
|
82
|
+
mask1_no = 1 - mask1
|
|
83
|
+
res1 = (h2p - h1p) * mask1_no * (torch.abs(h2p - h1p) <= 180).float()
|
|
84
|
+
res2 = ((h2p - h1p) - 360) * ((h2p - h1p) > 180).float() * mask1_no
|
|
85
|
+
res3 = ((h2p - h1p) + 360) * ((h2p - h1p) < -180).float() * mask1_no
|
|
86
|
+
|
|
87
|
+
return res1 + res2 + res3
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def ahpf_diff(c1, c2, h1p, h2p):
|
|
91
|
+
mask1 = ((c1 * c2) == 0).float()
|
|
92
|
+
mask1_no = 1 - mask1
|
|
93
|
+
mask2 = (torch.abs(h2p - h1p) <= 180).float()
|
|
94
|
+
mask2_no = 1 - mask2
|
|
95
|
+
mask3 = (torch.abs(h2p + h1p) < 360).float()
|
|
96
|
+
mask3_no = 1 - mask3
|
|
97
|
+
|
|
98
|
+
res1 = (h1p + h2p) * mask1_no * mask2
|
|
99
|
+
res2 = (h1p + h2p + 360.) * mask1_no * mask2_no * mask3
|
|
100
|
+
res3 = (h1p + h2p - 360.) * mask1_no * mask2_no * mask3_no
|
|
101
|
+
res = (res1 + res2 + res3) + (res1 + res2 + res3) * mask1
|
|
102
|
+
return res * 0.5
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def ciede2000_diff(lab1, lab2):
|
|
106
|
+
"""
|
|
107
|
+
CIEDE2000 metric to claculate the color distance map for a batch of image tensors defined in CIELAB space
|
|
108
|
+
|
|
109
|
+
This version contains errors:
|
|
110
|
+
- Typo in the formula for T: "- 39" should be "- 30"
|
|
111
|
+
- 0.0001s added for numerical stability change the conditional values
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
L1 = lab1[:, 0, :, :]
|
|
115
|
+
A1 = lab1[:, 1, :, :]
|
|
116
|
+
B1 = lab1[:, 2, :, :]
|
|
117
|
+
L2 = lab2[:, 0, :, :]
|
|
118
|
+
A2 = lab2[:, 1, :, :]
|
|
119
|
+
B2 = lab2[:, 2, :, :]
|
|
120
|
+
kL = 1
|
|
121
|
+
kC = 1
|
|
122
|
+
kH = 1
|
|
123
|
+
|
|
124
|
+
mask_value_0_input1 = ((A1 == 0) * (B1 == 0)).float()
|
|
125
|
+
mask_value_0_input2 = ((A2 == 0) * (B2 == 0)).float()
|
|
126
|
+
mask_value_0_input1_no = 1 - mask_value_0_input1
|
|
127
|
+
mask_value_0_input2_no = 1 - mask_value_0_input2
|
|
128
|
+
B1 = B1 + 0.0001 * mask_value_0_input1
|
|
129
|
+
B2 = B2 + 0.0001 * mask_value_0_input2
|
|
130
|
+
|
|
131
|
+
C1 = torch.sqrt((A1 ** 2.) + (B1 ** 2.))
|
|
132
|
+
C2 = torch.sqrt((A2 ** 2.) + (B2 ** 2.))
|
|
133
|
+
|
|
134
|
+
aC1C2 = (C1 + C2) / 2.
|
|
135
|
+
G = 0.5 * (1. - torch.sqrt((aC1C2 ** 7.) / ((aC1C2 ** 7.) + (25 ** 7.))))
|
|
136
|
+
a1P = (1. + G) * A1
|
|
137
|
+
a2P = (1. + G) * A2
|
|
138
|
+
c1P = torch.sqrt((a1P ** 2.) + (B1 ** 2.))
|
|
139
|
+
c2P = torch.sqrt((a2P ** 2.) + (B2 ** 2.))
|
|
140
|
+
|
|
141
|
+
h1P = hpf_diff(B1, a1P)
|
|
142
|
+
h2P = hpf_diff(B2, a2P)
|
|
143
|
+
h1P = h1P * mask_value_0_input1_no
|
|
144
|
+
h2P = h2P * mask_value_0_input2_no
|
|
145
|
+
|
|
146
|
+
dLP = L2 - L1
|
|
147
|
+
dCP = c2P - c1P
|
|
148
|
+
dhP = dhpf_diff(C1, C2, h1P, h2P)
|
|
149
|
+
dHP = 2. * torch.sqrt(c1P * c2P) * torch.sin(radians(dhP) / 2.)
|
|
150
|
+
mask_0_no = 1 - torch.max(mask_value_0_input1, mask_value_0_input2)
|
|
151
|
+
dHP = dHP * mask_0_no
|
|
152
|
+
|
|
153
|
+
aL = (L1 + L2) / 2.
|
|
154
|
+
aCP = (c1P + c2P) / 2.
|
|
155
|
+
aHP = ahpf_diff(C1, C2, h1P, h2P)
|
|
156
|
+
T = 1. - 0.17 * torch.cos(radians(aHP - 39)) + 0.24 * torch.cos(radians(2. * aHP)) + 0.32 * torch.cos(
|
|
157
|
+
radians(3. * aHP + 6.)) - 0.2 * torch.cos(radians(4. * aHP - 63.))
|
|
158
|
+
dRO = 30. * torch.exp(-1. * (((aHP - 275.) / 25.) ** 2.))
|
|
159
|
+
rC = torch.sqrt((aCP ** 7.) / ((aCP ** 7.) + (25. ** 7.)))
|
|
160
|
+
sL = 1. + ((0.015 * ((aL - 50.) ** 2.)) / torch.sqrt(20. + ((aL - 50.) ** 2.)))
|
|
161
|
+
|
|
162
|
+
sC = 1. + 0.045 * aCP
|
|
163
|
+
sH = 1. + 0.015 * aCP * T
|
|
164
|
+
rT = -2. * rC * torch.sin(radians(2. * dRO))
|
|
165
|
+
|
|
166
|
+
# res_square=((dLP / (sL * kL)) ** 2.) + ((dCP / (sC * kC)) ** 2.) + ((dHP / (sH * kH)) ** 2.) + rT * (dCP / (sC * kC)) * (dHP / (sH * kH))
|
|
167
|
+
|
|
168
|
+
res_square = ((dLP / (sL * kL)) ** 2.) + ((dCP / (sC * kC)) ** 2.) * mask_0_no + (
|
|
169
|
+
(dHP / (sH * kH)) ** 2.) * mask_0_no + rT * (dCP / (sC * kC)) * (dHP / (sH * kH)) * mask_0_no
|
|
170
|
+
mask_0 = (res_square <= 0).float()
|
|
171
|
+
mask_0_no = 1 - mask_0
|
|
172
|
+
res_square = res_square + 0.0001 * mask_0
|
|
173
|
+
res = torch.sqrt(res_square)
|
|
174
|
+
res = res * mask_0_no
|
|
175
|
+
|
|
176
|
+
return res
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def ciede2000_loss(input: Tensor, target: Tensor) -> Tensor:
|
|
180
|
+
Lab_input, Lab_target = map(rgb2lab_diff, (input, target))
|
|
181
|
+
return ciede2000_diff(Lab_input, Lab_target).flatten(1).norm(p=2, dim=1)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Adapted from https://github.com/ZhengyuZhao/PerC-Adversarial
|
|
2
|
+
|
|
3
|
+
from math import pi, cos
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
from torch import nn, Tensor
|
|
7
|
+
from torch.autograd import grad
|
|
8
|
+
|
|
9
|
+
from .differential_color_functions import rgb2lab_diff, ciede2000_diff
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def quantization(x):
|
|
13
|
+
"""quantize the continus image tensors into 255 levels (8 bit encoding)"""
|
|
14
|
+
x_quan = torch.round(x * 255) / 255
|
|
15
|
+
return x_quan
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def perc_al(model: nn.Module,
|
|
19
|
+
images: Tensor,
|
|
20
|
+
labels: Tensor,
|
|
21
|
+
num_classes: int,
|
|
22
|
+
targeted: bool = False,
|
|
23
|
+
max_iterations: int = 1000,
|
|
24
|
+
alpha_l_init: float = 1.,
|
|
25
|
+
alpha_c_init: float = 0.5,
|
|
26
|
+
confidence: float = 0, **kwargs) -> Tensor:
|
|
27
|
+
"""
|
|
28
|
+
PerC_AL: Alternating Loss of Classification and Color Differences to achieve imperceptibile perturbations with few
|
|
29
|
+
iterations. Adapted from https://github.com/ZhengyuZhao/PerC-Adversarial.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
model : nn.Module
|
|
34
|
+
Model to fool.
|
|
35
|
+
images : Tensor
|
|
36
|
+
Batch of image examples in the range of [0,1].
|
|
37
|
+
labels : Tensor
|
|
38
|
+
Original labels if untargeted, else labels of targets.
|
|
39
|
+
targeted : bool, optional
|
|
40
|
+
Whether to perform a targeted adversary or not.
|
|
41
|
+
max_iterations : int
|
|
42
|
+
Number of iterations for the optimization.
|
|
43
|
+
alpha_l_init: float
|
|
44
|
+
step size for updating perturbations with respect to classification loss
|
|
45
|
+
alpha_c_init: float
|
|
46
|
+
step size for updating perturbations with respect to perceptual color differences. for relatively easy
|
|
47
|
+
untargeted case, alpha_c_init is adjusted to a smaller value (e.g., 0.1 is used in the paper)
|
|
48
|
+
confidence : float, optional
|
|
49
|
+
Confidence of the adversary for Carlini's loss, in term of distance between logits.
|
|
50
|
+
Note that this approach only supports confidence setting in an untargeted case
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
Tensor
|
|
55
|
+
Batch of image samples modified to be adversarial
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
if images.min() < 0 or images.max() > 1: raise ValueError('Input values should be in the [0, 1] range.')
|
|
59
|
+
device = images.device
|
|
60
|
+
|
|
61
|
+
alpha_l_min = alpha_l_init / 100
|
|
62
|
+
alpha_c_min = alpha_c_init / 10
|
|
63
|
+
multiplier = -1 if targeted else 1
|
|
64
|
+
|
|
65
|
+
X_adv_round_best = images.clone()
|
|
66
|
+
inputs_LAB = rgb2lab_diff(images)
|
|
67
|
+
batch_size = images.shape[0]
|
|
68
|
+
delta = torch.zeros_like(images, requires_grad=True)
|
|
69
|
+
mask_isadv = torch.zeros(batch_size, dtype=torch.bool, device=device)
|
|
70
|
+
color_l2_delta_bound_best = torch.full((batch_size,), 100000, dtype=torch.float, device=device)
|
|
71
|
+
|
|
72
|
+
if (targeted == False) and confidence != 0:
|
|
73
|
+
labels_onehot = torch.zeros(labels.size(0), num_classes, device=device)
|
|
74
|
+
labels_onehot.scatter_(1, labels.unsqueeze(1), 1)
|
|
75
|
+
labels_infhot = torch.zeros_like(labels_onehot).scatter_(1, labels.unsqueeze(1), float('inf'))
|
|
76
|
+
if (targeted == True) and confidence != 0:
|
|
77
|
+
print('Only support setting confidence in untargeted case!')
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
# check if some images are already adversarial
|
|
81
|
+
if (targeted == False) and confidence != 0:
|
|
82
|
+
logits = model(images)
|
|
83
|
+
real = logits.gather(1, labels.unsqueeze(1)).squeeze(1)
|
|
84
|
+
other = (logits - labels_infhot).amax(dim=1)
|
|
85
|
+
mask_isadv = (real - other) <= -40
|
|
86
|
+
elif confidence == 0:
|
|
87
|
+
if targeted:
|
|
88
|
+
mask_isadv = model(images).argmax(1) == labels
|
|
89
|
+
else:
|
|
90
|
+
mask_isadv = model(images).argmax(1) != labels
|
|
91
|
+
color_l2_delta_bound_best[mask_isadv] = 0
|
|
92
|
+
X_adv_round_best[mask_isadv] = images[mask_isadv]
|
|
93
|
+
|
|
94
|
+
for i in range(max_iterations):
|
|
95
|
+
# cosine annealing for alpha_l_init and alpha_c_init
|
|
96
|
+
alpha_c = alpha_c_min + 0.5 * (alpha_c_init - alpha_c_min) * (1 + cos(i / max_iterations * pi))
|
|
97
|
+
alpha_l = alpha_l_min + 0.5 * (alpha_l_init - alpha_l_min) * (1 + cos(i / max_iterations * pi))
|
|
98
|
+
|
|
99
|
+
loss = multiplier * nn.CrossEntropyLoss(reduction='sum')(model(images + delta), labels)
|
|
100
|
+
grad_a = grad(loss, delta, only_inputs=True)[0]
|
|
101
|
+
delta.data[~mask_isadv] = delta.data[~mask_isadv] + alpha_l * (grad_a.permute(1, 2, 3, 0) / torch.norm(
|
|
102
|
+
grad_a.flatten(1), dim=1)).permute(3, 0, 1, 2)[~mask_isadv]
|
|
103
|
+
|
|
104
|
+
d_map = ciede2000_diff(inputs_LAB, rgb2lab_diff(images + delta)).unsqueeze(1)
|
|
105
|
+
color_dis = torch.norm(d_map.flatten(1), dim=1)
|
|
106
|
+
grad_color = grad(color_dis.sum(), delta, only_inputs=True)[0]
|
|
107
|
+
delta.data[mask_isadv] = delta.data[mask_isadv] - alpha_c * (grad_color.permute(1, 2, 3, 0) / torch.norm(
|
|
108
|
+
grad_color.flatten(1), dim=1)).permute(3, 0, 1, 2)[mask_isadv]
|
|
109
|
+
|
|
110
|
+
delta.data = (images + delta.data).clamp_(min=0, max=1) - images
|
|
111
|
+
X_adv_round = quantization(images + delta.data)
|
|
112
|
+
|
|
113
|
+
if (targeted == False) and confidence != 0:
|
|
114
|
+
logits = model(X_adv_round)
|
|
115
|
+
real = logits.gather(1, labels.unsqueeze(1)).squeeze(1)
|
|
116
|
+
other = (logits - labels_infhot).amax(dim=1)
|
|
117
|
+
mask_isadv = (real - other) <= -40
|
|
118
|
+
elif confidence == 0:
|
|
119
|
+
if targeted:
|
|
120
|
+
mask_isadv = model(X_adv_round).argmax(1) == labels
|
|
121
|
+
else:
|
|
122
|
+
mask_isadv = model(X_adv_round).argmax(1) != labels
|
|
123
|
+
mask_best = (color_dis.data < color_l2_delta_bound_best)
|
|
124
|
+
mask = mask_best * mask_isadv
|
|
125
|
+
color_l2_delta_bound_best[mask] = color_dis.data[mask]
|
|
126
|
+
X_adv_round_best[mask] = X_adv_round[mask]
|
|
127
|
+
|
|
128
|
+
return X_adv_round_best
|