Skip to content

Commit 44a5ddf

Browse files
authored
Merge pull request #2443 from NNPDF/load_weights_from_fit
Load weights from a fit
2 parents c63a535 + f4f0a45 commit 44a5ddf

7 files changed

Lines changed: 99 additions & 15 deletions

File tree

doc/sphinx/source/n3fit/runcard_detailed.rst

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Monte Carlo replica data
6363
------------------------
6464
The uncertainties on the PDFs are determined trough a Monte Carlo sampling procedure
6565
where each pseudodata replica is generated by adding some noise to the central value
66-
of the data points.
66+
of the data points.
6767
In order to generate the pseudodata, the user can set the following parameter in the runcard:
6868

6969
.. code-block:: yaml
@@ -74,8 +74,8 @@ The noise is generated by sampling from a normal distribution
7474
that is centred around zero. There are two options for the covariance matrix
7575
of the noise: the experimental covariance matrix or the t0 covariance matrix (default).
7676

77-
In order to not use the t0-covariance matrix in the pseudodata generation, it is enough
78-
to set
77+
In order to not use the t0-covariance matrix in the pseudodata generation, it is enough
78+
to set
7979

8080
.. code-block:: yaml
8181
@@ -396,9 +396,12 @@ Save and load weights of the model
396396
397397
save: "weights.h5"
398398
load: "weights.h5"
399+
load_weights_from_fit: NNPDF40_nnlo_as_01180_qcd
399400
400401
- ``save``: saves the weights of the PDF model in the selected file in the replica folder.
401-
- ``load``: loads the weights of the PDF model from the selected file.
402+
- ``load``: loads the weights of the PDF model from the selected file. Each replica will load the same weights from this file.
403+
- ``load_weights_from_fit``: loads the weights of the PDF model from a previous fit located in the `resultspath` directory.
404+
This requires that the fit contains the saved weights. Each replica loads the weights from the corresponding replica of the specified fit.
402405

403406
Since the weights depend only on the architecture of the Neural Network,
404407
it is possible to save the weights of a Neural Network trained with one set of hyperparameters and experiments

n3fit/src/n3fit/backends/keras_backend/MetaModel.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,17 @@ def set_replica_weights(self, weights, i_replica=0):
395395
layer = self.get_layer(layer_type)
396396
set_layer_replica_weights(layer=layer, weights=weights[layer_type], i_replica=i_replica)
397397

398+
def set_replica_weights_from_file(self, model_file, i_replica=0):
399+
"""
400+
Set the weights of replica i_replica from a model file.
401+
Wrapper around ``set_replica_weights`` that creates a temporary single-replica-model
402+
to get the weights in the appropiate format.
403+
"""
404+
single_replica = self.single_replica_generator(i_replica)
405+
single_replica.load_weights(model_file)
406+
weights = single_replica.get_replica_weights(0)
407+
self.set_replica_weights(weights, i_replica)
408+
398409
def split_replicas(self):
399410
"""
400411
Split the single multi-replica model into a list of separate single replica models,

n3fit/src/n3fit/checks.py

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@
1616

1717
NN_PARAMETERS = ["nodes_per_layer", "optimizer", "activation_per_layer"]
1818
HYPEROPTIMIZED_PARAMETERS = [
19-
"nodes_per_layer" ,
20-
"optimizer",
21-
"learning_rate",
19+
"nodes_per_layer",
20+
"optimizer",
21+
"learning_rate",
2222
"clipnorm",
2323
"activation_per_layer",
2424
"initializer",
2525
"epochs",
2626
"stopping_patience",
2727
"layer_type",
28-
"dropout"
29-
]
28+
"dropout",
29+
]
3030

3131

3232
def _is_floatable(num):
@@ -40,13 +40,17 @@ def _is_floatable(num):
4040
except (ValueError, TypeError):
4141
return False
4242

43+
4344
def check_hyperopt_parameters(parameters, trial_specs):
4445
tmp = []
4546
for param in HYPEROPTIMIZED_PARAMETERS:
4647
if param in parameters.keys():
4748
tmp.append(param)
4849
if tmp:
49-
raise CheckError(f"Parameters {tmp} already contained in the hyperoptimization scan. Please remove them from the parameters namespace.")
50+
raise CheckError(
51+
f"Parameters {tmp} already contained in the hyperoptimization scan. Please remove them from the parameters namespace."
52+
)
53+
5054

5155
# Checks on the NN parameters
5256
def check_existing_parameters(parameters):
@@ -228,11 +232,38 @@ def check_model_file(save, load):
228232
raise CheckError(f"Model file {load} seems to be empty")
229233

230234

235+
def check_load_fits_from_weight_file(load_weights_from_fit, load, load_weights_dict, replicas):
236+
"""Checks whether the load_weights_from_fit option is correctly defined.
237+
And whether the requested replica can be loaded
238+
"""
239+
if load_weights_from_fit is not None:
240+
if load is not None:
241+
raise CheckError(
242+
"Cannot use both `load` and `load_weights_from_fit` options at the same time, please select only one of them"
243+
)
244+
if replicas is not None and load_weights_from_fit is not None:
245+
missing_replicas = set(replicas) - set(load_weights_dict.keys())
246+
if missing_replicas:
247+
raise CheckError(
248+
f"Not all replicas requested have weights to be loaded, missing: {missing_replicas}"
249+
)
250+
251+
231252
@make_argcheck
232-
def wrapper_check_NN(tensorboard, save, load, parameters, trial_specs):
253+
def wrapper_check_NN(
254+
tensorboard,
255+
save,
256+
load,
257+
load_weights_from_fit,
258+
load_weights_dict,
259+
parameters,
260+
trial_specs,
261+
replicas,
262+
):
233263
"""Wrapper function for all NN-related checks"""
234264
check_tensorboard(tensorboard)
235265
check_model_file(save, load)
266+
check_load_fits_from_weight_file(load_weights_from_fit, load, load_weights_dict, replicas)
236267
check_lagrange_multipliers(parameters, "integrability")
237268
check_lagrange_multipliers(parameters, "positivity")
238269
if trial_specs:
@@ -489,7 +520,7 @@ def check_deprecated_options(fitting, parameters):
489520
raise CheckError(
490521
"`interpolation_points` no longer accepted, please change to `feature_scaling_points`"
491522
)
492-
523+
493524

494525
@make_argcheck
495526
def check_photonQED_exists(theoryid, fiatlux):

n3fit/src/n3fit/model_trainer.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@
2727
from n3fit.stopping import Stopping
2828
from n3fit.vpinterface import N3PDF, compute_hyperopt_metrics
2929
from validphys.core import DataGroupSpec
30+
from validphys.loader import Loader
3031
from validphys.photon.compute import Photon
3132

3233
log = logging.getLogger(__name__)
33-
34+
l = Loader()
3435
# Threshold defaults
3536
# Any partition with a chi2 over the threshold will discard its hyperparameters
3637
HYPER_THRESHOLD = 50.0
@@ -113,6 +114,7 @@ def __init__(
113114
lux_params=None,
114115
replicas=None,
115116
trials=None,
117+
load_weights_dict=None,
116118
):
117119
"""
118120
Parameters
@@ -179,6 +181,7 @@ def __init__(
179181
else:
180182
self.max_cores = max_cores
181183
self.model_file = model_file
184+
self.load_weights_dict = load_weights_dict
182185
self.print_summary = True
183186
self.mode_hyperopt = False
184187
self.impose_sumrule = sum_rules
@@ -987,12 +990,20 @@ def hyperparametrizable(self, params):
987990
# together with pdf model generated above
988991
models = self._model_generation(xinput, pdf_model, partition, k)
989992

990-
# Only after model generation, apply possible weight file
991-
# Starting every replica with the same weights
993+
# After model generation, apply possible weights files.
994+
# The possibilities are a single model file (`load:`)
995+
# so that every replica starts with the same weights
996+
# or a weight per replica from `load_weights_from_fit`
992997
if self.model_file:
993998
log.info("Applying model file %s", self.model_file)
994999
pdf_model.load_identical_replicas(self.model_file)
9951000

1001+
if self.load_weights_dict:
1002+
for replica in self.replicas:
1003+
weights_path = self.load_weights_dict[replica]
1004+
log.info("Loading weights from path: " + str(weights_path))
1005+
pdf_model.set_replica_weights_from_file(weights_path)
1006+
9961007
if k > 0:
9971008
# Reset the positivity and integrability multipliers
9981009
pos_and_int = self.training["posdatasets"] + self.training["integdatasets"]

n3fit/src/n3fit/n3fit_checks_provider.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@ def n3fit_checks_action(
2323
parameters,
2424
save=None,
2525
load=None,
26+
load_weights_from_fit=None,
27+
load_weights_dict,
2628
hyperscan_config=None,
2729
hyperopt=None,
2830
kfold=None,
2931
tensorboard=None,
3032
parallel_models=True,
3133
double_precision=False,
3234
trial_specs=None,
35+
replicas=None,
3336
):
3437
return
3538

n3fit/src/n3fit/performfit.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def performfit(
2828
basis,
2929
fitbasis,
3030
positivity_bound,
31+
load_weights_dict,
3132
sum_rules=True,
3233
parameters,
3334
replica_path,
@@ -201,6 +202,7 @@ def performfit(
201202
lux_params=fiatlux,
202203
replicas=replica_idxs,
203204
trials=trials,
205+
load_weights_dict=load_weights_dict
204206
)
205207

206208
# This is just to give a descriptive name to the fit function

validphys2/src/validphys/config.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,29 @@ def parse_pdf(self, name, unpolarized_bc=None):
165165

166166
return pdf
167167

168+
def produce_load_weights_dict(self, load_weights_from_fit=None):
169+
if load_weights_from_fit is None:
170+
return None
171+
try:
172+
fit_object = self.loader.check_fit(load_weights_from_fit)
173+
fit_folder = fit_object.path / "nnfit"
174+
weights_name = fit_object.as_input().get("save")
175+
if weights_name is None:
176+
raise LoadFailedError(f"{load_weights_from_fit} does not have saved weights")
177+
# Correct for extension already included
178+
if weights_name.endswith(".h5"):
179+
weights_name = weights_name[:-3]
180+
weights_dict = {}
181+
for p in fit_folder.glob(f"replica_*/{weights_name}.weights.h5"):
182+
replica_folder = p.parent.name
183+
replica_index = int(replica_folder.split("_")[1])
184+
weights_dict[replica_index] = p
185+
if not weights_dict:
186+
raise LoadFailedError(f"No weights found for: {load_weights_from_fit}")
187+
return weights_dict
188+
except LoadFailedError as e:
189+
raise ConfigError(str(e), load_weights_from_fit, self.loader.available_fits) from e
190+
168191
@element_of("unpolarized_bcs")
169192
@_id_with_label
170193
def parse_unpolarized_bc(self, name):

0 commit comments

Comments
 (0)