diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index aff1eb14..b78c16d2 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,7 +22,7 @@ jobs: - name: Set up Python 3.8 uses: actions/setup-python@v1 with: - python-version: 3.8 + python-version: 3.9 - name: Install package run: | pip install -e . diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 4f9e8b1f..1825daf2 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -12,7 +12,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v1 with: - python-version: '3.8' + python-version: '3.9' - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bd1f0443..f4b702e7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: strategy: max-parallel: 20 matrix: - python-version: ['3.8', '3.9'] + python-version: ['3.9', '3.10'] backend: ["none", "pytorch", "tensorflow", "jax", "numpy"] steps: diff --git a/foolbox/attacks/additive_noise.py b/foolbox/attacks/additive_noise.py index 29d55f0b..6a7fe988 100644 --- a/foolbox/attacks/additive_noise.py +++ b/foolbox/attacks/additive_noise.py @@ -6,7 +6,7 @@ from ..devutils import flatten from ..devutils import atleast_kd -from ..distances import l2, linf +from ..distances import l2, linf, Distance from .base import FixedEpsilonAttack from .base import Criterion @@ -56,7 +56,9 @@ def get_epsilons( class L2Mixin: - distance = l2 + @property + def distance(self) -> Distance: + return l2 def get_epsilons( self, x: ep.Tensor, p: ep.Tensor, epsilon: float, min_: float, max_: float @@ -66,7 +68,9 @@ def get_epsilons( class L2ClippingAwareMixin: - distance = l2 + @property + def distance(self) -> Distance: + return l2 def get_epsilons( self, x: ep.Tensor, p: ep.Tensor, epsilon: float, min_: float, max_: float @@ -77,7 +81,9 @@ def get_epsilons( class LinfMixin: - distance = linf + @property + def distance(self) -> Distance: + return linf def get_epsilons( self, x: ep.Tensor, p: ep.Tensor, epsilon: float, min_: float, max_: float diff --git a/foolbox/attacks/base.py b/foolbox/attacks/base.py index 6eaa1690..b3b80aea 100644 --- a/foolbox/attacks/base.py +++ b/foolbox/attacks/base.py @@ -241,7 +241,7 @@ def __call__( ... @final # noqa: F811 - def __call__( # type: ignore + def __call__( self, model: Model, inputs: T, @@ -381,7 +381,7 @@ def __call__( ... @final # noqa: F811 - def __call__( # type: ignore + def __call__( self, model: Model, inputs: T, diff --git a/foolbox/attacks/boundary_attack.py b/foolbox/attacks/boundary_attack.py index 4416028c..fd53babf 100644 --- a/foolbox/attacks/boundary_attack.py +++ b/foolbox/attacks/boundary_attack.py @@ -311,7 +311,7 @@ def clear(self, dims: ep.Tensor) -> None: self.tensor = dims # pragma: no cover dims = dims.numpy() assert dims.shape == (self.N,) - assert dims.dtype == np.bool + assert dims.dtype == np.bool_ self.data[:, dims] = np.nan def mean(self) -> ep.Tensor: diff --git a/foolbox/attacks/brendel_bethge.py b/foolbox/attacks/brendel_bethge.py index b3570bbc..d187056b 100644 --- a/foolbox/attacks/brendel_bethge.py +++ b/foolbox/attacks/brendel_bethge.py @@ -487,7 +487,7 @@ def logits_diff_and_grads(x) -> Tuple[Any, Any]: x = starting_points lrs = self.lr * np.ones(N) lr_reduction_interval = max(1, int(self.steps / self.lr_num_decay)) - converged = np.zeros(N, dtype=np.bool) + converged = np.zeros(N, dtype=np.bool_) rate_normalization = np.prod(x.shape) * (max_ - min_) original_shape = x.shape _best_advs = best_advs.numpy() @@ -569,7 +569,9 @@ def logits_diff_and_grads(x) -> Tuple[Any, Any]: # add step to current perturbation x = (x + ep.astensor(deltas)).reshape(original_shape) - tb.probability("converged", converged, step) + tb.probability( + "converged", ep.from_numpy(x, converged.astype(np.bool_)), step + ) tb.histogram("norms", source_norms, step) tb.histogram("candidates/distances", distances, step) diff --git a/foolbox/attacks/carlini_wagner.py b/foolbox/attacks/carlini_wagner.py index 3e7df10b..41a32262 100644 --- a/foolbox/attacks/carlini_wagner.py +++ b/foolbox/attacks/carlini_wagner.py @@ -174,7 +174,7 @@ def loss_fun( # after each tenth of the overall steps, check progress if not (loss <= 0.9999 * loss_at_previous_check): break # stop Adam if there has been no progress - loss_at_previous_check = loss + loss_at_previous_check = loss.item() found_advs_iter = is_adversarial(perturbed, logits) found_advs = np.logical_or(found_advs, found_advs_iter.numpy()) diff --git a/foolbox/attacks/dataset_attack.py b/foolbox/attacks/dataset_attack.py index 3ed719ad..37bf4434 100644 --- a/foolbox/attacks/dataset_attack.py +++ b/foolbox/attacks/dataset_attack.py @@ -101,10 +101,10 @@ def run( if found.all(): break - indices = np.array([pool[i] for pool in index_pools]) + indices_np = np.array([pool[i] for pool in index_pools]) - xp = self.inputs[indices] - yp = self.outputs[indices] + xp = self.inputs[indices_np] + yp = self.outputs[indices_np] is_adv = criterion(xp, yp) new_found = ep.logical_and(is_adv, found.logical_not()) diff --git a/foolbox/attacks/gen_attack.py b/foolbox/attacks/gen_attack.py index f359e4a4..34c30024 100644 --- a/foolbox/attacks/gen_attack.py +++ b/foolbox/attacks/gen_attack.py @@ -77,8 +77,8 @@ def apply_noise( def choice( self, a: int, size: Union[int, ep.TensorType], replace: bool, p: ep.TensorType ) -> Any: - p = p.numpy() - x = np.random.choice(a, size, replace, p) + p_np: np.ndarray = p.numpy() + x = np.random.choice(a, size, replace, p_np) # type: ignore return x def run( diff --git a/foolbox/attacks/gen_attack_utils.py b/foolbox/attacks/gen_attack_utils.py index 9eadef0a..76735932 100644 --- a/foolbox/attacks/gen_attack_utils.py +++ b/foolbox/attacks/gen_attack_utils.py @@ -39,13 +39,13 @@ def rclip(rows: np.ndarray) -> np.ndarray: wc = np.expand_dims((cols - col_lo) * (row_hi - rows), -1) wd = np.expand_dims((cols - col_lo) * (rows - row_lo), -1) - return wa * Ia + wb * Ib + wc * Ic + wd * Id # type: ignore + return wa * Ia + wb * Ib + wc * Ic + wd * Id nrows, ncols = img.shape[-3:-1] deltas = (0.5 / resize_rates[0], 0.5 / resize_rates[1]) - rows = np.linspace(deltas[0], nrows - deltas[0], np.int32(resize_rates[0] * nrows)) - cols = np.linspace(deltas[1], ncols - deltas[1], np.int32(resize_rates[1] * ncols)) + rows = np.linspace(deltas[0], nrows - deltas[0], int(resize_rates[0] * nrows)) + cols = np.linspace(deltas[1], ncols - deltas[1], int(resize_rates[1] * ncols)) rows_grid, cols_grid = np.meshgrid(rows - 0.5, cols - 0.5, indexing="ij") img_resize_vec = interpolate_bilinear(img, rows_grid.flatten(), cols_grid.flatten()) @@ -63,7 +63,7 @@ def rescale_numpy(x: ep.NumPyTensor, target_shape: List[int]) -> ep.NumPyTensor: resize_rates = (target_shape[1] / x.shape[1], target_shape[2] / x.shape[2]) - def interpolate_bilinear( # type: ignore + def interpolate_bilinear( im: np.ndarray, rows: np.ndarray, cols: np.ndarray ) -> np.ndarray: # based on http://stackoverflow.com/a/12729229 @@ -72,10 +72,10 @@ def interpolate_bilinear( # type: ignore row_lo = np.floor(rows).astype(int) row_hi = row_lo + 1 - def cclip(cols: np.ndarray) -> np.ndarray: # type: ignore + def cclip(cols: np.ndarray) -> np.ndarray: return np.clip(cols, 0, ncols - 1) - def rclip(rows: np.ndarray) -> np.ndarray: # type: ignore + def rclip(rows: np.ndarray) -> np.ndarray: return np.clip(rows, 0, nrows - 1) nrows, ncols = im.shape[-3:-1] @@ -90,7 +90,7 @@ def rclip(rows: np.ndarray) -> np.ndarray: # type: ignore wc = np.expand_dims((cols - col_lo) * (row_hi - rows), -1) wd = np.expand_dims((cols - col_lo) * (rows - row_lo), -1) - return wa * Ia + wb * Ib + wc * Ic + wd * Id + return wa * Ia + wb * Ib + wc * Ic + wd * Id # type: ignore nrows, ncols = img.shape[-3:-1] deltas = (0.5 / resize_rates[0], 0.5 / resize_rates[1]) diff --git a/foolbox/attacks/gradient_descent_base.py b/foolbox/attacks/gradient_descent_base.py index 2af79418..8ef156d6 100644 --- a/foolbox/attacks/gradient_descent_base.py +++ b/foolbox/attacks/gradient_descent_base.py @@ -132,7 +132,7 @@ def run( classes = criterion_.labels elif hasattr(criterion_, "target_classes"): gradient_step_sign = -1.0 - classes = criterion_.target_classes # type: ignore + classes = criterion_.target_classes else: raise ValueError("unsupported criterion") diff --git a/foolbox/attacks/hop_skip_jump.py b/foolbox/attacks/hop_skip_jump.py index f79546d9..5cebe675 100644 --- a/foolbox/attacks/hop_skip_jump.py +++ b/foolbox/attacks/hop_skip_jump.py @@ -328,7 +328,7 @@ def _binary_search( perturbed: ep.Tensor, ) -> ep.Tensor: # Choose upper thresholds in binary search based on constraint. - d = np.prod(perturbed.shape[1:]) + d = int(np.prod(perturbed.shape[1:])) if self.constraint == "linf": highs = linf(originals, perturbed) @@ -337,7 +337,7 @@ def _binary_search( thresholds = highs * self.gamma / (d * d) else: highs = ep.ones(perturbed, len(perturbed)) - thresholds = self.gamma / (d * math.sqrt(d)) + thresholds = highs * self.gamma / (d * math.sqrt(d)) lows = ep.zeros_like(highs) @@ -371,7 +371,7 @@ def select_delta( if step == 0: result = 0.1 * ep.ones_like(distances) else: - d = np.prod(originals.shape[1:]) + d = int(np.prod(originals.shape[1:])) if self.constraint == "linf": theta = self.gamma / (d * d) diff --git a/foolbox/attacks/pointwise.py b/foolbox/attacks/pointwise.py index a57d2628..1e7e23cb 100644 --- a/foolbox/attacks/pointwise.py +++ b/foolbox/attacks/pointwise.py @@ -105,9 +105,13 @@ def run( i = 0 while i < max([len(it) for it in untouched_indices]): # mask all samples that still have pixels to manipulate left - relevant_mask = [len(it) > i for it in untouched_indices] - relevant_mask = np.array(relevant_mask, dtype=bool) - relevant_mask_index = np.flatnonzero(relevant_mask) + relevant_mask_lst = [len(it) > i for it in untouched_indices] + relevant_mask: np.ndarray[Any, np.dtype[np.bool_]] = np.array( + relevant_mask_lst, dtype=bool + ) + relevant_mask_index: np.ndarray[ + Any, np.dtype[np.int_] + ] = np.flatnonzero(relevant_mask) # for each image get the index of the next pixel we try out relevant_indices = [it[i] for it in untouched_indices if len(it) > i] @@ -160,8 +164,8 @@ def run( i = 0 while i < max([len(it) for it in untouched_indices]): # mask all samples that still have pixels to manipulate left - relevant_mask = [len(it) > i for it in untouched_indices] - relevant_mask = np.array(relevant_mask, dtype=bool) + relevant_mask_lst = [len(it) > i for it in untouched_indices] + relevant_mask = np.array(relevant_mask_lst, dtype=bool) relevant_mask_index = np.flatnonzero(relevant_mask) # for each image get the index of the next pixel we try out @@ -227,8 +231,8 @@ def run( def _binary_search( self, x_adv_flat: ep.Tensor, - mask: Union[ep.Tensor, List[bool]], - mask_indices: ep.Tensor, + mask: Union[ep.Tensor, List[bool], np.ndarray[Any, np.dtype[np.bool_]]], + mask_indices: Union[ep.Tensor, np.ndarray[Any, np.dtype[np.int_]]], indices: Union[ep.Tensor, List[int]], adv_values: ep.Tensor, non_adv_values: ep.Tensor, diff --git a/foolbox/attacks/spatial_attack_transformations.py b/foolbox/attacks/spatial_attack_transformations.py index 52e87c99..96a59ee9 100644 --- a/foolbox/attacks/spatial_attack_transformations.py +++ b/foolbox/attacks/spatial_attack_transformations.py @@ -42,10 +42,10 @@ def transform_pt( # to pt x = x_e.raw - theta = torch.tensor(theta, device=x.device) + theta_t = torch.tensor(theta, device=x.device) assert len(x.shape) == 4 - assert theta.shape[1:] == (2, 3) + assert theta_t.shape[1:] == (2, 3) ( bs, @@ -64,8 +64,8 @@ def create_meshgrid(x: torch.Tensor) -> torch.Tensor: return grid meshgrid = create_meshgrid(x) - theta = theta[:, None, None, :, :].repeat(1, n_x, n_y, 1, 1) - new_coords = torch.matmul(theta, meshgrid) + theta_t = theta_t[:, None, None, :, :].repeat(1, n_x, n_y, 1, 1) + new_coords = torch.matmul(theta_t, meshgrid) new_coords = new_coords.squeeze_(-1) # align_corners=True to match tf implementation diff --git a/foolbox/plot.py b/foolbox/plot.py index 93e30895..ae7d4743 100644 --- a/foolbox/plot.py +++ b/foolbox/plot.py @@ -37,19 +37,19 @@ def images( "expected data_format to be 'channels_first' or 'channels_last'" ) assert channels_first != channels_last - x = x.numpy() + x_np = x.numpy() if channels_first: - x = np.transpose(x, axes=(0, 2, 3, 1)) + x_np = np.transpose(x_np, axes=(0, 2, 3, 1)) min_, max_ = bounds - x = (x - min_) / (max_ - min_) + x_np = (x_np - min_) / (max_ - min_) if nrows is None and ncols is None: nrows = 1 if ncols is None: assert nrows is not None - ncols = (len(x) + nrows - 1) // nrows + ncols = (len(x_np) + nrows - 1) // nrows elif nrows is None: - nrows = (len(x) + ncols - 1) // ncols + nrows = (len(x_np) + ncols - 1) // ncols if figsize is None: figsize = (ncols * scale, nrows * scale) fig, axes = plt.subplots( @@ -69,7 +69,7 @@ def images( ax.axis("off") i = row * ncols + col if i < len(x): - if x.shape[-1] == 1: - ax.imshow(x[i][:, :, 0]) + if x_np.shape[-1] == 1: + ax.imshow(x_np[i][:, :, 0]) else: - ax.imshow(x[i]) + ax.imshow(x_np[i]) diff --git a/foolbox/utils.py b/foolbox/utils.py index dd91deef..ca50c537 100644 --- a/foolbox/utils.py +++ b/foolbox/utils.py @@ -28,10 +28,10 @@ def samples( ) -> Any: if hasattr(fmodel, "data_format"): if data_format is None: - data_format = fmodel.data_format # type: ignore - elif data_format != fmodel.data_format: # type: ignore + data_format = fmodel.data_format + elif data_format != fmodel.data_format: raise ValueError( - f"data_format ({data_format}) does not match model.data_format ({fmodel.data_format})" # type: ignore + f"data_format ({data_format}) does not match model.data_format ({fmodel.data_format})" ) elif data_format is None: raise ValueError( @@ -50,9 +50,9 @@ def samples( bounds=bounds, ) - if hasattr(fmodel, "dummy") and fmodel.dummy is not None: # type: ignore - images = ep.from_numpy(fmodel.dummy, images).raw # type: ignore - labels = ep.from_numpy(fmodel.dummy, labels).raw # type: ignore + if hasattr(fmodel, "dummy") and fmodel.dummy is not None: + images = ep.from_numpy(fmodel.dummy, images).raw + labels = ep.from_numpy(fmodel.dummy, labels).raw else: warnings.warn(f"unknown model type {type(fmodel)}, returning NumPy arrays") diff --git a/requirements.txt b/requirements.txt index 76a3e286..70877a42 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ -flake8==4.0.1 +flake8==6.1.0 black==22.3.0 -pytest==7.1.1 +pytest==7.4.3 pytest-cov==3.0.0 coverage==6.3.2 -codecov==2.1.12 +codecov==2.1.13 coveralls==3.3.1 -mypy==0.942 +mypy==1.6.1 pre-commit==2.17.0 diff --git a/setup.cfg b/setup.cfg index 22351a09..c097bb7b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ max-complexity = 18 select = B,C,E,F,W,T4,B9 [mypy] -python_version = 3.8 +python_version = 3.9 warn_unused_ignores = True warn_unused_configs = True warn_return_any = True diff --git a/tests/requirements.txt b/tests/requirements.txt index 6966a831..1231d17b 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,9 +1,9 @@ -torch==1.10.1 -torchvision==0.11.2 -jax[cpu]==0.2.17 -tensorflow==2.6.4 -numba==0.55.1 -matplotlib==3.5.1 -pillow==9.0.1 -tensorboardX==2.0 -responses==0.10.9 +torch==2.1.0 +torchvision==0.16.0 +jax[cpu]==0.4.13 +tensorflow==2.13.1 +numba==0.58.1 +matplotlib==3.7.3 +pillow==10.1.0 +tensorboardX==2.6.2.2 +responses==0.23.3 \ No newline at end of file diff --git a/tests/test_utils.py b/tests/test_utils.py index 5c568e85..bf29bcde 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -23,7 +23,7 @@ def test_accuracy(fmodel_and_data: ModelAndData) -> None: def test_samples(fmodel_and_data: ModelAndData, batchsize: int, dataset: str) -> None: fmodel, _, _ = fmodel_and_data if hasattr(fmodel, "data_format"): - data_format = fmodel.data_format # type: ignore + data_format = fmodel.data_format x, y = fbn.samples(fmodel, dataset=dataset, batchsize=batchsize) assert len(x) == len(y) == batchsize assert not ep.istensor(x)