zeroth-learn 0.2.1__tar.gz → 0.2.3__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.
- {zeroth_learn-0.2.1/zeroth_learn.egg-info → zeroth_learn-0.2.3}/PKG-INFO +1 -1
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/pyproject.toml +1 -1
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/abstract/__init__.py +1 -1
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/abstract/model.py +60 -31
- zeroth_learn-0.2.3/zeroth/abstract/neural_network.py +20 -0
- zeroth_learn-0.2.3/zeroth/abstract/summary.py +56 -0
- zeroth_learn-0.2.3/zeroth/all.py +12 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/data.py +5 -1
- zeroth_learn-0.2.3/zeroth/experiment.py +123 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/first_order/layer.py +1 -1
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/first_order/model.py +7 -5
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/first_order/neural_network.py +9 -7
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/first_order/optimizers.py +1 -4
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/plot_losses.py +22 -19
- zeroth_learn-0.2.3/zeroth/utils/__init__.py +4 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/utils/dataclasses_utils.py +10 -2
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/zeroth_order/model.py +7 -5
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/zeroth_order/neural_network/neural_network.py +9 -6
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/zeroth_order/neural_network/parameter_manager.py +1 -1
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/zeroth_order/optimizers.py +1 -2
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3/zeroth_learn.egg-info}/PKG-INFO +1 -1
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth_learn.egg-info/SOURCES.txt +1 -0
- zeroth_learn-0.2.1/zeroth/abstract/neural_network.py +0 -26
- zeroth_learn-0.2.1/zeroth/abstract/summary.py +0 -13
- zeroth_learn-0.2.1/zeroth/experiment.py +0 -151
- zeroth_learn-0.2.1/zeroth/zeroth_order/neural_network/__init__.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/MANIFEST.in +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/README.md +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/setup.cfg +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/__init__.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/abstract/activation.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/abstract/blackbox.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/abstract/data_creator.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/abstract/loss.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/abstract/metric.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/abstract/optimizer.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/abstract/perturbation_matrix.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/first_order/__init__.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/losses/__init__.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/losses/cross_entropy.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/losses/mse.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/paths.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/types.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/utils/activation_functions.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/utils/metrics.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/utils/perturbation_matrices.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/zeroth_order/__init__.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/zeroth_order/gradient_estimators.py +0 -0
- {zeroth_learn-0.2.1/zeroth/utils → zeroth_learn-0.2.3/zeroth/zeroth_order/neural_network}/__init__.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/zeroth_order/zeroth_order_blackbox.py +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth_learn.egg-info/dependency_links.txt +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth_learn.egg-info/requires.txt +0 -0
- {zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth_learn.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ from .data_creator import DataCreator
|
|
|
4
4
|
from .loss import Loss
|
|
5
5
|
from .metric import Metric
|
|
6
6
|
from .model import Model, ModelConfig
|
|
7
|
-
from .neural_network import NeuralNetworkConfig,
|
|
7
|
+
from .neural_network import NeuralNetworkConfig, NeuralNetwork
|
|
8
8
|
from .optimizer import Optimizer
|
|
9
9
|
from .perturbation_matrix import PerturbationMatrix
|
|
10
10
|
from .summary import Summary
|
|
@@ -7,6 +7,7 @@ from abc import ABC, abstractmethod
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from typing import Callable
|
|
9
9
|
|
|
10
|
+
import matplotlib.pyplot as plt
|
|
10
11
|
import numpy as np
|
|
11
12
|
import pandas as pd
|
|
12
13
|
|
|
@@ -17,9 +18,37 @@ from .summary import Summary
|
|
|
17
18
|
from ..data import Data
|
|
18
19
|
from ..plot_losses import plot_losses
|
|
19
20
|
from ..types import Array
|
|
20
|
-
from ..utils.dataclasses_utils import config_serializer
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
@dataclass
|
|
24
|
+
class ModelRecord:
|
|
25
|
+
name: str
|
|
26
|
+
id: dict
|
|
27
|
+
training_loss: Array
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def load(cls, model_dir_path: str):
|
|
31
|
+
config_path = os.path.join(model_dir_path, "config.json")
|
|
32
|
+
with open(config_path, "r") as f:
|
|
33
|
+
config = json.load(f)
|
|
34
|
+
|
|
35
|
+
loss_path = os.path.join(model_dir_path, LOSS_FILE)
|
|
36
|
+
df_loss = pd.read_csv(loss_path)
|
|
37
|
+
|
|
38
|
+
return cls(
|
|
39
|
+
id=config.get("id", {}),
|
|
40
|
+
name=config.get("name", "Unknown"),
|
|
41
|
+
training_loss=df_loss["training_loss"].values
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def plot_loss(self, smooth_fraction: float = 0.05) -> plt.Figure:
|
|
45
|
+
fig = plot_losses(dimension=0,
|
|
46
|
+
models=[self],
|
|
47
|
+
title=self.name,
|
|
48
|
+
smooth_fraction=smooth_fraction)
|
|
49
|
+
plt.close(fig)
|
|
50
|
+
return fig
|
|
51
|
+
|
|
23
52
|
@dataclass(frozen=True, kw_only=True)
|
|
24
53
|
class ModelConfig(ABC, Summary):
|
|
25
54
|
"""
|
|
@@ -38,11 +67,11 @@ class ModelConfig(ABC, Summary):
|
|
|
38
67
|
nb_epochs: int = 1
|
|
39
68
|
|
|
40
69
|
@abstractmethod
|
|
41
|
-
def instantiate(self) -> Model:
|
|
70
|
+
def instantiate(self, data: Data) -> Model:
|
|
42
71
|
...
|
|
43
72
|
|
|
44
73
|
|
|
45
|
-
class Model(ABC):
|
|
74
|
+
class Model(ABC, ModelRecord):
|
|
46
75
|
"""
|
|
47
76
|
Base class orchestrating the training and testing loop.
|
|
48
77
|
|
|
@@ -50,14 +79,19 @@ class Model(ABC):
|
|
|
50
79
|
regardless of the underlying engine (Backpropagation or zeroth_order).
|
|
51
80
|
"""
|
|
52
81
|
|
|
82
|
+
LOSS_FILE: str = "training_loss.csv"
|
|
83
|
+
WEIGHTS_FILE: str = "weights.pkl"
|
|
84
|
+
CONFIG_FILE: str = "config.json"
|
|
85
|
+
|
|
53
86
|
neural_network: BlackBox
|
|
54
87
|
optimizer: Optimizer
|
|
55
88
|
|
|
56
|
-
def __init__(self, config: ModelConfig):
|
|
89
|
+
def __init__(self, config: ModelConfig, data: Data):
|
|
57
90
|
self.config = config
|
|
58
91
|
|
|
59
92
|
self.name: str = config.name
|
|
60
93
|
self.id: dict = config.id
|
|
94
|
+
self.data: Data = data
|
|
61
95
|
self.loss: Loss = config.loss
|
|
62
96
|
self.metric: Callable = config.metric
|
|
63
97
|
self.batch_size: int = config.batch_size
|
|
@@ -67,11 +101,10 @@ class Model(ABC):
|
|
|
67
101
|
self.test_loss: float = float("nan")
|
|
68
102
|
self.test_accuracy: float = float("nan")
|
|
69
103
|
|
|
70
|
-
def train(self,
|
|
104
|
+
def train(self, nb_print: int = 0) -> None:
|
|
71
105
|
"""Runs the training loop over the dataset.
|
|
72
106
|
|
|
73
107
|
Args:
|
|
74
|
-
data (Data): The dataset object containing train/test sets.
|
|
75
108
|
nb_print (int): Number of progress updates to print per epoch.
|
|
76
109
|
|
|
77
110
|
Returns:
|
|
@@ -79,30 +112,29 @@ class Model(ABC):
|
|
|
79
112
|
"""
|
|
80
113
|
print(f" Training {self.id} Model")
|
|
81
114
|
|
|
82
|
-
data.batch_size = self.batch_size
|
|
83
|
-
nb_batches = len(data)
|
|
115
|
+
self.data.batch_size = self.batch_size
|
|
116
|
+
nb_batches = len(self.data)
|
|
84
117
|
|
|
85
118
|
self.training_loss = np.zeros(self.nb_epochs * nb_batches, dtype=np.float64)
|
|
119
|
+
|
|
120
|
+
nb_print = nb_batches if nb_print == -1 else nb_print
|
|
86
121
|
print_indexes = np.linspace(0, nb_batches - 1, nb_print).astype(int)
|
|
87
122
|
|
|
88
123
|
for epoch_idx in range(self.nb_epochs):
|
|
89
124
|
print(f" epoch n°{epoch_idx + 1} out of {self.nb_epochs}")
|
|
90
|
-
data.permutation()
|
|
91
|
-
data.batch_size = self.batch_size
|
|
92
|
-
for batch_idx, (X_train, Y_train) in enumerate(data):
|
|
125
|
+
self.data.permutation()
|
|
126
|
+
self.data.batch_size = self.batch_size
|
|
127
|
+
for batch_idx, (X_train, Y_train) in enumerate(self.data):
|
|
93
128
|
avg_loss = self.optimizer.do_descent(self.neural_network, self.loss, X_train, Y_train)
|
|
94
129
|
self.training_loss[epoch_idx * nb_batches + batch_idx] = avg_loss
|
|
95
130
|
|
|
96
131
|
if batch_idx in print_indexes:
|
|
97
132
|
print(f" batch n°{batch_idx + 1} out of {nb_batches}, "
|
|
98
133
|
f"loss : {np.round(self.training_loss[epoch_idx * nb_batches + batch_idx], 3)}")
|
|
99
|
-
self.test(
|
|
100
|
-
|
|
101
|
-
def plot_loss(self, save_path: str = None, smooth_fraction: float = 0) -> None:
|
|
102
|
-
plot_losses(dimension=0, models=[self], title=self.name, save_path=save_path, smooth_fraction=smooth_fraction)
|
|
134
|
+
self.test()
|
|
103
135
|
|
|
104
|
-
def test(self
|
|
105
|
-
X_test, Y_true = data.X_test, data.Y_test # (in, batch), (out, batch)
|
|
136
|
+
def test(self) -> None:
|
|
137
|
+
X_test, Y_true = self.data.X_test, self.data.Y_test # (in, batch), (out, batch)
|
|
106
138
|
Y_pred = self.neural_network(X_test) # (out, batch)
|
|
107
139
|
|
|
108
140
|
self.test_accuracy = self.metric(Y_pred, Y_true)
|
|
@@ -110,26 +142,23 @@ class Model(ABC):
|
|
|
110
142
|
|
|
111
143
|
print(f" {self.id} accuracy : {self.test_accuracy}, loss : {self.test_loss}")
|
|
112
144
|
|
|
113
|
-
def
|
|
114
|
-
os.
|
|
115
|
-
|
|
116
|
-
with open(save_path, 'wb') as f:
|
|
117
|
-
pickle.dump(params_dict, f)
|
|
118
|
-
|
|
119
|
-
def save_config(self, save_path: str) -> None:
|
|
120
|
-
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
|
121
|
-
with open(save_path, "w") as f:
|
|
122
|
-
json.dump(self.config, f, default=config_serializer, indent=4)
|
|
123
|
-
|
|
124
|
-
def save_loss(self, save_path: str) -> None:
|
|
125
|
-
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
|
145
|
+
def save_loss(self, save_dir: str) -> None:
|
|
146
|
+
save_path = os.path.join(save_dir, self.LOSS_FILE)
|
|
147
|
+
os.makedirs(os.path.dirname(save_dir), exist_ok=True)
|
|
126
148
|
df = pd.DataFrame({
|
|
127
149
|
'training_loss': self.training_loss
|
|
128
150
|
})
|
|
129
151
|
df.to_csv(save_path)
|
|
130
152
|
|
|
131
|
-
def
|
|
153
|
+
def save_weights(self, save_dir: str) -> None:
|
|
154
|
+
save_path = os.path.join(save_dir, self.WEIGHTS_FILE)
|
|
155
|
+
params_dict = self.neural_network.get_params()
|
|
156
|
+
with open(save_path, 'wb') as f:
|
|
157
|
+
pickle.dump(params_dict, f)
|
|
158
|
+
|
|
159
|
+
def load_weights(self, load_dir: str) -> None:
|
|
132
160
|
"""Restaure les paramètres depuis un fichier pickle."""
|
|
161
|
+
load_path = os.path.join(load_dir, self.WEIGHTS_FILE)
|
|
133
162
|
with open(load_path, 'rb') as f:
|
|
134
163
|
params_dict = pickle.load(f)
|
|
135
164
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
from .activation import Activation
|
|
5
|
+
from .summary import Summary
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class NeuralNetworkConfig(Summary):
|
|
10
|
+
name: str
|
|
11
|
+
hidden_dims: list[int]
|
|
12
|
+
activations: list[Activation]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NeuralNetwork(ABC):
|
|
16
|
+
def __init__(self, config: NeuralNetworkConfig, input_dim: int, output_dim: int):
|
|
17
|
+
self.name: str = config.name
|
|
18
|
+
self.nb_layers: int = len(config.activations)
|
|
19
|
+
self.input_dim: int = input_dim
|
|
20
|
+
self.output_dim: int = output_dim
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Summary:
|
|
9
|
+
def summary(self, file=None) -> None:
|
|
10
|
+
print(self._summary(self, indent=0), file=file)
|
|
11
|
+
|
|
12
|
+
def save(self, save_dir: str) -> None:
|
|
13
|
+
|
|
14
|
+
save_path = os.path.join(save_dir, "config.txt")
|
|
15
|
+
|
|
16
|
+
if os.path.dirname(save_path):
|
|
17
|
+
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
|
18
|
+
|
|
19
|
+
with open(save_path, "w", encoding="utf-8") as f:
|
|
20
|
+
print(self._summary(self, indent=0), file=f)
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def load(cls, path: str, context: dict = None) -> Summary:
|
|
24
|
+
with open(path, "r") as f:
|
|
25
|
+
code = "".join(l for l in f)
|
|
26
|
+
return eval(code.strip(), {"__builtins__": __builtins__}, context)
|
|
27
|
+
|
|
28
|
+
def _summary(self, obj: Any, indent: int) -> str:
|
|
29
|
+
shift = " " * indent
|
|
30
|
+
next_shift = " " * (indent + 1)
|
|
31
|
+
|
|
32
|
+
if dataclasses.is_dataclass(obj):
|
|
33
|
+
cls_name = obj.__class__.__name__
|
|
34
|
+
items = []
|
|
35
|
+
for f in dataclasses.fields(obj):
|
|
36
|
+
val = getattr(obj, f.name)
|
|
37
|
+
formatted_val = self._summary(val, indent + 1)
|
|
38
|
+
items.append(f"{next_shift}{f.name}={formatted_val.lstrip()}")
|
|
39
|
+
|
|
40
|
+
content = ",\n".join(items)
|
|
41
|
+
return f"{cls_name}(\n{content}\n{shift})"
|
|
42
|
+
|
|
43
|
+
elif isinstance(obj, list):
|
|
44
|
+
if not obj: return "[]"
|
|
45
|
+
items = [self._summary(item, indent + 1) for item in obj]
|
|
46
|
+
content = ",\n".join([f"{next_shift}{item.lstrip()}" for item in items])
|
|
47
|
+
return f"[\n{content}\n{shift}]"
|
|
48
|
+
|
|
49
|
+
elif isinstance(obj, dict):
|
|
50
|
+
if not obj: return "{}"
|
|
51
|
+
items = [f"{next_shift}{repr(k)}: {self._summary(v, indent + 1).lstrip()}" for k, v in obj.items()]
|
|
52
|
+
content = ",\n".join(items)
|
|
53
|
+
return f"{{\n{content}\n{shift}}}"
|
|
54
|
+
|
|
55
|
+
else:
|
|
56
|
+
return repr(obj)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# noinspection PyUnresolvedReferences
|
|
2
|
+
from .losses import *
|
|
3
|
+
# noinspection PyUnresolvedReferences
|
|
4
|
+
from .abstract import *
|
|
5
|
+
# noinspection PyUnresolvedReferences
|
|
6
|
+
from .zeroth_order import *
|
|
7
|
+
# noinspection PyUnresolvedReferences
|
|
8
|
+
from .first_order import *
|
|
9
|
+
# noinspection PyUnresolvedReferences
|
|
10
|
+
from .experiment import *
|
|
11
|
+
# noinspection PyUnresolvedReferences
|
|
12
|
+
from .utils import *
|
|
@@ -6,13 +6,17 @@ from .types import Array
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class Data:
|
|
9
|
-
def __init__(self, raw_X_train: Array, raw_Y_train: Array, raw_X_test: Array, raw_Y_test: Array
|
|
9
|
+
def __init__(self, raw_X_train: Array, raw_Y_train: Array, raw_X_test: Array, raw_Y_test: Array,
|
|
10
|
+
nb_class: int = 0) -> None:
|
|
10
11
|
self.raw_X_train: Array = raw_X_train
|
|
11
12
|
self.raw_Y_train: Array = raw_Y_train
|
|
12
13
|
self.X_test: Array = raw_X_test
|
|
13
14
|
self.Y_test: Array = raw_Y_test
|
|
14
15
|
|
|
15
16
|
self.nb_data: int = raw_X_train.shape[0]
|
|
17
|
+
self.input_dim: int = self.raw_X_train.shape[1]
|
|
18
|
+
self.output_dim: int = self.raw_Y_train.shape[1] if nb_class == 0 else nb_class
|
|
19
|
+
|
|
16
20
|
self.batch_size: int | None = None
|
|
17
21
|
|
|
18
22
|
self.indices: np.ndarray = np.arange(self.nb_data)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
import os
|
|
5
|
+
from dataclasses import dataclass, replace
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
from .abstract import Model, ModelConfig, DataCreator, Summary
|
|
12
|
+
from .data import Data
|
|
13
|
+
from .plot_losses import plot_losses
|
|
14
|
+
from .utils.dataclasses_utils import get_name, set_value_by_path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class VariationConfig:
|
|
19
|
+
name: str
|
|
20
|
+
param: list[str]
|
|
21
|
+
values: Union[list, list[list]]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class ExperimentConfig(Summary):
|
|
26
|
+
name: str
|
|
27
|
+
base_model: ModelConfig
|
|
28
|
+
data_creator: DataCreator
|
|
29
|
+
variations: list[VariationConfig]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def instantiate(self) -> Experiment:
|
|
33
|
+
return Experiment(self)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Experiment:
|
|
37
|
+
"""Manages the full lifecycle of a deep learning experiment.
|
|
38
|
+
|
|
39
|
+
It handles data loading, model instantiation, training loops, and results visualization.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
name (str): Name of the experiment
|
|
43
|
+
models (list[Model]): List of models to train/compare
|
|
44
|
+
data (Data): The dataset wrapper.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
ACCURACY_FILE: str = "models_accuracy.csv"
|
|
48
|
+
CONFIG_FILE: str = "config.txt"
|
|
49
|
+
|
|
50
|
+
def __init__(self, config: ExperimentConfig) -> None:
|
|
51
|
+
self.config = config
|
|
52
|
+
self.name: str = config.name
|
|
53
|
+
self.base_model_config: ModelConfig = config.base_model
|
|
54
|
+
self.data = config.data_creator()
|
|
55
|
+
self.models: list[Model] = generate_models(config.base_model, config.variations, self.data)
|
|
56
|
+
|
|
57
|
+
def train_models(self, nb_print: int) -> None:
|
|
58
|
+
print(f"Training Models")
|
|
59
|
+
for model in self.models:
|
|
60
|
+
model.train(nb_print)
|
|
61
|
+
|
|
62
|
+
def plot_losses(self, title: str, plot_dimension: int, smooth_fraction: float = 0) -> plt.Figure:
|
|
63
|
+
fig = plot_losses(title=title,
|
|
64
|
+
dimension=plot_dimension,
|
|
65
|
+
models=self.models,
|
|
66
|
+
smooth_fraction=smooth_fraction)
|
|
67
|
+
|
|
68
|
+
plt.close(fig)
|
|
69
|
+
|
|
70
|
+
return fig
|
|
71
|
+
|
|
72
|
+
def test_models(self) -> None:
|
|
73
|
+
print(f"Testing Models")
|
|
74
|
+
for model in self.models:
|
|
75
|
+
model.test()
|
|
76
|
+
|
|
77
|
+
def save_df(self, save_dir: str) -> None:
|
|
78
|
+
"""
|
|
79
|
+
saves the models parameters and their args
|
|
80
|
+
"""
|
|
81
|
+
os.makedirs(save_dir, exist_ok=True)
|
|
82
|
+
print(f" Saving results to: {save_dir}")
|
|
83
|
+
|
|
84
|
+
data = [model.id | {"test_loss": model.test_loss, "test_accuracy": model.test_accuracy}
|
|
85
|
+
for model in self.models]
|
|
86
|
+
|
|
87
|
+
df = pd.DataFrame(data)
|
|
88
|
+
df.to_csv(os.path.join(save_dir, self.ACCURACY_FILE), index_label="iteration")
|
|
89
|
+
|
|
90
|
+
def save_weights(self, save_dir: str) -> None:
|
|
91
|
+
for i, model in enumerate(self.models):
|
|
92
|
+
save_path = os.path.join(save_dir, model.name)
|
|
93
|
+
model.save_weights(save_path)
|
|
94
|
+
|
|
95
|
+
def save_configs(self, save_dir: str) -> None:
|
|
96
|
+
|
|
97
|
+
config_path = os.path.join(save_dir, self.CONFIG_FILE)
|
|
98
|
+
self.config.save(config_path)
|
|
99
|
+
|
|
100
|
+
for i, model in enumerate(self.models):
|
|
101
|
+
save_path = os.path.join(save_dir, model.name)
|
|
102
|
+
model.config.save(save_path)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def generate_models(base_model: ModelConfig, variations: list[VariationConfig], data: Data) -> list[Model]:
|
|
106
|
+
models = []
|
|
107
|
+
|
|
108
|
+
values_lists = [v.values for v in variations]
|
|
109
|
+
|
|
110
|
+
for combination in itertools.product(*values_lists):
|
|
111
|
+
id_ = {}
|
|
112
|
+
current_model = base_model
|
|
113
|
+
|
|
114
|
+
for var_config, current_vals in zip(variations, combination):
|
|
115
|
+
id_[var_config.name] = get_name(current_vals[0])
|
|
116
|
+
|
|
117
|
+
for path, val in zip(var_config.param, current_vals):
|
|
118
|
+
current_model = set_value_by_path(current_model, path, val)
|
|
119
|
+
|
|
120
|
+
current_model = replace(current_model, id=id_)
|
|
121
|
+
models.append(current_model.instantiate(data))
|
|
122
|
+
|
|
123
|
+
return models
|
|
@@ -15,7 +15,7 @@ class Layer:
|
|
|
15
15
|
A (Array): Activated output. Shape (batch_size, output_dim).
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
def __init__(self,
|
|
18
|
+
def __init__(self, input_dim: int, output_dim: int, activation: Activation) -> None:
|
|
19
19
|
"""Initializes the layer with random weights and zeros biases.
|
|
20
20
|
|
|
21
21
|
Args:
|
|
@@ -5,6 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
from .neural_network import FirstOrderNeuralNetwork
|
|
6
6
|
from .optimizers import FirstOrderOptimizerConfig, FirstOrderOptimizer
|
|
7
7
|
from ..abstract import ModelConfig, Model, NeuralNetworkConfig
|
|
8
|
+
from ..data import Data
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
@dataclass(frozen=True, kw_only=True)
|
|
@@ -12,13 +13,14 @@ class FirstOrderModelConfig(ModelConfig):
|
|
|
12
13
|
neural_network_config: NeuralNetworkConfig
|
|
13
14
|
optimizer_config: FirstOrderOptimizerConfig
|
|
14
15
|
|
|
15
|
-
def instantiate(self) -> FirstOrderModel:
|
|
16
|
-
return FirstOrderModel(self)
|
|
16
|
+
def instantiate(self, data: Data) -> FirstOrderModel:
|
|
17
|
+
return FirstOrderModel(self, data)
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class FirstOrderModel(Model):
|
|
20
|
-
def __init__(self, config: FirstOrderModelConfig) -> None:
|
|
21
|
-
super().__init__(config)
|
|
21
|
+
def __init__(self, config: FirstOrderModelConfig, data: Data) -> None:
|
|
22
|
+
super().__init__(config, data)
|
|
22
23
|
|
|
23
|
-
self.neural_network: FirstOrderNeuralNetwork = FirstOrderNeuralNetwork(config.neural_network_config
|
|
24
|
+
self.neural_network: FirstOrderNeuralNetwork = FirstOrderNeuralNetwork(config.neural_network_config,
|
|
25
|
+
data.input_dim, data.output_dim)
|
|
24
26
|
self.optimizer: FirstOrderOptimizer = config.optimizer_config.instantiate()
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from .layer import Layer
|
|
2
2
|
from ..abstract.neural_network import NeuralNetwork, NeuralNetworkConfig
|
|
3
|
-
|
|
4
3
|
from ..types import Array
|
|
5
4
|
|
|
6
5
|
|
|
@@ -12,13 +11,16 @@ class FirstOrderNeuralNetwork(NeuralNetwork):
|
|
|
12
11
|
nb_layers (int): Number of layers.
|
|
13
12
|
"""
|
|
14
13
|
|
|
15
|
-
def __init__(self, config: NeuralNetworkConfig) -> None:
|
|
16
|
-
super().__init__(config)
|
|
14
|
+
def __init__(self, config: NeuralNetworkConfig, input_dim: int, output_dim: int) -> None:
|
|
15
|
+
super().__init__(config, input_dim, output_dim)
|
|
16
|
+
|
|
17
17
|
self.layers: list[Layer] = []
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
network_dimensions = [input_dim] + config.hidden_dims + [output_dim]
|
|
19
|
+
|
|
20
|
+
for i in range(self.nb_layers):
|
|
21
|
+
self.layers.append(Layer(network_dimensions[i],
|
|
22
|
+
network_dimensions[i + 1],
|
|
23
|
+
config.activations[i]))
|
|
22
24
|
|
|
23
25
|
def __call__(self, X: Array) -> Array:
|
|
24
26
|
return self.forward(X)
|
|
@@ -30,6 +30,7 @@ class FirstOrderSGDConfig(FirstOrderOptimizerConfig):
|
|
|
30
30
|
|
|
31
31
|
@dataclass(frozen=True)
|
|
32
32
|
class FirstOrderAdamConfig(FirstOrderSGDConfig):
|
|
33
|
+
name = "Adam"
|
|
33
34
|
beta1: float
|
|
34
35
|
beta2: float
|
|
35
36
|
epsilon: float
|
|
@@ -45,8 +46,6 @@ class FirstOrderOptimizer(Optimizer):
|
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
class FirstOrderSGD(FirstOrderOptimizer):
|
|
48
|
-
"""Abstract base class for gradient descent optimizers using first_order."""
|
|
49
|
-
|
|
50
49
|
def __init__(self, config: FirstOrderSGDConfig) -> None:
|
|
51
50
|
self.learning_rate: float = config.learning_rate
|
|
52
51
|
|
|
@@ -97,13 +96,11 @@ class FirstOrderSGD(FirstOrderOptimizer):
|
|
|
97
96
|
|
|
98
97
|
|
|
99
98
|
class FirstOrderAdam(FirstOrderSGD):
|
|
100
|
-
name = "Adam"
|
|
101
99
|
"""Implements the Adam optimization algorithm.
|
|
102
100
|
|
|
103
101
|
Adam (Adaptive Moment Estimation) stores moving averages of the gradients (m)
|
|
104
102
|
and squared gradients (v) to adapt the learning rate for each parameter.
|
|
105
103
|
"""
|
|
106
|
-
|
|
107
104
|
def __init__(self, config: FirstOrderAdamConfig) -> None:
|
|
108
105
|
super().__init__(config)
|
|
109
106
|
self.beta1: float = config.beta1
|
|
@@ -11,7 +11,7 @@ from matplotlib.axes import Axes
|
|
|
11
11
|
from .types import Array
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
|
-
from .abstract.model import
|
|
14
|
+
from .abstract.model import ModelRecord
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def set_style() -> None:
|
|
@@ -80,7 +80,7 @@ def smooth_curve(loss: Array, window_length: int) -> Array:
|
|
|
80
80
|
return np.exp(pd.Series(np.log(loss)).ewm(span=window_length, adjust=True).mean())
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
def plot_0d(models: list[
|
|
83
|
+
def plot_0d(models: list[ModelRecord], title: str, smooth_fraction: float = 50) -> plt.Figure:
|
|
84
84
|
"""
|
|
85
85
|
Plots a single graph overlaying multiple models that share the same hyperparameters.
|
|
86
86
|
"""
|
|
@@ -105,14 +105,17 @@ def plot_0d(models: list[Model], title: str, smooth_fraction: float = 50) -> Non
|
|
|
105
105
|
fig.legend(handles, labels, loc='lower center', ncol=len(handles),
|
|
106
106
|
bbox_to_anchor=(0.5, 0), frameon=False, fontsize=9)
|
|
107
107
|
|
|
108
|
+
return fig
|
|
108
109
|
|
|
109
|
-
|
|
110
|
+
|
|
111
|
+
def plot_1d(models: list[ModelRecord], title: str, key: str, smooth_fraction: float = 50) -> plt.Figure:
|
|
110
112
|
"""
|
|
111
113
|
Plots a row of subplots, varying one hyperparameter (key) across columns.
|
|
112
114
|
"""
|
|
113
|
-
|
|
115
|
+
print(len(models), [model.id for model in models])
|
|
114
116
|
cols = list(dict.fromkeys([m.id[key] for m in models]))
|
|
115
117
|
n_models = len(cols)
|
|
118
|
+
print(cols, n_models)
|
|
116
119
|
fig, axs = plt.subplots(1, n_models, figsize=(4.5 * n_models, 3.5), sharey=True)
|
|
117
120
|
|
|
118
121
|
for i, val in enumerate(cols):
|
|
@@ -141,8 +144,10 @@ def plot_1d(models: list[Model], title: str, key: str, smooth_fraction: float =
|
|
|
141
144
|
fig.legend(handles, labels, loc='lower center', ncol=len(handles),
|
|
142
145
|
bbox_to_anchor=(0.5, 0), frameon=False, fontsize=9)
|
|
143
146
|
|
|
147
|
+
return fig
|
|
144
148
|
|
|
145
|
-
|
|
149
|
+
|
|
150
|
+
def plot_2d(models: list[ModelRecord], title: str, row_key: str, col_key: str, smooth_fraction: float) -> plt.Figure:
|
|
146
151
|
"""
|
|
147
152
|
Plots a grid of subplots varying two hyperparameters: one across rows, one across columns.
|
|
148
153
|
|
|
@@ -191,32 +196,30 @@ def plot_2d(models: list[Model], title: str, row_key: str, col_key: str, smooth_
|
|
|
191
196
|
fig.text(0.5, 0.07, "Training steps", ha='center', fontsize=10)
|
|
192
197
|
fig.text(0.02, 0.5, "Training loss", va='center', rotation='vertical', fontsize=10)
|
|
193
198
|
|
|
199
|
+
return fig
|
|
200
|
+
|
|
194
201
|
|
|
195
|
-
def plot_losses(dimension: int, models: list[
|
|
202
|
+
def plot_losses(title: str, dimension: int, models: list[ModelRecord], smooth_fraction: float) -> plt.Figure:
|
|
196
203
|
"""
|
|
197
204
|
Main entry point for plotting. Automatically detects if the plot should be 0D, 1D, or 2D
|
|
198
205
|
based on the number of variation parameters.
|
|
199
206
|
|
|
200
207
|
Args:
|
|
208
|
+
title (str): The title of the plot.
|
|
201
209
|
dimension (int): dimension of the plot
|
|
202
210
|
models (list): List of model objects.
|
|
203
|
-
title (str): The title of the plot.
|
|
204
|
-
save_path (str, optional): File path to save the figure (e.g., 'plot.png').
|
|
205
211
|
smooth_fraction (int): span for smoothing.
|
|
206
212
|
"""
|
|
207
213
|
set_style()
|
|
208
214
|
|
|
209
215
|
keys = list(models[0].id.keys())
|
|
210
216
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if save_path:
|
|
219
|
-
plt.savefig(save_path, dpi=300, bbox_inches="tight")
|
|
220
|
-
print(f"Plot saved to {save_path}")
|
|
217
|
+
match dimension:
|
|
218
|
+
case 0:
|
|
219
|
+
fig = plot_0d(models, title, smooth_fraction)
|
|
220
|
+
case 1:
|
|
221
|
+
fig = plot_1d(models, title, keys[0], smooth_fraction)
|
|
222
|
+
case _:
|
|
223
|
+
fig = plot_2d(models, title, keys[0], keys[1], smooth_fraction)
|
|
221
224
|
|
|
222
|
-
|
|
225
|
+
return fig
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import
|
|
1
|
+
from dataclasses import replace
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
4
|
|
|
@@ -26,9 +26,17 @@ def set_value_by_path(obj: Any, path: str, value: Any) -> Any:
|
|
|
26
26
|
return replace(obj, **{field: value})
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
from dataclasses import is_dataclass, fields
|
|
30
|
+
|
|
31
|
+
|
|
29
32
|
def config_serializer(obj: Any):
|
|
30
33
|
if is_dataclass(obj):
|
|
31
|
-
|
|
34
|
+
data = {f.name: getattr(obj, f.name) for f in fields(obj)}
|
|
35
|
+
return {obj.__class__.__name__: data}
|
|
36
|
+
|
|
37
|
+
if hasattr(obj, "__class__") and obj.__class__.__module__ != "builtins":
|
|
38
|
+
return repr(obj)
|
|
39
|
+
|
|
32
40
|
if callable(obj):
|
|
33
41
|
return getattr(obj, "__name__", str(obj))
|
|
34
42
|
|
|
@@ -6,6 +6,7 @@ from .gradient_estimators import GradientEstimatorConfig, GradientEstimator
|
|
|
6
6
|
from .neural_network.neural_network import ZerothOrderNeuralNetwork
|
|
7
7
|
from .optimizers import ZerothOrderOptimizerConfig, ZerothOrderOptimizer
|
|
8
8
|
from ..abstract import Model, ModelConfig, NeuralNetworkConfig
|
|
9
|
+
from ..data import Data
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@dataclass(frozen=True)
|
|
@@ -14,15 +15,16 @@ class ZerothOrderModelConfig(ModelConfig):
|
|
|
14
15
|
optimizer_config: ZerothOrderOptimizerConfig
|
|
15
16
|
gradient_estimator_config: GradientEstimatorConfig
|
|
16
17
|
|
|
17
|
-
def instantiate(self) -> ZerothOrderModel:
|
|
18
|
-
return ZerothOrderModel(self)
|
|
18
|
+
def instantiate(self, data: Data) -> ZerothOrderModel:
|
|
19
|
+
return ZerothOrderModel(self, data)
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class ZerothOrderModel(Model):
|
|
22
|
-
def __init__(self, config: ZerothOrderModelConfig) -> None:
|
|
23
|
-
super().__init__(config)
|
|
23
|
+
def __init__(self, config: ZerothOrderModelConfig, data: Data) -> None:
|
|
24
|
+
super().__init__(config, data)
|
|
24
25
|
|
|
25
|
-
self.neural_network: ZerothOrderNeuralNetwork = ZerothOrderNeuralNetwork(config.neural_network_config
|
|
26
|
+
self.neural_network: ZerothOrderNeuralNetwork = ZerothOrderNeuralNetwork(config.neural_network_config,
|
|
27
|
+
data.input_dim, data.output_dim)
|
|
26
28
|
nb_params = self.neural_network.params.nb_params
|
|
27
29
|
self.gradient_estimator: GradientEstimator = config.gradient_estimator_config.instantiate(nb_params)
|
|
28
30
|
self.optimizer: ZerothOrderOptimizer = config.optimizer_config.instantiate(self.gradient_estimator)
|
{zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/zeroth_order/neural_network/neural_network.py
RENAMED
|
@@ -12,13 +12,16 @@ class ZerothOrderNeuralNetwork(NeuralNetwork, ZerothOrderBlackBox):
|
|
|
12
12
|
params (ParameterManager): Handler for flattening/reshaping weights (Theta <-> Ws/Bs).
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
def __init__(self, config: NeuralNetworkConfig) -> None:
|
|
16
|
-
super().__init__(config)
|
|
15
|
+
def __init__(self, config: NeuralNetworkConfig, input_dim: int, output_dim: int) -> None:
|
|
16
|
+
super().__init__(config, input_dim, output_dim)
|
|
17
17
|
self.params: ParameterManager = ParameterManager()
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
|
|
19
|
+
network_dimensions = [input_dim] + config.hidden_dims + [output_dim]
|
|
20
|
+
|
|
21
|
+
for i in range(self.nb_layers):
|
|
22
|
+
self.params.push_layer(network_dimensions[i],
|
|
23
|
+
network_dimensions[i + 1],
|
|
24
|
+
config.activations[i])
|
|
22
25
|
|
|
23
26
|
def __call__(self, X: Array) -> Array:
|
|
24
27
|
return self.forward(X)
|
{zeroth_learn-0.2.1 → zeroth_learn-0.2.3}/zeroth/zeroth_order/neural_network/parameter_manager.py
RENAMED
|
@@ -30,7 +30,7 @@ class ParameterManager:
|
|
|
30
30
|
self.nb_params: int = 0
|
|
31
31
|
self.Theta: Array = np.array([])
|
|
32
32
|
|
|
33
|
-
def push_layer(self,
|
|
33
|
+
def push_layer(self, input_dim: int, output_dim: int, f: Activation = ReLU()) -> None:
|
|
34
34
|
"""Adds a layer to the structure and updates the flat Theta vector.
|
|
35
35
|
|
|
36
36
|
Args:
|
|
@@ -97,14 +97,13 @@ class ZerothOrderSGD(ZerothOrderOptimizer):
|
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
class ZerothOrderAdam(ZerothOrderSGD):
|
|
100
|
-
name = "Adam"
|
|
101
100
|
"""Adaptive Moment Estimation (Adam) adapted for zeroth_order gradient estimates.
|
|
102
101
|
|
|
103
102
|
Note:
|
|
104
103
|
Since zeroth_order gradients are noisy approximations, Adam is often very effective
|
|
105
104
|
as its momentum terms (m, v) help smooth out the noise over time.
|
|
106
105
|
"""
|
|
107
|
-
|
|
106
|
+
name = "Adam"
|
|
108
107
|
def __init__(self, config: ZerothOrderAdamConfig, gradient_estimator: GradientEstimator) -> None:
|
|
109
108
|
self.beta1: float = config.beta1
|
|
110
109
|
self.beta2: float = config.beta2
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from abc import ABC
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
|
|
4
|
-
from .activation import Activation
|
|
5
|
-
from .summary import Summary
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@dataclass(frozen=True)
|
|
9
|
-
class LayerConfig(Summary):
|
|
10
|
-
input_dim: int
|
|
11
|
-
output_dim: int
|
|
12
|
-
activation: Activation
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dataclass(frozen=True)
|
|
16
|
-
class NeuralNetworkConfig(Summary):
|
|
17
|
-
name: str
|
|
18
|
-
layers_config: list[LayerConfig]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class NeuralNetwork(ABC):
|
|
22
|
-
def __init__(self, config: NeuralNetworkConfig):
|
|
23
|
-
self.name: str = config.name
|
|
24
|
-
self.nb_layers: int = len(config.layers_config)
|
|
25
|
-
self.input_dim: int = config.layers_config[0].input_dim
|
|
26
|
-
self.output_dim: int = config.layers_config[-1].output_dim
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import itertools
|
|
4
|
-
import os
|
|
5
|
-
from dataclasses import dataclass, replace
|
|
6
|
-
from typing import Union
|
|
7
|
-
|
|
8
|
-
import pandas as pd
|
|
9
|
-
|
|
10
|
-
from .abstract import Model, ModelConfig, DataCreator, Summary
|
|
11
|
-
from .data import Data
|
|
12
|
-
from .plot_losses import plot_losses
|
|
13
|
-
from .utils.dataclasses_utils import get_name, set_value_by_path
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@dataclass(frozen=True)
|
|
17
|
-
class VariationConfig:
|
|
18
|
-
name: str
|
|
19
|
-
param: list[str]
|
|
20
|
-
values: Union[list, list[list]]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@dataclass(frozen=True)
|
|
24
|
-
class ExperimentConfig(Summary):
|
|
25
|
-
name: str
|
|
26
|
-
title: str
|
|
27
|
-
base_model: ModelConfig
|
|
28
|
-
variations: list[VariationConfig]
|
|
29
|
-
data_creator: DataCreator
|
|
30
|
-
plot_dimension: int
|
|
31
|
-
smooth_fraction: float
|
|
32
|
-
|
|
33
|
-
def instantiate(self) -> Experiment:
|
|
34
|
-
return Experiment(self)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class Experiment:
|
|
38
|
-
"""Manages the full lifecycle of a deep learning experiment.
|
|
39
|
-
|
|
40
|
-
It handles data loading, model instantiation, training loops, and results visualization.
|
|
41
|
-
|
|
42
|
-
Attributes:
|
|
43
|
-
name (str): Name of the experiment
|
|
44
|
-
title (str): Title of the graphs
|
|
45
|
-
models (list[Model]): List of models to train/compare
|
|
46
|
-
data (Data): The dataset wrapper.
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
def __init__(self, config: ExperimentConfig):
|
|
50
|
-
self.config = config
|
|
51
|
-
self.name: str = config.name
|
|
52
|
-
self.title: str = config.title
|
|
53
|
-
self.base_model_config: ModelConfig = config.base_model
|
|
54
|
-
self.models: list[Model] = generate_models(config.base_model, config.variations)
|
|
55
|
-
self.data: Data = config.data_creator()
|
|
56
|
-
self.plot_dimension: int = config.plot_dimension
|
|
57
|
-
self.smooth_fraction: float = config.smooth_fraction
|
|
58
|
-
|
|
59
|
-
self.save_dir = os.path.join("experiments", self.name)
|
|
60
|
-
|
|
61
|
-
def launch(self, do_train: bool, do_test: bool, nb_print_train: int, do_plot_train: bool, do_save: bool) -> None:
|
|
62
|
-
"""
|
|
63
|
-
Executes the experiment pipeline.
|
|
64
|
-
|
|
65
|
-
Args:
|
|
66
|
-
do_train (bool): Whether to run the training loop.
|
|
67
|
-
do_test (bool): Whether to run evaluation on test set.
|
|
68
|
-
nb_print_train (int): Number of logs to print during training.
|
|
69
|
-
do_plot_train (bool): If True, plots loss curves after training.
|
|
70
|
-
do_save (bool): if True, saves the plots and dataframes
|
|
71
|
-
"""
|
|
72
|
-
print(f"### Launching Experiment : {self.name} ###")
|
|
73
|
-
if do_train:
|
|
74
|
-
self.train(nb_print=nb_print_train, do_plot=do_plot_train, do_save=do_save)
|
|
75
|
-
if do_test:
|
|
76
|
-
self.test()
|
|
77
|
-
if do_save:
|
|
78
|
-
self.save_df()
|
|
79
|
-
self.save_weights()
|
|
80
|
-
|
|
81
|
-
def train(self, nb_print: int, do_plot: bool, do_save: bool):
|
|
82
|
-
for model in self.models:
|
|
83
|
-
model.train(self.data, nb_print)
|
|
84
|
-
|
|
85
|
-
if do_plot:
|
|
86
|
-
plot_path = None
|
|
87
|
-
if do_save:
|
|
88
|
-
os.makedirs(self.save_dir, exist_ok=True)
|
|
89
|
-
plot_path = os.path.join(self.save_dir, "training_losses.png")
|
|
90
|
-
|
|
91
|
-
plot_losses(dimension=self.plot_dimension,
|
|
92
|
-
models=self.models,
|
|
93
|
-
title=self.title,
|
|
94
|
-
smooth_fraction=self.smooth_fraction,
|
|
95
|
-
save_path=plot_path)
|
|
96
|
-
|
|
97
|
-
def test(self) -> None:
|
|
98
|
-
for model in self.models:
|
|
99
|
-
model.test(self.data)
|
|
100
|
-
|
|
101
|
-
def save_df(self) -> None:
|
|
102
|
-
"""
|
|
103
|
-
saves the models parameters and their args
|
|
104
|
-
"""
|
|
105
|
-
os.makedirs(self.save_dir, exist_ok=True)
|
|
106
|
-
print(f" Saving results to: {self.save_dir}")
|
|
107
|
-
|
|
108
|
-
data = [model.id | {"test_loss": model.test_loss, "test_accuracy": model.test_accuracy}
|
|
109
|
-
for model in self.models]
|
|
110
|
-
|
|
111
|
-
df = pd.DataFrame(data)
|
|
112
|
-
df.to_csv(os.path.join(self.save_dir, "models_accuracy.csv"), index_label="iteration")
|
|
113
|
-
|
|
114
|
-
def save_weights(self) -> None:
|
|
115
|
-
weights_dir = os.path.join(self.save_dir, "models")
|
|
116
|
-
os.makedirs(weights_dir, exist_ok=True)
|
|
117
|
-
print(f" Saving weights to: {weights_dir}")
|
|
118
|
-
|
|
119
|
-
for i, model in enumerate(self.models):
|
|
120
|
-
model_dir = os.path.join(weights_dir, f"{model.get_folder_name()}")
|
|
121
|
-
|
|
122
|
-
weights_path = os.path.join(model_dir, "weights.pkl")
|
|
123
|
-
model.save_weights(weights_path)
|
|
124
|
-
|
|
125
|
-
config_path = os.path.join(model_dir, "config.json")
|
|
126
|
-
model.save_config(config_path)
|
|
127
|
-
|
|
128
|
-
loss_path = os.path.join(model_dir, "training_loss.csv")
|
|
129
|
-
model.save_loss(loss_path)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def generate_models(base_model: ModelConfig, variations: list[VariationConfig]) -> list[Model]:
|
|
133
|
-
models = []
|
|
134
|
-
|
|
135
|
-
values_lists = [v.values for v in variations]
|
|
136
|
-
|
|
137
|
-
for combination in itertools.product(*values_lists):
|
|
138
|
-
id_ = {}
|
|
139
|
-
current_model = base_model
|
|
140
|
-
|
|
141
|
-
for var_config, current_vals in zip(variations, combination):
|
|
142
|
-
|
|
143
|
-
id_[var_config.name] = get_name(current_vals[0])
|
|
144
|
-
|
|
145
|
-
for path, val in zip(var_config.param, current_vals):
|
|
146
|
-
current_model = set_value_by_path(current_model, path, val)
|
|
147
|
-
|
|
148
|
-
current_model = replace(current_model, id=id_)
|
|
149
|
-
models.append(current_model.instantiate())
|
|
150
|
-
|
|
151
|
-
return models
|
|
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
|