Skip to content

Commit 6fa6d32

Browse files
committed
finalize refactoring
1 parent f100e16 commit 6fa6d32

9 files changed

Lines changed: 146 additions & 109 deletions

File tree

n3fit/src/n3fit/hyper_optimization/penalties.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
New penalties can be added directly in this module.
1919
The name in the runcard must match the name used in this module.
2020
"""
21+
2122
import numpy as np
2223

2324
from n3fit.vpinterface import N3PDF, integrability_numbers
@@ -48,11 +49,11 @@ def saturation(pdf_model=None, n=100, min_x=1e-6, max_x=1e-4, flavors=None, **_k
4849
Example
4950
-------
5051
>>> from n3fit.hyper_optimization.penalties import saturation
51-
>>> from n3fit.model_gen import pdfNN_layer_generator
52+
>>> from n3fit.model_gen import generate_pdf_model
5253
>>> fake_fl = [{'fl' : i, 'largex' : [0,1], 'smallx': [1,2]} for i in ['u', 'ubar', 'd', 'dbar', 'c', 'g', 's', 'sbar']]
53-
>>> pdf_model = pdfNN_layer_generator(nodes=[8], activations=['linear'], seed=0, flav_info=fake_fl, fitbasis="FLAVOUR")
54-
>>> isinstance(saturation(pdf_model, 5), float)
55-
True
54+
>>> pdf_model = generate_pdf_model(nodes=[8], activations=['linear'], seed_list=[0], flav_info=fake_fl, fitbasis="FLAVOUR")
55+
>>> saturation(pdf_model, 5), float)
56+
array([0.0038])
5657
5758
"""
5859
if flavors is None:
@@ -128,11 +129,11 @@ def integrability(pdf_model=None, **_kwargs):
128129
Example
129130
-------
130131
>>> from n3fit.hyper_optimization.penalties import integrability
131-
>>> from n3fit.model_gen import pdfNN_layer_generator
132+
>>> from n3fit.model_gen import generate_pdf_model
132133
>>> fake_fl = [{'fl' : i, 'largex' : [0,1], 'smallx': [1,2]} for i in ['u', 'ubar', 'd', 'dbar', 'c', 'g', 's', 'sbar']]
133-
>>> pdf_model = pdfNN_layer_generator(nodes=[8], activations=['linear'], seed=0, flav_info=fake_fl, fitbasis="FLAVOUR")
134-
>>> isinstance(integrability(pdf_model), float)
135-
True
134+
>>> pdf_model = generate_pdf_model(nodes=[8], activations=['linear'], seed_list=[0], flav_info=fake_fl, fitbasis="FLAVOUR")
135+
>>> integrability(pdf_model)
136+
5.184705528587072e+21
136137
137138
"""
138139
pdf_instance = N3PDF(pdf_model.split_replicas())

n3fit/src/n3fit/hyper_optimization/rewards.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ def compute_loss(
213213
>>> ds = Loader().check_dataset("NMC_NC_NOTFIXED_P_EM-SIGMARED", variant="legacy", theoryid=399, cuts="internal")
214214
>>> experimental_data = [Loader().check_experiment("My DataGroupSpec", [ds])]
215215
>>> fake_fl = [{'fl' : i, 'largex' : [0,1], 'smallx': [1,2]} for i in ['u', 'ubar', 'd', 'dbar', 'c', 'g', 's', 'sbar']]
216-
>>> pdf_model = generate_pdf_model(nodes=[8], activations=['linear'], seed=0, num_replicas=2, flav_info=fake_fl, fitbasis="FLAVOUR")
216+
>>> pdf_model = generate_pdf_model(nodes=[8], activations=['linear'], seed=[0,2], flav_info=fake_fl, fitbasis="FLAVOUR")
217217
>>> pdf = N3PDF(pdf_model.split_replicas())
218218
>>> loss = hyper.compute_loss(penalties, experimental_loss, pdf, experimental_data)
219219
"""

n3fit/src/n3fit/model_gen.py

Lines changed: 79 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ def generate_pdf_model(
420420
[ list of a separate architecture per replica ]
421421
which can be, but is not necessary, equal for all replicas
422422
423-
<preprocessing factors>
423+
[ <preprocessing factors> ]
424424
postprocessing of the network output by a variation x^{alpha}*(1-x)^{beta}
425425
426426
<normalization>
@@ -474,29 +474,18 @@ def generate_pdf_model(
474474
Function to apply to the input. If given the input to the model
475475
will be a (1, None, 2) tensor where dim [:,:,0] is scaled
476476
When None, instead turn the x point into a (x, log(x)) pair
477-
num_replicas: int
478-
How many models should be trained in parallel.
479477
photons: :py:class:`validphys.photon.compute.Photon`
480478
If given, gives the AddPhoton layer a function to compute a photon which will be added at the
481479
index 0 of the 14-size FK basis
482480
This same function will also be used to compute the MSR component for the photon
483481
484-
485-
486482
Returns
487483
-------
488484
pdf_model: MetaModel
489485
pdf model, with `single_replica_generator` attached as an attribute
490486
"""
491-
if len(seed_list) != num_replicas:
492-
# TODO: remove this error, remove the num_replicas argument
493-
raise ValueError("This should not happen")
494-
495-
num_replicas = len(seed_list)
496-
497487
# Separate the settings which may be different for each replica
498488
# from those that are guaranteed to be equal for all replicas
499-
500489
all_replicas = []
501490
for seed in seed_list:
502491
tmp = _ReplicaSettings(
@@ -649,44 +638,44 @@ def _pdfNN_layer_generator(
649638
pdf_model: n3fit.backends.MetaModel
650639
a model f(x) = y where x is a tensor (1, xgrid, 1) and y a tensor (1, replicas, xgrid, out)
651640
"""
652-
# TODO: at the moment nothing changes, just the signature of the function
653-
seed = [i.seed for i in replicas_settings]
641+
all_seed = [i.seed for i in replicas_settings]
654642
num_replicas = len(replicas_settings)
655643

656644
if impose_sumrule is None:
657645
impose_sumrule = "All"
658646

659-
# Process input options. There are 2 options:
660-
# 1. Scale the input
661-
# 2. Concatenate log(x) to the input
662-
use_feature_scaling = scaler is not None
663-
664-
# When scaler is active we also want to do the subtraction of large x
665-
# TODO: make it its own option (i.e., one could want to use this without using scaler)
666-
subtract_one = use_feature_scaling
647+
## Process the input data (x grid)
648+
# There a currently two options:
649+
# 1. Append log(x) to the input
650+
# 2. Scale the input
651+
do_nothing = lambda x: x
652+
model_input = {}
667653

668-
# Feature scaling happens before the pdf model and changes x->(scaler(x), x),
669-
# so it adds an input dimension
670-
pdf_input_dimensions = 2 if use_feature_scaling else 1
671-
# Adding of logs happens inside, but before the NN and adds a dimension there
672-
nn_input_dimensions = 1 if use_feature_scaling else 2
654+
if scaler is None: # add log(x)
655+
use_feature_scaling = subtract_one = False
656+
# The PDF itself receives only x
657+
pdf_input_dimensions = 1
658+
# But the NN will see (x, log(x))
659+
nn_input_dimensions = 2
673660

674-
# Define the main input
675-
do_nothing = lambda x: x
676-
if use_feature_scaling:
677-
pdf_input = Input(shape=(None, pdf_input_dimensions), batch_size=1, name="scaledx_x")
678-
process_input = do_nothing
679-
extract_nn_input = Lambda(lambda x: op.op_gather_keep_dims(x, 0, axis=-1), name="x_scaled")
680-
extract_original = Lambda(lambda x: op.op_gather_keep_dims(x, 1, axis=-1), name="pdf_input")
681-
else: # add log(x)
682661
pdf_input = Input(shape=(None, pdf_input_dimensions), batch_size=1, name="pdf_input")
683662
process_input = Lambda(lambda x: op.concatenate([x, op.op_log(x)], axis=-1), name="x_logx")
684663
extract_original = do_nothing
685664
extract_nn_input = do_nothing
665+
else:
666+
use_feature_scaling = subtract_one = True
667+
# The NN will only receive x
668+
nn_input_dimensions = 1
669+
# But the PDF itself will receive both (x, scaler(x))
670+
pdf_input_dimensions = 2
686671

687-
model_input = {"pdf_input": pdf_input}
672+
pdf_input = Input(shape=(None, pdf_input_dimensions), batch_size=1, name="scaledx_x")
673+
process_input = do_nothing
674+
extract_nn_input = Lambda(lambda x: op.op_gather_keep_dims(x, 0, axis=-1), name="x_scaled")
675+
extract_original = Lambda(lambda x: op.op_gather_keep_dims(x, 1, axis=-1), name="pdf_input")
688676

689677
if subtract_one:
678+
# TODO: make it its own option, even though now it only activates in the scaler if above
690679
input_x_eq_1 = [1.0]
691680
if use_feature_scaling:
692681
input_x_eq_1 = scaler(input_x_eq_1)[0]
@@ -695,37 +684,9 @@ def _pdfNN_layer_generator(
695684
layer_x_eq_1 = op.numpy_to_input(np.array(input_x_eq_1).reshape(1, 1), name="x_eq_1")
696685
model_input["layer_x_eq_1"] = layer_x_eq_1
697686

698-
# the layer that multiplies the NN output by the preprocessing factor
699-
apply_preprocessing_factor = Lambda(op.op_multiply, name="prefactor_times_NN")
700-
701-
# Photon layer
702-
layer_photon = AddPhoton(photons=photons, name="add_photon")
703-
704-
# Basis rotation
705-
basis_rotation = FlavourToEvolution(
706-
flav_info=flav_info, fitbasis=fitbasis, name="pdf_evolution_basis"
707-
)
708-
709-
# Evolution layer
710-
layer_evln = FkRotation(output_dim=out, name="pdf_FK_basis")
711-
712-
# Normalization and sum rules
713-
if impose_sumrule:
714-
sumrule_layer, integrator_input = generate_msr_model_and_grid(
715-
fitbasis=fitbasis, mode=impose_sumrule, scaler=scaler, replica_seeds=seed
716-
)
717-
model_input["xgrid_integration"] = integrator_input
718-
else:
719-
sumrule_layer = lambda x: x
720-
721-
compute_preprocessing_factor = Preprocessing(
722-
flav_info=flav_info,
723-
name=PREPROCESSING_LAYER_ALL_REPLICAS,
724-
replica_seeds=seed,
725-
large_x=not subtract_one,
726-
)
687+
model_input["pdf_input"] = pdf_input
727688

728-
# Create the actual NeuralNetwork PDF
689+
## Create the actual NeuralNetwork PDF
729690
# loop over the settings for all replicas and generate a list of NN per replica
730691
# which will be then stack together and built into a single (input -> output) MetaModel
731692
# all PDFs _must_ share the same input layer
@@ -741,7 +702,21 @@ def _pdfNN_layer_generator(
741702
nn_pdfs = Lambda(lambda nns: op.stack(nns, axis=1), name=f"stack_replicas")(list_of_nn_pdfs)
742703
nn_replicas = MetaModel({'NN_input': x_input}, nn_pdfs, name=NN_LAYER_ALL_REPLICAS)
743704

744-
# The NN subtracted by NN(1), if applicable
705+
## Preprocessing factors:
706+
# the layer that multiplies the NN output by the preprocessing factor
707+
# This includes
708+
# - x^{a}(1-x)^{b}
709+
# - NN(x) - N(1.0)
710+
apply_preprocessing_factor = Lambda(op.op_multiply, name="prefactor_times_NN")
711+
712+
compute_preprocessing_factor = Preprocessing(
713+
flav_info=flav_info,
714+
name=PREPROCESSING_LAYER_ALL_REPLICAS,
715+
replica_seeds=all_seed,
716+
large_x=not subtract_one,
717+
)
718+
719+
# The NN subtracted by NN(1), if applicable, otherwise do nothing
745720
def nn_subtracted(x):
746721
NNs_x = nn_replicas(x)
747722

@@ -752,6 +727,21 @@ def nn_subtracted(x):
752727

753728
return NNs_x
754729

730+
## Unnormalized PDF
731+
# updf_r(x) = FkRotation( NN_r(input(x)) * preprocessing_layer_r(x) )
732+
# with _r: replica index
733+
# input: whatever processing is applied to the input
734+
# The preprocessing_layer and weights is specific to each replica
735+
# The final PDF will be in the 14 flavours evolution basis used in the FkTables
736+
737+
# Basis rotation
738+
basis_rotation = FlavourToEvolution(
739+
flav_info=flav_info, fitbasis=fitbasis, name="pdf_evolution_basis"
740+
)
741+
742+
# Evolution layer
743+
layer_evln = FkRotation(output_dim=out, name="pdf_FK_basis")
744+
755745
def compute_unnormalized_pdf(x):
756746
# Preprocess the input grid
757747
x_nn_input = extract_nn_input(x)
@@ -767,28 +757,39 @@ def compute_unnormalized_pdf(x):
767757
# Apply the preprocessing factor
768758
pref_NNs_x = apply_preprocessing_factor([preprocessing_factors_x, NNs_x])
769759

770-
# Apply basis rotation if needed
760+
# Transform to FK basis, this is the full evolution basis
761+
# Rotate to the 9f evolution basis first before expanding up to 14f
762+
# TODO: make these two steps into one
771763
if not basis_rotation.is_identity():
772764
pref_NNs_x = basis_rotation(pref_NNs_x)
773-
774-
# Transform to FK basis
775765
PDFs_unnormalized = layer_evln(pref_NNs_x)
776766

777767
return PDFs_unnormalized
778768

779769
PDFs_unnormalized = compute_unnormalized_pdf(pdf_input)
780770

771+
## Normalization and sum rules, produces normalized PDF
772+
# pdf_r(x) = updf_r(x) * Normalization(updf_r(integration_xgrid))
773+
# The normalization layer is shared across replicas (but evaluated at each replica separately)
774+
#
781775
if impose_sumrule:
776+
sumrule_layer, integrator_input = generate_msr_model_and_grid(
777+
fitbasis=fitbasis, mode=impose_sumrule, scaler=scaler, replica_seeds=all_seed
778+
)
779+
model_input["xgrid_integration"] = integrator_input
780+
781+
# We need a second unnormalized PDF evaluated on the integrated grid
782782
PDFs_integration_grid = compute_unnormalized_pdf(integrator_input)
783783

784+
# Photon contribution to the sum rule
784785
if photons:
785786
# add batch and flavor dimensions
786787
ph_tensor = op.numpy_to_tensor(photons.integral)
787788
photon_integrals = op.batchit(op.batchit(ph_tensor))
788789
else:
789790
photon_integrals = op.numpy_to_tensor(np.zeros((1, num_replicas, 1)))
790791

791-
PDFs_normalized = sumrule_layer(
792+
PDFs = sumrule_layer(
792793
{
793794
"pdf_x": PDFs_unnormalized,
794795
"pdf_xgrid_integration": PDFs_integration_grid,
@@ -797,18 +798,21 @@ def compute_unnormalized_pdf(x):
797798
"photon_integral": photon_integrals,
798799
}
799800
)
800-
PDFs = PDFs_normalized
801801
else:
802802
PDFs = PDFs_unnormalized
803+
sumrule_layer = lambda x: x
803804

805+
## Include the photon in the PDF for QED-enabled fits
806+
# (by default the entry corresponding to the photon is set to 0)
804807
if photons:
808+
layer_photon = AddPhoton(photons=photons, name="add_photon")
805809
PDFs = layer_photon(PDFs)
806810

811+
# Return a PDF without a replica axis, to extract single replicas from an ensemble
807812
if not replica_axis:
808813
PDFs = Lambda(lambda pdfs: pdfs[:, 0], name="remove_replica_axis")(PDFs)
809814

810-
pdf_model = MetaModel(model_input, PDFs, name=f"PDFs", scaler=scaler)
811-
return pdf_model
815+
return MetaModel(model_input, PDFs, name=f"PDFs", scaler=scaler)
812816

813817

814818
# TODO: is there a way of keeping sincronized the input of this function and _ReplicaSettings
@@ -899,7 +903,7 @@ def layer_generator(i_layer, nodes_out, activation):
899903
previous_layer = layer(previous_layer)
900904

901905
# Add dropout if any to the second to last layer
902-
if dropout_rate > 0 and layer_idx == (len(hidden_layers) - 1):
906+
if dropout_rate > 0 and layer_idx == (len(hidden_layers) - 2):
903907
dropout_l = base_layer_selector("dropout", rate=dropout_rate)
904908
previous_layer = dropout_l(previous_layer)
905909

n3fit/src/n3fit/model_trainer.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,6 @@ def _generate_pdf(
712712
regularizer_args=regularizer_args,
713713
impose_sumrule=self.impose_sumrule,
714714
scaler=self._scaler,
715-
num_replicas=len(self.replicas),
716715
photons=photons,
717716
)
718717
return pdf_model

n3fit/src/n3fit/tests/test_hyperopt.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,14 @@
1919
from validphys.loader import Loader
2020

2121

22-
def generate_pdf(seed, num_replicas):
22+
def generate_pdf(seeds):
2323
"""Generate generic pdf model."""
2424
fake_fl = [
2525
{"fl": i, "largex": [0, 1], "smallx": [1, 2]}
2626
for i in ["u", "ubar", "d", "dbar", "c", "g", "s", "sbar"]
2727
]
2828
pdf_model = generate_pdf_model(
29-
nodes=[8],
30-
activations=["linear"],
31-
seed_list=seed,
32-
num_replicas=num_replicas,
33-
flav_info=fake_fl,
34-
fitbasis="FLAVOUR",
29+
nodes=[8], activations=["linear"], seed_list=seeds, flav_info=fake_fl, fitbasis="FLAVOUR"
3530
)
3631
return pdf_model
3732

@@ -62,7 +57,7 @@ def test_compute_per_fold_loss(loss_type, replica_statistic, expected_per_fold_l
6257
This example assumes a 2 replica calculation with 3 added penalties.
6358
"""
6459
# generate 2 replica pdf model
65-
pdf_model = generate_pdf(seed=[1, 2], num_replicas=2)
60+
pdf_model = generate_pdf(seeds=[1, 2])
6661
# add 3 penalties for a 2 replica model
6762
penalties = {
6863
'saturation': np.array([0.0, 0.0]),

0 commit comments

Comments
 (0)