kcai-data-sampling-fgsm 0.1.0__tar.gz
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.
- kcai_data_sampling_fgsm-0.1.0/PKG-INFO +111 -0
- kcai_data_sampling_fgsm-0.1.0/README.md +93 -0
- kcai_data_sampling_fgsm-0.1.0/pyproject.toml +41 -0
- kcai_data_sampling_fgsm-0.1.0/requirements.txt +1 -0
- kcai_data_sampling_fgsm-0.1.0/setup.cfg +4 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm/__init__.py +12 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm/_version_.py +24 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm/api/__init__.py +7 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm/api/transformations/__init__.py +10 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm/api/transformations/fgsm.py +68 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm/configs.py +57 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm/py.typed +0 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm/transformations/__init__.py +11 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm/transformations/fgsm.py +40 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm.egg-info/PKG-INFO +111 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm.egg-info/SOURCES.txt +19 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm.egg-info/dependency_links.txt +1 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm.egg-info/entry_points.txt +2 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm.egg-info/requires.txt +1 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm.egg-info/scm_file_list.json +15 -0
- kcai_data_sampling_fgsm-0.1.0/src/kcai_data_sampling_fgsm.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kcai-data-sampling-fgsm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Adversarial perturbation (FGSM) for kcai data-sampling
|
|
5
|
+
Author-email: Safenai <support@safenai.io>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Keywords: ml,data,augmentation,robustness,adversarial,fgsm
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: kcai-data-sampling-core
|
|
18
|
+
|
|
19
|
+
# kcai-data-sampling-fgsm
|
|
20
|
+
|
|
21
|
+
The **adversarial** slot of kcai data-sampling: transformations where a model's
|
|
22
|
+
*response* enters the computation and the result is graded against it. Here the
|
|
23
|
+
model is a **target** — it is not asked to produce anything, only to react. That
|
|
24
|
+
is what makes the family adversarial, and the declaring `model_role = "target"`
|
|
25
|
+
is what tells the framework to treat it as such.
|
|
26
|
+
|
|
27
|
+
This package implements the Fast Gradient Sign Method as a single budgeted step
|
|
28
|
+
along the sign of the target model's gradient, and exposes it through the
|
|
29
|
+
`kcai_data_sampling.transformations` entry-point group.
|
|
30
|
+
|
|
31
|
+
**The target model is yours.** It arrives at run time through the `models:`
|
|
32
|
+
section of your configuration and is never shipped by this package.
|
|
33
|
+
|
|
34
|
+
## What it ships today
|
|
35
|
+
|
|
36
|
+
- `fgsm` — one method: one L-infinity step of budget `epsilon` along the sign of
|
|
37
|
+
the target's gradient.
|
|
38
|
+
|
|
39
|
+
One method, as an example of the slot rather than a catalogue.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install "kcai-data-sampling[fgsm]"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**No deep-learning framework is required.** This package depends only on the
|
|
48
|
+
core contracts — the gradient arrives from *your* model, so the work is yours
|
|
49
|
+
too, and nothing here pulls torch. A configuration driving `fgsm` may well need
|
|
50
|
+
torch in the environment that hosts the target model; that is a property of the
|
|
51
|
+
model you bring, not of this package.
|
|
52
|
+
|
|
53
|
+
## How the step works
|
|
54
|
+
|
|
55
|
+
The batch is normalized to `[0, 1]` pixel units, the target's gradient is taken
|
|
56
|
+
in the same units, and `epsilon * sign(grad)` is added — so only the *sign* of
|
|
57
|
+
the gradient participates, not its magnitude. The result is clipped back into
|
|
58
|
+
range and restored to the batch's dtype.
|
|
59
|
+
|
|
60
|
+
Three declarations follow from that, and the framework enforces each:
|
|
61
|
+
|
|
62
|
+
- **`clips = True`** — a signed step can push a pixel past the selection's value
|
|
63
|
+
range, so the output is clipped back rather than refused.
|
|
64
|
+
- **`reversible = False`** — the step is lossy. The original pixels are not
|
|
65
|
+
recoverable from the output, so the row records that it is not invertible.
|
|
66
|
+
- **Deterministic** — no randomness is drawn, so the recorded seed is `None`.
|
|
67
|
+
The same input and the same `epsilon` always give the same output.
|
|
68
|
+
|
|
69
|
+
`epsilon` is the adversarial budget, strictly positive, and sweepable, so a run
|
|
70
|
+
can walk a whole budget curve.
|
|
71
|
+
|
|
72
|
+
## Labels do not move
|
|
73
|
+
|
|
74
|
+
An attack moves the model's *prediction*, not the truth. The ground-truth label
|
|
75
|
+
belongs to the source row, travels with it, and is never touched here — this
|
|
76
|
+
package has no notion of a label at all, only of pixels.
|
|
77
|
+
|
|
78
|
+
That is what makes an adversarial sample meaningful: if the label had moved
|
|
79
|
+
along with the pixels, the pair would prove nothing. A label that changed under
|
|
80
|
+
perturbation, or a target model whose response was ignored, would both mean the
|
|
81
|
+
measurement was not an attack at all.
|
|
82
|
+
|
|
83
|
+
## Example
|
|
84
|
+
|
|
85
|
+
The `models` and `operations` sections of a job configuration, with a target
|
|
86
|
+
model supplied as your own source:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
models:
|
|
90
|
+
yolo:
|
|
91
|
+
type: python
|
|
92
|
+
path: adapters/yolo_target.py
|
|
93
|
+
export: YoloTarget
|
|
94
|
+
weights: yolov8n.pt
|
|
95
|
+
params: { conf: 0.25 }
|
|
96
|
+
operations:
|
|
97
|
+
transformations:
|
|
98
|
+
- name: fgsm
|
|
99
|
+
type: fgsm
|
|
100
|
+
target_model: yolo
|
|
101
|
+
epsilon:
|
|
102
|
+
range: [0.01, 0.05]
|
|
103
|
+
step: 0.01
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The transformation names its model with `target_model: yolo`. Only `grad` is
|
|
107
|
+
used from it, and its output is checked for shape and finiteness before the step,
|
|
108
|
+
so a misbehaving adapter fails loudly rather than corrupting the batch.
|
|
109
|
+
|
|
110
|
+
A target model is recognised by a non-empty `name` plus a `grad` method, and
|
|
111
|
+
conformance is checked against the `target` role the transformation declares.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# kcai-data-sampling-fgsm
|
|
2
|
+
|
|
3
|
+
The **adversarial** slot of kcai data-sampling: transformations where a model's
|
|
4
|
+
*response* enters the computation and the result is graded against it. Here the
|
|
5
|
+
model is a **target** — it is not asked to produce anything, only to react. That
|
|
6
|
+
is what makes the family adversarial, and the declaring `model_role = "target"`
|
|
7
|
+
is what tells the framework to treat it as such.
|
|
8
|
+
|
|
9
|
+
This package implements the Fast Gradient Sign Method as a single budgeted step
|
|
10
|
+
along the sign of the target model's gradient, and exposes it through the
|
|
11
|
+
`kcai_data_sampling.transformations` entry-point group.
|
|
12
|
+
|
|
13
|
+
**The target model is yours.** It arrives at run time through the `models:`
|
|
14
|
+
section of your configuration and is never shipped by this package.
|
|
15
|
+
|
|
16
|
+
## What it ships today
|
|
17
|
+
|
|
18
|
+
- `fgsm` — one method: one L-infinity step of budget `epsilon` along the sign of
|
|
19
|
+
the target's gradient.
|
|
20
|
+
|
|
21
|
+
One method, as an example of the slot rather than a catalogue.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install "kcai-data-sampling[fgsm]"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**No deep-learning framework is required.** This package depends only on the
|
|
30
|
+
core contracts — the gradient arrives from *your* model, so the work is yours
|
|
31
|
+
too, and nothing here pulls torch. A configuration driving `fgsm` may well need
|
|
32
|
+
torch in the environment that hosts the target model; that is a property of the
|
|
33
|
+
model you bring, not of this package.
|
|
34
|
+
|
|
35
|
+
## How the step works
|
|
36
|
+
|
|
37
|
+
The batch is normalized to `[0, 1]` pixel units, the target's gradient is taken
|
|
38
|
+
in the same units, and `epsilon * sign(grad)` is added — so only the *sign* of
|
|
39
|
+
the gradient participates, not its magnitude. The result is clipped back into
|
|
40
|
+
range and restored to the batch's dtype.
|
|
41
|
+
|
|
42
|
+
Three declarations follow from that, and the framework enforces each:
|
|
43
|
+
|
|
44
|
+
- **`clips = True`** — a signed step can push a pixel past the selection's value
|
|
45
|
+
range, so the output is clipped back rather than refused.
|
|
46
|
+
- **`reversible = False`** — the step is lossy. The original pixels are not
|
|
47
|
+
recoverable from the output, so the row records that it is not invertible.
|
|
48
|
+
- **Deterministic** — no randomness is drawn, so the recorded seed is `None`.
|
|
49
|
+
The same input and the same `epsilon` always give the same output.
|
|
50
|
+
|
|
51
|
+
`epsilon` is the adversarial budget, strictly positive, and sweepable, so a run
|
|
52
|
+
can walk a whole budget curve.
|
|
53
|
+
|
|
54
|
+
## Labels do not move
|
|
55
|
+
|
|
56
|
+
An attack moves the model's *prediction*, not the truth. The ground-truth label
|
|
57
|
+
belongs to the source row, travels with it, and is never touched here — this
|
|
58
|
+
package has no notion of a label at all, only of pixels.
|
|
59
|
+
|
|
60
|
+
That is what makes an adversarial sample meaningful: if the label had moved
|
|
61
|
+
along with the pixels, the pair would prove nothing. A label that changed under
|
|
62
|
+
perturbation, or a target model whose response was ignored, would both mean the
|
|
63
|
+
measurement was not an attack at all.
|
|
64
|
+
|
|
65
|
+
## Example
|
|
66
|
+
|
|
67
|
+
The `models` and `operations` sections of a job configuration, with a target
|
|
68
|
+
model supplied as your own source:
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
models:
|
|
72
|
+
yolo:
|
|
73
|
+
type: python
|
|
74
|
+
path: adapters/yolo_target.py
|
|
75
|
+
export: YoloTarget
|
|
76
|
+
weights: yolov8n.pt
|
|
77
|
+
params: { conf: 0.25 }
|
|
78
|
+
operations:
|
|
79
|
+
transformations:
|
|
80
|
+
- name: fgsm
|
|
81
|
+
type: fgsm
|
|
82
|
+
target_model: yolo
|
|
83
|
+
epsilon:
|
|
84
|
+
range: [0.01, 0.05]
|
|
85
|
+
step: 0.01
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The transformation names its model with `target_model: yolo`. Only `grad` is
|
|
89
|
+
used from it, and its output is checked for shape and finiteness before the step,
|
|
90
|
+
so a misbehaving adapter fails loudly rather than corrupting the batch.
|
|
91
|
+
|
|
92
|
+
A target model is recognised by a non-empty `name` plus a `grad` method, and
|
|
93
|
+
conformance is checked against the `target` role the transformation declares.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "kcai-data-sampling-fgsm"
|
|
3
|
+
dynamic = ["dependencies", "version"]
|
|
4
|
+
description = "Adversarial perturbation (FGSM) for kcai data-sampling"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Safenai", email = "support@safenai.io"},
|
|
7
|
+
]
|
|
8
|
+
license = "Apache-2.0"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
keywords = ["ml", "data", "augmentation", "robustness", "adversarial", "fgsm"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Programming Language :: Python :: 3.14",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.entry-points."kcai_data_sampling.transformations"]
|
|
23
|
+
fgsm = "kcai_data_sampling_fgsm.api.transformations.fgsm:Fgsm"
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["setuptools>=80", "setuptools-scm[simple]>=8"]
|
|
27
|
+
build-backend = "setuptools.build_meta"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools_scm]
|
|
30
|
+
version_file = "src/kcai_data_sampling_fgsm/_version_.py"
|
|
31
|
+
root = "../.."
|
|
32
|
+
fallback_version = "0.0.1"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.dynamic]
|
|
35
|
+
dependencies = {file = ["requirements.txt"]}
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["src"]
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.package-data]
|
|
41
|
+
kcai_data_sampling_fgsm = ["py.typed"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
kcai-data-sampling-core
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Adversarial perturbation with the Fast Gradient Sign Method.
|
|
2
|
+
|
|
3
|
+
Ships the ``fgsm`` transformation (``model_role = "target"``): a budgeted
|
|
4
|
+
sign-direction step along the gradient of the user's target model, expressed in
|
|
5
|
+
normalized pixel units. The transformation class lives under
|
|
6
|
+
``api/transformations/``, the pure step math under ``transformations/``, and
|
|
7
|
+
the configuration schema at the package root. No model framework is imported
|
|
8
|
+
and no model ships: the target model is the user's, provided at run time
|
|
9
|
+
through the ``models:`` section.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from kcai_data_sampling_fgsm._version_ import __version__ as __version__
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Subclasses of the generic core contracts.
|
|
2
|
+
|
|
3
|
+
The transformation classes wrap the pure step math from
|
|
4
|
+
``kcai_data_sampling_fgsm.transformations`` into the unary ``apply`` contract.
|
|
5
|
+
I/O stays out: reads happen in the ``-job`` dataloaders and writes in the
|
|
6
|
+
``-job`` outputwriters; everything flows as image batches.
|
|
7
|
+
"""
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Unary transformation classes.
|
|
2
|
+
|
|
3
|
+
The generic core contract lives in ``kcai_data_sampling_core.api``; the
|
|
4
|
+
``Fgsm`` class here wraps the pure step function from
|
|
5
|
+
``kcai_data_sampling_fgsm.transformations`` into the unary ``apply`` contract.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from kcai_data_sampling_fgsm.api.transformations.fgsm import Fgsm
|
|
9
|
+
|
|
10
|
+
__all__ = ["Fgsm"]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""The ``fgsm`` transformation class.
|
|
2
|
+
|
|
3
|
+
Wraps the normalized sign-direction step
|
|
4
|
+
(:func:`kcai_data_sampling_fgsm.transformations.fgsm.fgsm_step`) into the
|
|
5
|
+
unary transformation contract. The target model — the user's — is queried on
|
|
6
|
+
the normalized float batch and its gradient is funneled through
|
|
7
|
+
``check_output`` (shape and finiteness) before the step.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from kcai_data_sampling_core.api.roles import check_output
|
|
11
|
+
from kcai_data_sampling_core.api.unary import UnaryTransformation
|
|
12
|
+
import numpy as np
|
|
13
|
+
from typing_extensions import override
|
|
14
|
+
|
|
15
|
+
from kcai_data_sampling_fgsm.configs import FgsmTransformationConfig
|
|
16
|
+
from kcai_data_sampling_fgsm.transformations.fgsm import fgsm_step
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Fgsm(UnaryTransformation):
|
|
20
|
+
"""One budgeted step along the sign of the target model's gradient, in L-infinity.
|
|
21
|
+
|
|
22
|
+
The ``epsilon``-signed step may push pixels past the selection's value
|
|
23
|
+
range, so the algorithm declares ``clips`` and the base clips the output
|
|
24
|
+
back to it. The step is a lossy perturbation (not ``reversible``) and
|
|
25
|
+
deterministic, so the row records no seed. The target model is the user's
|
|
26
|
+
``TargetModel``: only its ``grad`` is used.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
algorithm = "fgsm"
|
|
30
|
+
|
|
31
|
+
#: The registered config schema this algorithm validates against.
|
|
32
|
+
Config = FgsmTransformationConfig
|
|
33
|
+
|
|
34
|
+
model_role = "target"
|
|
35
|
+
model_methods = ("grad",)
|
|
36
|
+
|
|
37
|
+
clips = True
|
|
38
|
+
reversible = False
|
|
39
|
+
|
|
40
|
+
@override
|
|
41
|
+
def apply(
|
|
42
|
+
self,
|
|
43
|
+
xs: np.ndarray, # (b, h, w, c) uint8
|
|
44
|
+
rngs: list[np.random.Generator] | None = None,
|
|
45
|
+
) -> np.ndarray:
|
|
46
|
+
"""Perturb the batch by ``epsilon`` along the sign of the target gradient.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
xs: Batch of sample arrays shaped ``(B, *sample)``.
|
|
50
|
+
rngs: Unused for this deterministic operation.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
A batch of the same shape and dtype, clipped within the value range.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ValueError: If the target model's ``grad`` output breaks the
|
|
57
|
+
numeric contract (wrong shape or non-finite).
|
|
58
|
+
"""
|
|
59
|
+
del rngs
|
|
60
|
+
normalized = xs.astype(np.float64) / np.iinfo(xs.dtype).max
|
|
61
|
+
grad = check_output(
|
|
62
|
+
self.algorithm,
|
|
63
|
+
self.target_model,
|
|
64
|
+
"grad",
|
|
65
|
+
normalized,
|
|
66
|
+
self.target_model.grad(normalized),
|
|
67
|
+
)
|
|
68
|
+
return fgsm_step(xs, grad, self.params["epsilon"])
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""The ``fgsm`` transformation's configuration schema.
|
|
2
|
+
|
|
3
|
+
The schema subclasses the core base ``TransformationConfig`` and pins ``type``
|
|
4
|
+
to the algorithm's literal, so the registry-resolved validator in ``JobConfig``
|
|
5
|
+
picks it by type. ``epsilon`` — the adversarial budget — is required, in
|
|
6
|
+
normalized ``[0, 1]`` pixel units, strictly positive, and sweepable (its
|
|
7
|
+
interval must stay ``> 0``). ``target_model`` names a model from the
|
|
8
|
+
``models:`` section of the job config; the CLI resolves the name to an
|
|
9
|
+
instance.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Literal, Self
|
|
13
|
+
|
|
14
|
+
from kcai_data_sampling_core.models.config import TransformationConfig
|
|
15
|
+
from kcai_data_sampling_core.models.sweep import SweepConfig
|
|
16
|
+
from pydantic import Field, model_validator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FgsmTransformationConfig(TransformationConfig):
|
|
20
|
+
"""Configuration of the ``fgsm`` transformation.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
target_model: Name of a target model from the job's ``models:``
|
|
24
|
+
section; the CLI resolves it to an instance. Optional here because
|
|
25
|
+
the transformation base consumes it before re-validating the
|
|
26
|
+
remaining parameters against this schema; a model-role
|
|
27
|
+
transformation without one is refused loudly at construction (the
|
|
28
|
+
slot check).
|
|
29
|
+
epsilon: Adversarial budget in normalized ``[0, 1]`` pixel units,
|
|
30
|
+
strictly positive, or a ``SweepConfig`` expanding it (its interval
|
|
31
|
+
must stay ``> 0``).
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
type: Literal["fgsm"] = "fgsm"
|
|
35
|
+
target_model: str | None = Field(
|
|
36
|
+
default=None,
|
|
37
|
+
exclude=True,
|
|
38
|
+
description="Name of a target model from the job's models: section; excluded"
|
|
39
|
+
" from the resolved parameters (the base consumes it before validation).",
|
|
40
|
+
)
|
|
41
|
+
epsilon: float | SweepConfig = Field(
|
|
42
|
+
description="Adversarial budget in normalized [0, 1] pixel units; a SweepConfig expands it."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@model_validator(mode="after")
|
|
46
|
+
def _parameter_bounds(self) -> Self:
|
|
47
|
+
"""Enforce the epsilon budget's range on the value or the sweep.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
The validated config.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
ValueError: If ``epsilon`` is not strictly positive, whether given
|
|
54
|
+
directly or as a sweep interval.
|
|
55
|
+
"""
|
|
56
|
+
self._check_parameter_bounds("epsilon", minimum=0, exclusive_min=True)
|
|
57
|
+
return self
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Pure algorithm math for the adversarial transformations.
|
|
2
|
+
|
|
3
|
+
All transformations share the image-batch convention ``(B, H, W, 4) uint8``.
|
|
4
|
+
The module-level functions here are the pure algorithm math; the
|
|
5
|
+
transformation class that wraps them into the unary contract lives in
|
|
6
|
+
``kcai_data_sampling_fgsm.api.transformations``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from kcai_data_sampling_fgsm.transformations.fgsm import fgsm_step
|
|
10
|
+
|
|
11
|
+
__all__ = ["fgsm_step"]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""The FGSM step, in normalized units: the math.
|
|
2
|
+
|
|
3
|
+
``fgsm_step(xs, grad, epsilon)`` is the pure numpy map — the transformation
|
|
4
|
+
class that wraps it into the unary contract lives in
|
|
5
|
+
``kcai_data_sampling_fgsm.api.transformations.fgsm``. The step runs in
|
|
6
|
+
normalized ``[0, 1]`` pixel units (the batch divided by its dtype maximum), and
|
|
7
|
+
the model's gradient is in the same units: only its sign participates.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def fgsm_step(xs: np.ndarray, grad: np.ndarray, epsilon: float) -> np.ndarray:
|
|
14
|
+
"""Apply one budgeted sign-direction step in normalized ``[0, 1]`` units.
|
|
15
|
+
|
|
16
|
+
Normalizes the integer batch by its dtype maximum, adds
|
|
17
|
+
``epsilon * sign(grad)``, clips back to ``[0, 1]``, and restores the
|
|
18
|
+
batch's dtype (scale, round, cast). ``grad`` is the target model's gradient
|
|
19
|
+
in the same normalized units — the step is ``epsilon * sign(grad)``, so
|
|
20
|
+
only the sign matters.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
xs: Integer batch of sample arrays ``(B, *sample)``.
|
|
24
|
+
grad: Signed gradient in normalized units, same shape as ``xs``.
|
|
25
|
+
epsilon: Adversarial budget in normalized ``[0, 1]`` pixel units,
|
|
26
|
+
strictly positive.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
The perturbed batch, same shape and dtype as ``xs``.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
ValueError: If ``xs`` is not an integer array (a normalization-by-max
|
|
33
|
+
has no meaning otherwise).
|
|
34
|
+
"""
|
|
35
|
+
if not np.issubdtype(xs.dtype, np.integer):
|
|
36
|
+
raise ValueError(f"fgsm_step: integer batches only, got dtype {xs.dtype}")
|
|
37
|
+
info = np.iinfo(xs.dtype)
|
|
38
|
+
normalized = xs.astype(np.float64) / info.max
|
|
39
|
+
stepped = np.clip(normalized + epsilon * np.sign(grad), 0.0, 1.0)
|
|
40
|
+
return np.rint(stepped * info.max).astype(xs.dtype)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kcai-data-sampling-fgsm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Adversarial perturbation (FGSM) for kcai data-sampling
|
|
5
|
+
Author-email: Safenai <support@safenai.io>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Keywords: ml,data,augmentation,robustness,adversarial,fgsm
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: kcai-data-sampling-core
|
|
18
|
+
|
|
19
|
+
# kcai-data-sampling-fgsm
|
|
20
|
+
|
|
21
|
+
The **adversarial** slot of kcai data-sampling: transformations where a model's
|
|
22
|
+
*response* enters the computation and the result is graded against it. Here the
|
|
23
|
+
model is a **target** — it is not asked to produce anything, only to react. That
|
|
24
|
+
is what makes the family adversarial, and the declaring `model_role = "target"`
|
|
25
|
+
is what tells the framework to treat it as such.
|
|
26
|
+
|
|
27
|
+
This package implements the Fast Gradient Sign Method as a single budgeted step
|
|
28
|
+
along the sign of the target model's gradient, and exposes it through the
|
|
29
|
+
`kcai_data_sampling.transformations` entry-point group.
|
|
30
|
+
|
|
31
|
+
**The target model is yours.** It arrives at run time through the `models:`
|
|
32
|
+
section of your configuration and is never shipped by this package.
|
|
33
|
+
|
|
34
|
+
## What it ships today
|
|
35
|
+
|
|
36
|
+
- `fgsm` — one method: one L-infinity step of budget `epsilon` along the sign of
|
|
37
|
+
the target's gradient.
|
|
38
|
+
|
|
39
|
+
One method, as an example of the slot rather than a catalogue.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install "kcai-data-sampling[fgsm]"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**No deep-learning framework is required.** This package depends only on the
|
|
48
|
+
core contracts — the gradient arrives from *your* model, so the work is yours
|
|
49
|
+
too, and nothing here pulls torch. A configuration driving `fgsm` may well need
|
|
50
|
+
torch in the environment that hosts the target model; that is a property of the
|
|
51
|
+
model you bring, not of this package.
|
|
52
|
+
|
|
53
|
+
## How the step works
|
|
54
|
+
|
|
55
|
+
The batch is normalized to `[0, 1]` pixel units, the target's gradient is taken
|
|
56
|
+
in the same units, and `epsilon * sign(grad)` is added — so only the *sign* of
|
|
57
|
+
the gradient participates, not its magnitude. The result is clipped back into
|
|
58
|
+
range and restored to the batch's dtype.
|
|
59
|
+
|
|
60
|
+
Three declarations follow from that, and the framework enforces each:
|
|
61
|
+
|
|
62
|
+
- **`clips = True`** — a signed step can push a pixel past the selection's value
|
|
63
|
+
range, so the output is clipped back rather than refused.
|
|
64
|
+
- **`reversible = False`** — the step is lossy. The original pixels are not
|
|
65
|
+
recoverable from the output, so the row records that it is not invertible.
|
|
66
|
+
- **Deterministic** — no randomness is drawn, so the recorded seed is `None`.
|
|
67
|
+
The same input and the same `epsilon` always give the same output.
|
|
68
|
+
|
|
69
|
+
`epsilon` is the adversarial budget, strictly positive, and sweepable, so a run
|
|
70
|
+
can walk a whole budget curve.
|
|
71
|
+
|
|
72
|
+
## Labels do not move
|
|
73
|
+
|
|
74
|
+
An attack moves the model's *prediction*, not the truth. The ground-truth label
|
|
75
|
+
belongs to the source row, travels with it, and is never touched here — this
|
|
76
|
+
package has no notion of a label at all, only of pixels.
|
|
77
|
+
|
|
78
|
+
That is what makes an adversarial sample meaningful: if the label had moved
|
|
79
|
+
along with the pixels, the pair would prove nothing. A label that changed under
|
|
80
|
+
perturbation, or a target model whose response was ignored, would both mean the
|
|
81
|
+
measurement was not an attack at all.
|
|
82
|
+
|
|
83
|
+
## Example
|
|
84
|
+
|
|
85
|
+
The `models` and `operations` sections of a job configuration, with a target
|
|
86
|
+
model supplied as your own source:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
models:
|
|
90
|
+
yolo:
|
|
91
|
+
type: python
|
|
92
|
+
path: adapters/yolo_target.py
|
|
93
|
+
export: YoloTarget
|
|
94
|
+
weights: yolov8n.pt
|
|
95
|
+
params: { conf: 0.25 }
|
|
96
|
+
operations:
|
|
97
|
+
transformations:
|
|
98
|
+
- name: fgsm
|
|
99
|
+
type: fgsm
|
|
100
|
+
target_model: yolo
|
|
101
|
+
epsilon:
|
|
102
|
+
range: [0.01, 0.05]
|
|
103
|
+
step: 0.01
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The transformation names its model with `target_model: yolo`. Only `grad` is
|
|
107
|
+
used from it, and its output is checked for shape and finiteness before the step,
|
|
108
|
+
so a misbehaving adapter fails loudly rather than corrupting the batch.
|
|
109
|
+
|
|
110
|
+
A target model is recognised by a non-empty `name` plus a `grad` method, and
|
|
111
|
+
conformance is checked against the `target` role the transformation declares.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
requirements.txt
|
|
4
|
+
src/kcai_data_sampling_fgsm/__init__.py
|
|
5
|
+
src/kcai_data_sampling_fgsm/_version_.py
|
|
6
|
+
src/kcai_data_sampling_fgsm/configs.py
|
|
7
|
+
src/kcai_data_sampling_fgsm/py.typed
|
|
8
|
+
src/kcai_data_sampling_fgsm.egg-info/PKG-INFO
|
|
9
|
+
src/kcai_data_sampling_fgsm.egg-info/SOURCES.txt
|
|
10
|
+
src/kcai_data_sampling_fgsm.egg-info/dependency_links.txt
|
|
11
|
+
src/kcai_data_sampling_fgsm.egg-info/entry_points.txt
|
|
12
|
+
src/kcai_data_sampling_fgsm.egg-info/requires.txt
|
|
13
|
+
src/kcai_data_sampling_fgsm.egg-info/scm_file_list.json
|
|
14
|
+
src/kcai_data_sampling_fgsm.egg-info/top_level.txt
|
|
15
|
+
src/kcai_data_sampling_fgsm/api/__init__.py
|
|
16
|
+
src/kcai_data_sampling_fgsm/api/transformations/__init__.py
|
|
17
|
+
src/kcai_data_sampling_fgsm/api/transformations/fgsm.py
|
|
18
|
+
src/kcai_data_sampling_fgsm/transformations/__init__.py
|
|
19
|
+
src/kcai_data_sampling_fgsm/transformations/fgsm.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
kcai-data-sampling-core
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
"README.md",
|
|
4
|
+
"pyproject.toml",
|
|
5
|
+
"requirements.txt",
|
|
6
|
+
"src/kcai_data_sampling_fgsm/__init__.py",
|
|
7
|
+
"src/kcai_data_sampling_fgsm/api/__init__.py",
|
|
8
|
+
"src/kcai_data_sampling_fgsm/api/transformations/__init__.py",
|
|
9
|
+
"src/kcai_data_sampling_fgsm/api/transformations/fgsm.py",
|
|
10
|
+
"src/kcai_data_sampling_fgsm/configs.py",
|
|
11
|
+
"src/kcai_data_sampling_fgsm/py.typed",
|
|
12
|
+
"src/kcai_data_sampling_fgsm/transformations/__init__.py",
|
|
13
|
+
"src/kcai_data_sampling_fgsm/transformations/fgsm.py"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
kcai_data_sampling_fgsm
|