spotoptim 3.1.0__tar.gz → 3.2.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.
- {spotoptim-3.1.0 → spotoptim-3.2.0}/PKG-INFO +2 -1
- {spotoptim-3.1.0 → spotoptim-3.2.0}/pyproject.toml +2 -1
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/data/__init__.py +7 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/data/manydataset.py +141 -2
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/nn/__init__.py +4 -0
- spotoptim-3.2.0/src/spotoptim/nn/training.py +234 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/__init__.py +3 -0
- spotoptim-3.2.0/src/spotoptim/utils/seed.py +60 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/README.md +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/SpotOptim.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/core/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/core/data.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/core/experiment.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/core/protocol.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/core/storage.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/data/base.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/data/diabetes.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/datasets/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/datasets/py.typed +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/datasets/test01.csv +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/datasets/test02.csv +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/datasets/test11.csv +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/eda/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/eda/plots.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/factor_analyzer/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/factor_analyzer/confirmatory_factor_analyzer.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/factor_analyzer/factor_analyzer.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/factor_analyzer/factor_analyzer_rotator.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/factor_analyzer/factor_analyzer_utils.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/function/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/function/cd_data.csv +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/function/forr08a.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/function/mo.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/function/remote.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/function/sequence_cv_objective.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/function/so.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/function/torch_objective.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/hyperparameters/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/hyperparameters/parameters.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/hyperparameters/repr_helpers.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/inspection/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/inspection/importance.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/inspection/predictions.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/mo/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/mo/mo_mm.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/mo/pareto.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/nn/linear_regressor.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/nn/many_to_many_rnn.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/nn/mlp.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/nn/optimizer.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/optimizer/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/optimizer/acquisition.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/optimizer/schedule_free.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/optimizer/wrapper.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/plot/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/plot/contour.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/plot/mo.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/plot/visualization.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/py.typed +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/reporting/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/reporting/analysis.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/reporting/results.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/sampling/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/sampling/design.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/sampling/effects.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/sampling/lhs.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/sampling/mm.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/surrogate/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/surrogate/kernels.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/surrogate/kriging.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/surrogate/mlp_surrogate.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/surrogate/nystroem.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/surrogate/pipeline.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/surrogate/simple_kriging.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/tricands/__init__.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/tricands/tricands.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/boundaries.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/convert.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/dimreduction.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/eval.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/file.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/mapping.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/ocba.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/pca.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/scaler.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/serialization.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/stats.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/tensorboard.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/transform.py +0 -0
- {spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/utils/variables.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: spotoptim
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Sequential Parameter Optimization Toolbox
|
|
5
5
|
Author: bartzbeielstein
|
|
6
6
|
Author-email: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com>
|
|
@@ -36,6 +36,7 @@ Requires-Dist: requests>=2.32.3 ; extra == 'remote'
|
|
|
36
36
|
Requires-Dist: statsmodels>=0.14.6 ; extra == 'stats'
|
|
37
37
|
Requires-Dist: torch>=2.9.1 ; extra == 'torch'
|
|
38
38
|
Requires-Dist: tensorboard>=2.20.0 ; extra == 'torch'
|
|
39
|
+
Requires-Dist: torchmetrics>=1.8.2 ; extra == 'torch'
|
|
39
40
|
Requires-Dist: matplotlib>=3.10.7 ; extra == 'viz'
|
|
40
41
|
Requires-Dist: seaborn>=0.13.2 ; extra == 'viz'
|
|
41
42
|
Requires-Python: >=3.13
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "spotoptim"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.2.0"
|
|
4
4
|
description = "Sequential Parameter Optimization Toolbox"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "AGPL-3.0-or-later" }
|
|
@@ -44,6 +44,7 @@ dev = [
|
|
|
44
44
|
torch = [
|
|
45
45
|
"torch>=2.9.1",
|
|
46
46
|
"tensorboard>=2.20.0",
|
|
47
|
+
"torchmetrics>=1.8.2",
|
|
47
48
|
]
|
|
48
49
|
viz = [
|
|
49
50
|
"matplotlib>=3.10.7",
|
|
@@ -19,6 +19,8 @@ __all__ = [
|
|
|
19
19
|
"ManyToOneDataset",
|
|
20
20
|
"PadSequenceManyToMany",
|
|
21
21
|
"PadSequenceManyToOne",
|
|
22
|
+
"load_sequence_data",
|
|
23
|
+
"load_pooled_sequence_data",
|
|
22
24
|
]
|
|
23
25
|
|
|
24
26
|
_lazy_map = {
|
|
@@ -28,6 +30,11 @@ _lazy_map = {
|
|
|
28
30
|
"ManyToOneDataset": ("spotoptim.data.manydataset", "ManyToOneDataset"),
|
|
29
31
|
"PadSequenceManyToMany": ("spotoptim.data.manydataset", "PadSequenceManyToMany"),
|
|
30
32
|
"PadSequenceManyToOne": ("spotoptim.data.manydataset", "PadSequenceManyToOne"),
|
|
33
|
+
"load_sequence_data": ("spotoptim.data.manydataset", "load_sequence_data"),
|
|
34
|
+
"load_pooled_sequence_data": (
|
|
35
|
+
"spotoptim.data.manydataset",
|
|
36
|
+
"load_pooled_sequence_data",
|
|
37
|
+
),
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
|
|
@@ -13,12 +13,12 @@ classes provided here.
|
|
|
13
13
|
Requires the ``torch`` optional extra (``pip install 'spotoptim[torch]'``).
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
from typing import List, Optional, Union
|
|
16
|
+
from typing import List, Optional, Tuple, Union
|
|
17
17
|
|
|
18
18
|
import pandas as pd
|
|
19
19
|
import torch
|
|
20
20
|
from torch.nn.utils.rnn import pad_sequence
|
|
21
|
-
from torch.utils.data import Dataset
|
|
21
|
+
from torch.utils.data import ConcatDataset, Dataset
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class ManyToManyDataset(Dataset):
|
|
@@ -234,3 +234,142 @@ class PadSequenceManyToOne:
|
|
|
234
234
|
lengths = torch.tensor([len(x) for x in batch_x])
|
|
235
235
|
|
|
236
236
|
return padded_batch_x, lengths, torch.tensor(batch_y)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def load_sequence_data(
|
|
240
|
+
data: pd.DataFrame,
|
|
241
|
+
target: str,
|
|
242
|
+
group_by: str,
|
|
243
|
+
drop: Optional[Union[str, List[str]]] = None,
|
|
244
|
+
input_features: Optional[List[str]] = None,
|
|
245
|
+
feature_scaling=None,
|
|
246
|
+
target_scaling=None,
|
|
247
|
+
dataset_type: str = "many_to_many",
|
|
248
|
+
) -> Tuple[Dataset, pd.DataFrame]:
|
|
249
|
+
"""Group a DataFrame into a variable-length sequence dataset.
|
|
250
|
+
|
|
251
|
+
Splits ``data`` by the values of ``group_by`` (e.g. one compressor speed
|
|
252
|
+
line per group) and wraps the groups in a `ManyToManyDataset` or
|
|
253
|
+
`ManyToOneDataset`. Ported from the schu25a study's ``load_data`` helper
|
|
254
|
+
(``src/rnn/utils.py``); the experiment-specific column defaults were
|
|
255
|
+
removed.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
data (pd.DataFrame): Input data; one row per time step.
|
|
259
|
+
target (str): The target column name.
|
|
260
|
+
group_by (str): Column whose values define the sequences.
|
|
261
|
+
drop (Optional[Union[str, List[str]]]): Column(s) to drop from the
|
|
262
|
+
groups before extracting features. Defaults to None.
|
|
263
|
+
input_features (Optional[List[str]]): Columns scaled by
|
|
264
|
+
``feature_scaling``. Only used when ``feature_scaling`` is given;
|
|
265
|
+
the dataset features are always all columns except ``drop`` and
|
|
266
|
+
``target``. Defaults to None.
|
|
267
|
+
feature_scaling: Optional sklearn-style scaler; applied in place to
|
|
268
|
+
``data[input_features]`` via ``fit_transform``. Defaults to None.
|
|
269
|
+
target_scaling: Optional sklearn-style scaler; applied in place to
|
|
270
|
+
``data[target]`` via ``fit_transform``. Defaults to None.
|
|
271
|
+
dataset_type (str, optional): Dataset flavor. Options:
|
|
272
|
+
- "many_to_many": one target value per time step.
|
|
273
|
+
- "many_to_one": one scalar target per sequence.
|
|
274
|
+
Defaults to "many_to_many".
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
tuple: ``(dataset, data)`` — the sequence dataset and the (possibly
|
|
278
|
+
scaled) DataFrame.
|
|
279
|
+
|
|
280
|
+
Raises:
|
|
281
|
+
ValueError: If ``dataset_type`` is not "many_to_many" or "many_to_one".
|
|
282
|
+
|
|
283
|
+
Examples:
|
|
284
|
+
```{python}
|
|
285
|
+
import pandas as pd
|
|
286
|
+
from spotoptim.data.manydataset import load_sequence_data
|
|
287
|
+
|
|
288
|
+
df = pd.DataFrame({
|
|
289
|
+
"line": [1, 1, 1, 2, 2],
|
|
290
|
+
"x": [0.1, 0.2, 0.3, 0.4, 0.5],
|
|
291
|
+
"y": [1.0, 2.0, 3.0, 4.0, 5.0],
|
|
292
|
+
})
|
|
293
|
+
ds, df = load_sequence_data(df, target="y", group_by="line", drop="line")
|
|
294
|
+
print(len(ds), ds[0][0].shape)
|
|
295
|
+
```
|
|
296
|
+
"""
|
|
297
|
+
if feature_scaling is not None:
|
|
298
|
+
data[input_features] = feature_scaling.fit_transform(data[input_features])
|
|
299
|
+
|
|
300
|
+
if target_scaling is not None:
|
|
301
|
+
data[target] = target_scaling.fit_transform(data[target])
|
|
302
|
+
|
|
303
|
+
groups = [group for _, group in data.groupby(group_by)]
|
|
304
|
+
|
|
305
|
+
if dataset_type == "many_to_many":
|
|
306
|
+
return ManyToManyDataset(groups, target=target, drop=drop), data
|
|
307
|
+
elif dataset_type == "many_to_one":
|
|
308
|
+
return ManyToOneDataset(groups, target=target, drop=drop), data
|
|
309
|
+
raise ValueError(
|
|
310
|
+
f"dataset_type {dataset_type!r} not supported. "
|
|
311
|
+
"Options: 'many_to_many', 'many_to_one'."
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def load_pooled_sequence_data(
|
|
316
|
+
data_list: List[pd.DataFrame],
|
|
317
|
+
target: str,
|
|
318
|
+
group_by: str,
|
|
319
|
+
drop: Optional[Union[str, List[str]]] = None,
|
|
320
|
+
input_features: Optional[List[str]] = None,
|
|
321
|
+
feature_scaling=None,
|
|
322
|
+
target_scaling=None,
|
|
323
|
+
dataset_type: str = "many_to_many",
|
|
324
|
+
) -> ConcatDataset:
|
|
325
|
+
"""Pool several DataFrames into one concatenated sequence dataset.
|
|
326
|
+
|
|
327
|
+
Applies `load_sequence_data` to every DataFrame (e.g. one compressor map
|
|
328
|
+
each) and concatenates the resulting sequence datasets — the pooled
|
|
329
|
+
training set of the schu25a "global training" workflow (ported from
|
|
330
|
+
``load_pretrain_data`` in ``src/rnn/utils.py``).
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
data_list (List[pd.DataFrame]): Input DataFrames; one per map.
|
|
334
|
+
target (str): The target column name.
|
|
335
|
+
group_by (str): Column whose values define the sequences.
|
|
336
|
+
drop (Optional[Union[str, List[str]]]): Column(s) to drop from the
|
|
337
|
+
groups before extracting features. Defaults to None.
|
|
338
|
+
input_features (Optional[List[str]]): Columns scaled by
|
|
339
|
+
``feature_scaling``; see `load_sequence_data`. Defaults to None.
|
|
340
|
+
feature_scaling: Optional sklearn-style scaler, applied per DataFrame.
|
|
341
|
+
Defaults to None.
|
|
342
|
+
target_scaling: Optional sklearn-style scaler, applied per DataFrame.
|
|
343
|
+
Defaults to None.
|
|
344
|
+
dataset_type (str, optional): "many_to_many" or "many_to_one".
|
|
345
|
+
Defaults to "many_to_many".
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
ConcatDataset: The concatenation of the per-DataFrame sequence datasets.
|
|
349
|
+
|
|
350
|
+
Examples:
|
|
351
|
+
```{python}
|
|
352
|
+
import pandas as pd
|
|
353
|
+
from spotoptim.data.manydataset import load_pooled_sequence_data
|
|
354
|
+
|
|
355
|
+
df1 = pd.DataFrame({"line": [1, 1, 2], "x": [0.1, 0.2, 0.3], "y": [1.0, 2.0, 3.0]})
|
|
356
|
+
df2 = pd.DataFrame({"line": [1, 1], "x": [0.4, 0.5], "y": [4.0, 5.0]})
|
|
357
|
+
pooled = load_pooled_sequence_data([df1, df2], target="y", group_by="line", drop="line")
|
|
358
|
+
print(len(pooled))
|
|
359
|
+
```
|
|
360
|
+
"""
|
|
361
|
+
ds_ls = []
|
|
362
|
+
for data in data_list:
|
|
363
|
+
ds, _ = load_sequence_data(
|
|
364
|
+
data,
|
|
365
|
+
target=target,
|
|
366
|
+
group_by=group_by,
|
|
367
|
+
drop=drop,
|
|
368
|
+
input_features=input_features,
|
|
369
|
+
feature_scaling=feature_scaling,
|
|
370
|
+
target_scaling=target_scaling,
|
|
371
|
+
dataset_type=dataset_type,
|
|
372
|
+
)
|
|
373
|
+
ds_ls.append(ds)
|
|
374
|
+
|
|
375
|
+
return ConcatDataset(ds_ls)
|
|
@@ -11,6 +11,8 @@ __all__ = [
|
|
|
11
11
|
"ManyToManyRNNRegressor",
|
|
12
12
|
"get_activation",
|
|
13
13
|
"optimizer_handler",
|
|
14
|
+
"train_sequences",
|
|
15
|
+
"evaluate_sequences",
|
|
14
16
|
]
|
|
15
17
|
|
|
16
18
|
_lazy_map = {
|
|
@@ -23,6 +25,8 @@ _lazy_map = {
|
|
|
23
25
|
),
|
|
24
26
|
"get_activation": ("spotoptim.nn.many_to_many_rnn", "get_activation"),
|
|
25
27
|
"optimizer_handler": ("spotoptim.nn.optimizer", "optimizer_handler"),
|
|
28
|
+
"train_sequences": ("spotoptim.nn.training", "train_sequences"),
|
|
29
|
+
"evaluate_sequences": ("spotoptim.nn.training", "evaluate_sequences"),
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 bartzbeielstein
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
"""Plain training and evaluation loops for variable-length sequence models.
|
|
6
|
+
|
|
7
|
+
Ported op-for-op from the ``train``/``evaluate_many`` helpers of the schu25a
|
|
8
|
+
compressor-map study (``src/rnn/train.py``), so that runs seeded with
|
|
9
|
+
`spotoptim.utils.seed.seed_everything` reproduce the original results
|
|
10
|
+
bit-identically on the same device. The models are called as
|
|
11
|
+
``model(x, lengths)`` on padded batches (e.g.
|
|
12
|
+
`spotoptim.nn.many_to_many_rnn.ManyToManyRNN` with batches collated by
|
|
13
|
+
`spotoptim.data.manydataset.PadSequenceManyToMany`); losses and metrics are
|
|
14
|
+
computed on the squeezed padded tensors, exactly as in the original. The
|
|
15
|
+
original's unused ``seed`` parameters were dropped.
|
|
16
|
+
|
|
17
|
+
Requires the ``torch`` optional extra (``pip install 'spotoptim[torch]'``),
|
|
18
|
+
which includes ``torchmetrics``.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from typing import List, Literal, Optional, Tuple, Union, overload
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
import torch
|
|
25
|
+
import torch.nn as nn
|
|
26
|
+
from torch.utils.data import DataLoader
|
|
27
|
+
from torchmetrics.regression import MeanAbsolutePercentageError, MeanSquaredError
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def train_sequences(
|
|
31
|
+
model: nn.Module,
|
|
32
|
+
train_loader: DataLoader,
|
|
33
|
+
optimizer: torch.optim.Optimizer,
|
|
34
|
+
criterion: nn.Module,
|
|
35
|
+
val_loader: Optional[DataLoader] = None,
|
|
36
|
+
epochs: int = 10,
|
|
37
|
+
device: str = "cpu",
|
|
38
|
+
verbose: bool = True,
|
|
39
|
+
) -> Union[Tuple[nn.Module, List[float]], Tuple[nn.Module, List[float], List[float]]]:
|
|
40
|
+
"""Train a sequence model with a plain epoch loop.
|
|
41
|
+
|
|
42
|
+
One optimizer step per batch; the loss is computed between
|
|
43
|
+
``model(x, lengths).squeeze()`` and the (padded) target batch. The
|
|
44
|
+
recorded training loss is the batch-mean loss per epoch.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
model (nn.Module): Model called as ``model(x, lengths)``.
|
|
48
|
+
train_loader (DataLoader): Loader yielding ``(x, lengths, y)`` batches,
|
|
49
|
+
e.g. collated by `PadSequenceManyToMany`.
|
|
50
|
+
optimizer (torch.optim.Optimizer): Optimizer over ``model.parameters()``.
|
|
51
|
+
criterion (nn.Module): Loss module, e.g. ``nn.MSELoss()``.
|
|
52
|
+
val_loader (Optional[DataLoader]): Optional validation loader; when
|
|
53
|
+
given, the mean per-batch RMSE from `evaluate_sequences` is
|
|
54
|
+
recorded after every epoch. Defaults to None.
|
|
55
|
+
epochs (int, optional): Number of epochs. Defaults to 10.
|
|
56
|
+
device (str, optional): Training device. Defaults to "cpu".
|
|
57
|
+
verbose (bool, optional): Print per-epoch losses. Defaults to True.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
tuple: ``(model, train_loss_ls)`` without a validation loader, or
|
|
61
|
+
``(model, train_loss_ls, val_loss_ls)`` with one.
|
|
62
|
+
|
|
63
|
+
Examples:
|
|
64
|
+
```{python}
|
|
65
|
+
import pandas as pd
|
|
66
|
+
import torch
|
|
67
|
+
import torch.nn as nn
|
|
68
|
+
from torch.utils.data import DataLoader
|
|
69
|
+
from spotoptim.data.manydataset import ManyToManyDataset, PadSequenceManyToMany
|
|
70
|
+
from spotoptim.nn.many_to_many_rnn import ManyToManyRNN
|
|
71
|
+
from spotoptim.nn.training import train_sequences
|
|
72
|
+
from spotoptim.utils.seed import seed_everything
|
|
73
|
+
|
|
74
|
+
seed_everything(42)
|
|
75
|
+
frames = [
|
|
76
|
+
pd.DataFrame({"x": [0.1, 0.2, 0.3], "y": [1.0, 2.0, 3.0]}),
|
|
77
|
+
pd.DataFrame({"x": [0.4, 0.5], "y": [4.0, 5.0]}),
|
|
78
|
+
]
|
|
79
|
+
ds = ManyToManyDataset(frames, target="y")
|
|
80
|
+
dl = DataLoader(ds, batch_size=2, shuffle=False, collate_fn=PadSequenceManyToMany())
|
|
81
|
+
model = ManyToManyRNN(input_size=1, rnn_units=8, fc_units=8)
|
|
82
|
+
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
|
|
83
|
+
model, losses = train_sequences(
|
|
84
|
+
model, dl, optimizer, nn.MSELoss(), epochs=2, verbose=False
|
|
85
|
+
)
|
|
86
|
+
print(len(losses))
|
|
87
|
+
```
|
|
88
|
+
"""
|
|
89
|
+
model.train()
|
|
90
|
+
model.to(device)
|
|
91
|
+
|
|
92
|
+
train_loss_ls = []
|
|
93
|
+
val_loss_ls = []
|
|
94
|
+
|
|
95
|
+
for epoch in range(epochs):
|
|
96
|
+
model.train()
|
|
97
|
+
train_loss = 0.0
|
|
98
|
+
for batch_x, lengths, batch_y in train_loader:
|
|
99
|
+
batch_x = batch_x.to(device)
|
|
100
|
+
batch_y = batch_y.to(device)
|
|
101
|
+
|
|
102
|
+
optimizer.zero_grad()
|
|
103
|
+
outputs = model(batch_x, lengths)
|
|
104
|
+
outputs = outputs.squeeze()
|
|
105
|
+
|
|
106
|
+
loss = criterion(outputs, batch_y)
|
|
107
|
+
loss.backward()
|
|
108
|
+
|
|
109
|
+
optimizer.step()
|
|
110
|
+
train_loss += loss.item()
|
|
111
|
+
|
|
112
|
+
train_loss /= len(train_loader)
|
|
113
|
+
train_loss_ls.append(train_loss)
|
|
114
|
+
|
|
115
|
+
if val_loader is not None:
|
|
116
|
+
_, _, _, mape, rmse = evaluate_sequences(model, val_loader, device=device)
|
|
117
|
+
val_loss_ls.append(float(np.mean(np.array(rmse))))
|
|
118
|
+
else:
|
|
119
|
+
mape = None
|
|
120
|
+
rmse = None
|
|
121
|
+
|
|
122
|
+
if verbose:
|
|
123
|
+
print(f"Epoch: {epoch + 1}/{epochs}, Train Loss: {train_loss:.4f}", end="")
|
|
124
|
+
if mape is not None:
|
|
125
|
+
print(
|
|
126
|
+
f", Validation: MAPE: {np.mean(np.array(mape)):.4f}, "
|
|
127
|
+
f"RMSE: {np.mean(np.array(rmse)):.4f}"
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
print()
|
|
131
|
+
|
|
132
|
+
if val_loader is not None:
|
|
133
|
+
return model, train_loss_ls, val_loss_ls
|
|
134
|
+
return model, train_loss_ls
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@overload
|
|
138
|
+
def evaluate_sequences(
|
|
139
|
+
model: nn.Module,
|
|
140
|
+
val_loader: DataLoader,
|
|
141
|
+
device: str = "cpu",
|
|
142
|
+
metrics_only: Literal[False] = False,
|
|
143
|
+
) -> Tuple[list, list, list, List[float], List[float]]: ...
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@overload
|
|
147
|
+
def evaluate_sequences(
|
|
148
|
+
model: nn.Module,
|
|
149
|
+
val_loader: DataLoader,
|
|
150
|
+
device: str = "cpu",
|
|
151
|
+
*,
|
|
152
|
+
metrics_only: Literal[True],
|
|
153
|
+
) -> Tuple[float, float]: ...
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def evaluate_sequences(
|
|
157
|
+
model: nn.Module,
|
|
158
|
+
val_loader: DataLoader,
|
|
159
|
+
device: str = "cpu",
|
|
160
|
+
metrics_only: bool = False,
|
|
161
|
+
) -> Union[Tuple[float, float], Tuple[list, list, list, List[float], List[float]]]:
|
|
162
|
+
"""Evaluate a sequence model with per-batch MAPE and RMSE.
|
|
163
|
+
|
|
164
|
+
Metrics are the torchmetrics ``MeanAbsolutePercentageError`` and
|
|
165
|
+
``MeanSquaredError(squared=False)`` between ``model(x, lengths).squeeze()``
|
|
166
|
+
and the squeezed target batch, as in the schu25a original. Inputs,
|
|
167
|
+
predictions, and targets are additionally returned as nested Python lists
|
|
168
|
+
for plotting.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
model (nn.Module): Model called as ``model(x, lengths)``.
|
|
172
|
+
val_loader (DataLoader): Loader yielding ``(x, lengths, y)`` batches.
|
|
173
|
+
Batches are converted to NumPy, so ``device`` should be "cpu"
|
|
174
|
+
unless ``metrics_only=True``.
|
|
175
|
+
device (str, optional): Evaluation device. Defaults to "cpu".
|
|
176
|
+
metrics_only (bool, optional): Return only ``(mean_mape, mean_rmse)``.
|
|
177
|
+
Defaults to False.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
tuple: ``(x_ls, y_hat_ls, y_ls, mape_loss_ls, rmse_loss_ls)`` with one
|
|
181
|
+
entry per batch, or ``(mean_mape, mean_rmse)`` if ``metrics_only``.
|
|
182
|
+
|
|
183
|
+
Examples:
|
|
184
|
+
```{python}
|
|
185
|
+
import pandas as pd
|
|
186
|
+
import torch
|
|
187
|
+
from torch.utils.data import DataLoader
|
|
188
|
+
from spotoptim.data.manydataset import ManyToManyDataset, PadSequenceManyToMany
|
|
189
|
+
from spotoptim.nn.many_to_many_rnn import ManyToManyRNN
|
|
190
|
+
from spotoptim.nn.training import evaluate_sequences
|
|
191
|
+
from spotoptim.utils.seed import seed_everything
|
|
192
|
+
|
|
193
|
+
seed_everything(42)
|
|
194
|
+
frames = [pd.DataFrame({"x": [0.1, 0.2, 0.3], "y": [1.0, 2.0, 3.0]})]
|
|
195
|
+
ds = ManyToManyDataset(frames, target="y")
|
|
196
|
+
dl = DataLoader(ds, batch_size=1, shuffle=False, collate_fn=PadSequenceManyToMany())
|
|
197
|
+
model = ManyToManyRNN(input_size=1, rnn_units=8, fc_units=8)
|
|
198
|
+
x, y_hat, y, mape, rmse = evaluate_sequences(model, dl)
|
|
199
|
+
print(len(y_hat), len(mape), len(rmse))
|
|
200
|
+
```
|
|
201
|
+
"""
|
|
202
|
+
mean_abs_percentage_error = MeanAbsolutePercentageError().to(device)
|
|
203
|
+
rmse_loss = MeanSquaredError(squared=False).to(device)
|
|
204
|
+
|
|
205
|
+
model.eval()
|
|
206
|
+
model.to(device)
|
|
207
|
+
|
|
208
|
+
x_ls = []
|
|
209
|
+
y_hat_ls = []
|
|
210
|
+
y_ls = []
|
|
211
|
+
mape_loss_ls = []
|
|
212
|
+
rmse_loss_ls = []
|
|
213
|
+
|
|
214
|
+
with torch.no_grad():
|
|
215
|
+
for batch_x, lengths, batch_y in val_loader:
|
|
216
|
+
batch_x = batch_x.to(device)
|
|
217
|
+
batch_y = batch_y.to(device)
|
|
218
|
+
|
|
219
|
+
y_hat = model(batch_x, lengths)
|
|
220
|
+
y_hat = y_hat.squeeze()
|
|
221
|
+
|
|
222
|
+
mape = mean_abs_percentage_error(y_hat, batch_y.squeeze())
|
|
223
|
+
rmse = rmse_loss(y_hat, batch_y.squeeze())
|
|
224
|
+
|
|
225
|
+
mape_loss_ls.append(mape.item())
|
|
226
|
+
rmse_loss_ls.append(rmse.item())
|
|
227
|
+
|
|
228
|
+
x_ls.append(batch_x.squeeze().detach().numpy().tolist())
|
|
229
|
+
y_hat_ls.append(y_hat.squeeze().detach().numpy().tolist())
|
|
230
|
+
y_ls.append(batch_y.squeeze().detach().numpy().tolist())
|
|
231
|
+
|
|
232
|
+
if metrics_only:
|
|
233
|
+
return float(np.mean(mape_loss_ls)), float(np.mean(rmse_loss_ls))
|
|
234
|
+
return x_ls, y_hat_ls, y_ls, mape_loss_ls, rmse_loss_ls
|
|
@@ -27,6 +27,7 @@ __all__ = [
|
|
|
27
27
|
"get_loading_scores",
|
|
28
28
|
"plot_loading_scores",
|
|
29
29
|
"TorchStandardScaler",
|
|
30
|
+
"seed_everything",
|
|
30
31
|
]
|
|
31
32
|
|
|
32
33
|
_lazy_map = {
|
|
@@ -44,6 +45,8 @@ _lazy_map = {
|
|
|
44
45
|
"plot_loading_scores": ("spotoptim.utils.pca", "plot_loading_scores"),
|
|
45
46
|
# scaler (pulls torch)
|
|
46
47
|
"TorchStandardScaler": ("spotoptim.utils.scaler", "TorchStandardScaler"),
|
|
48
|
+
# seed (pulls torch)
|
|
49
|
+
"seed_everything": ("spotoptim.utils.seed", "seed_everything"),
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 bartzbeielstein
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
"""Global seeding for fully reproducible torch experiments.
|
|
6
|
+
|
|
7
|
+
Ported from the ``seed_everything`` helper of the schu25a compressor-map
|
|
8
|
+
study (``src/rnn/utils.py``): seeds Python, NumPy, and torch (CPU, CUDA, and
|
|
9
|
+
MPS) and switches cuDNN to deterministic mode, so that repeated runs on the
|
|
10
|
+
same device produce bit-identical results.
|
|
11
|
+
|
|
12
|
+
Requires the ``torch`` optional extra (``pip install 'spotoptim[torch]'``).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import random
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
import torch
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def seed_everything(seed: int) -> None:
|
|
23
|
+
"""Seed every random number generator relevant to a torch experiment.
|
|
24
|
+
|
|
25
|
+
Seeds Python's ``random``, ``PYTHONHASHSEED``, NumPy, and torch (CPU and
|
|
26
|
+
CUDA unconditionally, MPS when available), and sets
|
|
27
|
+
``torch.backends.cudnn.deterministic = True`` and
|
|
28
|
+
``torch.backends.cudnn.benchmark = False``.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
seed (int): The seed value.
|
|
32
|
+
|
|
33
|
+
Note:
|
|
34
|
+
- cuDNN determinism and benchmark flags are process-global side
|
|
35
|
+
effects; they may slow down convolutional workloads.
|
|
36
|
+
- Unlike the schu25a original, the MPS generator is only seeded when
|
|
37
|
+
the MPS backend is available, so the function also works on
|
|
38
|
+
CPU-only Linux hosts.
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
```{python}
|
|
42
|
+
import torch
|
|
43
|
+
from spotoptim.utils.seed import seed_everything
|
|
44
|
+
|
|
45
|
+
seed_everything(42)
|
|
46
|
+
a = torch.rand(3)
|
|
47
|
+
seed_everything(42)
|
|
48
|
+
b = torch.rand(3)
|
|
49
|
+
print(torch.equal(a, b))
|
|
50
|
+
```
|
|
51
|
+
"""
|
|
52
|
+
random.seed(seed)
|
|
53
|
+
os.environ["PYTHONHASHSEED"] = str(seed)
|
|
54
|
+
np.random.seed(seed)
|
|
55
|
+
torch.manual_seed(seed)
|
|
56
|
+
torch.cuda.manual_seed(seed)
|
|
57
|
+
if torch.backends.mps.is_available():
|
|
58
|
+
torch.mps.manual_seed(seed)
|
|
59
|
+
torch.backends.cudnn.deterministic = True
|
|
60
|
+
torch.backends.cudnn.benchmark = False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/factor_analyzer/confirmatory_factor_analyzer.py
RENAMED
|
File without changes
|
|
File without changes
|
{spotoptim-3.1.0 → spotoptim-3.2.0}/src/spotoptim/factor_analyzer/factor_analyzer_rotator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|