Coverage for adaro_rl / attacks / random.py: 100%

44 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-14 07:50 +0000

1from typing import Tuple 

2import numpy as np 

3import torch 

4 

5from .base_attack import BaseAttack 

6 

7 

8class RandomUniformAttack(BaseAttack): 

9 """ 

10 Random Uniform Attack 

11 

12 This attack applies random perturbations sampled uniformly from the range [-1, 1], 

13 then scales them according to the specified norm and perturbation budget (`eps`). 

14 

15 Parameters 

16 ---------- 

17 obs_space : int | Tuple 

18 Shape of the observation space. 

19 perturb_space : int | Tuple 

20 Shape of the perturbation space. 

21 eps : int | float | np.ndarray 

22 Perturbation budget. 

23 norm : Optional[int | float], optional 

24 Norm to use for scaling the perturbation (e.g., 0, 2, np.inf, or None). 

25 is_proportional_mask : int | torch.Tensor, optional 

26 Mask that determines if perturbations should be scaled proportionally 

27 to the observation (1) or absolutely (0). Default is None. 

28 device : str, optional 

29 Computation device. Default is 'cpu'. 

30 """ 

31 

32 def __init__( 

33 self, 

34 obs_space: int | Tuple, 

35 perturb_space: int | Tuple, 

36 eps: int | float | np.ndarray, 

37 norm: int | float = None, 

38 is_proportional_mask: int | torch.Tensor = None, 

39 device="cpu", 

40 seed=None, 

41 ): 

42 super().__init__( 

43 obs_space, perturb_space, eps, norm, is_proportional_mask, device, seed 

44 ) 

45 

46 def generate_perturbation(self, observation_batch: torch.Tensor): 

47 """ 

48 Generate random uniform perturbations for a batch of observations. 

49 

50 Parameters 

51 ---------- 

52 observation_batch : torch.Tensor 

53 Batch of input observations. 

54 

55 Returns 

56 ------- 

57 torch.Tensor 

58 Batch of perturbations with same shape as input observations. 

59 """ 

60 

61 original_type = observation_batch.dtype 

62 int_conversion = np.issubdtype(original_type, np.integer) 

63 

64 batch_size = observation_batch.shape[0] 

65 

66 perturbation_map_batch = ( 

67 self.rng.random((batch_size, *self.perturb_shape)) * 2 - 1 

68 ) 

69 

70 perturbation_batch = self._scale_perturbation(perturbation_map_batch) 

71 

72 if int_conversion: 

73 perturbation_batch = perturbation_batch.round() 

74 

75 return perturbation_batch 

76 

77 

78class RandomSignAttack(BaseAttack): 

79 """ 

80 Random Sign Attack 

81 

82 This attack samples uniform random values in [-1, 1], keeps only the sign, 

83 and then scales the perturbation according to the specified norm and budget. 

84 

85 Parameters 

86 ---------- 

87 obs_space : int | Tuple 

88 Shape of the observation space. 

89 perturb_space : int | Tuple 

90 Shape of the perturbation space. 

91 eps : int | float | np.ndarray 

92 Perturbation budget. 

93 norm : Optional[int | float], optional 

94 Norm to use for scaling the perturbation (e.g., 0, 2, np.inf, or None). 

95 is_proportional_mask : int | torch.Tensor, optional 

96 Mask that determines if perturbations should be scaled proportionally 

97 to the observation (1) or absolutely (0). Default is None. 

98 device : str, optional 

99 Computation device. Default is 'cpu'. 

100 """ 

101 

102 def __init__( 

103 self, 

104 obs_space: int | Tuple, 

105 perturb_space: int | Tuple, 

106 eps: int | float | np.ndarray, 

107 norm: int | float = None, 

108 is_proportional_mask: int | torch.Tensor = None, 

109 device="cpu", 

110 seed=None, 

111 ): 

112 super().__init__( 

113 obs_space, perturb_space, eps, norm, is_proportional_mask, device, seed 

114 ) 

115 

116 def generate_perturbation(self, observation_batch: torch.Tensor): 

117 """ 

118 Generate random sign-based perturbations for a batch of observations. 

119 

120 Parameters 

121 ---------- 

122 observation_batch : torch.Tensor 

123 Batch of input observations. 

124 

125 Returns 

126 ------- 

127 torch.Tensor 

128 Batch of perturbations with same shape as input observations. 

129 """ 

130 original_type = observation_batch.dtype 

131 int_conversion = np.issubdtype(original_type, np.integer) 

132 

133 batch_size = observation_batch.shape[0] 

134 

135 perturbation_map_batch = ( 

136 self.rng.random((batch_size, *self.perturb_shape)) * 2 - 1 

137 ) 

138 

139 perturbation_batch = self._scale_perturbation( 

140 perturbation_map_batch, signed=True 

141 ) 

142 

143 if int_conversion: 

144 perturbation_batch = perturbation_batch.round() 

145 

146 return perturbation_batch 

147 

148 

149class RandomNormalAttack(BaseAttack): 

150 """ 

151 Random Normal Attack 

152 

153 This attack samples perturbations from a truncated normal distribution 

154 (centered at 0, clipped in [-3, 3]), then scales them according to the 

155 specified norm and perturbation budget (`eps`). 

156 

157 Parameters 

158 ---------- 

159 obs_space : int | Tuple 

160 Shape of the observation space. 

161 perturb_space : int | Tuple 

162 Shape of the perturbation space. 

163 eps : int | float | np.ndarray 

164 Perturbation budget. 

165 norm : Optional[int | float], optional 

166 Norm to use for scaling the perturbation (e.g., 0, 2, np.inf, or None). 

167 is_proportional_mask : int | torch.Tensor, optional 

168 Mask that determines if perturbations should be scaled proportionally 

169 to the observation (1) or absolutely (0). Default is None. 

170 device : str, optional 

171 Computation device. Default is 'cpu'. 

172 """ 

173 

174 def __init__( 

175 self, 

176 obs_space: int | Tuple, 

177 perturb_space: int | Tuple, 

178 eps: int | float | np.ndarray, 

179 norm: int | float = None, 

180 is_proportional_mask: int | torch.Tensor = None, 

181 device="cpu", 

182 seed=None, 

183 ): 

184 super().__init__( 

185 obs_space, perturb_space, eps, norm, is_proportional_mask, device, seed 

186 ) 

187 

188 def generate_perturbation(self, observation_batch: torch.Tensor): 

189 """ 

190 Generate perturbations using a truncated normal distribution. 

191 

192 Parameters 

193 ---------- 

194 observation_batch : torch.Tensor 

195 Batch of input observations. 

196 

197 Returns 

198 ------- 

199 torch.Tensor 

200 Batch of perturbations with same shape as input observations. 

201 """ 

202 original_type = observation_batch.dtype 

203 int_conversion = np.issubdtype(original_type, np.integer) 

204 

205 batch_size = observation_batch.shape[0] 

206 

207 perturbation_map_batch = _truncated_normal( 

208 self.rng, 0, 1, -3, 3, (batch_size,) + self.perturb_shape 

209 ) 

210 

211 perturbation_batch = self._scale_perturbation(perturbation_map_batch) 

212 

213 if int_conversion: 

214 perturbation_batch = perturbation_batch.round() 

215 

216 return perturbation_batch 

217 

218 

219def _truncated_normal(rng, mean, stddev, lower, upper, shape): 

220 """ 

221 Generate samples from a truncated normal distribution. 

222 

223 Parameters 

224 ---------- 

225 mean : float 

226 Mean of the normal distribution before truncation. 

227 stddev : float 

228 Standard deviation of the normal distribution. 

229 lower : float 

230 Minimum value for truncation. 

231 upper : float 

232 Maximum value for truncation. 

233 shape : tuple 

234 Shape of the output array. 

235 

236 Returns 

237 ------- 

238 np.ndarray 

239 Array of truncated normal samples. 

240 """ 

241 samples = rng.normal(loc=mean, scale=stddev, size=shape) 

242 samples = np.clip(samples, lower, upper) 

243 return samples