Skip to content

Commit 3cf3008

Browse files
wsxrdvkralka
andauthored
Cleanup dependencies (#428)
Another attempt --------- Co-authored-by: Karel Král <karelkral@google.com>
1 parent a08f294 commit 3cf3008

File tree

12 files changed

+1465
-1323
lines changed

12 files changed

+1465
-1323
lines changed

.github/workflows/mypy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
- name: Installing package
2424
run: |
2525
pip install --require-hashes --no-deps -r requirements.txt
26-
pip install --editable ".[dev]"
26+
pip install --editable ".[capture,dev,extra]"
2727
- name: Register matcher
2828
run: echo ::add-matcher::./.github/python_matcher.json
2929
- name: Running mypy

.github/workflows/pytest.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
- name: Installing package
2828
run: |
2929
pip install --require-hashes --no-deps -r requirements.txt
30-
pip install --editable ".[dev]"
30+
pip install --editable ".[capture,dev,extra]"
3131
- name: Install workflow dependencies
3232
run: |
3333
# Install PS drivers
@@ -53,7 +53,7 @@ jobs:
5353
- name: Installing package
5454
run: |
5555
pip install --require-hashes --no-deps -r requirements.txt
56-
pip install --editable ".[dev]"
56+
pip install --editable ".[capture,dev,extra]"
5757
- name: Install workflow dependencies
5858
run: |
5959
# Install PS drivers
@@ -90,7 +90,7 @@ jobs:
9090
run: rm -rf scaaml/
9191
- name: Installing package
9292
run: |
93-
pip install "scaaml[dev]"
93+
pip install "scaaml[capture,dev,extra]"
9494
- name: Install workflow dependencies
9595
run: |
9696
# Install PS drivers

pyproject.toml

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,13 @@ classifiers = [
2929
]
3030
dynamic = ["version"]
3131
dependencies = [
32-
"Pillow",
33-
"chipwhisperer",
34-
"colorama",
3532
"cryptography",
3633
"matplotlib",
37-
"networkx[default]",
3834
"numpy",
39-
"pandas",
4035
"picosdk",
4136
"pip",
42-
"pygments",
4337
"pyvisa",
4438
"pyvisa-py",
45-
"scipy",
4639
"semver",
4740
"setuptools",
4841
"tabulate",
@@ -56,15 +49,24 @@ dependencies = [
5649
version = {attr = "scaaml.__version__"}
5750

5851
[project.optional-dependencies]
52+
capture = [
53+
"chipwhisperer", # Capture pipeline.
54+
]
5955
dev = [
56+
"flake8",
6057
"mypy",
58+
"psutil",
6159
"pydantic",
60+
"pylint",
6261
"pytest",
6362
"pytest-cov",
64-
"flake8",
65-
"psutil",
6663
"types-cryptography",
6764
"types-tensorflow",
65+
"yapf",
66+
]
67+
extra = [
68+
"networkx[default]", # Enable relational heads in the GPAM model.
69+
"scipy", # Enable significance test metric.
6870
]
6971

7072
[project.scripts]

requirements.txt

Lines changed: 1187 additions & 1197 deletions
Large diffs are not rendered by default.

scaaml/capture/aes/capture_runner.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,8 @@ def capture_trace(self, crypto_input: CryptoInput) -> Optional[Trace]:
5757
AssertionError: If the textin in the trace is different from
5858
plaintext.
5959
"""
60-
# Convert to cw bytearray, which has nicer __str__ and __repr__ (helps
61-
# to comply with type-checking).
62-
plaintext = cw.bytearray(crypto_input.plaintext)
63-
key = cw.bytearray(crypto_input.key)
60+
plaintext = bytearray(crypto_input.plaintext)
61+
key = bytearray(crypto_input.key)
6462

6563
# Get the scope object.
6664
scope = self._scope.scope
@@ -75,8 +73,8 @@ def capture_trace(self, crypto_input: CryptoInput) -> Optional[Trace]:
7573
trace = cw.capture_trace(
7674
scope=scope, # type: ignore[arg-type]
7775
target=target,
78-
plaintext=plaintext,
79-
key=key,
76+
plaintext=plaintext, # type: ignore[arg-type]
77+
key=key, # type: ignore[arg-type]
8078
)
8179

8280
return trace

scaaml/metrics/__init__.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,16 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
"""Custom metrics."""
15-
from scaaml.metrics.custom import SignificanceTest
16-
from scaaml.metrics.custom import MaxRank
17-
from scaaml.metrics.custom import MeanRank
18-
from scaaml.metrics.custom import MeanConfidence
15+
from scaaml.metrics.custom import (
16+
MaxRank,
17+
MeanConfidence,
18+
MeanRank,
19+
)
20+
from scaaml.metrics.significance_test import SignificanceTest
1921

20-
__all__ = ["SignificanceTest", "MaxRank", "MeanRank", "MeanConfidence"]
22+
__all__ = [
23+
"MaxRank",
24+
"MeanConfidence",
25+
"MeanRank",
26+
"SignificanceTest",
27+
]

scaaml/metrics/custom.py

Lines changed: 2 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2022-2024 Google LLC
1+
# Copyright 2022-2025 Google LLC
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -22,8 +22,7 @@
2222

2323
import numpy as np
2424
import keras
25-
from keras.metrics import Metric, MeanMetricWrapper, categorical_accuracy
26-
import scipy
25+
from keras.metrics import Metric, MeanMetricWrapper
2726

2827

2928
def rank(y_true: Any, y_pred: Any, optimistic: bool = False) -> Any:
@@ -270,88 +269,3 @@ def reset_state(self) -> None:
270269
"""Reset the state for new measurement."""
271270
# The state of the metric will be reset at the start of each epoch.
272271
self.max_rank.assign(0.0)
273-
274-
275-
@keras.utils.register_keras_serializable(package="SCAAML")
276-
class SignificanceTest(Metric): # type: ignore[no-any-unimported,misc]
277-
"""Calculates the probability that a random guess would get the same
278-
accuracy. Probability is in the interval [0, 1] (impossible to always). By
279-
convention one rejects the null hypothesis at a given p-value (say 0.005 if
280-
we want to be sure).
281-
282-
Args:
283-
name: (Optional) String name of the metric instance.
284-
dtype: (Optional) Data type of the metric result.
285-
286-
Standalone usage:
287-
288-
>>> m = SignificanceTest()
289-
>>> m.update_state([[0., 1.], [1., 0.]], [[0.1, 0.9], [0.6, 0.4]])
290-
>>> m.result().numpy()
291-
0.25
292-
293-
Usage with `compile()` API:
294-
295-
```python
296-
model.compile(optimizer="sgd",
297-
loss="mse",
298-
metrics=[SignificanceTest()])
299-
```
300-
"""
301-
302-
def __init__(self, name: str = "SignificanceTest", **kwargs: Any) -> None:
303-
super().__init__(name=name, **kwargs)
304-
self.correct = self.add_weight(name="correct", initializer="zeros")
305-
self.possibilities = self.add_weight(name="possibilities",
306-
initializer="zeros")
307-
self.seen = self.add_weight(name="seen", initializer="zeros")
308-
309-
def update_state(self,
310-
y_true: Any,
311-
y_pred: Any,
312-
sample_weight: Optional[Any] = None) -> None:
313-
"""Update the state.
314-
315-
Args:
316-
y_true (batch of one-hot): One-hot ground truth values.
317-
y_pred (batch of one-hot): The prediction values.
318-
sample_weight (Optional weights): Does not make sense, as we count
319-
maximum.
320-
"""
321-
del sample_weight # unused
322-
323-
# Make into tensors.
324-
y_true = np.array(y_true, dtype=np.float32)
325-
y_pred = np.array(y_pred, dtype=np.float32)
326-
327-
# Update the number of seen examples.
328-
self.seen.assign(self.seen + y_true.shape[0])
329-
330-
# Update the number of correctly predicted examples.
331-
correct_now = keras.ops.sum(categorical_accuracy(y_true, y_pred))
332-
self.correct.assign(self.correct + correct_now)
333-
334-
# Update the number of possibilities.
335-
self.possibilities.assign(y_true.shape[-1])
336-
337-
def result(self) -> Any:
338-
"""Return the result."""
339-
340-
# Binomial distribution(n, p) -- how many successes out of n trials,
341-
# each succeeds with probability p independently on others.
342-
# scipy.stats.binom.cdf(k, n, p) -- probability there are <= k
343-
# successes.
344-
# We want to answer what is the probability that a random guess has at
345-
# least self.correct or more successes. Which is the same as 1 -
346-
# probability that it has at most k-1 successes.
347-
k = self.correct.numpy()
348-
n = self.seen.numpy()
349-
possibilities = self.possibilities.numpy()
350-
return 1 - scipy.stats.binom.cdf(k - 1, n, 1 / possibilities)
351-
352-
def reset_state(self) -> None:
353-
"""Reset the state for new measurement."""
354-
# The state of the metric will be reset at the start of each epoch.
355-
self.seen.assign(0.0)
356-
self.correct.assign(0.0)
357-
self.possibilities.assign(0.0)
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Custom metrics: MeanRank, MaxRank.
15+
16+
Related metrics:
17+
MinRank: Is zero as soon as accuracy is non-zero.
18+
keras.metrics.TopKCategoricalAccuracy: How often the correct class
19+
is in the top K predictions (how often is rank less than K).
20+
"""
21+
from typing import Any, Optional
22+
23+
import numpy as np
24+
import keras
25+
from keras.metrics import Metric, categorical_accuracy
26+
27+
from scaaml.utils import requires
28+
29+
30+
class SignificanceTest(Metric): # type: ignore[no-any-unimported,misc]
31+
"""Calculates the probability that a random guess would get the same
32+
accuracy. Probability is in the interval [0, 1] (impossible to always). By
33+
convention one rejects the null hypothesis at a given p-value (say 0.005 if
34+
we want to be sure).
35+
36+
The method `SignificanceTest.result` requires SciPy to be installed. We
37+
also mark the `__init__` so that users do not waste time without a chance
38+
to get the result.
39+
40+
Args:
41+
name: (Optional) String name of the metric instance.
42+
dtype: (Optional) Data type of the metric result.
43+
44+
Standalone usage:
45+
46+
```python
47+
>>> m = SignificanceTest()
48+
>>> m.update_state([[0., 1.], [1., 0.]], [[0.1, 0.9], [0.6, 0.4]])
49+
>>> m.result().numpy()
50+
0.25
51+
```
52+
53+
Usage with `compile()` API:
54+
55+
```python
56+
model.compile(optimizer="sgd",
57+
loss="mse",
58+
metrics=[SignificanceTest()])
59+
```
60+
"""
61+
62+
@requires("scipy")
63+
def __init__(self, name: str = "SignificanceTest", **kwargs: Any) -> None:
64+
super().__init__(name=name, **kwargs)
65+
self.correct = self.add_weight(name="correct", initializer="zeros")
66+
self.possibilities = self.add_weight(name="possibilities",
67+
initializer="zeros")
68+
self.seen = self.add_weight(name="seen", initializer="zeros")
69+
70+
def update_state(self,
71+
y_true: Any,
72+
y_pred: Any,
73+
sample_weight: Optional[Any] = None) -> None:
74+
"""Update the state.
75+
76+
Args:
77+
y_true (batch of one-hot): One-hot ground truth values.
78+
y_pred (batch of one-hot): The prediction values.
79+
sample_weight (Optional weights): Does not make sense, as we count
80+
maximum.
81+
"""
82+
del sample_weight # unused
83+
84+
# Make into tensors.
85+
y_true = np.array(y_true, dtype=np.float32)
86+
y_pred = np.array(y_pred, dtype=np.float32)
87+
88+
# Update the number of seen examples.
89+
self.seen.assign(self.seen + y_true.shape[0])
90+
91+
# Update the number of correctly predicted examples.
92+
correct_now = keras.ops.sum(categorical_accuracy(y_true, y_pred))
93+
self.correct.assign(self.correct + correct_now)
94+
95+
# Update the number of possibilities.
96+
self.possibilities.assign(y_true.shape[-1])
97+
98+
def result(self) -> Any:
99+
"""Return the result."""
100+
# SciPy is an optional dependency.
101+
import scipy # pylint: disable=import-outside-toplevel
102+
103+
# Binomial distribution(n, p) -- how many successes out of n trials,
104+
# each succeeds with probability p independently on others.
105+
# scipy.stats.binom.cdf(k, n, p) -- probability there are <= k
106+
# successes.
107+
# We want to answer what is the probability that a random guess has at
108+
# least self.correct or more successes. Which is the same as 1 -
109+
# probability that it has at most k-1 successes.
110+
k = self.correct.numpy()
111+
n = self.seen.numpy()
112+
possibilities = self.possibilities.numpy()
113+
return 1 - scipy.stats.binom.cdf(
114+
k - 1,
115+
n,
116+
1 / possibilities,
117+
)
118+
119+
def reset_state(self) -> None:
120+
"""Reset the state for new measurement."""
121+
# The state of the metric will be reset at the start of each epoch.
122+
self.seen.assign(0.0)
123+
self.correct.assign(0.0)
124+
self.possibilities.assign(0.0)

0 commit comments

Comments
 (0)