Skip to content

Commit

Permalink
merge with main
Browse files Browse the repository at this point in the history
  • Loading branch information
maurapintor committed Sep 21, 2023
2 parents 75c03ea + 5f0ee0a commit a4e2fa2
Show file tree
Hide file tree
Showing 51 changed files with 491 additions and 215 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/black.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: Lint

on: [push, pull_request]

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: psf/black@stable
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -244,4 +244,4 @@ $RECYCLE.BIN/
*.msp

# Windows shortcuts
*.lnk
*.lnk
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,35 @@
# SecML 2
New version of SecML

### Implement new functionalities
1. Create a card inside the project, by specifying what will be implemented (not how), and convert it to an issue. *Please be clear and concise*.
2. Create a new branch from main that will be used to implement the new functionality. Please provide a meaningful name while creating the new branch.
Do it from the GitHub web interface to avoid creating non-updated branches.
**If you create the branch locally, remember to pull the latest commit before branching!**
3. Checkout the new branch on your local machine, and implement unit tests and the intended functionality. Check out the “code style” section before pushing any commits.
4. Once the implementation of both unit testing and new functionality is completed, open a pull request to merge the newly created branch and main.
Please, write inside the description of the pull request a detailed changelog of the implemented functionalities and refactoring. Move the card you created at step 1 inside the “Review” column.
5. Now that the pull request has been open, wait until the maintainers review and approve your changes. DO NOT click on “Merge Pull Request” until somebody reviews your changes.
**Please remember that modified file outside the scope of the pull request will not be approved.**
6. Once this is done, you can move the card to “Done”.
7. Congrats! You have included a new functionality in SecML2!

### Code style
We leverage “Black” (https://github.com/psf/black) as default format for SecML2.
Before pushing to any branch of SecML2, execute:

```
black .
```

from the source of the repository.
If you work with an IDE, follow this guide to configure “Black”: https://black.readthedocs.io/en/stable/integrations/editors.html









File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@

from torch.utils.data import DataLoader

from src.adv.backends import Backends
from src.adv.evasion.perturbation_models import PerturbationModels
from src.models.base_model import BaseModel
from src.models.pytorch.base_pytorch_nn import BasePytorchClassifier
from src.models.sklearn.svm import SVM
from secml2.adv.backends import Backends
from secml2.adv.evasion.perturbation_models import PerturbationModels
from secml2.models.base_model import BaseModel


class BaseEvasionAttackCreator:
Expand Down Expand Up @@ -49,8 +47,3 @@ def __call__(self, model: BaseModel, data_loader: DataLoader) -> DataLoader:
:rtype: DataLoader
"""
...

def get_model(self, model):
if isinstance(model, SVM):
return BasePytorchClassifier(model._pytorch_model)
return model
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,42 @@

import torch.nn
from torch.nn import CrossEntropyLoss
from torch.optim import Adam, SGD
from torch.optim import Adam, SGD, Optimizer
from torch.utils.data import DataLoader, TensorDataset

from src.adv.evasion.base_evasion_attack import BaseEvasionAttack
from src.manipulations.manipulation import Manipulation
from src.models.base_model import BaseModel
from src.optimization.constraints import Constraint
from src.optimization.gradient_processing import GradientProcessing
from src.optimization.initializer import Initializer
from secml2.adv.evasion.base_evasion_attack import BaseEvasionAttack
from secml2.manipulations.manipulation import Manipulation
from secml2.models.base_model import BaseModel
from secml2.optimization.constraints import Constraint
from secml2.optimization.gradient_processing import GradientProcessing
from secml2.optimization.initializer import Initializer

CE_LOSS = 'ce_loss'
LOGITS_LOSS = 'logits_loss'
CE_LOSS = "ce_loss"
LOGITS_LOSS = "logits_loss"

LOSS_FUNCTIONS = {
CE_LOSS: CrossEntropyLoss,
}

ADAM = 'adam'
StochasticGD = 'sgd'
ADAM = "adam"
StochasticGD = "sgd"

OPTIMIZERS = {
ADAM: Adam,
StochasticGD: SGD
}
OPTIMIZERS = {ADAM: Adam, StochasticGD: SGD}


class CompositeEvasionAttack(BaseEvasionAttack):
def __init__(
self,
y_target: Union[int, None],
num_steps: int,
step_size: float,
loss_function: Union[str, torch.nn.Module],
optimizer_cls: Union[str, Type[torch.nn.Module]],
manipulation_function: Manipulation,
domain_constraints: List[Constraint],
perturbation_constraints: List[Type[Constraint]],
initializer: Initializer,
gradient_processing: GradientProcessing,
self,
y_target: Union[int, None],
num_steps: int,
step_size: float,
loss_function: Union[str, torch.nn.Module],
optimizer_cls: Union[str, Type[Optimizer]],
manipulation_function: Manipulation,
domain_constraints: List[Constraint],
perturbation_constraints: List[Type[Constraint]],
initializer: Initializer,
gradient_processing: GradientProcessing,
):
self.y_target = y_target
self.num_steps = num_steps
Expand All @@ -51,7 +48,8 @@ def __init__(
self.loss_function = LOSS_FUNCTIONS[loss_function]()
else:
raise ValueError(
f"{loss_function} not in list of init from string. Use one among {LOSS_FUNCTIONS.values()}")
f"{loss_function} not in list of init from string. Use one among {LOSS_FUNCTIONS.values()}"
)
else:
self.loss_function = loss_function

Expand All @@ -60,7 +58,8 @@ def __init__(
self.optimizer_cls = OPTIMIZERS[optimizer_cls]
else:
raise ValueError(
f"{optimizer_cls} not in list of init from string. Use one among {OPTIMIZERS.values()}")
f"{optimizer_cls} not in list of init from string. Use one among {OPTIMIZERS.values()}"
)
else:
self.optimizer_cls = optimizer_cls

Expand All @@ -72,44 +71,42 @@ def __init__(

super().__init__()

def init_perturbation_constraints(self, center: torch.Tensor) -> List[Constraint]:
def init_perturbation_constraints(self) -> List[Constraint]:
raise NotImplementedError("Must be implemented accordingly")

def __call__(self, model: BaseModel, data_loader: DataLoader) -> DataLoader:
adversarials = []
original_labels = []
multiplier = 1 if self.y_target is not None else -1
perturbation_constraints = self.init_perturbation_constraints()
for samples, labels in data_loader:
target = (
torch.zeros_like(labels) + self.y_target
if self.y_target is not None
else labels
)

).type(labels.dtype)
delta = self.initializer(samples.data)
delta.requires_grad = True
optimizer = self.optimizer_cls([delta], lr=self.step_size)
perturbation_constraints = self.init_perturbation_constraints(samples)
x_adv = self.manipulation_function(samples, delta)
for i in range(self.num_steps):
scores = model.decision_function(x_adv)
target = target.to(scores.device)
loss = self.loss_function(scores, target) * multiplier
loss = self.loss_function(scores, target)
loss = loss * multiplier
optimizer.zero_grad()
loss.backward()
gradient = delta.grad
gradient = self.gradient_processing(gradient)
delta.grad.data = gradient.data
delta.grad.data = self.gradient_processing(delta.grad.data)
optimizer.step()
for constraint in perturbation_constraints:
delta.data = constraint(delta.data)
x_adv = self.manipulation_function(samples, delta)
x_adv.data = self.manipulation_function(samples.data, delta.data)
for constraint in self.domain_constraints:
x_adv.data = constraint(x_adv.data)
adversarials.append(x_adv)
original_labels.append(labels)
# print('NORM : ', delta.flatten(start_dim=1).norm(p=float('inf')))
#TODO check best according to custom metric
delta.data = self.manipulation_function.invert(samples.data, x_adv.data)

adversarials.append(x_adv)
original_labels.append(labels)

adversarials = torch.vstack(adversarials)
original_labels = torch.hstack(original_labels)
Expand Down
17 changes: 13 additions & 4 deletions src/adv/evasion/foolbox.py → secml2/adv/evasion/foolbox.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from typing import Optional
from src.adv.evasion.base_evasion_attack import BaseEvasionAttack
from secml2.adv.evasion.base_evasion_attack import BaseEvasionAttack
from foolbox.attacks.base import Attack
from torch.utils.data import DataLoader
from src.models.base_model import BaseModel
from secml2.models.base_model import BaseModel
from secml2.models.pytorch.base_pytorch_nn import BasePytorchClassifier
from secml2.models.base_model import BaseModel
from foolbox.models.pytorch import PyTorchModel
from foolbox.criteria import Misclassification, TargetedMisclassification
import torch
Expand All @@ -26,7 +28,9 @@ def __init__(
super().__init__()

def __call__(self, model: BaseModel, data_loader: DataLoader) -> DataLoader:
model = self.get_model(model)
# TODO get here the correct model if not pytorch
if not isinstance(model, BasePytorchClassifier):
raise NotImplementedError("Model type not supported.")
device = model.get_device()
foolbox_model = PyTorchModel(model.model, (self.lb, self.ub), device=device)
adversarials = []
Expand All @@ -36,7 +40,12 @@ def __call__(self, model: BaseModel, data_loader: DataLoader) -> DataLoader:
if self.y_target is None:
criterion = Misclassification(labels)
else:
criterion = TargetedMisclassification(self.y_target)
target = (
torch.zeros_like(labels) + self.y_target
if self.y_target is not None
else labels
).type(labels.dtype)
criterion = TargetedMisclassification(target)
_, advx, _ = self.foolbox_attack(
model=foolbox_model,
inputs=samples,
Expand Down
File renamed without changes.
Loading

0 comments on commit a4e2fa2

Please sign in to comment.