From ae7fbc32538b5dfcaa0a5534571703674fd77500 Mon Sep 17 00:00:00 2001 From: cmadrid1 Date: Tue, 5 Dec 2023 15:04:59 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20from=20@=205188305f1245a8fecbb1677a?= =?UTF-8?q?80ecef107771cea6=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 404.html | 2 +- general_advice/after/after.html | 2 +- general_advice/before/domains.html | 2 +- general_advice/before/features.html | 2 +- general_advice/before/inputs.html | 2 +- general_advice/before/metrics.html | 2 +- general_advice/before/model.html | 2 +- general_advice/during/opt.html | 2 +- general_advice/during/overfitting.html | 2 +- general_advice/during/xvalidation.html | 2 +- general_advice/intro.html | 2 +- images/BDTscores_EXO19020.png | Bin 0 -> 375991 bytes images/DisCoPresentation_MLForum.png | Bin 0 -> 125562 bytes images/ML_Forum_talk_May8_2019.png | Bin 0 -> 219770 bytes images/doublediscoNN.png | Bin 0 -> 256934 bytes images/hig21002_bdtscores.png | Bin 0 -> 333803 bytes index.html | 2 +- inference/checklist.html | 2 +- inference/conifer.html | 2 +- inference/hls4ml.html | 2 +- inference/onnx.html | 2 +- inference/particlenet.html | 2 +- inference/performance.html | 2 +- inference/pyg.html | 2 +- inference/pytorch.html | 2 +- inference/sonic_triton.html | 2 +- inference/standalone.html | 2 +- inference/swan_aws.html | 2 +- inference/tensorflow1.html | 2 +- inference/tensorflow2.html | 2 +- inference/tfaas.html | 2 +- inference/xgboost.html | 2 +- innovation/hackathons.html | 2 +- innovation/journal_club.html | 2 +- optimization/data_augmentation.html | 2 +- optimization/importance.html | 2 +- optimization/model_optimization.html | 2 +- resources/cloud_resources/index.html | 2 +- resources/dataset_resources/index.html | 2 +- resources/fpga_resources/index.html | 2 +- .../cms_resources/lxplus_gpu.html | 2 +- .../cms_resources/lxplus_htcondor.html | 2 +- .../cms_resources/ml_cern_ch.html | 2 +- .../cms_resources/notebooks/pytorch_mnist.html | 2 +- .../notebooks/toptagging_mlp.html | 2 +- .../gpu_resources/cms_resources/swan.html | 2 +- search/search_index.json | 2 +- sitemap.xml | 5 +++++ software_envs/containers.html | 2 +- software_envs/lcg_environments.html | 2 +- training/Decorrelation.html | 1 + training/MLaaS4HEP.html | 2 +- training/autoencoders.html | 2 +- 53 files changed, 52 insertions(+), 46 deletions(-) create mode 100644 images/BDTscores_EXO19020.png create mode 100644 images/DisCoPresentation_MLForum.png create mode 100644 images/ML_Forum_talk_May8_2019.png create mode 100644 images/doublediscoNN.png create mode 100644 images/hig21002_bdtscores.png create mode 100644 training/Decorrelation.html diff --git a/404.html b/404.html index b5098ff..b6dc7a0 100644 --- a/404.html +++ b/404.html @@ -1 +1 @@ - CMS Machine Learning Documentation

404 - Not found

\ No newline at end of file + CMS Machine Learning Documentation

404 - Not found

\ No newline at end of file diff --git a/general_advice/after/after.html b/general_advice/after/after.html index 185d52a..10486e1 100644 --- a/general_advice/after/after.html +++ b/general_advice/after/after.html @@ -1 +1 @@ - After training - CMS Machine Learning Documentation
Skip to content

After training

After the necessary steps to design the ML experiment has been made, the training has been performed and verified to be stable and consistent, there are still a few things to be checked to further solidify the confidence in the model performance.

Final evaluation

Before the training, initial data set is to be split into the train and test parts, where the former is used to train the model (possibly, with cross-validation), while the latter remains blinded. Once all the optimisations to the model architecture have been made and the model is "frozen", one proceeds to the evaluation of the metrics' values on the test set. This would be the very last check of the model for overfitting and in case there is none, one expects to see little or no difference comparing to the values on (cross)validation set used throughout the training. In turn, any discrepancies could point to possible overfitting happening in the training stage (or also possibly data leakage), which requires further investigation.

The next step to check is the output score of the model (probability1) for each class. It can be done, for example, in the form of a TMVA-like overtraining check (see Figure 1) which also allows to spot overtraining:

Figure 1. Comparison of model output for signal and background classes overlaid for train and test data sets. [source: root-forum.cern.ch]

In general, what is important to look at is that in the category for class C (defined as argmax(score_i)), the score for a class C peaks at values closer to 1. Whereas the other classes doesn't have such property with peaking on the left side of 1 and smoothly falling down to zero as the model score in the category approaches 1. Or, in other words, that the distributions of the model score for various classes are not overlapping and are as far apart as possible. This would be an indication that the model indeed distinguishes between the classes.

Another thing to look at is the data/simulation agreement for class categories. Since it is the output of the model for each category which is used in further statistical inference step, it is important to verify that data/simulation agreement of input features is properly propagated through the model into categories' distribution. This can be achieved by producing the plot similar to the one shown on Figure 2: the stacked templates for backround processes are fitted and compared with the actual predictions for the data for the set of events classified to be in the given category (jet-fakes in the example). If the output data/simulation agreement is worse than the input one, it might point to an existing bias of the model in the way it treats data and simulation events.

Figure 2. Postfit jet-fake NN score for the mutau channel. Note that the distribution for jet-fakes class is dominant in this category and also peaks at value 1 (mind the log scale), which is an indication of good identification of this background process by the model. Furthermore, ratio of data and MC templates is equal to 1 within uncertainties. [source: CMS-PAS-HIG-20-006]

Robustness

Once there is high confidence that the model isn't overtrained and no distortion in the input feature data/MC agreement is introduced, one can consider studying the robustness of the model to the parameter/input variations. Effectively, the model can be considered as a "point estimate", and any variations are helpful to understand the variance of the model outputs - hence, the model's robustness to changes.

A simple example would be a hyperparameter optimisation, where various model parameters a varied to find the best one in terms of performance. Moreover, in HEP there is a helpful (for this particular case) notion of systematic uncertainties, which is a perfect tool to study model robustness to input data variations.

Since in any case they need to be incorporated into the final statistical fit (to be performed on some interpretation of the model score), it implies that these uncertainties need to be "propagated" through the model. A sizeable fraction of those uncertainties are so-called "up/down" (or shape) variations, and therefore it is a good opportunity to study, how the model output responds to those up/down input feature changes. If there is a high sensitivity observed, one need to consider removing the most influencing feature from the training, or trying decorrelation techniques to decrease the impact of systematic-affected feature on the model output.

Systematic biases

Lastly, possible systematic biases arising the ML approach should be estimated. Being a broad and not fully formalised topic, a few examples will be given below to outline the possible sources of those.

  • The first one could be a domain shift, that is the situation where the model is trained on one data domain, but is apllied to a different one (e.g. trained on simulated data, applied on real one). In order to account for that, corresponding scale factor corrections are traditionally derived, and those will come with some uncertainty as well.
  • Another example would be the case of undertraining. Consider the case of fitting a complex polynomial data with a simple linear function. In that case, the model has high bias (and low variance) which results in a systematic shift of its prediction to be taken into account.
  • Care needs to be taken in cases where a cut is applied on the model output. Cuts might potentially introduce shifts and in case of the model score, which is a variable with a complex and non-linear relationship with input features, it might create undesirable biases. For example, in case of cutting on the output score and looking at the invariant mass distribution (e.g. of two jets), one can observe an effect which is known as mass sculpting (see Figure 3). In that case, the background distribution peaks at the mass of the signal resonance used as a signal in the classification task. After applying such cut, signal and background shapes overlap and become very similar, which dillutes the discrimination power between two hypotheses if invariant mass was to be used as the observable to be fitted.
Figure 3. Left: Distributions of signal and background events without selection. Right: Background distributions at 50% signal efficiency (true positive rate) for different classifiers. The unconstrained classifier sculpts a peak at the W-boson mass, while other classifiers do not. [source: arXiv:2010.09745]

  1. Here it is assumed that it can be treated as probability to be assigned to a given class. This is mostly the case if there is a sigmoid/softmax used on the output layer of the neural network and the model is trained with a cross-entropy loss function. 


Last update: December 5, 2023
\ No newline at end of file + After training - CMS Machine Learning Documentation
Skip to content

After training

After the necessary steps to design the ML experiment has been made, the training has been performed and verified to be stable and consistent, there are still a few things to be checked to further solidify the confidence in the model performance.

Final evaluation

Before the training, initial data set is to be split into the train and test parts, where the former is used to train the model (possibly, with cross-validation), while the latter remains blinded. Once all the optimisations to the model architecture have been made and the model is "frozen", one proceeds to the evaluation of the metrics' values on the test set. This would be the very last check of the model for overfitting and in case there is none, one expects to see little or no difference comparing to the values on (cross)validation set used throughout the training. In turn, any discrepancies could point to possible overfitting happening in the training stage (or also possibly data leakage), which requires further investigation.

The next step to check is the output score of the model (probability1) for each class. It can be done, for example, in the form of a TMVA-like overtraining check (see Figure 1) which also allows to spot overtraining:

Figure 1. Comparison of model output for signal and background classes overlaid for train and test data sets. [source: root-forum.cern.ch]

In general, what is important to look at is that in the category for class C (defined as argmax(score_i)), the score for a class C peaks at values closer to 1. Whereas the other classes doesn't have such property with peaking on the left side of 1 and smoothly falling down to zero as the model score in the category approaches 1. Or, in other words, that the distributions of the model score for various classes are not overlapping and are as far apart as possible. This would be an indication that the model indeed distinguishes between the classes.

Another thing to look at is the data/simulation agreement for class categories. Since it is the output of the model for each category which is used in further statistical inference step, it is important to verify that data/simulation agreement of input features is properly propagated through the model into categories' distribution. This can be achieved by producing the plot similar to the one shown on Figure 2: the stacked templates for backround processes are fitted and compared with the actual predictions for the data for the set of events classified to be in the given category (jet-fakes in the example). If the output data/simulation agreement is worse than the input one, it might point to an existing bias of the model in the way it treats data and simulation events.

Figure 2. Postfit jet-fake NN score for the mutau channel. Note that the distribution for jet-fakes class is dominant in this category and also peaks at value 1 (mind the log scale), which is an indication of good identification of this background process by the model. Furthermore, ratio of data and MC templates is equal to 1 within uncertainties. [source: CMS-PAS-HIG-20-006]

Robustness

Once there is high confidence that the model isn't overtrained and no distortion in the input feature data/MC agreement is introduced, one can consider studying the robustness of the model to the parameter/input variations. Effectively, the model can be considered as a "point estimate", and any variations are helpful to understand the variance of the model outputs - hence, the model's robustness to changes.

A simple example would be a hyperparameter optimisation, where various model parameters a varied to find the best one in terms of performance. Moreover, in HEP there is a helpful (for this particular case) notion of systematic uncertainties, which is a perfect tool to study model robustness to input data variations.

Since in any case they need to be incorporated into the final statistical fit (to be performed on some interpretation of the model score), it implies that these uncertainties need to be "propagated" through the model. A sizeable fraction of those uncertainties are so-called "up/down" (or shape) variations, and therefore it is a good opportunity to study, how the model output responds to those up/down input feature changes. If there is a high sensitivity observed, one need to consider removing the most influencing feature from the training, or trying decorrelation techniques to decrease the impact of systematic-affected feature on the model output.

Systematic biases

Lastly, possible systematic biases arising the ML approach should be estimated. Being a broad and not fully formalised topic, a few examples will be given below to outline the possible sources of those.

  • The first one could be a domain shift, that is the situation where the model is trained on one data domain, but is apllied to a different one (e.g. trained on simulated data, applied on real one). In order to account for that, corresponding scale factor corrections are traditionally derived, and those will come with some uncertainty as well.
  • Another example would be the case of undertraining. Consider the case of fitting a complex polynomial data with a simple linear function. In that case, the model has high bias (and low variance) which results in a systematic shift of its prediction to be taken into account.
  • Care needs to be taken in cases where a cut is applied on the model output. Cuts might potentially introduce shifts and in case of the model score, which is a variable with a complex and non-linear relationship with input features, it might create undesirable biases. For example, in case of cutting on the output score and looking at the invariant mass distribution (e.g. of two jets), one can observe an effect which is known as mass sculpting (see Figure 3). In that case, the background distribution peaks at the mass of the signal resonance used as a signal in the classification task. After applying such cut, signal and background shapes overlap and become very similar, which dillutes the discrimination power between two hypotheses if invariant mass was to be used as the observable to be fitted.
Figure 3. Left: Distributions of signal and background events without selection. Right: Background distributions at 50% signal efficiency (true positive rate) for different classifiers. The unconstrained classifier sculpts a peak at the W-boson mass, while other classifiers do not. [source: arXiv:2010.09745]

  1. Here it is assumed that it can be treated as probability to be assigned to a given class. This is mostly the case if there is a sigmoid/softmax used on the output layer of the neural network and the model is trained with a cross-entropy loss function. 


Last update: December 5, 2023
\ No newline at end of file diff --git a/general_advice/before/domains.html b/general_advice/before/domains.html index 3319f51..43183b7 100644 --- a/general_advice/before/domains.html +++ b/general_advice/before/domains.html @@ -1 +1 @@ - Domains - CMS Machine Learning Documentation
Skip to content

Domains

Data plays a crucial role in the process of training any ML model. It is something from which the model learns to solve a given task and therefore care needs to be taken with its handling. There are two main considerations when collecting and preparing data for an ML task:

  1. The data set should be relevant to the problem and should represent the underlying structure of the problem without containing potential biases and irrelevant deviations (e.g. MC simulation artefacts).
  2. A proper preprocessing of the data set should be performed so that the training step goes smoothly.

In this section a general domain perspective on data will be covered. In the following sections a more granular look will be taken from the side of features and construction of inputs to the model.

Coverage

To begin with, one needs to bear in mind that training data should be as close as possible to data they expect to have in the context of analysis. Speaking in more formal terms,

Domains of training (used to train the model) and inference (used to make final predictions) data sets should not sizeably diverge.

Examples
  • In most of the cases the model is usually trained on MC simulated data and later on applied to data to produce predictions which are then passed on to statistical inference step. MC simulation isn't perfect and therefore there are always differences between simulation and data domains. This can lead to the cases when model learns simulation artefacts which come e.g. from detector response mismodelling. Thus, its performance on data may be at least suboptimal and at most meaningless.
  • Consider the model which is trained to predict the energy of a hadron given its energy deposits in the calorimeter (represented e.g. in the form of image or graph). Data consists of the showers initiated by a particle generated by a particle gun and having discrete values of energies (e.g. 1 GeV, 10 GeV, 20 GeV, etc.). However, in the real world settings, the model will be applied to showers produced by particles with underlying continuous energy spectrum. Although ML models are known for their capability to interpolate beyond their training domain, without apropriate tests model performance in the parts of the energy spectrum outside of its training domain is not a priori clear.

Solution

It is particularly not easy to build a model entirely robust to domain shift, so there is no general framework yet to approach and recover for discrepancies between training and inference domains altogether. However, there is research ongoing in this direction and several methods to recover for specific deviations have been already proposed.

It is a widely known practice to introduce scale factor (SF) corrections to account for possible discrepancies between data and MC simulation. Effectively, that means that the model is probed on some part of the domain on which it wasn't trained on (data) and then corrected for any differences by using a meaningful set of observables to derive SFs. One particularly promising approaches to remedy for data/MC domain difference is to use adversarial approaches to fully leverage the multidimensionality of the problem, as described in a DeepSF note.

Another solution would be to incorporate methods of domain adaptation into an ML pipeline, which essentially guide the model to be invariant and robust towards domain shift. Particularly in HEP, a Learning to Pivot with Adversarial Networks paper was one of the pioneers to investigate how a pile-up dependency can be mitigated, which can also be easily expanded to building a model robust to domain shift1.

Last but not the least, a usage of Bayesian neural networks has a great advantage of getting uncertainties estimate along with each prediction. If these uncertainties are significantly larger for some samples, this could indicate that they come from the domain beyond the training one (a so-called out-of-distribution samples). This post hoc analysis of prediction uncertainties, for example, can point to inconsistencies in or incompleteness of MC simulation/ data-driven methods of the background estimation.

Population

Furthermore, nowadays analyses are searching for very rare processes and therefore are interested in low-populated regions of the phase space. And even though the domain of interest may be covered in the training data set, it may also not be sufficiently covered in terms of the number of samples in the training data set, which populate those regions. That makes the model behaviour on an event which falls into those regions unpredictable - because it couldn't learn how to generalise in those areas due to a lack of data to learn from. Therefore,

It is important to make sure that the phase space of interest is well-represented in the training data set.

Example

This is what is often called in HEP jargon "little statistics in the tails": meaning that too few events can be found in the tails of the corresponding distribution, e.g. in the high-pt region. This might be important because the topology of events changes when one enters high-pt areas of the phase space (aka boosted regime). This further means that the model should be able to capture this change in the event signature. However, it might fail to do so due to a little available data to learn from comparing to a low-pt region.

Solution

Clearly, a way out in that case would be to provide enough training data to cover those regions (also ensuring that the model has enough capacity to embrace diverse and complex topologies).

Another solution would be to communicate to the model importance of specific topologies, which can be done for example by upweighting those events' contribution to the loss function.

Lastly, it might be worth trying to train several models, each targeting its specific region, instead of a general-purpose one (e.g. low-pt & boosted/merged topology tagger). Effectively, factorisation of various regions disentangle the problem of their separation for a single model and delegates it to an ensemble of dedicated models, each targeting its specific region.


  1. From that paper on, the HEP community started to explore a similar topic of model decorrelation, i.e. how to build a model which would be invariant to a particular variable or property of data. For a more detailed overview please refer to Section 2 of this paper


Last update: December 5, 2023
\ No newline at end of file + Domains - CMS Machine Learning Documentation
Skip to content

Domains

Data plays a crucial role in the process of training any ML model. It is something from which the model learns to solve a given task and therefore care needs to be taken with its handling. There are two main considerations when collecting and preparing data for an ML task:

  1. The data set should be relevant to the problem and should represent the underlying structure of the problem without containing potential biases and irrelevant deviations (e.g. MC simulation artefacts).
  2. A proper preprocessing of the data set should be performed so that the training step goes smoothly.

In this section a general domain perspective on data will be covered. In the following sections a more granular look will be taken from the side of features and construction of inputs to the model.

Coverage

To begin with, one needs to bear in mind that training data should be as close as possible to data they expect to have in the context of analysis. Speaking in more formal terms,

Domains of training (used to train the model) and inference (used to make final predictions) data sets should not sizeably diverge.

Examples
  • In most of the cases the model is usually trained on MC simulated data and later on applied to data to produce predictions which are then passed on to statistical inference step. MC simulation isn't perfect and therefore there are always differences between simulation and data domains. This can lead to the cases when model learns simulation artefacts which come e.g. from detector response mismodelling. Thus, its performance on data may be at least suboptimal and at most meaningless.
  • Consider the model which is trained to predict the energy of a hadron given its energy deposits in the calorimeter (represented e.g. in the form of image or graph). Data consists of the showers initiated by a particle generated by a particle gun and having discrete values of energies (e.g. 1 GeV, 10 GeV, 20 GeV, etc.). However, in the real world settings, the model will be applied to showers produced by particles with underlying continuous energy spectrum. Although ML models are known for their capability to interpolate beyond their training domain, without apropriate tests model performance in the parts of the energy spectrum outside of its training domain is not a priori clear.

Solution

It is particularly not easy to build a model entirely robust to domain shift, so there is no general framework yet to approach and recover for discrepancies between training and inference domains altogether. However, there is research ongoing in this direction and several methods to recover for specific deviations have been already proposed.

It is a widely known practice to introduce scale factor (SF) corrections to account for possible discrepancies between data and MC simulation. Effectively, that means that the model is probed on some part of the domain on which it wasn't trained on (data) and then corrected for any differences by using a meaningful set of observables to derive SFs. One particularly promising approaches to remedy for data/MC domain difference is to use adversarial approaches to fully leverage the multidimensionality of the problem, as described in a DeepSF note.

Another solution would be to incorporate methods of domain adaptation into an ML pipeline, which essentially guide the model to be invariant and robust towards domain shift. Particularly in HEP, a Learning to Pivot with Adversarial Networks paper was one of the pioneers to investigate how a pile-up dependency can be mitigated, which can also be easily expanded to building a model robust to domain shift1.

Last but not the least, a usage of Bayesian neural networks has a great advantage of getting uncertainties estimate along with each prediction. If these uncertainties are significantly larger for some samples, this could indicate that they come from the domain beyond the training one (a so-called out-of-distribution samples). This post hoc analysis of prediction uncertainties, for example, can point to inconsistencies in or incompleteness of MC simulation/ data-driven methods of the background estimation.

Population

Furthermore, nowadays analyses are searching for very rare processes and therefore are interested in low-populated regions of the phase space. And even though the domain of interest may be covered in the training data set, it may also not be sufficiently covered in terms of the number of samples in the training data set, which populate those regions. That makes the model behaviour on an event which falls into those regions unpredictable - because it couldn't learn how to generalise in those areas due to a lack of data to learn from. Therefore,

It is important to make sure that the phase space of interest is well-represented in the training data set.

Example

This is what is often called in HEP jargon "little statistics in the tails": meaning that too few events can be found in the tails of the corresponding distribution, e.g. in the high-pt region. This might be important because the topology of events changes when one enters high-pt areas of the phase space (aka boosted regime). This further means that the model should be able to capture this change in the event signature. However, it might fail to do so due to a little available data to learn from comparing to a low-pt region.

Solution

Clearly, a way out in that case would be to provide enough training data to cover those regions (also ensuring that the model has enough capacity to embrace diverse and complex topologies).

Another solution would be to communicate to the model importance of specific topologies, which can be done for example by upweighting those events' contribution to the loss function.

Lastly, it might be worth trying to train several models, each targeting its specific region, instead of a general-purpose one (e.g. low-pt & boosted/merged topology tagger). Effectively, factorisation of various regions disentangle the problem of their separation for a single model and delegates it to an ensemble of dedicated models, each targeting its specific region.


  1. From that paper on, the HEP community started to explore a similar topic of model decorrelation, i.e. how to build a model which would be invariant to a particular variable or property of data. For a more detailed overview please refer to Section 2 of this paper


Last update: December 5, 2023
\ No newline at end of file diff --git a/general_advice/before/features.html b/general_advice/before/features.html index 09d60e4..3721c49 100644 --- a/general_advice/before/features.html +++ b/general_advice/before/features.html @@ -1 +1 @@ - Features - CMS Machine Learning Documentation
Skip to content

Features

In the previous section, the data was considered from a general "domain" perspective and in this section a more low level view will be outlined. In particular, an emphasis will be made on features (input variables) as they play a crucial role in the training of any ML model. Essentially being the handle on and the gateway into data for the model, they are expected to reflect the data from the perspective which is important to the problem at hand and therefore define the model performance on the task.

The topic of feature engineering is very extensive and complex to be covered in this section, so the emphasis will be made primarily on the general aspects relevant to the HEP context. Broadly speaking, one should ask themselves the following questions during the data preparation:

  • Are features understood?
  • Are features correctly modelled?
  • Are features appropriately processed?

Understanding

Clearly one should motivate for themselves (and then possibly for analysis reviewers) why this exact set of features and not the other one has been selected1. Aside from physical understanding and intuition it would be good if a priori expert knowledge is supplemented by running further experiments.

Here one can consider either studies done prior to the training or after it. As for the former, studying feature correlations (with the target variable as well) e.g. by computing Pearson and/or Spearman correlation coefficients and plotting several histogram/scatter plots could bring some helpful insights. As for the latter, exploring feature importances as the trained model deems it important can boost the understanding of both the data and the model altogether.

Modelling

Although seemingly obvious, for the sake of completeness the point of achieving good data/MC agreement should be mentioned. It has always been a must to be checked in a cut-based approach and ML-based one is of no difference: the principle "garbage in, garbage out" still holds.

Example

For example, classical feed-forward neural network is just a continuous function mapping the input space to the output one, so any discrepancies in the input might propagate to the output. In case of boosted decision trees it is also applicable: any (domain) differences in the shape of input (training) distribution w.r.t. true "data" distribution might sizeably affect the construction of decision boundary in the feature space.

Figure 1. Control plot for a visible mass of tau lepton pair in emu final state. [source: CMS-TAU-18-001]

Since features are the handle on the data, checking for each input feature that the ratio of data to MC features' histograms is close to 1 within uncertainties (aka by eye) is one of the options. For a more formal approach, one can perform goodness of fit (GoF) tests in 1D and 2D, checking that as it was used for example in the analysis of Higgs boson decaying into tau leptons.

If the modelling is shown to be insufficient, the corresponding feature should be either removed, or mismodelling needs to be investigated and resolved.

Processing

Feature preprocessing can also be understood from a broader perspective of data preprocessing, i.e. transformations which need to be performed with data prior to training a model. Another way to look at this is of a step where raw data is converted into prepared data. That makes it an important part of any ML pipeline since it ensures that a smooth convergence and stability of the training is reached.

Example

In fact, the training process might not even begin (presence of NaN values) or break in the middle (outlier causing the gradients to explode). Furthermore, data can be completely misunderstood by the model which can potentially caused undesirable interpretation and performance (treatment of categorical variables as numerical).

Therefore, below there is a non-exhaustive list of the most common items to be addressed during the preprocessing step to ensure the good quality of training. For a more comprehensive overview and also code examples please refer to a detailed documentation of sklearn package and also on possible pitfalls which can arise at this point.

  • Feature encoding
  • NaN/inf/missing values2
  • Outliers & noisy data
  • Standartisation & transformations

Finally, these are the items which are worth considering in the preprocessing of data in general. However, one can also apply transformations at the level of batches as they are passed through the model. This will be briefly covered in the following section.


  1. Here it is already assumed that a proper data representation has been chosen, i.e. the way to vectorize the data to form a particular structure (e.g. image -> tensor, social network -> graph, text -> embeddings). Being on its own a whole big topic, it is left for a curious reader to dive into. 

  2. Depending on the library and how particular model is implemented there, these values can be handled automatically under the hood. 


Last update: December 5, 2023
\ No newline at end of file + Features - CMS Machine Learning Documentation
Skip to content

Features

In the previous section, the data was considered from a general "domain" perspective and in this section a more low level view will be outlined. In particular, an emphasis will be made on features (input variables) as they play a crucial role in the training of any ML model. Essentially being the handle on and the gateway into data for the model, they are expected to reflect the data from the perspective which is important to the problem at hand and therefore define the model performance on the task.

The topic of feature engineering is very extensive and complex to be covered in this section, so the emphasis will be made primarily on the general aspects relevant to the HEP context. Broadly speaking, one should ask themselves the following questions during the data preparation:

  • Are features understood?
  • Are features correctly modelled?
  • Are features appropriately processed?

Understanding

Clearly one should motivate for themselves (and then possibly for analysis reviewers) why this exact set of features and not the other one has been selected1. Aside from physical understanding and intuition it would be good if a priori expert knowledge is supplemented by running further experiments.

Here one can consider either studies done prior to the training or after it. As for the former, studying feature correlations (with the target variable as well) e.g. by computing Pearson and/or Spearman correlation coefficients and plotting several histogram/scatter plots could bring some helpful insights. As for the latter, exploring feature importances as the trained model deems it important can boost the understanding of both the data and the model altogether.

Modelling

Although seemingly obvious, for the sake of completeness the point of achieving good data/MC agreement should be mentioned. It has always been a must to be checked in a cut-based approach and ML-based one is of no difference: the principle "garbage in, garbage out" still holds.

Example

For example, classical feed-forward neural network is just a continuous function mapping the input space to the output one, so any discrepancies in the input might propagate to the output. In case of boosted decision trees it is also applicable: any (domain) differences in the shape of input (training) distribution w.r.t. true "data" distribution might sizeably affect the construction of decision boundary in the feature space.

Figure 1. Control plot for a visible mass of tau lepton pair in emu final state. [source: CMS-TAU-18-001]

Since features are the handle on the data, checking for each input feature that the ratio of data to MC features' histograms is close to 1 within uncertainties (aka by eye) is one of the options. For a more formal approach, one can perform goodness of fit (GoF) tests in 1D and 2D, checking that as it was used for example in the analysis of Higgs boson decaying into tau leptons.

If the modelling is shown to be insufficient, the corresponding feature should be either removed, or mismodelling needs to be investigated and resolved.

Processing

Feature preprocessing can also be understood from a broader perspective of data preprocessing, i.e. transformations which need to be performed with data prior to training a model. Another way to look at this is of a step where raw data is converted into prepared data. That makes it an important part of any ML pipeline since it ensures that a smooth convergence and stability of the training is reached.

Example

In fact, the training process might not even begin (presence of NaN values) or break in the middle (outlier causing the gradients to explode). Furthermore, data can be completely misunderstood by the model which can potentially caused undesirable interpretation and performance (treatment of categorical variables as numerical).

Therefore, below there is a non-exhaustive list of the most common items to be addressed during the preprocessing step to ensure the good quality of training. For a more comprehensive overview and also code examples please refer to a detailed documentation of sklearn package and also on possible pitfalls which can arise at this point.

  • Feature encoding
  • NaN/inf/missing values2
  • Outliers & noisy data
  • Standartisation & transformations

Finally, these are the items which are worth considering in the preprocessing of data in general. However, one can also apply transformations at the level of batches as they are passed through the model. This will be briefly covered in the following section.


  1. Here it is already assumed that a proper data representation has been chosen, i.e. the way to vectorize the data to form a particular structure (e.g. image -> tensor, social network -> graph, text -> embeddings). Being on its own a whole big topic, it is left for a curious reader to dive into. 

  2. Depending on the library and how particular model is implemented there, these values can be handled automatically under the hood. 


Last update: December 5, 2023
\ No newline at end of file diff --git a/general_advice/before/inputs.html b/general_advice/before/inputs.html index 2ccdf52..cc02737 100644 --- a/general_advice/before/inputs.html +++ b/general_advice/before/inputs.html @@ -1,4 +1,4 @@ - Inputs - CMS Machine Learning Documentation
Skip to content

Inputs

After data is preprocessed as a whole, there is a question of how this data should be supplied to the model. On its way there it potentially needs to undergo a few splits which will be described below. Plus, a few additional comments about training weights and motivation for their choice will be outlined.

Data split

The first thing one should consider to do is to perform a split of the entire data set into train/validation(/test) data sets. This is an important one because it serves the purpose of diagnosis for overfitting. The topic will be covered in more details in the corresponding section and here a brief introduction will be given.

Figure 1. Decision boundaries for underfitted, optimal and overfitted models. [source: ibm.com/cloud/learn/overfitting]

The trained model is called to be overfitted (or overtrained) when it fails to generalise to solve a given problem.

One of examples would be that the model learns to predict exactly the training data and once given a new unseen data drawn from the same distribution it fails to predict the target corrrectly (right plot on Figure 1). Obviously, this is an undesirable behaviour since one wants their model to be "universal" and provide robust and correct decisions regardless of the data subset sampled from the same population.

Hence the solution to check for ability to generalise and to spot overfitting: test a trained model on a separate data set, which is the same1 as the training one. If the model performance gets significantly worse there, it is a sign that something went wrong and the model's predictive power isn't generalising to the same population.

Figure 2. Data split worflow before the training. Also cross-validation is shown as the technique to find optimal hyperparameters. [source: scikit-learn.org/stable/modules/cross_validation.html]

Clearly, the simplest way to find this data set is to put aside a part of the original one and leave it untouched until the final model is trained - this is what is called "test" data set in the first paragraph of this subsection. When the model has been finalised and optimised, this data set is "unblinded" and model performance on it is evaluated. Practically, this split can be easily performed with train_test_split() method of sklearn library.

But it might be not that simple

Indeed, there are few things to be aware of. Firstly, there is a question of how much data needs to be left for validation. Usually it is common to take the test fraction in the range [0.1, 0.4], however it is mostly up for analyzers to decide. The important trade-off which needs to be taken into account here is that between robustness of the test metric estimate (too small test data set - poorly estimated metric) and robustness of the trained model (too little training data - less performative model).

Secondly, note that the split should be done in a way that each subset is as close as possible to the one which the model will face at the final inference stage. But since usually it isn't feasible to bridge the gap between domains, the split at least should be uniform between training/testing to be able to judge fairly the model performance.

Lastly, in extreme case there might be no sufficient amount of data to perform the training, not even speaking of setting aside a part of it for validation. Here a way out would be to go for a few-shot learning, using cross-validation during the training, regularising the model to avoid overfitting or to try to find/generate more (possibly similar) data.

Lastly, one can also considering to put aside yet another fraction of original data set, what was called "validation" data set. This can be used to monitor the model during the training and more details on that will follow in the overfitting section.

Batches

Usually it is the case the training/validation/testing data set can't entirely fit into the memory due to a large size. That is why it gets split into batches (chunks) of a given size which are then fed one by one into the model during the training/testing.

While forming the batches it is important to keep in mind that batches should be sampled uniformly (i.e. from the same underlying PDF as of the original data set).

That means that each batch is populated similarly to the others according to features which are important to the given task (e.g. particles' pt/eta, number of jets, etc.). This is needed to ensure that gradients computed for each batch aren't different from each other and therefore the gradient descent doesn't encounter any sizeable stochasticities during the optimisation step.2

Lastly, it was already mentioned that one should perform preprocessing of the data set prior to training. However, this step can be substituted and/or complemented with an addition of a layer into the architecture, which will essentially do a specified part of preprocessing on every batch as they go through the model. One of the most prominent examples could be an addition of batch/group normalization, coupled with weight standardization layers which turned out to sizeably boost the performance on the large variety of benchmarks.

Training weights

Next, one can zoom into the batch and consider the level of single entries there (e.g. events). This is where the training weights come into play. Since the value of a loss function for a given batch is represented as a sum over all the entries in the batch, this sum can be naturally turned into a weighted sum. For example, in case of a cross-entropy loss with y_pred, y_true, w being vectors of predicted labels, true labels and weights respectively:

def CrossEntropy(y_pred, y_true, w): # assuming y_true = {0, 1}
+ Inputs - CMS Machine Learning Documentation       

Inputs

After data is preprocessed as a whole, there is a question of how this data should be supplied to the model. On its way there it potentially needs to undergo a few splits which will be described below. Plus, a few additional comments about training weights and motivation for their choice will be outlined.

Data split

The first thing one should consider to do is to perform a split of the entire data set into train/validation(/test) data sets. This is an important one because it serves the purpose of diagnosis for overfitting. The topic will be covered in more details in the corresponding section and here a brief introduction will be given.

Figure 1. Decision boundaries for underfitted, optimal and overfitted models. [source: ibm.com/cloud/learn/overfitting]

The trained model is called to be overfitted (or overtrained) when it fails to generalise to solve a given problem.

One of examples would be that the model learns to predict exactly the training data and once given a new unseen data drawn from the same distribution it fails to predict the target corrrectly (right plot on Figure 1). Obviously, this is an undesirable behaviour since one wants their model to be "universal" and provide robust and correct decisions regardless of the data subset sampled from the same population.

Hence the solution to check for ability to generalise and to spot overfitting: test a trained model on a separate data set, which is the same1 as the training one. If the model performance gets significantly worse there, it is a sign that something went wrong and the model's predictive power isn't generalising to the same population.

Figure 2. Data split worflow before the training. Also cross-validation is shown as the technique to find optimal hyperparameters. [source: scikit-learn.org/stable/modules/cross_validation.html]

Clearly, the simplest way to find this data set is to put aside a part of the original one and leave it untouched until the final model is trained - this is what is called "test" data set in the first paragraph of this subsection. When the model has been finalised and optimised, this data set is "unblinded" and model performance on it is evaluated. Practically, this split can be easily performed with train_test_split() method of sklearn library.

But it might be not that simple

Indeed, there are few things to be aware of. Firstly, there is a question of how much data needs to be left for validation. Usually it is common to take the test fraction in the range [0.1, 0.4], however it is mostly up for analyzers to decide. The important trade-off which needs to be taken into account here is that between robustness of the test metric estimate (too small test data set - poorly estimated metric) and robustness of the trained model (too little training data - less performative model).

Secondly, note that the split should be done in a way that each subset is as close as possible to the one which the model will face at the final inference stage. But since usually it isn't feasible to bridge the gap between domains, the split at least should be uniform between training/testing to be able to judge fairly the model performance.

Lastly, in extreme case there might be no sufficient amount of data to perform the training, not even speaking of setting aside a part of it for validation. Here a way out would be to go for a few-shot learning, using cross-validation during the training, regularising the model to avoid overfitting or to try to find/generate more (possibly similar) data.

Lastly, one can also considering to put aside yet another fraction of original data set, what was called "validation" data set. This can be used to monitor the model during the training and more details on that will follow in the overfitting section.

Batches

Usually it is the case the training/validation/testing data set can't entirely fit into the memory due to a large size. That is why it gets split into batches (chunks) of a given size which are then fed one by one into the model during the training/testing.

While forming the batches it is important to keep in mind that batches should be sampled uniformly (i.e. from the same underlying PDF as of the original data set).

That means that each batch is populated similarly to the others according to features which are important to the given task (e.g. particles' pt/eta, number of jets, etc.). This is needed to ensure that gradients computed for each batch aren't different from each other and therefore the gradient descent doesn't encounter any sizeable stochasticities during the optimisation step.2

Lastly, it was already mentioned that one should perform preprocessing of the data set prior to training. However, this step can be substituted and/or complemented with an addition of a layer into the architecture, which will essentially do a specified part of preprocessing on every batch as they go through the model. One of the most prominent examples could be an addition of batch/group normalization, coupled with weight standardization layers which turned out to sizeably boost the performance on the large variety of benchmarks.

Training weights

Next, one can zoom into the batch and consider the level of single entries there (e.g. events). This is where the training weights come into play. Since the value of a loss function for a given batch is represented as a sum over all the entries in the batch, this sum can be naturally turned into a weighted sum. For example, in case of a cross-entropy loss with y_pred, y_true, w being vectors of predicted labels, true labels and weights respectively:

def CrossEntropy(y_pred, y_true, w): # assuming y_true = {0, 1}
     return -w*[y_true*log(y_pred) + (1-y_true)*log(1-y_pred)]
 

It is important to disentangle here two factors which define the weight to be applied on a per-event basis because of the different motivations behind them:

  • accounting for imbalance in training data
  • accounting for imbalance in nature

Imbalance in training data

The first point is related to the fact, that in case of classification we may have significantly more (>O(1) times) training data for one class than for the other. Since the training data usually comes from MC simulation, that corresponds to the case when there is more events generated for one physical process than for another. Therefore, here we want to make sure that model is equally presented with instances of each class - this may have a significant impact on the model performance depending on the loss/metric choice.

Example

Consider the case when there is 1M events of target = 0 and 100 events of target = 1 in the training data set and a model is fitted by minimising cross-entropy to distinguish between those classes. In that case the resulted model can easily turn out to be a constant function predicting the majority target = 0, simply because this would be the optimal solution in terms of the loss function minimisation. If using accuracy as a metric for validation, this will result in a value close to 1 on the training data.

To account for this type of imbalance, the following weight simply needs to be introduced according to the target label of an object:

train_df['weight'] = 1
 train_df.loc[train_df.target == 0, 'weight'] /= np.sum(train_df.loc[train_df.target == 0, 'weight'])
diff --git a/general_advice/before/metrics.html b/general_advice/before/metrics.html
index b2fc778..0d9c746 100644
--- a/general_advice/before/metrics.html
+++ b/general_advice/before/metrics.html
@@ -1 +1 @@
- Metrics & Losses - CMS Machine Learning Documentation       

Metrics & Losses

Metric

Metric is a function which evaluates model's performance given true labels and model predictions for a particular data set.

That makes it an important ingredient in the model training as being a measure of the model's quality. However, metrics as estimators can be sensitive to some effects (e.g. class imbalance) and provide biased or over/underoptimistic results. Additionally, they might not be relevant to a physical problem in mind and to the undestanding of what is a "good" model1. This in turn can result in suboptimally tuned hyperparameters or in general to suboptimally trained model.

Therefore, it is important to choose metrics wisely, so that they reflect the physical problem to be solved and additionaly don't introduce any biases in the performance estimate. The whole topic of metrics would be too broad to get covered in this section, so please refer to a corresponding documentation of sklearn as it provides an exhaustive list of available metrics with additional materials and can be used as a good starting point.

Examples of HEP-specific metrics

Speaking of those metrics which were developed in the HEP field, the most prominent one is approximate median significance (AMS), firstly introduced in Asymptotic formulae for likelihood-based tests of new physics and then adopted in the HiggsML challenge on Kaggle.

Essentially being an estimate of the expected signal sensitivity and hence being closely related to the final result of analysis, it can also be used not only as a metric but also as a loss function to be directly optimised in the training.

Loss function

In fact, metrics and loss functions are very similar to each other: they both give an estimate of how well (or bad) model performs and both used to monitor the quality of the model. So the same comments as in the metrics section apply to loss functions too. However, loss function plays a crucial role because it is additionally used in the training as a functional to be optimised. That makes its choice a handle to explicitly steer the training process towards a more optimal and relevant solution.

Example of things going wrong

It is known that L2 loss (MSE) is sensitive to outliers in data and L1 loss (MAE) on the other hand is robust to them. Therefore, if outliers were overlooked in the training data set and the model was fitted, it may result in significant bias in its predictions. As an illustration, this toy example compares Huber vs Ridge regressors, where the latter shows a more robust behaviour.

A simple example of that was already mentioned in domains section - namely, one can emphasise specific regions in the phase space by attributing events there a larger weight in the loss function. Intuitively, for the same fraction of mispredicted events in the training data set, the class with a larger attributed weight should bring more penalty to the loss function. This way model should be able to learn to pay more attention to those "upweighted" events2.

Examples in HEP beyond classical MSE/MAE/cross entropy
  • b-jet energy regression, being a part of nonresonant HH to bb gamma gamma analysis, uses Huber and two quantile loss terms for simultaneous prediction of point and dispersion estimators of the target disstribution.
  • DeepTau, a CMS deployed model for tau identification, uses several focal loss terms to give higher weight to more misclassified cases

However, one can go further than that and consider the training procedure from a larger, statistical inference perspective. From there, one can try to construct a loss function which would directly optimise the end goal of the analysis. INFERNO is an example of such an approach, with a loss function being an expected uncertainty on the parameter of interest. Moreover, one can try also to make the model aware of nuisance parameters which affect the analysis by incorporating those into the training procedure, please see this review for a comprehensive overview of the corresponding methods.


  1. For example, that corresponds to asking oneself a question: "what is more suitable for the purpose of the analysis: F1-score, accuracy, recall or ROC AUC?" 

  2. However, these are expectations one may have in theory. In practise, optimisation procedure depends on many variables and can go in different ways. Therefore, the weighting scheme should be studied by running experiments on the case-by-case basis. 


Last update: December 5, 2023
\ No newline at end of file + Metrics & Losses - CMS Machine Learning Documentation

Metrics & Losses

Metric

Metric is a function which evaluates model's performance given true labels and model predictions for a particular data set.

That makes it an important ingredient in the model training as being a measure of the model's quality. However, metrics as estimators can be sensitive to some effects (e.g. class imbalance) and provide biased or over/underoptimistic results. Additionally, they might not be relevant to a physical problem in mind and to the undestanding of what is a "good" model1. This in turn can result in suboptimally tuned hyperparameters or in general to suboptimally trained model.

Therefore, it is important to choose metrics wisely, so that they reflect the physical problem to be solved and additionaly don't introduce any biases in the performance estimate. The whole topic of metrics would be too broad to get covered in this section, so please refer to a corresponding documentation of sklearn as it provides an exhaustive list of available metrics with additional materials and can be used as a good starting point.

Examples of HEP-specific metrics

Speaking of those metrics which were developed in the HEP field, the most prominent one is approximate median significance (AMS), firstly introduced in Asymptotic formulae for likelihood-based tests of new physics and then adopted in the HiggsML challenge on Kaggle.

Essentially being an estimate of the expected signal sensitivity and hence being closely related to the final result of analysis, it can also be used not only as a metric but also as a loss function to be directly optimised in the training.

Loss function

In fact, metrics and loss functions are very similar to each other: they both give an estimate of how well (or bad) model performs and both used to monitor the quality of the model. So the same comments as in the metrics section apply to loss functions too. However, loss function plays a crucial role because it is additionally used in the training as a functional to be optimised. That makes its choice a handle to explicitly steer the training process towards a more optimal and relevant solution.

Example of things going wrong

It is known that L2 loss (MSE) is sensitive to outliers in data and L1 loss (MAE) on the other hand is robust to them. Therefore, if outliers were overlooked in the training data set and the model was fitted, it may result in significant bias in its predictions. As an illustration, this toy example compares Huber vs Ridge regressors, where the latter shows a more robust behaviour.

A simple example of that was already mentioned in domains section - namely, one can emphasise specific regions in the phase space by attributing events there a larger weight in the loss function. Intuitively, for the same fraction of mispredicted events in the training data set, the class with a larger attributed weight should bring more penalty to the loss function. This way model should be able to learn to pay more attention to those "upweighted" events2.

Examples in HEP beyond classical MSE/MAE/cross entropy
  • b-jet energy regression, being a part of nonresonant HH to bb gamma gamma analysis, uses Huber and two quantile loss terms for simultaneous prediction of point and dispersion estimators of the target disstribution.
  • DeepTau, a CMS deployed model for tau identification, uses several focal loss terms to give higher weight to more misclassified cases

However, one can go further than that and consider the training procedure from a larger, statistical inference perspective. From there, one can try to construct a loss function which would directly optimise the end goal of the analysis. INFERNO is an example of such an approach, with a loss function being an expected uncertainty on the parameter of interest. Moreover, one can try also to make the model aware of nuisance parameters which affect the analysis by incorporating those into the training procedure, please see this review for a comprehensive overview of the corresponding methods.


  1. For example, that corresponds to asking oneself a question: "what is more suitable for the purpose of the analysis: F1-score, accuracy, recall or ROC AUC?" 

  2. However, these are expectations one may have in theory. In practise, optimisation procedure depends on many variables and can go in different ways. Therefore, the weighting scheme should be studied by running experiments on the case-by-case basis. 


Last update: December 5, 2023
\ No newline at end of file diff --git a/general_advice/before/model.html b/general_advice/before/model.html index 29a456f..4e615e2 100644 --- a/general_advice/before/model.html +++ b/general_advice/before/model.html @@ -1 +1 @@ - Model - CMS Machine Learning Documentation

There is definitely an enormous variety of ML models available on the market, which makes the choice of a suitable one for a given problem at hand not entirely straightforward. So far being to a large extent an experimental field, the general advice here would be to try various and pick the one giving the best physical result.

However, there are in any case several common remarks to be pointed out, all glued together with a simple underlying idea:

Start off from a simple baseline, then gradually increase the complexity to improve upon it.

  1. In the first place, one need to carefully consider whether there is a need for training an ML model at all. There might be problems where this approach would be a (time-consuming) overkill and a simple conventional statistical methods would deliver results faster and even better.

  2. If ML methods are expected to bring improvement, then it makes sense to try out simple models first. Assuming a proper set of high-level features has been selected, ensemble of trees (random forest/boosted decision tree) or simple feedforward neural networks might be a good choice here. If time and resources permit, it might be beneficial to compare the results of these trainings to a no-ML approach (e.g. cut-based) to get the feeling of how much the gain in performance is. In most of the use cases, those models will be already sufficient to solve a given classification/regression problem in case of dealing with high-level variables.

  3. If it feels like there is still room for improvement, try hyperparameter tuning first to see if it is possible to squeeze more performance out of the current model and data. It can easily be that the model is sensitive to a hyperparameter choice and a have a sizeable variance in performance across hyperparameter space.

  4. If the hyperparameter space has been thoroughly explored and optimal point has been found, one can additionally try to play around with the data, for example, by augmenting the current data set with more samples. Since in general the model performance profits from having more training data, augmentation might also boost the overall performance.

  5. Lastly, more advanced architectures can be probed. At this point the choice of data representation plays a crucial role since more complex architectures are designed to adopt more sophisticated patterns in data. While in ML research is still ongoing to unify together all the complexity of such models (and promisingly, also using effective field theory approach), in HEP there's an ongoing process of probing various architectures to see which type fits the most in HEP field.

Models in HEP

One of the most prominent benchmarks so far is the one done by G. Kasieczka et. al on the top tagging data set, where in particular ParticleNet turned out to be a state of the art. This had been a yet another solid argument in favour of using graph neural networks in HEP due to its natural suitability in terms of data representation.

Illustration from G. Kasieczka et. al showing ROC curves for all evaluated algorithms.


Last update: December 5, 2023
\ No newline at end of file + Model - CMS Machine Learning Documentation

There is definitely an enormous variety of ML models available on the market, which makes the choice of a suitable one for a given problem at hand not entirely straightforward. So far being to a large extent an experimental field, the general advice here would be to try various and pick the one giving the best physical result.

However, there are in any case several common remarks to be pointed out, all glued together with a simple underlying idea:

Start off from a simple baseline, then gradually increase the complexity to improve upon it.

  1. In the first place, one need to carefully consider whether there is a need for training an ML model at all. There might be problems where this approach would be a (time-consuming) overkill and a simple conventional statistical methods would deliver results faster and even better.

  2. If ML methods are expected to bring improvement, then it makes sense to try out simple models first. Assuming a proper set of high-level features has been selected, ensemble of trees (random forest/boosted decision tree) or simple feedforward neural networks might be a good choice here. If time and resources permit, it might be beneficial to compare the results of these trainings to a no-ML approach (e.g. cut-based) to get the feeling of how much the gain in performance is. In most of the use cases, those models will be already sufficient to solve a given classification/regression problem in case of dealing with high-level variables.

  3. If it feels like there is still room for improvement, try hyperparameter tuning first to see if it is possible to squeeze more performance out of the current model and data. It can easily be that the model is sensitive to a hyperparameter choice and a have a sizeable variance in performance across hyperparameter space.

  4. If the hyperparameter space has been thoroughly explored and optimal point has been found, one can additionally try to play around with the data, for example, by augmenting the current data set with more samples. Since in general the model performance profits from having more training data, augmentation might also boost the overall performance.

  5. Lastly, more advanced architectures can be probed. At this point the choice of data representation plays a crucial role since more complex architectures are designed to adopt more sophisticated patterns in data. While in ML research is still ongoing to unify together all the complexity of such models (and promisingly, also using effective field theory approach), in HEP there's an ongoing process of probing various architectures to see which type fits the most in HEP field.

Models in HEP

One of the most prominent benchmarks so far is the one done by G. Kasieczka et. al on the top tagging data set, where in particular ParticleNet turned out to be a state of the art. This had been a yet another solid argument in favour of using graph neural networks in HEP due to its natural suitability in terms of data representation.

Illustration from G. Kasieczka et. al showing ROC curves for all evaluated algorithms.


Last update: December 5, 2023
\ No newline at end of file diff --git a/general_advice/during/opt.html b/general_advice/during/opt.html index b56221b..5cd020b 100644 --- a/general_advice/during/opt.html +++ b/general_advice/during/opt.html @@ -1 +1 @@ - Optimisation problems - CMS Machine Learning Documentation
Figure 1. The loss surfaces of ResNet-56 with/without skip connections. [source: "Visualizing the Loss Landscape of Neural Nets" paper]

However, it might be that for a given task overfitting is of no concern, but there are still instabilities in loss function convergence happening during the training1. The loss landscape is a complex object having multiple local minima and which is moreover not at all understood due to the high dimensionality of the problem. That makes the gradient descent procedure of finding a minimum not that simple. However, if instabilities are observed, there are a few common things which could explain that:

  • The main candidate for a problem might be the learning rate (LR). Being an important hyperparameter which steers the optimisation, setting it too high make cause extremily stochastic behaviour which will likely cause the optimisation to get stuck in some random minimum being way far from optimum. Oppositely, setting it too low may cause the convergence to take very long time. The optimal value in between those extremes can still be problematic due to a chance of getting stuck in a local minimum on the way towards a better one. That is why several approaches on LR schedulers (e.g. cosine annealing) and also adaptive LR (e.g. Adam being the most prominent one) have been developed to have more flexibility during the training, as opposed to setting LR fixed from the very beginning of the training until its end.

  • Another possibility is that there are NaN/inf values or uniformities/outliers appearing in the input batches. It can cause the gradient updates to go beyond the normal scale and therefore dramatically affect the stability of the loss optimisation. This can be avoided by careful data preprocessing and batch formation.

  • Last but not the least, there is a chance that gradients will explode or vanish during the training, which will reveal itself as a rapid increase/stagnation in the loss function values. This is largely the feature of deep architectures, where during the backpropagation gradients are accumulated from one layer to another, and therefore any minor deviations in scale can exponentially amplify/diminish as they get multiplied. Since it is the scale of the trainable weights themselves which defines the weight gradients, a proper weight initialisation can foster smooth and consistent gradient updates. Also, batch normalisation together with weight standartization showed to be a powerful technique to consistently improve performance across various domains. Finally, a choice of activation function is particularly important since it directly contributes to a gradient computation. For example, a sigmoid function is known to cause gradients to vanish due to its gradient being 0 at large input values. Therefore, it is often suggested to stick to classical ReLU or try other alternatives to see if it brings improvement in performance.


  1. Sometimes particularly peculiar


Last update: December 5, 2023
\ No newline at end of file + Optimisation problems - CMS Machine Learning Documentation
Figure 1. The loss surfaces of ResNet-56 with/without skip connections. [source: "Visualizing the Loss Landscape of Neural Nets" paper]

However, it might be that for a given task overfitting is of no concern, but there are still instabilities in loss function convergence happening during the training1. The loss landscape is a complex object having multiple local minima and which is moreover not at all understood due to the high dimensionality of the problem. That makes the gradient descent procedure of finding a minimum not that simple. However, if instabilities are observed, there are a few common things which could explain that:

  • The main candidate for a problem might be the learning rate (LR). Being an important hyperparameter which steers the optimisation, setting it too high make cause extremily stochastic behaviour which will likely cause the optimisation to get stuck in some random minimum being way far from optimum. Oppositely, setting it too low may cause the convergence to take very long time. The optimal value in between those extremes can still be problematic due to a chance of getting stuck in a local minimum on the way towards a better one. That is why several approaches on LR schedulers (e.g. cosine annealing) and also adaptive LR (e.g. Adam being the most prominent one) have been developed to have more flexibility during the training, as opposed to setting LR fixed from the very beginning of the training until its end.

  • Another possibility is that there are NaN/inf values or uniformities/outliers appearing in the input batches. It can cause the gradient updates to go beyond the normal scale and therefore dramatically affect the stability of the loss optimisation. This can be avoided by careful data preprocessing and batch formation.

  • Last but not the least, there is a chance that gradients will explode or vanish during the training, which will reveal itself as a rapid increase/stagnation in the loss function values. This is largely the feature of deep architectures, where during the backpropagation gradients are accumulated from one layer to another, and therefore any minor deviations in scale can exponentially amplify/diminish as they get multiplied. Since it is the scale of the trainable weights themselves which defines the weight gradients, a proper weight initialisation can foster smooth and consistent gradient updates. Also, batch normalisation together with weight standartization showed to be a powerful technique to consistently improve performance across various domains. Finally, a choice of activation function is particularly important since it directly contributes to a gradient computation. For example, a sigmoid function is known to cause gradients to vanish due to its gradient being 0 at large input values. Therefore, it is often suggested to stick to classical ReLU or try other alternatives to see if it brings improvement in performance.


  1. Sometimes particularly peculiar


Last update: December 5, 2023
\ No newline at end of file diff --git a/general_advice/during/overfitting.html b/general_advice/during/overfitting.html index 5f5f8b1..de1065f 100644 --- a/general_advice/during/overfitting.html +++ b/general_advice/during/overfitting.html @@ -1 +1 @@ - Overfitting - CMS Machine Learning Documentation

Overfitting

Given that the training experiment has been set up correctly (with some of the most common problems described in before training section), actually few things can go wrong during the training process itself. Broadly speaking, they fall into two categories: overfitting related and optimisation problem related. Both of them can be easily spotted by closely monitoring the training procedure, as will be described in the following.

Overfitting

The concept of overfitting (also called overtraining) was previously introduced in inputs section and here we will elaborate a bit more on that. In its essence, overfitting as the situation where the model fails to generalise to a given problem can have several underlying explanations:

The first one would be the case where the model complexity is way too large for a problem and a data set being considered.

Example

A simple example would be fitting of some linearly distributed data with a polynomial function of a large degree. Or in general, when the number of trainable parameters is significantly larger when the size of the training data set.

This can be solved prior to training by applying regularisation to the model, which in it essence means constraining its capacity to learn the data representation. This is somewhat related also to the concept of Ockham's razor: namely that the less complex an ML model, the more likely that a good empirical result is not just due to the peculiarities of the data sample. As of the practical side of regularisation, please have a look at this webpage for a detailed overview and implementation examples.

Furthermore, a recipe for training neural networks by A. Karpathy is a highly-recommended guideline not only on regularisation, but on training ML models in general.

The second case is a more general idea that any reasonable model at some point starts to overfit.

Example

Here one can look at overfitting as the point where the model considers noise to be of the same relevance and start to "focus" on it way too much. Since data almost always contains noise, this makes it in principle highly probable to reach overfitting at some point.

Both of the cases outlined above can be spotted simply by tracking the evolution of loss/metrics on the validation data set . Which means that additionally to the train/test split done prior to training (as described in inputs section), one need to set aside also some fraction of the training data to perform validation throughout the training. By plotting the values of loss function/metric both on train and validation sets as the training proceeds, overfitting manifests itself as the increase in the value of the metric on the validation set while it is still continues to decrease on the training set:

Figure 1. Error metric as a function of number of iterations for train and validation sets. Vertical dashed line represents the separation between the region of underfitting (model hasn't captured well the data complexity to solve the problem) and overfitting (model does not longer generalise to unseen data). The point between these two regions is the optimal moment when the training should stop. [source: ibm.com/cloud/learn/overfitting]

Essentially, it means that from that turning point onwards the model is trying to learn better and better the noise in training data at the expense of generalisation power. Therefore, it doesn't make sense to train the model from that point on and the training should be stopped.

To automate the process of finding this "sweat spot", many ML libraries include early stopping as one of its parameters in the fit() function. If early stopping is set to, for example, 10 iterations, the training will automatically stop once the validation metric is no longer improving for the last 10 iterations.


Last update: December 5, 2023
\ No newline at end of file + Overfitting - CMS Machine Learning Documentation

Overfitting

Given that the training experiment has been set up correctly (with some of the most common problems described in before training section), actually few things can go wrong during the training process itself. Broadly speaking, they fall into two categories: overfitting related and optimisation problem related. Both of them can be easily spotted by closely monitoring the training procedure, as will be described in the following.

Overfitting

The concept of overfitting (also called overtraining) was previously introduced in inputs section and here we will elaborate a bit more on that. In its essence, overfitting as the situation where the model fails to generalise to a given problem can have several underlying explanations:

The first one would be the case where the model complexity is way too large for a problem and a data set being considered.

Example

A simple example would be fitting of some linearly distributed data with a polynomial function of a large degree. Or in general, when the number of trainable parameters is significantly larger when the size of the training data set.

This can be solved prior to training by applying regularisation to the model, which in it essence means constraining its capacity to learn the data representation. This is somewhat related also to the concept of Ockham's razor: namely that the less complex an ML model, the more likely that a good empirical result is not just due to the peculiarities of the data sample. As of the practical side of regularisation, please have a look at this webpage for a detailed overview and implementation examples.

Furthermore, a recipe for training neural networks by A. Karpathy is a highly-recommended guideline not only on regularisation, but on training ML models in general.

The second case is a more general idea that any reasonable model at some point starts to overfit.

Example

Here one can look at overfitting as the point where the model considers noise to be of the same relevance and start to "focus" on it way too much. Since data almost always contains noise, this makes it in principle highly probable to reach overfitting at some point.

Both of the cases outlined above can be spotted simply by tracking the evolution of loss/metrics on the validation data set . Which means that additionally to the train/test split done prior to training (as described in inputs section), one need to set aside also some fraction of the training data to perform validation throughout the training. By plotting the values of loss function/metric both on train and validation sets as the training proceeds, overfitting manifests itself as the increase in the value of the metric on the validation set while it is still continues to decrease on the training set:

Figure 1. Error metric as a function of number of iterations for train and validation sets. Vertical dashed line represents the separation between the region of underfitting (model hasn't captured well the data complexity to solve the problem) and overfitting (model does not longer generalise to unseen data). The point between these two regions is the optimal moment when the training should stop. [source: ibm.com/cloud/learn/overfitting]

Essentially, it means that from that turning point onwards the model is trying to learn better and better the noise in training data at the expense of generalisation power. Therefore, it doesn't make sense to train the model from that point on and the training should be stopped.

To automate the process of finding this "sweat spot", many ML libraries include early stopping as one of its parameters in the fit() function. If early stopping is set to, for example, 10 iterations, the training will automatically stop once the validation metric is no longer improving for the last 10 iterations.


Last update: December 5, 2023
\ No newline at end of file diff --git a/general_advice/during/xvalidation.html b/general_advice/during/xvalidation.html index 2c5bde1..82c7fb3 100644 --- a/general_advice/during/xvalidation.html +++ b/general_advice/during/xvalidation.html @@ -1 +1 @@ - Cross-validation - CMS Machine Learning Documentation

However, in practice what one often deals with is a hyperparameter optimisation - running of several trainings to find the optimal hyperparameter for a given family of models (e.g. BDT or feed-forward NN).

The number of trials in the hyperparameter space can easily reach hundreds or thousands, and in that case naive approach of training the model for each hyperparameters' set on the same train data set and evaluating its performance on the same test data set is very likely prone to overfitting. In that case, an experimentalist overfits to the test data set by choosing the best value of the metric and effectively adapting the model to suit the test data set best, therefore loosing the model's ability to generalise.

In order to prevent that, a cross-validation (CV) technique is often used:

Figure 1. Illustration of the data set split for cross-validation. [source: scikit-learn.org/stable/modules/cross_validation.html]

The idea behind it is that instead of a single split of the data into train/validation sets, the training data set is split into N folds. Then, the model with the same fixed hyperparameter set is trained N times in a way that at the i-th iteration the i-th fold is left out of the training and used only for validation, while the other N-1 folds are used for the training.

In this fashion, after the training of N models in the end there is N values of a metric computed on each fold. The values now can be averaged to give a more robust estimate of model performance for a given hyperparameter set. Also a variance can be computed to estimate the range of metric values. After having completed the N-fold CV training, the same approach is to be repeated for other hyperparameter values and the best set of those is picked based on the best fold-averaged metric value.

Further insights

Effectively, with CV approach the whole training data set plays the role of a validation one, which makes the overfitting to a single chunk of it (as in naive train/val split) less likely to happen. Complementary to that, more training data is used to train a single model oppositely to a single and fixed train/val split, moreover making the model less dependant on the choice of the split.

Alternatively, one can think of this procedure is of building a model ensemble which is inherently an approach more robust to overfitting and in general performing better than a single model.


Last update: December 5, 2023
\ No newline at end of file + Cross-validation - CMS Machine Learning Documentation

However, in practice what one often deals with is a hyperparameter optimisation - running of several trainings to find the optimal hyperparameter for a given family of models (e.g. BDT or feed-forward NN).

The number of trials in the hyperparameter space can easily reach hundreds or thousands, and in that case naive approach of training the model for each hyperparameters' set on the same train data set and evaluating its performance on the same test data set is very likely prone to overfitting. In that case, an experimentalist overfits to the test data set by choosing the best value of the metric and effectively adapting the model to suit the test data set best, therefore loosing the model's ability to generalise.

In order to prevent that, a cross-validation (CV) technique is often used:

Figure 1. Illustration of the data set split for cross-validation. [source: scikit-learn.org/stable/modules/cross_validation.html]

The idea behind it is that instead of a single split of the data into train/validation sets, the training data set is split into N folds. Then, the model with the same fixed hyperparameter set is trained N times in a way that at the i-th iteration the i-th fold is left out of the training and used only for validation, while the other N-1 folds are used for the training.

In this fashion, after the training of N models in the end there is N values of a metric computed on each fold. The values now can be averaged to give a more robust estimate of model performance for a given hyperparameter set. Also a variance can be computed to estimate the range of metric values. After having completed the N-fold CV training, the same approach is to be repeated for other hyperparameter values and the best set of those is picked based on the best fold-averaged metric value.

Further insights

Effectively, with CV approach the whole training data set plays the role of a validation one, which makes the overfitting to a single chunk of it (as in naive train/val split) less likely to happen. Complementary to that, more training data is used to train a single model oppositely to a single and fixed train/val split, moreover making the model less dependant on the choice of the split.

Alternatively, one can think of this procedure is of building a model ensemble which is inherently an approach more robust to overfitting and in general performing better than a single model.


Last update: December 5, 2023
\ No newline at end of file diff --git a/general_advice/intro.html b/general_advice/intro.html index a489928..b24f271 100644 --- a/general_advice/intro.html +++ b/general_advice/intro.html @@ -1,4 +1,4 @@ - Introduction - CMS Machine Learning Documentation

Introduction

In general, ML models don't really work out of the box. For example, most often it is not sufficient to simply instantiate the model class, call its fit() method followed by predict(), and then proceed straight to the inference step of the analysis.

from sklearn.datasets import make_circles
+ Introduction - CMS Machine Learning Documentation       

Introduction

In general, ML models don't really work out of the box. For example, most often it is not sufficient to simply instantiate the model class, call its fit() method followed by predict(), and then proceed straight to the inference step of the analysis.

from sklearn.datasets import make_circles
 from sklearn.model_selection import train_test_split
 from sklearn.svm import SVC
 
diff --git a/images/BDTscores_EXO19020.png b/images/BDTscores_EXO19020.png
new file mode 100644
index 0000000000000000000000000000000000000000..bd10ed51315bc1fd3a9fec11325ad412e1992af4
GIT binary patch
literal 375991
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оDK`Pa!oH^1F~)A6CV%eM74F(pRHh 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∋ruAx(IFNk@DjXsbnzR&Qbk=gtd{^mE5SgkiPGEq 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

q^fTex5GYrB$=F9n-yU$mPl%5_g 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 literal 0 HcmV?d00001 diff --git a/images/DisCoPresentation_MLForum.png b/images/DisCoPresentation_MLForum.png new file mode 100644 index 0000000000000000000000000000000000000000..77c35ed6e5b00a7b8839063b876d5b8d8b5c2ed6 GIT binary patch literal 125562 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 literal 0 HcmV?d00001 diff --git a/images/ML_Forum_talk_May8_2019.png b/images/ML_Forum_talk_May8_2019.png new file mode 100644 index 0000000000000000000000000000000000000000..1a242946e67464822089a4e64f201f7be26b424e GIT binary patch literal 219770 zcmbTeWmp`|);3CjAi*IC3GO6lg1ZL^mf-FVgS&>{!5Q2M76{HT_~7pD3{H^28Dx;d z^E`X+?|aYL@1OHyuB*GNy1Kg7swMZmdLq6k$>6*od4Yt4gd-;_rGkWn(vF0LOooB> zbcJ8P&j$(V8KbqN8A%5_2WM4BV^ec+GY1oM6&Z<-{QP`KNbllJjf_-eS>E*< z6B-%yk1)S~;q0Lj9v-D)6foM}L)F*bVbs%}o~oy}_6C2g3kmfD$t&OH-gb<8b6 zI}`h`8dOvwfq@s4RHI64@0g5@jE0eFl7{48h#gW;>C(L?9X3ERA$~@VVdn6xdW!mx z$cP&sPF|HBM`cNcK#3bkLABrvJlv2Et9mYG_p6nP>Q8DyPGTw2S!-xsVPObEgg+I= zXBFnpVjvfPar3Vh-qI1@X+IRGyb?N3xO9CPg&h_3_Kny&?$>*Me6s8)6H9y(jBofE zv>okhZHa+_+jtVg1u22Xfw(9rk7~iG>C`Ae-$YQlAp8S`pUl6QzDfJ$PPl=BBnSCy zo1_bDd0II>b1gXwMMWg0r(+DHXJOVzs82`Fo_-`xzo!MxMgI2{l=fVde;*^0{nZ%K zaM^)`B!(m>C9dZA>@W-6NWUiyEIPD*^a;G|bNt*+HR|KM*tahd7{Ow)$QgEv_BFVI8vD$9} z#pv31Wd!?lq*0Xwkj;B~+_z5F3vMXbm}^&#A-2V)Np(LJGwdcCoZy`y#5xnb(Q-pB zOt?p>+(QPC3zdwknab=RhXI4!S7ap?*yb(f<;vYV6Z8 z6jN+^hK0BDhqVtv??FAUSd+Dn2R`;8+OELAUHL$6oh1)&~!B_RaQoLsC zDp_wQV(l_ySMMtQM&OnDwsF_>7QwiZi!vB$mx7085&Z^h=l45cFeUarfxfwJEYTn>6_WKc(ukkLJk zK>RuPW#kE=Ti-w-wnP|H-UO2?lMFP^m4(flSG{(F8`qDhRKAjyoZO~l!KzQTeA8j+4!l{F3vHKnEbBjrj|d(xi8$Obic7#nB3X5jC`0dJ@VP~qwtQUyM=#GzQ}={!FNTE_mPaD*LUZZ zK2FMVH#WEA0`$C5Y0^*Iz8L%Im6hLuK5mA$H%7GY$N@7My5{W9nJcgL>pzi_4uS^* z%{pCq)AlR%V-@kz zLXSU~RhX3jWV}fjPh=B`h>#$gNd8nJmRsRhUJJ@J;E{Ei`{tDG@&=cNCA<^a^^XjZ3W(&%L{ct0j%rq0h`Czm{T1Ewwx7vXwo+ z06{&Ua%PoKg*{zsXP9qdx_{WHjF3qdE%N_Mme{Kj9n(CCtd$TF|GL&lV#>>VPe{RY zodMy=qG0ESf`Wb9u0e7CnLyPBRfgy|AR11T_QH1FOw%HW|ZPP zFY!M9k+~z1#BJZq=QV4M1WtlP2PJn}ZaIDB3P=2?&B~(AfJ1{^EQPw&wv-7_3C_)tN4XGPJINOln%2D#V(gy^5b}!V(R4gAtt5vA7pq7AUFIR~ zP+8L(%msYcLo zY6ANRSbl@g$PO6%KO>LY*VIVJppx zWf)SS3y8ov+w$#`^`mr9RFvr?(0-G1jIY*n(ztZVxBXkGYuThfZ#M{#FGCV3nTQ3c=G=_mLakv5r zKbQKkwlFy52r>2(2Kmpc_-FJ#f4mBq-3Bj_tqKW7NMv2H>f7KsB1x<;D9k7mSVtCsU05rvvwPr>UF^$(G zvE%Q~8#VkmBzH;ZFJ?+RXKR3ioSG~^z;M#uV?&u3iJi1@=)Ceyl zjFekA$Ze0P@=xPDzh#X6MW3jSkMOyb{^{IK))Fj;lIxaojNQ$a*6>J35k787R0xI= z`!*HZsDxuKVqMye??zB}1lt1*TFVgn9@@43_nCE4Ol^`g_q}w<%>J=YR(2E@mFAg< z;39Q04b2yJe53KWUx9^dsH`>x9d1_pyUeKXl@gEPo2KDfX1Qobb z-jA#O6dS*$veNNh^>I)q1dZ7~oTV^H^hbEKU9x6S0j1GK4Bo7bj=R}XdiS$@037~& zHt`1MdkaIF`0-t3z{=~#-2jRtTv0yuRbK++)BWxUFztBnE!V-rttRg`TVCVuqH3Ew zNoTB6xq#7PQ9m^ibEYn?_5E#=nWsnHRWE|&AEA$eOqdLyNdId|(#s{N&K#8kA%DP0 zzzwl3^RT2GaR&)gLciG3kOskPWHw{wEgkRQvy%>a~_vo`pB1^oZU>v?`n7PNu#%(ltAK zJmQ6{v&lsA9rlU3Bd{Bam%V={MVi@7ojcauc1%9rP|7e-Ctu@nI9?%J#KEbdH!*^0 zDUhA>U)EUft%$CXC9ReW-DWJ8_SY-Po(P z+ay?`t&CO2_~Vcv*w|Y%Bw_Fvi^!Lh%j^5_tWP55NRYU1#0;L0(Oh`=`GefHM>uBj zyT8KAd-1h->)KmYxYp!TRJz7Ri*$p7PWb?rl9a}10KUUd`QSJs@af8L^Y$Q?x=l|L z7-5NcrkUg+zY9Nv(tdlp7AF^GyKW9z%e^R1Ng z1FrV$!oWIWntHa-HM)ZH-35yu4b=F3ru`1NX|iq+MQTG~MZbB(QT#c&2@V90_?U9v zlB8M|E-2BpEyqSg^FQ8J#`vCPiEka)oSOxmvX-I;DQ>;Psp@|@5os7dJ6mrpeZIr9 zVJYfj{dP-)8TRpOWrQQyiy*6?aV~~vi0t|DoAaDlnl`{M6U#k(cjQ3FJ|P*s1~SyqKaYpjXK*G zo*liN9wn|CHeEQ{x1~N5*a|W)#Gl~zW$O^7lczKNZkLUbK-^zTd`l=#{_$h28j${C zj<;aNER81oXh}$^Oi+D`}ZG)V;Y3<}DF%(HbL@ z^uL6>B=s;q=|Fx`1o0*>CH&P10~J3sRT$BccKNXHZq(a85M$r~VL(7?`-_|bwE)9T z4%iGGK-T{~GO)}lSoNO#EVcVDxA;Z$Dfwvd2dT1{ORbfOAt~x|h||_8#|Kjr8aOl6 zvHAA-;yPK37+y=fn%JY8T6E%=p#2axa~r3IwGr{i$tV05HO?>CWh zUpMQJU<{wOcs@cublO!a`*uG6*(AvTmMp;hM4M(KR;o5613_)+9G;SwPjfP)8`;nz zNV;ND0hE$h1_&>qap)g0o&~j0Uc$Cen5^3$V-)xahRLA`5xKEHB72FW57YzUq^xOk zE!B{!D^q&WVbMMe%fVGOi%?j>`X?%L1D?D4V;=2;0&k88p5qtR*;TmT8iv2D_+={u zN6i{tay*yoVwuC5MW=RJ=ObyYpgD&XM#F0YE^Xvrc_@^$2K!}-96Ybpq?_OZZdR-& zJqw{4zQ|~Ta|h%QEwIIhj>zDm1D|nBv?(mM=6M&je8>y-ocaQtpk={ zUmVd^9e988h=T__>N-35Wx`XuLG9@njLJMoXte+$^9$R;+EzFGjzECck86mpn!Lv`IFKW!_o|-lp{*ntZ))e` z74-Oy+^?DI)!TOEBdK629b+$cHB!zsnWa$a*CWIt-l@p6sa{Hn5nfp91Za3BYoB#L zT6Zo&WK9)q*03V^*%)$H%oE5y%W`zC*(LG&PC5iuOUN%pQ! zqL}E%R_$zGp??atL0Y9~hEBl{KKc6vdBuC0GE?^qo(Do+>&>>crHHiy(ii^GSuf)k zbQK(NS?LRYv-zYATlZ>h;QF{)hV`Px=v~nH(Axy3!;I{WiS=fxej!m{Y zdkLhr6Z4s2ag$I`2^`bG{%B}U;TU6C(_j)cC6@K$SDm%ld>K-P$zwGo*TYKC;IAj8 z$q_og+93aiz=6OF?rQyo+9X9{|IWBal~8}8T_4(|XqASyYln&Lt+tL{jS`oIaXI3X zFw%T7&>g(8pR5Wgm|@2vln%CV_bbJ4l=T}M)NSSq0b~}jp`~Ta$*NdqX@0fl0TrwE*u2C4mEsl z)YSD;%!=8z<((T}xNlTmjp5IV^AA7vnz%h-k;U`Gn!@QsRrmIOrXSnWBaW z;q_vS{n8u-ABe0M!a#pOB?`Kufr!?`M)>O^NanJ_PsqGgRT%Jvo}7v@*F3r?=uFGp zGmTl**s@7~K_hj+kOknFG5Bk$WOiX!GX*fui+EUSYjmqiKROv)Z#8=?=JXLbxaDG+Q)K1h4F=5<^tW<@*zh1)2;sJ?qM z``&;MGfUK8XsJoaWjcrF^r~;mD|uB}`RZ^_0epY|OzO5Hmv zHV~S2K!MH(DA^sN)yOY+di#o=g=lW$9M|4h--|~Q6RL0A^g(g?uPAmO9r-JYp6KHH zWDXj~UIH=O?K-N|dk)hW?c>jBi=p4qLab`hJq~{fwCIzFfj)!;dKt~g;RsNn#pUnM zrr5oaDo`W~_ip1kqt&U#``qJUaYUe?h^g{2y^`l>7WV5;n}My2H%2@q*EzXM+kQ8Y z3T<#R+iwYsLI3N?-AITGHSEm|m0X_o1r>oC`vd=m;C&y0?_4+uBHct3pML_x^nSO_ zVVPP*6exU=b-DNzdG=C0<3c5uUUWfRq-2)A#c~jlBvMx3u*p4}H1yI5n^K$of?Sz| z`Xsf+Bx!wHI(~&L&4m$I#d?p}F>(ad$Zm;HK8mb0x6S4V#sl=T`Hv*qYp>pMAUcl& zpgm%_V+3gwK)~vV7O3EM^hHE&j-5a$-pmsR*0)MHR3v8cFe z3N)okp+!@`=Ij;b%7;$ymR@w5BwaY+6Y4olimIfi|HYw{1dHV9G!p~a_+s}-B3%U^ z(hO}goHmyyKcrTEjEpt1m!4KUCK~(*BJ$(<1`*$}H7$$?y4%=&P55S#+5oEy!l-db zyM8s!81JGziQyBdCt=@`oQMd%qOmC@mL=c5s?mFB%L^87?L=_$1>>)YMk<%$ZG>pB zVwVx^ygHgI^j&xJ7bR=CF?Cy+M9VXRN5%3c(BhENt>P*0gPFsdmo=uJ<2P8z;{gFxU$h^H?JMvosq44-|EG4@0+wRr`KAueZXe5RqfTE9lJw3hmv)|L48w< zoj##}!yrrsTWHfAB$&kL?q_T8m<1p8-hLUBg?-%zN*THI6t3q!P~mg8Ulx~}$k8HO zz6^{*swd$dOzL?an}9Y4#Krv?@=5C_&bVKe@#uX`%Qh z7Xh`7;&BX3x!kw1WS+q~$&M!jb=V1aNR(4rAhA^Grl#-$g*Jq}DY;g?y`wo&B@52= z$6eROyuqfg6~t2Ga*?kGSR>W6`F#1}={fRQUcPUy#AP*?x9+EQSQX{f8)`V4H06nl zP#fB1$F1Wuma+>FDJ)l*Z^b9WJaaSoQtP5Bkrr;-o#^AD7mk2p*kE2IcV`Cp$!kiEl(REE zcTPFOo&>X7AuRcgdObCKNrbp@_tjF)$FXzIJt!T+Z1<*8CrmaBUTH-WWh0@>Zv?+a z%VS9QU+0=|`(wMjAw|bUxl+1oUw2od{o6ZKp}T7rOJe=5&TSN-zIl$j&{rDNuE`=C znkEQTuD5#omW8!tTFo+Nr=fn09&^qZH>Z01Zbezm6Inr9(q*ZeBbdlOyXD4K?tl?a zSh?BED`+8_IlhjFkbT<2t0)SK>s=$Yp~{Y$!iERTC_)6>S-TN(ZGS04Lg<;#hTcDx z7(V1E@m`yBsvZLpmct7qQc(NL2-_9s-ZPaSa>RlzyOy_VTdJAubP z@ach4Q{8b}1T$QT`Dm?4;y!m?zN+;RPZ{wl?u)8V-|{t?{oJTmRz(-+Cv(IN5$RJl zAKx6X*Yt3ZVu+`8wksUvJ{TqRtsRgPxGQ{mTxZ==RkH(H55+2dJ!PFayRnr7a1GUx z#Rda0P_a!HemU;FJEN;g%SmicY;3Gr;S7L~%8q?;%rcX}=lc1A$nl35H+q3v4moPZ z*<6(!0VeHbV;g;&vsoc>909*?YZ=;yg97$bxkNKmzK@F6!s^S^7glW_ ze)qK?%G+a?cl*3MWs8P;?sev@WLq&?ruZ}&q;0iRAR1l^m;fqOD^zN!xV*iyAVmfW z$&;KiD)+cevoY*Biy=Caa{W>_CHJ<)n42}vg7bnXIYQ-NDi#Ij4>zxE6toRrpITJr z4S)sXPZ%?0&w(8NCSe8K`myHNeB->-?GPc%`d{aBPaZe1k|C0ax3NGZb6;@CR54HZ z*^YkPK4+>;j_fRCnG@7>62Hr@M2e8zZuo4}o4J1+$-B*%GtQOWL?{$HAj_je*lIU<9QBT#0Ur$9|nVM1Y zOMC^yzHNW^XiU@W2QrIIp+3tYrKw%)2Bik`?5!-(cj=L+>h?Qx7=KWpScP(Fr=Mu) z5B9mKcVvJlZC-Z?*)BPht?^|$GRVt+$;G58@uyM{G@ygAqU=c-SM4vkH{WIN;cdRu zCq>Ru2wxmYPzpCR_)(bZ+ie*m853a?IX*h#X82Q%(PpDl;97Q_C1^|@_u%+~$w|2s8Az~Bl^7197E?mz|-Ng3(s2OT@LBNm4 zjg6|%;YxJg&p`9-+A7o{RPVS@3dL#|gbD6kTS$9FXeM)j$=PsDlCNKi=SCxjy$G4r zdGR9J_>J)NuJ#ho%voMY2k+~~FX^)eSF5_ZvvOnk1JW7TudRk6%6{xocZ}A&+T%^j z#aaBh9RVNEOnJAipag2WwF!G>+Xu(D?x7=FFGjOPIuTbzXNwyhZ;9u!dV}e>86o3o zc+}IL>i_+V!QBsQ;tFHr?dU-x9)$wzneL!t6e}b2{;O&pRloC6pJ4!!Jx*#|`Bhjna?lXCOOMIe(JnUJORNr#uKKn?sp0 zG1)vahSP+tw?sgA8u+OrKe;!(@zG9tJVtjeR8gu4sl!{<-v+<*vnvv19lRPIHP{ zc1P0%=24g6H|X2rF696pjepuZ%JpN93? zWlmkoc?g)(Z5u(~zJ+1NJ(woBrY0PuP}S(K9SMIJo)KE*|I0EdV@Of1-A?ONm;J+F zKYRAGfx+fIVM68;O0d^mOqLJ|e{~K=zIo=;CNn(xoA&cA*_*zv)W`_CgtsgO$N^?t zrCKH}_dVj>-#Ng$EKW)@xisN z8JeA!e+8|T7*fc-5U_8V$KT}ruVRcA|CJ}p*vak2&EDdf=c z;lReQ=o6?e{v2wZW!Gk>qJOkLvUlLDsNiFRA3nBL(!ss+cQ8!x7fLV8*Vp{7+JJUB ziEs2pz87=dEC^-+*q7(*kt)K-QcjGiZx{+ACbb^=QzEDkvd0m-%Dq=gysMrZH4$E! z;;0*(o@gDh^)XK}QF;^j2gSD&zZd&oH3e&~G-5`-0WZXQNIFqXoe!>=WO2X#&~E1W z48y>u3#W2KeMaNV(VZe@F!GX~58GGWN9kkFO}LmZ+E|}PtXm95rr=+1e*`iwll#9) z7ygmM-<1s0-`c-2j5}Cm^(7=EkRl5RocDQTXiWr&anv ztf}>PQ|D;(|E&bVF%(~`Se;hDQo6xqwm3vAjKV-uOA0ZaSed+y>R z$0OxPM!)_2SAz3jhMJ+p$8@}$@8i5HJN2mw;C5(tAD}& zd<34?N!#AX$-2O0Y{VrH3-)Efc&VyxoMB|u$eO0dTYU*@YHq>fh`iiGvI(%FZU((u z!Cq>(7n>oc^!7@-{{tk}jl~@)jp@nykEEwVpUN%js9%oF*`ngwe5tediDzYf2U475 zI~4w6@u@KEa6FH;=rk$%Xl1Y$#}1&n!&1%2Fy^tl?-p{PzaFc#nQ7O2^TXJE~rS(?3-)q zR}lFFJT4QjWxj`9j@FL(8U4E73n_#TX5zp%qTG1aJ4fN=2uq#4ye@bWUM$ySORZh= z{hG3i-Ts|o3{o4vLq7AWmUVRge{?68;mHzIU}kw%7|?$C*)cgyFL;=Avt;LCTtZGj zE$d*FoXwDx59cJ|iA!Fu`n}m9Y3^QCHoOS7+Yq^I+PM66lHqY!w}SXlt_CFJmb+Q+ z0^2&o0~R53KMG1>RXCA{X}JKIwy-6xT8n7s%(VBKi8c~eL)k%KpGR?-m??9E^pY?d z!QXk@@yV>rw2EUG-HqZi+}zOj+0hu&C-{9}yqA}q)hErO2AdjlF8cYIETn=h#VejSUk);%o%$;8I8JXn zVer|6EO#jz!&AJVV6z_=JA=hF>Qf2_@zD8O4;vG5-5miAz0KB*f$(-GswxTpmVT5m zRwts5yfH9)VL_hSjUaO+A|ccD*GrJ%c^#gS8fNxGvz*5pwPrI}1k>429hT;?^KtYM zjCnYx&3Ufh^e#k>`2UoNJ?(1b%y8K||556=tch`HR|jg<-IfA3<`lGNgX>l@Vj>9) zG3d$y$aMrQPeQjFTvmr4ogv$<()H-=%y)lHwK?)`x7GyGtoWSl3YS zl`Rm)@J^pP#|-5ODW<1Xy`_UPgF>U{T{Zpr^XHfIj6*dp&ShnK zuQ6}4v$Ku0wbR@fI>M%lJ&qO}rgkc8;9euFZR$ir!?MJAinX&ejrvPFP2T;oooQJV z)1$cBa(2n?o*hjR8FeQ0`kX>gX}Fa=j`GJ@Zj;x_*+7i4<*K=}^xgSg`>8F3C5Bmr zz_L|s$jXdchMqb;1LNZXXNpdQ>?s7LaJJ}Eh2UsTP}m?Wc8tARW26Q!oa;*rUEkI| z0Q^)aBrO^kX8img*JL9`MYu-N9#K3>j!M>1J{?u4@;ffE`k32kJ=vE^Qno9!kN3~n z9FRRnm3n)7n@txZoxQDcs-%I@3*k-9&PI)kz)hTow}vEVWEs~hy)h<|xpG5IMHFCq z47)YvR05gad+SW_s9*j@)6I!N1mby7g@hnrJ;a7pWbxCs6^)BKkb5wzE_fIXhs5mn zTv_~$ZTR?yZYXH;dc7sp;cdW1T{7!FnVSAPaZriPAw^xgG za|Wi7uJ=UBLdFOl0N9DB0))jzW(G7&GgIAFtYyn;o@cFZ84JasRI+$K6u6<{<9oIQ zKP1Jn3k&~1hm5?e@GPu?Vs@4R*P8i^ZG#nPb+;Ai>hCZ?%{;Gdf;D^Ov~*dMEk?9V zC;0Fb4*M#k5+d8yAJ!UVr7z`Dl>eS2Lc+Q^5{(917L`?N%Yzp+{V$Wh8$~-OV~i;q zGXKnc&m3a+>4$;2Ef1`YUH%{=E6rZlj6#vBJXt_SYVU6r_@6ilGmx*QE+6I-5rCi9m@Ut!15EYKmg(Cf z;IP_kVIQ4ZixEBYdcabVk!6@(Ixe|@T3un~(rT-Zep9(_y};R0jk)|%oox!Y&2;~| z#8Qp&z5CI;@queBTbAI$PBbZb3>mc5rMTR%ZS@xZ>b)kZW0X%Q5EoQpOB#P+(CWQV zEOH~~bF-ZS=#AQ&EmF)@%o3QpBliR`JD4t*c&S;h?kWm>- zA?&SnzBA%CVY^}xdAMU{L8fZSR*?B_;N-Grw;mIHo8;St&72m@aUOi6F6G+q^E|*~ zcOxgfeL%vf(s#UXppQ541#s7nbdh~plltN zh64^DyF+@9OIJ}ffh)R=$EV8wXJ1L=9_7Q*og;T?kav-g@f&vwOMKu9mNuGt9A&3fcKF9Xki#^l7Vl;9Z>6K;=kM3TeNk3je;&B|MEJ5G=EmvF-;;4Dd`mxD% z&t$VV8Yq2ABJ=GVel$ZtGV9#!L4Oy}kt;dzc@5^*ptS_HmxH3_gD-=YzpnOC1}qb> zC}WFWci=6K=Si*z_m8E_K~qdclnwnW0hfK$XL!OFajh4V^6m&&J*k)dd{qb7Zoy<4 z=S(}}4-Z&_r0?bct}`T^#`8;F)iw_x8P65mw(M|oX3hiasRzuI&e)%Q6Og2s`1zWc zSa)|ci+s=jsBzcsa(`xWZD)S1BM7$Yb6f|9Ej72?vj}8J{$@txXDgx^X{^m$QQ`v6stO^rWjq89U_K#3Sl% z!WHiT_i)6M>uBMrt<)F3avvN zgRlz7)*seUk$`XGOdZ7qM{hbiB@X7R^H~jA=B*D(h>B)0uJY~mo?O3U1=Tk}$)9HL zHBfTCP_^Z`+H4-a>XdX6{ZGE)CcQ>UyDeZy@+A4pB@8i<>)eP-NWr>C_=|LYP*v*a zMxR%pulN!ea4?((^*vWa$CuiVm!YVRd@59nr^ije>-HQ-pg+=42&4YA6~_OO4%fL+ zl>v;;!ZjHui1BuDv_ijG0EdyG&5CD2M`HXPWAH#qijP<}UlqZ>4q@MS19;0IV zRzJTPNlmOAkC*Vx7EozOk)P@+5pNRrhL#+NdVhQAa`lrXm0Xgf)yP2cZA-)z*spY| z=pOw0Brd5`C~k%DdCfwLq|T{@zTNC?7f-+e8Z!%9N`!qnVA1VM4l9pK55>@sFb(CJ zRJoXbsgy_Z;^EwDBj5@{wMGFCtN3zc?vLy`abM9X44?; ziJxQcg)Bd8vj;dWegOrf@=20W*Wy<(S2T1UHEhN(0jSI{R4~1|Gt+%fjI%0pKgWGk zaH4KY-E*c$7(wT_TfKtJu{CZHT8?6$N*+W}a+tjVu%s*c%26xf^Yrt0d9SZxq#N|V z-f6sAc8L|H0`SmfoNwVd1?83}!HICT2a}ky7TCS=$RB3wA?7G(*p3X4IDKX0fefx? z;>jqxWpxwajzb;Ji<(5X?+08>0@)Q9UDj#*?zx~q@-N!_&n9L-L7aYet9)HwZtVsW z8a?7?tJPOLf$vOAN7@^+2=0`VE7n845%D%*_DNIpb}Q!^cmupu%% zWUZjjr&I(WF3Wyf?{q6%w&c8NlzHQ%e|RTFlqEB()XFOxBzbXkI3ASO3zUn5^`omq ze}m;b-dSew0MfgjySNVTrpe|Vi_KE^g3wV(eD?%5&Hcmz{w5IrlOsKq;%@wvE^NH% zB^xzH71F*?cZGiPoRFJM025Mzh6et81}e{Rj=hZqmFCI%f5E2nNhlT=!G_tpST3-e+4KnfbPz&_C=Q!9p3{hsBNZwiTFczqKJDhB-mTZwq)=iAf{1$95` zabdH|O?9lbWbq4jbiJZq~(wNHJUD@GrtR8 zkeBJ-1NTB|ZPGXaoSnu=u?ZG+PZ2v=#L&;0Cai zmxMbmbx5+Zb1wbRtN_zyFP9#!xTwL!IW$KBX5@*6a*?yi#oOO{ul}HLrZl=YR`u6( zJxHP1SL9!7Mu9;)JJrTg#zi!1;zsK2R_v9;nXDIm4% zI#*#gjpbQ>GOMnZp`yLm_ft%ZNu;w#aJGA&K(20m#iTQyGvMV;(xAWj)vt6r+3oaN zYMh~ja!o0t+0&y{$lgcD&*IuBmiqH8(%*;C9y|h+Q$DML?W`R@&qL3lZcfEjR0o@K z^T5&Pa-jW-Egf9+K<=V9C;_a)Y&}dj~cbgaR z3(Xv~H91><@kV9M4>vln82L2&vj20pQY6?0_W7m&%v`4szfcyXP%gjJz3P4Mmci*4 zdifO23BSk&ePI~Hf~GOT){R-0@_sc<+U>lnL>mP9wrb!*$DX(-de4{QDIeZTPrdm1 z9ZCG>hscK>cK>xyvuSUX)9yH#Bnx#;96g!<&lAiNi*6q!fx@Ff9THvJH*NP~fl{w_k(lopv1iSy{CI}QR zEBs1Tt>_ACu6^m|1y|tZZ|9Oqu4)x~n7l{B2Gw*T&()J0NUeik&B_jyl1*^Vh&7P& z>zQS|a1WQ^1@(IO;=(4K6*_gjZI@~Qfnc1n5C)vjuPecdH0S*H-n!Bcq6`9^w>(zk z@Yvv^wCv{Y$fnS+QEJ9RCW`KzA>>q3L`d z!fn9m(BqD14u7aWeyeZVq^6XOAyTq6=H0QqeaDnKGNB?j@ZGSQWpa!Ex%*8xFxFbD z*MyZ4?pJ3?TdsXqeSn^+b~fb40~5+^H$<#7yH2EMfi(}PEg7!m!nQH5lr5kyKH?n{ z{Eg6ui}`(e@=vu5+gz*Q6@)x&<ax#Ye264ci4_@eG7|^?E{H! zy?HTdcTUd?|IyEg7#(|!BAk@us(>riw%*P44MJvv>cu9zWgFO{JLm%7V>jjke6p?M zlaf3IPFV68h>CY{3;HXFMZ~$FEulm(Kd+3%2tQBJ;}_%Fr;KGRObRommr^9K@M z`%J8;9;c%q_cu87TS709<%}hwXGsF@ewGvsr(Vpy8(#c_a${R6z9m)uHPStG+pC4k zA;A=Ot}}nPO$yj&vo_GhuG$#3=Lj0D<2P?i2HRCCBwoF5w20`(`|7_@WItr@^wo4} z?o=X88Hfh+lO#CqoweFq{UQJT;s}XT2~;(yv0X^3BpcaqcljJ>Kgs*$?dqzY(-HQg z`DEFzKe^oYi2&FcR|L=v-nQ>?_#S+GZw#&jB-t85T!Oh(a_K-3*>}l_OxLOXV$B-S zWb5;WYBubndJaTvhh`aI$|`?--NE9!Z{`Cv-!6>^7|`eJS9d}EFkQ`KnJku-RTOmC zab;!A!-~#Aw5g94)^^(Ao2!LJOfIfFhcsfg@QZY(ucxM zJ2NN`*yvKp$9xp6{&42k10V}6UjA3t6Y4x$r>|OH_Qu@TsaL?qXTe?Ooecc9md-nB zALF~&I~kmQk2!{-5?sn~nyu*VANY&uJUr8qH$Yr7 z!1%JV@v@KVmu~`Qx(R!n(r^xSjfl}Jy0obVnAcb;q zNy5boQouoeEA`4&LUxVOK;?$KNay1(`#1)Gp9{_xW=PYy3{^zWH95l7U)Rg)6Vp8o z+Y4oNePRl_SWfoD2J_lp9(O6_W=A99q49#w1-2<-`)WTLZrG-@DvCA?S8by#!i~W`NNpsxAOo^ zxI1D!=M&A3h`lqZFXK|QSZ)u$+tTeRGv}B5)f;ikMd58ZZNvVL%O*bhTd3Ad&Zh_L z25cO9CeZ6A0wdpp?=0;Kue#*?k}U;J=yg(P9&?gc*cA+a0?Ze3476+lZhNI<=pOEg zRS$u@c;Jx9Tl$?9EmWqj6hOHTb3QloZuhVUOJYPN{8iGUGBnlb30R*uNXGm)sdlqm zT;qSw%Sl#2hJYViE+~t*Rc9!yHK%mnwGEK8U`lz_^t;nMxj&~&;XJuu#sdAFx0Jjs zL77i5 zRXMZwU&qsFppby%C7u+lUOz*!^XaYb>5-NKb-lfRA5c zeCU<=IfFKQ1}_#wd2by7wg!$k8!t(`@_BCs~1YsnkOQ z6TFIX2a1aN{UO&alY4Cz&VUKIQZ{8FfIwCnr-R!k94^vTdR>ZNJqw*cTuRnhYgTh7gFU{aSe)eyyV>UNgwt)w zO7_OyuKFQZX*s)YU9C;8mR%zh?3uYJi|eY2&n#{~`JZPc@tAg`7m$2cbvwAwc%%w0 z-lt#`H2f)E+d^P5j<3azx>p*BugL$kpNQ3k>HAI!eYjGxnCrPNcil(HW?dp{uB#NX zvgCy9GN0K4--|P(;*QoOU1$X?=50exxmy>iXAqaiwaSBH0p^JIoVy~3$IbyUqKXBZ zq=vC76H($bVPK)YF;>(9m0HdB;vD?xpWRpVIqVC_{_SCwJmow31bhC73cZw(N92o+ z@iVM+V+jrgfPjkWtsM~RnlSgb3Ez`>gy56IZuZ|q+^u{v>P^Bx*5))wh43KJkz|Av z*w0W3y?l0!qI@JiAKjw)>b5PwF_o4(Cpmy|k*$(dm!%Uioj6m$<^!h`VsHRi^-T)b zYMLKKg0C|u$+x*$Xm7Xp%Q?FrRF6sRh{MYsPHzfhjKsH6%Auyr7l8%$xAG}VeF1mr z`aESpoagrJHA(LxZY0eJ&9#nOOh05m9BXZdhQd5tVK+s0S(U^Q&x>hgK1bF087A>W z^2*cx`@aClI4E97o%(eCkc(SlxBwdp@jwZlKE;mAB*jlP#`#v2^Dfht{_gQ+M_dWz zft&!bM-yx%yc?;-`S!cTX=D9MPVu%{>SwFv;g@lScVlfAc?4i3KaTi2XHmzHYl-D! z2so#U)Fhw^wEVCa8VE;Mj9P<^&|zG{Xen1+%b$pwE6QUFLwnJW1YpGqt})VrXZF;c zKO(rtqywz2fpRr)V`NmjU$nue=^ypgiY?#)0X}oD!xK|%O!!&Lmqkhk5}?eiV^g%1 zWxoi{yL}LrRcXC6ahBoiM6cp*X1q1}iG?1wXY=5F#L4{$%qxD%-5vvrD=~K)HYX25 zuZF(@&OmpiX?K`8thWI*O$`6~jF1Mo?un{)1Yl%MoS$(eG8Ly`oY3FvXph~Q2K>1) zxp_Y@Fjz%Fu50anHGDm&p13i6r_Yq5WJ%Fi)~?Nv%1)cNaGJ(ML-*=&S?bhW$|X+0 zCoc8D%=hX_=xxIx8w=R7n5F9fq3Rp_<7~dSH+Ey&wr#UX8oRM=JB@9tY3#;qY}=bO zw)O7we1CdB^B3&SnYquoG}vWKEq<@S6srV#E5%9$)d4m>xN`U2en7 zjF8k|80Js3XΠ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%0Aau` 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 S1kk(y{v<`^L@I>zzx*HJ*URbv literal 0 HcmV?d00001 diff --git a/images/doublediscoNN.png b/images/doublediscoNN.png new file mode 100644 index 0000000000000000000000000000000000000000..aca7296eaf292bf41309cbb0d6c31cc465e11163 GIT binary patch literal 256934 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 z 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 S7xumZ{>e!xOO}Wm`~M$IE!v9! literal 0 HcmV?d00001 diff --git a/images/hig21002_bdtscores.png b/images/hig21002_bdtscores.png new file mode 100644 index 0000000000000000000000000000000000000000..4501360677be528a27b9f62e43e6183c1c9eff24 GIT binary patch literal 333803 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 z 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≥XUq2Q*kPeNRy~=vPhfd*#a( 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�qk;{iTQ2H8KWFu0rC`G!brYvsZCSDJ#)!3fP!&J!nLbU z82Ku1Yme%@x?((tLDQF6@4R|ds!#qTowfG?6{n;hf1N?pQLPBtp3T|g+l4j<)<@!_ zkM%W#F`KZlv&ODd-4XOo9Ce-QNC~W3k1LwL=&rMO;4m{#z%oJg0_10b(b}BO$_oZO z&nCx2Us`hcwF2c)mGdm@94Kee}EBA>7_dK>ccz&JibdofDw7umDSi;E{|wm1j`6pTH}kL@^(YhM{5*0c!;IBvyf_Tsd9$V7tPO93d>l zZ$!=KSf*Dn>#B#Y-Mwl;ySmO*G;m=YNny<;``4QsI}vbWUECz#T4!=!sje0|lEhA-LE_{A1NaFjG&^71&jxd1NLxCcHrL=#M+duymZUdw^+}Wq4eo8=o{%n^lnE|2D+G|FQ#F zp8q!+oaJ#%qT-*O;MkGUooj@0B2Ssd=jB}+@~~MlX$U=fVPUB90ut99ZZ>nJrGxz3 zTJr=M(i(pOkSCYW=K!cK_0|2J&r+} z6Y%I~^UD$<*vCbjjL!xVVjFaup(jl_t*U?e285J1;4J(=IV+Ox;Frn z;g2N%&*mvd74cE~1(4Hcf{39nr4+n(IzgQ8Se@6Z0fMYfK+0x~Al@Iy1PL*O`5<+c zrZQbano+u6i|f}x^_Y+Svqv%?d&vdyWDT5;hKbtMuE%UMYFAYcox4hk;e6z(sP*kM zavJ!ZW*pIfk1+itM-3J#^P(s*`K#A!e%p(X2gde=UHaX}H=}AuVvjEdyGeM1Ivcbl zBRhz@Mn&G4e*QKbdv8smfbkWPG?#T%T&^Ck4JKsw;ng9UR42>T=k=p>UKJz4Ysy#+ zjH!Jc)Z)sqXk`^#?kMiak*qUtBKDDwf8XIOiMNUcs-SN(6Y=Y~?kG6AgR^Ss1Y813 zQ9c=O+nN3(^<093jKuSDtsyT$RNu3!Nx zNEJzVa;r9M@q!Dnuz@mQ19TM{Fu6YMwT>;l?=fuX{psa2%GlK z%WKAp=2$i#w&{(;n7%p87sG8s#|D#rNf*289`XAClWsNQk;s>lSXJLUto;DnVSQ=6j6F`gs1a0{wxI?pEcNxxUjaN`ySc z6S)t3zUa;1rwD+F!f1fkfFP?w(SH4RgT}}op*U7)AmLD)cqsc=%6tT;`S4M zl(ji3cfW7Ut3=!&QZ(}96SV4`?JeknJos2aZib*AB`yi%E}4kZT#d1E}ka65g6O{pOxx(5Cm%o2G|qAPD>Y+7DNHj04x5mEUxpZ55_ikHhHWbu`R@F0k4Cg{ehk}_Vz-}Ppe#dtsgoCKJ!bRnEdSGI?Mk7PnilX4-b3$4)l z+dHt$J3#@J4k8iTN5cpZV!z$yZWJL^wbFgrcBsO`7F2Pz_cTRyVDxBU19d^rA~Kvt zd-BC?;19JCeXI3gBuf!wi-A24lXrzr+G-H+q1w$rt}&_Jv~6>u&6l>xC%3=B?8V|> zbi~5QA5SBnEvNi`Fo{pmi$UYZE9KaUtKcvy@bYBe+~9DVl%B33@$%{ot9n0He!$0~ zKMLu3U)GGi`lLz~Gl}|=yKU&+bsF$C9FGshRp4YGrJ6lW;D5z5mcbs%|M2 z8MyY%Cs6_QFw;0DU$8S!f)a7=CIl#(Zu^bZIlHuI`y3Y#74;iCbP;=Wh0#8ZR`)y_ zY{oS52eU8#XvO7*o4_Bv8UtQ5?+L2%YRffH;xfAI7t(aTuO503NP)$1!?n5M;j-oo zWeor5kpZX$m0-#YXHLF7Rn+tRG&Gh4gVjTQX%==J>iOOx^I01xHTfkqf#P`O1B_yQ z$#RGK+wMHL3{VhO#g;=Buoi(v;hmpGWWMN^ZY^e^-Ul)f*RL5iiO&EoaBQOYAyPyt z>?Qr2imxRcxAB5fRNrM&q^ZoD`E2{7mQ2A;SI%X;SeS0AQV1gh;_vjt-!U#WwH4`d zytZxIogGuU1F=RFG^(fZRWHNHcEvK6gK)MjWFx!M!k5>>viR8NSFL+?0?nt?$u8&e zPslc{3ixn+TBeq0KLKl9Ju|89+#NKz1nS#j3-#|;?B@hIE7ZuJbf;VfMoj`^`qKiqYj!{TFp#K7 zu0Ws>cRtenr9>!`^l_){d){DQI$yuFzLfLoyfHkTT6qOc`VycvhXH9RioWO`R1-)m zLH8?D#`?jTDJ93;Dfm9sB#!<0+MBx1@AI@O57|LZB;_OSjJKbO>xjmEDsP)6e8TrK zli`gq8RpJ-z5e$z^o}ZB!@|(_=gwpz_Rqm5tlnrb6EUo)G2vX4v1PyV-7ef2oI7HPc?J0?wwQ?Nye(uy|~ER zYtn;j5|!u3lP|-iUgoXkNm345mDcO22}F?A*I{Rze0VLtY*4cV8f=cehnTo5eeHEX zTfKc;L)hllg~r|TpII}DcfZa!NbEVA!^(Zar$^s?x%#`?)t%X|>8 zFbWzbbY55w-iCfpQD6|>(fY%+GZelt8ULI_)F5?{TWl82hI5@K+ijw4Dd_&-601V| z-q{aa@<^ZkG?I_YL+j|Qa<+;mSxJPBV)o(<6Lyvt`$)py)r82 zHj_4I`}+BJ)sb?gNS*I(szBqz07i=xvtQR{IDSRb$vgsj=qL~FpkUm|Te z<#n`qSHNjxTPsBPd^;c7;{-$XB?C1Q;PL4FrAZMdYyRgv;$& z2q}sYvayyba}VaG;ZFptc7R<-1)xyl@-mn^m`^FqM;)v+;_GYc@&H{ ze2RvAH|?YES#cy-RjU%9vxOJA{WQMLv>!2zp7qgcI3m{h0ZX5|E2R3y7jAwjkMHiY z22U1!n$0f0e0c&KqGH{G+~}Hw^{%p%QyFre$O9OaL_YS6)l_ei+sk6fb|^X*6SZ)d z=KU%*aEFX-xI||5FQ*(Hx3~>A`4ZQ;1klI-LKOUN5%`8U&sd`$;1LDW3^g*fgdTrH z+=R@dfN5AQyrqU@y{kejPf$=H^(tftf!8NDL4$hS>RvMADS5^mUrHShwmTwfa%$FJ zir)c4=Q5~gkIywy>qv$Rd7o|(mLlciI~#2%s0%2Ooap?M`Sk7 zP=p+B^_ENZ$-Wjg69~OPf1XQ_S@pE5=gWT&30a~3QqZsBNbVvz>`08Mf-Z<=3dP3i zsFJH|^Xv^KeYoVC4~@6fw8MSUyf(&mp?FHY*P#4A%$2i9JqlEf`zsTs@|{UU8`G)V zYW@USm%!U6Ml&u9J^auIik1Nzgv%BH80T(%u8cs1v@JG$TpQxbP*M+9$}&;VON=hi z6g+dQz)+A+;fO>UOj84?uBeT%;HyoB#@AcDsUTcUKI{z{NbajdfI3jUb)_RH$BH-Z zv!@ABA0&KrCx-hEM=RG_KCF;G?aOL+x_AM|trpSCKtsA)K}gHBP|H^ee?trEvZMHG z(AnZd_cCscM6?sq1A|f*E4AH$h($XTN)9$uPq?0mn(mzDIt9^`xI(`@|BZNVOHeM#q5rRW}Z?4lrILf|M$UXKz$fj*sI(7KDVQ7yN z2n0(PH((Vut+=dW3{R>%-E}`$24W+wtC6jo(-lQq3s!t$1PA8*47%lL56VCah7W2~?D=NU!jEKygnc z9Gm+%0{Vpx(`bK)i?j{j`*nH%vA%A7YGey6FAX^me{~)o-LXi2BCc2-A%>wFlyL7> zQ)CQgRgeHX z87!9n9^4t-b0&79m18I2W`+_|btQ$ZYY5wZCVYp)QTB|$ataX_`oRK0p1k3lMH4WN zO?yS*aG9Ol=7ZvCeHlgKNBO=&z)m} zhs5(*+=VSe647V&{oPS`AhIs@Hg5Dmp^Yyx@mYzoi%n)6g&TY*Fx^dpDBjp-kFCE^RG%_y;_JRp@#*M966e<& zsXCkh^&yjIvDf)gdvoi#M^e2Pi76dT{iRlK@2WC>BNE-W`yNiw|JSvL1M_|Db)DZD zDcyc0x&l>NSK2$DvniN!aK~hrB#`@X_1eaG0Z-^~VCeg{k|RREHJ2Vh@P^$5yupAZ zb(90<;x|H)l2GO=gb+~;D!rmL=lXZ zj%3LLLm>7J*@Y4B3O`CSrC8@~tMOeh3a26xxhXL!nSAWS+mu7Tznf30-z{KPtRk-< zaV`wA8b)^g@fEpLjco@&gM^zKz5pYk5O47v1vMvtKvWs>2RIR>sl24_LBvfK;FRbx z*m@7;WR?nR4jt7EQFxZRmCIHc5H&03kglXtMJ7qYjg=F~Hi0g}H*M&m7{D<4d7}pg zm_ajacMpl#cMIg_?SmIUCz$JE=_v$W|IxL`C9O8UEwv4(UGB;v$83maZ>pX$hEzAN z&XaweGpJ;``LVpe;mRbS7WdvBX*p}Ea58w7=x2fX_$=|Hx~gdYv5YFL_nO#^P3oX0 zeC&V|tV?`bbDy`-G`#u1goPL{r!=OW5Ig8cS?WB_`R&AGpUD}9d;)=Uy}SP$9IbQC zOUC@N;5Wr}6?duxqzy(55vY*_??u(3SB5jL3_UDjbMe)!%wWLJ!#6dgQsd`Z-kozf zjV+{u1(6)vRl|L%mvgC$3eWNuF^y}kLtNtfPG-0?$$!9vd!a^Xym7wUp6H7mmEr&x z8<7m%qGud=&p0AK?S70{^u+~qedy7dOy!|HF>Wh-jV=VJ+nf=^j9i&%k1Y3us;7I(Kx-SGg@AUBw?+?nes}*8kr7nqjjAfCe<_Y??@<{ zD(Hz9DSQvTmsvvVKa1cEN6Jb$#O8pguw<@#n4w`>_u}rs^A2}foGm?ixL{Hcq2^N$ zP+AIEd%zP0QYdqd73>&+yC+xw4b&+Bi_}y{44nXY=-Dh-McJ78Q!tm9)sajbJ3n5j zFai32SK+36C$Nxe9Cj9M+-4j>Vg=pH>pO6koa6VB?Td7sk0ZsTr*ArU&783dZqPxG z7Mw4z)P^>g`kTm}d9s%B zqBra~GmEVM>AnV>rU>NK~*f$|*?=l+&kv9oK()PD{-#1jn2Xk)r= zO3+MwAGPw4_ja_R`DV~hT@QjRiqhI~>A-;pvLI#l3A<-+2GNj>Pug?YY29tM=Z!&? zw?`kwlTdNAehl!v@JM4{XM1AK#u>1DdJtKEiT~z~H;}xHxyv}sqMATZr(G;}s@xDc zYjtxmJ>2ack=qSr?h-IN(~UfFfmb}dK>krKhKoV1fCY*sVJ$ID@I6Yukad4X>TAu^ z)KV(&sB@j~8PpRMFLcnkWz*bfAKQVFrvlgK2e1-Wryf9GfAfU)bjo#T3*ve1wTpw} z24({;5BcPK@ONe<*E(g#aCk2BlkiR5+gSBxW;Zn?M+M>tP?nMnQY3{hfE0d%tUWHY zCa_f|Tk+e}`|bH!SYi+t=lYu+B^b~wK1EF69MYSLT$T)(7~YF{5F#$J=Y~h(z@9Vk zY_?u5vNN!HX1uLr z7d}@Z#x&-w0nHet>aM;}%qfB9LwA*D6FQ1mCR_oJoo z))D@OAzQF1iwJYlsH{`V<`vro-rnl3Q>!n7tF#vU3_yTGBb9qMo4dp3kMFq>Vt~Xy z>>GB8bE5u+?nE^eYGhG79b**1k^%janDCa<40<;(uIyIIVNGE|V%3a+ry+J4K<&NNzT_aac>Az+!h>^F2N{Hw71 z2_IXpl3^NW>n?p?G*TAmcfCI3y6Z#KXU3DWqfyv4mfvi20MV|*F5_OoCm35cTAYAN-NtQx zp!a(9{WKF({{c*KcgqYAe6}hI)&TUbHd4nhCgMVfQdDBVV$b$#@-AyPEGQT-jGSWw zC>f$pR9p2@jCwaa1sRpOsgh0SQ-C?4S+!C0%E!B^@n6o^2df*UYZyiB8$A_!p_=W6 za@B!Q*_|5-w==TGy0eH-cZ-Kd=RUIL*Pgp;^#jV@Lomh-N!=o#OvNAOzwS{FWRK=B zKcV`uN1Ls+V^*ET_liQ1S-DLK@UV6Ld7n1%iINSbj_uE{`c%J_A+i;~)HQypv?|z8 zn^b?=rnQv^K$l}P(|00K(yxSZqtGit01+6zEsyk8irM;`!@6dr~zRZqlQ zA|FTd8Cr1)m{?VALy=VO@Vw>K6%UHDyc`}!{Lb!JbMi^c^0aqAu+%0J&hWE8%PumfgGD9~LCa^HR{`ZkZ^LpD#_5@PXwxX`xCWvnjd zgq^XHOAJ)Du5Ym(1SnkmHK(4;Ag&C05TmgB zZ|fYDyru^XRbB_Ha!xhmkJCpWNDZgcex-&$!pn+vc44^@kXFix5@;}H5Ctn z$6U|V*$g))g`&ZHV{$g<9da_!yJa}f726f)-okGkX|_Ssv-%QLnp4U;=^%`FwMD@M zJVC$1s*AT@BdMJ{utgK0>u-sL*B#-3I^{X5ZFi6L&eHb{tG1`<(0FL zDmlcpE6Ds%)@zzk@ z6s>syfk}hg{jeMYH9x<1mKL;G$B)GB!dQO(>=)tm{a;4G{+})*t!p zAI;x!GZtZ_RrvIPZ+B?hy7d!O5 CMS Machine Learning Documentation

Welcome to the documentation hub for the CMS Machine Learning Group! The goal of this page is to provide CMS analyzers a centralized place to gather machine learning information relevant to their work. However, we are not seeking to rewrite external documentation. Whenever applicable, we will link to external documentation, such as the iML groups HEP Living Review or their ML Resources repository. What you will find here are pages covering:

  • ML best practices
  • How to optimize a NN
  • Common pitfalls for CMS analyzers
  • Direct and indirect inferencing using a variety of ML packages
  • How to get a model integrated into CMSSW

And much more!

If you think we are missing some important information, please contact the ML Knowledge Subgroup!


Last update: December 5, 2023
\ No newline at end of file + CMS Machine Learning Documentation

Welcome to the documentation hub for the CMS Machine Learning Group! The goal of this page is to provide CMS analyzers a centralized place to gather machine learning information relevant to their work. However, we are not seeking to rewrite external documentation. Whenever applicable, we will link to external documentation, such as the iML groups HEP Living Review or their ML Resources repository. What you will find here are pages covering:

  • ML best practices
  • How to optimize a NN
  • Common pitfalls for CMS analyzers
  • Direct and indirect inferencing using a variety of ML packages
  • How to get a model integrated into CMSSW

And much more!

If you think we are missing some important information, please contact the ML Knowledge Subgroup!


Last update: December 5, 2023
\ No newline at end of file diff --git a/inference/checklist.html b/inference/checklist.html index f1d7831..4f2d9ac 100644 --- a/inference/checklist.html +++ b/inference/checklist.html @@ -1 +1 @@ - Integration checklist - CMS Machine Learning Documentation

Integration checklist

Todo.


Last update: December 5, 2023
\ No newline at end of file + Integration checklist - CMS Machine Learning Documentation

Integration checklist

Todo.


Last update: December 5, 2023
\ No newline at end of file diff --git a/inference/conifer.html b/inference/conifer.html index 79e7383..9455816 100644 --- a/inference/conifer.html +++ b/inference/conifer.html @@ -1,4 +1,4 @@ - conifer - CMS Machine Learning Documentation

Direct inference with conifer

drawing

Introduction

conifer is a Python package developed by the Fast Machine Learning Lab for the deployment of Boosted Decision Trees in FPGAs for Level 1 Trigger applications. Documentation, examples, and tutorials are available from the conifer website, GitHub, and the hls4ml tutorial respectively. conifer is on the Python Package Index and can be installed like pip install conifer. Targeting FPGAs requires Xilinx's Vivado/Vitis suite of software. Here's a brief summary of features:

  • conversion from common BDT training frameworks: scikit-learn, XGBoost, Tensorflow Decision Forests (TF DF), TMVA, and ONNX
  • conversion to FPGA firmware with backends: HLS (C++ for FPGA), VHDL, C++ (for CPU)
  • utilities for bit- and cycle-accurate firmware simulation, and interface to FPGA synthesis tools for evaluation and deployment from Python

Emulation in CMSSW

All L1T algorithms require bit-exact emulation for performance studies and validation of the hardware system. For conifer this is provided with a single header file at L1Trigger/Phase2L1ParticleFlow/interface/conifer.h. The user must also provide the BDT JSON file exported from the conifer Python tool for their model. JSON loading in CMSSW uses the nlohmann/json external.

Both the conifer FPGA firmware and C++ emulation use Xilinx's arbitrary precision types for fixed-point arithmetic (hls external of CMSSW). This is cheaper and faster in the FPGA fabric than floating-point types. An important part of the model preparation process is choosing the proper fixed-point data types to avoid loss of performance compared to the trained model. Input preprocessing, in particular scaling, can help constrain the input variables to a smaller numerical range, but may also have a hardware cost to implement. In C++ the arbitrary precision types are specified like: ap_fixed<width, integer, rounding mode, saturation mode>.

Minimal preparation from Python:

import conifer
+ conifer - CMS Machine Learning Documentation       

Direct inference with conifer

drawing

Introduction

conifer is a Python package developed by the Fast Machine Learning Lab for the deployment of Boosted Decision Trees in FPGAs for Level 1 Trigger applications. Documentation, examples, and tutorials are available from the conifer website, GitHub, and the hls4ml tutorial respectively. conifer is on the Python Package Index and can be installed like pip install conifer. Targeting FPGAs requires Xilinx's Vivado/Vitis suite of software. Here's a brief summary of features:

  • conversion from common BDT training frameworks: scikit-learn, XGBoost, Tensorflow Decision Forests (TF DF), TMVA, and ONNX
  • conversion to FPGA firmware with backends: HLS (C++ for FPGA), VHDL, C++ (for CPU)
  • utilities for bit- and cycle-accurate firmware simulation, and interface to FPGA synthesis tools for evaluation and deployment from Python

Emulation in CMSSW

All L1T algorithms require bit-exact emulation for performance studies and validation of the hardware system. For conifer this is provided with a single header file at L1Trigger/Phase2L1ParticleFlow/interface/conifer.h. The user must also provide the BDT JSON file exported from the conifer Python tool for their model. JSON loading in CMSSW uses the nlohmann/json external.

Both the conifer FPGA firmware and C++ emulation use Xilinx's arbitrary precision types for fixed-point arithmetic (hls external of CMSSW). This is cheaper and faster in the FPGA fabric than floating-point types. An important part of the model preparation process is choosing the proper fixed-point data types to avoid loss of performance compared to the trained model. Input preprocessing, in particular scaling, can help constrain the input variables to a smaller numerical range, but may also have a hardware cost to implement. In C++ the arbitrary precision types are specified like: ap_fixed<width, integer, rounding mode, saturation mode>.

Minimal preparation from Python:

import conifer
 model = conifer. ... # convert or load a conifer model
 # e.g. model = conifer.converters.convert_from_xgboost(xgboost_model)
 model.save('my_bdt.json')
diff --git a/inference/hls4ml.html b/inference/hls4ml.html
index 7764c9f..9cae5a1 100644
--- a/inference/hls4ml.html
+++ b/inference/hls4ml.html
@@ -1 +1 @@
- hls4ml - CMS Machine Learning Documentation       

Direct inference with hls4ml

drawing

hls4ml is a Python package developed by the Fast Machine Learning Lab. It's primary purpose is to create firmware implementations of machine learning (ML) models to be run on FPGAs. The package interfaces with a high-level synthesis (HLS) backend (i.e. Xilinx Vivado HLS) to transpile the ML model into hardware description language (HDL). The primary hls4ml documentation, including API reference pages, is located here.

drawing

The main hls4ml tutorial code is kept on GitHub. Users are welcome to walk through the notebooks at their own pace. There is also a set of slides linked to the README.

That said, there have been several cases where the hls4ml developers have given live demonstrations and tutorials. Below is a non-exhaustive list of tutorials given in the last few years (newest on top).

Workshop/Conference Date Links
23rd Virtual IEEE Real Time Conference August 03, 2022 Indico
2022 CMS ML Town Hall July 22, 2022 Contribution Link
a3d3 hls4ml @ Snowmass CSS 2022: Tutorial July 21, 2022 Slides, Recording, JupyterHub
Fast Machine Learning for Science Workshop December 3, 2020 Indico, Slides, GitHub, Interactive Notebooks
hls4ml @ UZH ML Workshop November 17, 2020 Indico, Slides
ICCAD 2020 November 5, 2020 https://events-siteplex.confcats.io/iccad2022/wp-content/uploads/sites/72/2021/12/2020_ICCAD_ConferenceProgram.pdf, GitHub
4th IML Workshop October 19, 2020 Indico, Slides, Instructions, Notebooks, Recording
22nd Virtual IEEE Real Time Conference October 15, 2020 Indico, Slides, Notebooks
30th International Conference on Field-Programmable Logic and Applications September 4, 2020 Program
hls4ml tutorial @ CERN June 3, 2020 Indico, Slides, Notebooks
Fast Machine Learning September 12, 2019 Indico
1st Real Time Analysis Workshop, Université Paris-Saclay July 16, 2019 Indico, Slides, Autoencoder Tutorial

Last update: December 5, 2023
\ No newline at end of file + hls4ml - CMS Machine Learning Documentation

Direct inference with hls4ml

drawing

hls4ml is a Python package developed by the Fast Machine Learning Lab. It's primary purpose is to create firmware implementations of machine learning (ML) models to be run on FPGAs. The package interfaces with a high-level synthesis (HLS) backend (i.e. Xilinx Vivado HLS) to transpile the ML model into hardware description language (HDL). The primary hls4ml documentation, including API reference pages, is located here.

drawing

The main hls4ml tutorial code is kept on GitHub. Users are welcome to walk through the notebooks at their own pace. There is also a set of slides linked to the README.

That said, there have been several cases where the hls4ml developers have given live demonstrations and tutorials. Below is a non-exhaustive list of tutorials given in the last few years (newest on top).

Workshop/Conference Date Links
23rd Virtual IEEE Real Time Conference August 03, 2022 Indico
2022 CMS ML Town Hall July 22, 2022 Contribution Link
a3d3 hls4ml @ Snowmass CSS 2022: Tutorial July 21, 2022 Slides, Recording, JupyterHub
Fast Machine Learning for Science Workshop December 3, 2020 Indico, Slides, GitHub, Interactive Notebooks
hls4ml @ UZH ML Workshop November 17, 2020 Indico, Slides
ICCAD 2020 November 5, 2020 https://events-siteplex.confcats.io/iccad2022/wp-content/uploads/sites/72/2021/12/2020_ICCAD_ConferenceProgram.pdf, GitHub
4th IML Workshop October 19, 2020 Indico, Slides, Instructions, Notebooks, Recording
22nd Virtual IEEE Real Time Conference October 15, 2020 Indico, Slides, Notebooks
30th International Conference on Field-Programmable Logic and Applications September 4, 2020 Program
hls4ml tutorial @ CERN June 3, 2020 Indico, Slides, Notebooks
Fast Machine Learning September 12, 2019 Indico
1st Real Time Analysis Workshop, Université Paris-Saclay July 16, 2019 Indico, Slides, Autoencoder Tutorial

Last update: December 5, 2023
\ No newline at end of file diff --git a/inference/onnx.html b/inference/onnx.html index 7d46bf4..989c601 100644 --- a/inference/onnx.html +++ b/inference/onnx.html @@ -1,4 +1,4 @@ - ONNX - CMS Machine Learning Documentation

Direct inference with ONNX Runtime

ONNX is an open format built to represent machine learning models. It is designed to improve interoperability across a variety of frameworks and platforms in the AI tools community—most deep learning frameworks (e.g. XGBoost, TensorFlow, PyTorch which are frequently used in CMS) support converting their model into the ONNX format or loading a model from an ONNX format.

The figure showing the ONNX interoperability. (Source from website.)

ONNX Runtime is a tool aiming for the acceleration of machine learning inferencing across a variety of deployment platforms. It allows to "run any ONNX model using a single set of inference APIs that provide access to the best hardware acceleration available". It includes "built-in optimization features that trim and consolidate nodes without impacting model accuracy."

The CMSSW interface to ONNX Runtime is avaiable since CMSSW_11_1_X (cmssw#28112, cmsdist#5020). Its functionality is improved in CMSSW_11_2_X. The final implementation is also backported to CMSSW_10_6_X to facilitate Run 2 UL data reprocessing. The inference of a number of deep learning tagger models (e.g. DeepJet, DeepTauID, ParticleNet, DeepDoubleX, etc.) has been made with ONNX Runtime in the routine of UL processing and has gained substantial speedup.

On this page, we will use a simple example to show how to use ONNX Runtime for deep learning model inference in the CMSSW framework, both in C++ (e.g. to process the MiniAOD file) and in Python (e.g. using NanoAOD-tools to process the NanoAODs). This may help readers who will deploy an ONNX model into their analyses or in the CMSSW framework.

Software Setup

We use CMSSW_11_2_5_patch2 to show the simple example for ONNX Runtime inference. The example can also work under the new 12 releases (note that inference with C++ can also run on CMSSW_10_6_X)

 1
+ ONNX - CMS Machine Learning Documentation       

Direct inference with ONNX Runtime

ONNX is an open format built to represent machine learning models. It is designed to improve interoperability across a variety of frameworks and platforms in the AI tools community—most deep learning frameworks (e.g. XGBoost, TensorFlow, PyTorch which are frequently used in CMS) support converting their model into the ONNX format or loading a model from an ONNX format.

The figure showing the ONNX interoperability. (Source from website.)

ONNX Runtime is a tool aiming for the acceleration of machine learning inferencing across a variety of deployment platforms. It allows to "run any ONNX model using a single set of inference APIs that provide access to the best hardware acceleration available". It includes "built-in optimization features that trim and consolidate nodes without impacting model accuracy."

The CMSSW interface to ONNX Runtime is avaiable since CMSSW_11_1_X (cmssw#28112, cmsdist#5020). Its functionality is improved in CMSSW_11_2_X. The final implementation is also backported to CMSSW_10_6_X to facilitate Run 2 UL data reprocessing. The inference of a number of deep learning tagger models (e.g. DeepJet, DeepTauID, ParticleNet, DeepDoubleX, etc.) has been made with ONNX Runtime in the routine of UL processing and has gained substantial speedup.

On this page, we will use a simple example to show how to use ONNX Runtime for deep learning model inference in the CMSSW framework, both in C++ (e.g. to process the MiniAOD file) and in Python (e.g. using NanoAOD-tools to process the NanoAODs). This may help readers who will deploy an ONNX model into their analyses or in the CMSSW framework.

Software Setup

We use CMSSW_11_2_5_patch2 to show the simple example for ONNX Runtime inference. The example can also work under the new 12 releases (note that inference with C++ can also run on CMSSW_10_6_X)

 1
  2
  3
  4
diff --git a/inference/particlenet.html b/inference/particlenet.html
index 09038eb..19818ae 100644
--- a/inference/particlenet.html
+++ b/inference/particlenet.html
@@ -1,4 +1,4 @@
- ParticleNet - CMS Machine Learning Documentation       

ParticleNet

ParticleNet [arXiv:1902.08570] is an advanced neural network architecture that has many applications in CMS, including heavy flavour jet tagging, jet mass regression, etc. The network is fed by various low-level point-like objects as input, e.g., the particle-flow candidates, to predict a feature of a jet.

The full architecture of the ParticleNet model. We'll walk through the details in the following sections.

On this page, we introduce several user-specific aspects of the ParticleNet model. We cover the following items in three sections:

  1. An introduction to ParticleNet, including

    • a general description of ParticleNet
    • the advantages brought from the architecture by concept
    • a sketch of ParticleNet applications in CMS and other relevant works
  2. An introduction to Weaver and model implementations, introduced in a step-by-step manner:

    • build three network models and understand them from the technical side; use the out-of-the-box commands to run these examples on a benchmark task. The three networks are (1) a simple feed-forward NN, (2) a DeepAK8 model (based on 1D CNN), and eventually (3) the ParticleNet model (based on DGCNN).
    • try to reproduce the original performance and make the ROC plots.

    This section is friendly to the ML newcomers. The goal is to help readers understand the underlying structure of the "ParticleNet".

  3. Tuning the ParticleNet model, including

    • tips for readers who are using/modifying the ParticleNet model to achieve a better performance

    This section can be helpful in practice. It provides tips on model training, tunning, validation, etc. It targets the situations when readers apply their own ParticleNet (or ParticleNet-like) model to the custom task.

cms-ml/documentation

ParticleNet

ParticleNet [arXiv:1902.08570] is an advanced neural network architecture that has many applications in CMS, including heavy flavour jet tagging, jet mass regression, etc. The network is fed by various low-level point-like objects as input, e.g., the particle-flow candidates, to predict a feature of a jet.

The full architecture of the ParticleNet model. We'll walk through the details in the following sections.

On this page, we introduce several user-specific aspects of the ParticleNet model. We cover the following items in three sections:

  1. An introduction to ParticleNet, including

    • a general description of ParticleNet
    • the advantages brought from the architecture by concept
    • a sketch of ParticleNet applications in CMS and other relevant works
  2. An introduction to Weaver and model implementations, introduced in a step-by-step manner:

    • build three network models and understand them from the technical side; use the out-of-the-box commands to run these examples on a benchmark task. The three networks are (1) a simple feed-forward NN, (2) a DeepAK8 model (based on 1D CNN), and eventually (3) the ParticleNet model (based on DGCNN).
    • try to reproduce the original performance and make the ROC plots.

    This section is friendly to the ML newcomers. The goal is to help readers understand the underlying structure of the "ParticleNet".

  3. Tuning the ParticleNet model, including

    • tips for readers who are using/modifying the ParticleNet model to achieve a better performance

    This section can be helpful in practice. It provides tips on model training, tunning, validation, etc. It targets the situations when readers apply their own ParticleNet (or ParticleNet-like) model to the custom task.


Corresponding persons:

  • Huilin Qu, Loukas Gouskos (original developers of ParticleNet)
  • Congqiao Li (author of the page)

Introduction to ParticleNet

1. General description

ParticleNet is a graph neural net (GNN) model. The key ingredient of ParticleNet is the graph convolutional operation, i.e., the edge convolution (EdgeConv) and the dynamic graph CNN (DGCNN) method [arXiv:1801.07829] applied on the "point cloud" data structure.

We will disassemble the ParticleNet model and provide a detailed exploration in the next section, but here we briefly explain the key features of the model.

Intuitively, ParticleNet treats all candidates inside an object as a "point cloud", which is a permutational-invariant set of points (e.g. a set of PF candidates), each carrying a feature vector (η, φ, pT, charge, etc.). The DGCNN uses the EdgeConv operation to exploit their spatial correlations (two-dimensional on the η-φ plain) by finding the k-nearest neighbours of each point and generate a new latent graph layer where points are scattered on a high-dimensional latent space. This is a graph-type analogue of the classical 2D convolution operation, which acts on a regular 2D grid (e.g., a picture) using a 3×3 local patch to explore the relations of a single-pixel with its 8 nearest pixels, then generates a new 2D grid.

The cartoon illustrates the convolutional operation acted on the regular grid and on the point cloud (plot from ML4Jets 2018 talk).

As a consequence, the EdgeConv operation transforms the graph to a new graph, which has a changed spatial relationship among points. It then acts on the second graph to produce the third graph, showing the stackability of the convolution operation. This illustrates the "dynamic" property as the graph topology changes after each EdgeConv layer.

2. Advantage

By concept, the advantage of the network may come from exploiting the permutational-invariant symmetry of the points, which is intrinsic to our physics objects. This symmetry is held naturally in a point cloud representation.

In a recent study on jet physics or event-based analysis using ML techniques, there are increasing interest to explore the point cloud data structure. We explain here conceptually why a "point cloud" representation outperforms the classical ones, including the variable-length 2D vector structure passing to a 1D CNN or any type of RNN, and imaged-based representation passing through a 2D CNN. By using the 1D CNN, the points (PF candidates) are more often ordered by pT to fix on the 1D grid. Only correlations with neighbouring points with similar pT are learned by the network with a convolution operation. The Long Short-Term Memory (LSTM) type recurrent neural network (RNN) provides the flexibility to feed in a variant-length sequence and has a "memory" mechanism to cooperate the information it learns from an early node to the latest node. The concern is that such ordering of the sequence is somewhat artificial, and not an underlying property that an NN must learn to accomplish the classification task. As a comparison, in the task of the natural language processing where LSTM has a huge advantage, the order of words are important characteristic of a language itself (reflects the "grammar" in some circumstances) and is a feature the NN must learn to master the language. The imaged-based data explored by a 2D CNN stems from the image recognition task. A jet image with proper standardization is usually performed before feeding into the network. In this sense, it lacks local features which the 2D local patch is better at capturing, e.g. the ear of the cat that a local patch can capture by scanning over the entire image. The jet image is appearing to hold the features globally (e.g. two-prong structure for W-tagging). The sparsity of data is another concern in that it introduces redundant information to present a jet on the regular grid, making the network hard to capture the key properties.

Here we briefly summarize the applications and ongoing works on ParticleNet. Public CMS results include

  • large-R jet with R=0.8 tagging (for W/Z/H/t) using ParticleNet [CMS-DP-2020/002]
  • regression on the large-R jet mass based on the ParticleNet model [CMS-DP-2021/017]

ParticleNet architecture is also applied on small radius R=0.4 jets for the b/c-tagging and quark/gluon classification (see this talk (CMS internal)). A recent ongoing work applies the ParticleNet architecture in heavy flavour tagging at HLT (see this talk (CMS internal)). The ParticleNet model is recently updated to ParticleNeXt and see further improvement (see the ML4Jets 2021 talk).

Recent works in the joint field of HEP and ML also shed light on exploiting the point cloud data structure and GNN-based architectures. We see very active progress in recent years. Here list some useful materials for the reader's reference.

  • Some pheno-based work are summarized in the HEP × ML living review, especially in the "graph" and "sets" categories.
  • An overview of GNN applications to CMS, see CMS ML forum (CMS internal). Also see more recent GNN application progress in ML forums: Oct 20, Nov 3.
  • At the time of writing, various novel GNN-based models are explored and introduced in the recent ML4Jets2021 meeting.

Introduction to Weaver and model implementations

Weaver is a machine learning R&D framework for high energy physics (HEP) applications. It trains the neural net with PyTorch and is capable of exporting the model to the ONNX format for fast inference. A detailed guide is presented on Weaver README page.

Now we walk through three solid examples to get you familiar with Weaver. We use the benchmark of the top tagging task [arXiv:1707.08966] in the following example. Some useful information can be found in the "top tagging" section in the IML public datasets webpage (the gDoc).

Our goal is to do some warm-up with Weaver, and more importantly, to explore from a technical side the neural net architectures: a simple multi-layer perceptron (MLP) model, a more complicated "DeepAK8 tagger" model based on 1D CNN with ResNet, and the "ParticleNet model," which is based on DGCNN. We will dig deeper into their implementations in Weaver and try to illustrate as many details as possible. Finally, we compare their performance and see if we can reproduce the benchmark record with the model. Please clone the repo weaver-benchmark and we'll get started. The Weaver repo will be cloned as a submodule.

git clone --recursive https://github.com/colizz/weaver-benchmark.git
 
 # Create a soft link inside weaver so that it can find data/model cards
diff --git a/inference/performance.html b/inference/performance.html
index 0ec2551..ad74892 100644
--- a/inference/performance.html
+++ b/inference/performance.html
@@ -1 +1 @@
- Performance - CMS Machine Learning Documentation       

Performance of inference tools


Last update: December 5, 2023
\ No newline at end of file + Performance - CMS Machine Learning Documentation

Performance of inference tools


Last update: December 5, 2023
\ No newline at end of file diff --git a/inference/pyg.html b/inference/pyg.html index d77fa47..a0e9d9d 100644 --- a/inference/pyg.html +++ b/inference/pyg.html @@ -1,4 +1,4 @@ - PyTorch Geometric - CMS Machine Learning Documentation

PyTorch Geometric

Geometric deep learning (GDL) is an emerging field focused on applying machine learning (ML) techniques to non-Euclidean domains such as graphs, point clouds, and manifolds. The PyTorch Geometric (PyG) library extends PyTorch to include GDL functionality, for example classes necessary to handle data with irregular structure. PyG is introduced at a high level in Fast Graph Representation Learning with PyTorch Geometric and in detail in the PyG docs.

GDL with PyG

A complete reveiw of GDL is available in the following recently-published (and freely-available) textbook: Geometric Deep Learning: Grids, Groups, Graphs, Geodesics, and Gauges. The authors specify several key GDL architectures including convolutional neural networks (CNNs) operating on grids, Deep Sets architectures operating on sets, and graph neural networks (GNNs) operating on graphs, collections of nodes connected by edges. PyG is focused in particular on graph-structured data, which naturally encompases set-structured data. In fact, many state-of-the-art GNN architectures are implemented in PyG (see the docs)! A review of the landscape of GNN architectures is available in Graph Neural Networks: A Review of Methods and Applications.

The Data Class: PyG Graphs

Graphs are data structures designed to encode data structured as a set of objects and relations. Objects are embedded as graph nodes \(u\in\mathcal{V}\), where \(\mathcal{V}\) is the node set. Relations are represented by edges \((i,j)\in\mathcal{E}\) between nodes, where \(\mathcal{E}\) is the edge set. Denote the sizes of the node and edge sets as \(|\mathcal{V}|=n_\mathrm{nodes}\) and \(|\mathcal{E}|=n_\mathrm{edges}\) respectively. The choice of edge connectivity determines the local structure of a graph, which has important downstream effects on graph-based learning algorithms. Graph construction is the process of embedding input data onto a graph structure. Graph-based learning algorithms are correspondingly imbued with a relational inductive bias based on the choice of graph representation; a graph's edge connectivity defines its local structure. The simplest graph construction routine is to construct no edges, yielding a permutation invariant set of objects. On the other hand, fully-connected graphs connect every node-node pair with an edge, yielding \(n_\mathrm{edges}=n_\mathrm{nodes}(n_\mathrm{nodes}-1)/2\) edges. This representation may be feasible for small inputs like particle clouds corresponding to a jet, but is intractible for large-scale applications such as high-pileup tracking datasets. Notably, dynamic graph construction techniques operate on input point clouds, constructing edges on them dynamically during inference. For example, EdgeConv and GravNet GNN layers dynamically construct edges between nodes projected into a latent space; multiple such layers may be applied in sequence, yielding many intermediate graph representations on an input point cloud.

In general, nodes can have positions \(\{p_i\}_{i=1}^{n_\mathrm{nodes}}\), \(p_i\in\mathbb{R}^{n_\mathrm{space\_dim}}\), and features (attributes) \(\{x_i\}_{i=1}^{n_\mathrm{nodes}}\), \(x_i\in\mathbb{R}^{n_\mathrm{node\_dim}}\). In some applications like GNN-based particle tracking, node positions are taken to be the features. In others, e.g. jet identification, positional information may be used to seed dynamic graph consturction while kinematic features are propagated as edge features. Edges, too, can have features \(\{e_{ij}\}_{(i,j)\in\mathcal{E}}\), \(e_{ij}\in\mathbb{R}^{n_\mathrm{edge\_dim}}\), but do not have positions; instead, edges are defined by the nodes they connect, and may therefore be represented by, for example, the distance between the respective node-node pair. In PyG, graphs are stored as instances of the data class, whose fields fully specify the graph:

  • data.x: node feature matrix, \(X\in\mathbb{R}^{n_\mathrm{nodes}\times n_\mathrm{node\_dim}}\)
  • data.edge_index: node indices at each end of each edge, \(I\in\mathbb{R}^{2\times n_\mathrm{edges}}\)
  • data.edge_attr: edge feature matrix, \(E\in\mathbb{R}^{n_\mathrm{edges}\times n_\mathrm{edge\_dim}}\)
  • data.y: training target with arbitary shape (\(y\in\mathbb{R}^{n_\mathrm{nodes}\times n_\mathrm{out}}\) for node-level targets, \(y\in\mathbb{R}^{n_\mathrm{edges}\times n_\mathrm{out}}\) for edge-level targets or \(y\in\mathbb{R}^{1\times n_\mathrm{out}}\) for node-level targets).
  • data.pos: Node position matrix, \(P\in\mathbb{R}^{n_\mathrm{nodes}\times n_\mathrm{space\_dim}}\)

The PyG Introduction By Example tutorial covers the basics of graph creation, batching, transformation, and inference using this data class.

As an example, consider the ZINC chemical compounds dataset, which available as a built-in dataset in PyG:

from torch_geometric.datasets import ZINC
+ PyTorch Geometric - CMS Machine Learning Documentation       

PyTorch Geometric

Geometric deep learning (GDL) is an emerging field focused on applying machine learning (ML) techniques to non-Euclidean domains such as graphs, point clouds, and manifolds. The PyTorch Geometric (PyG) library extends PyTorch to include GDL functionality, for example classes necessary to handle data with irregular structure. PyG is introduced at a high level in Fast Graph Representation Learning with PyTorch Geometric and in detail in the PyG docs.

GDL with PyG

A complete reveiw of GDL is available in the following recently-published (and freely-available) textbook: Geometric Deep Learning: Grids, Groups, Graphs, Geodesics, and Gauges. The authors specify several key GDL architectures including convolutional neural networks (CNNs) operating on grids, Deep Sets architectures operating on sets, and graph neural networks (GNNs) operating on graphs, collections of nodes connected by edges. PyG is focused in particular on graph-structured data, which naturally encompases set-structured data. In fact, many state-of-the-art GNN architectures are implemented in PyG (see the docs)! A review of the landscape of GNN architectures is available in Graph Neural Networks: A Review of Methods and Applications.

The Data Class: PyG Graphs

Graphs are data structures designed to encode data structured as a set of objects and relations. Objects are embedded as graph nodes \(u\in\mathcal{V}\), where \(\mathcal{V}\) is the node set. Relations are represented by edges \((i,j)\in\mathcal{E}\) between nodes, where \(\mathcal{E}\) is the edge set. Denote the sizes of the node and edge sets as \(|\mathcal{V}|=n_\mathrm{nodes}\) and \(|\mathcal{E}|=n_\mathrm{edges}\) respectively. The choice of edge connectivity determines the local structure of a graph, which has important downstream effects on graph-based learning algorithms. Graph construction is the process of embedding input data onto a graph structure. Graph-based learning algorithms are correspondingly imbued with a relational inductive bias based on the choice of graph representation; a graph's edge connectivity defines its local structure. The simplest graph construction routine is to construct no edges, yielding a permutation invariant set of objects. On the other hand, fully-connected graphs connect every node-node pair with an edge, yielding \(n_\mathrm{edges}=n_\mathrm{nodes}(n_\mathrm{nodes}-1)/2\) edges. This representation may be feasible for small inputs like particle clouds corresponding to a jet, but is intractible for large-scale applications such as high-pileup tracking datasets. Notably, dynamic graph construction techniques operate on input point clouds, constructing edges on them dynamically during inference. For example, EdgeConv and GravNet GNN layers dynamically construct edges between nodes projected into a latent space; multiple such layers may be applied in sequence, yielding many intermediate graph representations on an input point cloud.

In general, nodes can have positions \(\{p_i\}_{i=1}^{n_\mathrm{nodes}}\), \(p_i\in\mathbb{R}^{n_\mathrm{space\_dim}}\), and features (attributes) \(\{x_i\}_{i=1}^{n_\mathrm{nodes}}\), \(x_i\in\mathbb{R}^{n_\mathrm{node\_dim}}\). In some applications like GNN-based particle tracking, node positions are taken to be the features. In others, e.g. jet identification, positional information may be used to seed dynamic graph consturction while kinematic features are propagated as edge features. Edges, too, can have features \(\{e_{ij}\}_{(i,j)\in\mathcal{E}}\), \(e_{ij}\in\mathbb{R}^{n_\mathrm{edge\_dim}}\), but do not have positions; instead, edges are defined by the nodes they connect, and may therefore be represented by, for example, the distance between the respective node-node pair. In PyG, graphs are stored as instances of the data class, whose fields fully specify the graph:

  • data.x: node feature matrix, \(X\in\mathbb{R}^{n_\mathrm{nodes}\times n_\mathrm{node\_dim}}\)
  • data.edge_index: node indices at each end of each edge, \(I\in\mathbb{R}^{2\times n_\mathrm{edges}}\)
  • data.edge_attr: edge feature matrix, \(E\in\mathbb{R}^{n_\mathrm{edges}\times n_\mathrm{edge\_dim}}\)
  • data.y: training target with arbitary shape (\(y\in\mathbb{R}^{n_\mathrm{nodes}\times n_\mathrm{out}}\) for node-level targets, \(y\in\mathbb{R}^{n_\mathrm{edges}\times n_\mathrm{out}}\) for edge-level targets or \(y\in\mathbb{R}^{1\times n_\mathrm{out}}\) for node-level targets).
  • data.pos: Node position matrix, \(P\in\mathbb{R}^{n_\mathrm{nodes}\times n_\mathrm{space\_dim}}\)

The PyG Introduction By Example tutorial covers the basics of graph creation, batching, transformation, and inference using this data class.

As an example, consider the ZINC chemical compounds dataset, which available as a built-in dataset in PyG:

from torch_geometric.datasets import ZINC
 train_dataset = ZINC(root='/tmp/ZINC', subset=True, split='train')
 test_dataset =  ZINC(root='/tmp/ZINC', subset=True, split='test')
 len(train_dataset)
diff --git a/inference/pytorch.html b/inference/pytorch.html
index 1180c9b..a983961 100644
--- a/inference/pytorch.html
+++ b/inference/pytorch.html
@@ -1,4 +1,4 @@
- PyTorch - CMS Machine Learning Documentation       

PyTorch Inference

PyTorch is an open source ML library developed by Facebook's AI Research lab. Initially released in late-2016, PyTorch is a relatively new tool, but has become increasingly popular among ML researchers (in fact, some analyses suggest it's becoming more popular than TensorFlow in academic communities!). PyTorch is written in idiomatic Python, so its syntax is easy to parse for experienced Python programmers. Additionally, it is highly compatible with graphics processing units (GPUs), which can substantially accelerate many deep learning workflows. To date PyTorch has not been integrated into CMSSW. Trained PyTorch models may be evaluated in CMSSW via ONNX Runtime, but model construction and training workflows must currently exist outside of CMSSW. Given the considerable interest in PyTorch within the HEP/ML community, we have reason to believe it will soon be available, so stay tuned!

Introductory References

The Basics

The following documentation surrounds a set of code snippets designed to highlight some important ML features made available in PyTorch. In the following sections, we'll break down snippets from this script, highlighting specifically the PyTorch objects in it.

Tensors

The fundamental PyTorch object is the tensor. At a glance, tensors behave similarly to NumPy arrays. For example, they are broadcasted, concatenated, and sliced in exactly the same way. The following examples highlight some common numpy-like tensor transformations:

a = torch.randn(size=(2,2))
+ PyTorch - CMS Machine Learning Documentation       

PyTorch Inference

PyTorch is an open source ML library developed by Facebook's AI Research lab. Initially released in late-2016, PyTorch is a relatively new tool, but has become increasingly popular among ML researchers (in fact, some analyses suggest it's becoming more popular than TensorFlow in academic communities!). PyTorch is written in idiomatic Python, so its syntax is easy to parse for experienced Python programmers. Additionally, it is highly compatible with graphics processing units (GPUs), which can substantially accelerate many deep learning workflows. To date PyTorch has not been integrated into CMSSW. Trained PyTorch models may be evaluated in CMSSW via ONNX Runtime, but model construction and training workflows must currently exist outside of CMSSW. Given the considerable interest in PyTorch within the HEP/ML community, we have reason to believe it will soon be available, so stay tuned!

Introductory References

The Basics

The following documentation surrounds a set of code snippets designed to highlight some important ML features made available in PyTorch. In the following sections, we'll break down snippets from this script, highlighting specifically the PyTorch objects in it.

Tensors

The fundamental PyTorch object is the tensor. At a glance, tensors behave similarly to NumPy arrays. For example, they are broadcasted, concatenated, and sliced in exactly the same way. The following examples highlight some common numpy-like tensor transformations:

a = torch.randn(size=(2,2))
 >>> tensor([[ 1.3552, -0.0204],
             [ 1.2677, -0.8926]])
 a.view(-1, 1)
diff --git a/inference/sonic_triton.html b/inference/sonic_triton.html
index dfba50a..b456fed 100644
--- a/inference/sonic_triton.html
+++ b/inference/sonic_triton.html
@@ -1 +1 @@
- Sonic/Triton - CMS Machine Learning Documentation       

Service-based inference with Triton/Sonic

This page is still under construction. For the moment, please see the Sonic+Triton tutorial given as part of the Machine Learning HATS@LPC 2021.


Last update: December 5, 2023
\ No newline at end of file + Sonic/Triton - CMS Machine Learning Documentation

Service-based inference with Triton/Sonic

This page is still under construction. For the moment, please see the Sonic+Triton tutorial given as part of the Machine Learning HATS@LPC 2021.


Last update: December 5, 2023
\ No newline at end of file diff --git a/inference/standalone.html b/inference/standalone.html index ff0c58c..66473da 100644 --- a/inference/standalone.html +++ b/inference/standalone.html @@ -1 +1 @@ - Standalone framework - CMS Machine Learning Documentation

Todo.

Idea: Working w/ TF+ROOT standalone (outside of CMSSW)


Last update: December 5, 2023
\ No newline at end of file + Standalone framework - CMS Machine Learning Documentation

Todo.

Idea: Working w/ TF+ROOT standalone (outside of CMSSW)


Last update: December 5, 2023
\ No newline at end of file diff --git a/inference/swan_aws.html b/inference/swan_aws.html index 66dcc4a..a381f82 100644 --- a/inference/swan_aws.html +++ b/inference/swan_aws.html @@ -1 +1 @@ - SWAN + AWS - CMS Machine Learning Documentation

Todo.

Ideas: best practices cost model instance priving need to log out monitoring madatory


Last update: December 5, 2023
\ No newline at end of file + SWAN + AWS - CMS Machine Learning Documentation

Todo.

Ideas: best practices cost model instance priving need to log out monitoring madatory


Last update: December 5, 2023
\ No newline at end of file diff --git a/inference/tensorflow1.html b/inference/tensorflow1.html index aaeabf4..06a5a80 100644 --- a/inference/tensorflow1.html +++ b/inference/tensorflow1.html @@ -1 +1 @@ - TensorFlow 1 - CMS Machine Learning Documentation

Direct inference with TensorFlow 1

While it is technically still possible to use TensorFlow 1, this version of TensorFlow is quite old and is no longer supported by CMSSW. We highly recommend that you update your model to TensorFlow 2 and follow the integration guide in the Inference/Direct inference/TensorFlow 2 documentation.


Last update: December 5, 2023
\ No newline at end of file + TensorFlow 1 - CMS Machine Learning Documentation

Direct inference with TensorFlow 1

While it is technically still possible to use TensorFlow 1, this version of TensorFlow is quite old and is no longer supported by CMSSW. We highly recommend that you update your model to TensorFlow 2 and follow the integration guide in the Inference/Direct inference/TensorFlow 2 documentation.


Last update: December 5, 2023
\ No newline at end of file diff --git a/inference/tensorflow2.html b/inference/tensorflow2.html index 15a60b2..6621c48 100644 --- a/inference/tensorflow2.html +++ b/inference/tensorflow2.html @@ -1,4 +1,4 @@ - TensorFlow 2 - CMS Machine Learning Documentation

Direct inference with TensorFlow 2


TensorFlow 2 is available since CMSSW_11_1_X (cmssw#28711, cmsdist#5525). The integration into the software stack can be found in cmsdist/tensorflow.spec and the interface is located in cmssw/PhysicsTools/TensorFlow.

Available versions

TensorFlow el8_amd64_gcc10 el8_amd64_gcc11
v2.6.0 ≥ CMSSW_12_3_4 -
v2.6.4 ≥ CMSSW_12_5_0 ≥ CMSSW_12_5_0
TensorFlow slc7_amd64_gcc900 slc7_amd64_gcc10 slc7_amd64_gcc11
v2.1.0 ≥ CMSSW_11_1_0 - -
v2.3.1 ≥ CMSSW_11_2_0 - -
v2.4.1 ≥ CMSSW_11_3_0 - -
v2.5.0 ≥ CMSSW_12_0_0 ≥ CMSSW_12_0_0 -
v2.6.0 ≥ CMSSW_12_1_0 ≥ CMSSW_12_1_0 ≥ CMSSW_12_3_0
v2.6.4 - ≥ CMSSW_12_5_0 ≥ CMSSW_13_0_0
TensorFlow slc7_amd64_gcc900
v2.1.0 ≥ CMSSW_11_1_0
v2.3.1 ≥ CMSSW_11_2_0

At this time, only CPU support is provided. While GPU support is generally possible, it is currently disabled due to some interference with production workflows but will be enabled once they are resolved.

Software setup

To run the examples shown below, create a mininmal inference setup with the following snippet. Adapt the SCRAM_ARCH according to your operating system and desired compiler.

 1
+ TensorFlow 2 - CMS Machine Learning Documentation       

Direct inference with TensorFlow 2


TensorFlow 2 is available since CMSSW_11_1_X (cmssw#28711, cmsdist#5525). The integration into the software stack can be found in cmsdist/tensorflow.spec and the interface is located in cmssw/PhysicsTools/TensorFlow.

Available versions

TensorFlow el8_amd64_gcc10 el8_amd64_gcc11
v2.6.0 ≥ CMSSW_12_3_4 -
v2.6.4 ≥ CMSSW_12_5_0 ≥ CMSSW_12_5_0
TensorFlow slc7_amd64_gcc900 slc7_amd64_gcc10 slc7_amd64_gcc11
v2.1.0 ≥ CMSSW_11_1_0 - -
v2.3.1 ≥ CMSSW_11_2_0 - -
v2.4.1 ≥ CMSSW_11_3_0 - -
v2.5.0 ≥ CMSSW_12_0_0 ≥ CMSSW_12_0_0 -
v2.6.0 ≥ CMSSW_12_1_0 ≥ CMSSW_12_1_0 ≥ CMSSW_12_3_0
v2.6.4 - ≥ CMSSW_12_5_0 ≥ CMSSW_13_0_0
TensorFlow slc7_amd64_gcc900
v2.1.0 ≥ CMSSW_11_1_0
v2.3.1 ≥ CMSSW_11_2_0

At this time, only CPU support is provided. While GPU support is generally possible, it is currently disabled due to some interference with production workflows but will be enabled once they are resolved.

Software setup

To run the examples shown below, create a mininmal inference setup with the following snippet. Adapt the SCRAM_ARCH according to your operating system and desired compiler.

 1
  2
  3
  4
diff --git a/inference/tfaas.html b/inference/tfaas.html
index 374019a..890c5c9 100644
--- a/inference/tfaas.html
+++ b/inference/tfaas.html
@@ -1,4 +1,4 @@
- TFaaS - CMS Machine Learning Documentation       

TFaaS

TensorFlow as a Service

TensorFlow as a Service (TFaas) was developed as a general purpose service which can be deployed on any infrastruction from personal laptop, VM, to cloud infrastructure, inculding kubernetes/docker based ones. The main repository contains all details about the service, including install, end-to-end example, and demo.

For CERN users we already deploy TFaaS on the following URL: https://cms-tfaas.cern.ch

It can be used by CMS members using any HTTP based client. For example, here is a basic access from curl client:

curl -k https://cms-tfaas.cern.ch/models
+ TFaaS - CMS Machine Learning Documentation       

TFaaS

TensorFlow as a Service

TensorFlow as a Service (TFaas) was developed as a general purpose service which can be deployed on any infrastruction from personal laptop, VM, to cloud infrastructure, inculding kubernetes/docker based ones. The main repository contains all details about the service, including install, end-to-end example, and demo.

For CERN users we already deploy TFaaS on the following URL: https://cms-tfaas.cern.ch

It can be used by CMS members using any HTTP based client. For example, here is a basic access from curl client:

curl -k https://cms-tfaas.cern.ch/models
 [
   {
     "name": "luca",
diff --git a/inference/xgboost.html b/inference/xgboost.html
index 2844ede..89caac9 100644
--- a/inference/xgboost.html
+++ b/inference/xgboost.html
@@ -1,4 +1,4 @@
- XGBoost - CMS Machine Learning Documentation       

Direct inference with XGBoost

General

XGBoost is avaliable (at least) since CMSSW_9_2_4 cmssw#19377.

In CMSSW environment, XGBoost can be used via its Python API.

For UL era, there are different verisons available for different SCRAM_ARCH:

  1. For slc7_amd64_gcc700 and above, ver.0.80 is available.

  2. For slc7_amd64_gcc900 and above, ver.1.3.3 is available.

  3. Please note that different major versions have different behavior( See Caveat Session).

Existing Examples

There are some existing good examples of using XGBoost under CMSSW, as listed below:

  1. Offical sample for testing the integration of XGBoost library with CMSSW.

  2. Useful codes created by Dr. Huilin Qu for inference with existing trained model.

  3. C/C++ Interface for inference with existing trained model.

We will provide examples for both C/C++ interface and python interface of XGBoost under CMSSW environment.

Example: Classification of points from joint-Gaussian distribution.

In this specific example, you will use XGBoost to classify data points generated from two 8-dimension joint-Gaussian distribution.

Feature Index 0 1 2 3 4 5 6 7
μ1 1 2 3 4 5 6 7 8
μ2 0 1.9 3.2 4.5 4.8 6.1 8.1 11
σ½ = σ 1 1 1 1 1 1 1 1
1 - μ2| / σ 1 0.1 0.2 0.5 0.2 0.1 1.1 3

All generated data points for train(1:10000,2:10000) and test(1:1000,2:1000) are stored as Train_data.csv/Test_data.csv.

Preparing Model

The training process of a XGBoost model can be done outside of CMSSW. We provide a python script for illustration.

# importing necessary models
+ XGBoost - CMS Machine Learning Documentation       

Direct inference with XGBoost

General

XGBoost is avaliable (at least) since CMSSW_9_2_4 cmssw#19377.

In CMSSW environment, XGBoost can be used via its Python API.

For UL era, there are different verisons available for different SCRAM_ARCH:

  1. For slc7_amd64_gcc700 and above, ver.0.80 is available.

  2. For slc7_amd64_gcc900 and above, ver.1.3.3 is available.

  3. Please note that different major versions have different behavior( See Caveat Session).

Existing Examples

There are some existing good examples of using XGBoost under CMSSW, as listed below:

  1. Offical sample for testing the integration of XGBoost library with CMSSW.

  2. Useful codes created by Dr. Huilin Qu for inference with existing trained model.

  3. C/C++ Interface for inference with existing trained model.

We will provide examples for both C/C++ interface and python interface of XGBoost under CMSSW environment.

Example: Classification of points from joint-Gaussian distribution.

In this specific example, you will use XGBoost to classify data points generated from two 8-dimension joint-Gaussian distribution.

Feature Index 0 1 2 3 4 5 6 7
μ1 1 2 3 4 5 6 7 8
μ2 0 1.9 3.2 4.5 4.8 6.1 8.1 11
σ½ = σ 1 1 1 1 1 1 1 1
1 - μ2| / σ 1 0.1 0.2 0.5 0.2 0.1 1.1 3

All generated data points for train(1:10000,2:10000) and test(1:1000,2:1000) are stored as Train_data.csv/Test_data.csv.

Preparing Model

The training process of a XGBoost model can be done outside of CMSSW. We provide a python script for illustration.

# importing necessary models
 import numpy as np
 import pandas as pd 
 from xgboost import XGBClassifier # Or XGBRegressor for Logistic Regression
diff --git a/innovation/hackathons.html b/innovation/hackathons.html
index 18b03eb..70da7f2 100644
--- a/innovation/hackathons.html
+++ b/innovation/hackathons.html
@@ -1 +1 @@
- ML Hackathons - CMS Machine Learning Documentation       

CMS Machine Learning Hackathons

Welcome to the CMS ML Hackathons! Here we encourage the exploration of cutting edge ML methods to particle physics problems through multi-day focused work. Form hackathon teams and work together with the ML Innovation group to get support with organization and announcements, hardware/software infrastructure, follow-up meetings and ML-related technical advise.

If you are interested in proposing a hackathon, please send an e-mail to the CMS ML Innovation conveners with a potential topic and we will get in touch!

Below follows a list of previous successful hackathons.

HGCAL TICL reconstruction

20 Jun 2022 - 24 Jun 2022
https://indico.cern.ch/e/ticlhack

Abstract: The HGCAL reconstruction relies on “The Iterative CLustering” (TICL) framework. It follows an iterative approach, first clusters energy deposits in the same layer (layer clusters) and then connect these layer clusters to reconstruct the particle shower by forming 3-D objects, the “tracksters”. There are multiple areas that could benefit from advanced ML techniques to further improve the reconstruction performance.

In this project we plan to tackle the following topics using ML:

  • trackster identification (ie, identification of the type of particle initiating the shower) and energy regression linking of tracksters stemming from the same particle to reconstruct the full shower and/or use a high-purity trackster as a seed and collect 2D (ie. layer clusters) and/or 3D (ie, tracksters) energy deposits in the vicinity of the seed trackster to fully reconstruct the particle shower
  • tuning of the existing pattern recognition algorithms
  • reconstruction under HL-LHC pile-up scenarios (eg., PU=150-200)
  • trackster characterization, ie. predict if a trackster is a sound object in itself or determine if it is more likely to be a composite one.

Material:

A CodiMD document has been created with an overview of the topics and to keep track of the activities during the hackathon:

https://codimd.web.cern.ch/s/hMd74Yi7J

Jet tagging

8 Nov 2021 - 11 Nov 2021
https://indico.cern.ch/e/jethack

Abstract: The identification of the initial particle (quark, gluon, W/Z boson, etc..) responsible for the formation of the jet, also known as jet tagging, provides a powerful handle in both standard model (SM) measurements and searches for physics beyond the SM (BSM). In this project we propose the development of jet tagging algorithms both for small-radius (i.e. AK4) and large-radius (i.e., AK8) jets using as inputs the PF candidates.

Two main projects are covered:

  • Jet tagging for scouting
  • Jet tagging for Level-1

Jet tagging for scouting

Using as inputs the PF candidates and local pixel tracks reconstructed in the scouting streams, the main goals of this project are the following:

Develop a jet-tagging baseline for scouting and compare the performance with the offline reconstruction Understand the importance of the different input variables and the impact of -various configurations (e.g., on pixel track reconstruction) in the performance Compare different jet tagging approaches with mind performance as well as inference time. Proof of concept: ggF H->bb, ggF HH->4b, VBF HH->4b

Jet tagging for Level-1

Using as input the newly developed particle flow candidates of Seeded Cone jets in the Level1 Correlator trigger, the following tasks will be worked on:

  • Developing a quark, gluon, b, pileup jet classifier for Seeded Cone R=0.4 jets using a combination of tt,VBF(H) and Drell-Yan Level1 samples
  • Develop tools to demonstrate the gain of such a jet tagging algorithm on a signal sample (like q vs g on VBF jets)
  • Study tagging performance as a function of the number of jet constituents
  • Study tagging performance for a "real" input vector (zero-paddes, perhaps unsorted)
  • Optimise jet constituent list of SeededCone Jets (N constituents, zero-removal, sorting etc)
  • Develop q/g/W/Z/t/H classifier for Seeded Cone R=0.8 jets

GNN-4-tracking

27 Sept 2021 - 1 Oct 2021

https://indico.cern.ch/e/gnn4tracks

Abstract: The aim of this hackathon is to integrate graph neural nets (GNNs) for particle tracking into CMSSW.

The hackathon will make use of a GNN model reported by the paper Charged particle tracking via edge-classifying interaction networks by Gage DeZoort, Savannah Thais, et.al. They used a GNN to predict connections between detector pixel hits, and achieved accurate track building. They did this with the TrackML dataset, which uses a generic detector designed to be similar to CMS or ATLAS. Work is ongoing to apply this GNN approach to CMS data.

Tasks: The hackathon aims to create a workflow that allows graph building and GNN inference within the framework of CMSSW. This would enable accurate testing of future GNN models and comparison to existing CMSSW track building methods. The hackathon will be divided into the following subtasks:

  • Task 1: Create a package for extracting graph features and building graphs in CMSSW.
  • Task 2. GNN inference on Sonic servers
  • Task 3: Track fitting after GNN track building
  • Task 4. Performance evaluation for the new track collection

Material:

Code is provided at this GitHub organisation. Project are listed here.

Anomaly detection

In this four day Machine Learning Hackathon, we will develop new anomaly detection algorithms for New Physics detection, intended for deployment in the two main stages of the CMS data aquisition system: The Level-1 trigger and the High Level Trigger.

There are two main projects:

Event-based anomaly detection algorithms for the Level-1 Trigger

Jet-based anomaly detection algorithms for the High Level Trigger, specifically targeting Run 3 scouting

Material:

A list of projects can be found in this document. Instructions for fetching the data and example code for the two projects can be found at Level-1 Anomaly Detection.


Last update: December 5, 2023
\ No newline at end of file + ML Hackathons - CMS Machine Learning Documentation

CMS Machine Learning Hackathons

Welcome to the CMS ML Hackathons! Here we encourage the exploration of cutting edge ML methods to particle physics problems through multi-day focused work. Form hackathon teams and work together with the ML Innovation group to get support with organization and announcements, hardware/software infrastructure, follow-up meetings and ML-related technical advise.

If you are interested in proposing a hackathon, please send an e-mail to the CMS ML Innovation conveners with a potential topic and we will get in touch!

Below follows a list of previous successful hackathons.

HGCAL TICL reconstruction

20 Jun 2022 - 24 Jun 2022
https://indico.cern.ch/e/ticlhack

Abstract: The HGCAL reconstruction relies on “The Iterative CLustering” (TICL) framework. It follows an iterative approach, first clusters energy deposits in the same layer (layer clusters) and then connect these layer clusters to reconstruct the particle shower by forming 3-D objects, the “tracksters”. There are multiple areas that could benefit from advanced ML techniques to further improve the reconstruction performance.

In this project we plan to tackle the following topics using ML:

  • trackster identification (ie, identification of the type of particle initiating the shower) and energy regression linking of tracksters stemming from the same particle to reconstruct the full shower and/or use a high-purity trackster as a seed and collect 2D (ie. layer clusters) and/or 3D (ie, tracksters) energy deposits in the vicinity of the seed trackster to fully reconstruct the particle shower
  • tuning of the existing pattern recognition algorithms
  • reconstruction under HL-LHC pile-up scenarios (eg., PU=150-200)
  • trackster characterization, ie. predict if a trackster is a sound object in itself or determine if it is more likely to be a composite one.

Material:

A CodiMD document has been created with an overview of the topics and to keep track of the activities during the hackathon:

https://codimd.web.cern.ch/s/hMd74Yi7J

Jet tagging

8 Nov 2021 - 11 Nov 2021
https://indico.cern.ch/e/jethack

Abstract: The identification of the initial particle (quark, gluon, W/Z boson, etc..) responsible for the formation of the jet, also known as jet tagging, provides a powerful handle in both standard model (SM) measurements and searches for physics beyond the SM (BSM). In this project we propose the development of jet tagging algorithms both for small-radius (i.e. AK4) and large-radius (i.e., AK8) jets using as inputs the PF candidates.

Two main projects are covered:

  • Jet tagging for scouting
  • Jet tagging for Level-1

Jet tagging for scouting

Using as inputs the PF candidates and local pixel tracks reconstructed in the scouting streams, the main goals of this project are the following:

Develop a jet-tagging baseline for scouting and compare the performance with the offline reconstruction Understand the importance of the different input variables and the impact of -various configurations (e.g., on pixel track reconstruction) in the performance Compare different jet tagging approaches with mind performance as well as inference time. Proof of concept: ggF H->bb, ggF HH->4b, VBF HH->4b

Jet tagging for Level-1

Using as input the newly developed particle flow candidates of Seeded Cone jets in the Level1 Correlator trigger, the following tasks will be worked on:

  • Developing a quark, gluon, b, pileup jet classifier for Seeded Cone R=0.4 jets using a combination of tt,VBF(H) and Drell-Yan Level1 samples
  • Develop tools to demonstrate the gain of such a jet tagging algorithm on a signal sample (like q vs g on VBF jets)
  • Study tagging performance as a function of the number of jet constituents
  • Study tagging performance for a "real" input vector (zero-paddes, perhaps unsorted)
  • Optimise jet constituent list of SeededCone Jets (N constituents, zero-removal, sorting etc)
  • Develop q/g/W/Z/t/H classifier for Seeded Cone R=0.8 jets

GNN-4-tracking

27 Sept 2021 - 1 Oct 2021

https://indico.cern.ch/e/gnn4tracks

Abstract: The aim of this hackathon is to integrate graph neural nets (GNNs) for particle tracking into CMSSW.

The hackathon will make use of a GNN model reported by the paper Charged particle tracking via edge-classifying interaction networks by Gage DeZoort, Savannah Thais, et.al. They used a GNN to predict connections between detector pixel hits, and achieved accurate track building. They did this with the TrackML dataset, which uses a generic detector designed to be similar to CMS or ATLAS. Work is ongoing to apply this GNN approach to CMS data.

Tasks: The hackathon aims to create a workflow that allows graph building and GNN inference within the framework of CMSSW. This would enable accurate testing of future GNN models and comparison to existing CMSSW track building methods. The hackathon will be divided into the following subtasks:

  • Task 1: Create a package for extracting graph features and building graphs in CMSSW.
  • Task 2. GNN inference on Sonic servers
  • Task 3: Track fitting after GNN track building
  • Task 4. Performance evaluation for the new track collection

Material:

Code is provided at this GitHub organisation. Project are listed here.

Anomaly detection

In this four day Machine Learning Hackathon, we will develop new anomaly detection algorithms for New Physics detection, intended for deployment in the two main stages of the CMS data aquisition system: The Level-1 trigger and the High Level Trigger.

There are two main projects:

Event-based anomaly detection algorithms for the Level-1 Trigger

Jet-based anomaly detection algorithms for the High Level Trigger, specifically targeting Run 3 scouting

Material:

A list of projects can be found in this document. Instructions for fetching the data and example code for the two projects can be found at Level-1 Anomaly Detection.


Last update: December 5, 2023
\ No newline at end of file diff --git a/innovation/journal_club.html b/innovation/journal_club.html index 436eac8..a0a5489 100644 --- a/innovation/journal_club.html +++ b/innovation/journal_club.html @@ -1 +1 @@ - ML Journal Club - CMS Machine Learning Documentation

CMS Machine Learning Journal Club

Welcome to the CMS Machine Learning Journal Club (JC)! Here we read an discuss new cutting edge ML papers, with an emphasis on how these can be used within the collaboration. Below you can find a summary of each JC as well as some code examples demonstrating how to use the tools or methods introduced.

To vote for or to propose new papers for discussion, go to https://cms-ml-journalclub.web.cern.ch/.

Below follows a complete list of all the previous CMS ML JHournal clubs, together with relevant documentation and code examples.

Dealing with Nuisance Parameters using Machine Learning in High Energy Physics: a Review

Tommaso Dorigo, Pablo de Castro

Abstract: In this work we discuss the impact of nuisance parameters on the effectiveness of machine learning in high-energy physics problems, and provide a review of techniques that allow to include their effect and reduce their impact in the search for optimal selection criteria and variable transformations. The introduction of nuisance parameters complicates the supervised learning task and its correspondence with the data analysis goal, due to their contribution degrading the model performances in real data, and the necessary addition of uncertainties in the resulting statistical inference. The approaches discussed include nuisance-parameterized models, modified or adversary losses, semi-supervised learning approaches, and inference-aware techniques.

Mapping Machine-Learned Physics into a Human-Readable Space

Taylor Faucett, Jesse Thaler, Daniel Whiteson

Abstract: We present a technique for translating a black-box machine-learned classifier operating on a high-dimensional input space into a small set of human-interpretable observables that can be combined to make the same classification decisions. We iteratively select these observables from a large space of high-level discriminants by finding those with the highest decision similarity relative to the black box, quantified via a metric we introduce that evaluates the relative ordering of pairs of inputs. Successive iterations focus only on the subset of input pairs that are misordered by the current set of observables. This method enables simplification of the machine-learning strategy, interpretation of the results in terms of well-understood physical concepts, validation of the physical model, and the potential for new insights into the nature of the problem itself. As a demonstration, we apply our approach to the benchmark task of jet classification in collider physics, where a convolutional neural network acting on calorimeter jet images outperforms a set of six well-known jet substructure observables. Our method maps the convolutional neural network into a set of observables called energy flow polynomials, and it closes the performance gap by identifying a class of observables with an interesting physical interpretation that has been previously overlooked in the jet substructure literature. - Indico - Paper

Model Interpretability (2 papers):

Identifying the relevant dependencies of the neural network response on characteristics of the input space

Stefan Wunsch, Raphael Friese, Roger Wolf, Günter Quast

Abstract: The relation between the input and output spaces of neural networks (NNs) is investigated to identify those characteristics of the input space that have a large influence on the output for a given task. For this purpose, the NN function is decomposed into a Taylor expansion in each element of the input space. The Taylor coefficients contain information about the sensitivity of the NN response to the inputs. A metric is introduced that allows for the identification of the characteristics that mostly determine the performance of the NN in solving a given task. Finally, the capability of this metric to analyze the performance of the NN is evaluated based on a task common to data analyses in high-energy particle physics experiments.

iNNvestigate neural networks!

Maximilian Alber, Sebastian Lapuschkin, Philipp Seegerer, Miriam Hägele, Kristof T. Schütt, Grégoire Montavon, Wojciech Samek, Klaus-Robert Müller, Sven Dähne, Pieter-Jan Kindermans

In recent years, deep neural networks have revolutionized many application domains of machine learning and are key components of many critical decision or predictive processes. Therefore, it is crucial that domain specialists can understand and analyze actions and pre- dictions, even of the most complex neural network architectures. Despite these arguments neural networks are often treated as black boxes. In the attempt to alleviate this short- coming many analysis methods were proposed, yet the lack of reference implementations often makes a systematic comparison between the methods a major effort. The presented library iNNvestigate addresses this by providing a common interface and out-of-the- box implementation for many analysis methods, including the reference implementation for PatternNet and PatternAttribution as well as for LRP-methods. To demonstrate the versatility of iNNvestigate, we provide an analysis of image classifications for variety of state-of-the-art neural network architectures.

Simulation-based inference in particle physics and beyond (and beyond)

Johann Brehmer, Kyle Cranmer

Abstract: Our predictions for particle physics processes are realized in a chain of complex simulators. They allow us to generate high-fidelity simulated data, but they are not well-suited for inference on the theory parameters with observed data. We explain why the likelihood function of high-dimensional LHC data cannot be explicitly evaluated, why this matters for data analysis, and reframe what the field has traditionally done to circumvent this problem. We then review new simulation-based inference methods that let us directly analyze high-dimensional data by combining machine learning techniques and information from the simulator. Initial studies indicate that these techniques have the potential to substantially improve the precision of LHC measurements. Finally, we discuss probabilistic programming, an emerging paradigm that lets us extend inference to the latent process of the simulator.

Efficiency Parameterization with Neural Networks

C. Badiali, F.A. Di Bello, G. Frattari, E. Gross, V. Ippolito, M. Kado, J. Shlomi

Abstract: Multidimensional efficiency maps are commonly used in high energy physics experiments to mitigate the limitations in the generation of large samples of simulated events. Binned multidimensional efficiency maps are however strongly limited by statistics. We propose a neural network approach to learn ratios of local densities to estimate in an optimal fashion efficiencies as a function of a set of parameters. Graph neural network techniques are used to account for the high dimensional correlations between different physics objects in the event. We show in a specific toy model how this method is applicable to produce accurate multidimensional efficiency maps for heavy flavor tagging classifiers in HEP experiments, including for processes on which it was not trained. - Indico - Paper - Code

A General Framework for Uncertainty Estimation in Deep Learning

Antonio Loquercio, Mattia Segù, Davide Scaramuzza

Neural networks predictions are unreliable when the input sample is out of the training distribution or corrupted by noise. Being able to detect such failures automatically is fundamental to integrate deep learning algorithms into robotics. Current approaches for uncertainty estimation of neural networks require changes to the network and optimization process, typically ignore prior knowledge about the data, and tend to make over-simplifying assumptions which underestimate uncertainty. To address these limitations, we propose a novel framework for uncertainty estimation. Based on Bayesian belief networks and Monte-Carlo sampling, our framework not only fully models the different sources of prediction uncertainty, but also incorporates prior data information, e.g. sensor noise. We show theoretically that this gives us the ability to capture uncertainty better than existing methods. In addition, our framework has several desirable properties: (i) it is agnostic to the network architecture and task; (ii) it does not require changes in the optimization process; (iii) it can be applied to already trained architectures. We thoroughly validate the proposed framework through extensive experiments on both computer vision and control tasks, where we outperform previous methods by up to 23% in accuracy.


Last update: December 5, 2023
\ No newline at end of file + ML Journal Club - CMS Machine Learning Documentation

CMS Machine Learning Journal Club

Welcome to the CMS Machine Learning Journal Club (JC)! Here we read an discuss new cutting edge ML papers, with an emphasis on how these can be used within the collaboration. Below you can find a summary of each JC as well as some code examples demonstrating how to use the tools or methods introduced.

To vote for or to propose new papers for discussion, go to https://cms-ml-journalclub.web.cern.ch/.

Below follows a complete list of all the previous CMS ML JHournal clubs, together with relevant documentation and code examples.

Dealing with Nuisance Parameters using Machine Learning in High Energy Physics: a Review

Tommaso Dorigo, Pablo de Castro

Abstract: In this work we discuss the impact of nuisance parameters on the effectiveness of machine learning in high-energy physics problems, and provide a review of techniques that allow to include their effect and reduce their impact in the search for optimal selection criteria and variable transformations. The introduction of nuisance parameters complicates the supervised learning task and its correspondence with the data analysis goal, due to their contribution degrading the model performances in real data, and the necessary addition of uncertainties in the resulting statistical inference. The approaches discussed include nuisance-parameterized models, modified or adversary losses, semi-supervised learning approaches, and inference-aware techniques.

Mapping Machine-Learned Physics into a Human-Readable Space

Taylor Faucett, Jesse Thaler, Daniel Whiteson

Abstract: We present a technique for translating a black-box machine-learned classifier operating on a high-dimensional input space into a small set of human-interpretable observables that can be combined to make the same classification decisions. We iteratively select these observables from a large space of high-level discriminants by finding those with the highest decision similarity relative to the black box, quantified via a metric we introduce that evaluates the relative ordering of pairs of inputs. Successive iterations focus only on the subset of input pairs that are misordered by the current set of observables. This method enables simplification of the machine-learning strategy, interpretation of the results in terms of well-understood physical concepts, validation of the physical model, and the potential for new insights into the nature of the problem itself. As a demonstration, we apply our approach to the benchmark task of jet classification in collider physics, where a convolutional neural network acting on calorimeter jet images outperforms a set of six well-known jet substructure observables. Our method maps the convolutional neural network into a set of observables called energy flow polynomials, and it closes the performance gap by identifying a class of observables with an interesting physical interpretation that has been previously overlooked in the jet substructure literature. - Indico - Paper

Model Interpretability (2 papers):

Identifying the relevant dependencies of the neural network response on characteristics of the input space

Stefan Wunsch, Raphael Friese, Roger Wolf, Günter Quast

Abstract: The relation between the input and output spaces of neural networks (NNs) is investigated to identify those characteristics of the input space that have a large influence on the output for a given task. For this purpose, the NN function is decomposed into a Taylor expansion in each element of the input space. The Taylor coefficients contain information about the sensitivity of the NN response to the inputs. A metric is introduced that allows for the identification of the characteristics that mostly determine the performance of the NN in solving a given task. Finally, the capability of this metric to analyze the performance of the NN is evaluated based on a task common to data analyses in high-energy particle physics experiments.

iNNvestigate neural networks!

Maximilian Alber, Sebastian Lapuschkin, Philipp Seegerer, Miriam Hägele, Kristof T. Schütt, Grégoire Montavon, Wojciech Samek, Klaus-Robert Müller, Sven Dähne, Pieter-Jan Kindermans

In recent years, deep neural networks have revolutionized many application domains of machine learning and are key components of many critical decision or predictive processes. Therefore, it is crucial that domain specialists can understand and analyze actions and pre- dictions, even of the most complex neural network architectures. Despite these arguments neural networks are often treated as black boxes. In the attempt to alleviate this short- coming many analysis methods were proposed, yet the lack of reference implementations often makes a systematic comparison between the methods a major effort. The presented library iNNvestigate addresses this by providing a common interface and out-of-the- box implementation for many analysis methods, including the reference implementation for PatternNet and PatternAttribution as well as for LRP-methods. To demonstrate the versatility of iNNvestigate, we provide an analysis of image classifications for variety of state-of-the-art neural network architectures.

Simulation-based inference in particle physics and beyond (and beyond)

Johann Brehmer, Kyle Cranmer

Abstract: Our predictions for particle physics processes are realized in a chain of complex simulators. They allow us to generate high-fidelity simulated data, but they are not well-suited for inference on the theory parameters with observed data. We explain why the likelihood function of high-dimensional LHC data cannot be explicitly evaluated, why this matters for data analysis, and reframe what the field has traditionally done to circumvent this problem. We then review new simulation-based inference methods that let us directly analyze high-dimensional data by combining machine learning techniques and information from the simulator. Initial studies indicate that these techniques have the potential to substantially improve the precision of LHC measurements. Finally, we discuss probabilistic programming, an emerging paradigm that lets us extend inference to the latent process of the simulator.

Efficiency Parameterization with Neural Networks

C. Badiali, F.A. Di Bello, G. Frattari, E. Gross, V. Ippolito, M. Kado, J. Shlomi

Abstract: Multidimensional efficiency maps are commonly used in high energy physics experiments to mitigate the limitations in the generation of large samples of simulated events. Binned multidimensional efficiency maps are however strongly limited by statistics. We propose a neural network approach to learn ratios of local densities to estimate in an optimal fashion efficiencies as a function of a set of parameters. Graph neural network techniques are used to account for the high dimensional correlations between different physics objects in the event. We show in a specific toy model how this method is applicable to produce accurate multidimensional efficiency maps for heavy flavor tagging classifiers in HEP experiments, including for processes on which it was not trained. - Indico - Paper - Code

A General Framework for Uncertainty Estimation in Deep Learning

Antonio Loquercio, Mattia Segù, Davide Scaramuzza

Neural networks predictions are unreliable when the input sample is out of the training distribution or corrupted by noise. Being able to detect such failures automatically is fundamental to integrate deep learning algorithms into robotics. Current approaches for uncertainty estimation of neural networks require changes to the network and optimization process, typically ignore prior knowledge about the data, and tend to make over-simplifying assumptions which underestimate uncertainty. To address these limitations, we propose a novel framework for uncertainty estimation. Based on Bayesian belief networks and Monte-Carlo sampling, our framework not only fully models the different sources of prediction uncertainty, but also incorporates prior data information, e.g. sensor noise. We show theoretically that this gives us the ability to capture uncertainty better than existing methods. In addition, our framework has several desirable properties: (i) it is agnostic to the network architecture and task; (ii) it does not require changes in the optimization process; (iii) it can be applied to already trained architectures. We thoroughly validate the proposed framework through extensive experiments on both computer vision and control tasks, where we outperform previous methods by up to 23% in accuracy.


Last update: December 5, 2023
\ No newline at end of file diff --git a/optimization/data_augmentation.html b/optimization/data_augmentation.html index 4e82432..ef54d3a 100644 --- a/optimization/data_augmentation.html +++ b/optimization/data_augmentation.html @@ -1,4 +1,4 @@ - Data augmentation - CMS Machine Learning Documentation

Data augmentation

Introduction

This introduction is based on papers by Shorten & Khoshgoftaar, 2019 and Rebuffi et al., 2021 among others

With the increasing complexity and sizes of neural networks one needs huge amounts of data in order to train a state-of-the-art model. However, generating this data is often very resource and time intensive. Thus, one might either augment the existing data with more descriptive variables or combat the data scarcity problem by artificially increasing the size of the dataset by adding new instances without the resource-heavy generation process. Both processes are known in machine learning (ML) applications as data augmentation (DA) methods.

The first type of these methods is more widely known as feature generation or feature engineering and is done on instance level. Feature engineering focuses on crafting informative input features for the algorithm, often inspired or derived from first principles specific to the algorithm's application domain.

The second type of method is done on the dataset level. These types of techniques can generally be divided into two main categories: real data augmentation (RDA) and synthetic data augmentation (SDA). As the name suggests, RDA makes minor changes to the already existing data in order to generate new samples, whereas SDA generates new data from scratch. Examples of RDA include rotating (especially useful if we expect the event to be rotationally symmetric) and zooming, among a plethora of other methods detailed in this overview article. Examples of SDA include traditional sampling methods and more complex generative models like Generative Adversaial Netoworks (GANs) and Variational Autoencoders (VAE). Going further, the generative methods used for synthetic data augmentation could also be used in fast simulation, which is a notable bottleneck in the overall physics analysis workflow.

Dataset augmentation may lead to more successful algorithm outcomes. For example, introducing noise into data to form additional data points improves the learning ability of several models which otherwise performed relatively poorly, as shown by Freer & Yang, 2020. This finding implies that this form of DA creates variations that the model may see in the real world. If done right, preprocessing the data with DA will result in superior training outcomes. This improvement in performance is due to the fact that DA methods act as a regularizer, reducing overfitting during training. In addition to simulating real-world variations, DA methods can also even out categorical data with imbalanced classes.

Data Augmentation
Fig. 1: Generic pipeline of a heuristic DA (figure taken from Li, 2020)

Before diving more in depth into the various DA methods and applications in HEP, here is a list of the most notable benefits of using DA methods in your ML workflow:

  • Improvement of model prediction precision
  • More training data for the model
  • Preventing data scarcity for state-of-the-art models
  • Reduction of over overfitting and creation of data variability
  • Increased model generalization properties
  • Help in resolving class imbalance problems in datasets
  • Reduced cost of data collection and labeling
  • Enabling rare event prediction

And some words of caution:

  • There is no 'one size fits all' in DA. Each dataset and usecase should be considered separately.
  • Don't trust the augmented data blindly
  • Make sure that the augmented data is representative of the problem at hand, otherwise it will negatively affect the model performance.
  • There must be no unnecessary duplication of existing data, only by adding unique information we gain more insights.
  • Ensure the validity of the augmented data before using it in ML models.
  • If a real dataset contains biases, data augmented from it will contain biases, too. So, identification of optimal data augmentation strategy is important. So, double check your DA strategy.

Feature Engineering

This part is based mostly on Erdmann et al., 2018

Feature engineering (FE) is one of the key components of a machine learning workflow. This process transforms and augments training data with additional features in order to make the training more effective.

With multi-variate analyeses (MVAs), such boosted decision trees (BDTs) and neural networks, one could start with raw, "low-level" features, like four-momenta, and the algorithm can learn higher level patterns, correlations, metrics, etc. However, using "high-level" variables, in many cases, leads to outcomes superior to the use of low-level variables. As such, features used in MVAs are handcrafted from physics first principles.

Still, it is shown that a deep neural network (DNN) can perform better if it is trained with both specifically constructed variables and low-level variables. This observation suggests that the network extracts additional information from the training data.

HEP Application - Lorentz Boosted Network

For the purposeses of FE in HEP, a novel ML architecture called a Lorentz Boost Network (LBN) (see Fig. 2) was proposed and implemented by Erdmann et al., 2018. It is a multipurpose method that uses Lorentz transformations to exploit and uncover structures in particle collision events. LBN is the first stage of a two-stage neural network (NN) model, that enables a fully autonomous and comprehensive characterization of collision events by exploiting exclusively the four-momenta of the final-state particles.

Within LBN, particles are combined to create rest frames representions, which enables the formation of further composite particles. These combinations are realized via linear combinations of N input four-vectors to a number of M particles and rest frames. Subsequently these composite particles are then transformed into said rest frames by Lorentz transformations in an efficient and fully vectorized implementation.

The properties of the composite, transformed particles are compiled in the form of characteristic variables like masses, angles, etc. that serve as input for a subsequent network - the second stage, which has to be configured for a specific analysis task, like classification.

The authors observed leading performance with the LBN and demonstrated that LBN forms physically meaningful particle combinations and generates suitable characteristic variables.

The usual ML workflow, employing LBN, is as follows:

Step-1: LBN(M, F)
+ Data augmentation - CMS Machine Learning Documentation       

Data augmentation

Introduction

This introduction is based on papers by Shorten & Khoshgoftaar, 2019 and Rebuffi et al., 2021 among others

With the increasing complexity and sizes of neural networks one needs huge amounts of data in order to train a state-of-the-art model. However, generating this data is often very resource and time intensive. Thus, one might either augment the existing data with more descriptive variables or combat the data scarcity problem by artificially increasing the size of the dataset by adding new instances without the resource-heavy generation process. Both processes are known in machine learning (ML) applications as data augmentation (DA) methods.

The first type of these methods is more widely known as feature generation or feature engineering and is done on instance level. Feature engineering focuses on crafting informative input features for the algorithm, often inspired or derived from first principles specific to the algorithm's application domain.

The second type of method is done on the dataset level. These types of techniques can generally be divided into two main categories: real data augmentation (RDA) and synthetic data augmentation (SDA). As the name suggests, RDA makes minor changes to the already existing data in order to generate new samples, whereas SDA generates new data from scratch. Examples of RDA include rotating (especially useful if we expect the event to be rotationally symmetric) and zooming, among a plethora of other methods detailed in this overview article. Examples of SDA include traditional sampling methods and more complex generative models like Generative Adversaial Netoworks (GANs) and Variational Autoencoders (VAE). Going further, the generative methods used for synthetic data augmentation could also be used in fast simulation, which is a notable bottleneck in the overall physics analysis workflow.

Dataset augmentation may lead to more successful algorithm outcomes. For example, introducing noise into data to form additional data points improves the learning ability of several models which otherwise performed relatively poorly, as shown by Freer & Yang, 2020. This finding implies that this form of DA creates variations that the model may see in the real world. If done right, preprocessing the data with DA will result in superior training outcomes. This improvement in performance is due to the fact that DA methods act as a regularizer, reducing overfitting during training. In addition to simulating real-world variations, DA methods can also even out categorical data with imbalanced classes.

Data Augmentation
Fig. 1: Generic pipeline of a heuristic DA (figure taken from Li, 2020)

Before diving more in depth into the various DA methods and applications in HEP, here is a list of the most notable benefits of using DA methods in your ML workflow:

  • Improvement of model prediction precision
  • More training data for the model
  • Preventing data scarcity for state-of-the-art models
  • Reduction of over overfitting and creation of data variability
  • Increased model generalization properties
  • Help in resolving class imbalance problems in datasets
  • Reduced cost of data collection and labeling
  • Enabling rare event prediction

And some words of caution:

  • There is no 'one size fits all' in DA. Each dataset and usecase should be considered separately.
  • Don't trust the augmented data blindly
  • Make sure that the augmented data is representative of the problem at hand, otherwise it will negatively affect the model performance.
  • There must be no unnecessary duplication of existing data, only by adding unique information we gain more insights.
  • Ensure the validity of the augmented data before using it in ML models.
  • If a real dataset contains biases, data augmented from it will contain biases, too. So, identification of optimal data augmentation strategy is important. So, double check your DA strategy.

Feature Engineering

This part is based mostly on Erdmann et al., 2018

Feature engineering (FE) is one of the key components of a machine learning workflow. This process transforms and augments training data with additional features in order to make the training more effective.

With multi-variate analyeses (MVAs), such boosted decision trees (BDTs) and neural networks, one could start with raw, "low-level" features, like four-momenta, and the algorithm can learn higher level patterns, correlations, metrics, etc. However, using "high-level" variables, in many cases, leads to outcomes superior to the use of low-level variables. As such, features used in MVAs are handcrafted from physics first principles.

Still, it is shown that a deep neural network (DNN) can perform better if it is trained with both specifically constructed variables and low-level variables. This observation suggests that the network extracts additional information from the training data.

HEP Application - Lorentz Boosted Network

For the purposeses of FE in HEP, a novel ML architecture called a Lorentz Boost Network (LBN) (see Fig. 2) was proposed and implemented by Erdmann et al., 2018. It is a multipurpose method that uses Lorentz transformations to exploit and uncover structures in particle collision events. LBN is the first stage of a two-stage neural network (NN) model, that enables a fully autonomous and comprehensive characterization of collision events by exploiting exclusively the four-momenta of the final-state particles.

Within LBN, particles are combined to create rest frames representions, which enables the formation of further composite particles. These combinations are realized via linear combinations of N input four-vectors to a number of M particles and rest frames. Subsequently these composite particles are then transformed into said rest frames by Lorentz transformations in an efficient and fully vectorized implementation.

The properties of the composite, transformed particles are compiled in the form of characteristic variables like masses, angles, etc. that serve as input for a subsequent network - the second stage, which has to be configured for a specific analysis task, like classification.

The authors observed leading performance with the LBN and demonstrated that LBN forms physically meaningful particle combinations and generates suitable characteristic variables.

The usual ML workflow, employing LBN, is as follows:

Step-1: LBN(M, F)
 
     1.0: Input hyperparameters: number of combinations M; number of features F
     1.0: Choose: number of incoming particles, N, according to the research
diff --git a/optimization/importance.html b/optimization/importance.html
index 70f31a5..516529f 100644
--- a/optimization/importance.html
+++ b/optimization/importance.html
@@ -1,4 +1,4 @@
- Feature importance - CMS Machine Learning Documentation       

Feature Importance

Feature importance is the impact a specific input field has on a prediction model's output. In general, these impacts can range from no impact (i.e. a feature with no variance) to perfect correlation with the ouput. There are several reasons to consider feature importance:

  • Important features can be used to create simplified models, e.g. to mitigate overfitting.
  • Using only important features can reduce the latency and memory requirements of the model.
  • The relative importance of a set of features can yield insight into the nature of an otherwise opaque model (improved interpretability).
  • If a model is sensitive to noise, rejecting irrelevant inputs may improve its performance.

In the following subsections, we detail several strategies for evaluating feature importance. We begin with a general discussion of feature importance at a high level before offering a code-based tutorial on some common techniques. We conclude with additional notes and comments in the last section.

General Discussion

Most feature importance methods fall into one of three broad categories: filter methods, embedding methods, and wrapper methods. Here we give a brief overview of each category with relevant examples:

Filter Methods

Filter methods do not rely on a specific model, instead considering features in the context of a given dataset. In this way, they may be considered to be pre-processing steps. In many cases, the goal of feature filtering is to reduce high dimensional data. However, these methods are also applicable to data exploration, wherein an analyst simply seeks to learn about a dataset without actually removing any features. This knowledge may help interpret the performance of a downstream predictive model. Relevant examples include,

  • Domain Knowledge: Perhaps the most obvious strategy is to select features relevant to the domain of interest.

  • Variance Thresholding: One basic filtering strategy is to simply remove features with low variance. In the extreme case, features with zero variance do not vary from example to example, and will therefore have no impact on the model's final prediction. Likewise, features with variance below a given threshold may not affect a model's downstream performance.

  • Fisher Scoring: Fisher scoring can be used to rank features; the analyst would then select the highest scoring features as inputs to a subsequent model.

  • Correlations: Correlated features introduce a certain degree of redundancy to a dataset, so reducing the number of strongly correlated variables may not impact a model's downstream performance.

Embedded Methods

Embedded methods are specific to a prediction model and independent of the dataset. Examples:

  • L1 Regularization (LASSO): L1 regularization directly penalizes large model weights. In the context of linear regression, for example, this amounts to enforcing sparsity in the output prediction; weights corresponding to less relevant features will be driven to 0, nullifying the feature's effect on the output.

Wrapper Methods

Wrapper methods iterate on prediction models in the context of a given dataset. In general they may be computationally expensive when compared to filter methods. Examples:

  • Permutation Importance: Direct interpretation isn't always feasible, so other methods have been developed to inspect a feature's importance. One common and broadly-applicable method is to randomly shuffle a given feature's input values and test the degredation of model performance. This process allows us to measure permutation importance as follows. First, fit a model (\(f\)) to training data, yielding \(f(X_\mathrm{train})\), where \(X_\mathrm{train}\in\mathbb{R}^{n\times d}\) for \(n\) input examples with \(d\) features. Next, measure the model's performance on testing data for some loss \(\mathcal{L}\), i.e. \(s=\mathcal{L}\big(f(X_\mathrm{test}), y_\mathrm{test}\big)\). For each feature \(j\in[1\ ..\ d]\), randomly shuffle the corresponding column in \(X_\mathrm{test}\) to form \(X_\mathrm{test}^{(j)}\). Repeat this process \(K\) times, so that for \(k\in [1\ ..\ K]\) each random shuffling of feature column \(j\) gives a corrupted input dataset \(X_\mathrm{test}^{(j,k)}\). Finally, define the permutation importance of feature \(j\) as the difference between the un-corrupted validation score and average validation score over the corrupted \(X_\mathrm{test}^{(j,k)}\) datasets:
\[\texttt{PI}_j = s - \frac{1}{K}\sum_{k=1}^{K} \mathcal{L}[f(X_\mathrm{test}^{(j,k)}), y_\mathrm{test}]\]
  • Recursive Feature Elimination (RFE): Given a prediction model and test/train dataset splits with \(D\) initial features, RFE returns the set of \(d < D\) features that maximize model performance. First, the model is trained on the full set of features. The importance of each feature is ranked depending on the model type (e.g. for regression, the slopes are a sufficient ranking measure; permutation importance may also be used). The least important feature is rejected and the model is retrained. This process is repeated until the most significant \(d\) features remain.

Introduction by Example

Direct Interpretation

Linear regression is particularly interpretable because the prediction coefficients themselves can be interpreted as a measure of feature importance. Here we will compare this direct interpretation to several model inspection techniques. In the following examples we use the Diabetes Dataset available as a Scikit-learn toy dataset. This dataset maps 10 biological markers to a 1-dimensional quantitative measure of diabetes progression:

from sklearn.datasets import load_diabetes
+ Feature importance - CMS Machine Learning Documentation       

Feature Importance

Feature importance is the impact a specific input field has on a prediction model's output. In general, these impacts can range from no impact (i.e. a feature with no variance) to perfect correlation with the ouput. There are several reasons to consider feature importance:

  • Important features can be used to create simplified models, e.g. to mitigate overfitting.
  • Using only important features can reduce the latency and memory requirements of the model.
  • The relative importance of a set of features can yield insight into the nature of an otherwise opaque model (improved interpretability).
  • If a model is sensitive to noise, rejecting irrelevant inputs may improve its performance.

In the following subsections, we detail several strategies for evaluating feature importance. We begin with a general discussion of feature importance at a high level before offering a code-based tutorial on some common techniques. We conclude with additional notes and comments in the last section.

General Discussion

Most feature importance methods fall into one of three broad categories: filter methods, embedding methods, and wrapper methods. Here we give a brief overview of each category with relevant examples:

Filter Methods

Filter methods do not rely on a specific model, instead considering features in the context of a given dataset. In this way, they may be considered to be pre-processing steps. In many cases, the goal of feature filtering is to reduce high dimensional data. However, these methods are also applicable to data exploration, wherein an analyst simply seeks to learn about a dataset without actually removing any features. This knowledge may help interpret the performance of a downstream predictive model. Relevant examples include,

  • Domain Knowledge: Perhaps the most obvious strategy is to select features relevant to the domain of interest.

  • Variance Thresholding: One basic filtering strategy is to simply remove features with low variance. In the extreme case, features with zero variance do not vary from example to example, and will therefore have no impact on the model's final prediction. Likewise, features with variance below a given threshold may not affect a model's downstream performance.

  • Fisher Scoring: Fisher scoring can be used to rank features; the analyst would then select the highest scoring features as inputs to a subsequent model.

  • Correlations: Correlated features introduce a certain degree of redundancy to a dataset, so reducing the number of strongly correlated variables may not impact a model's downstream performance.

Embedded Methods

Embedded methods are specific to a prediction model and independent of the dataset. Examples:

  • L1 Regularization (LASSO): L1 regularization directly penalizes large model weights. In the context of linear regression, for example, this amounts to enforcing sparsity in the output prediction; weights corresponding to less relevant features will be driven to 0, nullifying the feature's effect on the output.

Wrapper Methods

Wrapper methods iterate on prediction models in the context of a given dataset. In general they may be computationally expensive when compared to filter methods. Examples:

  • Permutation Importance: Direct interpretation isn't always feasible, so other methods have been developed to inspect a feature's importance. One common and broadly-applicable method is to randomly shuffle a given feature's input values and test the degredation of model performance. This process allows us to measure permutation importance as follows. First, fit a model (\(f\)) to training data, yielding \(f(X_\mathrm{train})\), where \(X_\mathrm{train}\in\mathbb{R}^{n\times d}\) for \(n\) input examples with \(d\) features. Next, measure the model's performance on testing data for some loss \(\mathcal{L}\), i.e. \(s=\mathcal{L}\big(f(X_\mathrm{test}), y_\mathrm{test}\big)\). For each feature \(j\in[1\ ..\ d]\), randomly shuffle the corresponding column in \(X_\mathrm{test}\) to form \(X_\mathrm{test}^{(j)}\). Repeat this process \(K\) times, so that for \(k\in [1\ ..\ K]\) each random shuffling of feature column \(j\) gives a corrupted input dataset \(X_\mathrm{test}^{(j,k)}\). Finally, define the permutation importance of feature \(j\) as the difference between the un-corrupted validation score and average validation score over the corrupted \(X_\mathrm{test}^{(j,k)}\) datasets:
\[\texttt{PI}_j = s - \frac{1}{K}\sum_{k=1}^{K} \mathcal{L}[f(X_\mathrm{test}^{(j,k)}), y_\mathrm{test}]\]
  • Recursive Feature Elimination (RFE): Given a prediction model and test/train dataset splits with \(D\) initial features, RFE returns the set of \(d < D\) features that maximize model performance. First, the model is trained on the full set of features. The importance of each feature is ranked depending on the model type (e.g. for regression, the slopes are a sufficient ranking measure; permutation importance may also be used). The least important feature is rejected and the model is retrained. This process is repeated until the most significant \(d\) features remain.

Introduction by Example

Direct Interpretation

Linear regression is particularly interpretable because the prediction coefficients themselves can be interpreted as a measure of feature importance. Here we will compare this direct interpretation to several model inspection techniques. In the following examples we use the Diabetes Dataset available as a Scikit-learn toy dataset. This dataset maps 10 biological markers to a 1-dimensional quantitative measure of diabetes progression:

from sklearn.datasets import load_diabetes
 from sklearn.model_selection import train_test_split
 
 diabetes = load_diabetes()
diff --git a/optimization/model_optimization.html b/optimization/model_optimization.html
index b9918bb..865fea3 100644
--- a/optimization/model_optimization.html
+++ b/optimization/model_optimization.html
@@ -1 +1 @@
- Model optimization - CMS Machine Learning Documentation       

Model optimization

This page summarizes the concepts shown in a contribution on Bayesian Optimization to the ML Forum and may be edited and published elsewhere by the author.

What we talk about when we talk about model optimization

Given some data \(x\) and a family of functionals parameterized by (a vector of) parameters \(\theta\) (e.g. for DNN training weights), the problem of learning consists in finding \(argmin_\theta Loss(f_\theta(x) - y_{true})\). The treatment below focusses on gradient descent, but the formalization is completely general, i.e. it can be applied also to methods that are not explicitly formulated in terms of gradient descent (e.g. BDTs). The mathematical formalism for the problem of learning is briefly explained in a contribution on statistical learning to the ML forum: for the purposes of this documentation we will proceed through two illustrations.

The first illustration, elaborated from an image by the huawei forums shows the general idea behind learning through gradient descent in a multidimensional parameter space, where the minimum of a loss function is found by following the function's gradient until the minimum.

The cartoon illustrates the general idea behind gradient descent to find the minimum of a function in a multidimensional parameter space (figure elaborated from an image by the huawei forums).

The model to be optimized via a loss function typically is a parametric function, where the set of parameters (e.g. the network weights in neural networks) corresponds to a certain fixed structure of the network. For example, a network with two inputs, two inner layers of two neurons, and one output neuron will have six parameters whose values will be changed until the loss function reaches its minimum.

When we talk about model optimization we refer to the fact that often we are interested in finding which model structure is the best to describe our data. The main concern is to design a model that has a sufficient complexity to store all the information contained in the training data. We can therefore think of parameterizing the network structure itself, e.g. in terms of the number of inner layers and number of neurons per layer: these hyperparameters define a space where we want to again minimize a loss function. Formally, the parametric function \(f_\theta\) is also a function of these hyperparameters \(\lambda\): \(f_{(\theta, \lambda)}\), and the \(\lambda\) can be optimized

The second illustration, also elaborated from an image by the huawei forums, broadly illustrates this concept: for each point in the hyperparameters space (that is, for each configuration of the model), the individual model is optimized as usual. The global minimum over the hyperparameters space is then sought.

The cartoon illustrates the general idea behind gradient descent to optimize the model complexity (in terms of the choice of hyperparameters) multidimensional parameter and hyperparameter space (figure elaborated from an image by the huawei forums).

Caveat: which data should you use to optimize your model

In typical machine learning studies, you should divide your dataset into three parts. One is used for training the model (training sample), one is used for testing the performance of the model (test sample), and the third one is the one where you actually use your trained model, e.g. for inference (application sample). Sometimes you may get away with using test data as application data: Helge Voss (Chap 5 of Behnke et al.) states that this is acceptable under three conditions that must be simultaneously valid:

  • no hyperparameter optimization is performed;
  • no overtraining is found;
  • the number of training data is high enough to make statistical fluctuations negligible.

If you are doing any kind of hyperparamters optimization, thou shalt NOT use the test sample as application sample. You should have at least three distinct sets, and ideally you should use four (training, testing, hyperparameter optimization, application).

The most simple hyperparameters optimization algorithm is the grid search, where you train all the models in the hyperparameters space to build the full landscape of the global loss function, as illustrated in Goodfellow, Bengio, Courville: "Deep Learning".

The cartoon illustrates the general idea behind grid search (image taken from Goodfellow, Bengio, Courville: "Deep Learning").

To perform a meaningful grid search, you have to provide a set of values within the acceptable range of each hyperparameters, then for each point in the cross-product space you have to train the corresponding model.

The main issue with grid search is that when there are nonimportant hyperparameters (i.e. hyperparameters whose value doesn't influence much the model performance) the algorithm spends an exponentially large time (in the number of nonimportant hyperparameters) in the noninteresting configurations: having \(m\) parameters and testing \(n\) values for each of them leads to \(\mathcal{O}(n^m)\) tested configurations. While the issue may be mitigated by parallelization, when the number of hyperparameters (the dimension of hyperparameters space) surpasses a handful, even parallelization can't help.

Another issue is that the search is binned: depending on the granularity in the scan, the global minimum may be invisible.

Despite these issues, grid search is sometimes still a feasible choice, and gives its best when done iteratively. For example, if you start from the interval \(\{-1, 0, 1\}\):

  • if the best parameter is found to be at the boundary (1), then extend range (\(\{1, 2, 3\}\)) and do the search in the new range;
  • if the best parameter is e.g. at 0, then maybe zoom in and do a search in the range \(\{-0.1, 0, 0.1\}\).

An improvement of the grid search is the random search, which proceeds like this:

  • you provide a marginal p.d.f. for each hyperparameter;
  • you sample from the joint p.d.f. a certain number of training configurations;
  • you train for each of these configurations to build the loss function landscape.

This procedure has significant advantages over a simple grid search: random search is not binned, because you are sampling from a continuous p.d.f., so the pool of explorable hyperparameter values is larger; random search is exponentially more efficient, because it tests a unique value for each influential hyperparameter on nearly every trial.

Random search also work best when done iteratively. The differences between grid and random search are again illustrated in Goodfellow, Bengio, Courville: "Deep Learning".

The cartoon illustrates the general idea behind random search, as opposed to grid search (image taken from Goodfellow, Bengio, Courville: "Deep Learning").

Model-based optimization by gradient descent

Now that we have looked at the most basic model optimization techniques, we are ready to look into using gradient descent to solve a model optimization problem. We will proceed by recasting the problem as one of model selection, where the hyperparameters are the input (decision) variables, and the model selection criterion is a differentiable validation set error. The validation set error attempts to describe the complexity of the network by a single hyperparameter (details in [a contribution on statistical learning to the ML forum]) The problem may be solved with standard gradient descent, as illustrated above, if we assume that the training criterion \(C\) is continuous and differentiable with respect to both the parameters \(\theta\) (e.g. weights) and hyperparameters \(\lambda\) Unfortunately, the gradient is seldom available (either because it has a prohibitive computational cost, or because it is non-differentiable as is the case when there are discrete variables).

A diagram illustrating the way gradient-based model optimization works has been prepared by Bengio, doi:10.1162/089976600300015187.

The diagram illustrates the way model optimization can be recast as a model selection problem, where a model selection criterion involves a differentiable validation set error (image taken from Bengio, doi:10.1162/089976600300015187).

Model-based optimization by surrogates

Sequential Model-based Global Optimization (SMBO) consists in replacing the loss function with a surrogate model of it, when the loss function (i.e. the validation set error) is not available. The surrogate is typically built as a Bayesian regression model, when one estimates the expected value of the validation set error for each hyperparameter together with the uncertainty in this expectation. The pseudocode for the SMBO algorithm is illustrated by Bergstra et al.

The diagram illustrates the pseudocode for the Sequential Model-based Global Optimization (image taken from Bergstra et al).

This procedure results in a tradeoff between: exploration, i.e. proposing hyperparameters with high uncertainty, which may result in substantial improvement or not; and exploitation (propose hyperparameters that will likely perform as well as the current proposal---usually this mean close to the current ones). The disadvantage is that the whole procedure must run until completion before giving as an output any usable information. By comparison, manual or random searches tend to give hints on the location of the minimum faster.

Bayesian Optimization

We are now ready to tackle in full what is referred to as Bayesian optimization.

Bayesian optimization assumes that the unknown function \(f(\theta, \lambda)\) was sampled from a Gaussian process (GP), and that after the observations it maintains the corresponding posterior. In this context, observations are the various validation set errors for different values of the hyperparameters \(\lambda\). In order to pick the next value to probe, one maximizes some estimate of the expected improvement (see below). To understand the meaning of "sampled from a Gaussian process", we need to define what a Gaussian process is.

Gaussian processes

Gaussian processes (GPs) generalize the concept of Gaussian distribution over discrete random variables to the concept of Gaussian distribution over continuous functions. Given some data and an estimate of the Gaussian noise, by fitting a function one can estimate also the noise at the interpolated points. This estimate is made by similarity with contiguous points, adjusted by the distance between points. A GP is therefore fully described by its mean and its covariance function. An illustration of Gaussian processes is given in Kevin Jamieson's CSE599 lecture notes.

The diagram illustrates the evolution of a Gaussian process, when adding interpolating points (image taken from Kevin Jamieson's CSE599 lecture notes).

GPs are great for Bayesian optimization because they out-of-the-box provide the expected value (i.e. the mean of the process) and its uncertainty (covariance function).

The basic idea behind Bayesian optimization

Gradient descent methods are intrinsically local: the decision on the next step is taken based on the local gradient and Hessian approximations- Bayesian optimization (BO) with GP priors uses a model that uses all the information from the previous steps by encoding it in the model giving the expectation and its uncertainty. The consequence is that GP-based BO can find the minimum of difficult nonconvex functions in relatively few evaluations, at the cost of performing more computations to find the next point to try in the hyperparameters space.

The BO prior is a prior over the space of the functions. GPs are especially suited to play the role of BO prior, because marginals and conditionals can be computed in closed form (thanks to the properties of the Gaussian distribution).

There are several methods to choose the acquisition function (the function that selects the next step for the algorithm), but there is no omnipurpose recipe: the best approach is problem-dependent. The acquisition function involves an accessory optimization to maximize a certain quantity; typical choices are:

  • maximize the probability of improvement over the current best value: can be calculated analytically for a GP;
  • maximize the expected improvement over the current best value: can also be calculated analytically for a GP;
  • maximize the GP Upper confidence bound: minimize "regret" over the course of the optimization.

Historical note

Gaussian process regression is also called kriging in geostatistics, after Daniel G. Krige (1951) who pioneered the concept later formalized by Matheron (1962)

Bayesian optimization in practice

The figure below, taken by a tutorial on BO by Martin Krasser, clarifies rather well the procedure. The task is to approximate the target function (labelled noise free objective in the figure), given some noisy samples of it (the black crosses). At the first iteration, one starts from a flat surrogate function, with a given uncertainty, and fits it to the noisy samples. To choose the next sampling location, a certain acquisition function is computed, and the value that maximizes it is chosen as the next sampling location At each iteration, more noisy samples are added, until the distance between consecutive sampling locations is minimized (or, equivalently, a measure of the value of the best selected sample is maximized).

Practical illustration of Bayesian Optimization (images taken from a tutorial on BO by Martin Krasser]).

Limitations (and some workaround) of Bayesian Optimization

There are three main limitations to the BO approach. A good overview of these limitations and of possible solutions can be found in arXiv:1206.2944.

First of all, it is unclear what is an appropriate choice for the covariance function and its associated hyperparameters. In particular, the standard squared exponential kernel is often too smooth. As a workaround, alternative kernels may be used: a common choice is the Matérn 5/2 kernel, which is similar to the squared exponential one but allows for non-smoothness.

Another issue is that, for certain problems, the function evaluation may take very long to compute. To overcome this, often one can replace the function evaluation with the Monte Carlo integration of the expected improvement over the GP hyperparameters, which is faster.

The third main issue is that for complex problems one would ideally like to take advantage of parallel computation. The procedure is iterative, however, and it is not easy to come up with a scheme to make it parallelizable. The referenced paper proposed sampling over the expected acquisition, conditioned on all the pending evaluations: this is computationally cheap and is intrinsically parallelizable.

Alternatives to Gaussian processes: Tree-based models

Gaussian Processes model directly \(P(hyperpar | data)\) but are not the only suitable surrogate models for Bayesian optimization

The so-called Tree-structured Parzen Estimator (TPE), described in Bergstra et al, models separately \(P(data | hyperpar)\) and \(P(hyperpar)\), to then obtain the posterior by explicit application of the Bayes theorem TPEs exploit the fact that the choice of hyperparameters is intrinsically graph-structured, in the sense that e.g. you first choose the number of layers, then choose neurons per layer, etc. TPEs run over this generative process by replacing the hyperparameters priors with nonparametric densities. These generative nonparametric densities are built by classifying them into those that result in worse/better loss than the current proposal.

TPEs have been used in CMS already around 2017 in a VHbb analysis (see repository by Sean-Jiun Wang) and in a charged Higgs to tb search (HIG-18-004, doi:10.1007/JHEP01(2020)096).

Implementations of Bayesian Optimization

Caveats: don't get too obsessed with model optimization

In general, optimizing model structure is a good thing. F. Chollet e.g. says "If you want to get to the very limit of what can be achieved on a given task, you can't be content with arbitrary choices made by a fallible human". On the other side, for many problems hyperparameter optimization does result in small improvements, and there is a tradeoff between improvement and time spent on the task: sometimes the time spent on optimization may not be worth, e.g. when the gradient of the loss in hyperparameters space is very flat (i.e. different hyperparameter sets give more or less the same results), particularly if you already know that small improvements will be eaten up by e.g. systematic uncertainties. On the other side, before you perform the optimization you don't know if the landscape is flat or if you can expect substantial improvements. Sometimes broad grid or random searches may give you a hint on whether the landscape of hyperparameters space is flat or not.

Sometimes you may get good (and faster) improvements by model ensembling rather than by model optimization. To do model ensembling, you first train a handful models (either different methods---BDT, SVM, NN, etc---or different hyperparameters sets): \(pred\_a = model\_a.predict(x)\), ..., \(pred\_d = model\_d.predict(x)\). You then pool the predictions: \(pooled\_pred = (pred\_a + pred\_b + pred\_c + pred\_d)/4.\). THis works if all models are kind of good: if one is significantly worse than the others, then \(pooled\_pred\) may not be as good as the best model of the pool.

You can also find ways of ensembling in a smarter way, e.g. by doing weighted rather than simple averages: \(pooled\_pred = 0.5\cdot pred\_a + 0.25\cdot pred\_b + 0.1\cdot pred\_c + 0.15\cdot pred\_d)/4.\). Here the idea is to give more weight to better classifiers. However, you transfer the problem to having to choose the weights. These can be found empirically empirically by using random search or other algorithms like Nelder-Mead (result = scipy.optimize.minimize(objective, pt, method='nelder-mead'), where you build simplexes (polytope with N+1 vertices in N dimensions, generalization of triangle) and stretch them towards higher values of the objective. Nelder-Mead can converge to nonstationary points, but there are extensions of the algorithm that may help.


This page summarizes the concepts shown in a contribution on Bayesian Optimization to the ML Forum. Content may be edited and published elsewhere by the author. Page author: Pietro Vischia, 2022


Last update: December 5, 2023
\ No newline at end of file + Model optimization - CMS Machine Learning Documentation

Model optimization

This page summarizes the concepts shown in a contribution on Bayesian Optimization to the ML Forum and may be edited and published elsewhere by the author.

What we talk about when we talk about model optimization

Given some data \(x\) and a family of functionals parameterized by (a vector of) parameters \(\theta\) (e.g. for DNN training weights), the problem of learning consists in finding \(argmin_\theta Loss(f_\theta(x) - y_{true})\). The treatment below focusses on gradient descent, but the formalization is completely general, i.e. it can be applied also to methods that are not explicitly formulated in terms of gradient descent (e.g. BDTs). The mathematical formalism for the problem of learning is briefly explained in a contribution on statistical learning to the ML forum: for the purposes of this documentation we will proceed through two illustrations.

The first illustration, elaborated from an image by the huawei forums shows the general idea behind learning through gradient descent in a multidimensional parameter space, where the minimum of a loss function is found by following the function's gradient until the minimum.

The cartoon illustrates the general idea behind gradient descent to find the minimum of a function in a multidimensional parameter space (figure elaborated from an image by the huawei forums).

The model to be optimized via a loss function typically is a parametric function, where the set of parameters (e.g. the network weights in neural networks) corresponds to a certain fixed structure of the network. For example, a network with two inputs, two inner layers of two neurons, and one output neuron will have six parameters whose values will be changed until the loss function reaches its minimum.

When we talk about model optimization we refer to the fact that often we are interested in finding which model structure is the best to describe our data. The main concern is to design a model that has a sufficient complexity to store all the information contained in the training data. We can therefore think of parameterizing the network structure itself, e.g. in terms of the number of inner layers and number of neurons per layer: these hyperparameters define a space where we want to again minimize a loss function. Formally, the parametric function \(f_\theta\) is also a function of these hyperparameters \(\lambda\): \(f_{(\theta, \lambda)}\), and the \(\lambda\) can be optimized

The second illustration, also elaborated from an image by the huawei forums, broadly illustrates this concept: for each point in the hyperparameters space (that is, for each configuration of the model), the individual model is optimized as usual. The global minimum over the hyperparameters space is then sought.

The cartoon illustrates the general idea behind gradient descent to optimize the model complexity (in terms of the choice of hyperparameters) multidimensional parameter and hyperparameter space (figure elaborated from an image by the huawei forums).

Caveat: which data should you use to optimize your model

In typical machine learning studies, you should divide your dataset into three parts. One is used for training the model (training sample), one is used for testing the performance of the model (test sample), and the third one is the one where you actually use your trained model, e.g. for inference (application sample). Sometimes you may get away with using test data as application data: Helge Voss (Chap 5 of Behnke et al.) states that this is acceptable under three conditions that must be simultaneously valid:

  • no hyperparameter optimization is performed;
  • no overtraining is found;
  • the number of training data is high enough to make statistical fluctuations negligible.

If you are doing any kind of hyperparamters optimization, thou shalt NOT use the test sample as application sample. You should have at least three distinct sets, and ideally you should use four (training, testing, hyperparameter optimization, application).

The most simple hyperparameters optimization algorithm is the grid search, where you train all the models in the hyperparameters space to build the full landscape of the global loss function, as illustrated in Goodfellow, Bengio, Courville: "Deep Learning".

The cartoon illustrates the general idea behind grid search (image taken from Goodfellow, Bengio, Courville: "Deep Learning").

To perform a meaningful grid search, you have to provide a set of values within the acceptable range of each hyperparameters, then for each point in the cross-product space you have to train the corresponding model.

The main issue with grid search is that when there are nonimportant hyperparameters (i.e. hyperparameters whose value doesn't influence much the model performance) the algorithm spends an exponentially large time (in the number of nonimportant hyperparameters) in the noninteresting configurations: having \(m\) parameters and testing \(n\) values for each of them leads to \(\mathcal{O}(n^m)\) tested configurations. While the issue may be mitigated by parallelization, when the number of hyperparameters (the dimension of hyperparameters space) surpasses a handful, even parallelization can't help.

Another issue is that the search is binned: depending on the granularity in the scan, the global minimum may be invisible.

Despite these issues, grid search is sometimes still a feasible choice, and gives its best when done iteratively. For example, if you start from the interval \(\{-1, 0, 1\}\):

  • if the best parameter is found to be at the boundary (1), then extend range (\(\{1, 2, 3\}\)) and do the search in the new range;
  • if the best parameter is e.g. at 0, then maybe zoom in and do a search in the range \(\{-0.1, 0, 0.1\}\).

An improvement of the grid search is the random search, which proceeds like this:

  • you provide a marginal p.d.f. for each hyperparameter;
  • you sample from the joint p.d.f. a certain number of training configurations;
  • you train for each of these configurations to build the loss function landscape.

This procedure has significant advantages over a simple grid search: random search is not binned, because you are sampling from a continuous p.d.f., so the pool of explorable hyperparameter values is larger; random search is exponentially more efficient, because it tests a unique value for each influential hyperparameter on nearly every trial.

Random search also work best when done iteratively. The differences between grid and random search are again illustrated in Goodfellow, Bengio, Courville: "Deep Learning".

The cartoon illustrates the general idea behind random search, as opposed to grid search (image taken from Goodfellow, Bengio, Courville: "Deep Learning").

Model-based optimization by gradient descent

Now that we have looked at the most basic model optimization techniques, we are ready to look into using gradient descent to solve a model optimization problem. We will proceed by recasting the problem as one of model selection, where the hyperparameters are the input (decision) variables, and the model selection criterion is a differentiable validation set error. The validation set error attempts to describe the complexity of the network by a single hyperparameter (details in [a contribution on statistical learning to the ML forum]) The problem may be solved with standard gradient descent, as illustrated above, if we assume that the training criterion \(C\) is continuous and differentiable with respect to both the parameters \(\theta\) (e.g. weights) and hyperparameters \(\lambda\) Unfortunately, the gradient is seldom available (either because it has a prohibitive computational cost, or because it is non-differentiable as is the case when there are discrete variables).

A diagram illustrating the way gradient-based model optimization works has been prepared by Bengio, doi:10.1162/089976600300015187.

The diagram illustrates the way model optimization can be recast as a model selection problem, where a model selection criterion involves a differentiable validation set error (image taken from Bengio, doi:10.1162/089976600300015187).

Model-based optimization by surrogates

Sequential Model-based Global Optimization (SMBO) consists in replacing the loss function with a surrogate model of it, when the loss function (i.e. the validation set error) is not available. The surrogate is typically built as a Bayesian regression model, when one estimates the expected value of the validation set error for each hyperparameter together with the uncertainty in this expectation. The pseudocode for the SMBO algorithm is illustrated by Bergstra et al.

The diagram illustrates the pseudocode for the Sequential Model-based Global Optimization (image taken from Bergstra et al).

This procedure results in a tradeoff between: exploration, i.e. proposing hyperparameters with high uncertainty, which may result in substantial improvement or not; and exploitation (propose hyperparameters that will likely perform as well as the current proposal---usually this mean close to the current ones). The disadvantage is that the whole procedure must run until completion before giving as an output any usable information. By comparison, manual or random searches tend to give hints on the location of the minimum faster.

Bayesian Optimization

We are now ready to tackle in full what is referred to as Bayesian optimization.

Bayesian optimization assumes that the unknown function \(f(\theta, \lambda)\) was sampled from a Gaussian process (GP), and that after the observations it maintains the corresponding posterior. In this context, observations are the various validation set errors for different values of the hyperparameters \(\lambda\). In order to pick the next value to probe, one maximizes some estimate of the expected improvement (see below). To understand the meaning of "sampled from a Gaussian process", we need to define what a Gaussian process is.

Gaussian processes

Gaussian processes (GPs) generalize the concept of Gaussian distribution over discrete random variables to the concept of Gaussian distribution over continuous functions. Given some data and an estimate of the Gaussian noise, by fitting a function one can estimate also the noise at the interpolated points. This estimate is made by similarity with contiguous points, adjusted by the distance between points. A GP is therefore fully described by its mean and its covariance function. An illustration of Gaussian processes is given in Kevin Jamieson's CSE599 lecture notes.

The diagram illustrates the evolution of a Gaussian process, when adding interpolating points (image taken from Kevin Jamieson's CSE599 lecture notes).

GPs are great for Bayesian optimization because they out-of-the-box provide the expected value (i.e. the mean of the process) and its uncertainty (covariance function).

The basic idea behind Bayesian optimization

Gradient descent methods are intrinsically local: the decision on the next step is taken based on the local gradient and Hessian approximations- Bayesian optimization (BO) with GP priors uses a model that uses all the information from the previous steps by encoding it in the model giving the expectation and its uncertainty. The consequence is that GP-based BO can find the minimum of difficult nonconvex functions in relatively few evaluations, at the cost of performing more computations to find the next point to try in the hyperparameters space.

The BO prior is a prior over the space of the functions. GPs are especially suited to play the role of BO prior, because marginals and conditionals can be computed in closed form (thanks to the properties of the Gaussian distribution).

There are several methods to choose the acquisition function (the function that selects the next step for the algorithm), but there is no omnipurpose recipe: the best approach is problem-dependent. The acquisition function involves an accessory optimization to maximize a certain quantity; typical choices are:

  • maximize the probability of improvement over the current best value: can be calculated analytically for a GP;
  • maximize the expected improvement over the current best value: can also be calculated analytically for a GP;
  • maximize the GP Upper confidence bound: minimize "regret" over the course of the optimization.

Historical note

Gaussian process regression is also called kriging in geostatistics, after Daniel G. Krige (1951) who pioneered the concept later formalized by Matheron (1962)

Bayesian optimization in practice

The figure below, taken by a tutorial on BO by Martin Krasser, clarifies rather well the procedure. The task is to approximate the target function (labelled noise free objective in the figure), given some noisy samples of it (the black crosses). At the first iteration, one starts from a flat surrogate function, with a given uncertainty, and fits it to the noisy samples. To choose the next sampling location, a certain acquisition function is computed, and the value that maximizes it is chosen as the next sampling location At each iteration, more noisy samples are added, until the distance between consecutive sampling locations is minimized (or, equivalently, a measure of the value of the best selected sample is maximized).

Practical illustration of Bayesian Optimization (images taken from a tutorial on BO by Martin Krasser]).

Limitations (and some workaround) of Bayesian Optimization

There are three main limitations to the BO approach. A good overview of these limitations and of possible solutions can be found in arXiv:1206.2944.

First of all, it is unclear what is an appropriate choice for the covariance function and its associated hyperparameters. In particular, the standard squared exponential kernel is often too smooth. As a workaround, alternative kernels may be used: a common choice is the Matérn 5/2 kernel, which is similar to the squared exponential one but allows for non-smoothness.

Another issue is that, for certain problems, the function evaluation may take very long to compute. To overcome this, often one can replace the function evaluation with the Monte Carlo integration of the expected improvement over the GP hyperparameters, which is faster.

The third main issue is that for complex problems one would ideally like to take advantage of parallel computation. The procedure is iterative, however, and it is not easy to come up with a scheme to make it parallelizable. The referenced paper proposed sampling over the expected acquisition, conditioned on all the pending evaluations: this is computationally cheap and is intrinsically parallelizable.

Alternatives to Gaussian processes: Tree-based models

Gaussian Processes model directly \(P(hyperpar | data)\) but are not the only suitable surrogate models for Bayesian optimization

The so-called Tree-structured Parzen Estimator (TPE), described in Bergstra et al, models separately \(P(data | hyperpar)\) and \(P(hyperpar)\), to then obtain the posterior by explicit application of the Bayes theorem TPEs exploit the fact that the choice of hyperparameters is intrinsically graph-structured, in the sense that e.g. you first choose the number of layers, then choose neurons per layer, etc. TPEs run over this generative process by replacing the hyperparameters priors with nonparametric densities. These generative nonparametric densities are built by classifying them into those that result in worse/better loss than the current proposal.

TPEs have been used in CMS already around 2017 in a VHbb analysis (see repository by Sean-Jiun Wang) and in a charged Higgs to tb search (HIG-18-004, doi:10.1007/JHEP01(2020)096).

Implementations of Bayesian Optimization

Caveats: don't get too obsessed with model optimization

In general, optimizing model structure is a good thing. F. Chollet e.g. says "If you want to get to the very limit of what can be achieved on a given task, you can't be content with arbitrary choices made by a fallible human". On the other side, for many problems hyperparameter optimization does result in small improvements, and there is a tradeoff between improvement and time spent on the task: sometimes the time spent on optimization may not be worth, e.g. when the gradient of the loss in hyperparameters space is very flat (i.e. different hyperparameter sets give more or less the same results), particularly if you already know that small improvements will be eaten up by e.g. systematic uncertainties. On the other side, before you perform the optimization you don't know if the landscape is flat or if you can expect substantial improvements. Sometimes broad grid or random searches may give you a hint on whether the landscape of hyperparameters space is flat or not.

Sometimes you may get good (and faster) improvements by model ensembling rather than by model optimization. To do model ensembling, you first train a handful models (either different methods---BDT, SVM, NN, etc---or different hyperparameters sets): \(pred\_a = model\_a.predict(x)\), ..., \(pred\_d = model\_d.predict(x)\). You then pool the predictions: \(pooled\_pred = (pred\_a + pred\_b + pred\_c + pred\_d)/4.\). THis works if all models are kind of good: if one is significantly worse than the others, then \(pooled\_pred\) may not be as good as the best model of the pool.

You can also find ways of ensembling in a smarter way, e.g. by doing weighted rather than simple averages: \(pooled\_pred = 0.5\cdot pred\_a + 0.25\cdot pred\_b + 0.1\cdot pred\_c + 0.15\cdot pred\_d)/4.\). Here the idea is to give more weight to better classifiers. However, you transfer the problem to having to choose the weights. These can be found empirically empirically by using random search or other algorithms like Nelder-Mead (result = scipy.optimize.minimize(objective, pt, method='nelder-mead'), where you build simplexes (polytope with N+1 vertices in N dimensions, generalization of triangle) and stretch them towards higher values of the objective. Nelder-Mead can converge to nonstationary points, but there are extensions of the algorithm that may help.


This page summarizes the concepts shown in a contribution on Bayesian Optimization to the ML Forum. Content may be edited and published elsewhere by the author. Page author: Pietro Vischia, 2022


Last update: December 5, 2023
\ No newline at end of file diff --git a/resources/cloud_resources/index.html b/resources/cloud_resources/index.html index b891d86..76bb801 100644 --- a/resources/cloud_resources/index.html +++ b/resources/cloud_resources/index.html @@ -1 +1 @@ - Cloud Resources - CMS Machine Learning Documentation

Work in progress.


Last update: December 5, 2023
\ No newline at end of file + Cloud Resources - CMS Machine Learning Documentation

Work in progress.


Last update: December 5, 2023
\ No newline at end of file diff --git a/resources/dataset_resources/index.html b/resources/dataset_resources/index.html index acd293a..639ab07 100644 --- a/resources/dataset_resources/index.html +++ b/resources/dataset_resources/index.html @@ -1,4 +1,4 @@ - Dataset Resources - CMS Machine Learning Documentation

CMS-ML Dataset Tab

Introduction

Welcome to CMS-ML Dataset tab! Our tab is designed to provide accurate, up-to-date, and relevant data across various purposes. We strive to make this tab resourceful for your analysis and decision-making needs. We are working on benchmarking more dataset and presenting them in a user-friendly format. This tab will be continuously updated to reflect the latest developments. Explore, analyze, and derive insights with ease!

1. JetNet

Github Repository

Zenodo

Description

JetNet is a project aimed at enhancing accessibility and reproducibility in jet-based machine learning. It offers easy-to-access and standardized interfaces for several datasets, including JetNet, TopTagging, and QuarkGluon. Additionally, JetNet provides standard implementations of various generative evaluation metrics such as Fréchet Physics Distance (FPD), Kernel Physics Distance (KPD), Wasserstein-1 (W1), Fréchet ParticleNet Distance (FPND), coverage, and Minimum Matching Distance (MMD). Beyond these, it includes a differentiable implementation of the energy mover's distance and other general jet utilities, making it a comprehensive resource for researchers and practitioners in the field.

Nature of Objects

  • Objects: Gluon (g), Top Quark (t), Light Quark (q), W boson (w), and Z boson (z) jets of ~1 TeV transverse momentum (\(p_T\))
  • Number of Objects: N = 177252, 177945, 170679, 177172, 176952 for g, t, q, w, z jets respectively.

Format of Dataset

  • File Type: HDF5
  • Structure: Each file has particle_features; and jet_features; arrays, containing the list of particles' features per jet and the corresponding jet's features, respectively. Particle_features is of shape [N, 30, 4], where N is the total number of jets, 30 is the max number of particles per jet, and 4 is the number of particle features, in order: []\eta, \varphi, \p_T, mask]. See Zenodo for definitions of these. jet_features is of shape [N, 4], where 4 is the number of jet features, in order: [\(p_T\), \(\eta\), mass, # of particles].

2. Top Tagging Benchmark Dataset

Zenodo

Description

A set of MC simulated training/testing events for the evaluation of top quark tagging architectures. - 14 TeV, hadronic tops for signal, qcd diets background, Delphes ATLAS detector card with Pythia8 - No MPI/pile-up included - Clustering of particle-flow entries (produced by Delphes E-flow) into anti-kT 0.8 jets in the pT range [550,650] GeV - All top jets are matched to a parton-level top within ∆R = 0.8, and to all top decay partons within 0.8 - Jets are required to have |eta| < 2 - The leading 200 jet constituent four-momenta are stored, with zero-padding for jets with fewer than 200 - Constituents are sorted by pT, with the highest pT one first - The truth top four-momentum is stored as truth_px etc. - A flag (1 for top, 0 for QCD) is kept for each jet. It is called is_signal_new - The variable "ttv" (= test/train/validation) is kept for each jet. It indicates to which dataset the jet belongs. It is redundant as the different sets are already distributed as different files.

Nature of Objects

  • Objects: 14 TeV, hadronic tops for signal, qcd diets background, Delphes ATLAS detector card with Pythia8
  • Number of Objects: In total 1.2M training events, 400k validation events and 400k test events.

Format of Dataset

  • File Type: HDF5
  • Structure: Use “train” for training, “val” for validation during the training and “test” for final testing and reporting results. For details, see the Zenodo link
  • Butter, Anja; Kasieczka, Gregor; Plehn, Tilman and Russell, Michael (2017). Based on data from 10.21468/SciPostPhys.5.3.028 (1707.08966)
  • Kasieczka, Gregor et al (2019). Dataset used for arXiv:1902.09914 (The Machine Learning Landscape of Top Taggers)

More dataset coming in!

Have any questions? Want your dataset shown on this page? Contact the ML Knowledge Subgroup!

cms-ml/documentation

CMS-ML Dataset Tab

Introduction

Welcome to CMS-ML Dataset tab! Our tab is designed to provide accurate, up-to-date, and relevant data across various purposes. We strive to make this tab resourceful for your analysis and decision-making needs. We are working on benchmarking more dataset and presenting them in a user-friendly format. This tab will be continuously updated to reflect the latest developments. Explore, analyze, and derive insights with ease!

1. JetNet

Github Repository

Zenodo

Description

JetNet is a project aimed at enhancing accessibility and reproducibility in jet-based machine learning. It offers easy-to-access and standardized interfaces for several datasets, including JetNet, TopTagging, and QuarkGluon. Additionally, JetNet provides standard implementations of various generative evaluation metrics such as Fréchet Physics Distance (FPD), Kernel Physics Distance (KPD), Wasserstein-1 (W1), Fréchet ParticleNet Distance (FPND), coverage, and Minimum Matching Distance (MMD). Beyond these, it includes a differentiable implementation of the energy mover's distance and other general jet utilities, making it a comprehensive resource for researchers and practitioners in the field.

Nature of Objects

  • Objects: Gluon (g), Top Quark (t), Light Quark (q), W boson (w), and Z boson (z) jets of ~1 TeV transverse momentum (\(p_T\))
  • Number of Objects: N = 177252, 177945, 170679, 177172, 176952 for g, t, q, w, z jets respectively.

Format of Dataset

  • File Type: HDF5
  • Structure: Each file has particle_features; and jet_features; arrays, containing the list of particles' features per jet and the corresponding jet's features, respectively. Particle_features is of shape [N, 30, 4], where N is the total number of jets, 30 is the max number of particles per jet, and 4 is the number of particle features, in order: []\eta, \varphi, \p_T, mask]. See Zenodo for definitions of these. jet_features is of shape [N, 4], where 4 is the number of jet features, in order: [\(p_T\), \(\eta\), mass, # of particles].

2. Top Tagging Benchmark Dataset

Zenodo

Description

A set of MC simulated training/testing events for the evaluation of top quark tagging architectures. - 14 TeV, hadronic tops for signal, qcd diets background, Delphes ATLAS detector card with Pythia8 - No MPI/pile-up included - Clustering of particle-flow entries (produced by Delphes E-flow) into anti-kT 0.8 jets in the pT range [550,650] GeV - All top jets are matched to a parton-level top within ∆R = 0.8, and to all top decay partons within 0.8 - Jets are required to have |eta| < 2 - The leading 200 jet constituent four-momenta are stored, with zero-padding for jets with fewer than 200 - Constituents are sorted by pT, with the highest pT one first - The truth top four-momentum is stored as truth_px etc. - A flag (1 for top, 0 for QCD) is kept for each jet. It is called is_signal_new - The variable "ttv" (= test/train/validation) is kept for each jet. It indicates to which dataset the jet belongs. It is redundant as the different sets are already distributed as different files.

Nature of Objects

  • Objects: 14 TeV, hadronic tops for signal, qcd diets background, Delphes ATLAS detector card with Pythia8
  • Number of Objects: In total 1.2M training events, 400k validation events and 400k test events.

Format of Dataset

  • File Type: HDF5
  • Structure: Use “train” for training, “val” for validation during the training and “test” for final testing and reporting results. For details, see the Zenodo link
  • Butter, Anja; Kasieczka, Gregor; Plehn, Tilman and Russell, Michael (2017). Based on data from 10.21468/SciPostPhys.5.3.028 (1707.08966)
  • Kasieczka, Gregor et al (2019). Dataset used for arXiv:1902.09914 (The Machine Learning Landscape of Top Taggers)

More dataset coming in!

Have any questions? Want your dataset shown on this page? Contact the ML Knowledge Subgroup!

cms-ml/documentation

Work in progress.


Last update: December 5, 2023
\ No newline at end of file + FPGA Resource - CMS Machine Learning Documentation

Work in progress.


Last update: December 5, 2023
\ No newline at end of file diff --git a/resources/gpu_resources/cms_resources/lxplus_gpu.html b/resources/gpu_resources/cms_resources/lxplus_gpu.html index aa428ca..b37c390 100644 --- a/resources/gpu_resources/cms_resources/lxplus_gpu.html +++ b/resources/gpu_resources/cms_resources/lxplus_gpu.html @@ -1,2 +1,2 @@ - lxplus-gpu - CMS Machine Learning Documentation

lxplus-gpu.cern.ch

How to use it?

lxplus-gpu are special lxplus nodes with GPU support. You can access these nodes by executing

ssh <your_user_name>@lxplus-gpu.cern.ch
+ lxplus-gpu - CMS Machine Learning Documentation       

lxplus-gpu.cern.ch

How to use it?

lxplus-gpu are special lxplus nodes with GPU support. You can access these nodes by executing

ssh <your_user_name>@lxplus-gpu.cern.ch
 

Untitled

The configuration of the software environment for lxplus-gpu is described in the Software Environments page.


Last update: December 5, 2023
\ No newline at end of file diff --git a/resources/gpu_resources/cms_resources/lxplus_htcondor.html b/resources/gpu_resources/cms_resources/lxplus_htcondor.html index 8f1a029..b49cc80 100644 --- a/resources/gpu_resources/cms_resources/lxplus_htcondor.html +++ b/resources/gpu_resources/cms_resources/lxplus_htcondor.html @@ -1,2 +1,2 @@ - CERN HTCondor - CMS Machine Learning Documentation

HTCondor With GPU resources

In general, HTCondor supports GPU jobs if there are some worker nodes which are configured with GPU devices. CMS Connect and lxplus both have access to worker nodes equipped with GPUs.

How to require GPUs in HTCondor

People can require their jobs to have GPU support by adding the following requirements to the condor submission file.

request_gpus = n # n equal to the number of GPUs required
+ CERN HTCondor - CMS Machine Learning Documentation       

HTCondor With GPU resources

In general, HTCondor supports GPU jobs if there are some worker nodes which are configured with GPU devices. CMS Connect and lxplus both have access to worker nodes equipped with GPUs.

How to require GPUs in HTCondor

People can require their jobs to have GPU support by adding the following requirements to the condor submission file.

request_gpus = n # n equal to the number of GPUs required
 

Further documentation

There are good materials providing detailed documentation on how to run HTCondor jobs with GPU support at both machines.

The configuration of the software environment for lxplus-gpu and HTcondor is described in the Software Environments page. Moreover the page Using container explains step by step how to build a docker image to be run on HTCondor jobs.

More available resources

  1. A complete documentation can be found from the GPUs section in CERN Batch Docs. Where a Tensorflow example is supplied. This documentation also contains instructions on advanced HTCondor configuration, for instance constraining GPU device or CUDA version.
  2. A good example on submitting GPU HTCondor job @ Lxplus is the weaver-benchmark project. It provides a concrete example on how to setup environment for weaver framework and operate trainning and testing process within a single job. Detailed description can be found at section ParticleNet of this documentation.

    In principle, this example can be run elsewhere as HTCondor jobs. However, paths to the datasets should be modified to meet the requirements.

  3. CMS Connect also provides a documentation on GPU job submission. In this documentation there is also a Tensorflow example.

    When submitting GPU jobs @ CMS Connect, especially for Machine Learning purpose, EOS space @ CERN are not accessible as a directory, therefore one should consider using xrootd utilities as documented in this page


Last update: December 5, 2023
\ No newline at end of file diff --git a/resources/gpu_resources/cms_resources/ml_cern_ch.html b/resources/gpu_resources/cms_resources/ml_cern_ch.html index 3ce4618..0698540 100644 --- a/resources/gpu_resources/cms_resources/ml_cern_ch.html +++ b/resources/gpu_resources/cms_resources/ml_cern_ch.html @@ -1 +1 @@ - ml.cern.ch - CMS Machine Learning Documentation

ml.cern.ch

ml.cern.ch is a Kubeflow based ML solution provided by CERN.

Kubeflow

Kubeflow is a Kubernetes based ML toolkits aiming at making deployments of ML workflows simple, portable and scalable. In Kubeflow, pipeline is an important concept. Machine Learning workflows are discribed as a Kubeflow pipeline for execution.

How to access

ml.cern.ch only accepts connections from within the CERN network. Therefore, if you are outside of CERN, you will need to use a network tunnel (eg. via ssh -D dynamic port forwarding as a SOCKS5 proxy)... The main website are shown below.

Untitled

Examples

After logging into the main website, you can click on the Examples entry to browser a gitlab repository containing a lot of examples. For instance, below are two examples from that repository with a well-documented readme file.

  1. mnist-kfp is an example on how to use jupyter notebooks to create a Kubeflow pipeline (kfp) and how to access CERN EOS files.
  2. katib gives an example on how to use the katib to operate hyperparameter tuning for jet tagging with ParticleNet.

Last update: December 5, 2023
\ No newline at end of file + ml.cern.ch - CMS Machine Learning Documentation

ml.cern.ch

ml.cern.ch is a Kubeflow based ML solution provided by CERN.

Kubeflow

Kubeflow is a Kubernetes based ML toolkits aiming at making deployments of ML workflows simple, portable and scalable. In Kubeflow, pipeline is an important concept. Machine Learning workflows are discribed as a Kubeflow pipeline for execution.

How to access

ml.cern.ch only accepts connections from within the CERN network. Therefore, if you are outside of CERN, you will need to use a network tunnel (eg. via ssh -D dynamic port forwarding as a SOCKS5 proxy)... The main website are shown below.

Untitled

Examples

After logging into the main website, you can click on the Examples entry to browser a gitlab repository containing a lot of examples. For instance, below are two examples from that repository with a well-documented readme file.

  1. mnist-kfp is an example on how to use jupyter notebooks to create a Kubeflow pipeline (kfp) and how to access CERN EOS files.
  2. katib gives an example on how to use the katib to operate hyperparameter tuning for jet tagging with ParticleNet.

Last update: December 5, 2023
\ No newline at end of file diff --git a/resources/gpu_resources/cms_resources/notebooks/pytorch_mnist.html b/resources/gpu_resources/cms_resources/notebooks/pytorch_mnist.html index af8114c..5f77079 100644 --- a/resources/gpu_resources/cms_resources/notebooks/pytorch_mnist.html +++ b/resources/gpu_resources/cms_resources/notebooks/pytorch_mnist.html @@ -1,4 +1,4 @@ - Pytorch mnist - CMS Machine Learning Documentation
from __future__ import print_function
+ Pytorch mnist - CMS Machine Learning Documentation      
from __future__ import print_function
 import argparse
 import torch
 import torch.nn as nn
diff --git a/resources/gpu_resources/cms_resources/notebooks/toptagging_mlp.html b/resources/gpu_resources/cms_resources/notebooks/toptagging_mlp.html
index be8cdd0..78f037a 100644
--- a/resources/gpu_resources/cms_resources/notebooks/toptagging_mlp.html
+++ b/resources/gpu_resources/cms_resources/notebooks/toptagging_mlp.html
@@ -1,4 +1,4 @@
- Toptagging mlp - CMS Machine Learning Documentation      

import torch
+ Toptagging mlp - CMS Machine Learning Documentation      

import torch
 import torch.nn as nn
 from torch.utils.data.dataset import Dataset
 import pandas as pd
diff --git a/resources/gpu_resources/cms_resources/swan.html b/resources/gpu_resources/cms_resources/swan.html
index 471f6a7..3b1eb50 100644
--- a/resources/gpu_resources/cms_resources/swan.html
+++ b/resources/gpu_resources/cms_resources/swan.html
@@ -1 +1 @@
- SWAN - CMS Machine Learning Documentation       

SWAN

Preparation

  1. Registration:

    To require GPU resources for SWAN: According to this thread, one can create a ticket through this link to ask for GPU support at SWAN, it is now in beta version and limited to a small scale. 2. Setup SWAN with GPU resources:

    1. Once the registration is done, one can login SWAN with Kerberes8 support and then create his SWAN environment.

Untitled

Untitled

Another important option is the environment script, which will be discussed later in this document.

Working with SWAN

  1. After creation, one will browse the SWAN main directory My Project where all existing projects are displayed. A new project can be created by clicking the upper right "+" button. After creation one will be redirected to the newly created project, at which point the "+" button on the upper right panel can be used for creating new notebook.

    Untitled

    Untitled

  2. It is possible to use the terminal for installing new packages or monitoring computational resources.

    1. For package installation, one can install packages with package management tools, e.g. pip for python. To use the installed packages, you will need to wrap the environment configuration in a scrip, which will be executed by SWAN. Detailed documentation can be found by clicking the upper right "?" button.

    2. In addition to using top and htop to monitor ordinary resources, you can use nvidia-smi to monitor GPU usage.

    Untitled

Examples

After installing package, you can then use GPU based machine learning algorithms. Two examples are supplied as an example.

  1. The first example aims at using a CNN to perform handwritten digits classification with MNIST dataset. The whole notebook can be found at pytorch_mnist. This example is modified from an official pytorch example.

  2. The second example is modified from the simple MLP example from weaver-benchmark. The whole notebook can be found at toptagging_mlp.


Last update: December 5, 2023
\ No newline at end of file + SWAN - CMS Machine Learning Documentation

SWAN

Preparation

  1. Registration:

    To require GPU resources for SWAN: According to this thread, one can create a ticket through this link to ask for GPU support at SWAN, it is now in beta version and limited to a small scale. 2. Setup SWAN with GPU resources:

    1. Once the registration is done, one can login SWAN with Kerberes8 support and then create his SWAN environment.

Untitled

Untitled

Another important option is the environment script, which will be discussed later in this document.

Working with SWAN

  1. After creation, one will browse the SWAN main directory My Project where all existing projects are displayed. A new project can be created by clicking the upper right "+" button. After creation one will be redirected to the newly created project, at which point the "+" button on the upper right panel can be used for creating new notebook.

    Untitled

    Untitled

  2. It is possible to use the terminal for installing new packages or monitoring computational resources.

    1. For package installation, one can install packages with package management tools, e.g. pip for python. To use the installed packages, you will need to wrap the environment configuration in a scrip, which will be executed by SWAN. Detailed documentation can be found by clicking the upper right "?" button.

    2. In addition to using top and htop to monitor ordinary resources, you can use nvidia-smi to monitor GPU usage.

    Untitled

Examples

After installing package, you can then use GPU based machine learning algorithms. Two examples are supplied as an example.

  1. The first example aims at using a CNN to perform handwritten digits classification with MNIST dataset. The whole notebook can be found at pytorch_mnist. This example is modified from an official pytorch example.

  2. The second example is modified from the simple MLP example from weaver-benchmark. The whole notebook can be found at toptagging_mlp.


Last update: December 5, 2023
\ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json index 970fcfb..d0682af 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"index.html","title":"Home","text":"

Welcome to the documentation hub for the CMS Machine Learning Group! The goal of this page is to provide CMS analyzers a centralized place to gather machine learning information relevant to their work. However, we are not seeking to rewrite external documentation. Whenever applicable, we will link to external documentation, such as the iML groups HEP Living Review or their ML Resources repository. What you will find here are pages covering:

  • ML best practices
  • How to optimize a NN
  • Common pitfalls for CMS analyzers
  • Direct and indirect inferencing using a variety of ML packages
  • How to get a model integrated into CMSSW

And much more!

If you think we are missing some important information, please contact the ML Knowledge Subgroup!

"},{"location":"general_advice/intro.html","title":"Introduction","text":"

In general, ML models don't really work out of the box. For example, most often it is not sufficient to simply instantiate the model class, call its fit() method followed by predict(), and then proceed straight to the inference step of the analysis.

from sklearn.datasets import make_circles\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.svm import SVC\n\nX, y = make_circles(noise=0.2, factor=0.5, random_state=1)\nX_train, X_test, y_train, y_test = \\\n        train_test_split(X, y, test_size=.4, random_state=42)\n\nclf = SVC(kernel=\"linear\", C=0.025)\nclf.fit(X_train, y_train)\nprint(f'Accuracy: {clf.score(X_test, y_test)}')\n# Accuracy: 0.4\n

Being an extremely simplified and naive example, one would be lucky to have the code above produce a valid and optimal model. This is because it explicitly doesn't check for those things which could've gone wrong and therefore is prone to producing undesirable results. Indeed, there are several pitfalls which one may encounter on the way towards implementation of ML into their analysis pipeline. These can be easily avoided by being aware of those and performing a few simple checks here and there.

Therefore, this section is intended to review potential issues on the ML side and how they can be approached in order to train a robust and optimal model. The section is designed to be, to a large extent, analysis-agnostic. It will focus on common, generalized validation steps from ML perspective, without paying particular emphasis on the physical context. However, for illustrative purposes, it will be supplemented with some examples from HEP and additional links for further reading. As the last remark, in the following there will mostly an emphasis on the validation items specific to supervised learning. This includes classification and regression problems as being so far the most common use cases amongst HEP analysts.

The General Advice chapter is divided into into 3 sections. Things become logically aligned if presented from the perspective of the training procedure (fitting/loss minimisation part). That is, the sections will group validation items as they need to be investigated:

  • Before training
  • During training
  • After training

Authors: Oleg Filatov

"},{"location":"general_advice/after/after.html","title":"After training","text":"

After the necessary steps to design the ML experiment has been made, the training has been performed and verified to be stable and consistent, there are still a few things to be checked to further solidify the confidence in the model performance.

"},{"location":"general_advice/after/after.html#final-evaluation","title":"Final evaluation","text":"

Before the training, initial data set is to be split into the train and test parts, where the former is used to train the model (possibly, with cross-validation), while the latter remains blinded. Once all the optimisations to the model architecture have been made and the model is \"frozen\", one proceeds to the evaluation of the metrics' values on the test set. This would be the very last check of the model for overfitting and in case there is none, one expects to see little or no difference comparing to the values on (cross)validation set used throughout the training. In turn, any discrepancies could point to possible overfitting happening in the training stage (or also possibly data leakage), which requires further investigation.

The next step to check is the output score of the model (probability1) for each class. It can be done, for example, in the form of a TMVA-like overtraining check (see Figure 1) which also allows to spot overtraining:

Figure 1. Comparison of model output for signal and background classes overlaid for train and test data sets. [source: root-forum.cern.ch]

In general, what is important to look at is that in the category for class C (defined as argmax(score_i)), the score for a class C peaks at values closer to 1. Whereas the other classes doesn't have such property with peaking on the left side of 1 and smoothly falling down to zero as the model score in the category approaches 1. Or, in other words, that the distributions of the model score for various classes are not overlapping and are as far apart as possible. This would be an indication that the model indeed distinguishes between the classes.

Another thing to look at is the data/simulation agreement for class categories. Since it is the output of the model for each category which is used in further statistical inference step, it is important to verify that data/simulation agreement of input features is properly propagated through the model into categories' distribution. This can be achieved by producing the plot similar to the one shown on Figure 2: the stacked templates for backround processes are fitted and compared with the actual predictions for the data for the set of events classified to be in the given category (jet-fakes in the example). If the output data/simulation agreement is worse than the input one, it might point to an existing bias of the model in the way it treats data and simulation events.

Figure 2. Postfit jet-fake NN score for the mutau channel. Note that the distribution for jet-fakes class is dominant in this category and also peaks at value 1 (mind the log scale), which is an indication of good identification of this background process by the model. Furthermore, ratio of data and MC templates is equal to 1 within uncertainties. [source: CMS-PAS-HIG-20-006]"},{"location":"general_advice/after/after.html#robustness","title":"Robustness","text":"

Once there is high confidence that the model isn't overtrained and no distortion in the input feature data/MC agreement is introduced, one can consider studying the robustness of the model to the parameter/input variations. Effectively, the model can be considered as a \"point estimate\", and any variations are helpful to understand the variance of the model outputs - hence, the model's robustness to changes.

A simple example would be a hyperparameter optimisation, where various model parameters a varied to find the best one in terms of performance. Moreover, in HEP there is a helpful (for this particular case) notion of systematic uncertainties, which is a perfect tool to study model robustness to input data variations.

Since in any case they need to be incorporated into the final statistical fit (to be performed on some interpretation of the model score), it implies that these uncertainties need to be \"propagated\" through the model. A sizeable fraction of those uncertainties are so-called \"up/down\" (or shape) variations, and therefore it is a good opportunity to study, how the model output responds to those up/down input feature changes. If there is a high sensitivity observed, one need to consider removing the most influencing feature from the training, or trying decorrelation techniques to decrease the impact of systematic-affected feature on the model output.

"},{"location":"general_advice/after/after.html#systematic-biases","title":"Systematic biases","text":"

Lastly, possible systematic biases arising the ML approach should be estimated. Being a broad and not fully formalised topic, a few examples will be given below to outline the possible sources of those.

  • The first one could be a domain shift, that is the situation where the model is trained on one data domain, but is apllied to a different one (e.g. trained on simulated data, applied on real one). In order to account for that, corresponding scale factor corrections are traditionally derived, and those will come with some uncertainty as well.
  • Another example would be the case of undertraining. Consider the case of fitting a complex polynomial data with a simple linear function. In that case, the model has high bias (and low variance) which results in a systematic shift of its prediction to be taken into account.
  • Care needs to be taken in cases where a cut is applied on the model output. Cuts might potentially introduce shifts and in case of the model score, which is a variable with a complex and non-linear relationship with input features, it might create undesirable biases. For example, in case of cutting on the output score and looking at the invariant mass distribution (e.g. of two jets), one can observe an effect which is known as mass sculpting (see Figure 3). In that case, the background distribution peaks at the mass of the signal resonance used as a signal in the classification task. After applying such cut, signal and background shapes overlap and become very similar, which dillutes the discrimination power between two hypotheses if invariant mass was to be used as the observable to be fitted.
Figure 3. Left: Distributions of signal and background events without selection. Right: Background distributions at 50% signal efficiency (true positive rate) for different classifiers. The unconstrained classifier sculpts a peak at the W-boson mass, while other classifiers do not. [source: arXiv:2010.09745]
  1. Here it is assumed that it can be treated as probability to be assigned to a given class. This is mostly the case if there is a sigmoid/softmax used on the output layer of the neural network and the model is trained with a cross-entropy loss function.\u00a0\u21a9

"},{"location":"general_advice/before/domains.html","title":"Domains","text":"

Data plays a crucial role in the process of training any ML model. It is something from which the model learns to solve a given task and therefore care needs to be taken with its handling. There are two main considerations when collecting and preparing data for an ML task:

  1. The data set should be relevant to the problem and should represent the underlying structure of the problem without containing potential biases and irrelevant deviations (e.g. MC simulation artefacts).
  2. A proper preprocessing of the data set should be performed so that the training step goes smoothly.

In this section a general domain perspective on data will be covered. In the following sections a more granular look will be taken from the side of features and construction of inputs to the model.

"},{"location":"general_advice/before/domains.html#coverage","title":"Coverage","text":"

To begin with, one needs to bear in mind that training data should be as close as possible to data they expect to have in the context of analysis. Speaking in more formal terms,

Domains of training (used to train the model) and inference (used to make final predictions) data sets should not sizeably diverge.

Examples
  • In most of the cases the model is usually trained on MC simulated data and later on applied to data to produce predictions which are then passed on to statistical inference step. MC simulation isn't perfect and therefore there are always differences between simulation and data domains. This can lead to the cases when model learns simulation artefacts which come e.g. from detector response mismodelling. Thus, its performance on data may be at least suboptimal and at most meaningless.
  • Consider the model which is trained to predict the energy of a hadron given its energy deposits in the calorimeter (represented e.g. in the form of image or graph). Data consists of the showers initiated by a particle generated by a particle gun and having discrete values of energies (e.g. 1 GeV, 10 GeV, 20 GeV, etc.). However, in the real world settings, the model will be applied to showers produced by particles with underlying continuous energy spectrum. Although ML models are known for their capability to interpolate beyond their training domain, without apropriate tests model performance in the parts of the energy spectrum outside of its training domain is not a priori clear.
"},{"location":"general_advice/before/domains.html#solution","title":"Solution","text":"

It is particularly not easy to build a model entirely robust to domain shift, so there is no general framework yet to approach and recover for discrepancies between training and inference domains altogether. However, there is research ongoing in this direction and several methods to recover for specific deviations have been already proposed.

It is a widely known practice to introduce scale factor (SF) corrections to account for possible discrepancies between data and MC simulation. Effectively, that means that the model is probed on some part of the domain on which it wasn't trained on (data) and then corrected for any differences by using a meaningful set of observables to derive SFs. One particularly promising approaches to remedy for data/MC domain difference is to use adversarial approaches to fully leverage the multidimensionality of the problem, as described in a DeepSF note.

Another solution would be to incorporate methods of domain adaptation into an ML pipeline, which essentially guide the model to be invariant and robust towards domain shift. Particularly in HEP, a Learning to Pivot with Adversarial Networks paper was one of the pioneers to investigate how a pile-up dependency can be mitigated, which can also be easily expanded to building a model robust to domain shift1.

Last but not the least, a usage of Bayesian neural networks has a great advantage of getting uncertainties estimate along with each prediction. If these uncertainties are significantly larger for some samples, this could indicate that they come from the domain beyond the training one (a so-called out-of-distribution samples). This post hoc analysis of prediction uncertainties, for example, can point to inconsistencies in or incompleteness of MC simulation/ data-driven methods of the background estimation.

"},{"location":"general_advice/before/domains.html#population","title":"Population","text":"

Furthermore, nowadays analyses are searching for very rare processes and therefore are interested in low-populated regions of the phase space. And even though the domain of interest may be covered in the training data set, it may also not be sufficiently covered in terms of the number of samples in the training data set, which populate those regions. That makes the model behaviour on an event which falls into those regions unpredictable - because it couldn't learn how to generalise in those areas due to a lack of data to learn from. Therefore,

It is important to make sure that the phase space of interest is well-represented in the training data set.

Example

This is what is often called in HEP jargon \"little statistics in the tails\": meaning that too few events can be found in the tails of the corresponding distribution, e.g. in the high-pt region. This might be important because the topology of events changes when one enters high-pt areas of the phase space (aka boosted regime). This further means that the model should be able to capture this change in the event signature. However, it might fail to do so due to a little available data to learn from comparing to a low-pt region.

"},{"location":"general_advice/before/domains.html#solution_1","title":"Solution","text":"

Clearly, a way out in that case would be to provide enough training data to cover those regions (also ensuring that the model has enough capacity to embrace diverse and complex topologies).

Another solution would be to communicate to the model importance of specific topologies, which can be done for example by upweighting those events' contribution to the loss function.

Lastly, it might be worth trying to train several models, each targeting its specific region, instead of a general-purpose one (e.g. low-pt & boosted/merged topology tagger). Effectively, factorisation of various regions disentangle the problem of their separation for a single model and delegates it to an ensemble of dedicated models, each targeting its specific region.

  1. From that paper on, the HEP community started to explore a similar topic of model decorrelation, i.e. how to build a model which would be invariant to a particular variable or property of data. For a more detailed overview please refer to Section 2 of this paper.\u00a0\u21a9

"},{"location":"general_advice/before/features.html","title":"Features","text":"

In the previous section, the data was considered from a general \"domain\" perspective and in this section a more low level view will be outlined. In particular, an emphasis will be made on features (input variables) as they play a crucial role in the training of any ML model. Essentially being the handle on and the gateway into data for the model, they are expected to reflect the data from the perspective which is important to the problem at hand and therefore define the model performance on the task.

The topic of feature engineering is very extensive and complex to be covered in this section, so the emphasis will be made primarily on the general aspects relevant to the HEP context. Broadly speaking, one should ask themselves the following questions during the data preparation:

  • Are features understood?
  • Are features correctly modelled?
  • Are features appropriately processed?
"},{"location":"general_advice/before/features.html#understanding","title":"Understanding","text":"

Clearly one should motivate for themselves (and then possibly for analysis reviewers) why this exact set of features and not the other one has been selected1. Aside from physical understanding and intuition it would be good if a priori expert knowledge is supplemented by running further experiments.

Here one can consider either studies done prior to the training or after it. As for the former, studying feature correlations (with the target variable as well) e.g. by computing Pearson and/or Spearman correlation coefficients and plotting several histogram/scatter plots could bring some helpful insights. As for the latter, exploring feature importances as the trained model deems it important can boost the understanding of both the data and the model altogether.

"},{"location":"general_advice/before/features.html#modelling","title":"Modelling","text":"

Although seemingly obvious, for the sake of completeness the point of achieving good data/MC agreement should be mentioned. It has always been a must to be checked in a cut-based approach and ML-based one is of no difference: the principle \"garbage in, garbage out\" still holds.

Example

For example, classical feed-forward neural network is just a continuous function mapping the input space to the output one, so any discrepancies in the input might propagate to the output. In case of boosted decision trees it is also applicable: any (domain) differences in the shape of input (training) distribution w.r.t. true \"data\" distribution might sizeably affect the construction of decision boundary in the feature space.

Figure 1. Control plot for a visible mass of tau lepton pair in emu final state. [source: CMS-TAU-18-001]

Since features are the handle on the data, checking for each input feature that the ratio of data to MC features' histograms is close to 1 within uncertainties (aka by eye) is one of the options. For a more formal approach, one can perform goodness of fit (GoF) tests in 1D and 2D, checking that as it was used for example in the analysis of Higgs boson decaying into tau leptons.

If the modelling is shown to be insufficient, the corresponding feature should be either removed, or mismodelling needs to be investigated and resolved.

"},{"location":"general_advice/before/features.html#processing","title":"Processing","text":"

Feature preprocessing can also be understood from a broader perspective of data preprocessing, i.e. transformations which need to be performed with data prior to training a model. Another way to look at this is of a step where raw data is converted into prepared data. That makes it an important part of any ML pipeline since it ensures that a smooth convergence and stability of the training is reached.

Example

In fact, the training process might not even begin (presence of NaN values) or break in the middle (outlier causing the gradients to explode). Furthermore, data can be completely misunderstood by the model which can potentially caused undesirable interpretation and performance (treatment of categorical variables as numerical).

Therefore, below there is a non-exhaustive list of the most common items to be addressed during the preprocessing step to ensure the good quality of training. For a more comprehensive overview and also code examples please refer to a detailed documentation of sklearn package and also on possible pitfalls which can arise at this point.

  • Feature encoding
  • NaN/inf/missing values2
  • Outliers & noisy data
  • Standartisation & transformations

Finally, these are the items which are worth considering in the preprocessing of data in general. However, one can also apply transformations at the level of batches as they are passed through the model. This will be briefly covered in the following section.

  1. Here it is already assumed that a proper data representation has been chosen, i.e. the way to vectorize the data to form a particular structure (e.g. image -> tensor, social network -> graph, text -> embeddings). Being on its own a whole big topic, it is left for a curious reader to dive into.\u00a0\u21a9

  2. Depending on the library and how particular model is implemented there, these values can be handled automatically under the hood.\u00a0\u21a9

"},{"location":"general_advice/before/inputs.html","title":"Inputs","text":"

After data is preprocessed as a whole, there is a question of how this data should be supplied to the model. On its way there it potentially needs to undergo a few splits which will be described below. Plus, a few additional comments about training weights and motivation for their choice will be outlined.

"},{"location":"general_advice/before/inputs.html#data-split","title":"Data split","text":"

The first thing one should consider to do is to perform a split of the entire data set into train/validation(/test) data sets. This is an important one because it serves the purpose of diagnosis for overfitting. The topic will be covered in more details in the corresponding section and here a brief introduction will be given.

Figure 1. Decision boundaries for underfitted, optimal and overfitted models. [source: ibm.com/cloud/learn/overfitting]

The trained model is called to be overfitted (or overtrained) when it fails to generalise to solve a given problem.

One of examples would be that the model learns to predict exactly the training data and once given a new unseen data drawn from the same distribution it fails to predict the target corrrectly (right plot on Figure 1). Obviously, this is an undesirable behaviour since one wants their model to be \"universal\" and provide robust and correct decisions regardless of the data subset sampled from the same population.

Hence the solution to check for ability to generalise and to spot overfitting: test a trained model on a separate data set, which is the same1 as the training one. If the model performance gets significantly worse there, it is a sign that something went wrong and the model's predictive power isn't generalising to the same population.

Figure 2. Data split worflow before the training. Also cross-validation is shown as the technique to find optimal hyperparameters. [source: scikit-learn.org/stable/modules/cross_validation.html]

Clearly, the simplest way to find this data set is to put aside a part of the original one and leave it untouched until the final model is trained - this is what is called \"test\" data set in the first paragraph of this subsection. When the model has been finalised and optimised, this data set is \"unblinded\" and model performance on it is evaluated. Practically, this split can be easily performed with train_test_split() method of sklearn library.

But it might be not that simple

Indeed, there are few things to be aware of. Firstly, there is a question of how much data needs to be left for validation. Usually it is common to take the test fraction in the range [0.1, 0.4], however it is mostly up for analyzers to decide. The important trade-off which needs to be taken into account here is that between robustness of the test metric estimate (too small test data set - poorly estimated metric) and robustness of the trained model (too little training data - less performative model).

Secondly, note that the split should be done in a way that each subset is as close as possible to the one which the model will face at the final inference stage. But since usually it isn't feasible to bridge the gap between domains, the split at least should be uniform between training/testing to be able to judge fairly the model performance.

Lastly, in extreme case there might be no sufficient amount of data to perform the training, not even speaking of setting aside a part of it for validation. Here a way out would be to go for a few-shot learning, using cross-validation during the training, regularising the model to avoid overfitting or to try to find/generate more (possibly similar) data.

Lastly, one can also considering to put aside yet another fraction of original data set, what was called \"validation\" data set. This can be used to monitor the model during the training and more details on that will follow in the overfitting section.

"},{"location":"general_advice/before/inputs.html#batches","title":"Batches","text":"

Usually it is the case the training/validation/testing data set can't entirely fit into the memory due to a large size. That is why it gets split into batches (chunks) of a given size which are then fed one by one into the model during the training/testing.

While forming the batches it is important to keep in mind that batches should be sampled uniformly (i.e. from the same underlying PDF as of the original data set).

That means that each batch is populated similarly to the others according to features which are important to the given task (e.g. particles' pt/eta, number of jets, etc.). This is needed to ensure that gradients computed for each batch aren't different from each other and therefore the gradient descent doesn't encounter any sizeable stochasticities during the optimisation step.2

Lastly, it was already mentioned that one should perform preprocessing of the data set prior to training. However, this step can be substituted and/or complemented with an addition of a layer into the architecture, which will essentially do a specified part of preprocessing on every batch as they go through the model. One of the most prominent examples could be an addition of batch/group normalization, coupled with weight standardization layers which turned out to sizeably boost the performance on the large variety of benchmarks.

"},{"location":"general_advice/before/inputs.html#training-weights","title":"Training weights","text":"

Next, one can zoom into the batch and consider the level of single entries there (e.g. events). This is where the training weights come into play. Since the value of a loss function for a given batch is represented as a sum over all the entries in the batch, this sum can be naturally turned into a weighted sum. For example, in case of a cross-entropy loss with y_pred, y_true, w being vectors of predicted labels, true labels and weights respectively:

def CrossEntropy(y_pred, y_true, w): # assuming y_true = {0, 1}\n    return -w*[y_true*log(y_pred) + (1-y_true)*log(1-y_pred)]\n

It is important to disentangle here two factors which define the weight to be applied on a per-event basis because of the different motivations behind them:

  • accounting for imbalance in training data
  • accounting for imbalance in nature
"},{"location":"general_advice/before/inputs.html#imbalance-in-training-data","title":"Imbalance in training data","text":"

The first point is related to the fact, that in case of classification we may have significantly more (>O(1) times) training data for one class than for the other. Since the training data usually comes from MC simulation, that corresponds to the case when there is more events generated for one physical process than for another. Therefore, here we want to make sure that model is equally presented with instances of each class - this may have a significant impact on the model performance depending on the loss/metric choice.

Example

Consider the case when there is 1M events of target = 0 and 100 events of target = 1 in the training data set and a model is fitted by minimising cross-entropy to distinguish between those classes. In that case the resulted model can easily turn out to be a constant function predicting the majority target = 0, simply because this would be the optimal solution in terms of the loss function minimisation. If using accuracy as a metric for validation, this will result in a value close to 1 on the training data.

To account for this type of imbalance, the following weight simply needs to be introduced according to the target label of an object:

train_df['weight'] = 1\ntrain_df.loc[train_df.target == 0, 'weight'] /= np.sum(train_df.loc[train_df.target == 0, 'weight'])\ntrain_df.loc[train_df.target == 1, 'weight'] /= np.sum(train_df.loc[train_df.target == 1, 'weight'])\n

Alternatively, one can consider using other ways of balancing classes aside of those with training weights. For a more detailed description of them and also a general problem statement see imbalanced-learn documentation.

"},{"location":"general_advice/before/inputs.html#imbalance-in-nature","title":"Imbalance in nature","text":"

The second case corresponds to the fact that in experiment we expect some classes to be more represented than the others. For example, the signal process usually has way smaller cross-section than background ones and therefore we expect to have in the end fewer events of the signal class. So the motivation of using weights in that case would be to augment the optimisation problem with additional knowledge of expected contribution of physical processes.

Practically, the notion of expected number of events is incorporated into the weights per physical process so that the following conditions hold3:

As a part of this reweighting, one would naturally need to perform the normalisation as of the previous point, however the difference between those two is something which is worth emphasising.

  1. That is, sampled independently and identically (i.i.d) from the same distribution.\u00a0\u21a9

  2. Although this is a somewhat intuitive statement which may or may not be impactful for a given task and depends on the training procedure itself, it is advisable to keep this aspect in mind while preparing batches for training.\u00a0\u21a9

  3. See also Chapter 2 of the HiggsML overview document \u21a9

"},{"location":"general_advice/before/metrics.html","title":"Metrics & Losses","text":""},{"location":"general_advice/before/metrics.html#metric","title":"Metric","text":"

Metric is a function which evaluates model's performance given true labels and model predictions for a particular data set.

That makes it an important ingredient in the model training as being a measure of the model's quality. However, metrics as estimators can be sensitive to some effects (e.g. class imbalance) and provide biased or over/underoptimistic results. Additionally, they might not be relevant to a physical problem in mind and to the undestanding of what is a \"good\" model1. This in turn can result in suboptimally tuned hyperparameters or in general to suboptimally trained model.

Therefore, it is important to choose metrics wisely, so that they reflect the physical problem to be solved and additionaly don't introduce any biases in the performance estimate. The whole topic of metrics would be too broad to get covered in this section, so please refer to a corresponding documentation of sklearn as it provides an exhaustive list of available metrics with additional materials and can be used as a good starting point.

Examples of HEP-specific metrics

Speaking of those metrics which were developed in the HEP field, the most prominent one is approximate median significance (AMS), firstly introduced in Asymptotic formulae for likelihood-based tests of new physics and then adopted in the HiggsML challenge on Kaggle.

Essentially being an estimate of the expected signal sensitivity and hence being closely related to the final result of analysis, it can also be used not only as a metric but also as a loss function to be directly optimised in the training.

"},{"location":"general_advice/before/metrics.html#loss-function","title":"Loss function","text":"

In fact, metrics and loss functions are very similar to each other: they both give an estimate of how well (or bad) model performs and both used to monitor the quality of the model. So the same comments as in the metrics section apply to loss functions too. However, loss function plays a crucial role because it is additionally used in the training as a functional to be optimised. That makes its choice a handle to explicitly steer the training process towards a more optimal and relevant solution.

Example of things going wrong

It is known that L2 loss (MSE) is sensitive to outliers in data and L1 loss (MAE) on the other hand is robust to them. Therefore, if outliers were overlooked in the training data set and the model was fitted, it may result in significant bias in its predictions. As an illustration, this toy example compares Huber vs Ridge regressors, where the latter shows a more robust behaviour.

A simple example of that was already mentioned in domains section - namely, one can emphasise specific regions in the phase space by attributing events there a larger weight in the loss function. Intuitively, for the same fraction of mispredicted events in the training data set, the class with a larger attributed weight should bring more penalty to the loss function. This way model should be able to learn to pay more attention to those \"upweighted\" events2.

Examples in HEP beyond classical MSE/MAE/cross entropy
  • b-jet energy regression, being a part of nonresonant HH to bb gamma gamma analysis, uses Huber and two quantile loss terms for simultaneous prediction of point and dispersion estimators of the target disstribution.
  • DeepTau, a CMS deployed model for tau identification, uses several focal loss terms to give higher weight to more misclassified cases

However, one can go further than that and consider the training procedure from a larger, statistical inference perspective. From there, one can try to construct a loss function which would directly optimise the end goal of the analysis. INFERNO is an example of such an approach, with a loss function being an expected uncertainty on the parameter of interest. Moreover, one can try also to make the model aware of nuisance parameters which affect the analysis by incorporating those into the training procedure, please see this review for a comprehensive overview of the corresponding methods.

  1. For example, that corresponds to asking oneself a question: \"what is more suitable for the purpose of the analysis: F1-score, accuracy, recall or ROC AUC?\"\u00a0\u21a9

  2. However, these are expectations one may have in theory. In practise, optimisation procedure depends on many variables and can go in different ways. Therefore, the weighting scheme should be studied by running experiments on the case-by-case basis.\u00a0\u21a9

"},{"location":"general_advice/before/model.html","title":"Model","text":"

There is definitely an enormous variety of ML models available on the market, which makes the choice of a suitable one for a given problem at hand not entirely straightforward. So far being to a large extent an experimental field, the general advice here would be to try various and pick the one giving the best physical result.

However, there are in any case several common remarks to be pointed out, all glued together with a simple underlying idea:

Start off from a simple baseline, then gradually increase the complexity to improve upon it.

  1. In the first place, one need to carefully consider whether there is a need for training an ML model at all. There might be problems where this approach would be a (time-consuming) overkill and a simple conventional statistical methods would deliver results faster and even better.

  2. If ML methods are expected to bring improvement, then it makes sense to try out simple models first. Assuming a proper set of high-level features has been selected, ensemble of trees (random forest/boosted decision tree) or simple feedforward neural networks might be a good choice here. If time and resources permit, it might be beneficial to compare the results of these trainings to a no-ML approach (e.g. cut-based) to get the feeling of how much the gain in performance is. In most of the use cases, those models will be already sufficient to solve a given classification/regression problem in case of dealing with high-level variables.

  3. If it feels like there is still room for improvement, try hyperparameter tuning first to see if it is possible to squeeze more performance out of the current model and data. It can easily be that the model is sensitive to a hyperparameter choice and a have a sizeable variance in performance across hyperparameter space.

  4. If the hyperparameter space has been thoroughly explored and optimal point has been found, one can additionally try to play around with the data, for example, by augmenting the current data set with more samples. Since in general the model performance profits from having more training data, augmentation might also boost the overall performance.

  5. Lastly, more advanced architectures can be probed. At this point the choice of data representation plays a crucial role since more complex architectures are designed to adopt more sophisticated patterns in data. While in ML research is still ongoing to unify together all the complexity of such models (and promisingly, also using effective field theory approach), in HEP there's an ongoing process of probing various architectures to see which type fits the most in HEP field.

Models in HEP

One of the most prominent benchmarks so far is the one done by G. Kasieczka et. al on the top tagging data set, where in particular ParticleNet turned out to be a state of the art. This had been a yet another solid argument in favour of using graph neural networks in HEP due to its natural suitability in terms of data representation.

Illustration from G. Kasieczka et. al showing ROC curves for all evaluated algorithms.

"},{"location":"general_advice/during/opt.html","title":"Optimisation problems","text":"Figure 1. The loss surfaces of ResNet-56 with/without skip connections. [source: \"Visualizing the Loss Landscape of Neural Nets\" paper]

However, it might be that for a given task overfitting is of no concern, but there are still instabilities in loss function convergence happening during the training1. The loss landscape is a complex object having multiple local minima and which is moreover not at all understood due to the high dimensionality of the problem. That makes the gradient descent procedure of finding a minimum not that simple. However, if instabilities are observed, there are a few common things which could explain that:

  • The main candidate for a problem might be the learning rate (LR). Being an important hyperparameter which steers the optimisation, setting it too high make cause extremily stochastic behaviour which will likely cause the optimisation to get stuck in some random minimum being way far from optimum. Oppositely, setting it too low may cause the convergence to take very long time. The optimal value in between those extremes can still be problematic due to a chance of getting stuck in a local minimum on the way towards a better one. That is why several approaches on LR schedulers (e.g. cosine annealing) and also adaptive LR (e.g. Adam being the most prominent one) have been developed to have more flexibility during the training, as opposed to setting LR fixed from the very beginning of the training until its end.

  • Another possibility is that there are NaN/inf values or uniformities/outliers appearing in the input batches. It can cause the gradient updates to go beyond the normal scale and therefore dramatically affect the stability of the loss optimisation. This can be avoided by careful data preprocessing and batch formation.

  • Last but not the least, there is a chance that gradients will explode or vanish during the training, which will reveal itself as a rapid increase/stagnation in the loss function values. This is largely the feature of deep architectures, where during the backpropagation gradients are accumulated from one layer to another, and therefore any minor deviations in scale can exponentially amplify/diminish as they get multiplied. Since it is the scale of the trainable weights themselves which defines the weight gradients, a proper weight initialisation can foster smooth and consistent gradient updates. Also, batch normalisation together with weight standartization showed to be a powerful technique to consistently improve performance across various domains. Finally, a choice of activation function is particularly important since it directly contributes to a gradient computation. For example, a sigmoid function is known to cause gradients to vanish due to its gradient being 0 at large input values. Therefore, it is often suggested to stick to classical ReLU or try other alternatives to see if it brings improvement in performance.

  1. Sometimes particularly peculiar.\u00a0\u21a9

"},{"location":"general_advice/during/overfitting.html","title":"Overfitting","text":"

Given that the training experiment has been set up correctly (with some of the most common problems described in before training section), actually few things can go wrong during the training process itself. Broadly speaking, they fall into two categories: overfitting related and optimisation problem related. Both of them can be easily spotted by closely monitoring the training procedure, as will be described in the following.

"},{"location":"general_advice/during/overfitting.html#overfitting","title":"Overfitting","text":"

The concept of overfitting (also called overtraining) was previously introduced in inputs section and here we will elaborate a bit more on that. In its essence, overfitting as the situation where the model fails to generalise to a given problem can have several underlying explanations:

The first one would be the case where the model complexity is way too large for a problem and a data set being considered.

Example

A simple example would be fitting of some linearly distributed data with a polynomial function of a large degree. Or in general, when the number of trainable parameters is significantly larger when the size of the training data set.

This can be solved prior to training by applying regularisation to the model, which in it essence means constraining its capacity to learn the data representation. This is somewhat related also to the concept of Ockham's razor: namely that the less complex an ML model, the more likely that a good empirical result is not just due to the peculiarities of the data sample. As of the practical side of regularisation, please have a look at this webpage for a detailed overview and implementation examples.

Furthermore, a recipe for training neural networks by A. Karpathy is a highly-recommended guideline not only on regularisation, but on training ML models in general.

The second case is a more general idea that any reasonable model at some point starts to overfit.

Example

Here one can look at overfitting as the point where the model considers noise to be of the same relevance and start to \"focus\" on it way too much. Since data almost always contains noise, this makes it in principle highly probable to reach overfitting at some point.

Both of the cases outlined above can be spotted simply by tracking the evolution of loss/metrics on the validation data set . Which means that additionally to the train/test split done prior to training (as described in inputs section), one need to set aside also some fraction of the training data to perform validation throughout the training. By plotting the values of loss function/metric both on train and validation sets as the training proceeds, overfitting manifests itself as the increase in the value of the metric on the validation set while it is still continues to decrease on the training set:

Figure 1. Error metric as a function of number of iterations for train and validation sets. Vertical dashed line represents the separation between the region of underfitting (model hasn't captured well the data complexity to solve the problem) and overfitting (model does not longer generalise to unseen data). The point between these two regions is the optimal moment when the training should stop. [source: ibm.com/cloud/learn/overfitting]

Essentially, it means that from that turning point onwards the model is trying to learn better and better the noise in training data at the expense of generalisation power. Therefore, it doesn't make sense to train the model from that point on and the training should be stopped.

To automate the process of finding this \"sweat spot\", many ML libraries include early stopping as one of its parameters in the fit() function. If early stopping is set to, for example, 10 iterations, the training will automatically stop once the validation metric is no longer improving for the last 10 iterations.

"},{"location":"general_advice/during/xvalidation.html","title":"Cross-validation","text":"

However, in practice what one often deals with is a hyperparameter optimisation - running of several trainings to find the optimal hyperparameter for a given family of models (e.g. BDT or feed-forward NN).

The number of trials in the hyperparameter space can easily reach hundreds or thousands, and in that case naive approach of training the model for each hyperparameters' set on the same train data set and evaluating its performance on the same test data set is very likely prone to overfitting. In that case, an experimentalist overfits to the test data set by choosing the best value of the metric and effectively adapting the model to suit the test data set best, therefore loosing the model's ability to generalise.

In order to prevent that, a cross-validation (CV) technique is often used:

Figure 1. Illustration of the data set split for cross-validation. [source: scikit-learn.org/stable/modules/cross_validation.html]

The idea behind it is that instead of a single split of the data into train/validation sets, the training data set is split into N folds. Then, the model with the same fixed hyperparameter set is trained N times in a way that at the i-th iteration the i-th fold is left out of the training and used only for validation, while the other N-1 folds are used for the training.

In this fashion, after the training of N models in the end there is N values of a metric computed on each fold. The values now can be averaged to give a more robust estimate of model performance for a given hyperparameter set. Also a variance can be computed to estimate the range of metric values. After having completed the N-fold CV training, the same approach is to be repeated for other hyperparameter values and the best set of those is picked based on the best fold-averaged metric value.

Further insights

Effectively, with CV approach the whole training data set plays the role of a validation one, which makes the overfitting to a single chunk of it (as in naive train/val split) less likely to happen. Complementary to that, more training data is used to train a single model oppositely to a single and fixed train/val split, moreover making the model less dependant on the choice of the split.

Alternatively, one can think of this procedure is of building a model ensemble which is inherently an approach more robust to overfitting and in general performing better than a single model.

"},{"location":"inference/checklist.html","title":"Integration checklist","text":"

Todo.

"},{"location":"inference/conifer.html","title":"Direct inference with conifer","text":""},{"location":"inference/conifer.html#introduction","title":"Introduction","text":"

conifer is a Python package developed by the Fast Machine Learning Lab for the deployment of Boosted Decision Trees in FPGAs for Level 1 Trigger applications. Documentation, examples, and tutorials are available from the conifer website, GitHub, and the hls4ml tutorial respectively. conifer is on the Python Package Index and can be installed like pip install conifer. Targeting FPGAs requires Xilinx's Vivado/Vitis suite of software. Here's a brief summary of features:

  • conversion from common BDT training frameworks: scikit-learn, XGBoost, Tensorflow Decision Forests (TF DF), TMVA, and ONNX
  • conversion to FPGA firmware with backends: HLS (C++ for FPGA), VHDL, C++ (for CPU)
  • utilities for bit- and cycle-accurate firmware simulation, and interface to FPGA synthesis tools for evaluation and deployment from Python
"},{"location":"inference/conifer.html#emulation-in-cmssw","title":"Emulation in CMSSW","text":"

All L1T algorithms require bit-exact emulation for performance studies and validation of the hardware system. For conifer this is provided with a single header file at L1Trigger/Phase2L1ParticleFlow/interface/conifer.h. The user must also provide the BDT JSON file exported from the conifer Python tool for their model. JSON loading in CMSSW uses the nlohmann/json external.

Both the conifer FPGA firmware and C++ emulation use Xilinx's arbitrary precision types for fixed-point arithmetic (hls external of CMSSW). This is cheaper and faster in the FPGA fabric than floating-point types. An important part of the model preparation process is choosing the proper fixed-point data types to avoid loss of performance compared to the trained model. Input preprocessing, in particular scaling, can help constrain the input variables to a smaller numerical range, but may also have a hardware cost to implement. In C++ the arbitrary precision types are specified like: ap_fixed<width, integer, rounding mode, saturation mode>.

Minimal preparation from Python:

import conifer\nmodel = conifer. ... # convert or load a conifer model\n# e.g. model = conifer.converters.convert_from_xgboost(xgboost_model)\nmodel.save('my_bdt.json')\n

CMSSW C++ user code:

// include the conifer emulation header file\n#include \"L1Trigger/Phase2L1ParticleFlow/interface/conifer.h\"\n\n... model setup\n// define the input/threshold and score types\n// important: this needs to match the firmware settings for bit-exactness!\n// note: can use native types like float/double for development/debugging\ntypedef ap_fixed<18,8> input_t;\ntypedef ap_fixed<12,3,AP_RND_CONV,AP_SAT> score_t;\n\n// create a conifer BDT instance\n// 'true' to use balanced add-tree score aggregation (needed for bit-exactness)\nbdt = conifer::BDT<input_t, score_t, true>(\"my_bdt.json\");\n\n... inference\n// prepare the inputs, vector length same as model n_features\nstd::vector<input_t> inputs = ... \n// run inference, scores vector length same as model n_classes (or 1 for binary classification/regression)\nstd::vector<score_t> scores = bdt.decision_function(inputs);\n

conifer does not compute class probabilities from the raw predictions for the avoidance of extra resource and latency cost in the L1T deployment. Cuts or working points should therefore be applied on the raw predictions.

"},{"location":"inference/hls4ml.html","title":"Direct inference with hls4ml","text":"

hls4ml is a Python package developed by the Fast Machine Learning Lab. It's primary purpose is to create firmware implementations of machine learning (ML) models to be run on FPGAs. The package interfaces with a high-level synthesis (HLS) backend (i.e. Xilinx Vivado HLS) to transpile the ML model into hardware description language (HDL). The primary hls4ml documentation, including API reference pages, is located here.

The main hls4ml tutorial code is kept on GitHub. Users are welcome to walk through the notebooks at their own pace. There is also a set of slides linked to the README.

That said, there have been several cases where the hls4ml developers have given live demonstrations and tutorials. Below is a non-exhaustive list of tutorials given in the last few years (newest on top).

Workshop/Conference Date Links 23rd Virtual IEEE Real Time Conference August 03, 2022 Indico 2022 CMS ML Town Hall July 22, 2022 Contribution Link a3d3 hls4ml @ Snowmass CSS 2022: Tutorial July 21, 2022 Slides, Recording, JupyterHub Fast Machine Learning for Science Workshop December 3, 2020 Indico, Slides, GitHub, Interactive Notebooks hls4ml @ UZH ML Workshop November 17, 2020 Indico, Slides ICCAD 2020 November 5, 2020 https://events-siteplex.confcats.io/iccad2022/wp-content/uploads/sites/72/2021/12/2020_ICCAD_ConferenceProgram.pdf, GitHub 4th IML Workshop October 19, 2020 Indico, Slides, Instructions, Notebooks, Recording 22nd Virtual IEEE Real Time Conference October 15, 2020 Indico, Slides, Notebooks 30th International Conference on Field-Programmable Logic and Applications September 4, 2020 Program hls4ml tutorial @ CERN June 3, 2020 Indico, Slides, Notebooks Fast Machine Learning September 12, 2019 Indico 1st Real Time Analysis Workshop, Universit\u00e9 Paris-Saclay July 16, 2019 Indico, Slides, Autoencoder Tutorial"},{"location":"inference/onnx.html","title":"Direct inference with ONNX Runtime","text":"

ONNX is an open format built to represent machine learning models. It is designed to improve interoperability across a variety of frameworks and platforms in the AI tools community\u2014most deep learning frameworks (e.g. XGBoost, TensorFlow, PyTorch which are frequently used in CMS) support converting their model into the ONNX format or loading a model from an ONNX format.

The figure showing the ONNX interoperability. (Source from website.)

ONNX Runtime is a tool aiming for the acceleration of machine learning inferencing across a variety of deployment platforms. It allows to \"run any ONNX model using a single set of inference APIs that provide access to the best hardware acceleration available\". It includes \"built-in optimization features that trim and consolidate nodes without impacting model accuracy.\"

The CMSSW interface to ONNX Runtime is avaiable since CMSSW_11_1_X (cmssw#28112, cmsdist#5020). Its functionality is improved in CMSSW_11_2_X. The final implementation is also backported to CMSSW_10_6_X to facilitate Run 2 UL data reprocessing. The inference of a number of deep learning tagger models (e.g. DeepJet, DeepTauID, ParticleNet, DeepDoubleX, etc.) has been made with ONNX Runtime in the routine of UL processing and has gained substantial speedup.

On this page, we will use a simple example to show how to use ONNX Runtime for deep learning model inference in the CMSSW framework, both in C++ (e.g. to process the MiniAOD file) and in Python (e.g. using NanoAOD-tools to process the NanoAODs). This may help readers who will deploy an ONNX model into their analyses or in the CMSSW framework.

"},{"location":"inference/onnx.html#software-setup","title":"Software Setup","text":"

We use CMSSW_11_2_5_patch2 to show the simple example for ONNX Runtime inference. The example can also work under the new 12 releases (note that inference with C++ can also run on CMSSW_10_6_X)

export SCRAM_ARCH=\"slc7_amd64_gcc900\"\nexport CMSSW_VERSION=\"CMSSW_11_2_5_patch2\"\n\nsource /cvmfs/cms.cern.ch/cmsset_default.sh\n\ncmsrel \"$CMSSW_VERSION\"\ncd \"$CMSSW_VERSION/src\"\n\ncmsenv\nscram b\n
"},{"location":"inference/onnx.html#converting-model-to-onnx","title":"Converting model to ONNX","text":"

The model deployed into CMSSW or our analysis needs to be converted to ONNX from the original framework format where it is trained. Please see here for a nice deck of tutorials on converting models from different mainstream frameworks into ONNX.

Here we take PyTorch as an example. A PyTorch model can be converted by torch.onnx.export(...). As a simple illustration, we convert a randomly initialized feed-forward network implemented in PyTorch, with 10 input nodes and 2 output nodes, and two hidden layers with 64 nodes each. The conversion code is presented below. The output model model.onnx will be deployed under the CMSSW framework in our following tutorial.

Click to expand
import torch\nimport torch.nn as nn\ntorch.manual_seed(42)\n\nclass SimpleMLP(nn.Module):\n\n    def __init__(self, **kwargs):\n        super(SimpleMLP, self).__init__(**kwargs)\n        self.mlp = nn.Sequential(\n            nn.Linear(10, 64), nn.BatchNorm1d(64), nn.ReLU(), \n            nn.Linear(64, 64), nn.BatchNorm1d(64), nn.ReLU(), \n            nn.Linear(64, 2), nn.ReLU(), \n            )\n    def forward(self, x):\n        # input x: (batch_size, feature_dim=10)\n        x = self.mlp(x)\n        return torch.softmax(x, dim=1)\n\nmodel = SimpleMLP()\n\n# create dummy input for the model\ndummy_input = torch.ones(1, 10, requires_grad=True) # batch size = 1\n\n# export model to ONNX\ntorch.onnx.export(model, dummy_input, \"model.onnx\", verbose=True, input_names=['my_input'], output_names=['my_output'])\n
"},{"location":"inference/onnx.html#inference-in-cmssw-c","title":"Inference in CMSSW (C++)","text":"

We will introduce how to write a module to run inference on the ONNX model under the CMSSW framework. CMSSW is known for its multi-threaded ability. In a threaded framework, multiple threads are served for processing events in the event loop. The logic is straightforward: a new event is assigned to idled threads following the first-come-first-serve princlple.

In most cases, each thread is able to process events individually as the majority of event processing workflow can be accomplished only by seeing the information of that event. Thus, the stream modules (stream EDAnalyzer and stream EDFilter) are used frequently as each thread holds an individual copy of the module instance\u2014they do not need to communicate with each other. It is however also possible to share a global cache object between all threads in case sharing information across threads is necessary. In all, such CMSSW EDAnalyzer modules are declared by class MyPlugin : public edm::stream::EDAnalyzer<edm::GlobalCache<CacheData>> (similar for EDFilter). Details can be found in documentation on the C++ interface of stream modules.

Let's then think about what would happen when interfacing CMSSW with ONNX for model inference. When ONNX Runtime accepts a model, it converts the model into an in-memory representation, and performance a variety of optimizations depending on the operators in the model. The procedure is done when an ONNX Runtime Session is created with an inputting model. The economic method will then be to hold only one Session for all threads\u2014this may save memory to a large extent, as the model has only one copy in memory. Upon request from multiple threads to do inference with their input data, the Session accepts those requests and serializes them, then produces the output data. ONNX Runtime has by design accepted that multithread threads invoke the Run() method on the same inference Session object. Therefore, what has left us to do is to

  1. create a Session as a global object in our CMSSW module and share it among all threads;
  2. in each thread, we process the input data and then call the Run() method from that global Session.

That's the main logic for implementing ONNX inference in CMSSW. For details of high-level designs of ONNX Runtime, please see documentation here.

With this concept, let's build the module.

"},{"location":"inference/onnx.html#1-includes","title":"1. includes","text":"
#include \"FWCore/Framework/interface/stream/EDAnalyzer.h\"\n#include \"PhysicsTools/ONNXRuntime/interface/ONNXRuntime.h\"\n// further framework includes\n...\n

We include stream/EDAnalyzer.h to build the stream CMSSW module.

"},{"location":"inference/onnx.html#2-global-cache-object","title":"2. Global cache object","text":"

In CMSSW there exists a class ONNXRuntime which can be used directly as the global cache object. Upon initialization from a given model, it holds the ONNX Runtime Session object and provides the handle to invoke the Run() for model inference.

We put the ONNXRuntime class in the edm::GlobalCache template argument:

class MyPlugin : public edm::stream::EDAnalyzer<edm::GlobalCache<ONNXRuntime>> {\n...\n};\n
"},{"location":"inference/onnx.html#3-initiate-objects","title":"3. Initiate objects","text":"

In the stream EDAnlyzer module, it provides a hook initializeGlobalCache() to initiate the global object. We simply do

std::unique_ptr<ONNXRuntime> MyPlugin::initializeGlobalCache(const edm::ParameterSet &iConfig) {\nreturn std::make_unique<ONNXRuntime>(iConfig.getParameter<edm::FileInPath>(\"model_path\").fullPath());\n}\n

to initiate the ONNXRuntime object upon a given model path.

"},{"location":"inference/onnx.html#4-inference","title":"4. Inference","text":"

We know the event processing step is implemented in the void EDAnalyzer::analyze method. When an event is assigned to a valid thread, the content will be processed in that thread. This can go in parallel with other threads processing other events.

We need to first construct the input data dedicated to the event. Here we create a dummy input: a sequence of consecutive integers of length 10. The input is set by replacing the values of our pre-booked vector, data_. This member variable has vector<vector<float>> format and is initialised as { {0, 0, ..., 0} } (contains only one element, which is a vector of 10 zeros). In processing of each event, the input data_ is modified:

std::vector<float> &group_data = data_[0];\nfor (size_t i = 0; i < 10; i++){\ngroup_data[i] = float(iEvent.id().event() % 100 + i);\n}\n

Then, we send data_ to the inference engine and get the model output:

std::vector<float> outputs = globalCache()->run(input_names_, data_, input_shapes_)[0];\n

We clarify a few details here.

First, we use globalCache() which is a class method in our stream CMSSW module to access the global object shared across all threads. In our case it is the ONNXRuntime instance.

The run() method is a wrapper to call Run() on the ONNX Session. Definations on the method arguments are (code from link):

// Run inference and get outputs\n// input_names: list of the names of the input nodes.\n// input_values: list of input arrays for each input node. The order of `input_values` must match `input_names`.\n// input_shapes: list of `int64_t` arrays specifying the shape of each input node. Can leave empty if the model does not have dynamic axes.\n// output_names: names of the output nodes to get outputs from. Empty list means all output nodes.\n// batch_size: number of samples in the batch. Each array in `input_values` must have a shape layout of (batch_size, ...).\n// Returns: a std::vector<std::vector<float>>, with the order matched to `output_names`.\n// When `output_names` is empty, will return all outputs ordered as in `getOutputNames()`.\nFloatArrays run(const std::vector<std::string>& input_names,\nFloatArrays& input_values,\nconst std::vector<std::vector<int64_t>>& input_shapes = {},\nconst std::vector<std::string>& output_names = {},\nint64_t batch_size = 1) const;\n
where we have
typedef std::vector<std::vector<float>> FloatArrays;\n

In our case, input_names is set to {\"my_input\"} which corresponds to the names upon model creation. input_values is a length-1 vector, and input_values[0] is a vector of float of length 10, which are inputs to the 10 nodes. input_shapes can be set empty here and will be necessary for advanced usage, when our input has dynamic lengths (e.g., in boosed jet tagging, we use different numbers of particle-flow candidates and secondary vertices as input).

For the usual model design, we have only one vector of output. In such a case, the output is simply a length-1 vector, and we use [0] to get the vector of two float numbers\u2014the output of the model.

"},{"location":"inference/onnx.html#full-example","title":"Full example","text":"

Let's construct the full example.

Click to expand

The example assumes the following directory structure:

MySubsystem/MyModule/\n\u2502\n\u251c\u2500\u2500 plugins/\n\u2502   \u251c\u2500\u2500 MyPlugin.cpp\n\u2502   \u2514\u2500\u2500 BuildFile.xml\n\u2502\n\u251c\u2500\u2500 test/\n\u2502   \u2514\u2500\u2500 my_plugin_cfg.py\n\u2502\n\u2514\u2500\u2500 data/\n    \u2514\u2500\u2500 model.onnx\n
plugins/MyPlugin.cppplugins/BuildFile.xmltest/my_plugin_cfg.pydata/model.onnx
/*\n * Example plugin to demonstrate the direct multi-threaded inference with ONNX Runtime.\n */\n\n#include <memory>\n#include <iostream>\n\n#include \"FWCore/Framework/interface/Event.h\"\n#include \"FWCore/Framework/interface/Frameworkfwd.h\"\n#include \"FWCore/Framework/interface/MakerMacros.h\"\n#include \"FWCore/Framework/interface/stream/EDAnalyzer.h\"\n#include \"FWCore/ParameterSet/interface/ParameterSet.h\"\n\n#include \"PhysicsTools/ONNXRuntime/interface/ONNXRuntime.h\"\n\nusing namespace cms::Ort;\n\nclass MyPlugin : public edm::stream::EDAnalyzer<edm::GlobalCache<ONNXRuntime>> {\npublic:\nexplicit MyPlugin(const edm::ParameterSet &, const ONNXRuntime *);\nstatic void fillDescriptions(edm::ConfigurationDescriptions&);\n\nstatic std::unique_ptr<ONNXRuntime> initializeGlobalCache(const edm::ParameterSet &);\nstatic void globalEndJob(const ONNXRuntime *);\n\nprivate:\nvoid beginJob();\nvoid analyze(const edm::Event&, const edm::EventSetup&);\nvoid endJob();\n\nstd::vector<std::string> input_names_;\nstd::vector<std::vector<int64_t>> input_shapes_;\nFloatArrays data_; // each stream hosts its own data\n};\n\n\nvoid MyPlugin::fillDescriptions(edm::ConfigurationDescriptions& descriptions) {\n// defining this function will lead to a *_cfi file being generated when compiling\nedm::ParameterSetDescription desc;\ndesc.add<edm::FileInPath>(\"model_path\", edm::FileInPath(\"MySubsystem/MyModule/data/model.onnx\"));\ndesc.add<std::vector<std::string>>(\"input_names\", std::vector<std::string>({\"my_input\"}));\ndescriptions.addWithDefaultLabel(desc);\n}\n\n\nMyPlugin::MyPlugin(const edm::ParameterSet &iConfig, const ONNXRuntime *cache)\n: input_names_(iConfig.getParameter<std::vector<std::string>>(\"input_names\")),\ninput_shapes_() {\n// initialize the input data arrays\n// note there is only one element in the FloatArrays type (i.e. vector<vector<float>>) variable\ndata_.emplace_back(10, 0);\n}\n\n\nstd::unique_ptr<ONNXRuntime> MyPlugin::initializeGlobalCache(const edm::ParameterSet &iConfig) {\nreturn std::make_unique<ONNXRuntime>(iConfig.getParameter<edm::FileInPath>(\"model_path\").fullPath());\n}\n\nvoid MyPlugin::globalEndJob(const ONNXRuntime *cache) {}\n\nvoid MyPlugin::analyze(const edm::Event &iEvent, const edm::EventSetup &iSetup) {\n// prepare dummy inputs for every event\nstd::vector<float> &group_data = data_[0];\nfor (size_t i = 0; i < 10; i++){\ngroup_data[i] = float(iEvent.id().event() % 100 + i);\n}\n\n// run prediction and get outputs\nstd::vector<float> outputs = globalCache()->run(input_names_, data_, input_shapes_)[0];\n\n// print the input and output data\nstd::cout << \"input data -> \";\nfor (auto &i: group_data) { std::cout << i << \" \"; }\nstd::cout << std::endl << \"output data -> \";\nfor (auto &i: outputs) { std::cout << i << \" \"; }\nstd::cout << std::endl;\n\n}\n\nDEFINE_FWK_MODULE(MyPlugin);\n
<use name=\"FWCore/Framework\" />\n<use name=\"FWCore/PluginManager\" />\n<use name=\"FWCore/ParameterSet\" />\n<use name=\"PhysicsTools/ONNXRuntime\" />\n\n<flags EDM_PLUGIN=\"1\" />\n
# coding: utf-8\n\nimport os\n\nimport FWCore.ParameterSet.Config as cms\nfrom FWCore.ParameterSet.VarParsing import VarParsing\n\n\n# setup minimal options\noptions = VarParsing(\"python\")\noptions.setDefault(\"inputFiles\", \"/store/mc/RunIISummer20UL18MiniAODv2/DYJetsToLL_M-50_TuneCP5_13TeV-amcatnloFXFX-pythia8/MINIAODSIM/106X_upgrade2018_realistic_v16_L1v1-v2/230000/4C8619B2-D0C0-4647-B946-B33754F4ED16.root\")  # noqa\noptions.parseArguments()\n\n# define the process to run\nprocess = cms.Process(\"TEST\")\n\n# minimal configuration\nprocess.load(\"FWCore.MessageService.MessageLogger_cfi\")\nprocess.MessageLogger.cerr.FwkReport.reportEvery = 1\nprocess.maxEvents = cms.untracked.PSet(input=cms.untracked.int32(10))\nprocess.source = cms.Source(\"PoolSource\",\n    fileNames=cms.untracked.vstring(options.inputFiles))\n\n# process options\nprocess.options = cms.untracked.PSet(\n    allowUnscheduled=cms.untracked.bool(True),\n    wantSummary=cms.untracked.bool(True),\n)\n\n# setup options for multithreaded\nprocess.options.numberOfThreads=cms.untracked.uint32(1)\nprocess.options.numberOfStreams=cms.untracked.uint32(0)\nprocess.options.numberOfConcurrentLuminosityBlocks=cms.untracked.uint32(1)\n\n\n# setup MyPlugin by loading the auto-generated cfi (see MyPlugin.fillDescriptions)\nprocess.load(\"MySubsystem.MyModule.myPlugin_cfi\")\n# specify the path of the ONNX model\nprocess.myPlugin.model_path = \"MySubsystem/MyModule/data/model.onnx\"\n# input names as defined in the model\n# the order of name strings should also corresponds to the order of input data array feed to the model\nprocess.myPlugin.input_names = [\"my_input\"]\n\n# define what to run in the path\nprocess.p = cms.Path(process.myPlugin)\n

The model is produced by code in the section \"Converting model to ONNX\" and can be downloaded here.

"},{"location":"inference/onnx.html#test-our-module","title":"Test our module","text":"

Under MySubsystem/MyModule/test, run cmsRun my_plugin_cfg.py to launch our module. You may see the following from the output, which include the input and output vectors in the inference process.

Click to see the output
...\n19-Jul-2022 10:50:41 CEST  Successfully opened file root://xrootd-cms.infn.it//store/mc/RunIISummer20UL18MiniAODv2/DYJetsToLL_M-50_TuneCP5_13TeV-amcatnloFXFX-pythia8/MINIAODSIM/106X_upgrade2018_realistic_v16_L1v1-v2/230000/4C8619B2-D0C0-4647-B946-B33754F4ED16.root\nBegin processing the 1st record. Run 1, Event 27074045, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.494 CEST\ninput data -> 45 46 47 48 49 50 51 52 53 54\noutput data -> 0.995657 0.00434343\nBegin processing the 2nd record. Run 1, Event 27074048, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.495 CEST\ninput data -> 48 49 50 51 52 53 54 55 56 57\noutput data -> 0.996884 0.00311563\nBegin processing the 3rd record. Run 1, Event 27074059, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.495 CEST\ninput data -> 59 60 61 62 63 64 65 66 67 68\noutput data -> 0.999081 0.000919373\nBegin processing the 4th record. Run 1, Event 27074061, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.495 CEST\ninput data -> 61 62 63 64 65 66 67 68 69 70\noutput data -> 0.999264 0.000736247\nBegin processing the 5th record. Run 1, Event 27074046, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.496 CEST\ninput data -> 46 47 48 49 50 51 52 53 54 55\noutput data -> 0.996112 0.00388828\nBegin processing the 6th record. Run 1, Event 27074047, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.496 CEST\ninput data -> 47 48 49 50 51 52 53 54 55 56\noutput data -> 0.996519 0.00348065\nBegin processing the 7th record. Run 1, Event 27074064, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.496 CEST\ninput data -> 64 65 66 67 68 69 70 71 72 73\noutput data -> 0.999472 0.000527586\nBegin processing the 8th record. Run 1, Event 27074074, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.496 CEST\ninput data -> 74 75 76 77 78 79 80 81 82 83\noutput data -> 0.999826 0.000173664\nBegin processing the 9th record. Run 1, Event 27074050, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.496 CEST\ninput data -> 50 51 52 53 54 55 56 57 58 59\noutput data -> 0.997504 0.00249614\nBegin processing the 10th record. Run 1, Event 27074060, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.496 CEST\ninput data -> 60 61 62 63 64 65 66 67 68 69\noutput data -> 0.999177 0.000822734\n19-Jul-2022 10:50:43 CEST  Closed file root://xrootd-cms.infn.it//store/mc/RunIISummer20UL18MiniAODv2/DYJetsToLL_M-50_TuneCP5_13TeV-amcatnloFXFX-pythia8/MINIAODSIM/106X_upgrade2018_realistic_v16_L1v1-v2/230000/4C8619B2-D0C0-4647-B946-B33754F4ED16.root\n

Also we could try launching the script with more threads. Change the corresponding line in my_plugin_cfg.py as follows to activate the multi-threaded mode with 4 threads.

process.options.numberOfThreads=cms.untracked.uint32(4)\n

Launch the script again, and one could see the same results, but with the inference processed concurrently on 4 threads.

"},{"location":"inference/onnx.html#inference-in-cmssw-python","title":"Inference in CMSSW (Python)","text":"

Doing ONNX Runtime inference with python is possible as well. For those releases that have the ONNX Runtime C++ package installed, the onnxruntime python package is also installed in python3 (except for CMSSW_10_6_X). We still use CMSSW_11_2_5_patch2 to run our examples. We could quickly check if onnxruntime is available by:

python3 -c \"import onnxruntime; print('onnxruntime available')\"\n

The python code is simple to construct: following the quick examples \"Get started with ORT for Python\", we create the file MySubsystem/MyModule/test/my_standalone_test.py as follows:

import onnxruntime as ort\nimport numpy as np\n\n# create input data in the float format (32 bit)\ndata = np.arange(45, 55).astype(np.float32)\n\n# create inference session using ort.InferenceSession from a given model\nort_sess = ort.InferenceSession('../data/model.onnx')\n\n# run inference\noutputs = ort_sess.run(None, {'my_input': np.array([data])})[0]\n\n# print input and output\nprint('input ->', data)\nprint('output ->', outputs)\n

Under the directory MySubsystem/MyModule/test, run the example with python3 my_standalone_test.py. Then we see the output:

input -> [45. 46. 47. 48. 49. 50. 51. 52. 53. 54.]\noutput -> [[0.9956566  0.00434343]]\n

Using ONNX Runtime on NanoAOD-tools follows the same logic. Here we create the ONNX Session in the beginning stage and run inference in the event loop. Note that NanoAOD-tools runs the event loop in the single-thread mode.

Please find details in the following block.

Click to see the NanoAOD-tools example

We run the NanoAOD-tools example following the above CMSSW_11_2_5_patch2 environment. According to the setup instruction in NanoAOD-tools, do

cd $CMSSW_BASE/src\ngit clone https://github.com/cms-nanoAOD/nanoAOD-tools.git PhysicsTools/NanoAODTools\ncd PhysicsTools/NanoAODTools\ncmsenv\nscram b\n

Now we add our custom module to run ONNX Runtime inference. Create a file PhysicsTools/NanoAODTools/python/postprocessing/examples/exampleOrtModule.py with the content:

from PhysicsTools.NanoAODTools.postprocessing.framework.datamodel import Collection\nfrom PhysicsTools.NanoAODTools.postprocessing.framework.eventloop import Module\nimport ROOT\nROOT.PyConfig.IgnoreCommandLineOptions = True\n\nimport onnxruntime as ort\nimport numpy as np\nimport os \n\nclass exampleOrtProducer(Module):\n    def __init__(self):\n        pass\n\n    def beginJob(self):\n        model_path = os.path.join(os.getenv(\"CMSSW_BASE\"), 'src', 'MySubsystem/MyModule/data/model.onnx')\nself.ort_sess = ort.InferenceSession(model_path)\ndef endJob(self):\n        pass\n\n    def beginFile(self, inputFile, outputFile, inputTree, wrappedOutputTree):\n        self.out = wrappedOutputTree\n        self.out.branch(\"OrtScore\", \"F\")\n\n    def endFile(self, inputFile, outputFile, inputTree, wrappedOutputTree):\n        pass\n\n    def analyze(self, event):\n\"\"\"process event, return True (go to next module) or False (fail, go to next event)\"\"\"\n\n        # create input data\n        data = np.arange(event.event % 100, event.event % 100 + 10).astype(np.float32)\n        # run inference\noutputs = self.ort_sess.run(None, {'my_input': np.array([data])})[0]\n# print input and output\n        print('input ->', data)\n        print('output ->', outputs)\n\n        self.out.fillBranch(\"OrtScore\", outputs[0][0])\n        return True\n\n\n# define modules using the syntax 'name = lambda : constructor' to avoid having them loaded when not needed\n\nexampleOrtModuleConstr = lambda: exampleOrtProducer()\n

Please notice the highlighted lines for the creation of ONNX Runtime Session and launching the inference.

Finally, following the test command from NanoAOD-tools, we run our custom module in python3 by

python3 scripts/nano_postproc.py outDir /eos/cms/store/user/andrey/f.root -I PhysicsTools.NanoAODTools.postprocessing.examples.exampleOrtModule exampleOrtModuleConstr -N 10\n

We should see the output as follows

processing.examples.exampleOrtModule exampleOrtModuleConstr -N 10\nLoading exampleOrtModuleConstr from PhysicsTools.NanoAODTools.postprocessing.examples.exampleOrtModule\nWill write selected trees to outDir\nPre-select 10 entries out of 10 (100.00%)\ninput -> [11. 12. 13. 14. 15. 16. 17. 18. 19. 20.]\noutput -> [[0.83919346 0.16080655]]\ninput -> [ 7.  8.  9. 10. 11. 12. 13. 14. 15. 16.]\noutput -> [[0.76994413 0.2300559 ]]\ninput -> [ 4.  5.  6.  7.  8.  9. 10. 11. 12. 13.]\noutput -> [[0.7116992 0.2883008]]\ninput -> [ 2.  3.  4.  5.  6.  7.  8.  9. 10. 11.]\noutput -> [[0.66414535 0.33585465]]\ninput -> [ 9. 10. 11. 12. 13. 14. 15. 16. 17. 18.]\noutput -> [[0.80617136 0.19382869]]\ninput -> [ 6.  7.  8.  9. 10. 11. 12. 13. 14. 15.]\noutput -> [[0.75187963 0.2481204 ]]\ninput -> [16. 17. 18. 19. 20. 21. 22. 23. 24. 25.]\noutput -> [[0.9014619  0.09853811]]\ninput -> [18. 19. 20. 21. 22. 23. 24. 25. 26. 27.]\noutput -> [[0.9202239  0.07977609]]\ninput -> [ 5.  6.  7.  8.  9. 10. 11. 12. 13. 14.]\noutput -> [[0.7330253  0.26697478]]\ninput -> [10. 11. 12. 13. 14. 15. 16. 17. 18. 19.]\noutput -> [[0.82333535 0.17666471]]\nProcessed 10 preselected entries from /eos/cms/store/user/andrey/f.root (10 entries). Finally selected 10 entries\nDone outDir/f_Skim.root\nTotal time 1.1 sec. to process 10 events. Rate = 9.3 Hz.\n

"},{"location":"inference/onnx.html#links-and-further-reading","title":"Links and further reading","text":"
  • ONNX/ONNX Runtime
    • Tutorials on converting models to ONNX format
    • ONNX Runtime C++ example
    • ONNX Runtime C++ API
    • ONNX Runtime python example
    • ONNX Runtime python API
    • ONNX Runtime in CMSSW (talk)

Developers: Huilin Qu

Authors: Congqiao Li

"},{"location":"inference/particlenet.html","title":"ParticleNet","text":"

ParticleNet [arXiv:1902.08570] is an advanced neural network architecture that has many applications in CMS, including heavy flavour jet tagging, jet mass regression, etc. The network is fed by various low-level point-like objects as input, e.g., the particle-flow candidates, to predict a feature of a jet.

The full architecture of the ParticleNet model. We'll walk through the details in the following sections.

On this page, we introduce several user-specific aspects of the ParticleNet model. We cover the following items in three sections:

  1. An introduction to ParticleNet, including

    • a general description of ParticleNet
    • the advantages brought from the architecture by concept
    • a sketch of ParticleNet applications in CMS and other relevant works
  2. An introduction to Weaver and model implementations, introduced in a step-by-step manner:

    • build three network models and understand them from the technical side; use the out-of-the-box commands to run these examples on a benchmark task. The three networks are (1) a simple feed-forward NN, (2) a DeepAK8 model (based on 1D CNN), and eventually (3) the ParticleNet model (based on DGCNN).
    • try to reproduce the original performance and make the ROC plots.

    This section is friendly to the ML newcomers. The goal is to help readers understand the underlying structure of the \"ParticleNet\".

  3. Tuning the ParticleNet model, including

    • tips for readers who are using/modifying the ParticleNet model to achieve a better performance

    This section can be helpful in practice. It provides tips on model training, tunning, validation, etc. It targets the situations when readers apply their own ParticleNet (or ParticleNet-like) model to the custom task.

Corresponding persons:

  • Huilin Qu, Loukas Gouskos (original developers of ParticleNet)
  • Congqiao Li (author of the page)
"},{"location":"inference/particlenet.html#introduction-to-particlenet","title":"Introduction to ParticleNet","text":""},{"location":"inference/particlenet.html#1-general-description","title":"1. General description","text":"

ParticleNet is a graph neural net (GNN) model. The key ingredient of ParticleNet is the graph convolutional operation, i.e., the edge convolution (EdgeConv) and the dynamic graph CNN (DGCNN) method [arXiv:1801.07829] applied on the \"point cloud\" data structure.

We will disassemble the ParticleNet model and provide a detailed exploration in the next section, but here we briefly explain the key features of the model.

Intuitively, ParticleNet treats all candidates inside an object as a \"point cloud\", which is a permutational-invariant set of points (e.g. a set of PF candidates), each carrying a feature vector (\u03b7, \u03c6, pT, charge, etc.). The DGCNN uses the EdgeConv operation to exploit their spatial correlations (two-dimensional on the \u03b7-\u03c6 plain) by finding the k-nearest neighbours of each point and generate a new latent graph layer where points are scattered on a high-dimensional latent space. This is a graph-type analogue of the classical 2D convolution operation, which acts on a regular 2D grid (e.g., a picture) using a 3\u00d73 local patch to explore the relations of a single-pixel with its 8 nearest pixels, then generates a new 2D grid.

The cartoon illustrates the convolutional operation acted on the regular grid and on the point cloud (plot from ML4Jets 2018 talk).

As a consequence, the EdgeConv operation transforms the graph to a new graph, which has a changed spatial relationship among points. It then acts on the second graph to produce the third graph, showing the stackability of the convolution operation. This illustrates the \"dynamic\" property as the graph topology changes after each EdgeConv layer.

"},{"location":"inference/particlenet.html#2-advantage","title":"2. Advantage","text":"

By concept, the advantage of the network may come from exploiting the permutational-invariant symmetry of the points, which is intrinsic to our physics objects. This symmetry is held naturally in a point cloud representation.

In a recent study on jet physics or event-based analysis using ML techniques, there are increasing interest to explore the point cloud data structure. We explain here conceptually why a \"point cloud\" representation outperforms the classical ones, including the variable-length 2D vector structure passing to a 1D CNN or any type of RNN, and imaged-based representation passing through a 2D CNN. By using the 1D CNN, the points (PF candidates) are more often ordered by pT to fix on the 1D grid. Only correlations with neighbouring points with similar pT are learned by the network with a convolution operation. The Long Short-Term Memory (LSTM) type recurrent neural network (RNN) provides the flexibility to feed in a variant-length sequence and has a \"memory\" mechanism to cooperate the information it learns from an early node to the latest node. The concern is that such ordering of the sequence is somewhat artificial, and not an underlying property that an NN must learn to accomplish the classification task. As a comparison, in the task of the natural language processing where LSTM has a huge advantage, the order of words are important characteristic of a language itself (reflects the \"grammar\" in some circumstances) and is a feature the NN must learn to master the language. The imaged-based data explored by a 2D CNN stems from the image recognition task. A jet image with proper standardization is usually performed before feeding into the network. In this sense, it lacks local features which the 2D local patch is better at capturing, e.g. the ear of the cat that a local patch can capture by scanning over the entire image. The jet image is appearing to hold the features globally (e.g. two-prong structure for W-tagging). The sparsity of data is another concern in that it introduces redundant information to present a jet on the regular grid, making the network hard to capture the key properties.

"},{"location":"inference/particlenet.html#3-applications-and-other-related-work","title":"3. Applications and other related work","text":"

Here we briefly summarize the applications and ongoing works on ParticleNet. Public CMS results include

  • large-R jet with R=0.8 tagging (for W/Z/H/t) using ParticleNet [CMS-DP-2020/002]
  • regression on the large-R jet mass based on the ParticleNet model [CMS-DP-2021/017]

ParticleNet architecture is also applied on small radius R=0.4 jets for the b/c-tagging and quark/gluon classification (see this talk (CMS internal)). A recent ongoing work applies the ParticleNet architecture in heavy flavour tagging at HLT (see this talk (CMS internal)). The ParticleNet model is recently updated to ParticleNeXt and see further improvement (see the ML4Jets 2021 talk).

Recent works in the joint field of HEP and ML also shed light on exploiting the point cloud data structure and GNN-based architectures. We see very active progress in recent years. Here list some useful materials for the reader's reference.

  • Some pheno-based work are summarized in the HEP \u00d7 ML living review, especially in the \"graph\" and \"sets\" categories.
  • An overview of GNN applications to CMS, see CMS ML forum (CMS internal). Also see more recent GNN application progress in ML forums: Oct 20, Nov 3.
  • At the time of writing, various novel GNN-based models are explored and introduced in the recent ML4Jets2021 meeting.
"},{"location":"inference/particlenet.html#introduction-to-weaver-and-model-implementations","title":"Introduction to Weaver and model implementations","text":"

Weaver is a machine learning R&D framework for high energy physics (HEP) applications. It trains the neural net with PyTorch and is capable of exporting the model to the ONNX format for fast inference. A detailed guide is presented on Weaver README page.

Now we walk through three solid examples to get you familiar with Weaver. We use the benchmark of the top tagging task [arXiv:1707.08966] in the following example. Some useful information can be found in the \"top tagging\" section in the IML public datasets webpage (the gDoc).

Our goal is to do some warm-up with Weaver, and more importantly, to explore from a technical side the neural net architectures: a simple multi-layer perceptron (MLP) model, a more complicated \"DeepAK8 tagger\" model based on 1D CNN with ResNet, and the \"ParticleNet model,\" which is based on DGCNN. We will dig deeper into their implementations in Weaver and try to illustrate as many details as possible. Finally, we compare their performance and see if we can reproduce the benchmark record with the model. Please clone the repo weaver-benchmark and we'll get started. The Weaver repo will be cloned as a submodule.

git clone --recursive https://github.com/colizz/weaver-benchmark.git\n\n# Create a soft link inside weaver so that it can find data/model cards\nln -s ../top_tagging weaver-benchmark/weaver/top_tagging\n

"},{"location":"inference/particlenet.html#1-build-models-in-weaver","title":"1. Build models in Weaver","text":"

When implementing a new training in Weaver, two key elements are crucial: the model and the data configuration file. The model defines the network architecture we are using, and the data configuration includes which variables to use for training, which pre-selection to apply, how to assign truth labels, etc.

Technically, The model configuration file includes a get_model function that returns a torch.nn.Module type model and a dictionary of model info used to export an ONNX-format model. The data configuration is a YAML file describing how to process the input data. Please see the Weaver README for details.

Before moving on, we need a preprocessing of the benchmark datasets. The original sample is an H5 file including branches like energy E_i and 3-momenta PX_i, PY_i, PZ_i for each jet constituent i (i=0, ..., 199) inside a jet. All branches are in the 1D flat structure. We reconstruct the data in a way that the jet features are 2D vectors (e.g., in the vector<float> format): Part_E, Part_PX, Part_PY, Part_PZ, with variable-length that corresponds to the number of constituents. Note that this is a commonly used data structure, similar to the NanoAOD format in CMS.

The datasets can be found at CERN EOS space /eos/user/c/coli/public/weaver-benchmark/top_tagging/samples. The input files used in this page are in fact the ROOT files produced by the preprocessing step, stored under the prep/ subdirectory. It includes three sets of data for training, validation, and test.

Note

To preprocess the input files from the original datasets manually, direct to the weaver-benchmark base directory and run

python utils/convert_top_datasets.py -i <your-sample-dir>\n
This will convert the .h5 file to ROOT ntuples and create some new variables for each jet, including the relative \u03b7 and \u03c6 value w.r.t. main axis of the jet of each jet constituent. The converted files are stored in prep/ subfolder of the original directory.

Then, we show three NN model configurations below and provide detailed explanations of the code. We make meticulous efforts on the illustration of the model architecture, especially in the ParticleNet case.

A simple MLPDeepAK8 (1D CNN)ParticleNet (DGCNN)

The full architecture of the proof-of-concept multi-layer perceptron model.

A simple multi-layer perceptron model is first provided here as proof of the concept. All layers are based on the linear transformation of the 1D vectors. The model configuration card is shown in top_tagging/networks/mlp_pf.py. First, we implement an MLP network in the nn.Module class.

MLP implementation

Also, see top_tagging/networks/mlp_pf.py. We elaborate here on several aspects.

  • A sequence of linear layers and ReLU activation functions is defined in nn.Sequential(nn.Linear(channels[i], channels[i + 1]), nn.ReLU()). By combining multiple of them, we construct a simple multi-layer perceptron.

  • The input data x takes the 3D format, in the dimension (N, C, P), which is decided by our data structure and the data configuration card. Here, N is the mini-batch size, C is the feature size, and P is the size of constituents per jet. To feed into our MLP, we flatten the last two dimensions by x = x.flatten(start_dim=1) to form the vector of dimension (N, L).

class MultiLayerPerceptron(nn.Module):\nr\"\"\"Parameters\n    ----------\n    input_dims : int\n        Input feature dimensions.\n    num_classes : int\n        Number of output classes.\n    layer_params : list\n        List of the feature size for each layer.\n    \"\"\"\n\n    def __init__(self, input_dims, num_classes,\n                layer_params=(1024, 256, 256),\n                **kwargs):\n\n        super(MultiLayerPerceptron, self).__init__(**kwargs)\n        channels = [input_dims] + list(layer_params) + [num_classes]\n        layers = []\n        for i in range(len(channels) - 1):\n            layers.append(nn.Sequential(nn.Linear(channels[i], channels[i + 1]),\n                                        nn.ReLU()))\n        self.mlp = nn.Sequential(*layers)\n\n    def forward(self, x):\n        # x: the feature vector initally read from the data structure, in dimension (N, C, P)\n        x = x.flatten(start_dim=1) # (N, L), where L = C * P\n        return self.mlp(x)\n

Then, we write the get_model and get_loss functions which will be sent into Weaver's training code.

get_model and get_loss function

Also see top_tagging/networks/mlp_pf.py. We elaborate here on several aspects.

  • Inside get_model, the model is essentially the MLP class we define, and the model_info takes the default definition, including the input/output shape, the dimensions of the dynamic axes for the input/output data shape that will guide the ONNX model exportation.
  • The get_loss function is not changed as in the classification task we always use the cross-entropy loss function.
def get_model(data_config, **kwargs):\n    layer_params = (1024, 256, 256)\n    _, pf_length, pf_features_dims = data_config.input_shapes['pf_features']\n    input_dims = pf_length * pf_features_dims\n    num_classes = len(data_config.label_value)\n    model = MultiLayerPerceptron(input_dims, num_classes, layer_params=layer_params)\n\n    model_info = {\n        'input_names':list(data_config.input_names),\n        'input_shapes':{k:((1,) + s[1:]) for k, s in data_config.input_shapes.items()},\n        'output_names':['softmax'],\n        'dynamic_axes':{**{k:{0:'N', 2:'n_' + k.split('_')[0]} for k in data_config.input_names}, **{'softmax':{0:'N'}}},\n        }\n\n    print(model, model_info)\n    return model, model_info\n\n\ndef get_loss(data_config, **kwargs):\n    return torch.nn.CrossEntropyLoss()\n

The output below shows the full structure of the MLP network printed by PyTorch. You will see it in the Weaver output during the training.

The full-scale structure of the MLP network
MultiLayerPerceptron(\n  |0.739 M, 100.000% Params, 0.001 GMac, 100.000% MACs|\n  (mlp): Sequential(\n    |0.739 M, 100.000% Params, 0.001 GMac, 100.000% MACs|\n    (0): Sequential(\n      |0.411 M, 55.540% Params, 0.0 GMac, 55.563% MACs|\n      (0): Linear(in_features=400, out_features=1024, bias=True, |0.411 M, 55.540% Params, 0.0 GMac, 55.425% MACs|)\n      (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.138% MACs|)\n    )\n    (1): Sequential(\n      |0.262 M, 35.492% Params, 0.0 GMac, 35.452% MACs|\n      (0): Linear(in_features=1024, out_features=256, bias=True, |0.262 M, 35.492% Params, 0.0 GMac, 35.418% MACs|)\n      (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.035% MACs|)\n    )\n    (2): Sequential(\n      |0.066 M, 8.899% Params, 0.0 GMac, 8.915% MACs|\n      (0): Linear(in_features=256, out_features=256, bias=True, |0.066 M, 8.899% Params, 0.0 GMac, 8.880% MACs|)\n      (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.035% MACs|)\n    )\n    (3): Sequential(\n      |0.001 M, 0.070% Params, 0.0 GMac, 0.070% MACs|\n      (0): Linear(in_features=256, out_features=2, bias=True, |0.001 M, 0.070% Params, 0.0 GMac, 0.069% MACs|)\n      (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.000% MACs|)\n    )\n  )\n)\n

The data card is shown in top_tagging/data/pf_features.yaml. It defines one input group, pf_features, which takes four variables Etarel, Phirel, E_log, P_log. This is based on our data structure, where these variables are 2D vectors with variable lengths. The length is chosen as 100 in a way that the last dimension (the jet constituent dimension) is always truncated or padded to have length 100.

MLP data config top_tagging/data/pf_features.yaml

Also see top_tagging/data/pf_features.yaml. See a tour guide to the data configuration card in Weaver README.

selection:\n### use `&`, `|`, `~` for logical operations on numpy arrays\n### can use functions from `math`, `np` (numpy), and `awkward` in the expression\n\nnew_variables:\n### [format] name: formula\n### can use functions from `math`, `np` (numpy), and `awkward` in the expression\nis_bkg: np.logical_not(is_signal_new)\n\npreprocess:\n### method: [manual, auto] - whether to use manually specified parameters for variable standardization\nmethod: manual\n### data_fraction: fraction of events to use when calculating the mean/scale for the standardization\ndata_fraction:\n\ninputs:\npf_features:\nlength: 100\nvars:\n### [format 1]: var_name (no transformation)\n### [format 2]: [var_name,\n###              subtract_by(optional, default=None, no transf. if preprocess.method=manual, auto transf. if preprocess.method=auto),\n###              multiply_by(optional, default=1),\n###              clip_min(optional, default=-5),\n###              clip_max(optional, default=5),\n###              pad_value(optional, default=0)]\n- Part_Etarel\n- Part_Phirel\n- [Part_E_log, 2, 1]\n- [Part_P_log, 2, 1]\n\nlabels:\n### type can be `simple`, `custom`\n### [option 1] use `simple` for binary/multi-class classification, then `value` is a list of 0-1 labels\ntype: simple\nvalue: [\nis_signal_new, is_bkg\n]\n### [option 2] otherwise use `custom` to define the label, then `value` is a map\n# type: custom\n# value:\n# target_mass: np.where(fj_isQCD, fj_genjet_sdmass, fj_gen_mass)\n\nobservers:\n- origIdx\n- idx\n- Part_E_tot\n- Part_PX_tot\n- Part_PY_tot\n- Part_PZ_tot\n- Part_P_tot\n- Part_Eta_tot\n- Part_Phi_tot\n\n# weights:\n### [option 1] use precomputed weights stored in the input files\n# use_precomputed_weights: true\n# weight_branches: [weight, class_weight]\n### [option 2] compute weights on-the-fly using reweighting histograms\n

In the following two models (i.e., the DeepAK8 and the ParticleNet model) you will see that the data card is very similar. The change will only be the way we present the input group(s).

The full architecture of the DeepAK8 model, which is based on 1D CNN with ResNet architecture.

Note

The DeepAK8 tagger is a widely used highly-boosted jet tagger in the CMS community. The design of the model can be found in the CMS paper [arXiv:2004.08262]. The original model is trained on MXNet and its configuration can be found here.

We now migrate the model architecture to Weaver and train it on PyTorch. Also, we narrow the multi-class output score to the binary output to adapt our binary classification task (top vs. QCD jet).

The model card is given in top_tagging/networks/deepak8_pf.py. The DeepAK8 model is inspired by the ResNet architecture. The key ingredient is the ResNet unit constructed by multiple CNN layers with a shortcut connection. First, we define the ResNet unit in the model card.

ResNet unit implementation

See top_tagging/networks/deepak8_pf.py. We elaborate here on several aspects.

  • A ResNet unit is made of two 1D CNNs with batch normalization and ReLU activation function.
  • The shortcut is introduced here by directly adding the input data to the processed data after passing the CNN layers. The shortcut connection help to ease the training for the \"deeper\" model [arXiv:1512.03385]. Note that a trivial linear transformation is applied (self.conv_sc) if the feature dimension of the input and output data does not match.
class ResNetUnit(nn.Module):\nr\"\"\"Parameters\n    ----------\n    in_channels : int\n        Number of channels in the input vectors.\n    out_channels : int\n        Number of channels in the output vectors.\n    strides: tuple\n        Strides of the two convolutional layers, in the form of (stride0, stride1)\n    \"\"\"\n\n    def __init__(self, in_channels, out_channels, strides=(1,1), **kwargs):\n\n        super(ResNetUnit, self).__init__(**kwargs)\n        self.conv1 = nn.Conv1d(in_channels, out_channels, kernel_size=3, stride=strides[0], padding=1)\n        self.bn1 = nn.BatchNorm1d(out_channels)\n        self.conv2 = nn.Conv1d(out_channels, out_channels, kernel_size=3, stride=strides[1], padding=1)\n        self.bn2 = nn.BatchNorm1d(out_channels)\n        self.relu = nn.ReLU()\n        self.dim_match = True\n        if not in_channels == out_channels or not strides == (1,1): # dimensions not match\n            self.dim_match = False\n            self.conv_sc = nn.Conv1d(in_channels, out_channels, kernel_size=1, stride=strides[0]*strides[1], bias=False)\n\n    def forward(self, x):\n        identity = x\n        x = self.conv1(x)\n        x = self.bn1(x)\n        x = self.relu(x)\n        x = self.conv2(x)\n        x = self.bn2(x)\n        x = self.relu(x)\n        # print('resnet unit', identity.shape, x.shape, self.dim_match)\n        if self.dim_match:\n            return identity + x\n        else:\n            return self.conv_sc(identity) + x\n

With the ResNet unit, we construct the DeepAK8 model. The model hyperparameters are chosen as follows.

conv_params = [(32,), (64, 64), (64, 64), (128, 128)]\nfc_params = [(512, 0.2)]\n

DeepAK8 model implementation

See top_tagging/networks/deepak8_pf.py. Note that the main architecture is a PyTorch re-implementation of the code here based on the MXNet.

class ResNet(nn.Module):\nr\"\"\"Parameters\n    ----------\n    features_dims : int\n        Input feature dimensions.\n    num_classes : int\n        Number of output classes.\n    conv_params : list\n        List of the convolution layer parameters.\n        The first element is a tuple of size 1, defining the transformed feature size for the initial feature convolution layer.\n        The following are tuples of feature size for multiple stages of the ResNet units. Each number defines an individual ResNet unit.\n    fc_params: list\n        List of fully connected layer parameters after all EdgeConv blocks, each element in the format of\n        (n_feat, drop_rate)\n    \"\"\"\n\n    def __init__(self, features_dims, num_classes,\n                conv_params=[(32,), (64, 64), (64, 64), (128, 128)],\n                fc_params=[(512, 0.2)],\n                **kwargs):\n\n        super(ResNet, self).__init__(**kwargs)\n        self.conv_params = conv_params\n        self.num_stages = len(conv_params) - 1\n        self.fts_conv = nn.Sequential(nn.Conv1d(in_channels=features_dims, out_channels=conv_params[0][0], kernel_size=3, stride=1, padding=1),\n                                    nn.BatchNorm1d(conv_params[0][0]),\n                                    nn.ReLU())\n\n        # define ResNet units for each stage. Each unit is composed of a sequence of ResNetUnit block\n        self.resnet_units = nn.ModuleDict()\n        for i in range(self.num_stages):\n            # stack units[i] layers in this stage\n            unit_layers = []\n            for j in range(len(conv_params[i + 1])):\n                in_channels, out_channels = (conv_params[i][-1], conv_params[i + 1][0]) if j == 0 \\\n                                            else (conv_params[i + 1][j - 1], conv_params[i + 1][j])\n                strides = (2, 1) if (j == 0 and i > 0) else (1, 1)\n                unit_layers.append(ResNetUnit(in_channels, out_channels, strides))\n\n            self.resnet_units.add_module('resnet_unit_%d' % i, nn.Sequential(*unit_layers))\n\n        # define fully connected layers\n        fcs = []\n        for idx, layer_param in enumerate(fc_params):\n            channels, drop_rate = layer_param\n            in_chn = conv_params[-1][-1] if idx == 0 else fc_params[idx - 1][0]\n            fcs.append(nn.Sequential(nn.Linear(in_chn, channels), nn.ReLU(), nn.Dropout(drop_rate)))\n        fcs.append(nn.Linear(fc_params[-1][0], num_classes))\n        self.fc = nn.Sequential(*fcs)\n\n    def forward(self, x):\n        # x: the feature vector, (N, C, P)\n        x = self.fts_conv(x)\n        for i in range(self.num_stages):\n            x = self.resnet_units['resnet_unit_%d' % i](x) # (N, C', P'), P'<P due to kernal_size>1 or stride>1\n\n        # global average pooling\n        x = x.sum(dim=-1) / x.shape[-1] # (N, C')\n        # fully connected\n        x = self.fc(x) # (N, out_chn)\n        return x\n\n\ndef get_model(data_config, **kwargs):\n    conv_params = [(32,), (64, 64), (64, 64), (128, 128)]\n    fc_params = [(512, 0.2)]\n\n    pf_features_dims = len(data_config.input_dicts['pf_features'])\n    num_classes = len(data_config.label_value)\n    model = ResNet(pf_features_dims, num_classes,\n                conv_params=conv_params,\n                fc_params=fc_params)\n\n    model_info = {\n        'input_names':list(data_config.input_names),\n        'input_shapes':{k:((1,) + s[1:]) for k, s in data_config.input_shapes.items()},\n        'output_names':['softmax'],\n        'dynamic_axes':{**{k:{0:'N', 2:'n_' + k.split('_')[0]} for k in data_config.input_names}, **{'softmax':{0:'N'}}},\n        }\n\n    print(model, model_info)\n    print(data_config.input_shapes)\n    return model, model_info\n\n\ndef get_loss(data_config, **kwargs):\n    return torch.nn.CrossEntropyLoss()\n

The output below shows the full structure of the DeepAK8 model based on 1D CNN with ResNet. It is printed by PyTorch and you will see it in the Weaver output during training.

The full-scale structure of the DeepAK8 architecture
ResNet(\n  |0.349 M, 100.000% Params, 0.012 GMac, 100.000% MACs|\n  (fts_conv): Sequential(\n    |0.0 M, 0.137% Params, 0.0 GMac, 0.427% MACs|\n    (0): Conv1d(4, 32, kernel_size=(3,), stride=(1,), padding=(1,), |0.0 M, 0.119% Params, 0.0 GMac, 0.347% MACs|)\n    (1): BatchNorm1d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.018% Params, 0.0 GMac, 0.053% MACs|)\n    (2): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.027% MACs|)\n  )\n  (resnet_units): ModuleDict(\n    |0.282 M, 80.652% Params, 0.012 GMac, 99.010% MACs|\n    (resnet_unit_0): Sequential(\n      |0.046 M, 13.124% Params, 0.005 GMac, 38.409% MACs|\n      (0): ResNetUnit(\n        |0.021 M, 5.976% Params, 0.002 GMac, 17.497% MACs|\n        (conv1): Conv1d(32, 64, kernel_size=(3,), stride=(1,), padding=(1,), |0.006 M, 1.778% Params, 0.001 GMac, 5.175% MACs|)\n        (bn1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.107% MACs|)\n        (conv2): Conv1d(64, 64, kernel_size=(3,), stride=(1,), padding=(1,), |0.012 M, 3.538% Params, 0.001 GMac, 10.296% MACs|)\n        (bn2): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.107% MACs|)\n        (relu): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.107% MACs|)\n        (conv_sc): Conv1d(32, 64, kernel_size=(1,), stride=(1,), bias=False, |0.002 M, 0.587% Params, 0.0 GMac, 1.707% MACs|)\n      )\n      (1): ResNetUnit(\n        |0.025 M, 7.149% Params, 0.003 GMac, 20.912% MACs|\n        (conv1): Conv1d(64, 64, kernel_size=(3,), stride=(1,), padding=(1,), |0.012 M, 3.538% Params, 0.001 GMac, 10.296% MACs|)\n        (bn1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.107% MACs|)\n        (conv2): Conv1d(64, 64, kernel_size=(3,), stride=(1,), padding=(1,), |0.012 M, 3.538% Params, 0.001 GMac, 10.296% MACs|)\n        (bn2): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.107% MACs|)\n        (relu): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.107% MACs|)\n      )\n    )\n    (resnet_unit_1): Sequential(\n      |0.054 M, 15.471% Params, 0.003 GMac, 22.619% MACs|\n      (0): ResNetUnit(\n        |0.029 M, 8.322% Params, 0.001 GMac, 12.163% MACs|\n        (conv1): Conv1d(64, 64, kernel_size=(3,), stride=(2,), padding=(1,), |0.012 M, 3.538% Params, 0.001 GMac, 5.148% MACs|)\n        (bn1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.053% MACs|)\n        (conv2): Conv1d(64, 64, kernel_size=(3,), stride=(1,), padding=(1,), |0.012 M, 3.538% Params, 0.001 GMac, 5.148% MACs|)\n        (bn2): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.053% MACs|)\n        (relu): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.053% MACs|)\n        (conv_sc): Conv1d(64, 64, kernel_size=(1,), stride=(2,), bias=False, |0.004 M, 1.173% Params, 0.0 GMac, 1.707% MACs|)\n      )\n      (1): ResNetUnit(\n        |0.025 M, 7.149% Params, 0.001 GMac, 10.456% MACs|\n        (conv1): Conv1d(64, 64, kernel_size=(3,), stride=(1,), padding=(1,), |0.012 M, 3.538% Params, 0.001 GMac, 5.148% MACs|)\n        (bn1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.053% MACs|)\n        (conv2): Conv1d(64, 64, kernel_size=(3,), stride=(1,), padding=(1,), |0.012 M, 3.538% Params, 0.001 GMac, 5.148% MACs|)\n        (bn2): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.053% MACs|)\n        (relu): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.053% MACs|)\n      )\n    )\n    (resnet_unit_2): Sequential(\n      |0.182 M, 52.057% Params, 0.005 GMac, 37.982% MACs|\n      (0): ResNetUnit(\n        |0.083 M, 23.682% Params, 0.002 GMac, 17.284% MACs|\n        (conv1): Conv1d(64, 128, kernel_size=(3,), stride=(2,), padding=(1,), |0.025 M, 7.075% Params, 0.001 GMac, 5.148% MACs|)\n        (bn1): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.073% Params, 0.0 GMac, 0.053% MACs|)\n        (conv2): Conv1d(128, 128, kernel_size=(3,), stride=(1,), padding=(1,), |0.049 M, 14.114% Params, 0.001 GMac, 10.269% MACs|)\n        (bn2): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.073% Params, 0.0 GMac, 0.053% MACs|)\n        (relu): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.053% MACs|)\n        (conv_sc): Conv1d(64, 128, kernel_size=(1,), stride=(2,), bias=False, |0.008 M, 2.346% Params, 0.0 GMac, 1.707% MACs|)\n      )\n      (1): ResNetUnit(\n        |0.099 M, 28.375% Params, 0.002 GMac, 20.698% MACs|\n        (conv1): Conv1d(128, 128, kernel_size=(3,), stride=(1,), padding=(1,), |0.049 M, 14.114% Params, 0.001 GMac, 10.269% MACs|)\n        (bn1): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.073% Params, 0.0 GMac, 0.053% MACs|)\n        (conv2): Conv1d(128, 128, kernel_size=(3,), stride=(1,), padding=(1,), |0.049 M, 14.114% Params, 0.001 GMac, 10.269% MACs|)\n        (bn2): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.073% Params, 0.0 GMac, 0.053% MACs|)\n        (relu): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.053% MACs|)\n      )\n    )\n  )\n  (fc): Sequential(\n    |0.067 M, 19.210% Params, 0.0 GMac, 0.563% MACs|\n    (0): Sequential(\n      |0.066 M, 18.917% Params, 0.0 GMac, 0.555% MACs|\n      (0): Linear(in_features=128, out_features=512, bias=True, |0.066 M, 18.917% Params, 0.0 GMac, 0.551% MACs|)\n      (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.004% MACs|)\n      (2): Dropout(p=0.2, inplace=False, |0.0 M, 0.000% Params, 0.0 GMac, 0.000% MACs|)\n    )\n    (1): Linear(in_features=512, out_features=2, bias=True, |0.001 M, 0.294% Params, 0.0 GMac, 0.009% MACs|)\n  )\n)\n

The data card is the same as the MLP case, shown in top_tagging/data/pf_features.yaml.

The full architecture of the ParticleNet model, which is based on DGCNN and EdgeConv.

Note

The ParticleNet model applied to the CMS analysis is provided in weaver/networks/particle_net_pf_sv.py, and the data card in weaver/data/ak15_points_pf_sv.yaml. Here we use a similar configuration card to deal with the benchmark task.

We will elaborate on the ParticleNet model and focus more on the technical side in this section. The model is defined in top_tagging/networks/particlenet_pf.py, but it imports some constructor, the EdgeConv block, in weaver/utils/nn/model/ParticleNet.py. The EdgeConv is illustrated in the cartoon.

Illustration of the EdgeConv block

From an EdgeConv block's point of view, it requires two classes of features as input: the \"coordinates\" and the \"features\". These features are the per point properties, in the 2D shape with dimensions (C, P), where C is the size of the features (the feature size of \"coordinates\" and the \"features\" can be different, marked as C_pts, C_fts in the following code), and P is the number of points. The block outputs the new features that the model learns, also in the 2D shape with dimensions (C_fts_out, P).

What happens inside the EdgeConv block? And how is the output feature vector transferred from the input features using the topology of the point cloud? The answer is encoded in the edge convolution (EdgeConv).

The edge convolution is an analogue convolution method defined on a point cloud, whose shape is given by the \"coordinates\" of points. Specifically, the input \"coordinates\" provide a view of spatial relations of the points in the Euclidean space. It determines the k-nearest neighbouring points for each point that will guide the update of the feature vector of a point. For each point, the updated feature vector is based on the current state of the point and its k neighbours. Guided by this spirit, all features of the point cloud forms a 3D vector with dimensions (C, P, K), where C is the per-point feature size (e.g., \u03b7, \u03c6, pT\uff0c...), P is the number of points, and K the k-NN number. The structured vector is linearly transformed by acting 2D CNN on the feature dimension C. This helps to aggregate the feature information and exploit the correlations of each point with its adjacent points. A shortcut connection is also introduced inspired by the ResNet.

Note

The feature dimension C after exploring the k neighbours of each point actually doubles the value of the initial feature dimension. Here, a new set of features is constructed by subtracting the feature a point carries to the features its k neighbours carry (namely xi \u2013 xi_j for point i, and j=1,...,k). This way, the correlation of each point with its neighbours are well captured.

Below shows how the EdgeConv structure is implemented in the code.

EdgeConv block implementation

See weaver/utils/nn/model/ParticleNet.py, or the following code block annotated with more comments. We elaborate here on several aspects.

  • The EdgeConvBlock takes the feature dimension in_feat, out_feats which are C_fts, C_fts_out we introduced above.
  • The input data vectors to forward() are \"coordinates\" and \"features\" vector, in the dimension of (N, C_pts(C_fts), P) as introduced above. The first dimension is the mini-batch size.
  • self.get_graph_feature() helps to aggregate k-nearest neighbours for each point. The resulting vector is in the dimension of (N, C_fts(0), P, K) as we discussed above, K being the k-NN number. Note that the C_fts(0) doubles the value of the original input feature dimension C_fts as mentioned above.
  • After convolutions, the per-point features are merged by taking the mean of all k-nearest neighbouring vectors:
    fts = x.mean(dim=-1)  # (N, C, P)\n
class EdgeConvBlock(nn.Module):\nr\"\"\"EdgeConv layer.\n    Introduced in \"`Dynamic Graph CNN for Learning on Point Clouds\n    <https://arxiv.org/pdf/1801.07829>`__\".  Can be described as follows:\n    .. math::\n    x_i^{(l+1)} = \\max_{j \\in \\mathcal{N}(i)} \\mathrm{ReLU}(\n    \\Theta \\cdot (x_j^{(l)} - x_i^{(l)}) + \\Phi \\cdot x_i^{(l)})\n    where :math:`\\mathcal{N}(i)` is the neighbor of :math:`i`.\n    Parameters\n    ----------\n    in_feat : int\n        Input feature size.\n    out_feat : int\n        Output feature size.\n    batch_norm : bool\n        Whether to include batch normalization on messages.\n    \"\"\"\n\n    def __init__(self, k, in_feat, out_feats, batch_norm=True, activation=True, cpu_mode=False):\n        super(EdgeConvBlock, self).__init__()\n        self.k = k\n        self.batch_norm = batch_norm\n        self.activation = activation\n        self.num_layers = len(out_feats)\n        self.get_graph_feature = get_graph_feature_v2 if cpu_mode else get_graph_feature_v1\n\n        self.convs = nn.ModuleList()\n        for i in range(self.num_layers):\n            self.convs.append(nn.Conv2d(2 * in_feat if i == 0 else out_feats[i - 1], out_feats[i], kernel_size=1, bias=False if self.batch_norm else True))\n\n        if batch_norm:\n            self.bns = nn.ModuleList()\n            for i in range(self.num_layers):\n                self.bns.append(nn.BatchNorm2d(out_feats[i]))\n\n        if activation:\n            self.acts = nn.ModuleList()\n            for i in range(self.num_layers):\n                self.acts.append(nn.ReLU())\n\n        if in_feat == out_feats[-1]:\n            self.sc = None\n        else:\n            self.sc = nn.Conv1d(in_feat, out_feats[-1], kernel_size=1, bias=False)\n            self.sc_bn = nn.BatchNorm1d(out_feats[-1])\n\n        if activation:\n            self.sc_act = nn.ReLU()\n\n    def forward(self, points, features):\n        # points:   (N, C_pts, P)\n        # features: (N, C_fts, P)\n        # N: batch size, C: feature size per point, P: number of points\n\n        topk_indices = knn(points, self.k) # (N, P, K)\n        x = self.get_graph_feature(features, self.k, topk_indices) # (N, C_fts(0), P, K)\n\n        for conv, bn, act in zip(self.convs, self.bns, self.acts):\n            x = conv(x)  # (N, C', P, K)\n            if bn:\n                x = bn(x)\n            if act:\n                x = act(x)\n\n        fts = x.mean(dim=-1)  # (N, C, P)\n\n        # shortcut\n        if self.sc:\n            sc = self.sc(features)  # (N, C_out, P)\n            sc = self.sc_bn(sc)\n        else:\n            sc = features\n\n        return self.sc_act(sc + fts)  # (N, C_out, P)\n

With the EdgeConv architecture as the building block, the ParticleNet model is constructed as follow.

The ParticleNet model stacks three EdgeConv blocks to construct higher-level features and passing them through the pipeline. The points (i.e., in our case, the particle candidates inside a jet) are not changing, but the per-point \"coordinates\" and \"features\" vectors changes, in both values and dimensions.

For the first EdgeConv block, the \"coordinates\" only include the relative \u03b7 and \u03c6 value of each particle. The \"features\" is a vector with a standard length of 32, which is linearly transformed from the initial feature vectors including the components of relative \u03b7, \u03c6, the log of pT, etc. The first EdgeConv block outputs a per-point feature vector of length 64, which is taken as both the \"coordinates\" and \"features\" to the next EdgeConv block. That is to say, the next k-NN is applied on the 64D high-dimensional spatial space to capture the new relations of points learned by the model. This is visualized by the input/output arrows showing the data flow of the model. We see that this architecture illustrates the stackability of the EdgeConv block, and is the core to the Dynamic Graph CNN (DGCNN), as the model can dynamically change the correlations of each point based on learnable features.

A fusion technique is also used by concatenating the three EdgeConv output vectors together (adding the dimensions), instead of using the last EdgeConv output, to form an output vector. This is also one form of shortcut implementations that helps to ease the training for a complex and deep convolutional network model.

The concatenated vectors per point are then averaged over points to produce a single 1D vector of the whole point cloud. The vector passes through one fully connected layer, with a dropout rate of p=0.1 to prevent overfitting. Then, in our example, the full network outputs two scores after a softmax, representing the one-hot encoding of the top vs. QCD class.

The ParticleNet implementation is shown below.

ParticleNet model implementation

See weaver/utils/nn/model/ParticleNet.py, or the following code block annotated with more comments. We elaborate here on several mean points.

  • The stack of multiple EdgeConv blocks are implemented in
    for idx, conv in enumerate(self.edge_convs):\n    pts = (points if idx == 0 else fts) + coord_shift\n    fts = conv(pts, fts) * mask\n
  • The multiple EdgeConv layer parameters are given by conv_params, which takes a list of tuples, each tuple in the format of (K, (C1, C2, C3)). K for the k-NN number, C1,2,3 for convolution feature sizes of three layers in an EdgeConv block.
  • The fully connected layer parameters are given by fc_params, which takes a list of tuples, each tuple in the format of (n_feat, drop_rate).
class ParticleNet(nn.Module):\nr\"\"\"Parameters\n    ----------\n    input_dims : int\n        Input feature dimensions (C_fts).\n    num_classes : int\n        Number of output classes.\n    conv_params : list\n        List of convolution parameters of EdgeConv blocks, each element in the format of (K, (C1, C2, C3)).\n        K for the kNN number, C1,2,3 for convolution feature sizes of three layers in an EdgeConv block.\n    fc_params: list\n        List of fully connected layer parameters after all EdgeConv blocks, each element in the format of\n        (n_feat, drop_rate)\n    use_fusion: bool\n        If true, concatenates all output features from each EdgeConv before the fully connected layer.\n    use_fts_bn: bool\n        If true, applies a batch norm before feeding to the EdgeConv block.\n    use_counts: bool\n        If true, uses the real count of points instead of the padded size (the max point size).\n    for_inference: bool\n        Whether this is an inference routine. If true, applies a softmax to the output.\n    for_segmentation: bool\n        Whether the model is set up for the point cloud segmentation (instead of classification) task. If true,\n        does not merge the features after the last EdgeConv, and apply Conv1D instead of the linear layer.\n        The output is hence each output_features per point, instead of output_features.\n    \"\"\"\n\n\n    def __init__(self,\n                input_dims,\n                num_classes,\n                conv_params=[(7, (32, 32, 32)), (7, (64, 64, 64))],\n                fc_params=[(128, 0.1)],\n                use_fusion=True,\n                use_fts_bn=True,\n                use_counts=True,\n                for_inference=False,\n                for_segmentation=False,\n                **kwargs):\n        super(ParticleNet, self).__init__(**kwargs)\n\n        self.use_fts_bn = use_fts_bn\n        if self.use_fts_bn:\n            self.bn_fts = nn.BatchNorm1d(input_dims)\n\n        self.use_counts = use_counts\n\n        self.edge_convs = nn.ModuleList()\n        for idx, layer_param in enumerate(conv_params):\n            k, channels = layer_param\n            in_feat = input_dims if idx == 0 else conv_params[idx - 1][1][-1]\n            self.edge_convs.append(EdgeConvBlock(k=k, in_feat=in_feat, out_feats=channels, cpu_mode=for_inference))\n\n        self.use_fusion = use_fusion\n        if self.use_fusion:\n            in_chn = sum(x[-1] for _, x in conv_params)\n            out_chn = np.clip((in_chn // 128) * 128, 128, 1024)\n            self.fusion_block = nn.Sequential(nn.Conv1d(in_chn, out_chn, kernel_size=1, bias=False), nn.BatchNorm1d(out_chn), nn.ReLU())\n\n        self.for_segmentation = for_segmentation\n\n        fcs = []\n        for idx, layer_param in enumerate(fc_params):\n            channels, drop_rate = layer_param\n            if idx == 0:\n                in_chn = out_chn if self.use_fusion else conv_params[-1][1][-1]\n            else:\n                in_chn = fc_params[idx - 1][0]\n            if self.for_segmentation:\n                fcs.append(nn.Sequential(nn.Conv1d(in_chn, channels, kernel_size=1, bias=False),\n                                        nn.BatchNorm1d(channels), nn.ReLU(), nn.Dropout(drop_rate)))\n            else:\n                fcs.append(nn.Sequential(nn.Linear(in_chn, channels), nn.ReLU(), nn.Dropout(drop_rate)))\n        if self.for_segmentation:\n            fcs.append(nn.Conv1d(fc_params[-1][0], num_classes, kernel_size=1))\n        else:\n            fcs.append(nn.Linear(fc_params[-1][0], num_classes))\n        self.fc = nn.Sequential(*fcs)\n\n        self.for_inference = for_inference\n\n    def forward(self, points, features, mask=None):\n#         print('points:\\n', points)\n#         print('features:\\n', features)\n        if mask is None:\n            mask = (features.abs().sum(dim=1, keepdim=True) != 0)  # (N, 1, P)\n        points *= mask\n        features *= mask\n        coord_shift = (mask == 0) * 1e9\n        if self.use_counts:\n            counts = mask.float().sum(dim=-1)\n            counts = torch.max(counts, torch.ones_like(counts))  # >=1\n\n        if self.use_fts_bn:\n            fts = self.bn_fts(features) * mask\n        else:\n            fts = features\n        outputs = []\n        for idx, conv in enumerate(self.edge_convs):\n            pts = (points if idx == 0 else fts) + coord_shift\n            fts = conv(pts, fts) * mask\n            if self.use_fusion:\n                outputs.append(fts)\n        if self.use_fusion:\n            fts = self.fusion_block(torch.cat(outputs, dim=1)) * mask\n\n#         assert(((fts.abs().sum(dim=1, keepdim=True) != 0).float() - mask.float()).abs().sum().item() == 0)\n\n        if self.for_segmentation:\n            x = fts\n        else:\n            if self.use_counts:\n                x = fts.sum(dim=-1) / counts  # divide by the real counts\n            else:\n                x = fts.mean(dim=-1)\n\n        output = self.fc(x)\n        if self.for_inference:\n            output = torch.softmax(output, dim=1)\n        # print('output:\\n', output)\n        return output\n

Above are the capsulation of all ParticleNet building blocks. Eventually, we have the model defined in the model card top_tagging/networks/particlenet_pf.py, in the ParticleNetTagger1Path class, meaning we only use the ParticleNet pipeline that deals with one set of the point cloud (i.e., the particle candidates).

Info

Two sets of point clouds in the CMS application, namely the particle-flow candidates and secondary vertices, are used. This requires special handling to merge the clouds before feeding them to the first layer of EdgeConv.

ParticleNet model config

Also see top_tagging/networks/particlenet_pf.py.

import torch\nimport torch.nn as nn\nfrom utils.nn.model.ParticleNet import ParticleNet, FeatureConv\n\n\nclass ParticleNetTagger1Path(nn.Module):\n\n    def __init__(self,\n                pf_features_dims,\n                num_classes,\n                conv_params=[(7, (32, 32, 32)), (7, (64, 64, 64))],\n                fc_params=[(128, 0.1)],\n                use_fusion=True,\n                use_fts_bn=True,\n                use_counts=True,\n                pf_input_dropout=None,\n                for_inference=False,\n                **kwargs):\n        super(ParticleNetTagger1Path, self).__init__(**kwargs)\n        self.pf_input_dropout = nn.Dropout(pf_input_dropout) if pf_input_dropout else None\n        self.pf_conv = FeatureConv(pf_features_dims, 32)\n        self.pn = ParticleNet(input_dims=32,\n                            num_classes=num_classes,\n                            conv_params=conv_params,\n                            fc_params=fc_params,\n                            use_fusion=use_fusion,\n                            use_fts_bn=use_fts_bn,\n                            use_counts=use_counts,\n                            for_inference=for_inference)\n\n    def forward(self, pf_points, pf_features, pf_mask):\n        if self.pf_input_dropout:\n            pf_mask = (self.pf_input_dropout(pf_mask) != 0).float()\n            pf_points *= pf_mask\n            pf_features *= pf_mask\n\n        return self.pn(pf_points, self.pf_conv(pf_features * pf_mask) * pf_mask, pf_mask)\n\n\ndef get_model(data_config, **kwargs):\n    conv_params = [\n        (16, (64, 64, 64)),\n        (16, (128, 128, 128)),\n        (16, (256, 256, 256)),\n        ]\n    fc_params = [(256, 0.1)]\n    use_fusion = True\n\n    pf_features_dims = len(data_config.input_dicts['pf_features'])\n    num_classes = len(data_config.label_value)\n    model = ParticleNetTagger1Path(pf_features_dims, num_classes,\n                            conv_params, fc_params,\n                            use_fusion=use_fusion,\n                            use_fts_bn=kwargs.get('use_fts_bn', False),\n                            use_counts=kwargs.get('use_counts', True),\n                            pf_input_dropout=kwargs.get('pf_input_dropout', None),\n                            for_inference=kwargs.get('for_inference', False)\n                            )\n    model_info = {\n        'input_names':list(data_config.input_names),\n        'input_shapes':{k:((1,) + s[1:]) for k, s in data_config.input_shapes.items()},\n        'output_names':['softmax'],\n        'dynamic_axes':{**{k:{0:'N', 2:'n_' + k.split('_')[0]} for k in data_config.input_names}, **{'softmax':{0:'N'}}},\n        }\n\n    print(model, model_info)\n    print(data_config.input_shapes)\n    return model, model_info\n\n\ndef get_loss(data_config, **kwargs):\n    return torch.nn.CrossEntropyLoss()\n

The most important parameters are conv_params and fc_params, which decides the model parameters of EdgeConv blocks and the fully connected layer. See details in the above \"ParticleNet model implementation\" box.

conv_params = [\n    (16, (64, 64, 64)),\n    (16, (128, 128, 128)),\n    (16, (256, 256, 256)),\n    ]\nfc_params = [(256, 0.1)]\n

A full structure printed from PyTorch is shown below. It will appear in the Weaver output during training.

ParticleNet full-scale structure
ParticleNetTagger1Path(\n  |0.577 M, 100.000% Params, 0.441 GMac, 100.000% MACs|\n  (pf_conv): FeatureConv(\n    |0.0 M, 0.035% Params, 0.0 GMac, 0.005% MACs|\n    (conv): Sequential(\n      |0.0 M, 0.035% Params, 0.0 GMac, 0.005% MACs|\n      (0): BatchNorm1d(4, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.001% Params, 0.0 GMac, 0.000% MACs|)\n      (1): Conv1d(4, 32, kernel_size=(1,), stride=(1,), bias=False, |0.0 M, 0.022% Params, 0.0 GMac, 0.003% MACs|)\n      (2): BatchNorm1d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.011% Params, 0.0 GMac, 0.001% MACs|)\n      (3): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.001% MACs|)\n    )\n  )\n  (pn): ParticleNet(\n    |0.577 M, 99.965% Params, 0.441 GMac, 99.995% MACs|\n    (edge_convs): ModuleList(\n      |0.305 M, 52.823% Params, 0.424 GMac, 96.047% MACs|\n      (0): EdgeConvBlock(\n        |0.015 M, 2.575% Params, 0.021 GMac, 4.716% MACs|\n        (convs): ModuleList(\n          |0.012 M, 2.131% Params, 0.02 GMac, 4.456% MACs|\n          (0): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.004 M, 0.710% Params, 0.007 GMac, 1.485% MACs|)\n          (1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.004 M, 0.710% Params, 0.007 GMac, 1.485% MACs|)\n          (2): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.004 M, 0.710% Params, 0.007 GMac, 1.485% MACs|)\n        )\n        (bns): ModuleList(\n          |0.0 M, 0.067% Params, 0.001 GMac, 0.139% MACs|\n          (0): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.022% Params, 0.0 GMac, 0.046% MACs|)\n          (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.022% Params, 0.0 GMac, 0.046% MACs|)\n          (2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.022% Params, 0.0 GMac, 0.046% MACs|)\n        )\n        (acts): ModuleList(\n          |0.0 M, 0.000% Params, 0.0 GMac, 0.070% MACs|\n          (0): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.023% MACs|)\n          (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.023% MACs|)\n          (2): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.023% MACs|)\n        )\n        (sc): Conv1d(32, 64, kernel_size=(1,), stride=(1,), bias=False, |0.002 M, 0.355% Params, 0.0 GMac, 0.046% MACs|)\n        (sc_bn): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.022% Params, 0.0 GMac, 0.003% MACs|)\n        (sc_act): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.001% MACs|)\n      )\n      (1): EdgeConvBlock(\n        |0.058 M, 10.121% Params, 0.081 GMac, 18.437% MACs|\n        (convs): ModuleList(\n          |0.049 M, 8.523% Params, 0.079 GMac, 17.825% MACs|\n          (0): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.016 M, 2.841% Params, 0.026 GMac, 5.942% MACs|)\n          (1): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.016 M, 2.841% Params, 0.026 GMac, 5.942% MACs|)\n          (2): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.016 M, 2.841% Params, 0.026 GMac, 5.942% MACs|)\n        )\n        (bns): ModuleList(\n          |0.001 M, 0.133% Params, 0.001 GMac, 0.279% MACs|\n          (0): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.044% Params, 0.0 GMac, 0.093% MACs|)\n          (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.044% Params, 0.0 GMac, 0.093% MACs|)\n          (2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.044% Params, 0.0 GMac, 0.093% MACs|)\n        )\n        (acts): ModuleList(\n          |0.0 M, 0.000% Params, 0.001 GMac, 0.139% MACs|\n          (0): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.046% MACs|)\n          (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.046% MACs|)\n          (2): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.046% MACs|)\n        )\n        (sc): Conv1d(64, 128, kernel_size=(1,), stride=(1,), bias=False, |0.008 M, 1.420% Params, 0.001 GMac, 0.186% MACs|)\n        (sc_bn): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.044% Params, 0.0 GMac, 0.006% MACs|)\n        (sc_act): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.003% MACs|)\n      )\n      (2): EdgeConvBlock(\n        |0.231 M, 40.128% Params, 0.322 GMac, 72.894% MACs|\n        (convs): ModuleList(\n          |0.197 M, 34.091% Params, 0.315 GMac, 71.299% MACs|\n          (0): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.066 M, 11.364% Params, 0.105 GMac, 23.766% MACs|)\n          (1): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.066 M, 11.364% Params, 0.105 GMac, 23.766% MACs|)\n          (2): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.066 M, 11.364% Params, 0.105 GMac, 23.766% MACs|)\n        )\n        (bns): ModuleList(\n          |0.002 M, 0.266% Params, 0.002 GMac, 0.557% MACs|\n          (0): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.001 M, 0.089% Params, 0.001 GMac, 0.186% MACs|)\n          (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.001 M, 0.089% Params, 0.001 GMac, 0.186% MACs|)\n          (2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.001 M, 0.089% Params, 0.001 GMac, 0.186% MACs|)\n        )\n        (acts): ModuleList(\n          |0.0 M, 0.000% Params, 0.001 GMac, 0.279% MACs|\n          (0): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.093% MACs|)\n          (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.093% MACs|)\n          (2): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.093% MACs|)\n        )\n        (sc): Conv1d(128, 256, kernel_size=(1,), stride=(1,), bias=False, |0.033 M, 5.682% Params, 0.003 GMac, 0.743% MACs|)\n        (sc_bn): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.001 M, 0.089% Params, 0.0 GMac, 0.012% MACs|)\n        (sc_act): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.006% MACs|)\n      )\n    )\n    (fusion_block): Sequential(\n      |0.173 M, 29.963% Params, 0.017 GMac, 3.925% MACs|\n      (0): Conv1d(448, 384, kernel_size=(1,), stride=(1,), bias=False, |0.172 M, 29.830% Params, 0.017 GMac, 3.899% MACs|)\n      (1): BatchNorm1d(384, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.001 M, 0.133% Params, 0.0 GMac, 0.017% MACs|)\n      (2): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.009% MACs|)\n    )\n    (fc): Sequential(\n      |0.099 M, 17.179% Params, 0.0 GMac, 0.023% MACs|\n      (0): Sequential(\n        |0.099 M, 17.090% Params, 0.0 GMac, 0.022% MACs|\n        (0): Linear(in_features=384, out_features=256, bias=True, |0.099 M, 17.090% Params, 0.0 GMac, 0.022% MACs|)\n        (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.000% MACs|)\n        (2): Dropout(p=0.1, inplace=False, |0.0 M, 0.000% Params, 0.0 GMac, 0.000% MACs|)\n      )\n      (1): Linear(in_features=256, out_features=2, bias=True, |0.001 M, 0.089% Params, 0.0 GMac, 0.000% MACs|)\n    )\n  )\n)\n

The data card is shown in top_tagging/data/pf_points_features.yaml, given in a similar way as in the MLP example. Here we group the inputs into three classes: pf_points, pf_features and pf_masks. They correspond to the forward(self, pf_points, pf_features, pf_mask) prototype of our nn.Module model, and will send in these 2D vectors in the mini-batch size for each iteration during training/prediction.

ParticleNet data config top_tagging/data/pf_points_features.yaml

See top_tagging/data/pf_points_features.yaml.

selection:\n### use `&`, `|`, `~` for logical operations on numpy arrays\n### can use functions from `math`, `np` (numpy), and `awkward` in the expression\n\nnew_variables:\n### [format] name: formula\n### can use functions from `math`, `np` (numpy), and `awkward` in the expression\npf_mask: awkward.JaggedArray.ones_like(Part_E)\nis_bkg: np.logical_not(is_signal_new)\n\npreprocess:\n### method: [manual, auto] - whether to use manually specified parameters for variable standardization\nmethod: manual\n### data_fraction: fraction of events to use when calculating the mean/scale for the standardization\ndata_fraction:\n\ninputs:\npf_points:\nlength: 100\nvars:\n- Part_Etarel\n- Part_Phirel\npf_features:\nlength: 100\nvars:\n### [format 1]: var_name (no transformation)\n### [format 2]: [var_name,\n###              subtract_by(optional, default=None, no transf. if preprocess.method=manual, auto transf. if preprocess.method=auto),\n###              multiply_by(optional, default=1),\n###              clip_min(optional, default=-5),\n###              clip_max(optional, default=5),\n###              pad_value(optional, default=0)]\n- Part_Etarel\n- Part_Phirel\n- [Part_E_log, 2, 1]\n- [Part_P_log, 2, 1]\npf_mask:\nlength: 100\nvars:\n- pf_mask\n\nlabels:\n### type can be `simple`, `custom`\n### [option 1] use `simple` for binary/multi-class classification, then `value` is a list of 0-1 labels\ntype: simple\nvalue: [\nis_signal_new, is_bkg\n]\n### [option 2] otherwise use `custom` to define the label, then `value` is a map\n# type: custom\n# value:\n# target_mass: np.where(fj_isQCD, fj_genjet_sdmass, fj_gen_mass)\n\nobservers:\n- origIdx\n- idx\n- Part_E_tot\n- Part_PX_tot\n- Part_PY_tot\n- Part_PZ_tot\n- Part_P_tot\n- Part_Eta_tot\n- Part_Phi_tot\n\n# weights:\n### [option 1] use precomputed weights stored in the input files\n# use_precomputed_weights: true\n# weight_branches: [weight, class_weight]\n### [option 2] compute weights on-the-fly using reweighting histograms\n

Now we have walked through the detailed description of three networks in their architecture as well as their implementations in Weaver.

Before ending this section, we summarize the three networks on their (1) model and data configuration cards, (2) the number of parameters, and (3) computational complexity in the following table. Note that we'll refer to the shell variables provided here in the following training example.

Model ${PREFIX} ${MODEL_CONFIG} ${DATA_CONFIG} Parameters Computational complexity MLP mlp mlp_pf.py pf_features.yaml 739k 0.001 GMac DeepAK8 (1D CNN) deepak8 deepak8_pf.py pf_features.yaml 349k 0.012 GMac ParticleNet (DGCNN) particlenet particlenet_pf.py pf_points_features.yaml 577k 0.441 GMac"},{"location":"inference/particlenet.html#2-start-training","title":"2. Start training!","text":"

Now we train the three neural networks based on the provided model and data configurations.

Here we present three ways of training. For readers who have a local machine with CUDA GPUs, please try out training on the local GPUs. Readers who would like to try on CPUs can also refer to the local GPU instruction. It is also possible to borrow the GPU resources from the lxplus HTCondor or CMS Connect. Please find in the following that meets your situation.

Train on local GPUsUse GPUs on lxplus HTCondorUse GPUs on CMS Connect

The three networks can be trained with a universal script. Enter the weaver base folder and run the following command. Note that ${DATA_CONFIG}, ${MODEL_CONFIG}, and ${PREFIX} refers to the value in the above table for each example, and the fake path should be replaced with the correct one.

PREFIX='<prefix-from-table>'\nMODEL_CONFIG='<model-config-from-table>'\nDATA_CONFIG='<data-config-from-table>'\nPATH_TO_SAMPLES='<your-path-to-samples>'\n\npython train.py \\\n --data-train ${PATH_TO_SAMPLES}'/prep/top_train_*.root' \\\n --data-val ${PATH_TO_SAMPLES}'/prep/top_val_*.root' \\\n --fetch-by-file --fetch-step 1 --num-workers 3 \\\n --data-config top_tagging/data/${DATA_CONFIG} \\\n --network-config top_tagging/networks/${MODEL_CONFIG} \\\n --model-prefix output/${PREFIX} \\\n --gpus 0,1 --batch-size 1024 --start-lr 5e-3 --num-epochs 20 --optimizer ranger \\\n --log output/${PREFIX}.train.log\n

Here --gpus 0,1 specifies the GPUs to run with the device ID 1 and 2. For training on CPUs, please use --gpu ''.

A detailed description of the training command can be found in Weaver README. Below we will note a few more caveats about the data loading options, though the specific settings will depend on the specifics of the input data.

Caveats on the data loading options

Our goal in data loading is to guarantee that the data loaded in every mini-batch is evenly distributed with different labels, though they are not necessarily stored evenly in the file. Besides, we also need to ensure that the on-the-fly loading and preprocessing of data should be smooth and not be a bottleneck of the data delivering pipeline. The total amount of loaded data also needs to be controlled so as not to explode the entire memory. The following guidelines should be used to choose the best options for your use case:

  • in the default case, data are loaded from every input file with a small proportion per fetch-step, provided by --fetch-step (default is 0.01). This adapts to the case when we have multiple classes of input, each class having multiple files (e.g., it adapts to the real CMS application because we may have multiple nano_i.root files for different input classes). The strategy gathered all pieces per fetch-step from all input files, shuffle them, and present the data we need in each regular mini-batch. One can also append --num-workers n with n being the number of paralleled workers to load the data.
  • --fetch-step 1 --num-workers 1. This strategy helps in the case we have few input files with data in different labels not evenly distributed. In the extreme case, we only have 1 file, with all data at the top being one class (signal) and data at the bottom being another class (background), or we have 2 or multiple files, each containing a specific class. In this option, --fetch-step 1 guarantees the entire data in the file is loaded and participate in the shuffle. Therefore all classes are safely mixed before sending to the mini-batch. --num-workers 1 means we only use one worker that takes care of all files to avoid inconsistent loading speeds of multiple workers (depending on CPUs). This strategy can further cooperate with --in-memory so that all data are put permanently in memory and will not be reloaded every epoch. --fetch-by-file is the option we can use when all input files have a similar structure. See Weaver README:

An alternative approach is the \"file-based\" strategy, which can be enabled with --fetch-by-files. This approach will instead read all events from every file for each step, and it will read m input files (m is set by --fetch-step) before mixing and shuffling the loaded events. This strategy is more suitable when each input file is already a mixture of all types of events (e.g., pre-processed with NNTools), otherwise it may lead to suboptimal training performance. However, a higher data loading speed can generally be achieved with this approach.

Please note that you can test if all data classes are well mixed by printing the truth label in each mini-batch. Also, remember to test if data are loaded just-in-time by monitoring the GPU performance \u2014 if switching the data loading strategy helps improve the GPU efficiency, it means the previous data loader is the bottleneck in the pipeline to deliver and use the data.

After training, we predict the score on the test datasets using the best model:

PREFIX='<prefix-from-table>'\nMODEL_CONFIG='<model-config-from-table>'\nDATA_CONFIG='<data-config-from-table>'\nPATH_TO_SAMPLES='<your-path-to-samples>'\n\npython train.py --predict \\\n --data-test ${PATH_TO_SAMPLES}'/prep/top_test_*.root' \\\n --num-workers 3 \\\n --data-config top_tagging/data/${DATA_CONFIG} \\\n --network-config top_tagging/networks/${MODEL_CONFIG} \\\n --model-prefix output/${PREFIX}_best_epoch_state.pt \\\n --gpus 0,1 --batch-size 1024 \\\n --predict-output output/${PREFIX}_predict.root\n

On lxplus HTCondor, the GPU(s) can be booked via the arguments request_gpus. To get familiar with the GPU service, please refer to the documentation here.

While it is not possible to test the script locally, you can try out the condor_ssh_to_job command to connect to the remote condor machine that runs the jobs. This interesting feature will help you with debugging or monitoring the condor job.

Here we provide the example executed script and the condor submitted file for the training and predicting task. Create the following two files:

The executable: run.sh

Still, please remember to specify ${DATA_CONFIG}, ${MODEL_CONFIG}, and ${PREFIX} as shown in the above table, and replace the fake path with the correct one.

#!/bin/bash\n\nPREFIX=$1\nMODEL_CONFIG=$2\nDATA_CONFIG=$3\nPATH_TO_SAMPLES=$4\nWORKDIR=`pwd`\n\n# Download miniconda\nwget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda_install.sh\nbash miniconda_install.sh -b -p ${WORKDIR}/miniconda\nexport PATH=$WORKDIR/miniconda/bin:$PATH\npip install numpy pandas scikit-learn scipy matplotlib tqdm PyYAML\npip install uproot3 awkward0 lz4 xxhash\npip install tables\npip install onnxruntime-gpu\npip install tensorboard\npip install torch\n\n# CUDA environment setup\nexport PATH=$PATH:/usr/local/cuda-10.2/bin\nexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda-10.2/lib64\nexport LIBRARY_PATH=$LIBRARY_PATH:/usr/local/cuda-10.2/lib64\n\n# Clone weaver-benchmark\ngit clone --recursive https://github.com/colizz/weaver-benchmark.git\nln -s ../top_tagging weaver-benchmark/weaver/top_tagging\ncd weaver-benchmark/weaver/\nmkdir output\n\n# Training, using 1 GPU\npython train.py \\\n--data-train ${PATH_TO_SAMPLES}'/prep/top_train_*.root' \\\n--data-val ${PATH_TO_SAMPLES}'/prep/top_val_*.root' \\\n--fetch-by-file --fetch-step 1 --num-workers 3 \\\n--data-config top_tagging/data/${DATA_CONFIG} \\\n--network-config top_tagging/networks/${MODEL_CONFIG} \\\n--model-prefix output/${PREFIX} \\\n--gpus 0 --batch-size 1024 --start-lr 5e-3 --num-epochs 20 --optimizer ranger \\\n--log output/${PREFIX}.train.log\n\n# Predicting score, using 1 GPU\npython train.py --predict \\\n--data-test ${PATH_TO_SAMPLES}'/prep/top_test_*.root' \\\n--num-workers 3 \\\n--data-config top_tagging/data/${DATA_CONFIG} \\\n--network-config top_tagging/networks/${MODEL_CONFIG} \\\n--model-prefix output/${PREFIX}_best_epoch_state.pt \\\n--gpus 0 --batch-size 1024 \\\n--predict-output output/${PREFIX}_predict.root\n\n[ -d \"runs/\" ] && tar -caf output.tar output/ runs/ || tar -caf output.tar output/\n

HTCondor submitted file: submit.sub

Modify the argument line. These are the bash variable PREFIX, MODEL_CONFIG, DATA_CONFIG, PATH_TO_SAMPLES used in the Weaver command. Since the EOS directory is accessable accross all condor nodes on lxplus, one may directly specify <your-path-to-samples> as the EOS path provided above. An example is shown in the commented line.

Universe                = vanilla\nexecutable              = run.sh\narguments               = <prefix> <model-config> <data-config> <your-path-to-samples>\n#arguments              = mlp mlp_pf.py pf_features.yaml /eos/user/c/coli/public/weaver-benchmark/top_tagging/samples\noutput                  = job.$(ClusterId).$(ProcId).out\nerror                   = job.$(ClusterId).$(ProcId).err\nlog                     = job.$(ClusterId).log\nshould_transfer_files   = YES\nwhen_to_transfer_output = ON_EXIT_OR_EVICT\ntransfer_output_files   = weaver-benchmark/weaver/output.tar\ntransfer_output_remaps  = \"output.tar = output.$(ClusterId).$(ProcId).tar\"\nrequest_GPUs = 1\nrequest_CPUs = 4\n+MaxRuntime = 604800\nqueue\n

Make the run.sh script an executable, then submit the job.

chmod +x run.sh\ncondor_submit submit.sub\n
A tarball will be transfered back with the weaver/output directory where the trained models and the predicted ROOT file are stored.

CMS Connect provides several GPU nodes. One can request to run GPU condor jobs in a similar way as on lxplus, please refer to the link: https://ci-connect.atlassian.net/wiki/spaces/CMS/pages/80117822/Requesting+GPUs

As the EOS user space may not be accessed from the remote node launched by CMS Connect, one may consider either (1) migrating the input files by condor, or (2) using XRootD to transfer the input file from EOS space to the condor node, before running the Weaver train command.

"},{"location":"inference/particlenet.html#3-evaluation-of-models","title":"3. Evaluation of models","text":"

In the output folder, we find the trained PyTorch models after every epoch and the log file that records the loss and accuracy in the runtime.

The predict step also produces a predicted root file in the output folder, including the truth label, the predicted store, and several observer variables we provided in the data card. With the predicted root file, we make the ROC curve comparing the performance of the three trained models.

Here is the result from my training:

Model AUC Accuracy 1/eB (@eS=0.3) MLP 0.961 0.898 186 DeepAK8 (1D CNN) 0.979 0.927 585 ParticleNet (DGCNN) 0.984 0.936 1030

We see that the ParticleNet model shows an outstanding performance in this classification task. Besides, the DeepAK8 and ParticleNet results are similar to the benchmark values found in the gDoc. We address that the performance can be further improved by some following tricks:

  • Train an ensemble of models with different initial parametrization. For each event/jet, take the final predicted score as the mean/median of the score ensembles predicted by each model. This is a widely used ML technique to pursue an extra few percent of improvements.
  • Use more input variables for training. We note that in the above training example, only four input variables are used instead of a full suite of input features as done in the ParticleNet paper [arXiv:1902.08570]. Additional variables (e.g. \u0394R or log(pT / pT(jet))) can be designed based on the given 4-momenta, and, although providing redundant information in principle, can still help the network fully exploit the point cloud structure and thus do a better discrimination job.
  • The fine-tuning of the model will also bring some performance gain. See details in the next section.
"},{"location":"inference/particlenet.html#tuning-the-particlenet-model","title":"Tuning the ParticleNet model","text":"

When it comes to the real application of any DNN model, tunning the hyperparameters is an important path towards a better performance. In this section, we provide some tips on the ParticleNet model tunning. For a more detailed discussion on this topic, see more in the \"validation\" chapter in the documentation.

"},{"location":"inference/particlenet.html#1-choices-on-the-optimizer-and-the-learning-rate","title":"1. Choices on the optimizer and the learning rate","text":"

The optimizer decides how our neural network update all its parameters, and the learning rate means how fast the parameters changes in one training iteration.

Learning rate is the most important hyperparameter to choose from before concrete training is done. Here we quote from a suggested strategy: if you only have the opportunity to optimize one hyperparameter, choose the learning rate. The optimizer is also important because a wiser strategy usually means avoid the zig-zagging updating route, avoid falling into the local minima and even adapting different strategies for the fast-changing parameters and the slow ones. Adam (and its several variations) is a widely used optimizer. Another recently developed advanced optimizer is Ranger that combines RAdam and LookAhead. However, one should note that the few percent level improvement by using different optimizers is likely to be smeared by an unoptimized learning rate.

The above training scheme uses a start learning rate of 5e-3, and Ranger as the optimizer. It uses a flat+decay schedular, in a way that the LR starts to decay after processing 70% of epochs, and gradually reduce to 0.01 of its original value when nearing the completion of all epochs.

First, we note that the current case is already well optimized. Therefore, by simply reuse the current choice, the training will converge to a stable result in general. But it is always good in practice to test several choices of the optimizer and reoptimize the learning rate.

Weaver integrates multiple optimizers. In the above training command, we use --optimizer ranger to adopt the Ranger optimizer. It is also possible to switch to --optimizer adam or --optimizer adamW.

Weaver also provides the interface to optimize the learning rate before real training is performed. In the ParticleNet model training, we append

--lr-finder 5e-6,5e0,200\n
in the command, then a specific learning-rate finder program will be launched. This setup scans over the LR from 5e-6 to 5e0 by applying 200 mini-batches of training. It outputs a plot showing the training loss for different starting learning rates. In general, a lower training loss means a better choice of the learning rate parameter.

Below shows the results from LR finder by specifying --lr-finder 5e-6,5e0,200, for the --optimizer adamW (left) and the --optimizer ranger (right) case.

The training loss forms a basin shape which indicates that the optimal learning rate falls somewhere in the middle. We extract two aspects from the plots. First, the basin covers a wide range, meaning that the LR finder only provides a rough estimation. But it is a good attempt to first run the LR finder to have an overall feeling. For the Ranger case (right figure), one can choose the range 1e-3 to 1e-2 and further determine the optminal learning rate by delivering the full training. Second, we should be aware that different optimizer takes different optimal LR values. As can be seen here, the AdamW in general requires a small LR than Ranger.

"},{"location":"inference/particlenet.html#2-visualize-the-training-with-tensorboard","title":"2. Visualize the training with TensorBoard","text":"

To monitor the full training/evaluation accuracy and the loss for each mini-batch, we can draw support from a nicely integrated utility, TensorBoard, to employ real-time monitoring. See the introduction page from PyTorch: https://pytorch.org/tutorials/recipes/recipes/tensorboard_with_pytorch.html

To activate TensorBoard, append (note that replace ${PREFIX} according to the above table)

--tensorboard ${PREFIX}\n
to the training command. The runs/ subfolder containing the TensorBoard monitoring log will appear in the Weaver directory (if you are launching condor jobs, the runs/ folder will be transferred back in the tarball). Then, one can run
tensorboard --logdir=runs\n
to start the TensorBoard service and go to URL https://localhost:6006 to view the TensorBoard dashboard.

The below plots show the training and evaluation loss, in our standard choice with LR being 5e-3, and in the case of a small LR 2e-3 and a large LR 1e-2. Note that all tested LR values are within the basin in the LR finder plots.

We see that in the evaluated loss plot, the standard LR outperforms two variational choices. The reason may be that a larger LR finds difficulty in converging to the global minima, while a smaller LR may not be adequate to reach the minima point in a journey of 20 epochs. Overall, we see 5e-3 as a good choice as the starting LR for the Ranger optimizer.

"},{"location":"inference/particlenet.html#3-optimize-the-model","title":"3. Optimize the model","text":"

In practice, tuning the model size is also an important task. By concept, a smaller model tends to have unsatisfactory performance due to the limited ability to learn many local features. As the model size goes up, the performance will climb to some extent, but may further decrease due to the network \"degradation\" (deeper models have difficulty learning features). Besides, a heavier model may also cause the overfitting issue. In practice, it also leads to larger inference time which is the main concern when coming to real applications.

For the ParticleNet model case, we also test between a smaller and larger variation of the model size. Recall that the original model is defined by the following layer parameters.

conv_params = [\n    (16, (64, 64, 64)),\n    (16, (128, 128, 128)),\n    (16, (256, 256, 256)),\n    ]\nfc_params = [(256, 0.1)]\n
We can replace the code block with
ec_k = kwargs.get('ec_k', 16)\nec_c1 = kwargs.get('ec_c1', 64)\nec_c2 = kwargs.get('ec_c2', 128)\nec_c3 = kwargs.get('ec_c3', 256)\nfc_c, fc_p = kwargs.get('fc_c', 256), kwargs.get('fc_p', 0.1)\nconv_params = [\n    (ec_k, (ec_c1, ec_c1, ec_c1)),\n    (ec_k, (ec_c2, ec_c2, ec_c2)),\n    (ec_k, (ec_c3, ec_c3, ec_c3)),\n    ]\nfc_params = [(fc_c, fc_p)]\n
Then we have the ability to tune the model parameters from the command line. Append the extra arguments in the training command
--network-option ec_k 32 --network-option ec_c1 128 --network-option ec_c2 192 --network-option ec_c3 256\n
and the model parameters will take the new values as specified.

We test over two cases, one with the above setting to enlarge the model, and another by using

--network-option ec_c1 64 --network-option ec_c2 64 --network-option ec_c3 96\n
to adopt a lite version.

The Tensorboard monitoring plots in the training/evaluation loss is shown as follows.

We see that the \"heavy\" model reaches even smaller training loss, meaning that the model does not meet the degradation issue yet. However, the evaluation loss is not catching up with the training loss, showing some degree of overtraining in this scheme. From the evaluation result, we see no improvement by moving to a heavy model.

"},{"location":"inference/particlenet.html#4-apply-preselection-and-class-weights","title":"4. Apply preselection and class weights","text":"

In HEP applications, it is sometimes required to train a multi-class classifier. While it is simple to specify the input classes in the label section of the Weaver data config, it is sometimes ignored to set up the preselection and assign the suitable class weights for training. Using an unoptimized configuration, the trained model will not reach the best performance although no error message will result.

Since our top tagging example is a binary classification problem, there is no specific need to configure the preselection and class weights. Below we summarize some experiences that may be applicable in reader's custom multi-class training task.

The preselection should be chosen in a way that all remaining events passing the selection should fall into one and only one category. In other words, events with no labels attached should not be kept since it will confuse the training process.

Class weights (the class_weights option under weights in the data config) control the relative importance of input sample categories for training. Implementation-wise, it changes the event probability in a specific category chosen as training input events. The class weight comes into effect when one trains a multi-class classifier. Take 3-class case (denoted as [A, B, C]) as an example, the class_weights: [1, 1, 1] gives equal weights to all categories. Retraining the input with class_weights: [10, 1, 1] may result in a better discriminating power for class A vs. B or A vs. C; while the power of B separating with C will be weakened. As a trade-off between separating A vs. C and B vs. C, the class weights need to be intentionally tuned to achieve reasonable performance.

After the class weights are tuned, one can use another method to further factor out the interplay across categories, i.e., to define a \"binarized\" score between two classes only. Suppose the raw score for the three classes are P(A), P(B), and P(C) (their sum should be 1), then one can define the discriminant P(BvsC) = P(B) / (P(B)+P(C)) to separate B vs. C. In this way, the saparating power of B vs. C will remain unchanged for class_weights configured as either [1, 1, 1] or [10, 1, 1]. This strategy has been widely used in CMS to define composite tagger discrimant which are applied analysis-wise.

Above, we discuss in a very detailed manner on various attempts we can make to optimize the model. We hope the practical experiences presented here will help readers develop and deploy the complex ML model.

"},{"location":"inference/performance.html","title":"Performance of inference tools","text":""},{"location":"inference/pyg.html","title":"PyTorch Geometric","text":"

Geometric deep learning (GDL) is an emerging field focused on applying machine learning (ML) techniques to non-Euclidean domains such as graphs, point clouds, and manifolds. The PyTorch Geometric (PyG) library extends PyTorch to include GDL functionality, for example classes necessary to handle data with irregular structure. PyG is introduced at a high level in Fast Graph Representation Learning with PyTorch Geometric and in detail in the PyG docs.

"},{"location":"inference/pyg.html#gdl-with-pyg","title":"GDL with PyG","text":"

A complete reveiw of GDL is available in the following recently-published (and freely-available) textbook: Geometric Deep Learning: Grids, Groups, Graphs, Geodesics, and Gauges. The authors specify several key GDL architectures including convolutional neural networks (CNNs) operating on grids, Deep Sets architectures operating on sets, and graph neural networks (GNNs) operating on graphs, collections of nodes connected by edges. PyG is focused in particular on graph-structured data, which naturally encompases set-structured data. In fact, many state-of-the-art GNN architectures are implemented in PyG (see the docs)! A review of the landscape of GNN architectures is available in Graph Neural Networks: A Review of Methods and Applications.

"},{"location":"inference/pyg.html#the-data-class-pyg-graphs","title":"The Data Class: PyG Graphs","text":"

Graphs are data structures designed to encode data structured as a set of objects and relations. Objects are embedded as graph nodes \\(u\\in\\mathcal{V}\\), where \\(\\mathcal{V}\\) is the node set. Relations are represented by edges \\((i,j)\\in\\mathcal{E}\\) between nodes, where \\(\\mathcal{E}\\) is the edge set. Denote the sizes of the node and edge sets as \\(|\\mathcal{V}|=n_\\mathrm{nodes}\\) and \\(|\\mathcal{E}|=n_\\mathrm{edges}\\) respectively. The choice of edge connectivity determines the local structure of a graph, which has important downstream effects on graph-based learning algorithms. Graph construction is the process of embedding input data onto a graph structure. Graph-based learning algorithms are correspondingly imbued with a relational inductive bias based on the choice of graph representation; a graph's edge connectivity defines its local structure. The simplest graph construction routine is to construct no edges, yielding a permutation invariant set of objects. On the other hand, fully-connected graphs connect every node-node pair with an edge, yielding \\(n_\\mathrm{edges}=n_\\mathrm{nodes}(n_\\mathrm{nodes}-1)/2\\) edges. This representation may be feasible for small inputs like particle clouds corresponding to a jet, but is intractible for large-scale applications such as high-pileup tracking datasets. Notably, dynamic graph construction techniques operate on input point clouds, constructing edges on them dynamically during inference. For example, EdgeConv and GravNet GNN layers dynamically construct edges between nodes projected into a latent space; multiple such layers may be applied in sequence, yielding many intermediate graph representations on an input point cloud.

In general, nodes can have positions \\(\\{p_i\\}_{i=1}^{n_\\mathrm{nodes}}\\), \\(p_i\\in\\mathbb{R}^{n_\\mathrm{space\\_dim}}\\), and features (attributes) \\(\\{x_i\\}_{i=1}^{n_\\mathrm{nodes}}\\), \\(x_i\\in\\mathbb{R}^{n_\\mathrm{node\\_dim}}\\). In some applications like GNN-based particle tracking, node positions are taken to be the features. In others, e.g. jet identification, positional information may be used to seed dynamic graph consturction while kinematic features are propagated as edge features. Edges, too, can have features \\(\\{e_{ij}\\}_{(i,j)\\in\\mathcal{E}}\\), \\(e_{ij}\\in\\mathbb{R}^{n_\\mathrm{edge\\_dim}}\\), but do not have positions; instead, edges are defined by the nodes they connect, and may therefore be represented by, for example, the distance between the respective node-node pair. In PyG, graphs are stored as instances of the data class, whose fields fully specify the graph:

  • data.x: node feature matrix, \\(X\\in\\mathbb{R}^{n_\\mathrm{nodes}\\times n_\\mathrm{node\\_dim}}\\)
  • data.edge_index: node indices at each end of each edge, \\(I\\in\\mathbb{R}^{2\\times n_\\mathrm{edges}}\\)
  • data.edge_attr: edge feature matrix, \\(E\\in\\mathbb{R}^{n_\\mathrm{edges}\\times n_\\mathrm{edge\\_dim}}\\)
  • data.y: training target with arbitary shape (\\(y\\in\\mathbb{R}^{n_\\mathrm{nodes}\\times n_\\mathrm{out}}\\) for node-level targets, \\(y\\in\\mathbb{R}^{n_\\mathrm{edges}\\times n_\\mathrm{out}}\\) for edge-level targets or \\(y\\in\\mathbb{R}^{1\\times n_\\mathrm{out}}\\) for node-level targets).
  • data.pos: Node position matrix, \\(P\\in\\mathbb{R}^{n_\\mathrm{nodes}\\times n_\\mathrm{space\\_dim}}\\)

The PyG Introduction By Example tutorial covers the basics of graph creation, batching, transformation, and inference using this data class.

As an example, consider the ZINC chemical compounds dataset, which available as a built-in dataset in PyG:

from torch_geometric.datasets import ZINC\ntrain_dataset = ZINC(root='/tmp/ZINC', subset=True, split='train')\ntest_dataset =  ZINC(root='/tmp/ZINC', subset=True, split='test')\nlen(train_dataset)\n>>> 10000\nlen(test_dataset)\n>>> 1000   \n
Each graph in the dataset is a chemical compound; nodes are atoms and edges are chemical bonds. The node features x are categorical atom labels and the edge features edge_attr are categorical bond labels. The edge_index matrix lists all bonds present in the compound in COO format. The truth labels y indicate a synthetic computed property called constrained solubility; given a set of molecules represented as graphs, the task is to regress the constrained solubility. Therefore, this dataset is suitable for graph-level regression. Let's take a look at one molecule:

data = train_dataset[27]\ndata.x # node features\n>>> tensor([[0], [0], [1], [2], [0], \n            [0], [2], [0], [1], [2],\n            [4], [0], [0], [0], [0],\n            [4], [0], [0], [0], [0]])\n\ndata.pos # node positions \n>>> None\n\ndata.edge_index # COO edge indices\n>>> tensor([[ 0,  1,  1,  1,  2,  3,  3,  4,  4,  \n              5,  5,  6,  6,  7,  7,  7,  8,  9, \n              9, 10, 10, 10, 11, 11, 12, 12, 13, \n              13, 14, 14, 15, 15, 15, 16, 16, 16,\n              16, 17, 18, 19], # node indices w/ outgoing edges\n            [ 1,  0,  2,  3,  1,  1,  4,  3,  5,  \n              4,  6,  5,  7,  6,  8,  9,  7,  7,\n              10,  9, 11, 15, 10, 12, 11, 13, 12, \n              14, 13, 15, 10, 14, 16, 15, 17, 18,\n              19, 16, 16, 16]]) # node indices w/ incoming edges\n\ndata.edge_attr # edge features\n>>> tensor([1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, \n            1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1,\n            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \n            1, 1, 1, 1])\n\ndata.y # truth labels\n>>> tensor([-0.0972])\n\ndata.num_nodes\n>>> 20\n\ndata.num_edges\n>>> 40\n\ndata.num_node_features\n>>> 1 \n

We can load the full set of graphs onto an available GPU and create PyG dataloaders as follows:

import torch\nfrom torch_geometric.data import DataLoader\n\ndevice = 'cuda:0' if torch.cuda.is_available() else 'cpu'\ntest_dataset = [d.to(device) for d in test_dataset]\ntrain_dataset = [d.to(device) for d in train_dataset]\ntest_loader = DataLoader(test_dataset, batch_size=1, shuffle=False)\ntrain_loader = DataLoader(train_dataset, batch_size=1, shuffle=True)\n

"},{"location":"inference/pyg.html#the-message-passing-base-class-pyg-gnns","title":"The Message Passing Base Class: PyG GNNs","text":"

The 2017 paper Neural Message Passing for Quantum Chemistry presents a unified framework for a swath of GNN architectures known as message passing neural networks (MPNNs). MPNNs are GNNs whose feature updates are given by:

\\[x_i^{(k)} = \\gamma^{(k)} \\left(x_i^{(k-1)}, \\square_{j \\in \\mathcal{N}(i)} \\, \\phi^{(k)}\\left(x_i^{(k-1)}, x_j^{(k-1)},e_{ij}\\right) \\right)\\]

Here, \\(\\gamma\\) and \\(\\phi\\) are learnable functions (which we can approximate as multilayer perceptrons), \\(\\square\\) is a permutation-invariant function (e.g. mean, max, add), and \\(\\mathcal{N}(i)\\) is the neighborhood of node \\(i\\). In PyG, you'd write your own MPNN by using the MessagePassing base class, implementing each of the above mathematical objects as an explicit function.

  • MessagePassing.message() : define an explicit NN for \\(\\phi\\), use it to calculate \"messages\" between a node \\(x_i^{(k-1)}\\) and its neighbors \\(x_j^{(k-1)}\\), \\(j\\in\\mathcal{N}(i)\\), leveraging edge features \\(e_{ij}\\) if applicable
  • MessagePassing.propagate() : in this step, messages are calculated via the message function and aggregated across each receiving node; the keyword aggr (which can be 'add', 'max', or 'mean') is used to specify the specific permutation invariant function \\(\\square_{j\\in\\mathcal{N}(i)}\\) used for message aggregation.
  • MessagePassing.update() : the results of message passing are used to update the node features \\(x_i^{(k)}\\) through the \\(\\gamma\\) MLP

The specific implementations of message(), propagate(), and update() are up to the user. A specific example is available in the PyG Creating Message Passing Networks tutorial

"},{"location":"inference/pyg.html#message-passing-with-zinc-data","title":"Message-Passing with ZINC Data","text":"

Returning to the ZINC molecular compound dataset, we can design a message-passing layer to aggregate messages across molecular graphs. Here, we'll define a multi-layer perceptron (MLP) class and use it to build a message passing layer (MPL) the following equation:

\\[x_i' = \\gamma \\left(x_i, \\frac{1}{|\\mathcal{N}(i)|}\\sum_{j \\in \\mathcal{N}(i)} \\, \\phi\\left([x_i, x_j, e_{j,i}\\right]) \\right)\\]

Here, the MLP dimensions are constrained. Since \\(x_i, e_{i,j}\\in\\mathbb{R}\\), the \\(\\phi\\) MLP must map \\(\\mathbb{R}^3\\) to \\(\\mathbb{R}^\\mathrm{message\\_size}\\). Similarly, \\(\\gamma\\) must map \\(\\mathbb{R}^{1+\\mathrm{\\mathrm{message\\_size}}}\\) to \\(\\mathbb{R}^\\mathrm{out}\\).

from torch_geometric.nn import MessagePassing\nimport torch.nn as nn\nfrom torch.nn import Sequential as Seq, Linear, ReLU\n\nclass MLP(nn.Module):\n    def __init__(self, input_size, output_size):\n        super(MLP, self).__init__()\n\n        self.layers = nn.Sequential(\n            nn.Linear(input_size, 16),\n            nn.ReLU(),\n            nn.Linear(16, 16),\n            nn.ReLU(),\n            nn.Linear(16, output_size),\n        )\n\n    def forward(self, x):\n        return self.layers(x)\n\nclass MPLayer(MessagePassing):\n    def __init__(self, n_node_feats, n_edge_feats, message_size, output_size):\n        super(MPLayer, self).__init__(aggr='mean', \n                                      flow='source_to_target')\n        self.phi = MLP(2*n_node_feats + n_edge_feats, message_size)\n        self.gamma = MLP(message_size + n_node_feats, output_size)\n\n    def forward(self, x, edge_index, edge_attr):\n        return self.propagate(edge_index, x=x, edge_attr=edge_attr)\n\n    def message(self, x_i, x_j, edge_attr):       \n        return self.phi(torch.cat([x_i, x_j, edge_attr], dim=1))\n\n    def update(self, aggr_out, x):\n        return self.gamma(torch.cat([x, aggr_out], dim=1))\n

Let's apply this layer to one of the ZINC molecules:

molecule = train_dataset[0]\ntorch.Size([29, 1]) # 29 atoms and 1 feature (atom label)\nmpl = MPLayer(1, 1, 16, 8).to(device) # message_size = 16, output_size = 8\nxprime = mpl(graph.x.float(), graph.edge_index, graph.edge_attr.unsqueeze(1))\nxprime.shape\n>>> torch.Size([29, 8]) # 29 atoms and 8 features\n
There we have it - the message passing layer has produced 8 new features for each atom.

"},{"location":"inference/pytorch.html","title":"PyTorch Inference","text":"

PyTorch is an open source ML library developed by Facebook's AI Research lab. Initially released in late-2016, PyTorch is a relatively new tool, but has become increasingly popular among ML researchers (in fact, some analyses suggest it's becoming more popular than TensorFlow in academic communities!). PyTorch is written in idiomatic Python, so its syntax is easy to parse for experienced Python programmers. Additionally, it is highly compatible with graphics processing units (GPUs), which can substantially accelerate many deep learning workflows. To date PyTorch has not been integrated into CMSSW. Trained PyTorch models may be evaluated in CMSSW via ONNX Runtime, but model construction and training workflows must currently exist outside of CMSSW. Given the considerable interest in PyTorch within the HEP/ML community, we have reason to believe it will soon be available, so stay tuned!

"},{"location":"inference/pytorch.html#introductory-references","title":"Introductory References","text":"
  • PyTorch Install Guide
  • PyTorch Tutorials
  • LPC HATs: PyTorch
  • Deep Learning w/ PyTorch Course Repo
  • CODAS-HEP
"},{"location":"inference/pytorch.html#the-basics","title":"The Basics","text":"

The following documentation surrounds a set of code snippets designed to highlight some important ML features made available in PyTorch. In the following sections, we'll break down snippets from this script, highlighting specifically the PyTorch objects in it.

"},{"location":"inference/pytorch.html#tensors","title":"Tensors","text":"

The fundamental PyTorch object is the tensor. At a glance, tensors behave similarly to NumPy arrays. For example, they are broadcasted, concatenated, and sliced in exactly the same way. The following examples highlight some common numpy-like tensor transformations:

a = torch.randn(size=(2,2))\n>>> tensor([[ 1.3552, -0.0204],\n            [ 1.2677, -0.8926]])\na.view(-1, 1)\n>>> tensor([[ 1.3552],\n            [-0.0204],\n            [ 1.2677],\n            [-0.8926]])\na.transpose(0, 1)\n>>> tensor([[ 1.3552,  1.2677],\n            [-0.0204, -0.8926]])\na.unsqueeze(dim=0)\n>>> tensor([[[ 1.3552, -0.0204],\n             [ 1.2677, -0.8926]]])\na.squeeze(dim=0)\n>>> tensor([[ 1.3552, -0.0204],\n            [ 1.2677, -0.8926]])\n
Additionally, torch supports familiar matrix operations with various syntax options:
m1 = torch.randn(size=(2,3))\nm2 = torch.randn(size=(3,2))\nx = torch.randn(3)\n\nm1 @ m2 == m1.mm(m2) # matrix multiplication\n>>> tensor([[True, True],\n            [True, True]])\n\nm1 @ x == m1.mv(x) # matrix-vector multiplication\n>>> tensor([True, True])\n\nm1.t() == m1.transpose(0, 1) # matrix transpose\n>>> tensor([[True, True],\n            [True, True],\n            [True, True]])\n
Note that tensor.transpose(dim0, dim1) is a more general operation than tensor.t(). It is important to note that tensors have been ''upgraded'' from Numpy arrays in two key ways: 1) Tensors have native GPU support. If a GPU is available at runtime, tensors can be transferred from CPU to GPU, where computations such as matrix operations are substantially faster. Note that tensor operations must be performed on objects on the same device. PyTorch supports CUDA tensor types for GPU computation (see the PyTorch Cuda Semantics guide). 2) Tensors support automatic gradient (audograd) calculations, such that operations on tensors flagged with requires_grad=True are automatically tracked. The flow of tracked tensor operations defines a computation graph in which nodes are tensors and edges are functions mapping input tensors to output tensors. Gradients are calculated numerically via autograd by walking through this computation graph.

"},{"location":"inference/pytorch.html#gpu-support","title":"GPU Support","text":"

Tensors are created on the host CPU by default:

b = torch.zeros([2,3], dtype=torch.int32)\nb.device\n>>> cpu\n

You can also create tensors on any available GPUs:

torch.cuda.is_available() # check that a GPU is available\n>>> True \ncuda0 = torch.device('cuda:0')\nc = torch.ones([2,3], dtype=torch.int32, device=cuda0)\nc.device\n>>> cuda:0\n

You can also move tensors between devices:

b = b.to(cuda0)\nb.device\n>>> cuda:0\n

There are trade-offs between computations on the CPU and GPU. GPUs have limited memory and there is a cost associated with transfering data from CPUs to GPUs. However, GPUs perform heavy matrix operations much faster than CPUs, and are therefore often used to speed up training routines.

N = 1000 # \nfor i, N in enumerate([10, 100, 500, 1000, 5000]):\n    print(\"({},{}) Matrices:\".format(N,N))\n    M1_cpu = torch.randn(size=(N,N), device='cpu')\n    M2_cpu = torch.randn(size=(N,N), device='cpu')\n    M1_gpu = torch.randn(size=(N,N), device=cuda0)\n    M2_gpu = torch.randn(size=(N,N), device=cuda0)\n    if (i==0):\n        print('Check devices for each tensor:')\n        print('M1_cpu, M2_cpu devices:', M1_cpu.device, M2_cpu.device)\n        print('M1_gpu, M2_gpu devices:', M1_gpu.device, M2_gpu.device)\n\n    def large_matrix_multiply(M1, M2):\n        return M1 * M2.transpose(0,1)\n\n    n_iter = 1000\n    t_cpu = Timer(lambda: large_matrix_multiply(M1_cpu, M2_cpu))\n    cpu_time = t_cpu.timeit(number=n_iter)/n_iter\n    print('cpu time per call: {:.6f} s'.format(cpu_time))\n\n    t_gpu = Timer(lambda: large_matrix_multiply(M1_gpu, M2_gpu))\n    gpu_time = t_gpu.timeit(number=n_iter)/n_iter\n    print('gpu time per call: {:.6f} s'.format(gpu_time))\n    print('gpu_time/cpu_time: {:.6f}\\n'.format(gpu_time/cpu_time))\n\n>>> (10,10) Matrices:\nCheck devices for each tensor:\nM1_cpu, M2_cpu devices: cpu cpu\nM1_gpu, M2_gpu devices: cuda:0 cuda:0\ncpu time per call: 0.000008 s\ngpu time per call: 0.000015 s\ngpu_time/cpu_time: 1.904711\n\n(100,100) Matrices:\ncpu time per call: 0.000015 s\ngpu time per call: 0.000015 s\ngpu_time/cpu_time: 0.993163\n\n(500,500) Matrices:\ncpu time per call: 0.000058 s\ngpu time per call: 0.000016 s\ngpu_time/cpu_time: 0.267371\n\n(1000,1000) Matrices:\ncpu time per call: 0.000170 s\ngpu time per call: 0.000015 s\ngpu_time/cpu_time: 0.089784\n\n(5000,5000) Matrices:\ncpu time per call: 0.025083 s\ngpu time per call: 0.000011 s\ngpu_time/cpu_time: 0.000419\n

The complete list of Torch Tensor operations is available in the docs.

"},{"location":"inference/pytorch.html#autograd","title":"Autograd","text":"

Backpropagation occurs automatically through autograd. For example, consider the following function and its derivatives:

\\[\\begin{aligned} f(\\textbf{a}, \\textbf{b}) &= \\textbf{a}^T \\textbf{X} \\textbf{b} \\\\ \\frac{\\partial f}{\\partial \\textbf{a}} &= \\textbf{b}^T \\textbf{X}^T\\\\ \\frac{\\partial f}{\\partial \\textbf{b}} &= \\textbf{a}^T \\textbf{X} \\end{aligned}\\]

Given specific choices of \\(\\textbf{X}\\), \\(\\textbf{a}\\), and \\(\\textbf{b}\\), we can calculate the corresponding derivatives via autograd by requiring a gradient to be stored in each relevant tensor:

X = torch.ones((2,2), requires_grad=True)\na = torch.tensor([0.5, 1], requires_grad=True)\nb = torch.tensor([0.5, -2], requires_grad=True)\nf = a.T @ X @ b\nf\n>>> tensor(-2.2500, grad_fn=<DotBackward>) \nf.backward() # backprop \na.grad\n>>> tensor([-1.5000, -1.5000])\nb.T @ X.T \n>>> tensor([-1.5000, -1.5000], grad_fn=<SqueezeBackward3>)\nb.grad\n>>> tensor([1.5000, 1.5000])\na.T @ X\n>>> tensor([1.5000, 1.5000], grad_fn=<SqueezeBackward3>)\n
The tensor.backward() call initiates backpropagation, accumulating the gradient backward through a series of grad_fn labels tied to each tensor (e.g. <DotBackward>, indicating the dot product \\((\\textbf{a}^T\\textbf{X})\\textbf{b}\\)).

"},{"location":"inference/pytorch.html#data-utils","title":"Data Utils","text":"

PyTorch is equipped with many useful data-handling utilities. For example, the torch.utils.data package implements datasets (torch.utils.data.Dataset) and iterable data loaders (torch.utils.data.DataLoader). Additionally, various batching and sampling schemes are available.

You can create custom iterable datasets via torch.utils.data.Dataset, for example a dataset collecting the results of XOR on two binary inputs:

from torch.utils.data import Dataset\n\nclass Data(Dataset):\n    def __init__(self, device):\n        self.samples = torch.tensor([[0,0], [0,1], [1,0], [1,1]]).float().to(device)\n        self.targets = np.logical_xor(self.samples[:,0], \n                                      self.samples[:,1]).float().to(device)\n\n    def __len__(self):\n        return len(self.targets)\n\n    def __getitem__(self,idx):\n        return({'x': self.samples[idx],\n                'y': self.targets[idx]})\n
Dataloaders, from torch.utils.data.DataLoader, can generate shuffled batches of data via multiple workers. Here, we load our datasets onto the GPU:
from torch.utils.data import DataLoader\n\ndevice = 'cpu'\ntrain_data = Data(device)\ntest_data = Data(device)\ntrain_loader = DataLoader(train_data, batch_size=1, shuffle=True, num_workers=2)\ntest_loader = DataLoader(test_data, batch_size=1, shuffle=False, num_workers=2)\nfor i, batch in enumerate(train_loader):\n    print(i, batch)\n\n>>> 0 {'x': tensor([[0., 0.]]), 'y': tensor([0.])}\n    1 {'x': tensor([[1., 0.]]), 'y': tensor([1.])}\n    2 {'x': tensor([[1., 1.]]), 'y': tensor([0.])}\n    3 {'x': tensor([[0., 1.]]), 'y': tensor([1.])}\n
The full set of data utils is available in the docs.

"},{"location":"inference/pytorch.html#neural-networks","title":"Neural Networks","text":"

The PyTorch nn package specifies a set of modules that correspond to different neural network (NN) components and operations. For example, the torch.nn.Linear module defines a linear transform with learnable parameters and the torch.nn.Flatten module flattens two contiguous tensor dimensions. The torch.nn.Sequential module contains a set of modules such as torch.nn.Linear and torch.nn.Sequential, chaining them together to form the forward pass of a forward network. Furthermore, one may specify various pre-implemented loss functions, for example torch.nn.BCELoss and torch.nn.KLDivLoss. The full set of PyTorch NN building blocks is available in the docs.

As an example, we can design a simple neural network designed to reproduce the output of the XOR operation on binary inputs. To do so, we can compute a simple NN of the form:

\\[\\begin{aligned} x_{in}&\\in\\{0,1\\}^{2}\\\\ l_1 &= \\sigma(W_1^Tx_{in} + b_1); \\ W_1\\in\\mathbb{R}^{2\\times2},\\ b_1\\in\\mathbb{R}^{2}\\\\ l_2 &= \\sigma(W_2^Tx + b_2); \\ W_2\\in\\mathbb{R}^{2},\\ b_1\\in\\mathbb{R}\\\\ \\end{aligned}\\]
import torch.nn as nn\n\nclass Network(nn.Module):\n\n    def __init__(self):\n        super().__init__()\n\n        self.l1 = nn.Linear(2, 2)\n        self.l2 = nn.Linear(2, 1)\n\n    def forward(self, x):\n        x = torch.sigmoid(self.l1(x))\n        x = torch.sigmoid(self.l2(x))\n        return x\n\nmodel = Network().to(device)\nmodel(train_data['x'])\n\n>>> tensor([[0.5000],\n            [0.4814],\n            [0.5148],\n            [0.4957]], grad_fn=<SigmoidBackward>)\n
"},{"location":"inference/pytorch.html#optimizers","title":"Optimizers","text":"

Training a neural network involves minimizing a loss function; classes in the torch.optim package implement various optimization strategies for example stochastic gradient descent and Adam through torch.optim.SGD and torch.optim.Adam respectively. Optimizers are configurable through parameters such as the learning rate (configuring the optimizer's step size). The full set of optimizers and accompanying tutorials are available in the docs.

To demonstrate the use of an optimizer, let's train the NN above to produce the results of the XOR operation on binary inputs. Here we'll use the Adam optimizer:

from torch import optim\nfrom torch.optim.lr_scheduler import StepLR\nfrom matplotlib import pyplot as plt\n\n# helpful references:\n# Learning XOR: exploring the space of a classic problem\n# https://towardsdatascience.com/how-neural-networks-solve-the-xor-problem-59763136bdd7\n# https://courses.cs.washington.edu/courses/cse446/18wi/sections/section8/XOR-Pytorch.html\n\n# the training function initiates backprop and \n# steps the optimizer towards the weights that \n# optimize the loss function \ndef train(model, train_loader, optimizer, epoch):\n    model.train()\n    losses = []\n    for i, batch in enumerate(train_loader):\n        optimizer.zero_grad()\n        output = model(batch['x'])\n        y, output = batch['y'], output.squeeze(1)\n\n        # optimize binary cross entropy:\n        # https://pytorch.org/docs/stable/generated/torch.nn.BCELoss.html\n        loss = F.binary_cross_entropy(output, y, reduction='mean')\n        loss.backward()\n        optimizer.step()\n        losses.append(loss.item())\n\n    return np.mean(losses)\n\n# the test function does not adjust the model's weights\ndef test(model, test_loader):\n    model.eval()\n    losses, n_correct, n_incorrect = [], 0, 0\n    with torch.no_grad():\n        for i, batch in enumerate(test_loader):\n            output = model(batch['x'])\n            y, output = batch['y'], output.squeeze(1)\n            loss = F.binary_cross_entropy(output, y, \n                                          reduction='mean').item()\n            losses.append(loss)\n\n            # determine accuracy by thresholding model output at 0.5\n            batch_correct = torch.sum(((output>0.5) & (y==1)) |\n                                      ((output<0.5) & (y==0)))\n            batch_incorrect = len(y) - batch_correct\n            n_correct += batch_correct\n            n_incorrect += batch_incorrect\n\n    return np.mean(losses), n_correct/(n_correct+n_incorrect)\n\n\n# randomly initialize the model's weights\nfor module in model.modules():\n    if isinstance(module, nn.Linear):\n        module.weight.data.normal_(0, 1)\n\n# send weights to optimizer \nlr = 2.5e-2\noptimizer = optim.Adam(model.parameters(), lr=lr)\n\nepochs = 500\nfor epoch in range(1, epochs + 1):\n    train_loss = train(model, train_loader, optimizer, epoch)\n    test_loss, test_acc = test(model, test_loader)\n    if epoch%25==0:\n        print('epoch={}: train_loss={:.3f}, test_loss={:.3f}, test_acc={:.3f}'\n              .format(epoch, train_loss, test_loss, test_acc))\n\n>>> epoch=25: train_loss=0.683, test_loss=0.681, test_acc=0.500\n    epoch=50: train_loss=0.665, test_loss=0.664, test_acc=0.750\n    epoch=75: train_loss=0.640, test_loss=0.635, test_acc=0.750\n    epoch=100: train_loss=0.598, test_loss=0.595, test_acc=0.750\n    epoch=125: train_loss=0.554, test_loss=0.550, test_acc=0.750\n    epoch=150: train_loss=0.502, test_loss=0.498, test_acc=0.750\n    epoch=175: train_loss=0.435, test_loss=0.432, test_acc=0.750\n    epoch=200: train_loss=0.360, test_loss=0.358, test_acc=0.750\n    epoch=225: train_loss=0.290, test_loss=0.287, test_acc=1.000\n    epoch=250: train_loss=0.230, test_loss=0.228, test_acc=1.000\n    epoch=275: train_loss=0.184, test_loss=0.183, test_acc=1.000\n    epoch=300: train_loss=0.149, test_loss=0.148, test_acc=1.000\n    epoch=325: train_loss=0.122, test_loss=0.122, test_acc=1.000\n    epoch=350: train_loss=0.102, test_loss=0.101, test_acc=1.000\n    epoch=375: train_loss=0.086, test_loss=0.086, test_acc=1.000\n    epoch=400: train_loss=0.074, test_loss=0.073, test_acc=1.000\n    epoch=425: train_loss=0.064, test_loss=0.063, test_acc=1.000\n    epoch=450: train_loss=0.056, test_loss=0.055, test_acc=1.000\n    epoch=475: train_loss=0.049, test_loss=0.049, test_acc=1.000\n    epoch=500: train_loss=0.043, test_loss=0.043, test_acc=1.000\n
Here, the model has converged to 100% test accuracy, indicating that it has learned to reproduce the XOR outputs perfectly. Note that even though the test accuracy is 100%, the test loss (BCE) decreases steadily; this is because the BCE loss is nonzero when \\(y_{output}\\) is not exactly 0 or 1, while accuracy is determined by thresholding the model outputs such that each prediction is the boolean \\((y_{output} > 0.5)\\). This highlights that it is important to choose the correct performance metric for an ML problem. In the case of XOR, perfect test accuracy is sufficient. Let's check that we've recovered the XOR output by extracting the model's weights and using them to build a custom XOR function:

for name, param in model.named_parameters():\n    if param.requires_grad:\n        print(name, param.data)\n\n>>> l1.weight tensor([[ 7.2888, -6.4168],\n                      [ 7.2824, -8.1637]])\n    l1.bias tensor([ 2.6895, -3.9633])\n    l2.weight tensor([[-6.3500,  8.0990]])\n    l2.bias tensor([2.5058])\n

Because our model was built with nn.Linear modules, we have weight matrices and bias terms. Next, we'll hard-code the matrix operations into a custom XOR function based on the architecture of the NN:

def XOR(x):\n    w1 = torch.tensor([[ 7.2888, -6.4168],\n                       [ 7.2824, -8.1637]]).t()\n    b1 = torch.tensor([ 2.6895, -3.9633])\n    layer1_out = torch.tensor([x[0]*w1[0,0] + x[1]*w1[1,0] + b1[0],\n                               x[0]*w1[0,1] + x[1]*w1[1,1] + b1[1]])\n    layer1_out = torch.sigmoid(layer1_out)\n\n    w2 = torch.tensor([-6.3500,  8.0990])\n    b2 = 2.5058\n    layer2_out = layer1_out[0]*w2[0] + layer1_out[1]*w2[1] + b2\n    layer2_out = torch.sigmoid(layer2_out)\n    return layer2_out, (layer2_out > 0.5)\n\nXOR([0.,0.])\n>>> (tensor(0.0359), tensor(False))\nXOR([0.,1.])\n>>> (tensor(0.9135), tensor(True))\nXOR([1.,0.])\n>>> (tensor(0.9815), tensor(True))\nXOR([1.,1.])\n>>> (tensor(0.0265), tensor(False))\n

There we have it - the NN learned XOR!

"},{"location":"inference/pytorch.html#pytorch-in-cmssw","title":"PyTorch in CMSSW","text":""},{"location":"inference/pytorch.html#via-onnx","title":"Via ONNX","text":"

One way to incorporate your PyTorch models into CMSSW is through the Open Neural Network Exchange (ONNX) Runtime tool. In brief, ONNX supports training and inference for a variety of ML frameworks, and is currently integrated into CMSSW (see the CMS ML tutorial). PyTorch hosts an excellent tutorial on exporting a model from PyTorch to ONNX. ONNX is available in CMSSW (see a relevant discussion in the CMSSW git repo).

"},{"location":"inference/pytorch.html#example-use-cases","title":"Example Use Cases","text":"

The \\(ZZ\\rightarrow 4b\\) analysis utilizes trained PyTorch models via ONNX in CMSSW (see the corresponding repo). Briefly, they run ONNX in CMSSW_11_X via the CMSSW package PhysicsTools/ONNXRuntime, using it to define a multiClassifierONNX class. This multiclassifier is capable of loading pre-trained PyTorch models specified by a modelFile string as follows:

#include \"PhysicsTools/ONNXRuntime/interface/ONNXRuntime.h\"\n\nstd::unique_ptr<cms::Ort::ONNXRuntime> model;\nOrt::SessionOptions* session_options = new Ort::SessionOptions();\nsession_options->SetIntraOpNumThreads(1);\nmodel = std::make_unique<cms::Ort::ONNXRuntime>(modelFile, session_options);\n
"},{"location":"inference/pytorch.html#via-triton","title":"Via Triton","text":"

Coprocessors (GPUs, FPGAs, etc.) are frequently used to accelerate ML operations such as inference and training. In the 'as-a-service' paradigm, users can access cloud-based applications through lightweight client inferfaces. The Services for Optimized Network Inference on Coprocessors (SONIC) framework implements this paradigm in CMSSW, allowing the optimal integration of GPUs into event processing workflows. One powerful implementation of SONIC is the the NVIDIA Triton Inference Server, which is flexible with respect to ML framework, storage source, and hardware infrastructure. For more details, see the corresponding NVIDIA developer blog entry.

A Graph Attention Network (GAN) is available via Triton in CMSSW, and can be accessed here: https://github.com/cms-sw/cmssw/tree/master/HeterogeneousCore/SonicTriton/test

"},{"location":"inference/pytorch.html#training-tips","title":"Training Tips","text":"
  • When instantiating a DataLoader, shuffle=True should be enabled for training data but not for validation and testing data. At each training epoch, this will vary the order of data objects in each batch; accordingly, it is not efficient to load the full dataset (in its original ordering) into GPU memory before training. Instead, enable num_workers>1; this allows the DataLoader to load batches to the GPU as they're prepared. Note that this launches muliple threads on the CPU. For more information, see a corresponding discussion in the PyTorch forum.
"},{"location":"inference/sonic_triton.html","title":"Service-based inference with Triton/Sonic","text":"

This page is still under construction. For the moment, please see the Sonic+Triton tutorial given as part of the Machine Learning HATS@LPC 2021.

  • Link to Indico agenda
  • Slides
  • Exercise twiki
"},{"location":"inference/standalone.html","title":"Standalone framework","text":"

Todo.

Idea: Working w/ TF+ROOT standalone (outside of CMSSW)

"},{"location":"inference/swan_aws.html","title":"SWAN + AWS","text":"

Todo.

Ideas: best practices cost model instance priving need to log out monitoring madatory

"},{"location":"inference/tensorflow1.html","title":"Direct inference with TensorFlow 1","text":"

While it is technically still possible to use TensorFlow 1, this version of TensorFlow is quite old and is no longer supported by CMSSW. We highly recommend that you update your model to TensorFlow 2 and follow the integration guide in the Inference/Direct inference/TensorFlow 2 documentation.

"},{"location":"inference/tensorflow2.html","title":"Direct inference with TensorFlow 2","text":"

TensorFlow 2 is available since CMSSW_11_1_X (cmssw#28711, cmsdist#5525). The integration into the software stack can be found in cmsdist/tensorflow.spec and the interface is located in cmssw/PhysicsTools/TensorFlow.

"},{"location":"inference/tensorflow2.html#available-versions","title":"Available versions","text":"Python 3 on el8Python 3 on slc7Python 2 on slc7 TensorFlow el8_amd64_gcc10 el8_amd64_gcc11 v2.6.0 \u2265 CMSSW_12_3_4 - v2.6.4 \u2265 CMSSW_12_5_0 \u2265 CMSSW_12_5_0 TensorFlow slc7_amd64_gcc900 slc7_amd64_gcc10 slc7_amd64_gcc11 v2.1.0 \u2265 CMSSW_11_1_0 - - v2.3.1 \u2265 CMSSW_11_2_0 - - v2.4.1 \u2265 CMSSW_11_3_0 - - v2.5.0 \u2265 CMSSW_12_0_0 \u2265 CMSSW_12_0_0 - v2.6.0 \u2265 CMSSW_12_1_0 \u2265 CMSSW_12_1_0 \u2265 CMSSW_12_3_0 v2.6.4 - \u2265 CMSSW_12_5_0 \u2265 CMSSW_13_0_0 TensorFlow slc7_amd64_gcc900 v2.1.0 \u2265 CMSSW_11_1_0 v2.3.1 \u2265 CMSSW_11_2_0

At this time, only CPU support is provided. While GPU support is generally possible, it is currently disabled due to some interference with production workflows but will be enabled once they are resolved.

"},{"location":"inference/tensorflow2.html#software-setup","title":"Software setup","text":"

To run the examples shown below, create a mininmal inference setup with the following snippet. Adapt the SCRAM_ARCH according to your operating system and desired compiler.

export SCRAM_ARCH=\"el8_amd64_gcc11\"\nexport CMSSW_VERSION=\"CMSSW_12_6_0\"\n\nsource \"/cvmfs/cms.cern.ch/cmsset_default.sh\" \"\"\n\ncmsrel \"${CMSSW_VERSION}\"\ncd \"${CMSSW_VERSION}/src\"\n\ncmsenv\nscram b\n

Below, the cmsml Python package is used to convert models from TensorFlow objects (tf.function's or Keras models) to protobuf graph files (documentation). It should be available after executing the commands above. You can check its version via

python -c \"import cmsml; print(cmsml.__version__)\"\n

and compare to the released tags. If you want to install a newer version from either the master branch of the cmsml repository or the Python package index (PyPI), you can simply do that via pip.

masterPyPI
# into your user directory (usually ~/.local)\npip install --upgrade --user git+https://github.com/cms-ml/cmsml\n\n# _or_\n\n# into a custom directory\npip install --upgrade --prefix \"CUSTOM_DIRECTORY\" git+https://github.com/cms-ml/cmsml\n
# into your user directory (usually ~/.local)\npip install --upgrade --user cmsml\n\n# _or_\n\n# into a custom directory\npip install --upgrade --prefix \"CUSTOM_DIRECTORY\" cmsml\n
"},{"location":"inference/tensorflow2.html#saving-your-model","title":"Saving your model","text":"

After successfully training, you should save your model in a protobuf graph file which can be read by the interface in CMSSW. Naturally, you only want to save that part of your model that is required to run the network prediction, i.e., it should not contain operations related to model training or loss functions (unless explicitely required). Also, to reduce the memory footprint and to accelerate the inference, variables should be converted to constant tensors. Both of these model transformations are provided by the cmsml package.

Instructions on how to transform and save your model are shown below, depending on whether you use Keras or plain TensorFlow with tf.function's.

Kerastf.function

The code below saves a Keras Model instance as a protobuf graph file using cmsml.tensorflow.save_graph. In order for Keras to built the internal graph representation before saving, make sure to either compile the model, or pass an input_shape to the first layer:

# coding: utf-8\n\nimport tensorflow as tf\nimport tf.keras.layers as layers\nimport cmsml\n\n# define your model\nmodel = tf.keras.Sequential()\nmodel.add(layers.InputLayer(input_shape=(10,), name=\"input\"))\nmodel.add(layers.Dense(100, activation=\"tanh\"))\nmodel.add(layers.Dense(3, activation=\"softmax\", name=\"output\"))\n\n# train it\n...\n\n# convert to binary (.pb extension) protobuf\n# with variables converted to constants\ncmsml.tensorflow.save_graph(\"graph.pb\", model, variables_to_constants=True)\n

Following the Keras naming conventions for certain layers, the input will be named \"input\" while the output is named \"sequential/output/Softmax\". To cross check the names, you can save the graph in text format by using the extension \".pb.txt\".

Let's consider you write your network model in a single tf.function.

# coding: utf-8\n\nimport tensorflow as tf\nimport cmsml\n\n# define the model\n@tf.function\ndef model(x):\n    # lift variable initialization to the lowest context so they are\n    # not re-initialized on every call (eager calls or signature tracing)\n    with tf.init_scope():\n        W = tf.Variable(tf.ones([10, 1]))\n        b = tf.Variable(tf.ones([1]))\n\n    # define your \"complex\" model here\n    h = tf.add(tf.matmul(x, W), b)\n    y = tf.tanh(h, name=\"y\")\n\n    return y\n

In TensorFlow terms, the model function is polymorphic - it accepts different types of the input tensor x (tf.float32, tf.float64, ...). For each type, TensorFlow will create a concrete function with an associated tf.Graph object. This mechanism is referred to as signature tracing. For deeper insights into tf.function, the concepts of signature tracing, polymorphic and concrete functions, see the guide on Better performance with tf.function.

To save the model as a protobuf graph file, you explicitely need to create a concrete function. However, this is fairly easy once you know the exact type and shape of all input arguments.

# create a concrete function\ncmodel = model.get_concrete_function(\n    tf.TensorSpec(shape=[2, 10], dtype=tf.float32),\n)\n\n# convert to binary (.pb extension) protobuf\n# with variables converted to constants\ncmsml.tensorflow.save_graph(\"graph.pb\", cmodel, variables_to_constants=True)\n

The input will be named \"x\" while the output is named \"y\". To cross check the names, you can save the graph in text format by using the extension \".pb.txt\".

Different method: Frozen signatures

Instead of creating a polymorphic tf.function and extracting a concrete one in a second step, you can directly define an input signature upon definition.

@tf.function(input_signature=(tf.TensorSpec(shape=[2, 10], dtype=tf.float32),))\ndef model(x):\n    ...\n

This disables signature tracing since the input signature is frozen. However, you can directly pass it to cmsml.tensorflow.save_graph.

"},{"location":"inference/tensorflow2.html#inference-in-cmssw","title":"Inference in CMSSW","text":"

The inference can be implemented to run in a single thread. In general, this does not mean that the module cannot be executed with multiple threads (cmsRun --numThreads <N> <CFG_FILE>), but rather that its performance in terms of evaluation time and especially memory consumption is likely to be suboptimal. Therefore, for modules to be integrated into CMSSW, the multi-threaded implementation is strongly recommended.

"},{"location":"inference/tensorflow2.html#cmssw-module-setup","title":"CMSSW module setup","text":"

If you aim to use the TensorFlow interface in a CMSSW plugin, make sure to include

<use name=\"PhysicsTools/TensorFlow\" />\n\n<flags EDM_PLUGIN=\"1\" />\n

in your plugins/BuildFile.xml file. If you are using the interface inside the src/ or interface/ directory of your module, make sure to create a global BuildFile.xml file next to theses directories, containing (at least):

<use name=\"PhysicsTools/TensorFlow\" />\n\n<export>\n<lib name=\"1\" />\n</export>\n
"},{"location":"inference/tensorflow2.html#single-threaded-inference","title":"Single-threaded inference","text":"

Despite tf.Session being removed in the Python interface as of TensorFlow 2, the concepts of

  • Graph's, containing the constant computational structure and trained variables of your model,
  • Session's, handling execution and data exchange, and
  • the separation between them

live on in the C++ interface. Thus, the overall inference approach is 1) include the interface, 2) initialize Graph and session, 3) per event create input tensors and run the inference, and 4) cleanup.

"},{"location":"inference/tensorflow2.html#1-includes","title":"1. Includes","text":"
#include \"PhysicsTools/TensorFlow/interface/TensorFlow.h\"\n#include \"FWCore/Framework/interface/one/EDAnalyzer.h\"\n// further framework includes\n...\n
"},{"location":"inference/tensorflow2.html#2-initialize-objects","title":"2. Initialize objects","text":"
// configure logging to show warnings (see table below)\ntensorflow::setLogging(\"2\");\n\n// load the graph definition\ntensorflow::GraphDef* graphDef = tensorflow::loadGraphDef(\"/path/to/constantgraph.pb\");\n\n// create a session\ntensorflow::Session* session = tensorflow::createSession(graphDef);\n
"},{"location":"inference/tensorflow2.html#3-inference","title":"3. Inference","text":"
// create an input tensor\n// (example: single batch of 10 values)\ntensorflow::Tensor input(tensorflow::DT_FLOAT, { 1, 10 });\n\n\n// fill the tensor with your input data\n// (example: just fill consecutive values)\nfor (size_t i = 0; i < 10; i++) {\ninput.matrix<float>()(0, i) = float(i);\n}\n\n// run the evaluation\nstd::vector<tensorflow::Tensor> outputs;\ntensorflow::run(session, { { \"input\", input } }, { \"output\" }, &outputs);\n\n// process the output tensor\n// (example: print the 5th value of the 0th (the only) example)\nstd::cout << outputs[0].matrix<float>()(0, 5) << std::endl;\n// -> float\n
"},{"location":"inference/tensorflow2.html#4-cleanup","title":"4. Cleanup","text":"
tensorflow::closeSession(session);\ndelete graphDef;\n
"},{"location":"inference/tensorflow2.html#full-example","title":"Full example","text":"Click to expand

The example assumes the following directory structure:

MySubsystem/MyModule/\n\u2502\n\u251c\u2500\u2500 plugins/\n\u2502   \u251c\u2500\u2500 MyPlugin.cpp\n\u2502   \u2514\u2500\u2500 BuildFile.xml\n\u2502\n\u251c\u2500\u2500 test/\n\u2502   \u2514\u2500\u2500 my_plugin_cfg.py\n\u2502\n\u2514\u2500\u2500 data/\n    \u2514\u2500\u2500 graph.pb\n
plugins/MyPlugin.cppplugins/BuildFile.xmltest/my_plugin_cfg.py
/*\n * Example plugin to demonstrate the direct single-threaded inference with TensorFlow 2.\n */\n\n#include <memory>\n\n#include \"FWCore/Framework/interface/Event.h\"\n#include \"FWCore/Framework/interface/Frameworkfwd.h\"\n#include \"FWCore/Framework/interface/MakerMacros.h\"\n#include \"FWCore/Framework/interface/one/EDAnalyzer.h\"\n#include \"FWCore/ParameterSet/interface/ParameterSet.h\"\n#include \"PhysicsTools/TensorFlow/interface/TensorFlow.h\"\n\nclass MyPlugin : public edm::one::EDAnalyzer<> {\npublic:\nexplicit MyPlugin(const edm::ParameterSet&);\n~MyPlugin(){};\n\nstatic void fillDescriptions(edm::ConfigurationDescriptions&);\n\nprivate:\nvoid beginJob();\nvoid analyze(const edm::Event&, const edm::EventSetup&);\nvoid endJob();\n\nstd::string graphPath_;\nstd::string inputTensorName_;\nstd::string outputTensorName_;\n\ntensorflow::GraphDef* graphDef_;\ntensorflow::Session* session_;\n};\n\nvoid MyPlugin::fillDescriptions(edm::ConfigurationDescriptions& descriptions) {\n// defining this function will lead to a *_cfi file being generated when compiling\nedm::ParameterSetDescription desc;\ndesc.add<std::string>(\"graphPath\");\ndesc.add<std::string>(\"inputTensorName\");\ndesc.add<std::string>(\"outputTensorName\");\ndescriptions.addWithDefaultLabel(desc);\n}\n\nMyPlugin::MyPlugin(const edm::ParameterSet& config)\n: graphPath_(config.getParameter<std::string>(\"graphPath\")),\ninputTensorName_(config.getParameter<std::string>(\"inputTensorName\")),\noutputTensorName_(config.getParameter<std::string>(\"outputTensorName\")),\ngraphDef_(nullptr),\nsession_(nullptr) {\n// set tensorflow log level to warning\ntensorflow::setLogging(\"2\");\n}\n\nvoid MyPlugin::beginJob() {\n// load the graph\ngraphDef_ = tensorflow::loadGraphDef(graphPath_);\n\n// create a new session and add the graphDef\nsession_ = tensorflow::createSession(graphDef_);\n}\n\nvoid MyPlugin::endJob() {\n// close the session\ntensorflow::closeSession(session_);\n\n// delete the graph\ndelete graphDef_;\ngraphDef_ = nullptr;\n}\n\nvoid MyPlugin::analyze(const edm::Event& event, const edm::EventSetup& setup) {\n// define a tensor and fill it with range(10)\ntensorflow::Tensor input(tensorflow::DT_FLOAT, {1, 10});\nfor (size_t i = 0; i < 10; i++) {\ninput.matrix<float>()(0, i) = float(i);\n}\n\n// define the output and run\nstd::vector<tensorflow::Tensor> outputs;\ntensorflow::run(session_, {{inputTensorName_, input}}, {outputTensorName_}, &outputs);\n\n// print the output\nstd::cout << \" -> \" << outputs[0].matrix<float>()(0, 0) << std::endl << std::endl;\n}\n\nDEFINE_FWK_MODULE(MyPlugin);\n
<use name=\"FWCore/Framework\" />\n<use name=\"FWCore/PluginManager\" />\n<use name=\"FWCore/ParameterSet\" />\n<use name=\"PhysicsTools/TensorFlow\" />\n\n<flags EDM_PLUGIN=\"1\" />\n
# coding: utf-8\n\nimport os\n\nimport FWCore.ParameterSet.Config as cms\nfrom FWCore.ParameterSet.VarParsing import VarParsing\n\n\n# get the data/ directory\nthisdir = os.path.dirname(os.path.abspath(__file__))\ndatadir = os.path.join(os.path.dirname(thisdir), \"data\")\n\n# setup minimal options\noptions = VarParsing(\"python\")\noptions.setDefault(\"inputFiles\", \"root://xrootd-cms.infn.it//store/mc/RunIISummer20UL17MiniAODv2/TTToSemiLeptonic_TuneCP5_13TeV-powheg-pythia8/MINIAODSIM/106X_mc2017_realistic_v9-v1/00000/005708B7-331C-904E-88B9-189011E6C9DD.root\")  # noqa\noptions.parseArguments()\n\n# define the process to run\nprocess = cms.Process(\"TEST\")\n\n# minimal configuration\nprocess.load(\"FWCore.MessageService.MessageLogger_cfi\")\nprocess.MessageLogger.cerr.FwkReport.reportEvery = 1\nprocess.maxEvents = cms.untracked.PSet(\n    input=cms.untracked.int32(10),\n)\nprocess.source = cms.Source(\n    \"PoolSource\",\n    fileNames=cms.untracked.vstring(options.inputFiles),\n)\n\n# process options\nprocess.options = cms.untracked.PSet(\n    allowUnscheduled=cms.untracked.bool(True),\n    wantSummary=cms.untracked.bool(True),\n)\n\n# setup MyPlugin by loading the auto-generated cfi (see MyPlugin.fillDescriptions)\nprocess.load(\"MySubsystem.MyModule.myPlugin_cfi\")\nprocess.myPlugin.graphPath = cms.string(os.path.join(datadir, \"graph.pb\"))\nprocess.myPlugin.inputTensorName = cms.string(\"input\")\nprocess.myPlugin.outputTensorName = cms.string(\"output\")\n\n# define what to run in the path\nprocess.p = cms.Path(process.myPlugin)\n
"},{"location":"inference/tensorflow2.html#multi-threaded-inference","title":"Multi-threaded inference","text":"

Compared to the single-threaded implementation above, the multi-threaded version has one major difference: both the Graph and the Session are no longer members of a particular module instance, but rather shared between all instances in all threads. See the documentation on the C++ interface of stream modules for details.

Recommendation updated

The previous recommendation stated that the Session is not constant and thus, should not be placed in the global cache, but rather created once per stream module instance. However, it was discovered that, although not explicitely declared as constant in the tensorflow::run() / Session::run() interface, the session is actually not changed during evaluation and can be treated as being effectively constant.

As a result, it is safe to move it to the global cache, next to the Graph object. The TensorFlow interface in CMSSW was adjusted in order to accept const objects in cmssw#40161.

Thus, the overall inference approach is 1) include the interface, 2) let your plugin inherit from edm::stream::EDAnalyzerasdasd and declare the GlobalCache, 3) store in cconst Session*, pointing to the cached session, and 4) per event create input tensors and run the inference.

"},{"location":"inference/tensorflow2.html#1-includes_1","title":"1. Includes","text":"
#include \"PhysicsTools/TensorFlow/interface/TensorFlow.h\"\n#include \"FWCore/Framework/interface/stream/EDAnalyzer.h\"\n// further framework includes\n...\n

Note that stream/EDAnalyzer.h is included rather than one/EDAnalyzer.h.

"},{"location":"inference/tensorflow2.html#2-define-and-use-the-global-cache","title":"2. Define and use the global cache","text":"

The cache definition is done by declaring a simple struct. However, for the purpose of just storing a graph and a session object, a so-called tensorflow::SessionCache struct is already provided centrally. It was added in cmssw#40284 and its usage is shown in the following. In case the tensorflow::SessionCache is not (yet) available in your version of CMSSW, expand the \"Custom cache struct\" section below.

Use it in the edm::GlobalCache template argument and adjust the plugin accordingly.

class MyPlugin : public edm::stream::EDAnalyzer<edm::GlobalCache<tensorflow::SessionCache>> {\npublic:\nexplicit GraphLoadingMT(const edm::ParameterSet&, const tensorflow::SessionCache*);\n~GraphLoadingMT();\n\n// an additional static method for initializing the global cache\nstatic std::unique_ptr<tensorflow::SessionCache> initializeGlobalCache(const edm::ParameterSet&);\nstatic void globalEndJob(const CacheData*);\n...\n

Implement initializeGlobalCache to control the behavior of how the cache object is created. The destructor of tensorflow::SessionCache already handles the closing of the session itself and the deletion of all objects.

std::unique_ptr<tensorflow::SessionCache> MyPlugin::initializeGlobalCache(const edm::ParameterSet& config) {\nstd::string graphPath = edm::FileInPath(params.getParameter<std::string>(\"graphPath\")).fullPath();\nreturn std::make_unique<tensorflow::SessionCache>(graphPath);\n}\n
Custom cache struct
struct MyCache {\nMyCache() : {\n}\n\nstd::atomic<tensorflow::GraphDef*> graph;\nstd::atomic<tensorflow::Session*> session;\n};\n

Use it in the edm::GlobalCache template argument and adjust the plugin accordingly.

class MyPlugin : public edm::stream::EDAnalyzer<edm::GlobalCache<CacheData>> {\npublic:\nexplicit GraphLoadingMT(const edm::ParameterSet&, const CacheData*);\n~GraphLoadingMT();\n\n// two additional static methods for handling the global cache\nstatic std::unique_ptr<CacheData> initializeGlobalCache(const edm::ParameterSet&);\nstatic void globalEndJob(const CacheData*);\n...\n

Implement initializeGlobalCache and globalEndJob to control the behavior of how the cache object is created and destroyed.

See the full example below for more details.

"},{"location":"inference/tensorflow2.html#3-initialize-objects","title":"3. Initialize objects","text":"

In your module constructor, you can get a pointer to the constant session to perform model evaluation during the event loop.

// declaration in header\nconst tensorflow::Session* _session;\n\n// get a pointer to the const session stored in the cache in the constructor init\nMyPlugin::MyPlugin(const edm::ParameterSet& config,  const tensorflow::SessionCache* cache)\n: session_(cache->getSession()) {\n...\n}\n
"},{"location":"inference/tensorflow2.html#4-inference","title":"4. Inference","text":"
// create an input tensor\n// (example: single batch of 10 values)\ntensorflow::Tensor input(tensorflow::DT_FLOAT, { 1, 10 });\n\n\n// fill the tensor with your input data\n// (example: just fill consecutive values)\nfor (size_t i = 0; i < 10; i++) {\ninput.matrix<float>()(0, i) = float(i);\n}\n\n// define the output\nstd::vector<tensorflow::Tensor> outputs;\n\n// evaluate\n// note: in case this line causes the compiler to complain about the const'ness of the session_ in\n//       this call, your CMSSW version might not yet support passing a const session, so in this\n//       case, pass \"const_cast<tensorflow::Session*>(session_)\"\ntensorflow::run(session_, { { inputTensorName, input } }, { outputTensorName }, &outputs);\n\n// process the output tensor\n// (example: print the 5th value of the 0th (the only) example)\nstd::cout << outputs[0].matrix<float>()(0, 5) << std::endl;\n// -> float\n

Note

If the TensorFlow interface in your CMSSW release does not yet accept const sessions, line 19 in the example above will cause an error during compilation. In this case, replace session_ in that line to

const_cast<tensorflow::Session*>(session_)\n
"},{"location":"inference/tensorflow2.html#full-example_1","title":"Full example","text":"Click to expand

The example assumes the following directory structure:

MySubsystem/MyModule/\n\u2502\n\u251c\u2500\u2500 plugins/\n\u2502   \u251c\u2500\u2500 MyPlugin.cpp\n\u2502   \u2514\u2500\u2500 BuildFile.xml\n\u2502\n\u251c\u2500\u2500 test/\n\u2502   \u2514\u2500\u2500 my_plugin_cfg.py\n\u2502\n\u2514\u2500\u2500 data/\n    \u2514\u2500\u2500 graph.pb\n
plugins/MyPlugin.cppplugins/BuildFile.xmltest/my_plugin_cfg.py
/*\n * Example plugin to demonstrate the direct multi-threaded inference with TensorFlow 2.\n */\n\n#include <memory>\n\n#include \"FWCore/Framework/interface/Event.h\"\n#include \"FWCore/Framework/interface/Frameworkfwd.h\"\n#include \"FWCore/Framework/interface/MakerMacros.h\"\n#include \"FWCore/Framework/interface/stream/EDAnalyzer.h\"\n#include \"FWCore/ParameterSet/interface/ParameterSet.h\"\n#include \"PhysicsTools/TensorFlow/interface/TensorFlow.h\"\n\n// put a tensorflow::SessionCache into the global cache structure\n// the session cache wraps both a tf graph and a tf session instance and also handles their deletion\nclass MyPlugin : public edm::stream::EDAnalyzer<edm::GlobalCache<tensorflow::SessionCache>> {\npublic:\nexplicit MyPlugin(const edm::ParameterSet&, const tensorflow::SessionCache*);\n~MyPlugin(){};\n\nstatic void fillDescriptions(edm::ConfigurationDescriptions&);\n\n// an additional static method for initializing the global cache\nstatic std::unique_ptr<tensorflow::SessionCache> initializeGlobalCache(const edm::ParameterSet&);\n\nprivate:\nvoid beginJob();\nvoid analyze(const edm::Event&, const edm::EventSetup&);\nvoid endJob();\n\nstd::string inputTensorName_;\nstd::string outputTensorName_;\n\n// a pointer to the session created by the global session cache\nconst tensorflow::Session* session_;\n};\n\nstd::unique_ptr<tensorflow::SessionCache> MyPlugin::initializeGlobalCache(const edm::ParameterSet& params) {\n// this method is supposed to create, initialize and return a SessionCache instance\nstd::string graphPath = edm::FileInPath(params.getParameter<std::string>(\"graphPath\")).fullPath();\n// Setup the TF backend by configuration\nif (params.getParameter<std::string>(\"tf_backend\") == \"cuda\"){\ntensorflow::Options options { tensorflow::Backend::cuda};\n}else {\ntensorflow::Options options { tensorflow::Backend::cpu};\n}\nreturn std::make_unique<tensorflow::SessionCache>(graphPath, options);\n}\n\nvoid MyPlugin::fillDescriptions(edm::ConfigurationDescriptions& descriptions) {\n// defining this function will lead to a *_cfi file being generated when compiling\nedm::ParameterSetDescription desc;\ndesc.add<std::string>(\"graphPath\");\ndesc.add<std::string>(\"inputTensorName\");\ndesc.add<std::string>(\"outputTensorName\");\ndescriptions.addWithDefaultLabel(desc);\n}\n\nMyPlugin::MyPlugin(const edm::ParameterSet& config,  const tensorflow::SessionCache* cache)\n: inputTensorName_(config.getParameter<std::string>(\"inputTensorName\")),\noutputTensorName_(config.getParameter<std::string>(\"outputTensorName\")),\nsession_(cache->getSession()) {}\n\nvoid MyPlugin::beginJob() {}\n\nvoid MyPlugin::endJob() {\n// close the session\ntensorflow::closeSession(session_);\n}\n\nvoid MyPlugin::analyze(const edm::Event& event, const edm::EventSetup& setup) {\n// define a tensor and fill it with range(10)\ntensorflow::Tensor input(tensorflow::DT_FLOAT, {1, 10});\nfor (size_t i = 0; i < 10; i++) {\ninput.matrix<float>()(0, i) = float(i);\n}\n\n// define the output\nstd::vector<tensorflow::Tensor> outputs;\n\n// evaluate\n// note: in case this line causes the compile to complain about the const'ness of the session_ in\n//       this call, your CMSSW version might not yet support passing a const session, so in this\n//       case, pass \"const_cast<tensorflow::Session*>(session_)\"\ntensorflow::run(session_, {{inputTensorName_, input}}, {outputTensorName_}, &outputs);\n\n// print the output\nstd::cout << \" -> \" << outputs[0].matrix<float>()(0, 0) << std::endl << std::endl;\n}\n\nDEFINE_FWK_MODULE(MyPlugin);\n
<use name=\"FWCore/Framework\" />\n<use name=\"FWCore/PluginManager\" />\n<use name=\"FWCore/ParameterSet\" />\n<use name=\"PhysicsTools/TensorFlow\" />\n\n<flags EDM_PLUGIN=\"1\" />\n
# coding: utf-8\n\nimport os\n\nimport FWCore.ParameterSet.Config as cms\nfrom FWCore.ParameterSet.VarParsing import VarParsing\n\n\n# get the data/ directory\nthisdir = os.path.dirname(os.path.abspath(__file__))\ndatadir = os.path.join(os.path.dirname(thisdir), \"data\")\n\n# setup minimal options\noptions = VarParsing(\"python\")\noptions.setDefault(\"inputFiles\", \"root://xrootd-cms.infn.it//store/mc/RunIISummer20UL17MiniAODv2/TTToSemiLeptonic_TuneCP5_13TeV-powheg-pythia8/MINIAODSIM/106X_mc2017_realistic_v9-v1/00000/005708B7-331C-904E-88B9-189011E6C9DD.root\")  # noqa\noptions.parseArguments()\n\n# define the process to run\nprocess = cms.Process(\"TEST\")\n\n# minimal configuration\nprocess.load(\"FWCore.MessageService.MessageLogger_cfi\")\nprocess.MessageLogger.cerr.FwkReport.reportEvery = 1\nprocess.maxEvents = cms.untracked.PSet(\n    input=cms.untracked.int32(10),\n)\nprocess.source = cms.Source(\n    \"PoolSource\",\n    fileNames=cms.untracked.vstring(options.inputFiles),\n)\n\n# process options\nprocess.options = cms.untracked.PSet(\n    allowUnscheduled=cms.untracked.bool(True),\n    wantSummary=cms.untracked.bool(True),\n)\n\n# setup MyPlugin by loading the auto-generated cfi (see MyPlugin.fillDescriptions)\nprocess.load(\"MySubsystem.MyModule.myPlugin_cfi\")\nprocess.myPlugin.graphPath = cms.string(os.path.join(datadir, \"graph.pb\"))\nprocess.myPlugin.inputTensorName = cms.string(\"input\")\nprocess.myPlugin.outputTensorName = cms.string(\"output\")\n\n# define what to run in the path\nprocess.p = cms.Path(process.myPlugin)\n
"},{"location":"inference/tensorflow2.html#gpu-backend","title":"GPU backend","text":"

By default the TensorFlow sessions get created for CPU running. Since CMSSW_13_1_X the GPU backend for TensorFlow is available in the cmssw release.

Minimal changes are needed in the inference code to move the model on the GPU. A tensorflow::Options struct is available to setup the backend.

tensorflow::Options options { tensorflow::Backend::cuda};\n\n# Initialize the cache\ntensorflow::SessionCache cache(pbFile, options);\n# or a single session\nconst tensorflow::Session* session = tensorflow::createSession(graphDef, options);\n

CMSSW modules should add an options in the PSets of the producers and analyzers to configure on the fly the TensorFlow backend for the sessions created by the plugins.

"},{"location":"inference/tensorflow2.html#optimization","title":"Optimization","text":"

Depending on the use case, the following approaches can optimize the inference performance. It could be worth checking them out in your algorithm.

Further optimization approaches can be found in the integration checklist.

"},{"location":"inference/tensorflow2.html#reusing-tensors","title":"Reusing tensors","text":"

In some cases, instead of creating new input tensors for each inference call, you might want to store input tensors as members of your plugin. This is of course possible if you know its exact shape a-prioro and comes with the cost of keeping the tensor in memory for the lifetime of your module instance.

You can use

tensor.flat<float>().setZero();\n

to reset the values of your tensor prior to each call.

"},{"location":"inference/tensorflow2.html#tensor-data-access-via-pointers","title":"Tensor data access via pointers","text":"

As shown in the examples above, tensor data can be accessed through methods such as flat<type>() or matrix<type>() which return objects that represent the underlying data in the requested structure (tensorflow::Tensor C++ API). To read and manipulate particular elements, you can directly call this object with the coordinates of an element.

// matrix returns a 2D representation\n// set element (b,i) to f\ntensor.matrix<float>()(b, i) = float(f);\n

However, doing this for a large input tensor might entail some overhead. Since the data is actually contiguous in memory (C-style \"row-major\" memory ordering), a faster (though less explicit) way of interacting with tensor data is using a pointer.

// get the pointer to the first tensor element\nfloat* d = tensor.flat<float>().data();\n

Now, the tensor data can be filled using simple and fast pointer arithmetic.

// fill tensor data using pointer arithmethic\n// memory ordering is row-major, so the most outer loop corresponds dimension 0\nfor (size_t b = 0; b < batchSize; b++) {\nfor (size_t i = 0; i < nFeatures; i++, d++) {  // note the d++\n*d = float(i);\n}\n}\n
"},{"location":"inference/tensorflow2.html#inter-and-intra-operation-parallelism","title":"Inter- and intra-operation parallelism","text":"

Debugging and local processing only

Parallelism between (inter) and within (intra) operations can greatly improve the inference performance. However, this allows TensorFlow to manage and schedule threads on its own, possibly interfering with the thread model inherent to CMSSW. For inference code that is to be officially integrated, you should avoid inter- and intra-op parallelism and rather adhere to the examples shown above.

You can configure the amount of inter- and infra-op threads via the second argument of the tensorflow::createSession method.

SimpleVerbose
tensorflow::Session* session = tensorflow::createSession(graphDef, nThreads);\n
tensorflow::SessionOptions sessionOptions;\nsessionOptions.config.set_intra_op_parallelism_threads(nThreads);\nsessionOptions.config.set_inter_op_parallelism_threads(nThreads);\n\ntensorflow::Session* session = tensorflow::createSession(graphDef, sessionOptions);\n

Then, when calling tensorflow::run, pass the internal name of the TensorFlow threadpool, i.e. \"tensorflow\", as the last argument.

std::vector<tensorflow::Tensor> outputs;\ntensorflow::run(\nsession,\n{ { inputTensorName, input } },\n{ outputTensorName },\n&outputs,\n\"tensorflow\"\n);\n
"},{"location":"inference/tensorflow2.html#miscellaneous","title":"Miscellaneous","text":""},{"location":"inference/tensorflow2.html#logging","title":"Logging","text":"

By default, TensorFlow logging is quite verbose. This can be changed by either setting the TF_CPP_MIN_LOG_LEVEL environment varibale before calling cmsRun, or within your code through tensorflow::setLogging(level).

Verbosity level TF_CPP_MIN_LOG_LEVEL debug \"0\" info \"1\" (default) warning \"2\" error \"3\" none \"4\"

Forwarding logs to the MessageLogger service is not possible yet.

"},{"location":"inference/tensorflow2.html#links-and-further-reading","title":"Links and further reading","text":"
  • cmsml package
  • CMSSW
    • TensorFlow interface documentation
    • TensorFlow interface header
    • CMSSW process options
    • C++ interface of stream modules
  • TensorFlow
    • TensorFlow 2 tutorial
    • tf.function
    • C++ API
    • tensorflow::Tensor
    • tensorflow::Operation
    • tensorflow::ClientSession
  • Keras
    • API

Authors: Marcel Rieger

"},{"location":"inference/tfaas.html","title":"TFaaS","text":""},{"location":"inference/tfaas.html#tensorflow-as-a-service","title":"TensorFlow as a Service","text":"

TensorFlow as a Service (TFaas) was developed as a general purpose service which can be deployed on any infrastruction from personal laptop, VM, to cloud infrastructure, inculding kubernetes/docker based ones. The main repository contains all details about the service, including install, end-to-end example, and demo.

For CERN users we already deploy TFaaS on the following URL: https://cms-tfaas.cern.ch

It can be used by CMS members using any HTTP based client. For example, here is a basic access from curl client:

curl -k https://cms-tfaas.cern.ch/models\n[\n  {\n    \"name\": \"luca\",\n    \"model\": \"prova.pb\",\n    \"labels\": \"labels.csv\",\n    \"options\": null,\n    \"inputNode\": \"dense_1_input\",\n    \"outputNode\": \"output_node0\",\n    \"description\": \"\",\n    \"timestamp\": \"2021-10-22 14:04:52.890554036 +0000 UTC m=+600537.976386186\"\n  },\n  {\n    \"name\": \"test_luca_1024\",\n    \"model\": \"saved_model.pb\",\n    \"labels\": \"labels.txt\",\n    \"options\": null,\n    \"inputNode\": \"dense_input_1:0\",\n    \"outputNode\": \"dense_3/Sigmoid:0\",\n    \"description\": \"\",\n    \"timestamp\": \"2021-10-22 14:04:52.890776518 +0000 UTC m=+600537.976608672\"\n  },\n  {\n    \"name\": \"vk\",\n    \"model\": \"model.pb\",\n    \"labels\": \"labels.txt\",\n    \"options\": null,\n    \"inputNode\": \"dense_1_input\",\n    \"outputNode\": \"output_node0\",\n    \"description\": \"\",\n    \"timestamp\": \"2021-10-22 14:04:52.890903234 +0000 UTC m=+600537.976735378\"\n  }\n]\n

The following APIs are available: - /upload to push your favorite TF model to TFaaS server either for Form or as tar-ball bundle, see examples below - /delete to delete your TF model from TFaaS server - /models to view existing TF models on TFaaS server - /predict/json to serve TF model predictions in JSON data-format - /predict/proto to serve TF model predictions in ProtoBuffer data-format - /predict/image to serve TF model predictions forimages in JPG/PNG formats

"},{"location":"inference/tfaas.html#look-up-your-favorite-model","title":"\u2780 look-up your favorite model","text":"

You may easily look-up your ML model from TFaaS server, e.g.

curl https://cms-tfaas.cern.ch/models\n# possible output may looks like this\n[\n  {\n    \"name\": \"luca\",\n    \"model\": \"prova.pb\",\n    \"labels\": \"labels.csv\",\n    \"options\": null,\n    \"inputNode\": \"dense_1_input\",\n    \"outputNode\": \"output_node0\",\n    \"description\": \"\",\n    \"timestamp\": \"2021-11-08 20:07:18.397487027 +0000 UTC m=+2091094.457327022\"\n  }\n  ...\n]\n
The provided /models API will list the name of the model, its file name, labels file, possible options, input and output nodes, description and proper timestamp when it was added to TFaaS repository

"},{"location":"inference/tfaas.html#upload-your-tf-model-to-tfaas-server","title":"\u2781 upload your TF model to TFaaS server","text":"

If your model is not in TFaaS server you may easily add it as following:

# example of image based model upload\ncurl -X POST https://cms-tfaas.cern.ch/upload\n-F 'name=ImageModel' -F 'params=@/path/params.json'\n-F 'model=@/path/tf_model.pb' -F 'labels=@/path/labels.txt'\n\n# example of TF pb file upload\ncurl -s -X POST https://cms-tfaas.cern.ch/upload \\\n    -F 'name=vk' -F 'params=@/path/params.json' \\\n    -F 'model=@/path/model.pb' -F 'labels=@/path/labels.txt'\n\n# example of bundle upload produce with Keras TF\n# here is our saved model area\nls model\nassets         saved_model.pb variables\n# we can create tarball and upload it to TFaaS via bundle end-point\ntar cfz model.tar.gz model\ncurl -X POST -H \"Content-Encoding: gzip\" \\\n             -H \"content-type: application/octet-stream\" \\\n             --data-binary @/path/models.tar.gz https://cms-tfaas.cern.ch/upload\n

"},{"location":"inference/tfaas.html#get-your-predictions","title":"\u2782 get your predictions","text":"

Finally, you may obtain predictions from your favorite model by using proper API, e.g.

# obtain predictions from your ImageModel\ncurl https://cms-tfaas.cern.ch/image -F 'image=@/path/file.png' -F 'model=ImageModel'\n\n# obtain predictions from your TF based model\ncat input.json\n{\"keys\": [...], \"values\": [...], \"model\":\"model\"}\n\n# call to get predictions from /json end-point using input.json\ncurl -s -X POST -H \"Content-type: application/json\" \\\n    -d@/path/input.json https://cms-tfaas.cern.ch/json\n

Fore more information please visit curl client page.

"},{"location":"inference/tfaas.html#tfaas-interface","title":"TFaaS interface","text":"

Clients communicate with TFaaS via HTTP protocol. See examples for Curl, Python and C++ clients.

"},{"location":"inference/tfaas.html#tfaas-benchmarks","title":"TFaaS benchmarks","text":"

Benchmark results on CentOS, 24 cores, 32GB of RAM serving DL NN with 42x128x128x128x64x64x1x1 architecture (JSON and ProtoBuffer formats show similar performance): - 400 req/sec for 100 concurrent clients, 1000 requests in total - 480 req/sec for 200 concurrent clients, 5000 requests in total

For more information please visit bencmarks page.

"},{"location":"inference/xgboost.html","title":"Direct inference with XGBoost","text":""},{"location":"inference/xgboost.html#general","title":"General","text":"

XGBoost is avaliable (at least) since CMSSW_9_2_4 cmssw#19377.

In CMSSW environment, XGBoost can be used via its Python API.

For UL era, there are different verisons available for different SCRAM_ARCH:

  1. For slc7_amd64_gcc700 and above, ver.0.80 is available.

  2. For slc7_amd64_gcc900 and above, ver.1.3.3 is available.

  3. Please note that different major versions have different behavior( See Caveat Session).

"},{"location":"inference/xgboost.html#existing-examples","title":"Existing Examples","text":"

There are some existing good examples of using XGBoost under CMSSW, as listed below:

  1. Offical sample for testing the integration of XGBoost library with CMSSW.

  2. Useful codes created by Dr. Huilin Qu for inference with existing trained model.

  3. C/C++ Interface for inference with existing trained model.

We will provide examples for both C/C++ interface and python interface of XGBoost under CMSSW environment.

"},{"location":"inference/xgboost.html#example-classification-of-points-from-joint-gaussian-distribution","title":"Example: Classification of points from joint-Gaussian distribution.","text":"

In this specific example, you will use XGBoost to classify data points generated from two 8-dimension joint-Gaussian distribution.

Feature Index 0 1 2 3 4 5 6 7 \u03bc1 1 2 3 4 5 6 7 8 \u03bc2 0 1.9 3.2 4.5 4.8 6.1 8.1 11 \u03c3\u00bd = \u03c3 1 1 1 1 1 1 1 1 |\u03bc1 - \u03bc2| / \u03c3 1 0.1 0.2 0.5 0.2 0.1 1.1 3

All generated data points for train(1:10000,2:10000) and test(1:1000,2:1000) are stored as Train_data.csv/Test_data.csv.

"},{"location":"inference/xgboost.html#preparing-model","title":"Preparing Model","text":"

The training process of a XGBoost model can be done outside of CMSSW. We provide a python script for illustration.

# importing necessary models\nimport numpy as np\nimport pandas as pd \nfrom xgboost import XGBClassifier # Or XGBRegressor for Logistic Regression\nimport matplotlib.pyplot as plt\nimport pandas as pd\n\n# specify parameters via map\nparam = {'n_estimators':50}\nxgb = XGBClassifier(param)\n\n# using Pandas.DataFrame data-format, other available format are XGBoost's DMatrix and numpy.ndarray\n\ntrain_data = pd.read_csv(\"path/to/the/data\") # The training dataset is code/XGBoost/Train_data.csv\n\ntrain_Variable = train_data['0', '1', '2', '3', '4', '5', '6', '7']\ntrain_Score = train_data['Type'] # Score should be integer, 0, 1, (2 and larger for multiclass)\n\ntest_data = pd.read_csv(\"path/to/the/data\") # The testing dataset is code/XGBoost/Test_data.csv\n\ntest_Variable = test_data['0', '1', '2', '3', '4', '5', '6', '7']\ntest_Score = test_data['Type']\n\n# Now the data are well prepared and named as train_Variable, train_Score and test_Variable, test_Score.\n\nxgb.fit(train_Variable, train_Score) # Training\n\nxgb.predict(test_Variable) # Outputs are integers\n\nxgb.predict_proba(test_Variable) # Output scores , output structre: [prob for 0, prob for 1,...]\n\nxgb.save_model(\"\\Path\\To\\Where\\You\\Want\\ModelName.model\") # Saving model\n
The saved model ModelName.model is thus available for python and C/C++ api to load. Please use the XGBoost major version consistently (see Caveat).

While training with data from different datasets, proper treatment of weights are necessary for better model performance. Please refer to Official Recommendation for more details.

"},{"location":"inference/xgboost.html#cc-usage-with-cmssw","title":"C/C++ Usage with CMSSW","text":"

To use a saved XGBoost model with C/C++ code, it is convenient to use the XGBoost's offical C api. Here we provide a simple example as following.

"},{"location":"inference/xgboost.html#module-setup","title":"Module setup","text":"

There is no official CMSSW interface for XGBoost while its library are placed in cvmfs of CMSSW. Thus we have to use the raw c_api as well as setting up the library manually.

  1. To run XGBoost's c_api within CMSSW framework, in addition to the following standard setup.
    export SCRAM_ARCH=\"slc7_amd64_gcc700\" # To use higher version, please switch to slc7_amd64_900\nexport CMSSW_VERSION=\"CMSSW_X_Y_Z\"\n\nsource /cvmfs/cms.cern.ch/cmsset_default.sh\n\ncmsrel \"$CMSSW_VERSION\"\ncd \"$CMSSW_VERSION/src\"\n\ncmsenv\nscram b\n
    The addtional effort is to add corresponding xml file(s) to $CMSSW_BASE/toolbox$CMSSW_BASE/config/toolbox/$SCRAM_ARCH/tools/selected/ for setting up XGBoost.
  1. For lower version (<1), add two xml files as below.

    xgboost.xml

     <tool name=\"xgboost\" version=\"0.80\">\n<lib name=\"xgboost\"/>\n<client>\n<environment name=\"LIBDIR\" default=\"/cvmfs/cms.cern.ch/$SCRAM_ARCH/external/py2-xgboost/0.80-ikaegh/lib/python2.7/site-packages/xgboost/lib\"/>\n<environment name=\"INCLUDE\" default=\"/cvmfs/cms.cern.ch/$SCRAM_ARCH/external/py2-xgboost/0.80-ikaegh/lib/python2.7/site-packages/xgboost/include/\"/>\n</client>\n<runtime name=\"ROOT_INCLUDE_PATH\" value=\"$INCLUDE\" type=\"path\"/>\n<runtime name=\"PATH\" value=\"$INCLUDE\" type=\"path\"/>\n<use name=\"rabit\"/>\n</tool>\n
    rabit.xml
     <tool name=\"rabit\" version=\"0.80\">\n<client>\n<environment name=\"INCLUDE\" default=\"/cvmfs/cms.cern.ch/$SCRAM_ARCH/external/py2-xgboost/0.80-ikaegh/lib/python2.7/site-packages/xgboost/rabit/include/\"/>\n</client>\n<runtime name=\"ROOT_INCLUDE_PATH\" value=\"$INCLUDE\" type=\"path\"/>\n<runtime name=\"PATH\" value=\"$INCLUDE\" type=\"path\"/>  </tool>\n
    Please note that the path in cvmfs is not fixed, one can list all available versions in the py2-xgboost directory and choose one to use.

  2. For higher version (>=1), and one xml file

    xgboost.xml

    <tool name=\"xgboost\" version=\"0.80\">\n<lib name=\"xgboost\"/>\n<client>\n<environment name=\"LIBDIR\" default=\"/cvmfs/cms.cern.ch/$SCRAM_ARCH/external/xgboost/1.3.3/lib64\"/>\n<environment name=\"INCLUDE\" default=\"/cvmfs/cms.cern.ch/$SCRAM_ARCH/external/xgboost/1.3.3/include/\"/>\n</client>\n<runtime name=\"ROOT_INCLUDE_PATH\" value=\"$INCLUDE\" type=\"path\"/>\n<runtime name=\"PATH\" value=\"$INCLUDE\" type=\"path\"/>  </tool>\n
    Also one has the freedom to choose the available xgboost version inside xgboost directory.

  1. After adding xml file(s), the following commands should be executed for setting up.

    1. For lower version (<1), use
      scram setup rabit\nscram setup xgboost\n
    2. For higher version (>=1), use
      scram setup xgboost\n
  2. For using XGBoost as a plugin of CMSSW, it is necessary to add

    <use name=\"xgboost\"/>\n<flags EDM_PLUGIN=\"1\"/>\n
    in your plugins/BuildFile.xml. If you are using the interface inside the src/ or interface/ directory of your module, make sure to create a global BuildFile.xml file next to theses directories, containing (at least):
    <use name=\"xgboost\"/>\n<export>\n<lib   name=\"1\"/>\n</export>\n

  3. The libxgboost.so would be too large to load for cmsRun job, please using the following commands for pre-loading:

    export LD_PRELOAD=$CMSSW_BASE/external/$SCRAM_ARCH/lib/libxgboost.so\n

"},{"location":"inference/xgboost.html#basic-usage-of-c-api","title":"Basic Usage of C API","text":"

In order to use c_api of XGBoost to load model and operate inference, one should construct necessaries objects:

  1. Files to include

    #include <xgboost/c_api.h> 

  2. BoosterHandle: worker of XGBoost

    // Declare Object\nBoosterHandle booster_;\n// Allocate memory in C style\nXGBoosterCreate(NULL,0,&booster_);\n// Load Model\nXGBoosterLoadModel(booster_,model_path.c_str()); // second argument should be a const char *.\n

  3. DMatrixHandle: handle to dmatrix, the data format of XGBoost

    float TestData[2000][8] // Suppose 2000 data points, each data point has 8 dimension\n// Assign data to the \"TestData\" 2d array ... \n// Declare object\nDMatrixHandle data_;\n// Allocate memory and use external float array to initialize\nXGDMatrixCreateFromMat((float *)TestData,2000,8,-1,&data_); // The first argument takes in float * namely 1d float array only, 2nd & 3rd: shape of input, 4th: value to replace missing ones\n

  4. XGBoosterPredict: function for inference

    bst_ulong outlen; // bst_ulong is a typedef of unsigned long\nconst float *f; // array to store predictions\nXGBoosterPredict(booster_,data_,0,0,&out_len,&f);// lower version API\n// XGBoosterPredict(booster_,data_,0,0,0,&out_len,&f);// higher version API\n/*\nlower version (ver.<1) API\nXGB_DLL int XGBoosterPredict(   \nBoosterHandle   handle,\nDMatrixHandle   dmat,\nint     option_mask, // 0 for normal output, namely reporting scores\nint     training, // 0 for prediction\nbst_ulong *     out_len,\nconst float **  out_result \n)\n\nhigher version (ver.>=1) API\nXGB_DLL int XGBoosterPredict(   \nBoosterHandle   handle,\nDMatrixHandle   dmat,\nint     option_mask, // 0 for normal output, namely reporting scores\nint ntree_limit, // how many trees for prediction, set to 0 means no limit\nint     training, // 0 for prediction\nbst_ulong *     out_len,\nconst float **  out_result \n)\n*/\n

"},{"location":"inference/xgboost.html#full-example","title":"Full Example","text":"Click to expand full example

The example assumes the following directory structure:

MySubsystem/MyModule/\n\u2502\n\u251c\u2500\u2500 plugins/\n\u2502   \u251c\u2500\u2500 XGBoostExample.cc\n\u2502   \u2514\u2500\u2500 BuildFile.xml\n\u2502\n\u251c\u2500\u2500 python/\n\u2502   \u2514\u2500\u2500 xgboost_cfg.py\n\u2502\n\u251c\u2500\u2500 toolbox/ (storing necessary xml(s) to be copied to toolbox/ of $CMSSW_BASE)\n\u2502   \u2514\u2500\u2500 xgboost.xml\n\u2502   \u2514\u2500\u2500 rabit.xml (lower version only)\n\u2502\n\u2514\u2500\u2500 data/\n    \u2514\u2500\u2500 Test_data.csv\n    \u2514\u2500\u2500 lowVer.model / highVer.model \n
Please also note that in order to operate inference in an event-by-event way, please put XGBoosterPredict in analyze rather than beginJob.

plugins/XGBoostExample.cc for lower version XGBoostplugins/BuildFile.xml for lower version XGBoostpython/xgboost_cfg.py for lower version XGBoostplugins/XGBoostExample.cc for higher version XGBoostplugins/BuildFile.xml for higher version XGBoostpython/xgboost_cfg.py for higher version XGBoost
// -*- C++ -*-\n//\n// Package:    XGB_Example/XGBoostExample\n// Class:      XGBoostExample\n//\n/**\\class XGBoostExample XGBoostExample.cc XGB_Example/XGBoostExample/plugins/XGBoostExample.cc\n\n Description: [one line class summary]\n\n Implementation:\n     [Notes on implementation]\n*/\n//\n// Original Author:  Qian Sitian\n//         Created:  Sat, 19 Jun 2021 08:38:51 GMT\n//\n//\n\n\n// system include files\n#include <memory>\n\n// user include files\n#include \"FWCore/Framework/interface/Frameworkfwd.h\"\n#include \"FWCore/Framework/interface/one/EDAnalyzer.h\"\n\n#include \"FWCore/Framework/interface/Event.h\"\n#include \"FWCore/Framework/interface/MakerMacros.h\"\n\n#include \"FWCore/ParameterSet/interface/ParameterSet.h\"\n#include \"FWCore/Utilities/interface/InputTag.h\"\n#include \"DataFormats/TrackReco/interface/Track.h\"\n#include \"DataFormats/TrackReco/interface/TrackFwd.h\"\n\n#include <xgboost/c_api.h>\n#include <vector>\n#include <tuple>\n#include <string>\n#include <iostream>\n#include <fstream>\n#include <sstream>\n\nusing namespace std;\n\nvector<vector<double>> readinCSV(const char* name){\nauto fin = ifstream(name);\nvector<vector<double>> floatVec;\nstring strFloat;\nfloat fNum;\nint counter = 0;\ngetline(fin,strFloat);\nwhile(getline(fin,strFloat))\n{\nstd::stringstream  linestream(strFloat);\nfloatVec.push_back(std::vector<double>());\nwhile(linestream>>fNum)\n{\nfloatVec[counter].push_back(fNum);\nif (linestream.peek() == ',')\nlinestream.ignore();\n}\n++counter;\n}\nreturn floatVec;\n}\n\n//\n// class declaration\n//\n\n// If the analyzer does not use TFileService, please remove\n// the template argument to the base class so the class inherits\n// from  edm::one::EDAnalyzer<>\n// This will improve performance in multithreaded jobs.\n\n\n\nclass XGBoostExample : public edm::one::EDAnalyzer<>  {\npublic:\nexplicit XGBoostExample(const edm::ParameterSet&);\n~XGBoostExample();\n\nstatic void fillDescriptions(edm::ConfigurationDescriptions& descriptions);\n\n\nprivate:\nvirtual void beginJob() ;\nvirtual void analyze(const edm::Event&, const edm::EventSetup&) ;\nvirtual void endJob() ;\n\n// ----------member data ---------------------------\n\nstd::string test_data_path;\nstd::string model_path;\n\n\n\n\n};\n\n//\n// constants, enums and typedefs\n//\n\n//\n// static data member definitions\n//\n\n//\n// constructors and destructor\n//\nXGBoostExample::XGBoostExample(const edm::ParameterSet& config):\ntest_data_path(config.getParameter<std::string>(\"test_data_path\")),\nmodel_path(config.getParameter<std::string>(\"model_path\"))\n{\n\n}\n\n\nXGBoostExample::~XGBoostExample()\n{\n\n// do anything here that needs to be done at desctruction time\n// (e.g. close files, deallocate resources etc.)\n\n}\n\n\n//\n// member functions\n//\n\nvoid\nXGBoostExample::analyze(const edm::Event& iEvent, const edm::EventSetup& iSetup)\n{\n}\n\n\nvoid\nXGBoostExample::beginJob()\n{\nBoosterHandle booster_;\nXGBoosterCreate(NULL,0,&booster_);\ncout<<\"Hello World No.2\"<<endl;\nXGBoosterLoadModel(booster_,model_path.c_str());\nunsigned long numFeature = 0;\ncout<<\"Hello World No.3\"<<endl;\nvector<vector<double>> TestDataVector = readinCSV(test_data_path.c_str());\ncout<<\"Hello World No.4\"<<endl;\nfloat TestData[2000][8];\ncout<<\"Hello World No.5\"<<endl;\nfor(unsigned i=0; (i < 2000); i++)\n{ for(unsigned j=0; (j < 8); j++)\n{\nTestData[i][j] = TestDataVector[i][j];\n//  cout<<TestData[i][j]<<\"\\t\";\n} //cout<<endl;\n}\ncout<<\"Hello World No.6\"<<endl;\nDMatrixHandle data_;\nXGDMatrixCreateFromMat((float *)TestData,2000,8,-1,&data_);\ncout<<\"Hello World No.7\"<<endl;\nbst_ulong out_len=0;\nconst float *f;\ncout<<out_len<<endl;\nauto ret=XGBoosterPredict(booster_, data_, 0,0,&out_len,&f);\ncout<<ret<<endl;\nfor (unsigned int i=0;i<2;i++)\nstd::cout <<  i << \"\\t\"<< f[i] << std::endl;\ncout<<\"Hello World No.8\"<<endl;\n}\n\nvoid\nXGBoostExample::endJob()\n{\n}\n\nvoid\nXGBoostExample::fillDescriptions(edm::ConfigurationDescriptions& descriptions) {\n//The following says we do not know what parameters are allowed so do no validation\n// Please change this to state exactly what you do use, even if it is no parameters\nedm::ParameterSetDescription desc;\ndesc.add<std::string>(\"test_data_path\");\ndesc.add<std::string>(\"model_path\");\ndescriptions.addWithDefaultLabel(desc);\n\n//Specify that only 'tracks' is allowed\n//To use, remove the default given above and uncomment below\n//ParameterSetDescription desc;\n//desc.addUntracked<edm::InputTag>(\"tracks\",\"ctfWithMaterialTracks\");\n//descriptions.addDefault(desc);\n}\n\n//define this as a plug-in\nDEFINE_FWK_MODULE(XGBoostExample);\n
<use name=\"FWCore/Framework\"/>\n<use name=\"FWCore/PluginManager\"/>\n<use name=\"FWCore/ParameterSet\"/>\n<use name=\"DataFormats/TrackReco\"/>\n<use name=\"xgboost\"/>\n<flags EDM_PLUGIN=\"1\"/>\n
# coding: utf-8\n\nimport os\n\nimport FWCore.ParameterSet.Config as cms\nfrom FWCore.ParameterSet.VarParsing import VarParsing\n\n# setup minimal options\n#options = VarParsing(\"python\")\n#options.setDefault(\"inputFiles\", \"root://xrootd-cms.infn.it//store/mc/RunIIFall17MiniAOD/DYJetsToLL_M-10to50_TuneCP5_13TeV-madgraphMLM-pythia8/MINIAODSIM/94X_mc2017_realistic_v10-v2/00000/9A439935-1FFF-E711-AE07-D4AE5269F5FF.root\")  # noqa\n#options.parseArguments()\n\n# define the process to run\nprocess = cms.Process(\"TEST\")\n\n# minimal configuration\nprocess.load(\"FWCore.MessageService.MessageLogger_cfi\")\nprocess.MessageLogger.cerr.FwkReport.reportEvery = 1\nprocess.maxEvents = cms.untracked.PSet(input=cms.untracked.int32(1))\n#process.source = cms.Source(\"PoolSource\",\n#    fileNames=cms.untracked.vstring('file:/afs/cern.ch/cms/Tutorials/TWIKI_DATA/TTJets_8TeV_53X.root'))\nprocess.source = cms.Source(\"EmptySource\")\n# process options\nprocess.options = cms.untracked.PSet(\n    allowUnscheduled=cms.untracked.bool(True),\n    wantSummary=cms.untracked.bool(True),\n)\n\nprocess.XGBoostExample = cms.EDAnalyzer(\"XGBoostExample\")\n\n# setup MyPlugin by loading the auto-generated cfi (see MyPlugin.fillDescriptions)\n#process.load(\"XGB_Example.XGBoostExample.XGBoostExample_cfi\")\nprocess.XGBoostExample.model_path = cms.string(\"/Your/Path/data/lowVer.model\")\nprocess.XGBoostExample.test_data_path = cms.string(\"/Your/Path/data/Test_data.csv\")\n\n# define what to run in the path\nprocess.p = cms.Path(process.XGBoostExample)\n
// -*- C++ -*-\n//\n// Package:    XGB_Example/XGBoostExample\n// Class:      XGBoostExample\n//\n/**\\class XGBoostExample XGBoostExample.cc XGB_Example/XGBoostExample/plugins/XGBoostExample.cc\n\n Description: [one line class summary]\n\n Implementation:\n     [Notes on implementation]\n*/\n//\n// Original Author:  Qian Sitian\n//         Created:  Sat, 19 Jun 2021 08:38:51 GMT\n//\n//\n\n\n// system include files\n#include <memory>\n\n// user include files\n#include \"FWCore/Framework/interface/Frameworkfwd.h\"\n#include \"FWCore/Framework/interface/one/EDAnalyzer.h\"\n\n#include \"FWCore/Framework/interface/Event.h\"\n#include \"FWCore/Framework/interface/MakerMacros.h\"\n\n#include \"FWCore/ParameterSet/interface/ParameterSet.h\"\n#include \"FWCore/Utilities/interface/InputTag.h\"\n#include \"DataFormats/TrackReco/interface/Track.h\"\n#include \"DataFormats/TrackReco/interface/TrackFwd.h\"\n\n#include <xgboost/c_api.h>\n#include <vector>\n#include <tuple>\n#include <string>\n#include <iostream>\n#include <fstream>\n#include <sstream>\n\nusing namespace std;\n\nvector<vector<double>> readinCSV(const char* name){\nauto fin = ifstream(name);\nvector<vector<double>> floatVec;\nstring strFloat;\nfloat fNum;\nint counter = 0;\ngetline(fin,strFloat);\nwhile(getline(fin,strFloat))\n{\nstd::stringstream  linestream(strFloat);\nfloatVec.push_back(std::vector<double>());\nwhile(linestream>>fNum)\n{\nfloatVec[counter].push_back(fNum);\nif (linestream.peek() == ',')\nlinestream.ignore();\n}\n++counter;\n}\nreturn floatVec;\n}\n\n//\n// class declaration\n//\n\n// If the analyzer does not use TFileService, please remove\n// the template argument to the base class so the class inherits\n// from  edm::one::EDAnalyzer<>\n// This will improve performance in multithreaded jobs.\n\n\n\nclass XGBoostExample : public edm::one::EDAnalyzer<>  {\npublic:\nexplicit XGBoostExample(const edm::ParameterSet&);\n~XGBoostExample();\n\nstatic void fillDescriptions(edm::ConfigurationDescriptions& descriptions);\n\n\nprivate:\nvirtual void beginJob() ;\nvirtual void analyze(const edm::Event&, const edm::EventSetup&) ;\nvirtual void endJob() ;\n\n// ----------member data ---------------------------\n\nstd::string test_data_path;\nstd::string model_path;\n\n\n\n\n};\n\n//\n// constants, enums and typedefs\n//\n\n//\n// static data member definitions\n//\n\n//\n// constructors and destructor\n//\nXGBoostExample::XGBoostExample(const edm::ParameterSet& config):\ntest_data_path(config.getParameter<std::string>(\"test_data_path\")),\nmodel_path(config.getParameter<std::string>(\"model_path\"))\n{\n\n}\n\n\nXGBoostExample::~XGBoostExample()\n{\n\n// do anything here that needs to be done at desctruction time\n// (e.g. close files, deallocate resources etc.)\n\n}\n\n\n//\n// member functions\n//\n\nvoid\nXGBoostExample::analyze(const edm::Event& iEvent, const edm::EventSetup& iSetup)\n{\n}\n\n\nvoid\nXGBoostExample::beginJob()\n{\nBoosterHandle booster_;\nXGBoosterCreate(NULL,0,&booster_);\nXGBoosterLoadModel(booster_,model_path.c_str());\nunsigned long numFeature = 0;\nvector<vector<double>> TestDataVector = readinCSV(test_data_path.c_str());\nfloat TestData[2000][8];\nfor(unsigned i=0; (i < 2000); i++)\n{ for(unsigned j=0; (j < 8); j++)\n{\nTestData[i][j] = TestDataVector[i][j];\n//  cout<<TestData[i][j]<<\"\\t\";\n} //cout<<endl;\n}\nDMatrixHandle data_;\nXGDMatrixCreateFromMat((float *)TestData,2000,8,-1,&data_);\nbst_ulong out_len=0;\nconst float *f;\nauto ret=XGBoosterPredict(booster_, data_,0, 0,0,&out_len,&f);\nfor (unsigned int i=0;i<out_len;i++)\nstd::cout <<  i << \"\\t\"<< f[i] << std::endl;\n}\n\nvoid\nXGBoostExample::endJob()\n{\n}\n\nvoid\nXGBoostExample::fillDescriptions(edm::ConfigurationDescriptions& descriptions) {\n//The following says we do not know what parameters are allowed so do no validation\n// Please change this to state exactly what you do use, even if it is no parameters\nedm::ParameterSetDescription desc;\ndesc.add<std::string>(\"test_data_path\");\ndesc.add<std::string>(\"model_path\");\ndescriptions.addWithDefaultLabel(desc);\n\n//Specify that only 'tracks' is allowed\n//To use, remove the default given above and uncomment below\n//ParameterSetDescription desc;\n//desc.addUntracked<edm::InputTag>(\"tracks\",\"ctfWithMaterialTracks\");\n//descriptions.addDefault(desc);\n}\n\n//define this as a plug-in\nDEFINE_FWK_MODULE(XGBoostExample);\n
<use name=\"FWCore/Framework\"/>\n<use name=\"FWCore/PluginManager\"/>\n<use name=\"FWCore/ParameterSet\"/>\n<use name=\"DataFormats/TrackReco\"/>\n<use name=\"xgboost\"/>\n<flags EDM_PLUGIN=\"1\"/>\n
# coding: utf-8\n\nimport os\n\nimport FWCore.ParameterSet.Config as cms\nfrom FWCore.ParameterSet.VarParsing import VarParsing\n\n# setup minimal options\n#options = VarParsing(\"python\")\n#options.setDefault(\"inputFiles\", \"root://xrootd-cms.infn.it//store/mc/RunIIFall17MiniAOD/DYJetsToLL_M-10to50_TuneCP5_13TeV-madgraphMLM-pythia8/MINIAODSIM/94X_mc2017_realistic_v10-v2/00000/9A439935-1FFF-E711-AE07-D4AE5269F5FF.root\")  # noqa\n#options.parseArguments()\n\n# define the process to run\nprocess = cms.Process(\"TEST\")\n\n# minimal configuration\nprocess.load(\"FWCore.MessageService.MessageLogger_cfi\")\nprocess.MessageLogger.cerr.FwkReport.reportEvery = 1\nprocess.maxEvents = cms.untracked.PSet(input=cms.untracked.int32(10))\n#process.source = cms.Source(\"PoolSource\",\n#    fileNames=cms.untracked.vstring('file:/afs/cern.ch/cms/Tutorials/TWIKI_DATA/TTJets_8TeV_53X.root'))\nprocess.source = cms.Source(\"EmptySource\")\n#process.source = cms.Source(\"PoolSource\",\n#    fileNames=cms.untracked.vstring(options.inputFiles))\n# process options\nprocess.options = cms.untracked.PSet(\n    allowUnscheduled=cms.untracked.bool(True),\n    wantSummary=cms.untracked.bool(True),\n)\n\nprocess.XGBoostExample = cms.EDAnalyzer(\"XGBoostExample\")\n\n# setup MyPlugin by loading the auto-generated cfi (see MyPlugin.fillDescriptions)\n#process.load(\"XGB_Example.XGBoostExample.XGBoostExample_cfi\")\nprocess.XGBoostExample.model_path = cms.string(\"/Your/Path/data/highVer.model\")  \nprocess.XGBoostExample.test_data_path = cms.string(\"/Your/Path/data/Test_data.csv\")\n\n# define what to run in the path\nprocess.p = cms.Path(process.XGBoostExample)\n
"},{"location":"inference/xgboost.html#python-usage","title":"Python Usage","text":"

To use XGBoost's python interface, using the snippet below under CMSSW environment

# importing necessary models\nimport numpy as np\nimport pandas as pd \nfrom xgboost import XGBClassifier\nimport matplotlib.pyplot as plt\nimport pandas as pd\n\n\nxgb = XGBClassifier()\nxgb.load_model('ModelName.model')\n\n# After loading model, usage is the same as discussed in the model preparation section.\n

"},{"location":"inference/xgboost.html#caveat","title":"Caveat","text":"

It is worth mentioning that both behavior and APIs of different XGBoost version can have difference.

  1. When using c_api for C/C++ inference, for ver.<1, the API is XGB_DLL int XGBoosterPredict(BoosterHandle handle, DMatrixHandle dmat,int option_mask, int training, bst_ulong * out_len,const float ** out_result), while for ver.>=1 the API changes to XGB_DLL int XGBoosterPredict(BoosterHandle handle, DMatrixHandle dmat,int option_mask, unsigned int ntree_limit, int training, bst_ulong * out_len,const float ** out_result).

  2. Model from ver.>=1 cannot be used for ver.<1.

Other important issue for C/C++ user is that DMatrix only takes in single precision floats (float), not double precision floats (double).

"},{"location":"inference/xgboost.html#appendix-tips-for-xgboost-users","title":"Appendix: Tips for XGBoost users","text":""},{"location":"inference/xgboost.html#importance-plot","title":"Importance Plot","text":"

XGBoost uses F-score to describe feature importance quantatitively. XGBoost's python API provides a nice tool,plot_importance, to plot the feature importance conveniently after finishing train.

# Once the training is done, the plot_importance function can thus be used to plot the feature importance.\nfrom xgboost import plot_importance # Import the function\n\nplot_importance(xgb) # suppose the xgboost object is named \"xgb\"\nplt.savefig(\"importance_plot.pdf\") # plot_importance is based on matplotlib, so the plot can be saved use plt.savefig()\n
The importance plot is consistent with our expectation, as in our toy-model, the data points differ by most on the feature \"7\". (see toy model setup).

"},{"location":"inference/xgboost.html#roc-curve-and-auc","title":"ROC Curve and AUC","text":"

The receiver operating characteristic (ROC) and auccrency (AUC) are key quantities to describe the model performance. For XGBoost, ROC curve and auc score can be easily obtained with the help of sci-kit learn (sklearn) functionals, which is also in CMSSW software.

from sklearn.metrics import roc_auc_score,roc_curve,auc\n# ROC and AUC should be obtained on test set\n# Suppose the ground truth is 'y_test', and the output score is named as 'y_score'\n\nfpr, tpr, _ = roc_curve(y_test, y_score)\nroc_auc = auc(fpr, tpr)\n\nplt.figure()\nlw = 2\nplt.plot(fpr, tpr, color='darkorange',\n         lw=lw, label='ROC curve (area = %0.2f)' % roc_auc)\nplt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')\nplt.xlim([0.0, 1.0])\nplt.ylim([0.0, 1.05])\nplt.xlabel('False Positive Rate')\nplt.ylabel('True Positive Rate')\nplt.title('Receiver operating characteristic example')\nplt.legend(loc=\"lower right\")\n# plt.show() # display the figure when not using jupyter display\nplt.savefig(\"roc.png\") # resulting plot is shown below\n

"},{"location":"inference/xgboost.html#reference-of-xgboost","title":"Reference of XGBoost","text":"
  1. XGBoost Wiki: https://en.wikipedia.org/wiki/XGBoost
  2. XGBoost Github Repo.: https://github.com/dmlc/xgboost
  3. XGBoost offical api tutorial
  4. Latest, Python: https://xgboost.readthedocs.io/en/latest/python/index.html
  5. Latest, C/C++: https://xgboost.readthedocs.io/en/latest/tutorials/c_api_tutorial.html
  6. Older (0.80), Python: https://xgboost.readthedocs.io/en/release_0.80/python/index.html
  7. No Tutorial for older version C/C++ api, source code: https://github.com/dmlc/xgboost/blob/release_0.80/src/c_api/c_api.cc
"},{"location":"innovation/hackathons.html","title":"CMS Machine Learning Hackathons","text":"

Welcome to the CMS ML Hackathons! Here we encourage the exploration of cutting edge ML methods to particle physics problems through multi-day focused work. Form hackathon teams and work together with the ML Innovation group to get support with organization and announcements, hardware/software infrastructure, follow-up meetings and ML-related technical advise.

If you are interested in proposing a hackathon, please send an e-mail to the CMS ML Innovation conveners with a potential topic and we will get in touch!

Below follows a list of previous successful hackathons.

"},{"location":"innovation/hackathons.html#hgcal-ticl-reconstruction","title":"HGCAL TICL reconstruction","text":"

20 Jun 2022 - 24 Jun 2022 https://indico.cern.ch/e/ticlhack

Abstract: The HGCAL reconstruction relies on \u201cThe Iterative CLustering\u201d (TICL) framework. It follows an iterative approach, first clusters energy deposits in the same layer (layer clusters) and then connect these layer clusters to reconstruct the particle shower by forming 3-D objects, the \u201ctracksters\u201d. There are multiple areas that could benefit from advanced ML techniques to further improve the reconstruction performance.

In this project we plan to tackle the following topics using ML:

  • trackster identification (ie, identification of the type of particle initiating the shower) and energy regression linking of tracksters stemming from the same particle to reconstruct the full shower and/or use a high-purity trackster as a seed and collect 2D (ie. layer clusters) and/or 3D (ie, tracksters) energy deposits in the vicinity of the seed trackster to fully reconstruct the particle shower
  • tuning of the existing pattern recognition algorithms
  • reconstruction under HL-LHC pile-up scenarios (eg., PU=150-200)
  • trackster characterization, ie. predict if a trackster is a sound object in itself or determine if it is more likely to be a composite one.
"},{"location":"innovation/hackathons.html#material","title":"Material:","text":"

A CodiMD document has been created with an overview of the topics and to keep track of the activities during the hackathon:

https://codimd.web.cern.ch/s/hMd74Yi7J

"},{"location":"innovation/hackathons.html#jet-tagging","title":"Jet tagging","text":"

8 Nov 2021 - 11 Nov 2021 https://indico.cern.ch/e/jethack

Abstract: The identification of the initial particle (quark, gluon, W/Z boson, etc..) responsible for the formation of the jet, also known as jet tagging, provides a powerful handle in both standard model (SM) measurements and searches for physics beyond the SM (BSM). In this project we propose the development of jet tagging algorithms both for small-radius (i.e. AK4) and large-radius (i.e., AK8) jets using as inputs the PF candidates.

Two main projects are covered:

  • Jet tagging for scouting
  • Jet tagging for Level-1
"},{"location":"innovation/hackathons.html#jet-tagging-for-scouting","title":"Jet tagging for scouting","text":"

Using as inputs the PF candidates and local pixel tracks reconstructed in the scouting streams, the main goals of this project are the following:

Develop a jet-tagging baseline for scouting and compare the performance with the offline reconstruction Understand the importance of the different input variables and the impact of -various configurations (e.g., on pixel track reconstruction) in the performance Compare different jet tagging approaches with mind performance as well as inference time. Proof of concept: ggF H->bb, ggF HH->4b, VBF HH->4b

"},{"location":"innovation/hackathons.html#jet-tagging-for-level-1","title":"Jet tagging for Level-1","text":"

Using as input the newly developed particle flow candidates of Seeded Cone jets in the Level1 Correlator trigger, the following tasks will be worked on:

  • Developing a quark, gluon, b, pileup jet classifier for Seeded Cone R=0.4 jets using a combination of tt,VBF(H) and Drell-Yan Level1 samples
  • Develop tools to demonstrate the gain of such a jet tagging algorithm on a signal sample (like q vs g on VBF jets)
  • Study tagging performance as a function of the number of jet constituents
  • Study tagging performance for a \"real\" input vector (zero-paddes, perhaps unsorted)
  • Optimise jet constituent list of SeededCone Jets (N constituents, zero-removal, sorting etc)
  • Develop q/g/W/Z/t/H classifier for Seeded Cone R=0.8 jets
"},{"location":"innovation/hackathons.html#gnn-4-tracking","title":"GNN-4-tracking","text":"

27 Sept 2021 - 1 Oct 2021

https://indico.cern.ch/e/gnn4tracks

Abstract: The aim of this hackathon is to integrate graph neural nets (GNNs) for particle tracking into CMSSW.

The hackathon will make use of a GNN model reported by the paper Charged particle tracking via edge-classifying interaction networks by Gage DeZoort, Savannah Thais, et.al. They used a GNN to predict connections between detector pixel hits, and achieved accurate track building. They did this with the TrackML dataset, which uses a generic detector designed to be similar to CMS or ATLAS. Work is ongoing to apply this GNN approach to CMS data.

Tasks: The hackathon aims to create a workflow that allows graph building and GNN inference within the framework of CMSSW. This would enable accurate testing of future GNN models and comparison to existing CMSSW track building methods. The hackathon will be divided into the following subtasks:

  • Task 1: Create a package for extracting graph features and building graphs in CMSSW.
  • Task 2. GNN inference on Sonic servers
  • Task 3: Track fitting after GNN track building
  • Task 4. Performance evaluation for the new track collection
"},{"location":"innovation/hackathons.html#material_1","title":"Material:","text":"

Code is provided at this GitHub organisation. Project are listed here.

"},{"location":"innovation/hackathons.html#anomaly-detection","title":"Anomaly detection","text":"

In this four day Machine Learning Hackathon, we will develop new anomaly detection algorithms for New Physics detection, intended for deployment in the two main stages of the CMS data aquisition system: The Level-1 trigger and the High Level Trigger.

There are two main projects:

"},{"location":"innovation/hackathons.html#event-based-anomaly-detection-algorithms-for-the-level-1-trigger","title":"Event-based anomaly detection algorithms for the Level-1 Trigger","text":""},{"location":"innovation/hackathons.html#jet-based-anomaly-detection-algorithms-for-the-high-level-trigger-specifically-targeting-run-3-scouting","title":"Jet-based anomaly detection algorithms for the High Level Trigger, specifically targeting Run 3 scouting","text":""},{"location":"innovation/hackathons.html#material_2","title":"Material:","text":"

A list of projects can be found in this document. Instructions for fetching the data and example code for the two projects can be found at Level-1 Anomaly Detection.

"},{"location":"innovation/journal_club.html","title":"CMS Machine Learning Journal Club","text":"

Welcome to the CMS Machine Learning Journal Club (JC)! Here we read an discuss new cutting edge ML papers, with an emphasis on how these can be used within the collaboration. Below you can find a summary of each JC as well as some code examples demonstrating how to use the tools or methods introduced.

To vote for or to propose new papers for discussion, go to https://cms-ml-journalclub.web.cern.ch/.

Below follows a complete list of all the previous CMS ML JHournal clubs, together with relevant documentation and code examples.

"},{"location":"innovation/journal_club.html#dealing-with-nuisance-parameters-using-machine-learning-in-high-energy-physics-a-review","title":"Dealing with Nuisance Parameters using Machine Learning in High Energy Physics: a Review","text":"

Tommaso Dorigo, Pablo de Castro

Abstract: In this work we discuss the impact of nuisance parameters on the effectiveness of machine learning in high-energy physics problems, and provide a review of techniques that allow to include their effect and reduce their impact in the search for optimal selection criteria and variable transformations. The introduction of nuisance parameters complicates the supervised learning task and its correspondence with the data analysis goal, due to their contribution degrading the model performances in real data, and the necessary addition of uncertainties in the resulting statistical inference. The approaches discussed include nuisance-parameterized models, modified or adversary losses, semi-supervised learning approaches, and inference-aware techniques.

  • Indico
  • Paper
"},{"location":"innovation/journal_club.html#mapping-machine-learned-physics-into-a-human-readable-space","title":"Mapping Machine-Learned Physics into a Human-Readable Space","text":"

Taylor Faucett, Jesse Thaler, Daniel Whiteson

Abstract: We present a technique for translating a black-box machine-learned classifier operating on a high-dimensional input space into a small set of human-interpretable observables that can be combined to make the same classification decisions. We iteratively select these observables from a large space of high-level discriminants by finding those with the highest decision similarity relative to the black box, quantified via a metric we introduce that evaluates the relative ordering of pairs of inputs. Successive iterations focus only on the subset of input pairs that are misordered by the current set of observables. This method enables simplification of the machine-learning strategy, interpretation of the results in terms of well-understood physical concepts, validation of the physical model, and the potential for new insights into the nature of the problem itself. As a demonstration, we apply our approach to the benchmark task of jet classification in collider physics, where a convolutional neural network acting on calorimeter jet images outperforms a set of six well-known jet substructure observables. Our method maps the convolutional neural network into a set of observables called energy flow polynomials, and it closes the performance gap by identifying a class of observables with an interesting physical interpretation that has been previously overlooked in the jet substructure literature. - Indico - Paper

"},{"location":"innovation/journal_club.html#model-interpretability-2-papers","title":"Model Interpretability (2 papers):","text":"
  • Indico
"},{"location":"innovation/journal_club.html#identifying-the-relevant-dependencies-of-the-neural-network-response-on-characteristics-of-the-input-space","title":"Identifying the relevant dependencies of the neural network response on characteristics of the input space","text":"

Stefan Wunsch, Raphael Friese, Roger Wolf, G\u00fcnter Quast

Abstract: The relation between the input and output spaces of neural networks (NNs) is investigated to identify those characteristics of the input space that have a large influence on the output for a given task. For this purpose, the NN function is decomposed into a Taylor expansion in each element of the input space. The Taylor coefficients contain information about the sensitivity of the NN response to the inputs. A metric is introduced that allows for the identification of the characteristics that mostly determine the performance of the NN in solving a given task. Finally, the capability of this metric to analyze the performance of the NN is evaluated based on a task common to data analyses in high-energy particle physics experiments.

  • Paper
"},{"location":"innovation/journal_club.html#innvestigate-neural-networks","title":"iNNvestigate neural networks!","text":"

Maximilian Alber, Sebastian Lapuschkin, Philipp Seegerer, Miriam H\u00e4gele, Kristof T. Sch\u00fctt, Gr\u00e9goire Montavon, Wojciech Samek, Klaus-Robert M\u00fcller, Sven D\u00e4hne, Pieter-Jan Kindermans

In recent years, deep neural networks have revolutionized many application domains of machine learning and are key components of many critical decision or predictive processes. Therefore, it is crucial that domain specialists can understand and analyze actions and pre- dictions, even of the most complex neural network architectures. Despite these arguments neural networks are often treated as black boxes. In the attempt to alleviate this short- coming many analysis methods were proposed, yet the lack of reference implementations often makes a systematic comparison between the methods a major effort. The presented library iNNvestigate addresses this by providing a common interface and out-of-the- box implementation for many analysis methods, including the reference implementation for PatternNet and PatternAttribution as well as for LRP-methods. To demonstrate the versatility of iNNvestigate, we provide an analysis of image classifications for variety of state-of-the-art neural network architectures.

  • Paper
  • Code
"},{"location":"innovation/journal_club.html#simulation-based-inference-in-particle-physics-and-beyond-and-beyond","title":"Simulation-based inference in particle physics and beyond (and beyond)","text":"

Johann Brehmer, Kyle Cranmer

Abstract: Our predictions for particle physics processes are realized in a chain of complex simulators. They allow us to generate high-fidelity simulated data, but they are not well-suited for inference on the theory parameters with observed data. We explain why the likelihood function of high-dimensional LHC data cannot be explicitly evaluated, why this matters for data analysis, and reframe what the field has traditionally done to circumvent this problem. We then review new simulation-based inference methods that let us directly analyze high-dimensional data by combining machine learning techniques and information from the simulator. Initial studies indicate that these techniques have the potential to substantially improve the precision of LHC measurements. Finally, we discuss probabilistic programming, an emerging paradigm that lets us extend inference to the latent process of the simulator.

  • Indico
  • Paper
  • Code
"},{"location":"innovation/journal_club.html#efficiency-parameterization-with-neural-networks","title":"Efficiency Parameterization with Neural Networks","text":"

C. Badiali, F.A. Di Bello, G. Frattari, E. Gross, V. Ippolito, M. Kado, J. Shlomi

Abstract: Multidimensional efficiency maps are commonly used in high energy physics experiments to mitigate the limitations in the generation of large samples of simulated events. Binned multidimensional efficiency maps are however strongly limited by statistics. We propose a neural network approach to learn ratios of local densities to estimate in an optimal fashion efficiencies as a function of a set of parameters. Graph neural network techniques are used to account for the high dimensional correlations between different physics objects in the event. We show in a specific toy model how this method is applicable to produce accurate multidimensional efficiency maps for heavy flavor tagging classifiers in HEP experiments, including for processes on which it was not trained. - Indico - Paper - Code

"},{"location":"innovation/journal_club.html#a-general-framework-for-uncertainty-estimation-in-deep-learning","title":"A General Framework for Uncertainty Estimation in Deep Learning","text":"

Antonio Loquercio, Mattia Seg\u00f9, Davide Scaramuzza

Neural networks predictions are unreliable when the input sample is out of the training distribution or corrupted by noise. Being able to detect such failures automatically is fundamental to integrate deep learning algorithms into robotics. Current approaches for uncertainty estimation of neural networks require changes to the network and optimization process, typically ignore prior knowledge about the data, and tend to make over-simplifying assumptions which underestimate uncertainty. To address these limitations, we propose a novel framework for uncertainty estimation. Based on Bayesian belief networks and Monte-Carlo sampling, our framework not only fully models the different sources of prediction uncertainty, but also incorporates prior data information, e.g. sensor noise. We show theoretically that this gives us the ability to capture uncertainty better than existing methods. In addition, our framework has several desirable properties: (i) it is agnostic to the network architecture and task; (ii) it does not require changes in the optimization process; (iii) it can be applied to already trained architectures. We thoroughly validate the proposed framework through extensive experiments on both computer vision and control tasks, where we outperform previous methods by up to 23% in accuracy.

  • Indico
  • Paper
  • Code
"},{"location":"optimization/data_augmentation.html","title":"Data augmentation","text":""},{"location":"optimization/data_augmentation.html#introduction","title":"Introduction","text":"

This introduction is based on papers by Shorten & Khoshgoftaar, 2019 and Rebuffi et al., 2021 among others

With the increasing complexity and sizes of neural networks one needs huge amounts of data in order to train a state-of-the-art model. However, generating this data is often very resource and time intensive. Thus, one might either augment the existing data with more descriptive variables or combat the data scarcity problem by artificially increasing the size of the dataset by adding new instances without the resource-heavy generation process. Both processes are known in machine learning (ML) applications as data augmentation (DA) methods.

The first type of these methods is more widely known as feature generation or feature engineering and is done on instance level. Feature engineering focuses on crafting informative input features for the algorithm, often inspired or derived from first principles specific to the algorithm's application domain.

The second type of method is done on the dataset level. These types of techniques can generally be divided into two main categories: real data augmentation (RDA) and synthetic data augmentation (SDA). As the name suggests, RDA makes minor changes to the already existing data in order to generate new samples, whereas SDA generates new data from scratch. Examples of RDA include rotating (especially useful if we expect the event to be rotationally symmetric) and zooming, among a plethora of other methods detailed in this overview article. Examples of SDA include traditional sampling methods and more complex generative models like Generative Adversaial Netoworks (GANs) and Variational Autoencoders (VAE). Going further, the generative methods used for synthetic data augmentation could also be used in fast simulation, which is a notable bottleneck in the overall physics analysis workflow.

Dataset augmentation may lead to more successful algorithm outcomes. For example, introducing noise into data to form additional data points improves the learning ability of several models which otherwise performed relatively poorly, as shown by Freer & Yang, 2020. This finding implies that this form of DA creates variations that the model may see in the real world. If done right, preprocessing the data with DA will result in superior training outcomes. This improvement in performance is due to the fact that DA methods act as a regularizer, reducing overfitting during training. In addition to simulating real-world variations, DA methods can also even out categorical data with imbalanced classes.

Fig. 1: Generic pipeline of a heuristic DA (figure taken from Li, 2020)

Before diving more in depth into the various DA methods and applications in HEP, here is a list of the most notable benefits of using DA methods in your ML workflow:

  • Improvement of model prediction precision
  • More training data for the model
  • Preventing data scarcity for state-of-the-art models
  • Reduction of over overfitting and creation of data variability
  • Increased model generalization properties
  • Help in resolving class imbalance problems in datasets
  • Reduced cost of data collection and labeling
  • Enabling rare event prediction

And some words of caution:

  • There is no 'one size fits all' in DA. Each dataset and usecase should be considered separately.
  • Don't trust the augmented data blindly
  • Make sure that the augmented data is representative of the problem at hand, otherwise it will negatively affect the model performance.
  • There must be no unnecessary duplication of existing data, only by adding unique information we gain more insights.
  • Ensure the validity of the augmented data before using it in ML models.
  • If a real dataset contains biases, data augmented from it will contain biases, too. So, identification of optimal data augmentation strategy is important. So, double check your DA strategy.
"},{"location":"optimization/data_augmentation.html#feature-engineering","title":"Feature Engineering","text":"

This part is based mostly on Erdmann et al., 2018

Feature engineering (FE) is one of the key components of a machine learning workflow. This process transforms and augments training data with additional features in order to make the training more effective.

With multi-variate analyeses (MVAs), such boosted decision trees (BDTs) and neural networks, one could start with raw, \"low-level\" features, like four-momenta, and the algorithm can learn higher level patterns, correlations, metrics, etc. However, using \"high-level\" variables, in many cases, leads to outcomes superior to the use of low-level variables. As such, features used in MVAs are handcrafted from physics first principles.

Still, it is shown that a deep neural network (DNN) can perform better if it is trained with both specifically constructed variables and low-level variables. This observation suggests that the network extracts additional information from the training data.

"},{"location":"optimization/data_augmentation.html#hep-application-lorentz-boosted-network","title":"HEP Application - Lorentz Boosted Network","text":"

For the purposeses of FE in HEP, a novel ML architecture called a Lorentz Boost Network (LBN) (see Fig. 2) was proposed and implemented by Erdmann et al., 2018. It is a multipurpose method that uses Lorentz transformations to exploit and uncover structures in particle collision events. LBN is the first stage of a two-stage neural network (NN) model, that enables a fully autonomous and comprehensive characterization of collision events by exploiting exclusively the four-momenta of the final-state particles.

Within LBN, particles are combined to create rest frames representions, which enables the formation of further composite particles. These combinations are realized via linear combinations of N input four-vectors to a number of M particles and rest frames. Subsequently these composite particles are then transformed into said rest frames by Lorentz transformations in an efficient and fully vectorized implementation.

The properties of the composite, transformed particles are compiled in the form of characteristic variables like masses, angles, etc. that serve as input for a subsequent network - the second stage, which has to be configured for a specific analysis task, like classification.

The authors observed leading performance with the LBN and demonstrated that LBN forms physically meaningful particle combinations and generates suitable characteristic variables.

The usual ML workflow, employing LBN, is as follows:

Step-1: LBN(M, F)\n\n    1.0: Input hyperparameters: number of combinations M; number of features F\n    1.0: Choose: number of incoming particles, N, according to the research\n         question\n\n    1.1: Combination of input four-vectors to particles and rest frames\n\n    1.2: Lorentz transformations\n\n    1.3 Extraction of suitable high-level objects\n\n\nStep-2: NN\n\n    2.X: Train some form of a NN using an objective function that depends on\n         the analysis / research question.\n
Fig. 2: The Lorentz Boost Network architecture (figure taken from Erdmann et al., 2018)

The LBN package is also pip-installable:

pip install lbn\n
"},{"location":"optimization/data_augmentation.html#rda-techniques","title":"RDA Techniques","text":"

This section and the following subsection are based on the papers by Freer & Yang, 2020, Dolan & Ore, 2021, Barnard et al., 2016, and Bradshaw et al., 2019

RDA methods augment the existing dataset by performance some transformation on the existing data points. These transformations could include rotation, flipping, color shift (for an image), Fourier transforming (for signal processing) or some other transformation that preserves the validity of the data point and its corresponding label. As mentioned in Freer & Yang, 2020, these types of transformations augment the dataset to capture potential variations that the population of data may exhibit, allowing the network to capture a more generalized view of the sampled data.

"},{"location":"optimization/data_augmentation.html#hep-application-zooming","title":"HEP Application - Zooming","text":"

In Barnard et al., 2016, the authors investigate the effect of parton shower modelling in DNN jet taggers using images of hadronically decaying W bosons. They introduce a method known as zooming to study the scale invariance of these networks. This is the RDA strategy used by Dolan & Ore, 2021. Zooming is similar to a normalization procedure such that it standardizes features in signal data, but it aims to not create similar features in background.

After some standard data processing steps, including jet trimming and clustering via the \\(k_t\\) algorithm, and some further processing to remove spatial symmetries, the resulting jet image depicts the leading subjet and subleading subjet directly below. Barnard et al., 2016 notes that the separation between the leading and subleading subjets varies linearly as \\(2m/p_T\\) where \\(m\\) and \\(p_T\\) are the mass and transverse momentum of the jet. Standardizing this separation, or removing the linear dependence, would allow the DNN tagger to generalize to a wide range of jet \\(p_T\\). To this end, the authors construct a factor, \\(R/\\DeltaR_{act}\\), where \\(R\\) is some fixed value and \\(\\DeltaR_{act}\\) is the separation between the leading and subleading subjets. To discriminate between signal and background images with this factor, the authors enlarge the jet images by a scaling factor of \\(\\text{max}(R/s,1)\\) where \\(s = 2m_W/p_T\\) and \\(R\\) is the original jet clustering size. This process of jet image enlargement by a linear mass and \\(p_T\\) dependent factor to account for the distane between the leading and subleading jet is known as zooming. This process can be thought of as an RDA technique to augment the data in a domain-specific way.

Advantage of using the zooming technique is that it makes the construction of scale invariant taggers easier. Scale invariant searches which are able to interpolate between the boosted and resolved parts of phase space have the advantage of being applicable over a broad range of masses and kinematics, allowing a single search or analysis to be effective where previously more than one may have been necessary.

As predicted the zoomed network outperforms the unzoomed one, particularly at low signal efficiency, where the background rejection rises by around 20%. Zooming has the greatest effect at high pT.

"},{"location":"optimization/data_augmentation.html#traditional-sda-techniques","title":"Traditional SDA Techniques","text":"

Text in part based on He et al., 2010

Generally speaking, imbalanced learning occurs whenever some type of data distribution dominates the instance space compared to other data distributions. Methods for handling imbalanced learning problems can be divided into the following five major categories:

  • Sampling strategies
  • Synthetic data generation (SMOTE & ADASYN & DataBoost-IM) - aims to overcome the imbalance by artificially generating data samples.
  • Cost-sensitive learning - uses cost-matrix for different types of errors or instance to facilitate learning from imbalanced data sets. This means that cost-sensitive learning does not modify the imbalanced data distribution directly, but targets this problem by using different cost-matrices that describe the cost for misclassifying any particular data sample.
  • Active learning - conventionally used to solve problems related to unlabeled data, though recently it has been used in learning imbalanced data sets. Instead of searching the entire training space, this method effectively selects informative instances from a random set of training populations, therefore significantly reducing the computational cost when dealing with large imbalanced data sets.
  • Kernel-based methods - by integrating the regularized orthogonal weighed least squares (ROWLS) estimator, a kernel classifier construction algorithm is based on orthogonal forward selection (OFS) to optimize the model generalization for learning from two-class imbalanced data sets.
"},{"location":"optimization/data_augmentation.html#sampling","title":"Sampling","text":"

When the percentage of the minority class is less than 5%, it can be considered a rare event. When a dataset is imbalanced or when a rare event occurs, it will be difficult to get a meaningful and good predictive model due to lack of information about the rare event Au et al., 2010. In these cases, re-sampling techniques can be helpful. The re-sampling techniques are implemented in four different categories: undersampling the majority class, oversampling the minority class, combining over- and undersampling, and ensembling sampling. Oversampling and undersampling are found to work well in improving the classification for the imbalanced dataset. Yap et al., 2013

Stratified sampling (STS) This technique is used in cases where the data can be partitioned into strata (subpopulations), where each strata should be collectively exhaustive and mutually exclusive. The process of dividing the data into homogeneus subgroups before sampling is referred to as stratification. The two common strategies of STS are proportionate allocation (PA) and optimum (disproportionate) allocation (OA). The former uses a fraction in each of the stata that is proportional to that of the total population. The latter uses the standard deviation of the distribution of the variable as well, so that the larger samples are taken from the strata that has the greatest variability to generate the least possible sampling variance. The advantages of using STS include smaller error in estimation (if measurements within strata have lower standard deviation) and similarity in uncertainties across all strata in case there is high variability in a given strata.

NOTE: STS is only useful if the population can be exhaustively partitioned into subgroups. Also in case of unknown class priors (the ratio of strata to the whole population) might have deleterious effects on the classification performance.

Over- and undersampling Oversampling randomly duplicates minority class samples, while undersampling discards majority class samples in order to modify the class distribution. While oversampling might lead to overfitting, since it makes exact copies of the minority samples, undersampling may discard potentially useful majority samples.

Oversampling and undersampling are essentially opposite and roughly equivalent techniques. There are also more complex oversampling techniques, including the creation of artificial data points with algorithms like Synthetic Minority Over-sampling TEchnique (SMOTE).

It has been shown that the combination of SMOTE and undersampling performs better than only undersampling the majority class. However, over- and undersampling remain popular as it each is much easier to implement alone than in some complex hybrid approach.

Synthetic Minority Over-sampling Technique (SMOTE) Text mostly based on Chawla et al., 2002 and in part on He et al., 2010

In case of Synthetic Minority Over-sampling Technique (SMOTE), the minority class is oversampled by creating synthetic examples along the line segments joining any or all of the \\(k\\)-nearest neighbours in the minority class. The synthetic examples cause the classifier to create larger and less specific decision regions, rather than smaller and more specific regions. More general regions are now learned for the minority class samples rather than those being subsumed by the majority class samples around them. In this way SMOTE shifts the classifier learning bias toward the minority class and thus has the effect of allowing the model to generalize better.

There also exist extensions of this work like SMOTE-Boost in which the syntetic procedure was integrated with adaptive boosting techniques to change the method of updating weights to better compensate for skewed distributions.

So in general SMOTE proceeds as follows

SMOTE(N, X, k)\nInput: N - Number of synthetic samples to be generated\n       X - Underrepresented data\n       k - Hyperparameter of number of nearest neighbours to be chosen\n\nCreate an empty list SYNTHETIC_SAMPLES\nWhile N_SYNTHETIC_SAMPLES < N\n    1. Randomly choose an entry xRand from X\n    2. Find k nearest neighbours from X\n    3. Randomly choose an entry xNeighbour from the k nearest neighbours\n    4. Take difference dx between the xRand and xNeighbour\n    5. Multiply dx by a random number between 0 and 1\n    6. Append the result to SYNTHETIC_SAMPLES\nExtend X by SYNTHETIC_SAMPLES\n

Adaptive synthetic sampling approach (ADASYN) Text mostly based on He et al., 2010

Adaptive synthetic sampling approach (ADASYN) is a sampling approach for learning from imbalanced datasets. The main idea is to use a weighted distribution for different minority class examples according to their level of difficulty in learning, where more synthetic data is generated for minority class examples that are harder to learn compared to those minority examples that are easier to learn. Thus, ADASYN improves learning with respect to the data distributions by reducing the bias introduced by the class imbalance and by adaptively shifting the classification boundary toward the difficult examples.

The objectives of ADASYN are reducing bias and learning adaptively. The key idea of this algorithm is to use a density distribution as a criterion to decide the number of synthetic samples that need to be generated for each minority data example. Physically, this density distribution is a distribution of weights for different minority class examples according to their level of difficulty in learning. The resulting dataset after using ADASYN will not only provide a balanced representation of the data distribution (according to the desired balance level defined in the configuration), but it also forces the learning algorithm to focus on those difficult to learn examples. It has been shown He et al., 2010, that this algorithm improves accuracy for both minority and majority classes and does not sacrifice one class in preference for another.

ADASYN is not limited to only two-class learning, but can also be generalized to multiple-class imbalanced learning problems as well as incremental learning applications.

For more details and comparisons of ADASYN to other algorithms, please see He et al., 2010.

"},{"location":"optimization/data_augmentation.html#existing-implementations","title":"Existing implementations","text":"

Imbalanced-learn is an open-source Python library which provides a suite of algorithms for treating the class imbalance problem.

For augmentig image data, one can use of of the following:

  • Albumentations
  • ImgAug
  • Autoaugment
  • Augmentor
  • DeepAugmnent

But it is also possible to use tools directly implemented by tensorflow, keras etc. For example:

flipped_image = tf.image.flip_left_right(image)\n
"},{"location":"optimization/data_augmentation.html#deep-learning-based-sda-techniques","title":"Deep Learning-based SDA Techniques","text":"

In data science, data augmentation techniques are used to increase the amount of data by either synthetically creating data from already existing samples via a GAN or modifying the data at hand with small noise or rotation. (Rebuffi et al., 2021)

More recently, data augmentation studies have begun to focus on the field of deep learning (DL), more specifically on the ability of generative models, like Generative Adversarial Networks (GANs) and Variational Autoencoders (VAEs), to create artificial data. This synthetic data is then introduced during the classification model training process to improve performance and results.

Generative Adversarial Networks (GANs) The following text is written based on the works by Musella & Pandolfi, 2018 and Hashemi et al., 2019 and Kansal et al., 2022 and Rehm et al., 2021 and Choi & Lim, 2021 and Kansal et al., 2020

GANs have been proposed as a fast and accurate way of modeling high energy jet formation (Paganini et al., 2017a) and modeling showers throughcalorimeters of high-energy physics experiments (Paganini et al., 2017 ; Paganini et al., 2012; Erdman et al., 2020; Musella & Pandolfi, 2018) GANs have also been trained to accurately approximate bottlenecks in computationally expensive simulations of particle physics experiments. Applications in the context of present and proposed CERN experiments have demonstrated the potential of these methods for accelerating simulation and/or improving simulation fidelity (ATLAS Collaboration, 2018; SHiP Collaboration, 2019).

The generative model approximates the combined response of aparticle detecor simulation and reconstruction algorithms to hadronic jets given the latent space of uniformly distributed noise, auxiliary features and jet image at particle level (jets clustered from the list of stable particles produced by PYTHIA).

In the paper by Musella & Pandolfi, 2018, the authors apply generative models parametrized by neural networks (GANs in particular) to the simulation of particles-detector response to hadronic jets. They show that this parametrization achieves high-fidelity while increasing the processing speed by several orders of magnitude.

Their model is trained to be capable of predicting the combined effect of particle-detector simulation models and reconstruction algorithms to hadronic jets.

Generative adversarial networks (GANs) are pairs of neural networks, a generative and a discriminative one, that are trained concurrently as players of a minimax game (Musella & Pandolfi, 2018). The task of the generative network is to produce, starting from a latent space with a fixed distribution, samples that the discriminative model tries to distinguish from samples drawn from a target dataset. This kind of setup allows the distribution of the target dataset to be learned, provided that both of the networks have high enough capacity.

The input to these networks are hadronic jets, represented as \"gray-scale\" images of fixed size centered around the jet axis, with the pixel intensity corresponding to the energy fraction in a given cell. The architectures of the networks are based on the image-to-image translation. There few differences between this approach and image-to-image translation. Firstly, non-empty pixels are explicitly modelled in the generated images since these are much sparser than the natural ones. Secondly, feature matching and a dedicated adversarial classifier enforce good modelling of the total pixel intensity (energy). Lastly, the generator is conditioned on some auxiliary inputs.

By predicting directly the objects used at analysis level and thus reproducing the output of both detector simulation and reconstruction algorithms, computation time is reduced. This kind of philosophy is very similar to parametrized detector simulations, which are used in HEP for phenomenological studies. The attained accuracies are comparable to the full simulation and reconstruction chain.

"},{"location":"optimization/data_augmentation.html#variational-autoencoders-vaes","title":"Variational autoencoders (VAEs)","text":"

The following section is partly based on Otten et al., 2021

In contrast to the traditional autoencoder (AE) that outputs a single value for each encoding dimension, variational autoencoders (VAEs) provide a probabilistic interpretation for describing an observation in latent space.

In case of VAEs, the encoder model is sometimes referred to as the recognition model and the decoder model as generative model.

By constructing the encoder model to output a distribution of the values from which we randomly sample to feed into our decoder model, we are enforcing a continuous, smooth latent space representation. Thus we expect our decoder model to be able to accurately reconstruct the input for any sampling of the latent distributions, which then means that values residing close to each other in latent space should have very similar reconstructions.

"},{"location":"optimization/data_augmentation.html#ml-powered-data-generation-for-fast-simulation","title":"ML-powered Data Generation for Fast Simulation","text":"

The following text is based on this Chen et al., 2020

We rely on accurate simulation of physics processes, however currently it is very common for LHC physics to be affected by large systematic uncertanties due to the limited amount of simulated data, especially for precise measurements of SM processes for which large datasets are already available. So far the most widely used simulator is GEANT4 that provides state-of-the-art accuracy. But running this is demanding, both in terms of time and resources. Consequently, delivering synthetic data at the pace at which LHC delivers real data is one of the most challenging tasks for computing infrastructures of the LHC experiments. The typical time it takes to simulate one single event is in the ballpark of 100 seconds.

Recently, generative algorithms based on deep learning have been proposed as a possible solution to speed up GEANT4. However, one needs to work beyond the collision-as-image paradigm so that the DL-based simulation accounts for the irregular geometry of a typical detector while delivering a dataset in a format compatible with downstream reconstruction software.

One method to solve this bottleneck was proposed by Chen et al., 2020. They adopt a generative DL model to convert an analysis specific representation of collision events at generator level to the corresponding representation at reconstruction level. Thus, this novel, fast-simulation workflow starts from a large amount of generator-level events to deliver large analysis-specific samples.

They trained a neural network to model detector resolution effects as a transfer function acting on an analysis-specific set of relevant features, computed at generator level. However, their model does not sample events from a latent space (like a GAN or a plain VAE). Instead, it works as a fast simulator of a given generator-level event, preserving the correspondence between the reconstructed and the generated event, which allows us to compare event-by-event residual distributions. Furthermore, this model is much simpler than a generative model.

Step one in this workflow is generating events in their full format, which is the most resource heavy task, where, as noted before, generating one event takes roughly 100 seconds. However, with this new proposed method O(1000) events are generated per second. This would save on storage: for the full format O(1) MB/event is needed, where for the DL model only 8 MB was used to store 100000 events. To train the model, they used NVIDIA RTX2080 and it trained for 30 minutes, which in terms of overall production time is negligible. For generating N=1M events and n=10%N, one would save 90% of the CPU resources and 79% of the disk storage. Thus augmenting the centrally produced data is a viable method and could help the HEP community to face the computing challenges of the High-Luminosity LHC.

Another more extreme approach investigated the use of GANs and VAEs for generating physics quantities which are relevant to a specific analysis. In this case, one learns the N-dimensional density function of the event, in a space defined by the quantities of interest for a given analysis. So sampling from this function, one can generate new data. Trade-off between statistical precision (decreases with the increasing amount of generated events) and the systematic uncertainty that could be induced by a non accurate description of the n-dim pdf.

Qualitatively, no accuracy deterioration was observed due to scaling the dataset size for DL. This fact proves the robustness of the proposed methodology and its effectiveness for data augmentation.

"},{"location":"optimization/data_augmentation.html#open-challenges-in-data-augmentation","title":"Open challenges in Data Augmentation","text":"

Excerpts are taken from Li, 2020

The limitations of conventional data augmentation approaches reveal huge opportunities for research advances. Below we summarize a few challenges that motivate some of the works in the area of data augmentation.

  • From manual to automated search algorithms: As opposed to performing suboptimal manual search, how can we design learnable algorithms to find augmentation strategies that can outperform human-designed heuristics?
  • From practical to theoretical understanding: Despite the rapid progress of creating various augmentation approaches pragmatically, understanding their benefits remains a mystery because of a lack of analytic tools. How can we theoretically understand various data augmentations used in practice?
  • From coarse-grained to fine-grained model quality assurance: While most existing data augmentation approaches focus on improving the overall performance of a model, it is often imperative to have a finer-grained perspective on critical subpopulations of data. When a model exhibits inconsistent predictions on important subgroups of data, how can we exploit data augmentations to mitigate the performance gap in a prescribed way?
"},{"location":"optimization/data_augmentation.html#references","title":"References","text":"
  • Shorten & Khoshgoftaar, 2019, \"A survey on Image Data Augmentationfor Deep Learning\"
  • Freer & Yang, 2020, \"Data augmentation for self-paced motor imagery classification with C-LSTM\"
  • Li, 2020, \"Automating Data Augmentation: Practice, Theory and New Direction\"
  • Rebuffi et al., 2021, \"Data Augmentation Can Improve Robustness\"
  • Erdmann et al., 2018, \"Lorentz Boost Networks: Autonomous Physics-Inspired Feature Engineering\"
  • Dolan & Ore, 2021, \"Meta-learning and data augmentation for mass-generalised jet taggers\"
  • Bradshaw et al., 2019, \"Mass agnostic jet taggers\"
  • Chang et al., 2018, \"What is the Machine Learning?\"
  • Oliveira et al. 2017, \"Jet-Images \u2013 Deep Learning Edition\"
  • Barnard et al., 2016, \"Parton Shower Uncertainties in Jet Substructure Analyses with Deep Neural Networks\"
  • Chen et al., 2020, \"Data augmentation at the LHC through analysis-specific fast simulation with deep learning\"
  • Musella & Pandolfi, 2018, \"Fast and accurate simulation of particle detectors using generative adversarial networks\"
  • Hashemi et al., 2019, \"LHC analysis-specific datasets with Generative Adversarial Networks\"
  • Kansal et al., 2022, \"Particle Cloud Generation with Message Passing Generative Adversarial Networks\"
  • Rehm et al., 2021, \"Reduced Precision Strategies for Deep Learning: A High Energy Physics Generative Adversarial Network Use Case\"
  • Choi & Lim, 2021, \"A Data-driven Event Generator for Hadron Colliders using Wasserstein Generative Adversarial Network\"
  • Kansal et al., 2020, \"Graph Generative Adversarial Networks for Sparse Data Generation in High Energy Physics\"
  • Otten et al., 2021, \"Event Generation and Statistical Sampling for Physics with Deep Generative Models and a Density Information Buffer\"
  • Yap et al., 2013, \"An Application of Oversampling, Undersampling, Bagging and Boosting in Handling Imbalanced Datasets\"
  • Au et al., 2010, \"Mining Rare Events Data by Sampling and Boosting: A Case Study\"
  • Chawla et al., 2002, \"SMOTE: Synthetic Minority Over-sampling Technique\"
  • He et al., 2010, \"ADASYN: Adaptive Synthetic Sampling Approach for Imbalanced Learning\"
  • Erdman et al., 2020, \"Precise simulation of electromagnetic calorimeter showers using a Wasserstein Generative Adversarial Network\"
  • Paganini et al., 2012, \"CaloGAN: Simulating 3D High Energy Particle Showers in Multi-Layer Electromagnetic Calorimeters with Generative Adversarial Networks\"
  • Paganini et al., 2017, \"Accelerating Science with Generative Adversarial Networks: An Application to 3D Particle Showers in Multi-Layer Calorimeters\"
  • Paganini et al., 2017, \"Learning Particle Physics by Example: Location-Aware Generative Adversarial Networks for Physics Synthesis\"
  • ATLAS Collaboration, 2018, \"Deep generative models for fast shower simulation in ATLAS\"
  • SHiP Collaboration, 2019, \"Fast simulation of muons produced at the SHiP experiment using Generative Adversarial Networks\"

Content may be edited and published elsewhere by the author.

Page author: Laurits Tani, 2022

"},{"location":"optimization/importance.html","title":"Feature Importance","text":"

Feature importance is the impact a specific input field has on a prediction model's output. In general, these impacts can range from no impact (i.e. a feature with no variance) to perfect correlation with the ouput. There are several reasons to consider feature importance:

  • Important features can be used to create simplified models, e.g. to mitigate overfitting.
  • Using only important features can reduce the latency and memory requirements of the model.
  • The relative importance of a set of features can yield insight into the nature of an otherwise opaque model (improved interpretability).
  • If a model is sensitive to noise, rejecting irrelevant inputs may improve its performance.

In the following subsections, we detail several strategies for evaluating feature importance. We begin with a general discussion of feature importance at a high level before offering a code-based tutorial on some common techniques. We conclude with additional notes and comments in the last section.

"},{"location":"optimization/importance.html#general-discussion","title":"General Discussion","text":"

Most feature importance methods fall into one of three broad categories: filter methods, embedding methods, and wrapper methods. Here we give a brief overview of each category with relevant examples:

"},{"location":"optimization/importance.html#filter-methods","title":"Filter Methods","text":"

Filter methods do not rely on a specific model, instead considering features in the context of a given dataset. In this way, they may be considered to be pre-processing steps. In many cases, the goal of feature filtering is to reduce high dimensional data. However, these methods are also applicable to data exploration, wherein an analyst simply seeks to learn about a dataset without actually removing any features. This knowledge may help interpret the performance of a downstream predictive model. Relevant examples include,

  • Domain Knowledge: Perhaps the most obvious strategy is to select features relevant to the domain of interest.

  • Variance Thresholding: One basic filtering strategy is to simply remove features with low variance. In the extreme case, features with zero variance do not vary from example to example, and will therefore have no impact on the model's final prediction. Likewise, features with variance below a given threshold may not affect a model's downstream performance.

  • Fisher Scoring: Fisher scoring can be used to rank features; the analyst would then select the highest scoring features as inputs to a subsequent model.

  • Correlations: Correlated features introduce a certain degree of redundancy to a dataset, so reducing the number of strongly correlated variables may not impact a model's downstream performance.

"},{"location":"optimization/importance.html#embedded-methods","title":"Embedded Methods","text":"

Embedded methods are specific to a prediction model and independent of the dataset. Examples:

  • L1 Regularization (LASSO): L1 regularization directly penalizes large model weights. In the context of linear regression, for example, this amounts to enforcing sparsity in the output prediction; weights corresponding to less relevant features will be driven to 0, nullifying the feature's effect on the output.
"},{"location":"optimization/importance.html#wrapper-methods","title":"Wrapper Methods","text":"

Wrapper methods iterate on prediction models in the context of a given dataset. In general they may be computationally expensive when compared to filter methods. Examples:

  • Permutation Importance: Direct interpretation isn't always feasible, so other methods have been developed to inspect a feature's importance. One common and broadly-applicable method is to randomly shuffle a given feature's input values and test the degredation of model performance. This process allows us to measure permutation importance as follows. First, fit a model (\\(f\\)) to training data, yielding \\(f(X_\\mathrm{train})\\), where \\(X_\\mathrm{train}\\in\\mathbb{R}^{n\\times d}\\) for \\(n\\) input examples with \\(d\\) features. Next, measure the model's performance on testing data for some loss \\(\\mathcal{L}\\), i.e. \\(s=\\mathcal{L}\\big(f(X_\\mathrm{test}), y_\\mathrm{test}\\big)\\). For each feature \\(j\\in[1\\ ..\\ d]\\), randomly shuffle the corresponding column in \\(X_\\mathrm{test}\\) to form \\(X_\\mathrm{test}^{(j)}\\). Repeat this process \\(K\\) times, so that for \\(k\\in [1\\ ..\\ K]\\) each random shuffling of feature column \\(j\\) gives a corrupted input dataset \\(X_\\mathrm{test}^{(j,k)}\\). Finally, define the permutation importance of feature \\(j\\) as the difference between the un-corrupted validation score and average validation score over the corrupted \\(X_\\mathrm{test}^{(j,k)}\\) datasets:
\\[\\texttt{PI}_j = s - \\frac{1}{K}\\sum_{k=1}^{K} \\mathcal{L}[f(X_\\mathrm{test}^{(j,k)}), y_\\mathrm{test}]\\]
  • Recursive Feature Elimination (RFE): Given a prediction model and test/train dataset splits with \\(D\\) initial features, RFE returns the set of \\(d < D\\) features that maximize model performance. First, the model is trained on the full set of features. The importance of each feature is ranked depending on the model type (e.g. for regression, the slopes are a sufficient ranking measure; permutation importance may also be used). The least important feature is rejected and the model is retrained. This process is repeated until the most significant \\(d\\) features remain.
"},{"location":"optimization/importance.html#introduction-by-example","title":"Introduction by Example","text":""},{"location":"optimization/importance.html#direct-interpretation","title":"Direct Interpretation","text":"

Linear regression is particularly interpretable because the prediction coefficients themselves can be interpreted as a measure of feature importance. Here we will compare this direct interpretation to several model inspection techniques. In the following examples we use the Diabetes Dataset available as a Scikit-learn toy dataset. This dataset maps 10 biological markers to a 1-dimensional quantitative measure of diabetes progression:

from sklearn.datasets import load_diabetes\nfrom sklearn.model_selection import train_test_split\n\ndiabetes = load_diabetes()\nX_train, X_val, y_train, y_val = train_test_split(diabetes.data, diabetes.target, random_state=0)\nprint(X_train.shape)\n>>> (331,10)\nprint(y_train.shape)\n>>> (331,)\nprint(X_val.shape)\n>>> (111, 10)\nprint(y_val.shape)\n>>> (111,)\nprint(diabetes.feature_names)\n['age', 'sex', 'bmi', 'bp', 's1', 's2', 's3', 's4', 's5', 's6']\n
To begin, let's use Ridge Regression (L2-regularized linear regression) to model diabetes progression as a function of the input markers. The absolute value of a regression coefficient (slope) corresponding to a feature can be interpreted the impact of a feature on the final fit:

from sklearn.linear_model import Ridge\nfrom sklearn.feature_selection import RFE\n\nmodel = Ridge(alpha=1e-2).fit(X_train, y_train)\nprint(f'Initial model score: {model.score(X_val, y_val):.3f}')\n\nfor i in np.argsort(-abs(model.coef_)):\n    print(diabetes.feature_names[i], abs(model.coef_[i]))\n\n>>> Initial model score: 0.357\n>>> bmi: 592.253\n>>> s5: 580.078\n>>> bp: 297.258\n>>> s1: 252.425\n>>> sex: 203.436\n>>> s3: 145.196\n>>> s4: 97.033\n>>> age: 39.103\n>>> s6: 32.945\n>>> s2: 20.906\n
These results indicate that the bmi and s5 fields have the largest impact on the output of this regression model, while age, s6, and s2 have the smallest. Further interpretation is subject to the nature of the input data (see Common Pitfalls in the Interpretation of Coefficients of Linear Models). Note that scikit-learn has tools available to faciliate feature selections.

"},{"location":"optimization/importance.html#permutation-importance","title":"Permutation Importance","text":"

In the context of our ridge regression example, we can calculate the permutation importance of each feature as follows (based on scikit-learn docs):

from sklearn.inspection import permutation_importance\n\nmodel = Ridge(alpha=1e-2).fit(X_train, y_train)\nprint(f'Initial model score: {model.score(X_val, y_val):.3f}')\n\nr = permutation_importance(model, X_val, y_val, n_repeats=30, random_state=0)\nfor i in r.importances_mean.argsort()[::-1]:\n    print(f\"{diabetes.feature_names[i]:<8}\"\n          f\"{r.importances_mean[i]:.3f}\"\n          f\" +/- {r.importances_std[i]:.3f}\")\n\n>>> Initial model score: 0.357\n>>> s5      0.204 +/- 0.050\n>>> bmi     0.176 +/- 0.048\n>>> bp      0.088 +/- 0.033\n>>> sex     0.056 +/- 0.023\n>>> s1      0.042 +/- 0.031\n>>> s4      0.003 +/- 0.008\n>>> s6      0.003 +/- 0.003\n>>> s3      0.002 +/- 0.013\n>>> s2      0.002 +/- 0.003\n>>> age     -0.002 +/- 0.004\n
These results are roughly consistent with the direct interpretation of the linear regression parameters; s5 and bmi are the most permutation-important features. This is because both have significant permutation importance scores (0.204, 0.176) when compared to the initial model score (0.357), meaning their random permutations significantly degraded the model perforamnce. On the other hand, s2 and age have approximately no permutation importance, meaning that the model's performance was robust to random permutations of these features.

"},{"location":"optimization/importance.html#l1-enforced-sparsity","title":"L1-Enforced Sparsity","text":"

In some applications it may be useful to reject features with low importance. Models biased towards sparsity are one way to achieve this goal, as they are designed to ignore a subset of features with the least impact on the model's output. In the context of linear regression, sparsity can be enforced by imposing L1 regularization on the regression coefficients (LASSO regression):

\\[\\mathcal{L}_\\mathrm{LASSO} = \\frac{1}{2n}||y - Xw||^2_2 + \\alpha||w||_1\\]

Depending on the strength of the regularization \\((\\alpha)\\), this loss function is biased to zero-out features of low importance. In our diabetes regression example,

model = Lasso(alpha=1e-1).fit(X_train, y_train)\nprint(f'Model score: {model.score(X_val, y_val):.3f}')\n\nfor i in np.argsort(-abs(model.coef_)):\n    print(f'{diabetes.feature_names[i]}: {abs(model.coef_[i]):.3f}')\n\n>>> Model score: 0.355\n>>> bmi: 592.203\n>>> s5: 507.363\n>>> bp: 240.124\n>>> s3: 219.104\n>>> sex: 129.784\n>>> s2: 47.628\n>>> s1: 41.641\n>>> age: 0.000\n>>> s4: 0.000\n>>> s6: 0.000\n
For this value of \\(\\alpha\\), we see that the model has rejected the age, s4, and s6 features as unimportant (consistent with the permutation importance measures above) while achieving a similar model score as the previous ridge regression strategy.

"},{"location":"optimization/importance.html#recursive-feature-elimination","title":"Recursive Feature Elimination","text":"

Another common strategy is recursive feature elimination (RFE). Though RFE can be used for regression applications as well, we turn our attention to a classification task for the sake of variety. The following discussions are based on the Breast Cancer Wisconsin Diagnostic Dataset, which maps 30 numeric features corresponding to digitized breast mass images to a binary classification of benign or malignant.

from sklearn.datasets import load_breast_cancer\nfrom sklearn.svm import SVC\nfrom sklearn.model_selection import StratifiedKFold\n\ndata = load_breast_cancer()\nX_train, X_val, y_train, y_val = train_test_split(data.data, data.target, random_state=0)\nprint(X_train.shape)\n>>> (426, 30)\nprint(y_train.shape)\n>>> (426,)\nprint(X_val.shape)\n>>> (143, 30)\nprint(y_val.shape)\n>>> (143,)\nprint(breast_cancer.feature_names)\n>>> ['mean radius' 'mean texture' 'mean perimeter' 'mean area' 'mean smoothness' 'mean compactness' 'mean concavity' 'mean concave points' 'mean symmetry' 'mean fractal dimension' 'radius error' 'texture error' 'perimeter error' 'area error' 'smoothness error' 'compactness error' 'concavity error' 'concave points error' 'symmetry error' 'fractal dimension error' 'worst radius' 'worst texture' 'worst perimeter' 'worst area' 'worst smoothness' 'worst compactness' 'worst concavity' 'worst concave points' 'worst symmetry' 'worst fractal dimension']\n

Given a classifier and a classification task, recursive feature elimination (RFE, see original paper) is the process of identifying the subset of input features leading to the most performative model. Here we employ a support vector machine classifier (SVM) with a linear kernel to perform binary classification on the input data. We ask for the top \\(j\\in[1\\ .. \\ d]\\) most important features in a for loop, computing the classification accuracy when only these features are leveraged.

from sklearn.feature_selection import RFE\n\nfeatures = np.array(breast_cancer.feature_names)\nsvc = SVC(kernel='linear')\nfor n_features in np.arange(1, 30, 1):\n    rfe = RFE(estimator=svc, step=1, n_features_to_select=n_features)\n    rfe.fit(X_train, y_train)\n    print(f'n_features={n_features}, accuracy={rfe.score(X_val, y_val):.3f}')\n    print(f' - selected: {features[rfe.support_]}')\n\n>>> n_features=1, accuracy=0.881\n>>>  - selected: ['worst concave points']\n>>> n_features=2, accuracy=0.874\n>>>  - selected: ['worst concavity' 'worst concave points']\n>>> n_features=3, accuracy=0.867\n>>>  - selected: ['mean concave points' 'worst concavity' 'worst concave points']\n ...\n>>> n_features=16, accuracy=0.930\n>>> n_features=17, accuracy=0.965\n>>> n_features=18, accuracy=0.951\n...\n>>> n_features=27, accuracy=0.958\n>>> n_features=28, accuracy=0.958\n>>> n_features=29, accuracy=0.958\n
Here we've shown a subset of the output. In the first output lines, we see that the 'worst concave points' feature alone leads to 88.1% accuracy. Including the next two most important features actually degrades the classification accuracy. We then skip to the top 17 features, which in this case we observe to yield the best performance for the linear SVM classifier. The addition of more features does not lead to additional perforamnce boosts. In this way, RFE can be treated as a model wrapper introducing an additional hyperparameter, n_features_to_select, which can be used to optimize model performance. A more principled optimization using k-fold cross validation with RFE is available in the scikit-learn docs.

"},{"location":"optimization/importance.html#feature-correlations","title":"Feature Correlations","text":"

In the above, we have focused specifically on interpreting the importance of single features. However, it may be that several features are correlated, sharing the responsibility for the overall prediction of the model. In this case, some measures of feature importance may inappropriately downweight correlated features in a so-called correlation bias (see Classification with Correlated Features: Unrelability of Feature Ranking and Solutions). For example, the permutation invariance of \\(d\\) correlated features is shown to decrease (as a function of correlation strength) faster for higher \\(d\\) (see Correlation and Variable importance in Random Forests).

We can see these effects in action using the breast cancer dataset, following the corresponding scikit-learn example

from sklearn.ensemble import RandomForestClassifier\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.datasets import load_breast_cancer\n\ndata = load_breast_cancer()\nX, y = data.data, data.target\nX_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)\n\nclf = RandomForestClassifier(n_estimators=100, random_state=42)\nclf.fit(X_train, y_train)\nprint(\"Accuracy on test data: {:.2f}\".format(clf.score(X_test, y_test)))\n\n>>> Accuracy on test data: 0.97\n
Here we've implemented a random forest classifier and achieved a high accuracy (97%) on the benign vs. malignent predictions. The permutation importances for the 10 most important training features are:

r = permutation_importance(clf, X_train, y_train, n_repeats=10, random_state=42)\nfor i in r.importances_mean.argsort()[::-1][:10]:\n    print(f\"{breast_cancer.feature_names[i]:<8}\"\n          f\"  {r.importances_mean[i]:.5f}\"\n          f\" +/- {r.importances_std[i]:.5f}\")\n\n>>> worst concave points  0.00681 +/- 0.00305\n>>> mean concave points  0.00329 +/- 0.00188\n>>> worst texture  0.00258 +/- 0.00070\n>>> radius error  0.00235 +/- 0.00000\n>>> mean texture  0.00188 +/- 0.00094\n>>> mean compactness  0.00188 +/- 0.00094\n>>> area error  0.00188 +/- 0.00094\n>>> worst concavity  0.00164 +/- 0.00108\n>>> mean radius  0.00141 +/- 0.00115\n>>> compactness error  0.00141 +/- 0.00115\n

In this case, even the most permutation important features have mean importance scores \\(<0.007\\), which doesn't indicate much importance. This is surprising, because we saw via RFE that a linear SVM can achieve \\(\\approx 88\\%\\) classification accuracy with this feature alone. This indicates that worst concave points, in addition to other meaningful features, may belong to subclusters of correlated features. In the corresponding scikit-learn example, the authors show that subsets of correlated features can be extracted by calculating a dendogram and selecting representative features from each correlated subset. They achieve \\(97\\%\\) accuracy (the same as with the full dataset) by selecting only five such representative variables.

"},{"location":"optimization/importance.html#feature-importance-in-decision-trees","title":"Feature Importance in Decision Trees","text":"

Here we focus on decision trees, which are particularly interpretable classifiers that often appear as ensembles (or boosted decision tree (BDT) algorithms) in HEP. Consider a classification dataset \\(X=\\{x_n\\}_{n=1}^{N}\\), \\(x_n\\in\\mathbb{R}^{D}\\), with truth labels \\(Y=\\{y_n\\}_{n=1}^N\\), \\(y_n\\in\\{1,...,C\\}\\) corresponding \\(C\\) classes. These truth labels naturally partition \\(X\\) into subsets \\(X_c\\) with class probabilities \\(p(c)=|X_c|/|X|\\). Decision trees begin with a root node \\(t_0\\) containing all of \\(X\\). The tree is grown from the root by recursively splitting the input set \\(X\\) in a principled way; internal nodes (or branch nodes) correspond to a decision of the form

\\[\\begin{aligned} &(x_n)_d\\leq\\delta \\implies\\ \\text{sample}\\ n\\ \\text{goes to left child node}\\\\ &(x_n)_d>\\delta \\implies\\ \\text{sample}\\ n\\ \\text{goes to right child node} \\end{aligned}\\]

We emphasize that the decision boundary is drawn by considering a single feature field \\(d\\) and partitioning the \\(n^\\mathrm{th}\\) sample by the value at that feature field. Decision boundaries at each internal parent node \\(t_P\\) are formed by choosing a \"split criterion,\" which describes how to partition the set of elements at this node into left and right child nodes \\(t_L\\), \\(t_R\\) with \\(X_{t_L}\\subset X_{t_P}\\) and \\(X_{t_R}\\subset X_{t_P}\\), \\(X_{t_L}\\cup X_{t_R}=X_{t_P}\\). This partitioning is optimal if \\(X_{t_L}\\) and \\(X_{t_R}\\) are pure, each containing only members of the same class. Impurity measures are used to evaluate the degree to which the set of data points at a given tree node \\(t\\) are not pure. One common impurity measure is Gini Impurity,

\\[\\begin{aligned} I(t) = \\sum_{c=1}^C p(c|t)(1-p(c|t)) \\end{aligned}\\]

Here, \\(p(c|t)\\) is the probability of drawing a member of class \\(c\\) from the set of elements at node \\(t\\). For example, the Gini impurity at the root node (corresponding to the whole dataset) is

\\[\\begin{aligned} I(t_0) = \\sum_{c=1}^C \\frac{|X_c|}{|X|}(1-\\frac{|X_c|}{|X|}) \\end{aligned}\\]

In a balanced binary dataset, this would give \\(I(t_0)=1/2\\). If the set at node \\(t\\) is pure, i.e. class labels corresponding to \\(X_t\\) are identical, then \\(I(t)=0\\). We can use \\(I(t)\\) to produce an optimal splitting from parent \\(t_p\\) to children \\(t_L\\) and \\(t_R\\) by defining an impurity gain,

\\[\\begin{aligned} \\Delta I = I(t_P) - I(t_L) - I(t_R) \\end{aligned}\\]

This quantity describes the relative impurity between a parent node and its children. If \\(X_{t_P}\\) contains only two classes, an optimal splitting would separate them into \\(X_{p_L}\\) and \\(X_{p_R}\\), producing pure children nodes with \\(I(t_L)=I(t_R)=0\\) and, correspondingly, \\(\\Delta I(t_p) = I(t_P)\\). Accordingly, good splitting decisions should maximize impurity gain. Note that the impurity gain is often weighted, for example Scikit-Learn defines:

\\[\\begin{aligned} \\Delta I(t_p) = \\frac{|X_{t_p}|}{|X|}\\bigg(I(t_p) - \\frac{|X_{t_L}|}{|X_{t_p}|} I(t_L) - \\frac{|X_{t_R}|}{|X_{t_p}|} I(t_R) \\bigg) \\end{aligned}\\]

In general, a pure node cannot be split further and must therefore be a leaf. Likewise, a node for which there is no splitting yielding \\(\\Delta I > 0\\) must be labeled a leaf. These splitting decisions are made recursively at each node in a tree until some stopping condition is met. Stopping conditions may include maximum tree depths or leaf node counts, or threshhold on the maximum impurity gain.

Impurity gain gives us insight into the importance of a decision. In particular, larger \\(\\Delta I\\) indicates a more important decision. If some feature \\((x_n)_d\\) is the basis for several decision splits in a decision tree, the sum of impurity gains at these splits gives insight into the importance of this feature. Accordingly, one measure of the feature importance of \\(d\\) is the average (with respect to the total number of internal nodes) impurity gain imparted by decision split on \\(d\\). This method generalizes to the case of BDTs, in which case one would average this quantity across all weak learner trees in the ensemble.

Note that though decision trees are based on the feature \\(d\\) producing the best (maximum impurity gain) split at a given branch node, surrogate splits are often used to retain additional splits corresponding to features other than \\(d\\). Denote the feature maximizing the impurity gain \\(d_1\\) and producing a split boundary \\(\\delta_1\\). Surrogte splitting involves tracking secondary splits with boundaries \\(\\delta_2, \\delta_3,...\\) corresponding to \\(d_2,d_3,...\\) that have the highest correlation with the maximum impurity gain split. The upshot is that in the event that input data is missing a value at field \\(d_1\\), there are backup decision boundaries to use, mitigating the need to define multiple trees for similar data. Using this generalized notion of a decision tree, wherein each branch node contains a primary decision boundary maximizing impurity gain and several additional surrogate split boundaries, we can average the impurity gain produced at feature field \\(d\\) over all its occurances as a decision split or a surrogate split. This definition of feature importance generalizes the previous to include additional correlations.

"},{"location":"optimization/importance.html#example","title":"Example","text":"

Let us now turn to an example:

import numpy as np\nimport matplotlib.pyplot as plt\nfrom sklearn.tree import DecisionTreeClassifier\nfrom sklearn.datasets import load_wine\nfrom sklearn.inspection import DecisionBoundaryDisplay\nfrom sklearn.metrics import log_loss\nfrom sklearn.model_selection import train_test_split\n\nwine_data = load_wine() \nprint(wine_data.data.shape)\nprint(wine_data.feature_names)\nprint(np.unique(wine_data.target))\n>>> (178, 13)\n>>> ['alcohol', 'malic_acid', 'ash', 'alcalinity_of_ash', 'magnesium', 'total_phenols', 'flavanoids', 'nonflavanoid_phenols', 'proanthocyanins', 'color_intensity', 'hue', 'od280/od315_of_diluted_wines', 'proline']\n>>> [0 1 2]\n

This sklearn wine dataset has 178 entries with 13 features and truth labels corresponding to membership in one of \\(C=3\\) classes. We can train a decision tree classifier as follows:

X, y = wine_data.data, wine_data.target\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)\nclassifier = DecisionTreeClassifier(criterion='gini', splitter='best', random_state=27)\nclassifier.fit(X_train, y_train)\nX_test_pred = classifier.predict(X_test)\nprint('Test Set Performance')\nprint('Number misclassified:', sum(X_test_pred!=y_test))\nprint(f'Accuracy: {classifier.score(X_test, y_test):.3f}')\n>>> Test Set Performance\n>>> Number misclassified: 0\n>>> Accuracy: 1.000\n

In this case, the classifier has generalized perfectly, fitting the test set with \\(100\\%\\) accuracy. Let's take a look into how it makes predictions:

tree = classifier.tree_\nn_nodes = tree.node_count\nnode_features = tree.feature\nthresholds = tree.threshold\nchildren_L = tree.children_left\nchildren_R = tree.children_right\nfeature_names = np.array(wine_data.feature_names)\n\nprint(f'The tree has {n_nodes} nodes')\nfor n in range(n_nodes):\n    if children_L[n]==children_R[n]: continue # leaf node\n    print(f'Decision split at node {n}:',\n          f'{feature_names[node_features[n]]}({node_features[n]}) <=',\n          f'{thresholds[n]:.2f}')\n\n>>> The tree has 13 nodes\n>>> Decision split at node 0: color_intensity(9) <= 3.46\n>>> Decision split at node 2: od280/od315_of_diluted_wines(11) <= 2.48\n>>> Decision split at node 3: flavanoids(6) <= 1.40\n>>> Decision split at node 5: color_intensity(9) <= 7.18\n>>> Decision split at node 8: proline(12) <= 724.50\n>>> Decision split at node 9: malic_acid(1) <= 3.33\n

Here we see that several features are used to generate decision boundaries. For example, the dataset is split at the root node by a cut on the \\(\\texttt{color_intensity}\\) feature. The importance of each feature can be taken to be the average impurity gain it generates across all nodes, so we expect that one (or several) of the five unique features used at the decision splits will be the most important features by this definition. Indeed, we see,

feature_names = np.array(wine_data.feature_names)\nimportances = classifier.feature_importances_\nfor i in range(len(importances)):\n    print(f'{feature_names[i]}: {importances[i]:.3f}')\nprint('\\nMost important features', \n      feature_names[np.argsort(importances)[-3:]])\n\n>>> alcohol: 0.000\n>>> malic_acid: 0.021\n>>> ash: 0.000\n>>> alcalinity_of_ash: 0.000\n>>> magnesium: 0.000\n>>> total_phenols: 0.000\n>>> flavanoids: 0.028\n>>> nonflavanoid_phenols: 0.000\n>>> proanthocyanins: 0.000\n>>> color_intensity: 0.363\n>>> hue: 0.000\n>>> od280/od315_of_diluted_wines: 0.424\n>>> proline: 0.165\n\n>>> Most important features ['proline' 'color_intensity' 'od280/od315_of_diluted_wines']\n

This is an embedded method for generating feature importance - it's cooked right into the decision tree model. Let's verify these results using a wrapper method, permutation importance:

from sklearn.inspection import permutation_importance\n\nprint(f'Initial classifier score: {classifier.score(X_test, y_test):.3f}')\n\nr = permutation_importance(classifier, X_test, y_test, n_repeats=30, random_state=0)\nfor i in r.importances_mean.argsort()[::-1]:\n    print(f\"{feature_names[i]:<8}\"\n          f\" {r.importances_mean[i]:.3f}\"\n          f\" +/- {r.importances_std[i]:.3f}\")\n\n>>> Initial classifier score: 1.000\n\n>>> color_intensity 0.266 +/- 0.040\n>>> od280/od315_of_diluted_wines 0.237 +/- 0.049\n>>> proline  0.210 +/- 0.041\n>>> flavanoids 0.127 +/- 0.025\n>>> malic_acid 0.004 +/- 0.008\n>>> hue      0.000 +/- 0.000\n>>> proanthocyanins 0.000 +/- 0.000\n>>> nonflavanoid_phenols 0.000 +/- 0.000\n>>> total_phenols 0.000 +/- 0.000\n>>> magnesium 0.000 +/- 0.000\n>>> alcalinity_of_ash 0.000 +/- 0.000\n>>> ash      0.000 +/- 0.000\n>>> alcohol  0.000 +/- 0.000\n

The tree's performance is hurt the most if the \\(\\texttt{color_intensity}\\), \\(\\texttt{od280/od315_of_diluted_wines}\\), or \\(\\texttt{proline}\\) features are permuted, consistent with the impurity gain measure of feature importance.

"},{"location":"optimization/model_optimization.html","title":"Model optimization","text":"

This page summarizes the concepts shown in a contribution on Bayesian Optimization to the ML Forum and may be edited and published elsewhere by the author.

"},{"location":"optimization/model_optimization.html#what-we-talk-about-when-we-talk-about-model-optimization","title":"What we talk about when we talk about model optimization","text":"

Given some data \\(x\\) and a family of functionals parameterized by (a vector of) parameters \\(\\theta\\) (e.g. for DNN training weights), the problem of learning consists in finding \\(argmin_\\theta Loss(f_\\theta(x) - y_{true})\\). The treatment below focusses on gradient descent, but the formalization is completely general, i.e. it can be applied also to methods that are not explicitly formulated in terms of gradient descent (e.g. BDTs). The mathematical formalism for the problem of learning is briefly explained in a contribution on statistical learning to the ML forum: for the purposes of this documentation we will proceed through two illustrations.

The first illustration, elaborated from an image by the huawei forums shows the general idea behind learning through gradient descent in a multidimensional parameter space, where the minimum of a loss function is found by following the function's gradient until the minimum.

The cartoon illustrates the general idea behind gradient descent to find the minimum of a function in a multidimensional parameter space (figure elaborated from an image by the huawei forums).

The model to be optimized via a loss function typically is a parametric function, where the set of parameters (e.g. the network weights in neural networks) corresponds to a certain fixed structure of the network. For example, a network with two inputs, two inner layers of two neurons, and one output neuron will have six parameters whose values will be changed until the loss function reaches its minimum.

When we talk about model optimization we refer to the fact that often we are interested in finding which model structure is the best to describe our data. The main concern is to design a model that has a sufficient complexity to store all the information contained in the training data. We can therefore think of parameterizing the network structure itself, e.g. in terms of the number of inner layers and number of neurons per layer: these hyperparameters define a space where we want to again minimize a loss function. Formally, the parametric function \\(f_\\theta\\) is also a function of these hyperparameters \\(\\lambda\\): \\(f_{(\\theta, \\lambda)}\\), and the \\(\\lambda\\) can be optimized

The second illustration, also elaborated from an image by the huawei forums, broadly illustrates this concept: for each point in the hyperparameters space (that is, for each configuration of the model), the individual model is optimized as usual. The global minimum over the hyperparameters space is then sought.

The cartoon illustrates the general idea behind gradient descent to optimize the model complexity (in terms of the choice of hyperparameters) multidimensional parameter and hyperparameter space (figure elaborated from an image by the huawei forums)."},{"location":"optimization/model_optimization.html#caveat-which-data-should-you-use-to-optimize-your-model","title":"Caveat: which data should you use to optimize your model","text":"

In typical machine learning studies, you should divide your dataset into three parts. One is used for training the model (training sample), one is used for testing the performance of the model (test sample), and the third one is the one where you actually use your trained model, e.g. for inference (application sample). Sometimes you may get away with using test data as application data: Helge Voss (Chap 5 of Behnke et al.) states that this is acceptable under three conditions that must be simultaneously valid:

  • no hyperparameter optimization is performed;
  • no overtraining is found;
  • the number of training data is high enough to make statistical fluctuations negligible.

If you are doing any kind of hyperparamters optimization, thou shalt NOT use the test sample as application sample. You should have at least three distinct sets, and ideally you should use four (training, testing, hyperparameter optimization, application).

"},{"location":"optimization/model_optimization.html#grid-search","title":"Grid Search","text":"

The most simple hyperparameters optimization algorithm is the grid search, where you train all the models in the hyperparameters space to build the full landscape of the global loss function, as illustrated in Goodfellow, Bengio, Courville: \"Deep Learning\".

The cartoon illustrates the general idea behind grid search (image taken from Goodfellow, Bengio, Courville: \"Deep Learning\").

To perform a meaningful grid search, you have to provide a set of values within the acceptable range of each hyperparameters, then for each point in the cross-product space you have to train the corresponding model.

The main issue with grid search is that when there are nonimportant hyperparameters (i.e. hyperparameters whose value doesn't influence much the model performance) the algorithm spends an exponentially large time (in the number of nonimportant hyperparameters) in the noninteresting configurations: having \\(m\\) parameters and testing \\(n\\) values for each of them leads to \\(\\mathcal{O}(n^m)\\) tested configurations. While the issue may be mitigated by parallelization, when the number of hyperparameters (the dimension of hyperparameters space) surpasses a handful, even parallelization can't help.

Another issue is that the search is binned: depending on the granularity in the scan, the global minimum may be invisible.

Despite these issues, grid search is sometimes still a feasible choice, and gives its best when done iteratively. For example, if you start from the interval \\(\\{-1, 0, 1\\}\\):

  • if the best parameter is found to be at the boundary (1), then extend range (\\(\\{1, 2, 3\\}\\)) and do the search in the new range;
  • if the best parameter is e.g. at 0, then maybe zoom in and do a search in the range \\(\\{-0.1, 0, 0.1\\}\\).
"},{"location":"optimization/model_optimization.html#random-search","title":"Random search","text":"

An improvement of the grid search is the random search, which proceeds like this:

  • you provide a marginal p.d.f. for each hyperparameter;
  • you sample from the joint p.d.f. a certain number of training configurations;
  • you train for each of these configurations to build the loss function landscape.

This procedure has significant advantages over a simple grid search: random search is not binned, because you are sampling from a continuous p.d.f., so the pool of explorable hyperparameter values is larger; random search is exponentially more efficient, because it tests a unique value for each influential hyperparameter on nearly every trial.

Random search also work best when done iteratively. The differences between grid and random search are again illustrated in Goodfellow, Bengio, Courville: \"Deep Learning\".

The cartoon illustrates the general idea behind random search, as opposed to grid search (image taken from Goodfellow, Bengio, Courville: \"Deep Learning\")."},{"location":"optimization/model_optimization.html#model-based-optimization-by-gradient-descent","title":"Model-based optimization by gradient descent","text":"

Now that we have looked at the most basic model optimization techniques, we are ready to look into using gradient descent to solve a model optimization problem. We will proceed by recasting the problem as one of model selection, where the hyperparameters are the input (decision) variables, and the model selection criterion is a differentiable validation set error. The validation set error attempts to describe the complexity of the network by a single hyperparameter (details in [a contribution on statistical learning to the ML forum]) The problem may be solved with standard gradient descent, as illustrated above, if we assume that the training criterion \\(C\\) is continuous and differentiable with respect to both the parameters \\(\\theta\\) (e.g. weights) and hyperparameters \\(\\lambda\\) Unfortunately, the gradient is seldom available (either because it has a prohibitive computational cost, or because it is non-differentiable as is the case when there are discrete variables).

A diagram illustrating the way gradient-based model optimization works has been prepared by Bengio, doi:10.1162/089976600300015187.

The diagram illustrates the way model optimization can be recast as a model selection problem, where a model selection criterion involves a differentiable validation set error (image taken from Bengio, doi:10.1162/089976600300015187)."},{"location":"optimization/model_optimization.html#model-based-optimization-by-surrogates","title":"Model-based optimization by surrogates","text":"

Sequential Model-based Global Optimization (SMBO) consists in replacing the loss function with a surrogate model of it, when the loss function (i.e. the validation set error) is not available. The surrogate is typically built as a Bayesian regression model, when one estimates the expected value of the validation set error for each hyperparameter together with the uncertainty in this expectation. The pseudocode for the SMBO algorithm is illustrated by Bergstra et al.

The diagram illustrates the pseudocode for the Sequential Model-based Global Optimization (image taken from Bergstra et al).

This procedure results in a tradeoff between: exploration, i.e. proposing hyperparameters with high uncertainty, which may result in substantial improvement or not; and exploitation (propose hyperparameters that will likely perform as well as the current proposal---usually this mean close to the current ones). The disadvantage is that the whole procedure must run until completion before giving as an output any usable information. By comparison, manual or random searches tend to give hints on the location of the minimum faster.

"},{"location":"optimization/model_optimization.html#bayesian-optimization","title":"Bayesian Optimization","text":"

We are now ready to tackle in full what is referred to as Bayesian optimization.

Bayesian optimization assumes that the unknown function \\(f(\\theta, \\lambda)\\) was sampled from a Gaussian process (GP), and that after the observations it maintains the corresponding posterior. In this context, observations are the various validation set errors for different values of the hyperparameters \\(\\lambda\\). In order to pick the next value to probe, one maximizes some estimate of the expected improvement (see below). To understand the meaning of \"sampled from a Gaussian process\", we need to define what a Gaussian process is.

"},{"location":"optimization/model_optimization.html#gaussian-processes","title":"Gaussian processes","text":"

Gaussian processes (GPs) generalize the concept of Gaussian distribution over discrete random variables to the concept of Gaussian distribution over continuous functions. Given some data and an estimate of the Gaussian noise, by fitting a function one can estimate also the noise at the interpolated points. This estimate is made by similarity with contiguous points, adjusted by the distance between points. A GP is therefore fully described by its mean and its covariance function. An illustration of Gaussian processes is given in Kevin Jamieson's CSE599 lecture notes.

The diagram illustrates the evolution of a Gaussian process, when adding interpolating points (image taken from Kevin Jamieson's CSE599 lecture notes).

GPs are great for Bayesian optimization because they out-of-the-box provide the expected value (i.e. the mean of the process) and its uncertainty (covariance function).

"},{"location":"optimization/model_optimization.html#the-basic-idea-behind-bayesian-optimization","title":"The basic idea behind Bayesian optimization","text":"

Gradient descent methods are intrinsically local: the decision on the next step is taken based on the local gradient and Hessian approximations- Bayesian optimization (BO) with GP priors uses a model that uses all the information from the previous steps by encoding it in the model giving the expectation and its uncertainty. The consequence is that GP-based BO can find the minimum of difficult nonconvex functions in relatively few evaluations, at the cost of performing more computations to find the next point to try in the hyperparameters space.

The BO prior is a prior over the space of the functions. GPs are especially suited to play the role of BO prior, because marginals and conditionals can be computed in closed form (thanks to the properties of the Gaussian distribution).

There are several methods to choose the acquisition function (the function that selects the next step for the algorithm), but there is no omnipurpose recipe: the best approach is problem-dependent. The acquisition function involves an accessory optimization to maximize a certain quantity; typical choices are:

  • maximize the probability of improvement over the current best value: can be calculated analytically for a GP;
  • maximize the expected improvement over the current best value: can also be calculated analytically for a GP;
  • maximize the GP Upper confidence bound: minimize \"regret\" over the course of the optimization.
"},{"location":"optimization/model_optimization.html#historical-note","title":"Historical note","text":"

Gaussian process regression is also called kriging in geostatistics, after Daniel G. Krige (1951) who pioneered the concept later formalized by Matheron (1962)

"},{"location":"optimization/model_optimization.html#bayesian-optimization-in-practice","title":"Bayesian optimization in practice","text":"

The figure below, taken by a tutorial on BO by Martin Krasser, clarifies rather well the procedure. The task is to approximate the target function (labelled noise free objective in the figure), given some noisy samples of it (the black crosses). At the first iteration, one starts from a flat surrogate function, with a given uncertainty, and fits it to the noisy samples. To choose the next sampling location, a certain acquisition function is computed, and the value that maximizes it is chosen as the next sampling location At each iteration, more noisy samples are added, until the distance between consecutive sampling locations is minimized (or, equivalently, a measure of the value of the best selected sample is maximized).

Practical illustration of Bayesian Optimization (images taken from a tutorial on BO by Martin Krasser])."},{"location":"optimization/model_optimization.html#limitations-and-some-workaround-of-bayesian-optimization","title":"Limitations (and some workaround) of Bayesian Optimization","text":"

There are three main limitations to the BO approach. A good overview of these limitations and of possible solutions can be found in arXiv:1206.2944.

First of all, it is unclear what is an appropriate choice for the covariance function and its associated hyperparameters. In particular, the standard squared exponential kernel is often too smooth. As a workaround, alternative kernels may be used: a common choice is the Mat\u00e9rn 5/2 kernel, which is similar to the squared exponential one but allows for non-smoothness.

Another issue is that, for certain problems, the function evaluation may take very long to compute. To overcome this, often one can replace the function evaluation with the Monte Carlo integration of the expected improvement over the GP hyperparameters, which is faster.

The third main issue is that for complex problems one would ideally like to take advantage of parallel computation. The procedure is iterative, however, and it is not easy to come up with a scheme to make it parallelizable. The referenced paper proposed sampling over the expected acquisition, conditioned on all the pending evaluations: this is computationally cheap and is intrinsically parallelizable.

"},{"location":"optimization/model_optimization.html#alternatives-to-gaussian-processes-tree-based-models","title":"Alternatives to Gaussian processes: Tree-based models","text":"

Gaussian Processes model directly \\(P(hyperpar | data)\\) but are not the only suitable surrogate models for Bayesian optimization

The so-called Tree-structured Parzen Estimator (TPE), described in Bergstra et al, models separately \\(P(data | hyperpar)\\) and \\(P(hyperpar)\\), to then obtain the posterior by explicit application of the Bayes theorem TPEs exploit the fact that the choice of hyperparameters is intrinsically graph-structured, in the sense that e.g. you first choose the number of layers, then choose neurons per layer, etc. TPEs run over this generative process by replacing the hyperparameters priors with nonparametric densities. These generative nonparametric densities are built by classifying them into those that result in worse/better loss than the current proposal.

TPEs have been used in CMS already around 2017 in a VHbb analysis (see repository by Sean-Jiun Wang) and in a charged Higgs to tb search (HIG-18-004, doi:10.1007/JHEP01(2020)096).

"},{"location":"optimization/model_optimization.html#implementations-of-bayesian-optimization","title":"Implementations of Bayesian Optimization","text":"
  • Implementations in R are readily available as the R-studio tuning package;
  • Scikit-learn provides a handy implementation of Gaussian processes;
  • **scipy* provides a handy implementation of the optimization routines;
  • hyperopt provides a handy implementation of distributed hyperparameter optimization routines;
    • GPs not coded by default, hence must rely on scikit-learn;
    • Parzen tree estimators are implemented by default (together with random search);
  • Several handy tutorials online focussed on hyperparameters optimization
    • Tutorial by Martin Krasser;
    • Tutorial by Jason Brownlee;
  • Early example of hyperopt in CMS
    • VHbb analysis: repository by Sean-Jiun Wang), for optimization of a BDT;
    • Charged Higgs HIG-18-004, doi:10.1007/JHEP01(2020)096) for optimization of a DNN (no public link for the code, contact me if needed)
  • Several expansions and improvements (particularly targeted at HPC clusters) are available, see e.g. this talk by Eric Wulff.
"},{"location":"optimization/model_optimization.html#caveats-dont-get-too-obsessed-with-model-optimization","title":"Caveats: don't get too obsessed with model optimization","text":"

In general, optimizing model structure is a good thing. F. Chollet e.g. says \"If you want to get to the very limit of what can be achieved on a given task, you can't be content with arbitrary choices made by a fallible human\". On the other side, for many problems hyperparameter optimization does result in small improvements, and there is a tradeoff between improvement and time spent on the task: sometimes the time spent on optimization may not be worth, e.g. when the gradient of the loss in hyperparameters space is very flat (i.e. different hyperparameter sets give more or less the same results), particularly if you already know that small improvements will be eaten up by e.g. systematic uncertainties. On the other side, before you perform the optimization you don't know if the landscape is flat or if you can expect substantial improvements. Sometimes broad grid or random searches may give you a hint on whether the landscape of hyperparameters space is flat or not.

Sometimes you may get good (and faster) improvements by model ensembling rather than by model optimization. To do model ensembling, you first train a handful models (either different methods---BDT, SVM, NN, etc---or different hyperparameters sets): \\(pred\\_a = model\\_a.predict(x)\\), ..., \\(pred\\_d = model\\_d.predict(x)\\). You then pool the predictions: \\(pooled\\_pred = (pred\\_a + pred\\_b + pred\\_c + pred\\_d)/4.\\). THis works if all models are kind of good: if one is significantly worse than the others, then \\(pooled\\_pred\\) may not be as good as the best model of the pool.

You can also find ways of ensembling in a smarter way, e.g. by doing weighted rather than simple averages: \\(pooled\\_pred = 0.5\\cdot pred\\_a + 0.25\\cdot pred\\_b + 0.1\\cdot pred\\_c + 0.15\\cdot pred\\_d)/4.\\). Here the idea is to give more weight to better classifiers. However, you transfer the problem to having to choose the weights. These can be found empirically empirically by using random search or other algorithms like Nelder-Mead (result = scipy.optimize.minimize(objective, pt, method='nelder-mead'), where you build simplexes (polytope with N+1 vertices in N dimensions, generalization of triangle) and stretch them towards higher values of the objective. Nelder-Mead can converge to nonstationary points, but there are extensions of the algorithm that may help.

This page summarizes the concepts shown in a contribution on Bayesian Optimization to the ML Forum. Content may be edited and published elsewhere by the author. Page author: Pietro Vischia, 2022

"},{"location":"resources/cloud_resources/index.html","title":"Cloud Resources","text":"

Work in progress.

"},{"location":"resources/dataset_resources/index.html","title":"CMS-ML Dataset Tab","text":""},{"location":"resources/dataset_resources/index.html#introduction","title":"Introduction","text":"

Welcome to CMS-ML Dataset tab! Our tab is designed to provide accurate, up-to-date, and relevant data across various purposes. We strive to make this tab resourceful for your analysis and decision-making needs. We are working on benchmarking more dataset and presenting them in a user-friendly format. This tab will be continuously updated to reflect the latest developments. Explore, analyze, and derive insights with ease!

"},{"location":"resources/dataset_resources/index.html#1-jetnet","title":"1. JetNet","text":""},{"location":"resources/dataset_resources/index.html#links","title":"Links","text":"

Github Repository

Zenodo

"},{"location":"resources/dataset_resources/index.html#description","title":"Description","text":"

JetNet is a project aimed at enhancing accessibility and reproducibility in jet-based machine learning. It offers easy-to-access and standardized interfaces for several datasets, including JetNet, TopTagging, and QuarkGluon. Additionally, JetNet provides standard implementations of various generative evaluation metrics such as Fr\u00e9chet Physics Distance (FPD), Kernel Physics Distance (KPD), Wasserstein-1 (W1), Fr\u00e9chet ParticleNet Distance (FPND), coverage, and Minimum Matching Distance (MMD). Beyond these, it includes a differentiable implementation of the energy mover's distance and other general jet utilities, making it a comprehensive resource for researchers and practitioners in the field.

"},{"location":"resources/dataset_resources/index.html#nature-of-objects","title":"Nature of Objects","text":"
  • Objects: Gluon (g), Top Quark (t), Light Quark (q), W boson (w), and Z boson (z) jets of ~1 TeV transverse momentum (\\(p_T\\))
  • Number of Objects: N = 177252, 177945, 170679, 177172, 176952 for g, t, q, w, z jets respectively.
"},{"location":"resources/dataset_resources/index.html#format-of-dataset","title":"Format of Dataset","text":"
  • File Type: HDF5
  • Structure: Each file has particle_features; and jet_features; arrays, containing the list of particles' features per jet and the corresponding jet's features, respectively. Particle_features is of shape [N, 30, 4], where N is the total number of jets, 30 is the max number of particles per jet, and 4 is the number of particle features, in order: []\\eta, \\varphi, \\p_T, mask]. See Zenodo for definitions of these. jet_features is of shape [N, 4], where 4 is the number of jet features, in order: [\\(p_T\\), \\(\\eta\\), mass, # of particles].
"},{"location":"resources/dataset_resources/index.html#related-projects","title":"Related Projects","text":"
  • Top tagging benchmark
  • Particle Cloud Generation with Message Passing Generative Adversarial Networks
"},{"location":"resources/dataset_resources/index.html#2-top-tagging-benchmark-dataset","title":"2. Top Tagging Benchmark Dataset","text":""},{"location":"resources/dataset_resources/index.html#links_1","title":"Links","text":"

Zenodo

"},{"location":"resources/dataset_resources/index.html#description_1","title":"Description","text":"

A set of MC simulated training/testing events for the evaluation of top quark tagging architectures. - 14 TeV, hadronic tops for signal, qcd diets background, Delphes ATLAS detector card with Pythia8 - No MPI/pile-up included - Clustering of particle-flow entries (produced by Delphes E-flow) into anti-kT 0.8 jets in the pT range [550,650] GeV - All top jets are matched to a parton-level top within \u2206R = 0.8, and to all top decay partons within 0.8 - Jets are required to have |eta| < 2 - The leading 200 jet constituent four-momenta are stored, with zero-padding for jets with fewer than 200 - Constituents are sorted by pT, with the highest pT one first - The truth top four-momentum is stored as truth_px etc. - A flag (1 for top, 0 for QCD) is kept for each jet. It is called is_signal_new - The variable \"ttv\" (= test/train/validation) is kept for each jet. It indicates to which dataset the jet belongs. It is redundant as the different sets are already distributed as different files.

"},{"location":"resources/dataset_resources/index.html#nature-of-objects_1","title":"Nature of Objects","text":"
  • Objects: 14 TeV, hadronic tops for signal, qcd diets background, Delphes ATLAS detector card with Pythia8
  • Number of Objects: In total 1.2M training events, 400k validation events and 400k test events.
"},{"location":"resources/dataset_resources/index.html#format-of-dataset_1","title":"Format of Dataset","text":"
  • File Type: HDF5
  • Structure: Use \u201ctrain\u201d for training, \u201cval\u201d for validation during the training and \u201ctest\u201d for final testing and reporting results. For details, see the Zenodo link
"},{"location":"resources/dataset_resources/index.html#related-projects_1","title":"Related Projects","text":"
  • Butter, Anja; Kasieczka, Gregor; Plehn, Tilman and Russell, Michael (2017). Based on data from 10.21468/SciPostPhys.5.3.028 (1707.08966)
  • Kasieczka, Gregor et al (2019). Dataset used for arXiv:1902.09914 (The Machine Learning Landscape of Top Taggers)
"},{"location":"resources/dataset_resources/index.html#more-dataset-coming-in","title":"More dataset coming in!","text":"

Have any questions? Want your dataset shown on this page? Contact the ML Knowledge Subgroup!

"},{"location":"resources/fpga_resources/index.html","title":"FPGA Resource","text":"

Work in progress.

"},{"location":"resources/gpu_resources/cms_resources/lxplus_gpu.html","title":"lxplus-gpu.cern.ch","text":""},{"location":"resources/gpu_resources/cms_resources/lxplus_gpu.html#how-to-use-it","title":"How to use it?","text":"

lxplus-gpu are special lxplus nodes with GPU support. You can access these nodes by executing

ssh <your_user_name>@lxplus-gpu.cern.ch\n

The configuration of the software environment for lxplus-gpu is described in the Software Environments page.

"},{"location":"resources/gpu_resources/cms_resources/lxplus_htcondor.html","title":"HTCondor With GPU resources","text":"

In general, HTCondor supports GPU jobs if there are some worker nodes which are configured with GPU devices. CMS Connect and lxplus both have access to worker nodes equipped with GPUs.

"},{"location":"resources/gpu_resources/cms_resources/lxplus_htcondor.html#how-to-require-gpus-in-htcondor","title":"How to require GPUs in HTCondor","text":"

People can require their jobs to have GPU support by adding the following requirements to the condor submission file.

request_gpus = n # n equal to the number of GPUs required\n
"},{"location":"resources/gpu_resources/cms_resources/lxplus_htcondor.html#further-documentation","title":"Further documentation","text":"

There are good materials providing detailed documentation on how to run HTCondor jobs with GPU support at both machines.

The configuration of the software environment for lxplus-gpu and HTcondor is described in the Software Environments page. Moreover the page Using container explains step by step how to build a docker image to be run on HTCondor jobs.

"},{"location":"resources/gpu_resources/cms_resources/lxplus_htcondor.html#more-available-resources","title":"More available resources","text":"
  1. A complete documentation can be found from the GPUs section in CERN Batch Docs. Where a Tensorflow example is supplied. This documentation also contains instructions on advanced HTCondor configuration, for instance constraining GPU device or CUDA version.
  2. A good example on submitting GPU HTCondor job @ Lxplus is the weaver-benchmark project. It provides a concrete example on how to setup environment for weaver framework and operate trainning and testing process within a single job. Detailed description can be found at section ParticleNet of this documentation.

    In principle, this example can be run elsewhere as HTCondor jobs. However, paths to the datasets should be modified to meet the requirements.

  3. CMS Connect also provides a documentation on GPU job submission. In this documentation there is also a Tensorflow example.

    When submitting GPU jobs @ CMS Connect, especially for Machine Learning purpose, EOS space @ CERN are not accessible as a directory, therefore one should consider using xrootd utilities as documented in this page

"},{"location":"resources/gpu_resources/cms_resources/ml_cern_ch.html","title":"ml.cern.ch","text":"

ml.cern.ch is a Kubeflow based ML solution provided by CERN.

"},{"location":"resources/gpu_resources/cms_resources/ml_cern_ch.html#kubeflow","title":"Kubeflow","text":"

Kubeflow is a Kubernetes based ML toolkits aiming at making deployments of ML workflows simple, portable and scalable. In Kubeflow, pipeline is an important concept. Machine Learning workflows are discribed as a Kubeflow pipeline for execution.

"},{"location":"resources/gpu_resources/cms_resources/ml_cern_ch.html#how-to-access","title":"How to access","text":"

ml.cern.ch only accepts connections from within the CERN network. Therefore, if you are outside of CERN, you will need to use a network tunnel (eg. via ssh -D dynamic port forwarding as a SOCKS5 proxy)... The main website are shown below.

"},{"location":"resources/gpu_resources/cms_resources/ml_cern_ch.html#examples","title":"Examples","text":"

After logging into the main website, you can click on the Examples entry to browser a gitlab repository containing a lot of examples. For instance, below are two examples from that repository with a well-documented readme file.

  1. mnist-kfp is an example on how to use jupyter notebooks to create a Kubeflow pipeline (kfp) and how to access CERN EOS files.
  2. katib gives an example on how to use the katib to operate hyperparameter tuning for jet tagging with ParticleNet.
"},{"location":"resources/gpu_resources/cms_resources/swan.html","title":"SWAN","text":""},{"location":"resources/gpu_resources/cms_resources/swan.html#preparation","title":"Preparation","text":"
  1. Registration:

    To require GPU resources for SWAN: According to this thread, one can create a ticket through this link to ask for GPU support at SWAN, it is now in beta version and limited to a small scale. 2. Setup SWAN with GPU resources:

    1. Once the registration is done, one can login SWAN with Kerberes8 support and then create his SWAN environment.

      \ud83d\udca1 Note: When configuring the SWAN environment you will be given your choice of software stack. Be careful to use a software release with GPU support as well as an appropriate CUDA version. If you need to install additional software, it must be compatible with your chosen CUDA version.

Another important option is the environment script, which will be discussed later in this document.

"},{"location":"resources/gpu_resources/cms_resources/swan.html#working-with-swan","title":"Working with SWAN","text":"
  1. After creation, one will browse the SWAN main directory My Project where all existing projects are displayed. A new project can be created by clicking the upper right \"+\" button. After creation one will be redirected to the newly created project, at which point the \"+\" button on the upper right panel can be used for creating new notebook.

  2. It is possible to use the terminal for installing new packages or monitoring computational resources.

    1. For package installation, one can install packages with package management tools, e.g. pip for python. To use the installed packages, you will need to wrap the environment configuration in a scrip, which will be executed by SWAN. Detailed documentation can be found by clicking the upper right \"?\" button.

    2. In addition to using top and htop to monitor ordinary resources, you can use nvidia-smi to monitor GPU usage.

"},{"location":"resources/gpu_resources/cms_resources/swan.html#examples","title":"Examples","text":"

After installing package, you can then use GPU based machine learning algorithms. Two examples are supplied as an example.

  1. The first example aims at using a CNN to perform handwritten digits classification with MNIST dataset. The whole notebook can be found at pytorch_mnist. This example is modified from an official pytorch example.

  2. The second example is modified from the simple MLP example from weaver-benchmark. The whole notebook can be found at toptagging_mlp.

"},{"location":"resources/gpu_resources/cms_resources/notebooks/pytorch_mnist.html","title":"Pytorch mnist","text":"
from __future__ import print_function\nimport argparse\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torchvision import datasets, transforms\nfrom torch.optim.lr_scheduler import StepLR\n
class Net(nn.Module):\n    def __init__(self):\n        super(Net, self).__init__()\n        self.conv1 = nn.Conv2d(1, 32, 3, 1)\n        self.conv2 = nn.Conv2d(32, 64, 3, 1)\n        self.dropout1 = nn.Dropout(0.25)\n        self.dropout2 = nn.Dropout(0.5)\n        self.fc1 = nn.Linear(9216, 128)\n        self.fc2 = nn.Linear(128, 10)\n\n    def forward(self, x):\n        x = self.conv1(x)\n        x = F.relu(x)\n        x = self.conv2(x)\n        x = F.relu(x)\n        x = F.max_pool2d(x, 2)\n        x = self.dropout1(x)\n        x = torch.flatten(x, 1)\n        x = self.fc1(x)\n        x = F.relu(x)\n        x = self.dropout2(x)\n        x = self.fc2(x)\n        output = F.log_softmax(x, dim=1)\n        return output\n
def train(args, model, device, train_loader, optimizer, epoch):\n    model.train()\n    for batch_idx, (data, target) in enumerate(train_loader):\n        data, target = data.to(device), target.to(device)\n\n        optimizer.zero_grad()\n        output = model(data)\n        loss = F.nll_loss(output, target)\n        loss.backward()\n        optimizer.step()\n        if batch_idx % args[\"log_interval\"] == 0:\n            print('Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}'.format(\n                epoch, batch_idx * len(data), len(train_loader.dataset),\n                100. * batch_idx / len(train_loader), loss.item()))\n            if args[\"dry_run\"]:\n                break\n
def test(model, device, test_loader):\n    model.eval()\n    test_loss = 0\n    correct = 0\n    with torch.no_grad():\n        for data, target in test_loader:\n            data, target = data.to(device), target.to(device)\n            output = model(data)\n            test_loss += F.nll_loss(output, target, reduction='sum').item()  # sum up batch loss\n            pred = output.argmax(dim=1, keepdim=True)  # get the index of the max log-probability\n            correct += pred.eq(target.view_as(pred)).sum().item()\n\n    test_loss /= len(test_loader.dataset)\n\n    print('\\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\\n'.format(\n        test_loss, correct, len(test_loader.dataset),\n        100. * correct / len(test_loader.dataset)))\n
torch.cuda.is_available() # Check if cuda is available\n
train_kwargs = {\"batch_size\":64}\ntest_kwargs = {\"batch_size\":1000}\n
cuda_kwargs = {'num_workers': 1,\n               'pin_memory': True,\n               'shuffle': True}\ntrain_kwargs.update(cuda_kwargs)\ntest_kwargs.update(cuda_kwargs)\n
transform=transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize((0.1307,), (0.3081,))\n    ])\n
dataset1 = datasets.MNIST('./data', train=True, download=True,\n                   transform=transform)\ndataset2 = datasets.MNIST('./data', train=False,\n                   transform=transform)\ntrain_loader = torch.utils.data.DataLoader(dataset1,**train_kwargs)\ntest_loader = torch.utils.data.DataLoader(dataset2, **test_kwargs)\n
device = torch.device(\"cuda\")\nmodel = Net().to(device)\noptimizer = optim.Adadelta(model.parameters(), lr=1.0)\nscheduler = StepLR(optimizer, step_size=1, gamma=0.7)\n
args = {\"dry_run\":False, \"log_interval\":100}\nfor epoch in range(1, 14 + 1):\n    train(args, model, device, train_loader, optimizer, epoch)\n    test(model, device, test_loader)\n    scheduler.step()\n
"},{"location":"resources/gpu_resources/cms_resources/notebooks/toptagging_mlp.html","title":"Toptagging mlp","text":"

import torch\nimport torch.nn as nn\nfrom torch.utils.data.dataset import Dataset\nimport pandas as pd\nimport numpy as np\nimport uproot3\nimport torch.optim as optim\nfrom torch.optim.lr_scheduler import StepLR\nimport torch.nn.functional as F\nimport awkward0\n
class MultiLayerPerceptron(nn.Module):\nr\"\"\"Parameters\n    ----------\n    input_dims : int\n        Input feature dimensions.\n    num_classes : int\n        Number of output classes.\n    layer_params : list\n        List of the feature size for each layer.\n    \"\"\"\n\n    def __init__(self, input_dims, num_classes,\n                 layer_params=(256,64,16),\n                 **kwargs):\n\n        super(MultiLayerPerceptron, self).__init__(**kwargs)\n        channels = [input_dims] + list(layer_params) + [num_classes]\n        layers = []\n        for i in range(len(channels) - 1):\n            layers.append(nn.Sequential(nn.Linear(channels[i], channels[i + 1]),\n                                        nn.ReLU()))\n        self.mlp = nn.Sequential(*layers)\n\n    def forward(self, x):\n        # x: the feature vector initally read from the data structure, in dimension (N, C, P)\n        x = x.flatten(start_dim=1) # (N, L), where L = C * P\n        return self.mlp(x)\n\n    def predict(self,x):\n        pred = F.softmax(self.forward(x))\n        ans = []\n        for t in pred:\n            if t[0] > t[1]:\n                ans.append(1)\n            else:\n                ans.append(0)\n        return torch.tensor(ans)\n

def train(args, model, device, train_loader, optimizer, epoch):\n    model.train()\n    for batch_idx, (data, target) in enumerate(train_loader):\n        data, target = data.to(device), target.to(device)\n        optimizer.zero_grad()\n        output = model(data)\n        loss = F.nll_loss(output, target)\n        loss.backward()\n        optimizer.step()\n        if batch_idx % args[\"log_interval\"] == 0:\n            print('Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}'.format(\n                epoch, batch_idx * len(data), len(train_loader.dataset),\n                100. * batch_idx / len(train_loader), loss.item()))\n            if args[\"dry_run\"]:\n                break\n
input_branches = [\n                  'Part_Etarel',\n                  'Part_Phirel',\n                  'Part_E_log',\n                  'Part_P_log'\n                 ]\n\noutput_branches = ['is_signal_new']\n
train_dataset = uproot3.open(\"TopTaggingMLP/train.root\")[\"Events\"].arrays(input_branches+output_branches,namedecode='utf-8')\ntrain_dataset = {name:train_dataset[name].astype(\"float32\") for name in input_branches+output_branches}\ntest_dataset = uproot3.open(\"/eos/user/c/coli/public/weaver-benchmark/top_tagging/samples/prep/top_test_0.root\")[\"Events\"].arrays(input_branches+output_branches,namedecode='utf-8')\ntest_dataset = {name:test_dataset[name].astype(\"float32\") for name in input_branches+output_branches}\n
for ds in [train_dataset,test_dataset]:\n    for name in ds.keys():\n        if isinstance(ds[name],awkward0.JaggedArray):\n            ds[name] = ds[name].pad(30,clip=True).fillna(0).regular().astype(\"float32\")\n
class PF_Features(Dataset):\n    def __init__(self,mode = \"train\"):\n        if mode == \"train\":\n            self.x = {key:train_dataset[key] for key in input_branches}\n            self.y = {'is_signal_new':train_dataset['is_signal_new']}\n        elif mode == \"test\":\n            self.x = {key:test_dataset[key] for key in input_branches}\n            self.y = {'is_signal_new':test_dataset['is_signal_new']}\n        elif model == \"val\":\n            self.x = {key:test_dataset[key] for key in input_branches}\n            self.y = {'is_signal_new':test_dataset['is_signal_new']}\n\n    def __len__(self):\n        return len(self.y['is_signal_new'])\n\n    def __getitem__(self,idx):\n        X = [self.x[key][idx].copy() for key in input_branches]\n        X = np.vstack(X)\n        y = self.y['is_signal_new'][idx].copy()\n        return X,y\n
torch.cuda.is_available() # Check if cuda is available\n
True\n
device = torch.device(\"cuda\")\n
train_kwargs = {\"batch_size\":1000}\ntest_kwargs = {\"batch_size\":10}\ncuda_kwargs = {'num_workers': 1,\n               'pin_memory': True,\n               'shuffle': True}\ntrain_kwargs.update(cuda_kwargs)\ntest_kwargs.update(cuda_kwargs)\n
model = MultiLayerPerceptron(input_dims = 4 * 30, num_classes=2).to(device)\n
optimizer = optim.Adam(model.parameters(), lr=0.01)\n
train_loader = torch.utils.data.DataLoader(PF_Features(mode=\"train\"),**train_kwargs)\ntest_loader = torch.utils.data.DataLoader(PF_Features(mode=\"test\"),**test_kwargs)\n
loss_func = torch.nn.CrossEntropyLoss()\n
args = {\"dry_run\":False, \"log_interval\":500}\nfor epoch in range(1,10+1):\n    for batch_idx, (data, target) in enumerate(train_loader):\n        inputs = data.to(device)#.flatten(start_dim=1)\n        target = target.long().to(device)\n        optimizer.zero_grad()\n        output = model.forward(inputs)\n        loss = loss_func(output,target)\n        loss.backward()\n        optimizer.step()\n        if batch_idx % args[\"log_interval\"] == 0:\n            print('Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}'.format(\n                epoch, batch_idx * len(data), len(train_loader.dataset),\n                100. * batch_idx / len(train_loader), loss.item()))\n
"},{"location":"software_envs/containers.html","title":"Using containers","text":"

Containers are a great solution to isolate a software environment, especially in batch systems like lxplus. At the moment two container solutations are supported Apptainer ( previously called Singularity), and Docker.

"},{"location":"software_envs/containers.html#using-singularity","title":"Using Singularity","text":"

The unpacked.cern.ch service mounts on CVMFS contains many singularity images, some of which are suitable for machine learning applications. A description of each of the images is beyond the scope of this document. However, if you find an image which is useful for your application, you can use if by running a Singularity container with the appropriate options. For example:

singularity run --nv --bind <bind_mount_path> /cvmfs/unpacked.cern.ch/<path_to_image>\n

"},{"location":"software_envs/containers.html#examples","title":"Examples","text":"

After installing package, you can then use GPU based machine learning algorithms. Two examples are supplied as an example.

  1. The first example aims at using a CNN to perform handwritten digits classification with MNIST dataset. The whole notebook can be found at pytorch_mnist. This example is modified from an official pytorch example.

  2. The second example is modified from the simple MLP example from weaver-benchmark. The whole notebook can be found at toptagging_mlp.

"},{"location":"software_envs/containers.html#using-docker","title":"Using Docker","text":"

Docker is not supported at the moment in the interactive node of lxplus (like lxplus-gpu). However Docker is supported on HTCondor for job submission.

This option can be very handy for users, as HTCondor can pull images from any public registry, like DockerHub or GitLab registry. The user can follow this workflow: 1. Define a custom image on top of a commonly available pytorch or tensorflow image 2. Add the desidered packages and configuration 3. Push the docker image on a registry 4. Use the image in a HTCondor job

The rest of the page is a step by step tutorial for this workflow.

"},{"location":"software_envs/containers.html#define-the-image","title":"Define the image","text":"
  1. Define a file Dockerfile

    FROM pytorch/pytorch:latest\n\nADD localfolder_with_code /opt/mycode\n\n\nRUN  cd /opt/mycode && pip install -e . # or pip install requirements\n\n# Install the required Python packages\nRUN pip install \\\n    numpy \\\n    sympy \\\n    scikit-learn \\\n    numba \\\n    opt_einsum \\\n    h5py \\\n    cytoolz \\\n    tensorboardx \\\n    seaborn \\\n    rich \\\n    pytorch-lightning==1.7\n\nor \nADD requirements.txt \npip install -r requirements.txt\n
  2. Build the image

    docker build -t username/pytorch-condor-gpu:tag .\n

    and push it (after having setup the credentials with docker login hub.docker.com)

    docker push username/pytorch-condor-gpu:tag\n
  3. Setup the condor job with a submission file submitfile as:

    universe                = docker\ndocker_image            = user/pytorch-condor-gpu:tag\nexecutable              = job.sh\nwhen_to_transfer_output = ON_EXIT\noutput                  = $(ClusterId).$(ProcId).out\nerror                   = $(ClusterId).$(ProcId).err\nlog                     = $(ClusterId).$(ProcId).log\nrequest_gpus            = 1\nrequest_cpus            = 2\n+Requirements           = OpSysAndVer =?= \"CentOS7\"\n+JobFlavour = espresso\nqueue 1\n
  4. For testing purpose one can start a job interactively and debug

    condor_submit -interactive submitfile\n
"},{"location":"software_envs/lcg_environments.html","title":"LCG environments","text":""},{"location":"software_envs/lcg_environments.html#software-environment","title":"Software Environment","text":"

The software environment for ML application trainings can be setup in different ways. In this page we focus on the CERN lxplus environment.

"},{"location":"software_envs/lcg_environments.html#lcg-release-software","title":"LCG release software","text":"

Checking out an ideal software bundle with Cuda support at http://lcginfo.cern.ch/, one can set up an LCG environment by executing

source /cvmfs/sft.cern.ch/lcg/views/<name of bundle>/**x86_64-centos*-gcc11-opt**/setup.sh\n

On lxplus-gpu nodes, usually equipped with AlmaLinux 9.1 (also called Centos9), one should use the proper lcg release. At the time of writing (May 2023) the recommended environment to use GPUs is:

source /cvmfs/sft.cern.ch/lcg/views/LCG_103cuda/x86_64-centos9-gcc11-opt/setup.sh\n
"},{"location":"software_envs/lcg_environments.html#customized-environments","title":"Customized environments","text":"

One can create custom Python environment using virtualenv or venv tools, in order to avoid messing up with the global python environment.

The user has the choice of building a virtual environment from scratch or by basing on top of a LCG release.

"},{"location":"software_envs/lcg_environments.html#virtual-environment-from-scratch","title":"Virtual environment from scratch","text":"

The first approach is cleaner but requires downloading the full set of libraries needed for pytorch or TensorFlow (very heavy). Moreover the compatibility with the computing environment (usually lxplus-gpu) is not guaranteed.

  1. Create the environment in a folder of choice, usually called myenv

    python3 -m venv --system-site-packages myenv\nsource myenv/bin/activate   # activate the environment\n# Add following line to .bashrc if you want to activate this environment by default (not recommended)\n# source \"/afs/cern.ch/user/<first letter of your username>/<username>/<path-to-myenv-folder>/myenv/bin/activate\"\n
  2. To install packages properly, one should carefully check the CUDA version with nvidia-smi (as shown in figure before), and then find a proper version, pytorch is used as an example.

    # Execute the command shown in your terminal\npip install torch==1.10.0+cu113 torchvision==0.11.1+cu113 torchaudio==0.10.0+cu113 -f https://download.pytorch.org/whl/cu113/torch_stable.html\npip install jupyterlab matplotlib scikit-hep # install other packages if they are needed\n
"},{"location":"software_envs/lcg_environments.html#virtual-environment-on-top-of-lcg","title":"Virtual environment on top of LCG","text":"

Creating a virtual environment only to add packages on top of a specific LCG release can be a very effective and inexpesive way to manage the Python environment in lxplus.

N.B A caveat is that the users needs to remember to activate the lcg environment before activating his virtual environment.

  1. Activate the lcg environment of choice

    source /cvmfs/sft.cern.ch/lcg/views/LCG_103cuda/x86_64-centos9-gcc11-opt/setup.sh\n
  2. Create the enviroment as above

    python3 -m venv --system-site-packages myenv\nsource myenv/bin/activate   # activate the environment\n
  3. Now the user can work in the environment as before but Pytorch and tensorflow libraries will be available. If a single package needs to be update one can do

pip install --upgrade tensorflow=newer.version\n

This will install the package in the local environment.

At the next login, the user will need to perform these steps to get back the environment:

source /cvmfs/sft.cern.ch/lcg/views/LCG_103cuda/x86_64-centos9-gcc11-opt/setup.sh\nsource myenv/bin/activate\n
"},{"location":"software_envs/lcg_environments.html#conda-environments","title":"Conda environments","text":"

Using conda package manager: conda pacakge manager is more convenient to install and use. To begin with, obtaining an Anaconda or Miniconda installer for Linux x86_64 platform. Then execute it on Lxplus.

1. Please note that if you update your shell configuration (e.g. `.bashrc` file) by `conda init`, you may encounter failure due to inconsistent environment configuration.\n2. Installing packages via `conda` also needs special consideration on selecting proper CUDA version as discussed in `pip` part.\n
"},{"location":"training/MLaaS4HEP.html","title":"MLaaS4HEP","text":""},{"location":"training/MLaaS4HEP.html#machine-learning-as-a-service-for-hep","title":"Machine Learning as a Service for HEP","text":"

MLaaS for HEP is a set of Python-based modules to support reading HEP data and stream them to the ML tool of the user's choice. It consists of three independent layers: - Data Streaming layer to handle remote data, see reader.py - Data Training layer to train ML model for given HEP data, see workflow.py - Data Inference layer, see tfaas_client.py

The MLaaS4HEP resopitory can be found here.

The general architecture of MLaaS4HEP looks like this:

Even though this architecture was originally developed for dealing with HEP ROOT files, we extend it to other data formats. As of right now, following data formats are supported: JSON, CSV, Parquet, and ROOT. All of the formats support reading files from the local file system or HDFS, while the ROOT format supports reading files via the XRootD protocol.

The pre-trained models can be easily uploaded to TFaaS inference server for serving them to clients. The TFaaS documentation can be found here.

"},{"location":"training/MLaaS4HEP.html#dependencies","title":"Dependencies","text":"

Here is a list of the dependencies: - pyarrow for reading data from HDFS file system - uproot for reading ROOT files - numpy, pandas for data representation - modin for fast panda support - numba for speeing up individual functions

"},{"location":"training/MLaaS4HEP.html#installation","title":"Installation","text":"

The easiest way to install and run MLaaS4HEP and TFaaS is to use pre-build docker images

# run MLaaS4HEP docker container\ndocker run veknet/mlaas4hep\n# run TFaaS docker container\ndocker run veknet/tfaas\n

"},{"location":"training/MLaaS4HEP.html#reading-root-files","title":"Reading ROOT files","text":"

MLaaS4HEP python repository provides the reader.py module that defines a DataReader class able to read either local or remote ROOT files (via xrootd) in chunks. It is based on the uproot framework.

Basic usage

# setup the proper environment, e.g.\n# export PYTHONPATH=/path/src/python # path to MLaaS4HEP python framework\n# export PATH=/path/bin:$PATH # path to MLaaS4HEP binaries\n\n# get help and option description\nreader --help\n\n# here is a concrete example of reading local ROOT file:\nreader --fin=/opt/cms/data/Tau_Run2017F-31Mar2018-v1_NANOAOD.root --info --verbose=1 --nevts=2000\n\n# here is an example of reading remote ROOT file:\nreader --fin=root://cms-xrd-global.cern.ch//store/data/Run2017F/Tau/NANOAOD/31Mar2018-v1/20000/6C6F7EAE-7880-E811-82C1-008CFA165F28.root --verbose=1 --nevts=2000 --info\n\n# both of aforementioned commands produce the following output\nReading root://cms-xrd-global.cern.ch//store/data/Run2017F/Tau/NANOAOD/31Mar2018-v1/20000/6C6F7EAE-7880-E811-82C1-008CFA165F28.root\n# 1000 entries, 883 branches, 4.113945007324219 MB, 0.6002757549285889 sec, 6.853425235896175 MB/sec, 1.6659010326328503 kHz\n# 1000 entries, 883 branches, 4.067909240722656 MB, 1.3497390747070312 sec, 3.0138486148558896 MB/sec, 0.740883937302516 kHz\n###total time elapsed for reading + specs computing: 2.2570559978485107 sec; number of chunks 2\n###total time elapsed for reading: 1.9500117301940918 sec; number of chunks 2\n\n--- first pass: 1131872 events, (648-flat, 232-jagged) branches, 2463 attrs\nVMEM used: 29.896704 (MB) SWAP used: 0.0 (MB)\n<__main__.RootDataReader object at 0x7fb0cdfe4a00> init is complete in 2.265552043914795 sec\nNumber of events  : 1131872\n# flat branches   : 648\nCaloMET_phi values in [-3.140625, 3.13671875] range, dim=N/A\nCaloMET_pt values in [0.783203125, 257.75] range, dim=N/A\nCaloMET_sumEt values in [820.0, 3790.0] range, dim=N/A\n

More examples about using uproot may be found here and here.

"},{"location":"training/MLaaS4HEP.html#how-to-train-ml-models-on-hep-root-data","title":"How to train ML models on HEP ROOT data","text":"

The MLaaS4HEP framework allows to train ML models in different ways: - using full dataset (i.e. the entire amount of events stored in input ROOT files) - using chunks, as subsets of a dataset, which dimension can be chosen directly by the user and can vary between 1 and the total number of events - using local or remote ROOT files.

The training phase is managed by the workflow.py module which performs the following actions: - read all input ROOT files in chunks to compute a specs file (where the main information about the ROOT files are stored: the dimension of branches, the minimum and the maximum for each branch, and the number of events for each ROOT file) - perform the training cycle (each time using a new chunk of events) - create a new chunk of events taken proportionally from the input ROOT files - extract and convert each event in a list of NumPy arrays - normalize the events - fix the Jagged Arrays dimension - create the masking vector - use the chunk to train the ML model provided by the user

A schematic representation of the steps performed in the MLaaS4HEP pipeline, in particular those inside the Data Streaming and Data Training layers, is:

If the dataset is large and exceed the amount of RAM on the training node, then the user should consider the chunk approach. This allows to train the ML model each time using a different chunk, until the entire dataset is completely read. In this case the user should pay close attention to the ML model convergence, and validate it after each chunk. For more information look at this, this and this. Using different training approach has pros and cons. For instance, training on entire dataset can guarantee the ML model convergence, but the dataset should fits into RAM of the training node. While chunk approach allows to split the dataset to fit in the hardware resources, but it requires proper model evaluation after each chunk training. In terms of training speed, this choice should be faster than training on the entire dataset, since after having used a chunk for training, that chunk is no longer read and used subsequently (this effect is prominent when remote ROOT files are used). Finally, user should be aware of potential divergence of ML model when training last chunk of the dataset and check for bias towards last chunk. For instance, user may implement a K-fold cross validation approach to train on N-1 chunks (i.e. folds in this case) and use one chunk for validation.

A detailed description of how to use the workflow.py module for training a ML model reading ROOT files from the opendata portal, can be found here. Please see how the user has to provide several information when run the workflow.py module, e.g. the definition of the ML model, and then is task of MLaaS4HEP framework to perform all the training procedure using the ML model provided by the user.

For a complete description of MLaaS4HEP see this paper.

"},{"location":"training/autoencoders.html","title":"Autoencoders","text":""},{"location":"training/autoencoders.html#introduction","title":"Introduction","text":"

Autoencoders are a powerful tool that has gained popularity in HEP and beyond recently. These types of algorithms are neural networks that learn to decompress data with minimal reconstruction error (Goodfellow, et. al.).

The idea of using neural networks for dimensionality reduction or feature learning dates back to the early 1990s. Autoencoders, or \"autoassociative neural networks,\" were originally proposed as a nonlinear generalization of principle component analysis (PCA) (Kramer). More recently, connections between autoencoders and latent variable models have brought these types of algorithms into the generative modeling space.

The two main parts of an autoencoder algorithm are the encoder function \\(f(x)\\) and the decoder function \\(g(x)\\). The learning process of an autoencoder is a minimization of a loss function, \\(L(x,g(f(x)))\\), that compares the original data to the output of the decoder, similar to that of a neural network. As such, these algorithms can be trained using the same techniques, like minibatch gradient descent with backpropagation. Below is a representation of an autoencoder from Mathworks.

"},{"location":"training/autoencoders.html#constrained-autoencoders-undercomplete-and-regularized","title":"Constrained Autoencoders (Undercomplete and Regularized)","text":"

Information in this section can be found in Goodfellow, et. al.

An autoencoder that is able to perfectly reconstruct the original data one-to-one, such that \\(g(f(x)) = x\\), is not very useful for extracting salient information from the data. There are several methods imposed on simple autoencoders to encourage them to extract useful aspects of the data.

One way of avoiding perfect data reconstruction is by constraining the dimension of the encoding function \\(f(x)\\) to be less than the data \\(x\\). These types of autoencoders are called undercomplete autoencoders, which force the imperfect copying of the data such that the encoding and decoding networks can prioritize the most useful aspects of the data.

However, if undercomplete encoders are given too much capacity, they will struggle to learn anything of importance from the data. Similarly, this problem occurs in autoencoders with encoder dimensionality greater than or equal to the data (the overcomplete case). In order to train any architecture of AE successfully, constraints based on the complexity of the target distribution must be imposed, apart from small dimensionality. These regularized autoencoders can have constraints on sparsity, robustness to noise, and robustness to changes in data (the derivative).

"},{"location":"training/autoencoders.html#sparse-autoencoders","title":"Sparse Autoencoders","text":"

Sparse autoencoders place a penalty to enforce sparsity in the encoding layer \\(\\mathbf{h} = f(\\mathbf{x})\\) such that \\(L(\\mathbf{x}, g(f(\\mathbf{x}))) + \\Omega(\\mathbf{h})\\). This penalty prevents the autoencoder from learning the identity transformation, extracting useful features of the data to be used in later tasks, such as classification. While the penalty term can be thought of as a regularizing term for a feedforward network, we can expand this view to think of the entire sparse autoencoder framework as approximating the maximum likelihood estimation of a generative model with latent variables \\(h\\). When approximating the maximum likelihood, the joint distribution \\(p_{\\text{model}}(\\mathbf{x}, \\mathbf{h})\\) can be approximated as

\\[ \\text{log} [ p_{\\text{model}}(\\mathbf{x})] = \\text{log} [p_{\\text{model}}(\\mathbf{h})] + [\\text{log} p_{\\text{model}}(\\mathbf{x} | \\mathbf{h})] \\]

where \\(p_{\\text{model}}(\\mathbf{h})\\) is the prior distribution over the latent variables, instead of the model's parameters. Here, we approximate the sum over all possible prior distribution values to be a point estimate at one highly likely value of \\(\\mathbf{h}\\). This prior term is what introduces the sparsity requirement, for example with the Laplace prior, $$ p_{\\text{model}}(h_i) = \\frac{\\lambda}{2}e^{-\\lambda|h_i|}. $$

The log-prior is then

$$ \\text{log} [p_{\\text{model}}(\\mathbf{h})] = \\sum_i (\\lambda|h_i| - \\text{log}\\frac{\\lambda}{2}) = \\Omega(\\mathbf{h}) + \\text{const}. $$ This example demonstrates how the model's distribution over latent variables (prior) gives rise to a sparsity penalty.

"},{"location":"training/autoencoders.html#penalized-autoencoders","title":"Penalized Autoencoders","text":"

Similar to sparse autoencoders, a traditional penalty term can be introduced to the cost function to regularize the autoencoder, such that the function to minimize becomes $$ L(\\mathbf{x},g(f(\\mathbf{x}))) + \\Omega(\\mathbf{h},\\mathbf{x}). $$ where $$ \\Omega(\\mathbf{h},\\mathbf{x}) = \\lambda\\sum_i ||\\nabla_{\\mathbf{x}}h_i||^2. $$ Because of the dependence on the gradient of the latent variables with respect to the input variables, if \\(\\mathbf{x}\\) changes slightly, the model is penalized for learning those slight variations. This type of regularization leads to a contractive autoencoder (CAE).

"},{"location":"training/autoencoders.html#denoising-autoencoders","title":"Denoising Autoencoders","text":"

Another way to encourage autoencoders to learn useful features of the data is training the algorithm to minimize a cost function that compares the original data (\\(\\mathbf{x}\\)) to encoded and decoded data that has been injected with noise (\\(f(g(\\mathbf{\\tilde{x}}))\\), $$ L(\\mathbf{x},g(f(\\mathbf{\\tilde{x}}))) $$ Denoising autoencoders then must learn to undo the effect of the noise in the encoded/decoded data. The autoencoder is able to learn the structure of the probability density function of the data (\\(p_{\\text{data}}\\)) as a function of the input variables (\\(x\\)) through this process (Alain, Bengio, Bengio, et. al.). With this type of cost function, even overcomplete, high-capacity autoencoders can avoid learning the identity transformation.

"},{"location":"training/autoencoders.html#variational-autoencoders","title":"Variational Autoencoders","text":"

Variational autoencoders (VAEs), introduced by Kigma and Welling, are similar to normal AEs. They are comprised of neural nets, which maps the input to latent space (encoder) and back (decoder), where the latent space is a low-dimensional, variational distribution. VAEs are bidirectional, generating data or estimating distributions, and were initially designed for unsupervised learning but can also be very useful in semi-supervised and fully supervised scenarios (Goodfellow, et. al.).

VAEs are trained by maximizing the variational lower bound associated with data point \\(\\mathbf{x}\\), which is a function of the approximate posterior (inference network, or encoder), \\(q(\\mathbf{z})\\). Latent variable \\(\\mathbf{z}\\) is drawn from this encoder distribution, with \\(p_\\text{model}(\\mathbf{x} | \\mathbf{z})\\) viewed as the decoder network. The variational lower bound (also called the evidence lower bound or ELBO) is a trade-off between the join log-likelihood of the visible and latent variables, and the KL divergence between the model prior and the approximate posterior, shown below (Goodfellow, et. al.).

$$ \\mathcal{L}(q) = E_{\\mathbf{z} \\sim q(\\mathbf{z} | \\mathbf{x})} \\text{log}p_\\text{model}(\\mathbf{x} | \\mathbf{z}) - D_\\text{KL}(q || p) $$.

Methods for optimizing the VAE by learning the variational lower bound include EM meta-algorithms like probabilistic PCA (Goodfellow, et. al.).

"},{"location":"training/autoencoders.html#applications-in-hep","title":"Applications in HEP","text":"

One of the more popular applications of AEs in HEP include anomaly detection. Because autoencoders are trained to learn latent features of a dataset, any new data that does not match those features could be classified as an anomaly and picked out by the AE. Examples of AEs for anomaly detection in HEP are listed below:

  • Anomaly detection in high-energy physics using a quantum autoencoder
  • Particle Graph Autoencoders and Differentiable, Learned Energy Mover's Distance
  • Bump Hunting in Latent Space

Another application of (V)AEs in HEP is data generation, as once the likelihood of the latent variables is approximated it can be used to generate new data. Examples of this application in HEP for simulation of various physics processes are listed below:

  • Deep generative models for fast shower simulation in ATLAS
  • Sparse Data Generation for Particle-Based Simulation of Hadronic Jets in the LHC
  • Variational Autoencoders for Jet Simulation

Finally, the latent space learned by (V)AEs give a parsimonious and information-rich phase space from which one can make inferences. Examples of using (V)AEs to learn approximate and/or compressed representations of data are given below:

  • An Exploration of Learnt Representations of W Jets
  • Machine-Learning Compression for Particle Physics Discoveries
  • Decoding Photons: Physics in the Latent Space of a BIB-AE Generative Network

More examples of (V)AEs in HEP can be found at the HEP ML Living Review.

"},{"location":"training/autoencoders.html#references","title":"References","text":"
  • Goodfellow, et. al., 2016, Deep Learning
  • Alain, Bengio, 2013, \"What Regularized Auto-Encoders Learn from the Data Generating Distribution\"
  • Bengio, et. al., 2013, \"Generalized Denoising Auto-Encoders as Generative Models\"
  • Kramer, 1991, \"Nonlinear principle component analysis using autoassociative neural networks\"
  • Kingma, Welling, 2013, \"Auto-Encoding Variational Bayes\"
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"index.html","title":"Home","text":"

Welcome to the documentation hub for the CMS Machine Learning Group! The goal of this page is to provide CMS analyzers a centralized place to gather machine learning information relevant to their work. However, we are not seeking to rewrite external documentation. Whenever applicable, we will link to external documentation, such as the iML groups HEP Living Review or their ML Resources repository. What you will find here are pages covering:

  • ML best practices
  • How to optimize a NN
  • Common pitfalls for CMS analyzers
  • Direct and indirect inferencing using a variety of ML packages
  • How to get a model integrated into CMSSW

And much more!

If you think we are missing some important information, please contact the ML Knowledge Subgroup!

"},{"location":"general_advice/intro.html","title":"Introduction","text":"

In general, ML models don't really work out of the box. For example, most often it is not sufficient to simply instantiate the model class, call its fit() method followed by predict(), and then proceed straight to the inference step of the analysis.

from sklearn.datasets import make_circles\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.svm import SVC\n\nX, y = make_circles(noise=0.2, factor=0.5, random_state=1)\nX_train, X_test, y_train, y_test = \\\n        train_test_split(X, y, test_size=.4, random_state=42)\n\nclf = SVC(kernel=\"linear\", C=0.025)\nclf.fit(X_train, y_train)\nprint(f'Accuracy: {clf.score(X_test, y_test)}')\n# Accuracy: 0.4\n

Being an extremely simplified and naive example, one would be lucky to have the code above produce a valid and optimal model. This is because it explicitly doesn't check for those things which could've gone wrong and therefore is prone to producing undesirable results. Indeed, there are several pitfalls which one may encounter on the way towards implementation of ML into their analysis pipeline. These can be easily avoided by being aware of those and performing a few simple checks here and there.

Therefore, this section is intended to review potential issues on the ML side and how they can be approached in order to train a robust and optimal model. The section is designed to be, to a large extent, analysis-agnostic. It will focus on common, generalized validation steps from ML perspective, without paying particular emphasis on the physical context. However, for illustrative purposes, it will be supplemented with some examples from HEP and additional links for further reading. As the last remark, in the following there will mostly an emphasis on the validation items specific to supervised learning. This includes classification and regression problems as being so far the most common use cases amongst HEP analysts.

The General Advice chapter is divided into into 3 sections. Things become logically aligned if presented from the perspective of the training procedure (fitting/loss minimisation part). That is, the sections will group validation items as they need to be investigated:

  • Before training
  • During training
  • After training

Authors: Oleg Filatov

"},{"location":"general_advice/after/after.html","title":"After training","text":"

After the necessary steps to design the ML experiment has been made, the training has been performed and verified to be stable and consistent, there are still a few things to be checked to further solidify the confidence in the model performance.

"},{"location":"general_advice/after/after.html#final-evaluation","title":"Final evaluation","text":"

Before the training, initial data set is to be split into the train and test parts, where the former is used to train the model (possibly, with cross-validation), while the latter remains blinded. Once all the optimisations to the model architecture have been made and the model is \"frozen\", one proceeds to the evaluation of the metrics' values on the test set. This would be the very last check of the model for overfitting and in case there is none, one expects to see little or no difference comparing to the values on (cross)validation set used throughout the training. In turn, any discrepancies could point to possible overfitting happening in the training stage (or also possibly data leakage), which requires further investigation.

The next step to check is the output score of the model (probability1) for each class. It can be done, for example, in the form of a TMVA-like overtraining check (see Figure 1) which also allows to spot overtraining:

Figure 1. Comparison of model output for signal and background classes overlaid for train and test data sets. [source: root-forum.cern.ch]

In general, what is important to look at is that in the category for class C (defined as argmax(score_i)), the score for a class C peaks at values closer to 1. Whereas the other classes doesn't have such property with peaking on the left side of 1 and smoothly falling down to zero as the model score in the category approaches 1. Or, in other words, that the distributions of the model score for various classes are not overlapping and are as far apart as possible. This would be an indication that the model indeed distinguishes between the classes.

Another thing to look at is the data/simulation agreement for class categories. Since it is the output of the model for each category which is used in further statistical inference step, it is important to verify that data/simulation agreement of input features is properly propagated through the model into categories' distribution. This can be achieved by producing the plot similar to the one shown on Figure 2: the stacked templates for backround processes are fitted and compared with the actual predictions for the data for the set of events classified to be in the given category (jet-fakes in the example). If the output data/simulation agreement is worse than the input one, it might point to an existing bias of the model in the way it treats data and simulation events.

Figure 2. Postfit jet-fake NN score for the mutau channel. Note that the distribution for jet-fakes class is dominant in this category and also peaks at value 1 (mind the log scale), which is an indication of good identification of this background process by the model. Furthermore, ratio of data and MC templates is equal to 1 within uncertainties. [source: CMS-PAS-HIG-20-006]"},{"location":"general_advice/after/after.html#robustness","title":"Robustness","text":"

Once there is high confidence that the model isn't overtrained and no distortion in the input feature data/MC agreement is introduced, one can consider studying the robustness of the model to the parameter/input variations. Effectively, the model can be considered as a \"point estimate\", and any variations are helpful to understand the variance of the model outputs - hence, the model's robustness to changes.

A simple example would be a hyperparameter optimisation, where various model parameters a varied to find the best one in terms of performance. Moreover, in HEP there is a helpful (for this particular case) notion of systematic uncertainties, which is a perfect tool to study model robustness to input data variations.

Since in any case they need to be incorporated into the final statistical fit (to be performed on some interpretation of the model score), it implies that these uncertainties need to be \"propagated\" through the model. A sizeable fraction of those uncertainties are so-called \"up/down\" (or shape) variations, and therefore it is a good opportunity to study, how the model output responds to those up/down input feature changes. If there is a high sensitivity observed, one need to consider removing the most influencing feature from the training, or trying decorrelation techniques to decrease the impact of systematic-affected feature on the model output.

"},{"location":"general_advice/after/after.html#systematic-biases","title":"Systematic biases","text":"

Lastly, possible systematic biases arising the ML approach should be estimated. Being a broad and not fully formalised topic, a few examples will be given below to outline the possible sources of those.

  • The first one could be a domain shift, that is the situation where the model is trained on one data domain, but is apllied to a different one (e.g. trained on simulated data, applied on real one). In order to account for that, corresponding scale factor corrections are traditionally derived, and those will come with some uncertainty as well.
  • Another example would be the case of undertraining. Consider the case of fitting a complex polynomial data with a simple linear function. In that case, the model has high bias (and low variance) which results in a systematic shift of its prediction to be taken into account.
  • Care needs to be taken in cases where a cut is applied on the model output. Cuts might potentially introduce shifts and in case of the model score, which is a variable with a complex and non-linear relationship with input features, it might create undesirable biases. For example, in case of cutting on the output score and looking at the invariant mass distribution (e.g. of two jets), one can observe an effect which is known as mass sculpting (see Figure 3). In that case, the background distribution peaks at the mass of the signal resonance used as a signal in the classification task. After applying such cut, signal and background shapes overlap and become very similar, which dillutes the discrimination power between two hypotheses if invariant mass was to be used as the observable to be fitted.
Figure 3. Left: Distributions of signal and background events without selection. Right: Background distributions at 50% signal efficiency (true positive rate) for different classifiers. The unconstrained classifier sculpts a peak at the W-boson mass, while other classifiers do not. [source: arXiv:2010.09745]
  1. Here it is assumed that it can be treated as probability to be assigned to a given class. This is mostly the case if there is a sigmoid/softmax used on the output layer of the neural network and the model is trained with a cross-entropy loss function.\u00a0\u21a9

"},{"location":"general_advice/before/domains.html","title":"Domains","text":"

Data plays a crucial role in the process of training any ML model. It is something from which the model learns to solve a given task and therefore care needs to be taken with its handling. There are two main considerations when collecting and preparing data for an ML task:

  1. The data set should be relevant to the problem and should represent the underlying structure of the problem without containing potential biases and irrelevant deviations (e.g. MC simulation artefacts).
  2. A proper preprocessing of the data set should be performed so that the training step goes smoothly.

In this section a general domain perspective on data will be covered. In the following sections a more granular look will be taken from the side of features and construction of inputs to the model.

"},{"location":"general_advice/before/domains.html#coverage","title":"Coverage","text":"

To begin with, one needs to bear in mind that training data should be as close as possible to data they expect to have in the context of analysis. Speaking in more formal terms,

Domains of training (used to train the model) and inference (used to make final predictions) data sets should not sizeably diverge.

Examples
  • In most of the cases the model is usually trained on MC simulated data and later on applied to data to produce predictions which are then passed on to statistical inference step. MC simulation isn't perfect and therefore there are always differences between simulation and data domains. This can lead to the cases when model learns simulation artefacts which come e.g. from detector response mismodelling. Thus, its performance on data may be at least suboptimal and at most meaningless.
  • Consider the model which is trained to predict the energy of a hadron given its energy deposits in the calorimeter (represented e.g. in the form of image or graph). Data consists of the showers initiated by a particle generated by a particle gun and having discrete values of energies (e.g. 1 GeV, 10 GeV, 20 GeV, etc.). However, in the real world settings, the model will be applied to showers produced by particles with underlying continuous energy spectrum. Although ML models are known for their capability to interpolate beyond their training domain, without apropriate tests model performance in the parts of the energy spectrum outside of its training domain is not a priori clear.
"},{"location":"general_advice/before/domains.html#solution","title":"Solution","text":"

It is particularly not easy to build a model entirely robust to domain shift, so there is no general framework yet to approach and recover for discrepancies between training and inference domains altogether. However, there is research ongoing in this direction and several methods to recover for specific deviations have been already proposed.

It is a widely known practice to introduce scale factor (SF) corrections to account for possible discrepancies between data and MC simulation. Effectively, that means that the model is probed on some part of the domain on which it wasn't trained on (data) and then corrected for any differences by using a meaningful set of observables to derive SFs. One particularly promising approaches to remedy for data/MC domain difference is to use adversarial approaches to fully leverage the multidimensionality of the problem, as described in a DeepSF note.

Another solution would be to incorporate methods of domain adaptation into an ML pipeline, which essentially guide the model to be invariant and robust towards domain shift. Particularly in HEP, a Learning to Pivot with Adversarial Networks paper was one of the pioneers to investigate how a pile-up dependency can be mitigated, which can also be easily expanded to building a model robust to domain shift1.

Last but not the least, a usage of Bayesian neural networks has a great advantage of getting uncertainties estimate along with each prediction. If these uncertainties are significantly larger for some samples, this could indicate that they come from the domain beyond the training one (a so-called out-of-distribution samples). This post hoc analysis of prediction uncertainties, for example, can point to inconsistencies in or incompleteness of MC simulation/ data-driven methods of the background estimation.

"},{"location":"general_advice/before/domains.html#population","title":"Population","text":"

Furthermore, nowadays analyses are searching for very rare processes and therefore are interested in low-populated regions of the phase space. And even though the domain of interest may be covered in the training data set, it may also not be sufficiently covered in terms of the number of samples in the training data set, which populate those regions. That makes the model behaviour on an event which falls into those regions unpredictable - because it couldn't learn how to generalise in those areas due to a lack of data to learn from. Therefore,

It is important to make sure that the phase space of interest is well-represented in the training data set.

Example

This is what is often called in HEP jargon \"little statistics in the tails\": meaning that too few events can be found in the tails of the corresponding distribution, e.g. in the high-pt region. This might be important because the topology of events changes when one enters high-pt areas of the phase space (aka boosted regime). This further means that the model should be able to capture this change in the event signature. However, it might fail to do so due to a little available data to learn from comparing to a low-pt region.

"},{"location":"general_advice/before/domains.html#solution_1","title":"Solution","text":"

Clearly, a way out in that case would be to provide enough training data to cover those regions (also ensuring that the model has enough capacity to embrace diverse and complex topologies).

Another solution would be to communicate to the model importance of specific topologies, which can be done for example by upweighting those events' contribution to the loss function.

Lastly, it might be worth trying to train several models, each targeting its specific region, instead of a general-purpose one (e.g. low-pt & boosted/merged topology tagger). Effectively, factorisation of various regions disentangle the problem of their separation for a single model and delegates it to an ensemble of dedicated models, each targeting its specific region.

  1. From that paper on, the HEP community started to explore a similar topic of model decorrelation, i.e. how to build a model which would be invariant to a particular variable or property of data. For a more detailed overview please refer to Section 2 of this paper.\u00a0\u21a9

"},{"location":"general_advice/before/features.html","title":"Features","text":"

In the previous section, the data was considered from a general \"domain\" perspective and in this section a more low level view will be outlined. In particular, an emphasis will be made on features (input variables) as they play a crucial role in the training of any ML model. Essentially being the handle on and the gateway into data for the model, they are expected to reflect the data from the perspective which is important to the problem at hand and therefore define the model performance on the task.

The topic of feature engineering is very extensive and complex to be covered in this section, so the emphasis will be made primarily on the general aspects relevant to the HEP context. Broadly speaking, one should ask themselves the following questions during the data preparation:

  • Are features understood?
  • Are features correctly modelled?
  • Are features appropriately processed?
"},{"location":"general_advice/before/features.html#understanding","title":"Understanding","text":"

Clearly one should motivate for themselves (and then possibly for analysis reviewers) why this exact set of features and not the other one has been selected1. Aside from physical understanding and intuition it would be good if a priori expert knowledge is supplemented by running further experiments.

Here one can consider either studies done prior to the training or after it. As for the former, studying feature correlations (with the target variable as well) e.g. by computing Pearson and/or Spearman correlation coefficients and plotting several histogram/scatter plots could bring some helpful insights. As for the latter, exploring feature importances as the trained model deems it important can boost the understanding of both the data and the model altogether.

"},{"location":"general_advice/before/features.html#modelling","title":"Modelling","text":"

Although seemingly obvious, for the sake of completeness the point of achieving good data/MC agreement should be mentioned. It has always been a must to be checked in a cut-based approach and ML-based one is of no difference: the principle \"garbage in, garbage out\" still holds.

Example

For example, classical feed-forward neural network is just a continuous function mapping the input space to the output one, so any discrepancies in the input might propagate to the output. In case of boosted decision trees it is also applicable: any (domain) differences in the shape of input (training) distribution w.r.t. true \"data\" distribution might sizeably affect the construction of decision boundary in the feature space.

Figure 1. Control plot for a visible mass of tau lepton pair in emu final state. [source: CMS-TAU-18-001]

Since features are the handle on the data, checking for each input feature that the ratio of data to MC features' histograms is close to 1 within uncertainties (aka by eye) is one of the options. For a more formal approach, one can perform goodness of fit (GoF) tests in 1D and 2D, checking that as it was used for example in the analysis of Higgs boson decaying into tau leptons.

If the modelling is shown to be insufficient, the corresponding feature should be either removed, or mismodelling needs to be investigated and resolved.

"},{"location":"general_advice/before/features.html#processing","title":"Processing","text":"

Feature preprocessing can also be understood from a broader perspective of data preprocessing, i.e. transformations which need to be performed with data prior to training a model. Another way to look at this is of a step where raw data is converted into prepared data. That makes it an important part of any ML pipeline since it ensures that a smooth convergence and stability of the training is reached.

Example

In fact, the training process might not even begin (presence of NaN values) or break in the middle (outlier causing the gradients to explode). Furthermore, data can be completely misunderstood by the model which can potentially caused undesirable interpretation and performance (treatment of categorical variables as numerical).

Therefore, below there is a non-exhaustive list of the most common items to be addressed during the preprocessing step to ensure the good quality of training. For a more comprehensive overview and also code examples please refer to a detailed documentation of sklearn package and also on possible pitfalls which can arise at this point.

  • Feature encoding
  • NaN/inf/missing values2
  • Outliers & noisy data
  • Standartisation & transformations

Finally, these are the items which are worth considering in the preprocessing of data in general. However, one can also apply transformations at the level of batches as they are passed through the model. This will be briefly covered in the following section.

  1. Here it is already assumed that a proper data representation has been chosen, i.e. the way to vectorize the data to form a particular structure (e.g. image -> tensor, social network -> graph, text -> embeddings). Being on its own a whole big topic, it is left for a curious reader to dive into.\u00a0\u21a9

  2. Depending on the library and how particular model is implemented there, these values can be handled automatically under the hood.\u00a0\u21a9

"},{"location":"general_advice/before/inputs.html","title":"Inputs","text":"

After data is preprocessed as a whole, there is a question of how this data should be supplied to the model. On its way there it potentially needs to undergo a few splits which will be described below. Plus, a few additional comments about training weights and motivation for their choice will be outlined.

"},{"location":"general_advice/before/inputs.html#data-split","title":"Data split","text":"

The first thing one should consider to do is to perform a split of the entire data set into train/validation(/test) data sets. This is an important one because it serves the purpose of diagnosis for overfitting. The topic will be covered in more details in the corresponding section and here a brief introduction will be given.

Figure 1. Decision boundaries for underfitted, optimal and overfitted models. [source: ibm.com/cloud/learn/overfitting]

The trained model is called to be overfitted (or overtrained) when it fails to generalise to solve a given problem.

One of examples would be that the model learns to predict exactly the training data and once given a new unseen data drawn from the same distribution it fails to predict the target corrrectly (right plot on Figure 1). Obviously, this is an undesirable behaviour since one wants their model to be \"universal\" and provide robust and correct decisions regardless of the data subset sampled from the same population.

Hence the solution to check for ability to generalise and to spot overfitting: test a trained model on a separate data set, which is the same1 as the training one. If the model performance gets significantly worse there, it is a sign that something went wrong and the model's predictive power isn't generalising to the same population.

Figure 2. Data split worflow before the training. Also cross-validation is shown as the technique to find optimal hyperparameters. [source: scikit-learn.org/stable/modules/cross_validation.html]

Clearly, the simplest way to find this data set is to put aside a part of the original one and leave it untouched until the final model is trained - this is what is called \"test\" data set in the first paragraph of this subsection. When the model has been finalised and optimised, this data set is \"unblinded\" and model performance on it is evaluated. Practically, this split can be easily performed with train_test_split() method of sklearn library.

But it might be not that simple

Indeed, there are few things to be aware of. Firstly, there is a question of how much data needs to be left for validation. Usually it is common to take the test fraction in the range [0.1, 0.4], however it is mostly up for analyzers to decide. The important trade-off which needs to be taken into account here is that between robustness of the test metric estimate (too small test data set - poorly estimated metric) and robustness of the trained model (too little training data - less performative model).

Secondly, note that the split should be done in a way that each subset is as close as possible to the one which the model will face at the final inference stage. But since usually it isn't feasible to bridge the gap between domains, the split at least should be uniform between training/testing to be able to judge fairly the model performance.

Lastly, in extreme case there might be no sufficient amount of data to perform the training, not even speaking of setting aside a part of it for validation. Here a way out would be to go for a few-shot learning, using cross-validation during the training, regularising the model to avoid overfitting or to try to find/generate more (possibly similar) data.

Lastly, one can also considering to put aside yet another fraction of original data set, what was called \"validation\" data set. This can be used to monitor the model during the training and more details on that will follow in the overfitting section.

"},{"location":"general_advice/before/inputs.html#batches","title":"Batches","text":"

Usually it is the case the training/validation/testing data set can't entirely fit into the memory due to a large size. That is why it gets split into batches (chunks) of a given size which are then fed one by one into the model during the training/testing.

While forming the batches it is important to keep in mind that batches should be sampled uniformly (i.e. from the same underlying PDF as of the original data set).

That means that each batch is populated similarly to the others according to features which are important to the given task (e.g. particles' pt/eta, number of jets, etc.). This is needed to ensure that gradients computed for each batch aren't different from each other and therefore the gradient descent doesn't encounter any sizeable stochasticities during the optimisation step.2

Lastly, it was already mentioned that one should perform preprocessing of the data set prior to training. However, this step can be substituted and/or complemented with an addition of a layer into the architecture, which will essentially do a specified part of preprocessing on every batch as they go through the model. One of the most prominent examples could be an addition of batch/group normalization, coupled with weight standardization layers which turned out to sizeably boost the performance on the large variety of benchmarks.

"},{"location":"general_advice/before/inputs.html#training-weights","title":"Training weights","text":"

Next, one can zoom into the batch and consider the level of single entries there (e.g. events). This is where the training weights come into play. Since the value of a loss function for a given batch is represented as a sum over all the entries in the batch, this sum can be naturally turned into a weighted sum. For example, in case of a cross-entropy loss with y_pred, y_true, w being vectors of predicted labels, true labels and weights respectively:

def CrossEntropy(y_pred, y_true, w): # assuming y_true = {0, 1}\n    return -w*[y_true*log(y_pred) + (1-y_true)*log(1-y_pred)]\n

It is important to disentangle here two factors which define the weight to be applied on a per-event basis because of the different motivations behind them:

  • accounting for imbalance in training data
  • accounting for imbalance in nature
"},{"location":"general_advice/before/inputs.html#imbalance-in-training-data","title":"Imbalance in training data","text":"

The first point is related to the fact, that in case of classification we may have significantly more (>O(1) times) training data for one class than for the other. Since the training data usually comes from MC simulation, that corresponds to the case when there is more events generated for one physical process than for another. Therefore, here we want to make sure that model is equally presented with instances of each class - this may have a significant impact on the model performance depending on the loss/metric choice.

Example

Consider the case when there is 1M events of target = 0 and 100 events of target = 1 in the training data set and a model is fitted by minimising cross-entropy to distinguish between those classes. In that case the resulted model can easily turn out to be a constant function predicting the majority target = 0, simply because this would be the optimal solution in terms of the loss function minimisation. If using accuracy as a metric for validation, this will result in a value close to 1 on the training data.

To account for this type of imbalance, the following weight simply needs to be introduced according to the target label of an object:

train_df['weight'] = 1\ntrain_df.loc[train_df.target == 0, 'weight'] /= np.sum(train_df.loc[train_df.target == 0, 'weight'])\ntrain_df.loc[train_df.target == 1, 'weight'] /= np.sum(train_df.loc[train_df.target == 1, 'weight'])\n

Alternatively, one can consider using other ways of balancing classes aside of those with training weights. For a more detailed description of them and also a general problem statement see imbalanced-learn documentation.

"},{"location":"general_advice/before/inputs.html#imbalance-in-nature","title":"Imbalance in nature","text":"

The second case corresponds to the fact that in experiment we expect some classes to be more represented than the others. For example, the signal process usually has way smaller cross-section than background ones and therefore we expect to have in the end fewer events of the signal class. So the motivation of using weights in that case would be to augment the optimisation problem with additional knowledge of expected contribution of physical processes.

Practically, the notion of expected number of events is incorporated into the weights per physical process so that the following conditions hold3:

As a part of this reweighting, one would naturally need to perform the normalisation as of the previous point, however the difference between those two is something which is worth emphasising.

  1. That is, sampled independently and identically (i.i.d) from the same distribution.\u00a0\u21a9

  2. Although this is a somewhat intuitive statement which may or may not be impactful for a given task and depends on the training procedure itself, it is advisable to keep this aspect in mind while preparing batches for training.\u00a0\u21a9

  3. See also Chapter 2 of the HiggsML overview document \u21a9

"},{"location":"general_advice/before/metrics.html","title":"Metrics & Losses","text":""},{"location":"general_advice/before/metrics.html#metric","title":"Metric","text":"

Metric is a function which evaluates model's performance given true labels and model predictions for a particular data set.

That makes it an important ingredient in the model training as being a measure of the model's quality. However, metrics as estimators can be sensitive to some effects (e.g. class imbalance) and provide biased or over/underoptimistic results. Additionally, they might not be relevant to a physical problem in mind and to the undestanding of what is a \"good\" model1. This in turn can result in suboptimally tuned hyperparameters or in general to suboptimally trained model.

Therefore, it is important to choose metrics wisely, so that they reflect the physical problem to be solved and additionaly don't introduce any biases in the performance estimate. The whole topic of metrics would be too broad to get covered in this section, so please refer to a corresponding documentation of sklearn as it provides an exhaustive list of available metrics with additional materials and can be used as a good starting point.

Examples of HEP-specific metrics

Speaking of those metrics which were developed in the HEP field, the most prominent one is approximate median significance (AMS), firstly introduced in Asymptotic formulae for likelihood-based tests of new physics and then adopted in the HiggsML challenge on Kaggle.

Essentially being an estimate of the expected signal sensitivity and hence being closely related to the final result of analysis, it can also be used not only as a metric but also as a loss function to be directly optimised in the training.

"},{"location":"general_advice/before/metrics.html#loss-function","title":"Loss function","text":"

In fact, metrics and loss functions are very similar to each other: they both give an estimate of how well (or bad) model performs and both used to monitor the quality of the model. So the same comments as in the metrics section apply to loss functions too. However, loss function plays a crucial role because it is additionally used in the training as a functional to be optimised. That makes its choice a handle to explicitly steer the training process towards a more optimal and relevant solution.

Example of things going wrong

It is known that L2 loss (MSE) is sensitive to outliers in data and L1 loss (MAE) on the other hand is robust to them. Therefore, if outliers were overlooked in the training data set and the model was fitted, it may result in significant bias in its predictions. As an illustration, this toy example compares Huber vs Ridge regressors, where the latter shows a more robust behaviour.

A simple example of that was already mentioned in domains section - namely, one can emphasise specific regions in the phase space by attributing events there a larger weight in the loss function. Intuitively, for the same fraction of mispredicted events in the training data set, the class with a larger attributed weight should bring more penalty to the loss function. This way model should be able to learn to pay more attention to those \"upweighted\" events2.

Examples in HEP beyond classical MSE/MAE/cross entropy
  • b-jet energy regression, being a part of nonresonant HH to bb gamma gamma analysis, uses Huber and two quantile loss terms for simultaneous prediction of point and dispersion estimators of the target disstribution.
  • DeepTau, a CMS deployed model for tau identification, uses several focal loss terms to give higher weight to more misclassified cases

However, one can go further than that and consider the training procedure from a larger, statistical inference perspective. From there, one can try to construct a loss function which would directly optimise the end goal of the analysis. INFERNO is an example of such an approach, with a loss function being an expected uncertainty on the parameter of interest. Moreover, one can try also to make the model aware of nuisance parameters which affect the analysis by incorporating those into the training procedure, please see this review for a comprehensive overview of the corresponding methods.

  1. For example, that corresponds to asking oneself a question: \"what is more suitable for the purpose of the analysis: F1-score, accuracy, recall or ROC AUC?\"\u00a0\u21a9

  2. However, these are expectations one may have in theory. In practise, optimisation procedure depends on many variables and can go in different ways. Therefore, the weighting scheme should be studied by running experiments on the case-by-case basis.\u00a0\u21a9

"},{"location":"general_advice/before/model.html","title":"Model","text":"

There is definitely an enormous variety of ML models available on the market, which makes the choice of a suitable one for a given problem at hand not entirely straightforward. So far being to a large extent an experimental field, the general advice here would be to try various and pick the one giving the best physical result.

However, there are in any case several common remarks to be pointed out, all glued together with a simple underlying idea:

Start off from a simple baseline, then gradually increase the complexity to improve upon it.

  1. In the first place, one need to carefully consider whether there is a need for training an ML model at all. There might be problems where this approach would be a (time-consuming) overkill and a simple conventional statistical methods would deliver results faster and even better.

  2. If ML methods are expected to bring improvement, then it makes sense to try out simple models first. Assuming a proper set of high-level features has been selected, ensemble of trees (random forest/boosted decision tree) or simple feedforward neural networks might be a good choice here. If time and resources permit, it might be beneficial to compare the results of these trainings to a no-ML approach (e.g. cut-based) to get the feeling of how much the gain in performance is. In most of the use cases, those models will be already sufficient to solve a given classification/regression problem in case of dealing with high-level variables.

  3. If it feels like there is still room for improvement, try hyperparameter tuning first to see if it is possible to squeeze more performance out of the current model and data. It can easily be that the model is sensitive to a hyperparameter choice and a have a sizeable variance in performance across hyperparameter space.

  4. If the hyperparameter space has been thoroughly explored and optimal point has been found, one can additionally try to play around with the data, for example, by augmenting the current data set with more samples. Since in general the model performance profits from having more training data, augmentation might also boost the overall performance.

  5. Lastly, more advanced architectures can be probed. At this point the choice of data representation plays a crucial role since more complex architectures are designed to adopt more sophisticated patterns in data. While in ML research is still ongoing to unify together all the complexity of such models (and promisingly, also using effective field theory approach), in HEP there's an ongoing process of probing various architectures to see which type fits the most in HEP field.

Models in HEP

One of the most prominent benchmarks so far is the one done by G. Kasieczka et. al on the top tagging data set, where in particular ParticleNet turned out to be a state of the art. This had been a yet another solid argument in favour of using graph neural networks in HEP due to its natural suitability in terms of data representation.

Illustration from G. Kasieczka et. al showing ROC curves for all evaluated algorithms.

"},{"location":"general_advice/during/opt.html","title":"Optimisation problems","text":"Figure 1. The loss surfaces of ResNet-56 with/without skip connections. [source: \"Visualizing the Loss Landscape of Neural Nets\" paper]

However, it might be that for a given task overfitting is of no concern, but there are still instabilities in loss function convergence happening during the training1. The loss landscape is a complex object having multiple local minima and which is moreover not at all understood due to the high dimensionality of the problem. That makes the gradient descent procedure of finding a minimum not that simple. However, if instabilities are observed, there are a few common things which could explain that:

  • The main candidate for a problem might be the learning rate (LR). Being an important hyperparameter which steers the optimisation, setting it too high make cause extremily stochastic behaviour which will likely cause the optimisation to get stuck in some random minimum being way far from optimum. Oppositely, setting it too low may cause the convergence to take very long time. The optimal value in between those extremes can still be problematic due to a chance of getting stuck in a local minimum on the way towards a better one. That is why several approaches on LR schedulers (e.g. cosine annealing) and also adaptive LR (e.g. Adam being the most prominent one) have been developed to have more flexibility during the training, as opposed to setting LR fixed from the very beginning of the training until its end.

  • Another possibility is that there are NaN/inf values or uniformities/outliers appearing in the input batches. It can cause the gradient updates to go beyond the normal scale and therefore dramatically affect the stability of the loss optimisation. This can be avoided by careful data preprocessing and batch formation.

  • Last but not the least, there is a chance that gradients will explode or vanish during the training, which will reveal itself as a rapid increase/stagnation in the loss function values. This is largely the feature of deep architectures, where during the backpropagation gradients are accumulated from one layer to another, and therefore any minor deviations in scale can exponentially amplify/diminish as they get multiplied. Since it is the scale of the trainable weights themselves which defines the weight gradients, a proper weight initialisation can foster smooth and consistent gradient updates. Also, batch normalisation together with weight standartization showed to be a powerful technique to consistently improve performance across various domains. Finally, a choice of activation function is particularly important since it directly contributes to a gradient computation. For example, a sigmoid function is known to cause gradients to vanish due to its gradient being 0 at large input values. Therefore, it is often suggested to stick to classical ReLU or try other alternatives to see if it brings improvement in performance.

  1. Sometimes particularly peculiar.\u00a0\u21a9

"},{"location":"general_advice/during/overfitting.html","title":"Overfitting","text":"

Given that the training experiment has been set up correctly (with some of the most common problems described in before training section), actually few things can go wrong during the training process itself. Broadly speaking, they fall into two categories: overfitting related and optimisation problem related. Both of them can be easily spotted by closely monitoring the training procedure, as will be described in the following.

"},{"location":"general_advice/during/overfitting.html#overfitting","title":"Overfitting","text":"

The concept of overfitting (also called overtraining) was previously introduced in inputs section and here we will elaborate a bit more on that. In its essence, overfitting as the situation where the model fails to generalise to a given problem can have several underlying explanations:

The first one would be the case where the model complexity is way too large for a problem and a data set being considered.

Example

A simple example would be fitting of some linearly distributed data with a polynomial function of a large degree. Or in general, when the number of trainable parameters is significantly larger when the size of the training data set.

This can be solved prior to training by applying regularisation to the model, which in it essence means constraining its capacity to learn the data representation. This is somewhat related also to the concept of Ockham's razor: namely that the less complex an ML model, the more likely that a good empirical result is not just due to the peculiarities of the data sample. As of the practical side of regularisation, please have a look at this webpage for a detailed overview and implementation examples.

Furthermore, a recipe for training neural networks by A. Karpathy is a highly-recommended guideline not only on regularisation, but on training ML models in general.

The second case is a more general idea that any reasonable model at some point starts to overfit.

Example

Here one can look at overfitting as the point where the model considers noise to be of the same relevance and start to \"focus\" on it way too much. Since data almost always contains noise, this makes it in principle highly probable to reach overfitting at some point.

Both of the cases outlined above can be spotted simply by tracking the evolution of loss/metrics on the validation data set . Which means that additionally to the train/test split done prior to training (as described in inputs section), one need to set aside also some fraction of the training data to perform validation throughout the training. By plotting the values of loss function/metric both on train and validation sets as the training proceeds, overfitting manifests itself as the increase in the value of the metric on the validation set while it is still continues to decrease on the training set:

Figure 1. Error metric as a function of number of iterations for train and validation sets. Vertical dashed line represents the separation between the region of underfitting (model hasn't captured well the data complexity to solve the problem) and overfitting (model does not longer generalise to unseen data). The point between these two regions is the optimal moment when the training should stop. [source: ibm.com/cloud/learn/overfitting]

Essentially, it means that from that turning point onwards the model is trying to learn better and better the noise in training data at the expense of generalisation power. Therefore, it doesn't make sense to train the model from that point on and the training should be stopped.

To automate the process of finding this \"sweat spot\", many ML libraries include early stopping as one of its parameters in the fit() function. If early stopping is set to, for example, 10 iterations, the training will automatically stop once the validation metric is no longer improving for the last 10 iterations.

"},{"location":"general_advice/during/xvalidation.html","title":"Cross-validation","text":"

However, in practice what one often deals with is a hyperparameter optimisation - running of several trainings to find the optimal hyperparameter for a given family of models (e.g. BDT or feed-forward NN).

The number of trials in the hyperparameter space can easily reach hundreds or thousands, and in that case naive approach of training the model for each hyperparameters' set on the same train data set and evaluating its performance on the same test data set is very likely prone to overfitting. In that case, an experimentalist overfits to the test data set by choosing the best value of the metric and effectively adapting the model to suit the test data set best, therefore loosing the model's ability to generalise.

In order to prevent that, a cross-validation (CV) technique is often used:

Figure 1. Illustration of the data set split for cross-validation. [source: scikit-learn.org/stable/modules/cross_validation.html]

The idea behind it is that instead of a single split of the data into train/validation sets, the training data set is split into N folds. Then, the model with the same fixed hyperparameter set is trained N times in a way that at the i-th iteration the i-th fold is left out of the training and used only for validation, while the other N-1 folds are used for the training.

In this fashion, after the training of N models in the end there is N values of a metric computed on each fold. The values now can be averaged to give a more robust estimate of model performance for a given hyperparameter set. Also a variance can be computed to estimate the range of metric values. After having completed the N-fold CV training, the same approach is to be repeated for other hyperparameter values and the best set of those is picked based on the best fold-averaged metric value.

Further insights

Effectively, with CV approach the whole training data set plays the role of a validation one, which makes the overfitting to a single chunk of it (as in naive train/val split) less likely to happen. Complementary to that, more training data is used to train a single model oppositely to a single and fixed train/val split, moreover making the model less dependant on the choice of the split.

Alternatively, one can think of this procedure is of building a model ensemble which is inherently an approach more robust to overfitting and in general performing better than a single model.

"},{"location":"inference/checklist.html","title":"Integration checklist","text":"

Todo.

"},{"location":"inference/conifer.html","title":"Direct inference with conifer","text":""},{"location":"inference/conifer.html#introduction","title":"Introduction","text":"

conifer is a Python package developed by the Fast Machine Learning Lab for the deployment of Boosted Decision Trees in FPGAs for Level 1 Trigger applications. Documentation, examples, and tutorials are available from the conifer website, GitHub, and the hls4ml tutorial respectively. conifer is on the Python Package Index and can be installed like pip install conifer. Targeting FPGAs requires Xilinx's Vivado/Vitis suite of software. Here's a brief summary of features:

  • conversion from common BDT training frameworks: scikit-learn, XGBoost, Tensorflow Decision Forests (TF DF), TMVA, and ONNX
  • conversion to FPGA firmware with backends: HLS (C++ for FPGA), VHDL, C++ (for CPU)
  • utilities for bit- and cycle-accurate firmware simulation, and interface to FPGA synthesis tools for evaluation and deployment from Python
"},{"location":"inference/conifer.html#emulation-in-cmssw","title":"Emulation in CMSSW","text":"

All L1T algorithms require bit-exact emulation for performance studies and validation of the hardware system. For conifer this is provided with a single header file at L1Trigger/Phase2L1ParticleFlow/interface/conifer.h. The user must also provide the BDT JSON file exported from the conifer Python tool for their model. JSON loading in CMSSW uses the nlohmann/json external.

Both the conifer FPGA firmware and C++ emulation use Xilinx's arbitrary precision types for fixed-point arithmetic (hls external of CMSSW). This is cheaper and faster in the FPGA fabric than floating-point types. An important part of the model preparation process is choosing the proper fixed-point data types to avoid loss of performance compared to the trained model. Input preprocessing, in particular scaling, can help constrain the input variables to a smaller numerical range, but may also have a hardware cost to implement. In C++ the arbitrary precision types are specified like: ap_fixed<width, integer, rounding mode, saturation mode>.

Minimal preparation from Python:

import conifer\nmodel = conifer. ... # convert or load a conifer model\n# e.g. model = conifer.converters.convert_from_xgboost(xgboost_model)\nmodel.save('my_bdt.json')\n

CMSSW C++ user code:

// include the conifer emulation header file\n#include \"L1Trigger/Phase2L1ParticleFlow/interface/conifer.h\"\n\n... model setup\n// define the input/threshold and score types\n// important: this needs to match the firmware settings for bit-exactness!\n// note: can use native types like float/double for development/debugging\ntypedef ap_fixed<18,8> input_t;\ntypedef ap_fixed<12,3,AP_RND_CONV,AP_SAT> score_t;\n\n// create a conifer BDT instance\n// 'true' to use balanced add-tree score aggregation (needed for bit-exactness)\nbdt = conifer::BDT<input_t, score_t, true>(\"my_bdt.json\");\n\n... inference\n// prepare the inputs, vector length same as model n_features\nstd::vector<input_t> inputs = ... \n// run inference, scores vector length same as model n_classes (or 1 for binary classification/regression)\nstd::vector<score_t> scores = bdt.decision_function(inputs);\n

conifer does not compute class probabilities from the raw predictions for the avoidance of extra resource and latency cost in the L1T deployment. Cuts or working points should therefore be applied on the raw predictions.

"},{"location":"inference/hls4ml.html","title":"Direct inference with hls4ml","text":"

hls4ml is a Python package developed by the Fast Machine Learning Lab. It's primary purpose is to create firmware implementations of machine learning (ML) models to be run on FPGAs. The package interfaces with a high-level synthesis (HLS) backend (i.e. Xilinx Vivado HLS) to transpile the ML model into hardware description language (HDL). The primary hls4ml documentation, including API reference pages, is located here.

The main hls4ml tutorial code is kept on GitHub. Users are welcome to walk through the notebooks at their own pace. There is also a set of slides linked to the README.

That said, there have been several cases where the hls4ml developers have given live demonstrations and tutorials. Below is a non-exhaustive list of tutorials given in the last few years (newest on top).

Workshop/Conference Date Links 23rd Virtual IEEE Real Time Conference August 03, 2022 Indico 2022 CMS ML Town Hall July 22, 2022 Contribution Link a3d3 hls4ml @ Snowmass CSS 2022: Tutorial July 21, 2022 Slides, Recording, JupyterHub Fast Machine Learning for Science Workshop December 3, 2020 Indico, Slides, GitHub, Interactive Notebooks hls4ml @ UZH ML Workshop November 17, 2020 Indico, Slides ICCAD 2020 November 5, 2020 https://events-siteplex.confcats.io/iccad2022/wp-content/uploads/sites/72/2021/12/2020_ICCAD_ConferenceProgram.pdf, GitHub 4th IML Workshop October 19, 2020 Indico, Slides, Instructions, Notebooks, Recording 22nd Virtual IEEE Real Time Conference October 15, 2020 Indico, Slides, Notebooks 30th International Conference on Field-Programmable Logic and Applications September 4, 2020 Program hls4ml tutorial @ CERN June 3, 2020 Indico, Slides, Notebooks Fast Machine Learning September 12, 2019 Indico 1st Real Time Analysis Workshop, Universit\u00e9 Paris-Saclay July 16, 2019 Indico, Slides, Autoencoder Tutorial"},{"location":"inference/onnx.html","title":"Direct inference with ONNX Runtime","text":"

ONNX is an open format built to represent machine learning models. It is designed to improve interoperability across a variety of frameworks and platforms in the AI tools community\u2014most deep learning frameworks (e.g. XGBoost, TensorFlow, PyTorch which are frequently used in CMS) support converting their model into the ONNX format or loading a model from an ONNX format.

The figure showing the ONNX interoperability. (Source from website.)

ONNX Runtime is a tool aiming for the acceleration of machine learning inferencing across a variety of deployment platforms. It allows to \"run any ONNX model using a single set of inference APIs that provide access to the best hardware acceleration available\". It includes \"built-in optimization features that trim and consolidate nodes without impacting model accuracy.\"

The CMSSW interface to ONNX Runtime is avaiable since CMSSW_11_1_X (cmssw#28112, cmsdist#5020). Its functionality is improved in CMSSW_11_2_X. The final implementation is also backported to CMSSW_10_6_X to facilitate Run 2 UL data reprocessing. The inference of a number of deep learning tagger models (e.g. DeepJet, DeepTauID, ParticleNet, DeepDoubleX, etc.) has been made with ONNX Runtime in the routine of UL processing and has gained substantial speedup.

On this page, we will use a simple example to show how to use ONNX Runtime for deep learning model inference in the CMSSW framework, both in C++ (e.g. to process the MiniAOD file) and in Python (e.g. using NanoAOD-tools to process the NanoAODs). This may help readers who will deploy an ONNX model into their analyses or in the CMSSW framework.

"},{"location":"inference/onnx.html#software-setup","title":"Software Setup","text":"

We use CMSSW_11_2_5_patch2 to show the simple example for ONNX Runtime inference. The example can also work under the new 12 releases (note that inference with C++ can also run on CMSSW_10_6_X)

export SCRAM_ARCH=\"slc7_amd64_gcc900\"\nexport CMSSW_VERSION=\"CMSSW_11_2_5_patch2\"\n\nsource /cvmfs/cms.cern.ch/cmsset_default.sh\n\ncmsrel \"$CMSSW_VERSION\"\ncd \"$CMSSW_VERSION/src\"\n\ncmsenv\nscram b\n
"},{"location":"inference/onnx.html#converting-model-to-onnx","title":"Converting model to ONNX","text":"

The model deployed into CMSSW or our analysis needs to be converted to ONNX from the original framework format where it is trained. Please see here for a nice deck of tutorials on converting models from different mainstream frameworks into ONNX.

Here we take PyTorch as an example. A PyTorch model can be converted by torch.onnx.export(...). As a simple illustration, we convert a randomly initialized feed-forward network implemented in PyTorch, with 10 input nodes and 2 output nodes, and two hidden layers with 64 nodes each. The conversion code is presented below. The output model model.onnx will be deployed under the CMSSW framework in our following tutorial.

Click to expand
import torch\nimport torch.nn as nn\ntorch.manual_seed(42)\n\nclass SimpleMLP(nn.Module):\n\n    def __init__(self, **kwargs):\n        super(SimpleMLP, self).__init__(**kwargs)\n        self.mlp = nn.Sequential(\n            nn.Linear(10, 64), nn.BatchNorm1d(64), nn.ReLU(), \n            nn.Linear(64, 64), nn.BatchNorm1d(64), nn.ReLU(), \n            nn.Linear(64, 2), nn.ReLU(), \n            )\n    def forward(self, x):\n        # input x: (batch_size, feature_dim=10)\n        x = self.mlp(x)\n        return torch.softmax(x, dim=1)\n\nmodel = SimpleMLP()\n\n# create dummy input for the model\ndummy_input = torch.ones(1, 10, requires_grad=True) # batch size = 1\n\n# export model to ONNX\ntorch.onnx.export(model, dummy_input, \"model.onnx\", verbose=True, input_names=['my_input'], output_names=['my_output'])\n
"},{"location":"inference/onnx.html#inference-in-cmssw-c","title":"Inference in CMSSW (C++)","text":"

We will introduce how to write a module to run inference on the ONNX model under the CMSSW framework. CMSSW is known for its multi-threaded ability. In a threaded framework, multiple threads are served for processing events in the event loop. The logic is straightforward: a new event is assigned to idled threads following the first-come-first-serve princlple.

In most cases, each thread is able to process events individually as the majority of event processing workflow can be accomplished only by seeing the information of that event. Thus, the stream modules (stream EDAnalyzer and stream EDFilter) are used frequently as each thread holds an individual copy of the module instance\u2014they do not need to communicate with each other. It is however also possible to share a global cache object between all threads in case sharing information across threads is necessary. In all, such CMSSW EDAnalyzer modules are declared by class MyPlugin : public edm::stream::EDAnalyzer<edm::GlobalCache<CacheData>> (similar for EDFilter). Details can be found in documentation on the C++ interface of stream modules.

Let's then think about what would happen when interfacing CMSSW with ONNX for model inference. When ONNX Runtime accepts a model, it converts the model into an in-memory representation, and performance a variety of optimizations depending on the operators in the model. The procedure is done when an ONNX Runtime Session is created with an inputting model. The economic method will then be to hold only one Session for all threads\u2014this may save memory to a large extent, as the model has only one copy in memory. Upon request from multiple threads to do inference with their input data, the Session accepts those requests and serializes them, then produces the output data. ONNX Runtime has by design accepted that multithread threads invoke the Run() method on the same inference Session object. Therefore, what has left us to do is to

  1. create a Session as a global object in our CMSSW module and share it among all threads;
  2. in each thread, we process the input data and then call the Run() method from that global Session.

That's the main logic for implementing ONNX inference in CMSSW. For details of high-level designs of ONNX Runtime, please see documentation here.

With this concept, let's build the module.

"},{"location":"inference/onnx.html#1-includes","title":"1. includes","text":"
#include \"FWCore/Framework/interface/stream/EDAnalyzer.h\"\n#include \"PhysicsTools/ONNXRuntime/interface/ONNXRuntime.h\"\n// further framework includes\n...\n

We include stream/EDAnalyzer.h to build the stream CMSSW module.

"},{"location":"inference/onnx.html#2-global-cache-object","title":"2. Global cache object","text":"

In CMSSW there exists a class ONNXRuntime which can be used directly as the global cache object. Upon initialization from a given model, it holds the ONNX Runtime Session object and provides the handle to invoke the Run() for model inference.

We put the ONNXRuntime class in the edm::GlobalCache template argument:

class MyPlugin : public edm::stream::EDAnalyzer<edm::GlobalCache<ONNXRuntime>> {\n...\n};\n
"},{"location":"inference/onnx.html#3-initiate-objects","title":"3. Initiate objects","text":"

In the stream EDAnlyzer module, it provides a hook initializeGlobalCache() to initiate the global object. We simply do

std::unique_ptr<ONNXRuntime> MyPlugin::initializeGlobalCache(const edm::ParameterSet &iConfig) {\nreturn std::make_unique<ONNXRuntime>(iConfig.getParameter<edm::FileInPath>(\"model_path\").fullPath());\n}\n

to initiate the ONNXRuntime object upon a given model path.

"},{"location":"inference/onnx.html#4-inference","title":"4. Inference","text":"

We know the event processing step is implemented in the void EDAnalyzer::analyze method. When an event is assigned to a valid thread, the content will be processed in that thread. This can go in parallel with other threads processing other events.

We need to first construct the input data dedicated to the event. Here we create a dummy input: a sequence of consecutive integers of length 10. The input is set by replacing the values of our pre-booked vector, data_. This member variable has vector<vector<float>> format and is initialised as { {0, 0, ..., 0} } (contains only one element, which is a vector of 10 zeros). In processing of each event, the input data_ is modified:

std::vector<float> &group_data = data_[0];\nfor (size_t i = 0; i < 10; i++){\ngroup_data[i] = float(iEvent.id().event() % 100 + i);\n}\n

Then, we send data_ to the inference engine and get the model output:

std::vector<float> outputs = globalCache()->run(input_names_, data_, input_shapes_)[0];\n

We clarify a few details here.

First, we use globalCache() which is a class method in our stream CMSSW module to access the global object shared across all threads. In our case it is the ONNXRuntime instance.

The run() method is a wrapper to call Run() on the ONNX Session. Definations on the method arguments are (code from link):

// Run inference and get outputs\n// input_names: list of the names of the input nodes.\n// input_values: list of input arrays for each input node. The order of `input_values` must match `input_names`.\n// input_shapes: list of `int64_t` arrays specifying the shape of each input node. Can leave empty if the model does not have dynamic axes.\n// output_names: names of the output nodes to get outputs from. Empty list means all output nodes.\n// batch_size: number of samples in the batch. Each array in `input_values` must have a shape layout of (batch_size, ...).\n// Returns: a std::vector<std::vector<float>>, with the order matched to `output_names`.\n// When `output_names` is empty, will return all outputs ordered as in `getOutputNames()`.\nFloatArrays run(const std::vector<std::string>& input_names,\nFloatArrays& input_values,\nconst std::vector<std::vector<int64_t>>& input_shapes = {},\nconst std::vector<std::string>& output_names = {},\nint64_t batch_size = 1) const;\n
where we have
typedef std::vector<std::vector<float>> FloatArrays;\n

In our case, input_names is set to {\"my_input\"} which corresponds to the names upon model creation. input_values is a length-1 vector, and input_values[0] is a vector of float of length 10, which are inputs to the 10 nodes. input_shapes can be set empty here and will be necessary for advanced usage, when our input has dynamic lengths (e.g., in boosed jet tagging, we use different numbers of particle-flow candidates and secondary vertices as input).

For the usual model design, we have only one vector of output. In such a case, the output is simply a length-1 vector, and we use [0] to get the vector of two float numbers\u2014the output of the model.

"},{"location":"inference/onnx.html#full-example","title":"Full example","text":"

Let's construct the full example.

Click to expand

The example assumes the following directory structure:

MySubsystem/MyModule/\n\u2502\n\u251c\u2500\u2500 plugins/\n\u2502   \u251c\u2500\u2500 MyPlugin.cpp\n\u2502   \u2514\u2500\u2500 BuildFile.xml\n\u2502\n\u251c\u2500\u2500 test/\n\u2502   \u2514\u2500\u2500 my_plugin_cfg.py\n\u2502\n\u2514\u2500\u2500 data/\n    \u2514\u2500\u2500 model.onnx\n
plugins/MyPlugin.cppplugins/BuildFile.xmltest/my_plugin_cfg.pydata/model.onnx
/*\n * Example plugin to demonstrate the direct multi-threaded inference with ONNX Runtime.\n */\n\n#include <memory>\n#include <iostream>\n\n#include \"FWCore/Framework/interface/Event.h\"\n#include \"FWCore/Framework/interface/Frameworkfwd.h\"\n#include \"FWCore/Framework/interface/MakerMacros.h\"\n#include \"FWCore/Framework/interface/stream/EDAnalyzer.h\"\n#include \"FWCore/ParameterSet/interface/ParameterSet.h\"\n\n#include \"PhysicsTools/ONNXRuntime/interface/ONNXRuntime.h\"\n\nusing namespace cms::Ort;\n\nclass MyPlugin : public edm::stream::EDAnalyzer<edm::GlobalCache<ONNXRuntime>> {\npublic:\nexplicit MyPlugin(const edm::ParameterSet &, const ONNXRuntime *);\nstatic void fillDescriptions(edm::ConfigurationDescriptions&);\n\nstatic std::unique_ptr<ONNXRuntime> initializeGlobalCache(const edm::ParameterSet &);\nstatic void globalEndJob(const ONNXRuntime *);\n\nprivate:\nvoid beginJob();\nvoid analyze(const edm::Event&, const edm::EventSetup&);\nvoid endJob();\n\nstd::vector<std::string> input_names_;\nstd::vector<std::vector<int64_t>> input_shapes_;\nFloatArrays data_; // each stream hosts its own data\n};\n\n\nvoid MyPlugin::fillDescriptions(edm::ConfigurationDescriptions& descriptions) {\n// defining this function will lead to a *_cfi file being generated when compiling\nedm::ParameterSetDescription desc;\ndesc.add<edm::FileInPath>(\"model_path\", edm::FileInPath(\"MySubsystem/MyModule/data/model.onnx\"));\ndesc.add<std::vector<std::string>>(\"input_names\", std::vector<std::string>({\"my_input\"}));\ndescriptions.addWithDefaultLabel(desc);\n}\n\n\nMyPlugin::MyPlugin(const edm::ParameterSet &iConfig, const ONNXRuntime *cache)\n: input_names_(iConfig.getParameter<std::vector<std::string>>(\"input_names\")),\ninput_shapes_() {\n// initialize the input data arrays\n// note there is only one element in the FloatArrays type (i.e. vector<vector<float>>) variable\ndata_.emplace_back(10, 0);\n}\n\n\nstd::unique_ptr<ONNXRuntime> MyPlugin::initializeGlobalCache(const edm::ParameterSet &iConfig) {\nreturn std::make_unique<ONNXRuntime>(iConfig.getParameter<edm::FileInPath>(\"model_path\").fullPath());\n}\n\nvoid MyPlugin::globalEndJob(const ONNXRuntime *cache) {}\n\nvoid MyPlugin::analyze(const edm::Event &iEvent, const edm::EventSetup &iSetup) {\n// prepare dummy inputs for every event\nstd::vector<float> &group_data = data_[0];\nfor (size_t i = 0; i < 10; i++){\ngroup_data[i] = float(iEvent.id().event() % 100 + i);\n}\n\n// run prediction and get outputs\nstd::vector<float> outputs = globalCache()->run(input_names_, data_, input_shapes_)[0];\n\n// print the input and output data\nstd::cout << \"input data -> \";\nfor (auto &i: group_data) { std::cout << i << \" \"; }\nstd::cout << std::endl << \"output data -> \";\nfor (auto &i: outputs) { std::cout << i << \" \"; }\nstd::cout << std::endl;\n\n}\n\nDEFINE_FWK_MODULE(MyPlugin);\n
<use name=\"FWCore/Framework\" />\n<use name=\"FWCore/PluginManager\" />\n<use name=\"FWCore/ParameterSet\" />\n<use name=\"PhysicsTools/ONNXRuntime\" />\n\n<flags EDM_PLUGIN=\"1\" />\n
# coding: utf-8\n\nimport os\n\nimport FWCore.ParameterSet.Config as cms\nfrom FWCore.ParameterSet.VarParsing import VarParsing\n\n\n# setup minimal options\noptions = VarParsing(\"python\")\noptions.setDefault(\"inputFiles\", \"/store/mc/RunIISummer20UL18MiniAODv2/DYJetsToLL_M-50_TuneCP5_13TeV-amcatnloFXFX-pythia8/MINIAODSIM/106X_upgrade2018_realistic_v16_L1v1-v2/230000/4C8619B2-D0C0-4647-B946-B33754F4ED16.root\")  # noqa\noptions.parseArguments()\n\n# define the process to run\nprocess = cms.Process(\"TEST\")\n\n# minimal configuration\nprocess.load(\"FWCore.MessageService.MessageLogger_cfi\")\nprocess.MessageLogger.cerr.FwkReport.reportEvery = 1\nprocess.maxEvents = cms.untracked.PSet(input=cms.untracked.int32(10))\nprocess.source = cms.Source(\"PoolSource\",\n    fileNames=cms.untracked.vstring(options.inputFiles))\n\n# process options\nprocess.options = cms.untracked.PSet(\n    allowUnscheduled=cms.untracked.bool(True),\n    wantSummary=cms.untracked.bool(True),\n)\n\n# setup options for multithreaded\nprocess.options.numberOfThreads=cms.untracked.uint32(1)\nprocess.options.numberOfStreams=cms.untracked.uint32(0)\nprocess.options.numberOfConcurrentLuminosityBlocks=cms.untracked.uint32(1)\n\n\n# setup MyPlugin by loading the auto-generated cfi (see MyPlugin.fillDescriptions)\nprocess.load(\"MySubsystem.MyModule.myPlugin_cfi\")\n# specify the path of the ONNX model\nprocess.myPlugin.model_path = \"MySubsystem/MyModule/data/model.onnx\"\n# input names as defined in the model\n# the order of name strings should also corresponds to the order of input data array feed to the model\nprocess.myPlugin.input_names = [\"my_input\"]\n\n# define what to run in the path\nprocess.p = cms.Path(process.myPlugin)\n

The model is produced by code in the section \"Converting model to ONNX\" and can be downloaded here.

"},{"location":"inference/onnx.html#test-our-module","title":"Test our module","text":"

Under MySubsystem/MyModule/test, run cmsRun my_plugin_cfg.py to launch our module. You may see the following from the output, which include the input and output vectors in the inference process.

Click to see the output
...\n19-Jul-2022 10:50:41 CEST  Successfully opened file root://xrootd-cms.infn.it//store/mc/RunIISummer20UL18MiniAODv2/DYJetsToLL_M-50_TuneCP5_13TeV-amcatnloFXFX-pythia8/MINIAODSIM/106X_upgrade2018_realistic_v16_L1v1-v2/230000/4C8619B2-D0C0-4647-B946-B33754F4ED16.root\nBegin processing the 1st record. Run 1, Event 27074045, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.494 CEST\ninput data -> 45 46 47 48 49 50 51 52 53 54\noutput data -> 0.995657 0.00434343\nBegin processing the 2nd record. Run 1, Event 27074048, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.495 CEST\ninput data -> 48 49 50 51 52 53 54 55 56 57\noutput data -> 0.996884 0.00311563\nBegin processing the 3rd record. Run 1, Event 27074059, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.495 CEST\ninput data -> 59 60 61 62 63 64 65 66 67 68\noutput data -> 0.999081 0.000919373\nBegin processing the 4th record. Run 1, Event 27074061, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.495 CEST\ninput data -> 61 62 63 64 65 66 67 68 69 70\noutput data -> 0.999264 0.000736247\nBegin processing the 5th record. Run 1, Event 27074046, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.496 CEST\ninput data -> 46 47 48 49 50 51 52 53 54 55\noutput data -> 0.996112 0.00388828\nBegin processing the 6th record. Run 1, Event 27074047, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.496 CEST\ninput data -> 47 48 49 50 51 52 53 54 55 56\noutput data -> 0.996519 0.00348065\nBegin processing the 7th record. Run 1, Event 27074064, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.496 CEST\ninput data -> 64 65 66 67 68 69 70 71 72 73\noutput data -> 0.999472 0.000527586\nBegin processing the 8th record. Run 1, Event 27074074, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.496 CEST\ninput data -> 74 75 76 77 78 79 80 81 82 83\noutput data -> 0.999826 0.000173664\nBegin processing the 9th record. Run 1, Event 27074050, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.496 CEST\ninput data -> 50 51 52 53 54 55 56 57 58 59\noutput data -> 0.997504 0.00249614\nBegin processing the 10th record. Run 1, Event 27074060, LumiSection 10021 on stream 0 at 19-Jul-2022 10:50:43.496 CEST\ninput data -> 60 61 62 63 64 65 66 67 68 69\noutput data -> 0.999177 0.000822734\n19-Jul-2022 10:50:43 CEST  Closed file root://xrootd-cms.infn.it//store/mc/RunIISummer20UL18MiniAODv2/DYJetsToLL_M-50_TuneCP5_13TeV-amcatnloFXFX-pythia8/MINIAODSIM/106X_upgrade2018_realistic_v16_L1v1-v2/230000/4C8619B2-D0C0-4647-B946-B33754F4ED16.root\n

Also we could try launching the script with more threads. Change the corresponding line in my_plugin_cfg.py as follows to activate the multi-threaded mode with 4 threads.

process.options.numberOfThreads=cms.untracked.uint32(4)\n

Launch the script again, and one could see the same results, but with the inference processed concurrently on 4 threads.

"},{"location":"inference/onnx.html#inference-in-cmssw-python","title":"Inference in CMSSW (Python)","text":"

Doing ONNX Runtime inference with python is possible as well. For those releases that have the ONNX Runtime C++ package installed, the onnxruntime python package is also installed in python3 (except for CMSSW_10_6_X). We still use CMSSW_11_2_5_patch2 to run our examples. We could quickly check if onnxruntime is available by:

python3 -c \"import onnxruntime; print('onnxruntime available')\"\n

The python code is simple to construct: following the quick examples \"Get started with ORT for Python\", we create the file MySubsystem/MyModule/test/my_standalone_test.py as follows:

import onnxruntime as ort\nimport numpy as np\n\n# create input data in the float format (32 bit)\ndata = np.arange(45, 55).astype(np.float32)\n\n# create inference session using ort.InferenceSession from a given model\nort_sess = ort.InferenceSession('../data/model.onnx')\n\n# run inference\noutputs = ort_sess.run(None, {'my_input': np.array([data])})[0]\n\n# print input and output\nprint('input ->', data)\nprint('output ->', outputs)\n

Under the directory MySubsystem/MyModule/test, run the example with python3 my_standalone_test.py. Then we see the output:

input -> [45. 46. 47. 48. 49. 50. 51. 52. 53. 54.]\noutput -> [[0.9956566  0.00434343]]\n

Using ONNX Runtime on NanoAOD-tools follows the same logic. Here we create the ONNX Session in the beginning stage and run inference in the event loop. Note that NanoAOD-tools runs the event loop in the single-thread mode.

Please find details in the following block.

Click to see the NanoAOD-tools example

We run the NanoAOD-tools example following the above CMSSW_11_2_5_patch2 environment. According to the setup instruction in NanoAOD-tools, do

cd $CMSSW_BASE/src\ngit clone https://github.com/cms-nanoAOD/nanoAOD-tools.git PhysicsTools/NanoAODTools\ncd PhysicsTools/NanoAODTools\ncmsenv\nscram b\n

Now we add our custom module to run ONNX Runtime inference. Create a file PhysicsTools/NanoAODTools/python/postprocessing/examples/exampleOrtModule.py with the content:

from PhysicsTools.NanoAODTools.postprocessing.framework.datamodel import Collection\nfrom PhysicsTools.NanoAODTools.postprocessing.framework.eventloop import Module\nimport ROOT\nROOT.PyConfig.IgnoreCommandLineOptions = True\n\nimport onnxruntime as ort\nimport numpy as np\nimport os \n\nclass exampleOrtProducer(Module):\n    def __init__(self):\n        pass\n\n    def beginJob(self):\n        model_path = os.path.join(os.getenv(\"CMSSW_BASE\"), 'src', 'MySubsystem/MyModule/data/model.onnx')\nself.ort_sess = ort.InferenceSession(model_path)\ndef endJob(self):\n        pass\n\n    def beginFile(self, inputFile, outputFile, inputTree, wrappedOutputTree):\n        self.out = wrappedOutputTree\n        self.out.branch(\"OrtScore\", \"F\")\n\n    def endFile(self, inputFile, outputFile, inputTree, wrappedOutputTree):\n        pass\n\n    def analyze(self, event):\n\"\"\"process event, return True (go to next module) or False (fail, go to next event)\"\"\"\n\n        # create input data\n        data = np.arange(event.event % 100, event.event % 100 + 10).astype(np.float32)\n        # run inference\noutputs = self.ort_sess.run(None, {'my_input': np.array([data])})[0]\n# print input and output\n        print('input ->', data)\n        print('output ->', outputs)\n\n        self.out.fillBranch(\"OrtScore\", outputs[0][0])\n        return True\n\n\n# define modules using the syntax 'name = lambda : constructor' to avoid having them loaded when not needed\n\nexampleOrtModuleConstr = lambda: exampleOrtProducer()\n

Please notice the highlighted lines for the creation of ONNX Runtime Session and launching the inference.

Finally, following the test command from NanoAOD-tools, we run our custom module in python3 by

python3 scripts/nano_postproc.py outDir /eos/cms/store/user/andrey/f.root -I PhysicsTools.NanoAODTools.postprocessing.examples.exampleOrtModule exampleOrtModuleConstr -N 10\n

We should see the output as follows

processing.examples.exampleOrtModule exampleOrtModuleConstr -N 10\nLoading exampleOrtModuleConstr from PhysicsTools.NanoAODTools.postprocessing.examples.exampleOrtModule\nWill write selected trees to outDir\nPre-select 10 entries out of 10 (100.00%)\ninput -> [11. 12. 13. 14. 15. 16. 17. 18. 19. 20.]\noutput -> [[0.83919346 0.16080655]]\ninput -> [ 7.  8.  9. 10. 11. 12. 13. 14. 15. 16.]\noutput -> [[0.76994413 0.2300559 ]]\ninput -> [ 4.  5.  6.  7.  8.  9. 10. 11. 12. 13.]\noutput -> [[0.7116992 0.2883008]]\ninput -> [ 2.  3.  4.  5.  6.  7.  8.  9. 10. 11.]\noutput -> [[0.66414535 0.33585465]]\ninput -> [ 9. 10. 11. 12. 13. 14. 15. 16. 17. 18.]\noutput -> [[0.80617136 0.19382869]]\ninput -> [ 6.  7.  8.  9. 10. 11. 12. 13. 14. 15.]\noutput -> [[0.75187963 0.2481204 ]]\ninput -> [16. 17. 18. 19. 20. 21. 22. 23. 24. 25.]\noutput -> [[0.9014619  0.09853811]]\ninput -> [18. 19. 20. 21. 22. 23. 24. 25. 26. 27.]\noutput -> [[0.9202239  0.07977609]]\ninput -> [ 5.  6.  7.  8.  9. 10. 11. 12. 13. 14.]\noutput -> [[0.7330253  0.26697478]]\ninput -> [10. 11. 12. 13. 14. 15. 16. 17. 18. 19.]\noutput -> [[0.82333535 0.17666471]]\nProcessed 10 preselected entries from /eos/cms/store/user/andrey/f.root (10 entries). Finally selected 10 entries\nDone outDir/f_Skim.root\nTotal time 1.1 sec. to process 10 events. Rate = 9.3 Hz.\n

"},{"location":"inference/onnx.html#links-and-further-reading","title":"Links and further reading","text":"
  • ONNX/ONNX Runtime
    • Tutorials on converting models to ONNX format
    • ONNX Runtime C++ example
    • ONNX Runtime C++ API
    • ONNX Runtime python example
    • ONNX Runtime python API
    • ONNX Runtime in CMSSW (talk)

Developers: Huilin Qu

Authors: Congqiao Li

"},{"location":"inference/particlenet.html","title":"ParticleNet","text":"

ParticleNet [arXiv:1902.08570] is an advanced neural network architecture that has many applications in CMS, including heavy flavour jet tagging, jet mass regression, etc. The network is fed by various low-level point-like objects as input, e.g., the particle-flow candidates, to predict a feature of a jet.

The full architecture of the ParticleNet model. We'll walk through the details in the following sections.

On this page, we introduce several user-specific aspects of the ParticleNet model. We cover the following items in three sections:

  1. An introduction to ParticleNet, including

    • a general description of ParticleNet
    • the advantages brought from the architecture by concept
    • a sketch of ParticleNet applications in CMS and other relevant works
  2. An introduction to Weaver and model implementations, introduced in a step-by-step manner:

    • build three network models and understand them from the technical side; use the out-of-the-box commands to run these examples on a benchmark task. The three networks are (1) a simple feed-forward NN, (2) a DeepAK8 model (based on 1D CNN), and eventually (3) the ParticleNet model (based on DGCNN).
    • try to reproduce the original performance and make the ROC plots.

    This section is friendly to the ML newcomers. The goal is to help readers understand the underlying structure of the \"ParticleNet\".

  3. Tuning the ParticleNet model, including

    • tips for readers who are using/modifying the ParticleNet model to achieve a better performance

    This section can be helpful in practice. It provides tips on model training, tunning, validation, etc. It targets the situations when readers apply their own ParticleNet (or ParticleNet-like) model to the custom task.

Corresponding persons:

  • Huilin Qu, Loukas Gouskos (original developers of ParticleNet)
  • Congqiao Li (author of the page)
"},{"location":"inference/particlenet.html#introduction-to-particlenet","title":"Introduction to ParticleNet","text":""},{"location":"inference/particlenet.html#1-general-description","title":"1. General description","text":"

ParticleNet is a graph neural net (GNN) model. The key ingredient of ParticleNet is the graph convolutional operation, i.e., the edge convolution (EdgeConv) and the dynamic graph CNN (DGCNN) method [arXiv:1801.07829] applied on the \"point cloud\" data structure.

We will disassemble the ParticleNet model and provide a detailed exploration in the next section, but here we briefly explain the key features of the model.

Intuitively, ParticleNet treats all candidates inside an object as a \"point cloud\", which is a permutational-invariant set of points (e.g. a set of PF candidates), each carrying a feature vector (\u03b7, \u03c6, pT, charge, etc.). The DGCNN uses the EdgeConv operation to exploit their spatial correlations (two-dimensional on the \u03b7-\u03c6 plain) by finding the k-nearest neighbours of each point and generate a new latent graph layer where points are scattered on a high-dimensional latent space. This is a graph-type analogue of the classical 2D convolution operation, which acts on a regular 2D grid (e.g., a picture) using a 3\u00d73 local patch to explore the relations of a single-pixel with its 8 nearest pixels, then generates a new 2D grid.

The cartoon illustrates the convolutional operation acted on the regular grid and on the point cloud (plot from ML4Jets 2018 talk).

As a consequence, the EdgeConv operation transforms the graph to a new graph, which has a changed spatial relationship among points. It then acts on the second graph to produce the third graph, showing the stackability of the convolution operation. This illustrates the \"dynamic\" property as the graph topology changes after each EdgeConv layer.

"},{"location":"inference/particlenet.html#2-advantage","title":"2. Advantage","text":"

By concept, the advantage of the network may come from exploiting the permutational-invariant symmetry of the points, which is intrinsic to our physics objects. This symmetry is held naturally in a point cloud representation.

In a recent study on jet physics or event-based analysis using ML techniques, there are increasing interest to explore the point cloud data structure. We explain here conceptually why a \"point cloud\" representation outperforms the classical ones, including the variable-length 2D vector structure passing to a 1D CNN or any type of RNN, and imaged-based representation passing through a 2D CNN. By using the 1D CNN, the points (PF candidates) are more often ordered by pT to fix on the 1D grid. Only correlations with neighbouring points with similar pT are learned by the network with a convolution operation. The Long Short-Term Memory (LSTM) type recurrent neural network (RNN) provides the flexibility to feed in a variant-length sequence and has a \"memory\" mechanism to cooperate the information it learns from an early node to the latest node. The concern is that such ordering of the sequence is somewhat artificial, and not an underlying property that an NN must learn to accomplish the classification task. As a comparison, in the task of the natural language processing where LSTM has a huge advantage, the order of words are important characteristic of a language itself (reflects the \"grammar\" in some circumstances) and is a feature the NN must learn to master the language. The imaged-based data explored by a 2D CNN stems from the image recognition task. A jet image with proper standardization is usually performed before feeding into the network. In this sense, it lacks local features which the 2D local patch is better at capturing, e.g. the ear of the cat that a local patch can capture by scanning over the entire image. The jet image is appearing to hold the features globally (e.g. two-prong structure for W-tagging). The sparsity of data is another concern in that it introduces redundant information to present a jet on the regular grid, making the network hard to capture the key properties.

"},{"location":"inference/particlenet.html#3-applications-and-other-related-work","title":"3. Applications and other related work","text":"

Here we briefly summarize the applications and ongoing works on ParticleNet. Public CMS results include

  • large-R jet with R=0.8 tagging (for W/Z/H/t) using ParticleNet [CMS-DP-2020/002]
  • regression on the large-R jet mass based on the ParticleNet model [CMS-DP-2021/017]

ParticleNet architecture is also applied on small radius R=0.4 jets for the b/c-tagging and quark/gluon classification (see this talk (CMS internal)). A recent ongoing work applies the ParticleNet architecture in heavy flavour tagging at HLT (see this talk (CMS internal)). The ParticleNet model is recently updated to ParticleNeXt and see further improvement (see the ML4Jets 2021 talk).

Recent works in the joint field of HEP and ML also shed light on exploiting the point cloud data structure and GNN-based architectures. We see very active progress in recent years. Here list some useful materials for the reader's reference.

  • Some pheno-based work are summarized in the HEP \u00d7 ML living review, especially in the \"graph\" and \"sets\" categories.
  • An overview of GNN applications to CMS, see CMS ML forum (CMS internal). Also see more recent GNN application progress in ML forums: Oct 20, Nov 3.
  • At the time of writing, various novel GNN-based models are explored and introduced in the recent ML4Jets2021 meeting.
"},{"location":"inference/particlenet.html#introduction-to-weaver-and-model-implementations","title":"Introduction to Weaver and model implementations","text":"

Weaver is a machine learning R&D framework for high energy physics (HEP) applications. It trains the neural net with PyTorch and is capable of exporting the model to the ONNX format for fast inference. A detailed guide is presented on Weaver README page.

Now we walk through three solid examples to get you familiar with Weaver. We use the benchmark of the top tagging task [arXiv:1707.08966] in the following example. Some useful information can be found in the \"top tagging\" section in the IML public datasets webpage (the gDoc).

Our goal is to do some warm-up with Weaver, and more importantly, to explore from a technical side the neural net architectures: a simple multi-layer perceptron (MLP) model, a more complicated \"DeepAK8 tagger\" model based on 1D CNN with ResNet, and the \"ParticleNet model,\" which is based on DGCNN. We will dig deeper into their implementations in Weaver and try to illustrate as many details as possible. Finally, we compare their performance and see if we can reproduce the benchmark record with the model. Please clone the repo weaver-benchmark and we'll get started. The Weaver repo will be cloned as a submodule.

git clone --recursive https://github.com/colizz/weaver-benchmark.git\n\n# Create a soft link inside weaver so that it can find data/model cards\nln -s ../top_tagging weaver-benchmark/weaver/top_tagging\n

"},{"location":"inference/particlenet.html#1-build-models-in-weaver","title":"1. Build models in Weaver","text":"

When implementing a new training in Weaver, two key elements are crucial: the model and the data configuration file. The model defines the network architecture we are using, and the data configuration includes which variables to use for training, which pre-selection to apply, how to assign truth labels, etc.

Technically, The model configuration file includes a get_model function that returns a torch.nn.Module type model and a dictionary of model info used to export an ONNX-format model. The data configuration is a YAML file describing how to process the input data. Please see the Weaver README for details.

Before moving on, we need a preprocessing of the benchmark datasets. The original sample is an H5 file including branches like energy E_i and 3-momenta PX_i, PY_i, PZ_i for each jet constituent i (i=0, ..., 199) inside a jet. All branches are in the 1D flat structure. We reconstruct the data in a way that the jet features are 2D vectors (e.g., in the vector<float> format): Part_E, Part_PX, Part_PY, Part_PZ, with variable-length that corresponds to the number of constituents. Note that this is a commonly used data structure, similar to the NanoAOD format in CMS.

The datasets can be found at CERN EOS space /eos/user/c/coli/public/weaver-benchmark/top_tagging/samples. The input files used in this page are in fact the ROOT files produced by the preprocessing step, stored under the prep/ subdirectory. It includes three sets of data for training, validation, and test.

Note

To preprocess the input files from the original datasets manually, direct to the weaver-benchmark base directory and run

python utils/convert_top_datasets.py -i <your-sample-dir>\n
This will convert the .h5 file to ROOT ntuples and create some new variables for each jet, including the relative \u03b7 and \u03c6 value w.r.t. main axis of the jet of each jet constituent. The converted files are stored in prep/ subfolder of the original directory.

Then, we show three NN model configurations below and provide detailed explanations of the code. We make meticulous efforts on the illustration of the model architecture, especially in the ParticleNet case.

A simple MLPDeepAK8 (1D CNN)ParticleNet (DGCNN)

The full architecture of the proof-of-concept multi-layer perceptron model.

A simple multi-layer perceptron model is first provided here as proof of the concept. All layers are based on the linear transformation of the 1D vectors. The model configuration card is shown in top_tagging/networks/mlp_pf.py. First, we implement an MLP network in the nn.Module class.

MLP implementation

Also, see top_tagging/networks/mlp_pf.py. We elaborate here on several aspects.

  • A sequence of linear layers and ReLU activation functions is defined in nn.Sequential(nn.Linear(channels[i], channels[i + 1]), nn.ReLU()). By combining multiple of them, we construct a simple multi-layer perceptron.

  • The input data x takes the 3D format, in the dimension (N, C, P), which is decided by our data structure and the data configuration card. Here, N is the mini-batch size, C is the feature size, and P is the size of constituents per jet. To feed into our MLP, we flatten the last two dimensions by x = x.flatten(start_dim=1) to form the vector of dimension (N, L).

class MultiLayerPerceptron(nn.Module):\nr\"\"\"Parameters\n    ----------\n    input_dims : int\n        Input feature dimensions.\n    num_classes : int\n        Number of output classes.\n    layer_params : list\n        List of the feature size for each layer.\n    \"\"\"\n\n    def __init__(self, input_dims, num_classes,\n                layer_params=(1024, 256, 256),\n                **kwargs):\n\n        super(MultiLayerPerceptron, self).__init__(**kwargs)\n        channels = [input_dims] + list(layer_params) + [num_classes]\n        layers = []\n        for i in range(len(channels) - 1):\n            layers.append(nn.Sequential(nn.Linear(channels[i], channels[i + 1]),\n                                        nn.ReLU()))\n        self.mlp = nn.Sequential(*layers)\n\n    def forward(self, x):\n        # x: the feature vector initally read from the data structure, in dimension (N, C, P)\n        x = x.flatten(start_dim=1) # (N, L), where L = C * P\n        return self.mlp(x)\n

Then, we write the get_model and get_loss functions which will be sent into Weaver's training code.

get_model and get_loss function

Also see top_tagging/networks/mlp_pf.py. We elaborate here on several aspects.

  • Inside get_model, the model is essentially the MLP class we define, and the model_info takes the default definition, including the input/output shape, the dimensions of the dynamic axes for the input/output data shape that will guide the ONNX model exportation.
  • The get_loss function is not changed as in the classification task we always use the cross-entropy loss function.
def get_model(data_config, **kwargs):\n    layer_params = (1024, 256, 256)\n    _, pf_length, pf_features_dims = data_config.input_shapes['pf_features']\n    input_dims = pf_length * pf_features_dims\n    num_classes = len(data_config.label_value)\n    model = MultiLayerPerceptron(input_dims, num_classes, layer_params=layer_params)\n\n    model_info = {\n        'input_names':list(data_config.input_names),\n        'input_shapes':{k:((1,) + s[1:]) for k, s in data_config.input_shapes.items()},\n        'output_names':['softmax'],\n        'dynamic_axes':{**{k:{0:'N', 2:'n_' + k.split('_')[0]} for k in data_config.input_names}, **{'softmax':{0:'N'}}},\n        }\n\n    print(model, model_info)\n    return model, model_info\n\n\ndef get_loss(data_config, **kwargs):\n    return torch.nn.CrossEntropyLoss()\n

The output below shows the full structure of the MLP network printed by PyTorch. You will see it in the Weaver output during the training.

The full-scale structure of the MLP network
MultiLayerPerceptron(\n  |0.739 M, 100.000% Params, 0.001 GMac, 100.000% MACs|\n  (mlp): Sequential(\n    |0.739 M, 100.000% Params, 0.001 GMac, 100.000% MACs|\n    (0): Sequential(\n      |0.411 M, 55.540% Params, 0.0 GMac, 55.563% MACs|\n      (0): Linear(in_features=400, out_features=1024, bias=True, |0.411 M, 55.540% Params, 0.0 GMac, 55.425% MACs|)\n      (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.138% MACs|)\n    )\n    (1): Sequential(\n      |0.262 M, 35.492% Params, 0.0 GMac, 35.452% MACs|\n      (0): Linear(in_features=1024, out_features=256, bias=True, |0.262 M, 35.492% Params, 0.0 GMac, 35.418% MACs|)\n      (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.035% MACs|)\n    )\n    (2): Sequential(\n      |0.066 M, 8.899% Params, 0.0 GMac, 8.915% MACs|\n      (0): Linear(in_features=256, out_features=256, bias=True, |0.066 M, 8.899% Params, 0.0 GMac, 8.880% MACs|)\n      (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.035% MACs|)\n    )\n    (3): Sequential(\n      |0.001 M, 0.070% Params, 0.0 GMac, 0.070% MACs|\n      (0): Linear(in_features=256, out_features=2, bias=True, |0.001 M, 0.070% Params, 0.0 GMac, 0.069% MACs|)\n      (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.000% MACs|)\n    )\n  )\n)\n

The data card is shown in top_tagging/data/pf_features.yaml. It defines one input group, pf_features, which takes four variables Etarel, Phirel, E_log, P_log. This is based on our data structure, where these variables are 2D vectors with variable lengths. The length is chosen as 100 in a way that the last dimension (the jet constituent dimension) is always truncated or padded to have length 100.

MLP data config top_tagging/data/pf_features.yaml

Also see top_tagging/data/pf_features.yaml. See a tour guide to the data configuration card in Weaver README.

selection:\n### use `&`, `|`, `~` for logical operations on numpy arrays\n### can use functions from `math`, `np` (numpy), and `awkward` in the expression\n\nnew_variables:\n### [format] name: formula\n### can use functions from `math`, `np` (numpy), and `awkward` in the expression\nis_bkg: np.logical_not(is_signal_new)\n\npreprocess:\n### method: [manual, auto] - whether to use manually specified parameters for variable standardization\nmethod: manual\n### data_fraction: fraction of events to use when calculating the mean/scale for the standardization\ndata_fraction:\n\ninputs:\npf_features:\nlength: 100\nvars:\n### [format 1]: var_name (no transformation)\n### [format 2]: [var_name,\n###              subtract_by(optional, default=None, no transf. if preprocess.method=manual, auto transf. if preprocess.method=auto),\n###              multiply_by(optional, default=1),\n###              clip_min(optional, default=-5),\n###              clip_max(optional, default=5),\n###              pad_value(optional, default=0)]\n- Part_Etarel\n- Part_Phirel\n- [Part_E_log, 2, 1]\n- [Part_P_log, 2, 1]\n\nlabels:\n### type can be `simple`, `custom`\n### [option 1] use `simple` for binary/multi-class classification, then `value` is a list of 0-1 labels\ntype: simple\nvalue: [\nis_signal_new, is_bkg\n]\n### [option 2] otherwise use `custom` to define the label, then `value` is a map\n# type: custom\n# value:\n# target_mass: np.where(fj_isQCD, fj_genjet_sdmass, fj_gen_mass)\n\nobservers:\n- origIdx\n- idx\n- Part_E_tot\n- Part_PX_tot\n- Part_PY_tot\n- Part_PZ_tot\n- Part_P_tot\n- Part_Eta_tot\n- Part_Phi_tot\n\n# weights:\n### [option 1] use precomputed weights stored in the input files\n# use_precomputed_weights: true\n# weight_branches: [weight, class_weight]\n### [option 2] compute weights on-the-fly using reweighting histograms\n

In the following two models (i.e., the DeepAK8 and the ParticleNet model) you will see that the data card is very similar. The change will only be the way we present the input group(s).

The full architecture of the DeepAK8 model, which is based on 1D CNN with ResNet architecture.

Note

The DeepAK8 tagger is a widely used highly-boosted jet tagger in the CMS community. The design of the model can be found in the CMS paper [arXiv:2004.08262]. The original model is trained on MXNet and its configuration can be found here.

We now migrate the model architecture to Weaver and train it on PyTorch. Also, we narrow the multi-class output score to the binary output to adapt our binary classification task (top vs. QCD jet).

The model card is given in top_tagging/networks/deepak8_pf.py. The DeepAK8 model is inspired by the ResNet architecture. The key ingredient is the ResNet unit constructed by multiple CNN layers with a shortcut connection. First, we define the ResNet unit in the model card.

ResNet unit implementation

See top_tagging/networks/deepak8_pf.py. We elaborate here on several aspects.

  • A ResNet unit is made of two 1D CNNs with batch normalization and ReLU activation function.
  • The shortcut is introduced here by directly adding the input data to the processed data after passing the CNN layers. The shortcut connection help to ease the training for the \"deeper\" model [arXiv:1512.03385]. Note that a trivial linear transformation is applied (self.conv_sc) if the feature dimension of the input and output data does not match.
class ResNetUnit(nn.Module):\nr\"\"\"Parameters\n    ----------\n    in_channels : int\n        Number of channels in the input vectors.\n    out_channels : int\n        Number of channels in the output vectors.\n    strides: tuple\n        Strides of the two convolutional layers, in the form of (stride0, stride1)\n    \"\"\"\n\n    def __init__(self, in_channels, out_channels, strides=(1,1), **kwargs):\n\n        super(ResNetUnit, self).__init__(**kwargs)\n        self.conv1 = nn.Conv1d(in_channels, out_channels, kernel_size=3, stride=strides[0], padding=1)\n        self.bn1 = nn.BatchNorm1d(out_channels)\n        self.conv2 = nn.Conv1d(out_channels, out_channels, kernel_size=3, stride=strides[1], padding=1)\n        self.bn2 = nn.BatchNorm1d(out_channels)\n        self.relu = nn.ReLU()\n        self.dim_match = True\n        if not in_channels == out_channels or not strides == (1,1): # dimensions not match\n            self.dim_match = False\n            self.conv_sc = nn.Conv1d(in_channels, out_channels, kernel_size=1, stride=strides[0]*strides[1], bias=False)\n\n    def forward(self, x):\n        identity = x\n        x = self.conv1(x)\n        x = self.bn1(x)\n        x = self.relu(x)\n        x = self.conv2(x)\n        x = self.bn2(x)\n        x = self.relu(x)\n        # print('resnet unit', identity.shape, x.shape, self.dim_match)\n        if self.dim_match:\n            return identity + x\n        else:\n            return self.conv_sc(identity) + x\n

With the ResNet unit, we construct the DeepAK8 model. The model hyperparameters are chosen as follows.

conv_params = [(32,), (64, 64), (64, 64), (128, 128)]\nfc_params = [(512, 0.2)]\n

DeepAK8 model implementation

See top_tagging/networks/deepak8_pf.py. Note that the main architecture is a PyTorch re-implementation of the code here based on the MXNet.

class ResNet(nn.Module):\nr\"\"\"Parameters\n    ----------\n    features_dims : int\n        Input feature dimensions.\n    num_classes : int\n        Number of output classes.\n    conv_params : list\n        List of the convolution layer parameters.\n        The first element is a tuple of size 1, defining the transformed feature size for the initial feature convolution layer.\n        The following are tuples of feature size for multiple stages of the ResNet units. Each number defines an individual ResNet unit.\n    fc_params: list\n        List of fully connected layer parameters after all EdgeConv blocks, each element in the format of\n        (n_feat, drop_rate)\n    \"\"\"\n\n    def __init__(self, features_dims, num_classes,\n                conv_params=[(32,), (64, 64), (64, 64), (128, 128)],\n                fc_params=[(512, 0.2)],\n                **kwargs):\n\n        super(ResNet, self).__init__(**kwargs)\n        self.conv_params = conv_params\n        self.num_stages = len(conv_params) - 1\n        self.fts_conv = nn.Sequential(nn.Conv1d(in_channels=features_dims, out_channels=conv_params[0][0], kernel_size=3, stride=1, padding=1),\n                                    nn.BatchNorm1d(conv_params[0][0]),\n                                    nn.ReLU())\n\n        # define ResNet units for each stage. Each unit is composed of a sequence of ResNetUnit block\n        self.resnet_units = nn.ModuleDict()\n        for i in range(self.num_stages):\n            # stack units[i] layers in this stage\n            unit_layers = []\n            for j in range(len(conv_params[i + 1])):\n                in_channels, out_channels = (conv_params[i][-1], conv_params[i + 1][0]) if j == 0 \\\n                                            else (conv_params[i + 1][j - 1], conv_params[i + 1][j])\n                strides = (2, 1) if (j == 0 and i > 0) else (1, 1)\n                unit_layers.append(ResNetUnit(in_channels, out_channels, strides))\n\n            self.resnet_units.add_module('resnet_unit_%d' % i, nn.Sequential(*unit_layers))\n\n        # define fully connected layers\n        fcs = []\n        for idx, layer_param in enumerate(fc_params):\n            channels, drop_rate = layer_param\n            in_chn = conv_params[-1][-1] if idx == 0 else fc_params[idx - 1][0]\n            fcs.append(nn.Sequential(nn.Linear(in_chn, channels), nn.ReLU(), nn.Dropout(drop_rate)))\n        fcs.append(nn.Linear(fc_params[-1][0], num_classes))\n        self.fc = nn.Sequential(*fcs)\n\n    def forward(self, x):\n        # x: the feature vector, (N, C, P)\n        x = self.fts_conv(x)\n        for i in range(self.num_stages):\n            x = self.resnet_units['resnet_unit_%d' % i](x) # (N, C', P'), P'<P due to kernal_size>1 or stride>1\n\n        # global average pooling\n        x = x.sum(dim=-1) / x.shape[-1] # (N, C')\n        # fully connected\n        x = self.fc(x) # (N, out_chn)\n        return x\n\n\ndef get_model(data_config, **kwargs):\n    conv_params = [(32,), (64, 64), (64, 64), (128, 128)]\n    fc_params = [(512, 0.2)]\n\n    pf_features_dims = len(data_config.input_dicts['pf_features'])\n    num_classes = len(data_config.label_value)\n    model = ResNet(pf_features_dims, num_classes,\n                conv_params=conv_params,\n                fc_params=fc_params)\n\n    model_info = {\n        'input_names':list(data_config.input_names),\n        'input_shapes':{k:((1,) + s[1:]) for k, s in data_config.input_shapes.items()},\n        'output_names':['softmax'],\n        'dynamic_axes':{**{k:{0:'N', 2:'n_' + k.split('_')[0]} for k in data_config.input_names}, **{'softmax':{0:'N'}}},\n        }\n\n    print(model, model_info)\n    print(data_config.input_shapes)\n    return model, model_info\n\n\ndef get_loss(data_config, **kwargs):\n    return torch.nn.CrossEntropyLoss()\n

The output below shows the full structure of the DeepAK8 model based on 1D CNN with ResNet. It is printed by PyTorch and you will see it in the Weaver output during training.

The full-scale structure of the DeepAK8 architecture
ResNet(\n  |0.349 M, 100.000% Params, 0.012 GMac, 100.000% MACs|\n  (fts_conv): Sequential(\n    |0.0 M, 0.137% Params, 0.0 GMac, 0.427% MACs|\n    (0): Conv1d(4, 32, kernel_size=(3,), stride=(1,), padding=(1,), |0.0 M, 0.119% Params, 0.0 GMac, 0.347% MACs|)\n    (1): BatchNorm1d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.018% Params, 0.0 GMac, 0.053% MACs|)\n    (2): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.027% MACs|)\n  )\n  (resnet_units): ModuleDict(\n    |0.282 M, 80.652% Params, 0.012 GMac, 99.010% MACs|\n    (resnet_unit_0): Sequential(\n      |0.046 M, 13.124% Params, 0.005 GMac, 38.409% MACs|\n      (0): ResNetUnit(\n        |0.021 M, 5.976% Params, 0.002 GMac, 17.497% MACs|\n        (conv1): Conv1d(32, 64, kernel_size=(3,), stride=(1,), padding=(1,), |0.006 M, 1.778% Params, 0.001 GMac, 5.175% MACs|)\n        (bn1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.107% MACs|)\n        (conv2): Conv1d(64, 64, kernel_size=(3,), stride=(1,), padding=(1,), |0.012 M, 3.538% Params, 0.001 GMac, 10.296% MACs|)\n        (bn2): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.107% MACs|)\n        (relu): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.107% MACs|)\n        (conv_sc): Conv1d(32, 64, kernel_size=(1,), stride=(1,), bias=False, |0.002 M, 0.587% Params, 0.0 GMac, 1.707% MACs|)\n      )\n      (1): ResNetUnit(\n        |0.025 M, 7.149% Params, 0.003 GMac, 20.912% MACs|\n        (conv1): Conv1d(64, 64, kernel_size=(3,), stride=(1,), padding=(1,), |0.012 M, 3.538% Params, 0.001 GMac, 10.296% MACs|)\n        (bn1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.107% MACs|)\n        (conv2): Conv1d(64, 64, kernel_size=(3,), stride=(1,), padding=(1,), |0.012 M, 3.538% Params, 0.001 GMac, 10.296% MACs|)\n        (bn2): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.107% MACs|)\n        (relu): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.107% MACs|)\n      )\n    )\n    (resnet_unit_1): Sequential(\n      |0.054 M, 15.471% Params, 0.003 GMac, 22.619% MACs|\n      (0): ResNetUnit(\n        |0.029 M, 8.322% Params, 0.001 GMac, 12.163% MACs|\n        (conv1): Conv1d(64, 64, kernel_size=(3,), stride=(2,), padding=(1,), |0.012 M, 3.538% Params, 0.001 GMac, 5.148% MACs|)\n        (bn1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.053% MACs|)\n        (conv2): Conv1d(64, 64, kernel_size=(3,), stride=(1,), padding=(1,), |0.012 M, 3.538% Params, 0.001 GMac, 5.148% MACs|)\n        (bn2): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.053% MACs|)\n        (relu): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.053% MACs|)\n        (conv_sc): Conv1d(64, 64, kernel_size=(1,), stride=(2,), bias=False, |0.004 M, 1.173% Params, 0.0 GMac, 1.707% MACs|)\n      )\n      (1): ResNetUnit(\n        |0.025 M, 7.149% Params, 0.001 GMac, 10.456% MACs|\n        (conv1): Conv1d(64, 64, kernel_size=(3,), stride=(1,), padding=(1,), |0.012 M, 3.538% Params, 0.001 GMac, 5.148% MACs|)\n        (bn1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.053% MACs|)\n        (conv2): Conv1d(64, 64, kernel_size=(3,), stride=(1,), padding=(1,), |0.012 M, 3.538% Params, 0.001 GMac, 5.148% MACs|)\n        (bn2): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.037% Params, 0.0 GMac, 0.053% MACs|)\n        (relu): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.053% MACs|)\n      )\n    )\n    (resnet_unit_2): Sequential(\n      |0.182 M, 52.057% Params, 0.005 GMac, 37.982% MACs|\n      (0): ResNetUnit(\n        |0.083 M, 23.682% Params, 0.002 GMac, 17.284% MACs|\n        (conv1): Conv1d(64, 128, kernel_size=(3,), stride=(2,), padding=(1,), |0.025 M, 7.075% Params, 0.001 GMac, 5.148% MACs|)\n        (bn1): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.073% Params, 0.0 GMac, 0.053% MACs|)\n        (conv2): Conv1d(128, 128, kernel_size=(3,), stride=(1,), padding=(1,), |0.049 M, 14.114% Params, 0.001 GMac, 10.269% MACs|)\n        (bn2): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.073% Params, 0.0 GMac, 0.053% MACs|)\n        (relu): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.053% MACs|)\n        (conv_sc): Conv1d(64, 128, kernel_size=(1,), stride=(2,), bias=False, |0.008 M, 2.346% Params, 0.0 GMac, 1.707% MACs|)\n      )\n      (1): ResNetUnit(\n        |0.099 M, 28.375% Params, 0.002 GMac, 20.698% MACs|\n        (conv1): Conv1d(128, 128, kernel_size=(3,), stride=(1,), padding=(1,), |0.049 M, 14.114% Params, 0.001 GMac, 10.269% MACs|)\n        (bn1): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.073% Params, 0.0 GMac, 0.053% MACs|)\n        (conv2): Conv1d(128, 128, kernel_size=(3,), stride=(1,), padding=(1,), |0.049 M, 14.114% Params, 0.001 GMac, 10.269% MACs|)\n        (bn2): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.073% Params, 0.0 GMac, 0.053% MACs|)\n        (relu): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.053% MACs|)\n      )\n    )\n  )\n  (fc): Sequential(\n    |0.067 M, 19.210% Params, 0.0 GMac, 0.563% MACs|\n    (0): Sequential(\n      |0.066 M, 18.917% Params, 0.0 GMac, 0.555% MACs|\n      (0): Linear(in_features=128, out_features=512, bias=True, |0.066 M, 18.917% Params, 0.0 GMac, 0.551% MACs|)\n      (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.004% MACs|)\n      (2): Dropout(p=0.2, inplace=False, |0.0 M, 0.000% Params, 0.0 GMac, 0.000% MACs|)\n    )\n    (1): Linear(in_features=512, out_features=2, bias=True, |0.001 M, 0.294% Params, 0.0 GMac, 0.009% MACs|)\n  )\n)\n

The data card is the same as the MLP case, shown in top_tagging/data/pf_features.yaml.

The full architecture of the ParticleNet model, which is based on DGCNN and EdgeConv.

Note

The ParticleNet model applied to the CMS analysis is provided in weaver/networks/particle_net_pf_sv.py, and the data card in weaver/data/ak15_points_pf_sv.yaml. Here we use a similar configuration card to deal with the benchmark task.

We will elaborate on the ParticleNet model and focus more on the technical side in this section. The model is defined in top_tagging/networks/particlenet_pf.py, but it imports some constructor, the EdgeConv block, in weaver/utils/nn/model/ParticleNet.py. The EdgeConv is illustrated in the cartoon.

Illustration of the EdgeConv block

From an EdgeConv block's point of view, it requires two classes of features as input: the \"coordinates\" and the \"features\". These features are the per point properties, in the 2D shape with dimensions (C, P), where C is the size of the features (the feature size of \"coordinates\" and the \"features\" can be different, marked as C_pts, C_fts in the following code), and P is the number of points. The block outputs the new features that the model learns, also in the 2D shape with dimensions (C_fts_out, P).

What happens inside the EdgeConv block? And how is the output feature vector transferred from the input features using the topology of the point cloud? The answer is encoded in the edge convolution (EdgeConv).

The edge convolution is an analogue convolution method defined on a point cloud, whose shape is given by the \"coordinates\" of points. Specifically, the input \"coordinates\" provide a view of spatial relations of the points in the Euclidean space. It determines the k-nearest neighbouring points for each point that will guide the update of the feature vector of a point. For each point, the updated feature vector is based on the current state of the point and its k neighbours. Guided by this spirit, all features of the point cloud forms a 3D vector with dimensions (C, P, K), where C is the per-point feature size (e.g., \u03b7, \u03c6, pT\uff0c...), P is the number of points, and K the k-NN number. The structured vector is linearly transformed by acting 2D CNN on the feature dimension C. This helps to aggregate the feature information and exploit the correlations of each point with its adjacent points. A shortcut connection is also introduced inspired by the ResNet.

Note

The feature dimension C after exploring the k neighbours of each point actually doubles the value of the initial feature dimension. Here, a new set of features is constructed by subtracting the feature a point carries to the features its k neighbours carry (namely xi \u2013 xi_j for point i, and j=1,...,k). This way, the correlation of each point with its neighbours are well captured.

Below shows how the EdgeConv structure is implemented in the code.

EdgeConv block implementation

See weaver/utils/nn/model/ParticleNet.py, or the following code block annotated with more comments. We elaborate here on several aspects.

  • The EdgeConvBlock takes the feature dimension in_feat, out_feats which are C_fts, C_fts_out we introduced above.
  • The input data vectors to forward() are \"coordinates\" and \"features\" vector, in the dimension of (N, C_pts(C_fts), P) as introduced above. The first dimension is the mini-batch size.
  • self.get_graph_feature() helps to aggregate k-nearest neighbours for each point. The resulting vector is in the dimension of (N, C_fts(0), P, K) as we discussed above, K being the k-NN number. Note that the C_fts(0) doubles the value of the original input feature dimension C_fts as mentioned above.
  • After convolutions, the per-point features are merged by taking the mean of all k-nearest neighbouring vectors:
    fts = x.mean(dim=-1)  # (N, C, P)\n
class EdgeConvBlock(nn.Module):\nr\"\"\"EdgeConv layer.\n    Introduced in \"`Dynamic Graph CNN for Learning on Point Clouds\n    <https://arxiv.org/pdf/1801.07829>`__\".  Can be described as follows:\n    .. math::\n    x_i^{(l+1)} = \\max_{j \\in \\mathcal{N}(i)} \\mathrm{ReLU}(\n    \\Theta \\cdot (x_j^{(l)} - x_i^{(l)}) + \\Phi \\cdot x_i^{(l)})\n    where :math:`\\mathcal{N}(i)` is the neighbor of :math:`i`.\n    Parameters\n    ----------\n    in_feat : int\n        Input feature size.\n    out_feat : int\n        Output feature size.\n    batch_norm : bool\n        Whether to include batch normalization on messages.\n    \"\"\"\n\n    def __init__(self, k, in_feat, out_feats, batch_norm=True, activation=True, cpu_mode=False):\n        super(EdgeConvBlock, self).__init__()\n        self.k = k\n        self.batch_norm = batch_norm\n        self.activation = activation\n        self.num_layers = len(out_feats)\n        self.get_graph_feature = get_graph_feature_v2 if cpu_mode else get_graph_feature_v1\n\n        self.convs = nn.ModuleList()\n        for i in range(self.num_layers):\n            self.convs.append(nn.Conv2d(2 * in_feat if i == 0 else out_feats[i - 1], out_feats[i], kernel_size=1, bias=False if self.batch_norm else True))\n\n        if batch_norm:\n            self.bns = nn.ModuleList()\n            for i in range(self.num_layers):\n                self.bns.append(nn.BatchNorm2d(out_feats[i]))\n\n        if activation:\n            self.acts = nn.ModuleList()\n            for i in range(self.num_layers):\n                self.acts.append(nn.ReLU())\n\n        if in_feat == out_feats[-1]:\n            self.sc = None\n        else:\n            self.sc = nn.Conv1d(in_feat, out_feats[-1], kernel_size=1, bias=False)\n            self.sc_bn = nn.BatchNorm1d(out_feats[-1])\n\n        if activation:\n            self.sc_act = nn.ReLU()\n\n    def forward(self, points, features):\n        # points:   (N, C_pts, P)\n        # features: (N, C_fts, P)\n        # N: batch size, C: feature size per point, P: number of points\n\n        topk_indices = knn(points, self.k) # (N, P, K)\n        x = self.get_graph_feature(features, self.k, topk_indices) # (N, C_fts(0), P, K)\n\n        for conv, bn, act in zip(self.convs, self.bns, self.acts):\n            x = conv(x)  # (N, C', P, K)\n            if bn:\n                x = bn(x)\n            if act:\n                x = act(x)\n\n        fts = x.mean(dim=-1)  # (N, C, P)\n\n        # shortcut\n        if self.sc:\n            sc = self.sc(features)  # (N, C_out, P)\n            sc = self.sc_bn(sc)\n        else:\n            sc = features\n\n        return self.sc_act(sc + fts)  # (N, C_out, P)\n

With the EdgeConv architecture as the building block, the ParticleNet model is constructed as follow.

The ParticleNet model stacks three EdgeConv blocks to construct higher-level features and passing them through the pipeline. The points (i.e., in our case, the particle candidates inside a jet) are not changing, but the per-point \"coordinates\" and \"features\" vectors changes, in both values and dimensions.

For the first EdgeConv block, the \"coordinates\" only include the relative \u03b7 and \u03c6 value of each particle. The \"features\" is a vector with a standard length of 32, which is linearly transformed from the initial feature vectors including the components of relative \u03b7, \u03c6, the log of pT, etc. The first EdgeConv block outputs a per-point feature vector of length 64, which is taken as both the \"coordinates\" and \"features\" to the next EdgeConv block. That is to say, the next k-NN is applied on the 64D high-dimensional spatial space to capture the new relations of points learned by the model. This is visualized by the input/output arrows showing the data flow of the model. We see that this architecture illustrates the stackability of the EdgeConv block, and is the core to the Dynamic Graph CNN (DGCNN), as the model can dynamically change the correlations of each point based on learnable features.

A fusion technique is also used by concatenating the three EdgeConv output vectors together (adding the dimensions), instead of using the last EdgeConv output, to form an output vector. This is also one form of shortcut implementations that helps to ease the training for a complex and deep convolutional network model.

The concatenated vectors per point are then averaged over points to produce a single 1D vector of the whole point cloud. The vector passes through one fully connected layer, with a dropout rate of p=0.1 to prevent overfitting. Then, in our example, the full network outputs two scores after a softmax, representing the one-hot encoding of the top vs. QCD class.

The ParticleNet implementation is shown below.

ParticleNet model implementation

See weaver/utils/nn/model/ParticleNet.py, or the following code block annotated with more comments. We elaborate here on several mean points.

  • The stack of multiple EdgeConv blocks are implemented in
    for idx, conv in enumerate(self.edge_convs):\n    pts = (points if idx == 0 else fts) + coord_shift\n    fts = conv(pts, fts) * mask\n
  • The multiple EdgeConv layer parameters are given by conv_params, which takes a list of tuples, each tuple in the format of (K, (C1, C2, C3)). K for the k-NN number, C1,2,3 for convolution feature sizes of three layers in an EdgeConv block.
  • The fully connected layer parameters are given by fc_params, which takes a list of tuples, each tuple in the format of (n_feat, drop_rate).
class ParticleNet(nn.Module):\nr\"\"\"Parameters\n    ----------\n    input_dims : int\n        Input feature dimensions (C_fts).\n    num_classes : int\n        Number of output classes.\n    conv_params : list\n        List of convolution parameters of EdgeConv blocks, each element in the format of (K, (C1, C2, C3)).\n        K for the kNN number, C1,2,3 for convolution feature sizes of three layers in an EdgeConv block.\n    fc_params: list\n        List of fully connected layer parameters after all EdgeConv blocks, each element in the format of\n        (n_feat, drop_rate)\n    use_fusion: bool\n        If true, concatenates all output features from each EdgeConv before the fully connected layer.\n    use_fts_bn: bool\n        If true, applies a batch norm before feeding to the EdgeConv block.\n    use_counts: bool\n        If true, uses the real count of points instead of the padded size (the max point size).\n    for_inference: bool\n        Whether this is an inference routine. If true, applies a softmax to the output.\n    for_segmentation: bool\n        Whether the model is set up for the point cloud segmentation (instead of classification) task. If true,\n        does not merge the features after the last EdgeConv, and apply Conv1D instead of the linear layer.\n        The output is hence each output_features per point, instead of output_features.\n    \"\"\"\n\n\n    def __init__(self,\n                input_dims,\n                num_classes,\n                conv_params=[(7, (32, 32, 32)), (7, (64, 64, 64))],\n                fc_params=[(128, 0.1)],\n                use_fusion=True,\n                use_fts_bn=True,\n                use_counts=True,\n                for_inference=False,\n                for_segmentation=False,\n                **kwargs):\n        super(ParticleNet, self).__init__(**kwargs)\n\n        self.use_fts_bn = use_fts_bn\n        if self.use_fts_bn:\n            self.bn_fts = nn.BatchNorm1d(input_dims)\n\n        self.use_counts = use_counts\n\n        self.edge_convs = nn.ModuleList()\n        for idx, layer_param in enumerate(conv_params):\n            k, channels = layer_param\n            in_feat = input_dims if idx == 0 else conv_params[idx - 1][1][-1]\n            self.edge_convs.append(EdgeConvBlock(k=k, in_feat=in_feat, out_feats=channels, cpu_mode=for_inference))\n\n        self.use_fusion = use_fusion\n        if self.use_fusion:\n            in_chn = sum(x[-1] for _, x in conv_params)\n            out_chn = np.clip((in_chn // 128) * 128, 128, 1024)\n            self.fusion_block = nn.Sequential(nn.Conv1d(in_chn, out_chn, kernel_size=1, bias=False), nn.BatchNorm1d(out_chn), nn.ReLU())\n\n        self.for_segmentation = for_segmentation\n\n        fcs = []\n        for idx, layer_param in enumerate(fc_params):\n            channels, drop_rate = layer_param\n            if idx == 0:\n                in_chn = out_chn if self.use_fusion else conv_params[-1][1][-1]\n            else:\n                in_chn = fc_params[idx - 1][0]\n            if self.for_segmentation:\n                fcs.append(nn.Sequential(nn.Conv1d(in_chn, channels, kernel_size=1, bias=False),\n                                        nn.BatchNorm1d(channels), nn.ReLU(), nn.Dropout(drop_rate)))\n            else:\n                fcs.append(nn.Sequential(nn.Linear(in_chn, channels), nn.ReLU(), nn.Dropout(drop_rate)))\n        if self.for_segmentation:\n            fcs.append(nn.Conv1d(fc_params[-1][0], num_classes, kernel_size=1))\n        else:\n            fcs.append(nn.Linear(fc_params[-1][0], num_classes))\n        self.fc = nn.Sequential(*fcs)\n\n        self.for_inference = for_inference\n\n    def forward(self, points, features, mask=None):\n#         print('points:\\n', points)\n#         print('features:\\n', features)\n        if mask is None:\n            mask = (features.abs().sum(dim=1, keepdim=True) != 0)  # (N, 1, P)\n        points *= mask\n        features *= mask\n        coord_shift = (mask == 0) * 1e9\n        if self.use_counts:\n            counts = mask.float().sum(dim=-1)\n            counts = torch.max(counts, torch.ones_like(counts))  # >=1\n\n        if self.use_fts_bn:\n            fts = self.bn_fts(features) * mask\n        else:\n            fts = features\n        outputs = []\n        for idx, conv in enumerate(self.edge_convs):\n            pts = (points if idx == 0 else fts) + coord_shift\n            fts = conv(pts, fts) * mask\n            if self.use_fusion:\n                outputs.append(fts)\n        if self.use_fusion:\n            fts = self.fusion_block(torch.cat(outputs, dim=1)) * mask\n\n#         assert(((fts.abs().sum(dim=1, keepdim=True) != 0).float() - mask.float()).abs().sum().item() == 0)\n\n        if self.for_segmentation:\n            x = fts\n        else:\n            if self.use_counts:\n                x = fts.sum(dim=-1) / counts  # divide by the real counts\n            else:\n                x = fts.mean(dim=-1)\n\n        output = self.fc(x)\n        if self.for_inference:\n            output = torch.softmax(output, dim=1)\n        # print('output:\\n', output)\n        return output\n

Above are the capsulation of all ParticleNet building blocks. Eventually, we have the model defined in the model card top_tagging/networks/particlenet_pf.py, in the ParticleNetTagger1Path class, meaning we only use the ParticleNet pipeline that deals with one set of the point cloud (i.e., the particle candidates).

Info

Two sets of point clouds in the CMS application, namely the particle-flow candidates and secondary vertices, are used. This requires special handling to merge the clouds before feeding them to the first layer of EdgeConv.

ParticleNet model config

Also see top_tagging/networks/particlenet_pf.py.

import torch\nimport torch.nn as nn\nfrom utils.nn.model.ParticleNet import ParticleNet, FeatureConv\n\n\nclass ParticleNetTagger1Path(nn.Module):\n\n    def __init__(self,\n                pf_features_dims,\n                num_classes,\n                conv_params=[(7, (32, 32, 32)), (7, (64, 64, 64))],\n                fc_params=[(128, 0.1)],\n                use_fusion=True,\n                use_fts_bn=True,\n                use_counts=True,\n                pf_input_dropout=None,\n                for_inference=False,\n                **kwargs):\n        super(ParticleNetTagger1Path, self).__init__(**kwargs)\n        self.pf_input_dropout = nn.Dropout(pf_input_dropout) if pf_input_dropout else None\n        self.pf_conv = FeatureConv(pf_features_dims, 32)\n        self.pn = ParticleNet(input_dims=32,\n                            num_classes=num_classes,\n                            conv_params=conv_params,\n                            fc_params=fc_params,\n                            use_fusion=use_fusion,\n                            use_fts_bn=use_fts_bn,\n                            use_counts=use_counts,\n                            for_inference=for_inference)\n\n    def forward(self, pf_points, pf_features, pf_mask):\n        if self.pf_input_dropout:\n            pf_mask = (self.pf_input_dropout(pf_mask) != 0).float()\n            pf_points *= pf_mask\n            pf_features *= pf_mask\n\n        return self.pn(pf_points, self.pf_conv(pf_features * pf_mask) * pf_mask, pf_mask)\n\n\ndef get_model(data_config, **kwargs):\n    conv_params = [\n        (16, (64, 64, 64)),\n        (16, (128, 128, 128)),\n        (16, (256, 256, 256)),\n        ]\n    fc_params = [(256, 0.1)]\n    use_fusion = True\n\n    pf_features_dims = len(data_config.input_dicts['pf_features'])\n    num_classes = len(data_config.label_value)\n    model = ParticleNetTagger1Path(pf_features_dims, num_classes,\n                            conv_params, fc_params,\n                            use_fusion=use_fusion,\n                            use_fts_bn=kwargs.get('use_fts_bn', False),\n                            use_counts=kwargs.get('use_counts', True),\n                            pf_input_dropout=kwargs.get('pf_input_dropout', None),\n                            for_inference=kwargs.get('for_inference', False)\n                            )\n    model_info = {\n        'input_names':list(data_config.input_names),\n        'input_shapes':{k:((1,) + s[1:]) for k, s in data_config.input_shapes.items()},\n        'output_names':['softmax'],\n        'dynamic_axes':{**{k:{0:'N', 2:'n_' + k.split('_')[0]} for k in data_config.input_names}, **{'softmax':{0:'N'}}},\n        }\n\n    print(model, model_info)\n    print(data_config.input_shapes)\n    return model, model_info\n\n\ndef get_loss(data_config, **kwargs):\n    return torch.nn.CrossEntropyLoss()\n

The most important parameters are conv_params and fc_params, which decides the model parameters of EdgeConv blocks and the fully connected layer. See details in the above \"ParticleNet model implementation\" box.

conv_params = [\n    (16, (64, 64, 64)),\n    (16, (128, 128, 128)),\n    (16, (256, 256, 256)),\n    ]\nfc_params = [(256, 0.1)]\n

A full structure printed from PyTorch is shown below. It will appear in the Weaver output during training.

ParticleNet full-scale structure
ParticleNetTagger1Path(\n  |0.577 M, 100.000% Params, 0.441 GMac, 100.000% MACs|\n  (pf_conv): FeatureConv(\n    |0.0 M, 0.035% Params, 0.0 GMac, 0.005% MACs|\n    (conv): Sequential(\n      |0.0 M, 0.035% Params, 0.0 GMac, 0.005% MACs|\n      (0): BatchNorm1d(4, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.001% Params, 0.0 GMac, 0.000% MACs|)\n      (1): Conv1d(4, 32, kernel_size=(1,), stride=(1,), bias=False, |0.0 M, 0.022% Params, 0.0 GMac, 0.003% MACs|)\n      (2): BatchNorm1d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.011% Params, 0.0 GMac, 0.001% MACs|)\n      (3): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.001% MACs|)\n    )\n  )\n  (pn): ParticleNet(\n    |0.577 M, 99.965% Params, 0.441 GMac, 99.995% MACs|\n    (edge_convs): ModuleList(\n      |0.305 M, 52.823% Params, 0.424 GMac, 96.047% MACs|\n      (0): EdgeConvBlock(\n        |0.015 M, 2.575% Params, 0.021 GMac, 4.716% MACs|\n        (convs): ModuleList(\n          |0.012 M, 2.131% Params, 0.02 GMac, 4.456% MACs|\n          (0): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.004 M, 0.710% Params, 0.007 GMac, 1.485% MACs|)\n          (1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.004 M, 0.710% Params, 0.007 GMac, 1.485% MACs|)\n          (2): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.004 M, 0.710% Params, 0.007 GMac, 1.485% MACs|)\n        )\n        (bns): ModuleList(\n          |0.0 M, 0.067% Params, 0.001 GMac, 0.139% MACs|\n          (0): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.022% Params, 0.0 GMac, 0.046% MACs|)\n          (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.022% Params, 0.0 GMac, 0.046% MACs|)\n          (2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.022% Params, 0.0 GMac, 0.046% MACs|)\n        )\n        (acts): ModuleList(\n          |0.0 M, 0.000% Params, 0.0 GMac, 0.070% MACs|\n          (0): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.023% MACs|)\n          (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.023% MACs|)\n          (2): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.023% MACs|)\n        )\n        (sc): Conv1d(32, 64, kernel_size=(1,), stride=(1,), bias=False, |0.002 M, 0.355% Params, 0.0 GMac, 0.046% MACs|)\n        (sc_bn): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.022% Params, 0.0 GMac, 0.003% MACs|)\n        (sc_act): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.001% MACs|)\n      )\n      (1): EdgeConvBlock(\n        |0.058 M, 10.121% Params, 0.081 GMac, 18.437% MACs|\n        (convs): ModuleList(\n          |0.049 M, 8.523% Params, 0.079 GMac, 17.825% MACs|\n          (0): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.016 M, 2.841% Params, 0.026 GMac, 5.942% MACs|)\n          (1): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.016 M, 2.841% Params, 0.026 GMac, 5.942% MACs|)\n          (2): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.016 M, 2.841% Params, 0.026 GMac, 5.942% MACs|)\n        )\n        (bns): ModuleList(\n          |0.001 M, 0.133% Params, 0.001 GMac, 0.279% MACs|\n          (0): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.044% Params, 0.0 GMac, 0.093% MACs|)\n          (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.044% Params, 0.0 GMac, 0.093% MACs|)\n          (2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.044% Params, 0.0 GMac, 0.093% MACs|)\n        )\n        (acts): ModuleList(\n          |0.0 M, 0.000% Params, 0.001 GMac, 0.139% MACs|\n          (0): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.046% MACs|)\n          (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.046% MACs|)\n          (2): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.046% MACs|)\n        )\n        (sc): Conv1d(64, 128, kernel_size=(1,), stride=(1,), bias=False, |0.008 M, 1.420% Params, 0.001 GMac, 0.186% MACs|)\n        (sc_bn): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.0 M, 0.044% Params, 0.0 GMac, 0.006% MACs|)\n        (sc_act): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.003% MACs|)\n      )\n      (2): EdgeConvBlock(\n        |0.231 M, 40.128% Params, 0.322 GMac, 72.894% MACs|\n        (convs): ModuleList(\n          |0.197 M, 34.091% Params, 0.315 GMac, 71.299% MACs|\n          (0): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.066 M, 11.364% Params, 0.105 GMac, 23.766% MACs|)\n          (1): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.066 M, 11.364% Params, 0.105 GMac, 23.766% MACs|)\n          (2): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False, |0.066 M, 11.364% Params, 0.105 GMac, 23.766% MACs|)\n        )\n        (bns): ModuleList(\n          |0.002 M, 0.266% Params, 0.002 GMac, 0.557% MACs|\n          (0): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.001 M, 0.089% Params, 0.001 GMac, 0.186% MACs|)\n          (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.001 M, 0.089% Params, 0.001 GMac, 0.186% MACs|)\n          (2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.001 M, 0.089% Params, 0.001 GMac, 0.186% MACs|)\n        )\n        (acts): ModuleList(\n          |0.0 M, 0.000% Params, 0.001 GMac, 0.279% MACs|\n          (0): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.093% MACs|)\n          (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.093% MACs|)\n          (2): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.093% MACs|)\n        )\n        (sc): Conv1d(128, 256, kernel_size=(1,), stride=(1,), bias=False, |0.033 M, 5.682% Params, 0.003 GMac, 0.743% MACs|)\n        (sc_bn): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.001 M, 0.089% Params, 0.0 GMac, 0.012% MACs|)\n        (sc_act): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.006% MACs|)\n      )\n    )\n    (fusion_block): Sequential(\n      |0.173 M, 29.963% Params, 0.017 GMac, 3.925% MACs|\n      (0): Conv1d(448, 384, kernel_size=(1,), stride=(1,), bias=False, |0.172 M, 29.830% Params, 0.017 GMac, 3.899% MACs|)\n      (1): BatchNorm1d(384, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, |0.001 M, 0.133% Params, 0.0 GMac, 0.017% MACs|)\n      (2): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.009% MACs|)\n    )\n    (fc): Sequential(\n      |0.099 M, 17.179% Params, 0.0 GMac, 0.023% MACs|\n      (0): Sequential(\n        |0.099 M, 17.090% Params, 0.0 GMac, 0.022% MACs|\n        (0): Linear(in_features=384, out_features=256, bias=True, |0.099 M, 17.090% Params, 0.0 GMac, 0.022% MACs|)\n        (1): ReLU(|0.0 M, 0.000% Params, 0.0 GMac, 0.000% MACs|)\n        (2): Dropout(p=0.1, inplace=False, |0.0 M, 0.000% Params, 0.0 GMac, 0.000% MACs|)\n      )\n      (1): Linear(in_features=256, out_features=2, bias=True, |0.001 M, 0.089% Params, 0.0 GMac, 0.000% MACs|)\n    )\n  )\n)\n

The data card is shown in top_tagging/data/pf_points_features.yaml, given in a similar way as in the MLP example. Here we group the inputs into three classes: pf_points, pf_features and pf_masks. They correspond to the forward(self, pf_points, pf_features, pf_mask) prototype of our nn.Module model, and will send in these 2D vectors in the mini-batch size for each iteration during training/prediction.

ParticleNet data config top_tagging/data/pf_points_features.yaml

See top_tagging/data/pf_points_features.yaml.

selection:\n### use `&`, `|`, `~` for logical operations on numpy arrays\n### can use functions from `math`, `np` (numpy), and `awkward` in the expression\n\nnew_variables:\n### [format] name: formula\n### can use functions from `math`, `np` (numpy), and `awkward` in the expression\npf_mask: awkward.JaggedArray.ones_like(Part_E)\nis_bkg: np.logical_not(is_signal_new)\n\npreprocess:\n### method: [manual, auto] - whether to use manually specified parameters for variable standardization\nmethod: manual\n### data_fraction: fraction of events to use when calculating the mean/scale for the standardization\ndata_fraction:\n\ninputs:\npf_points:\nlength: 100\nvars:\n- Part_Etarel\n- Part_Phirel\npf_features:\nlength: 100\nvars:\n### [format 1]: var_name (no transformation)\n### [format 2]: [var_name,\n###              subtract_by(optional, default=None, no transf. if preprocess.method=manual, auto transf. if preprocess.method=auto),\n###              multiply_by(optional, default=1),\n###              clip_min(optional, default=-5),\n###              clip_max(optional, default=5),\n###              pad_value(optional, default=0)]\n- Part_Etarel\n- Part_Phirel\n- [Part_E_log, 2, 1]\n- [Part_P_log, 2, 1]\npf_mask:\nlength: 100\nvars:\n- pf_mask\n\nlabels:\n### type can be `simple`, `custom`\n### [option 1] use `simple` for binary/multi-class classification, then `value` is a list of 0-1 labels\ntype: simple\nvalue: [\nis_signal_new, is_bkg\n]\n### [option 2] otherwise use `custom` to define the label, then `value` is a map\n# type: custom\n# value:\n# target_mass: np.where(fj_isQCD, fj_genjet_sdmass, fj_gen_mass)\n\nobservers:\n- origIdx\n- idx\n- Part_E_tot\n- Part_PX_tot\n- Part_PY_tot\n- Part_PZ_tot\n- Part_P_tot\n- Part_Eta_tot\n- Part_Phi_tot\n\n# weights:\n### [option 1] use precomputed weights stored in the input files\n# use_precomputed_weights: true\n# weight_branches: [weight, class_weight]\n### [option 2] compute weights on-the-fly using reweighting histograms\n

Now we have walked through the detailed description of three networks in their architecture as well as their implementations in Weaver.

Before ending this section, we summarize the three networks on their (1) model and data configuration cards, (2) the number of parameters, and (3) computational complexity in the following table. Note that we'll refer to the shell variables provided here in the following training example.

Model ${PREFIX} ${MODEL_CONFIG} ${DATA_CONFIG} Parameters Computational complexity MLP mlp mlp_pf.py pf_features.yaml 739k 0.001 GMac DeepAK8 (1D CNN) deepak8 deepak8_pf.py pf_features.yaml 349k 0.012 GMac ParticleNet (DGCNN) particlenet particlenet_pf.py pf_points_features.yaml 577k 0.441 GMac"},{"location":"inference/particlenet.html#2-start-training","title":"2. Start training!","text":"

Now we train the three neural networks based on the provided model and data configurations.

Here we present three ways of training. For readers who have a local machine with CUDA GPUs, please try out training on the local GPUs. Readers who would like to try on CPUs can also refer to the local GPU instruction. It is also possible to borrow the GPU resources from the lxplus HTCondor or CMS Connect. Please find in the following that meets your situation.

Train on local GPUsUse GPUs on lxplus HTCondorUse GPUs on CMS Connect

The three networks can be trained with a universal script. Enter the weaver base folder and run the following command. Note that ${DATA_CONFIG}, ${MODEL_CONFIG}, and ${PREFIX} refers to the value in the above table for each example, and the fake path should be replaced with the correct one.

PREFIX='<prefix-from-table>'\nMODEL_CONFIG='<model-config-from-table>'\nDATA_CONFIG='<data-config-from-table>'\nPATH_TO_SAMPLES='<your-path-to-samples>'\n\npython train.py \\\n --data-train ${PATH_TO_SAMPLES}'/prep/top_train_*.root' \\\n --data-val ${PATH_TO_SAMPLES}'/prep/top_val_*.root' \\\n --fetch-by-file --fetch-step 1 --num-workers 3 \\\n --data-config top_tagging/data/${DATA_CONFIG} \\\n --network-config top_tagging/networks/${MODEL_CONFIG} \\\n --model-prefix output/${PREFIX} \\\n --gpus 0,1 --batch-size 1024 --start-lr 5e-3 --num-epochs 20 --optimizer ranger \\\n --log output/${PREFIX}.train.log\n

Here --gpus 0,1 specifies the GPUs to run with the device ID 1 and 2. For training on CPUs, please use --gpu ''.

A detailed description of the training command can be found in Weaver README. Below we will note a few more caveats about the data loading options, though the specific settings will depend on the specifics of the input data.

Caveats on the data loading options

Our goal in data loading is to guarantee that the data loaded in every mini-batch is evenly distributed with different labels, though they are not necessarily stored evenly in the file. Besides, we also need to ensure that the on-the-fly loading and preprocessing of data should be smooth and not be a bottleneck of the data delivering pipeline. The total amount of loaded data also needs to be controlled so as not to explode the entire memory. The following guidelines should be used to choose the best options for your use case:

  • in the default case, data are loaded from every input file with a small proportion per fetch-step, provided by --fetch-step (default is 0.01). This adapts to the case when we have multiple classes of input, each class having multiple files (e.g., it adapts to the real CMS application because we may have multiple nano_i.root files for different input classes). The strategy gathered all pieces per fetch-step from all input files, shuffle them, and present the data we need in each regular mini-batch. One can also append --num-workers n with n being the number of paralleled workers to load the data.
  • --fetch-step 1 --num-workers 1. This strategy helps in the case we have few input files with data in different labels not evenly distributed. In the extreme case, we only have 1 file, with all data at the top being one class (signal) and data at the bottom being another class (background), or we have 2 or multiple files, each containing a specific class. In this option, --fetch-step 1 guarantees the entire data in the file is loaded and participate in the shuffle. Therefore all classes are safely mixed before sending to the mini-batch. --num-workers 1 means we only use one worker that takes care of all files to avoid inconsistent loading speeds of multiple workers (depending on CPUs). This strategy can further cooperate with --in-memory so that all data are put permanently in memory and will not be reloaded every epoch. --fetch-by-file is the option we can use when all input files have a similar structure. See Weaver README:

An alternative approach is the \"file-based\" strategy, which can be enabled with --fetch-by-files. This approach will instead read all events from every file for each step, and it will read m input files (m is set by --fetch-step) before mixing and shuffling the loaded events. This strategy is more suitable when each input file is already a mixture of all types of events (e.g., pre-processed with NNTools), otherwise it may lead to suboptimal training performance. However, a higher data loading speed can generally be achieved with this approach.

Please note that you can test if all data classes are well mixed by printing the truth label in each mini-batch. Also, remember to test if data are loaded just-in-time by monitoring the GPU performance \u2014 if switching the data loading strategy helps improve the GPU efficiency, it means the previous data loader is the bottleneck in the pipeline to deliver and use the data.

After training, we predict the score on the test datasets using the best model:

PREFIX='<prefix-from-table>'\nMODEL_CONFIG='<model-config-from-table>'\nDATA_CONFIG='<data-config-from-table>'\nPATH_TO_SAMPLES='<your-path-to-samples>'\n\npython train.py --predict \\\n --data-test ${PATH_TO_SAMPLES}'/prep/top_test_*.root' \\\n --num-workers 3 \\\n --data-config top_tagging/data/${DATA_CONFIG} \\\n --network-config top_tagging/networks/${MODEL_CONFIG} \\\n --model-prefix output/${PREFIX}_best_epoch_state.pt \\\n --gpus 0,1 --batch-size 1024 \\\n --predict-output output/${PREFIX}_predict.root\n

On lxplus HTCondor, the GPU(s) can be booked via the arguments request_gpus. To get familiar with the GPU service, please refer to the documentation here.

While it is not possible to test the script locally, you can try out the condor_ssh_to_job command to connect to the remote condor machine that runs the jobs. This interesting feature will help you with debugging or monitoring the condor job.

Here we provide the example executed script and the condor submitted file for the training and predicting task. Create the following two files:

The executable: run.sh

Still, please remember to specify ${DATA_CONFIG}, ${MODEL_CONFIG}, and ${PREFIX} as shown in the above table, and replace the fake path with the correct one.

#!/bin/bash\n\nPREFIX=$1\nMODEL_CONFIG=$2\nDATA_CONFIG=$3\nPATH_TO_SAMPLES=$4\nWORKDIR=`pwd`\n\n# Download miniconda\nwget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda_install.sh\nbash miniconda_install.sh -b -p ${WORKDIR}/miniconda\nexport PATH=$WORKDIR/miniconda/bin:$PATH\npip install numpy pandas scikit-learn scipy matplotlib tqdm PyYAML\npip install uproot3 awkward0 lz4 xxhash\npip install tables\npip install onnxruntime-gpu\npip install tensorboard\npip install torch\n\n# CUDA environment setup\nexport PATH=$PATH:/usr/local/cuda-10.2/bin\nexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda-10.2/lib64\nexport LIBRARY_PATH=$LIBRARY_PATH:/usr/local/cuda-10.2/lib64\n\n# Clone weaver-benchmark\ngit clone --recursive https://github.com/colizz/weaver-benchmark.git\nln -s ../top_tagging weaver-benchmark/weaver/top_tagging\ncd weaver-benchmark/weaver/\nmkdir output\n\n# Training, using 1 GPU\npython train.py \\\n--data-train ${PATH_TO_SAMPLES}'/prep/top_train_*.root' \\\n--data-val ${PATH_TO_SAMPLES}'/prep/top_val_*.root' \\\n--fetch-by-file --fetch-step 1 --num-workers 3 \\\n--data-config top_tagging/data/${DATA_CONFIG} \\\n--network-config top_tagging/networks/${MODEL_CONFIG} \\\n--model-prefix output/${PREFIX} \\\n--gpus 0 --batch-size 1024 --start-lr 5e-3 --num-epochs 20 --optimizer ranger \\\n--log output/${PREFIX}.train.log\n\n# Predicting score, using 1 GPU\npython train.py --predict \\\n--data-test ${PATH_TO_SAMPLES}'/prep/top_test_*.root' \\\n--num-workers 3 \\\n--data-config top_tagging/data/${DATA_CONFIG} \\\n--network-config top_tagging/networks/${MODEL_CONFIG} \\\n--model-prefix output/${PREFIX}_best_epoch_state.pt \\\n--gpus 0 --batch-size 1024 \\\n--predict-output output/${PREFIX}_predict.root\n\n[ -d \"runs/\" ] && tar -caf output.tar output/ runs/ || tar -caf output.tar output/\n

HTCondor submitted file: submit.sub

Modify the argument line. These are the bash variable PREFIX, MODEL_CONFIG, DATA_CONFIG, PATH_TO_SAMPLES used in the Weaver command. Since the EOS directory is accessable accross all condor nodes on lxplus, one may directly specify <your-path-to-samples> as the EOS path provided above. An example is shown in the commented line.

Universe                = vanilla\nexecutable              = run.sh\narguments               = <prefix> <model-config> <data-config> <your-path-to-samples>\n#arguments              = mlp mlp_pf.py pf_features.yaml /eos/user/c/coli/public/weaver-benchmark/top_tagging/samples\noutput                  = job.$(ClusterId).$(ProcId).out\nerror                   = job.$(ClusterId).$(ProcId).err\nlog                     = job.$(ClusterId).log\nshould_transfer_files   = YES\nwhen_to_transfer_output = ON_EXIT_OR_EVICT\ntransfer_output_files   = weaver-benchmark/weaver/output.tar\ntransfer_output_remaps  = \"output.tar = output.$(ClusterId).$(ProcId).tar\"\nrequest_GPUs = 1\nrequest_CPUs = 4\n+MaxRuntime = 604800\nqueue\n

Make the run.sh script an executable, then submit the job.

chmod +x run.sh\ncondor_submit submit.sub\n
A tarball will be transfered back with the weaver/output directory where the trained models and the predicted ROOT file are stored.

CMS Connect provides several GPU nodes. One can request to run GPU condor jobs in a similar way as on lxplus, please refer to the link: https://ci-connect.atlassian.net/wiki/spaces/CMS/pages/80117822/Requesting+GPUs

As the EOS user space may not be accessed from the remote node launched by CMS Connect, one may consider either (1) migrating the input files by condor, or (2) using XRootD to transfer the input file from EOS space to the condor node, before running the Weaver train command.

"},{"location":"inference/particlenet.html#3-evaluation-of-models","title":"3. Evaluation of models","text":"

In the output folder, we find the trained PyTorch models after every epoch and the log file that records the loss and accuracy in the runtime.

The predict step also produces a predicted root file in the output folder, including the truth label, the predicted store, and several observer variables we provided in the data card. With the predicted root file, we make the ROC curve comparing the performance of the three trained models.

Here is the result from my training:

Model AUC Accuracy 1/eB (@eS=0.3) MLP 0.961 0.898 186 DeepAK8 (1D CNN) 0.979 0.927 585 ParticleNet (DGCNN) 0.984 0.936 1030

We see that the ParticleNet model shows an outstanding performance in this classification task. Besides, the DeepAK8 and ParticleNet results are similar to the benchmark values found in the gDoc. We address that the performance can be further improved by some following tricks:

  • Train an ensemble of models with different initial parametrization. For each event/jet, take the final predicted score as the mean/median of the score ensembles predicted by each model. This is a widely used ML technique to pursue an extra few percent of improvements.
  • Use more input variables for training. We note that in the above training example, only four input variables are used instead of a full suite of input features as done in the ParticleNet paper [arXiv:1902.08570]. Additional variables (e.g. \u0394R or log(pT / pT(jet))) can be designed based on the given 4-momenta, and, although providing redundant information in principle, can still help the network fully exploit the point cloud structure and thus do a better discrimination job.
  • The fine-tuning of the model will also bring some performance gain. See details in the next section.
"},{"location":"inference/particlenet.html#tuning-the-particlenet-model","title":"Tuning the ParticleNet model","text":"

When it comes to the real application of any DNN model, tunning the hyperparameters is an important path towards a better performance. In this section, we provide some tips on the ParticleNet model tunning. For a more detailed discussion on this topic, see more in the \"validation\" chapter in the documentation.

"},{"location":"inference/particlenet.html#1-choices-on-the-optimizer-and-the-learning-rate","title":"1. Choices on the optimizer and the learning rate","text":"

The optimizer decides how our neural network update all its parameters, and the learning rate means how fast the parameters changes in one training iteration.

Learning rate is the most important hyperparameter to choose from before concrete training is done. Here we quote from a suggested strategy: if you only have the opportunity to optimize one hyperparameter, choose the learning rate. The optimizer is also important because a wiser strategy usually means avoid the zig-zagging updating route, avoid falling into the local minima and even adapting different strategies for the fast-changing parameters and the slow ones. Adam (and its several variations) is a widely used optimizer. Another recently developed advanced optimizer is Ranger that combines RAdam and LookAhead. However, one should note that the few percent level improvement by using different optimizers is likely to be smeared by an unoptimized learning rate.

The above training scheme uses a start learning rate of 5e-3, and Ranger as the optimizer. It uses a flat+decay schedular, in a way that the LR starts to decay after processing 70% of epochs, and gradually reduce to 0.01 of its original value when nearing the completion of all epochs.

First, we note that the current case is already well optimized. Therefore, by simply reuse the current choice, the training will converge to a stable result in general. But it is always good in practice to test several choices of the optimizer and reoptimize the learning rate.

Weaver integrates multiple optimizers. In the above training command, we use --optimizer ranger to adopt the Ranger optimizer. It is also possible to switch to --optimizer adam or --optimizer adamW.

Weaver also provides the interface to optimize the learning rate before real training is performed. In the ParticleNet model training, we append

--lr-finder 5e-6,5e0,200\n
in the command, then a specific learning-rate finder program will be launched. This setup scans over the LR from 5e-6 to 5e0 by applying 200 mini-batches of training. It outputs a plot showing the training loss for different starting learning rates. In general, a lower training loss means a better choice of the learning rate parameter.

Below shows the results from LR finder by specifying --lr-finder 5e-6,5e0,200, for the --optimizer adamW (left) and the --optimizer ranger (right) case.

The training loss forms a basin shape which indicates that the optimal learning rate falls somewhere in the middle. We extract two aspects from the plots. First, the basin covers a wide range, meaning that the LR finder only provides a rough estimation. But it is a good attempt to first run the LR finder to have an overall feeling. For the Ranger case (right figure), one can choose the range 1e-3 to 1e-2 and further determine the optminal learning rate by delivering the full training. Second, we should be aware that different optimizer takes different optimal LR values. As can be seen here, the AdamW in general requires a small LR than Ranger.

"},{"location":"inference/particlenet.html#2-visualize-the-training-with-tensorboard","title":"2. Visualize the training with TensorBoard","text":"

To monitor the full training/evaluation accuracy and the loss for each mini-batch, we can draw support from a nicely integrated utility, TensorBoard, to employ real-time monitoring. See the introduction page from PyTorch: https://pytorch.org/tutorials/recipes/recipes/tensorboard_with_pytorch.html

To activate TensorBoard, append (note that replace ${PREFIX} according to the above table)

--tensorboard ${PREFIX}\n
to the training command. The runs/ subfolder containing the TensorBoard monitoring log will appear in the Weaver directory (if you are launching condor jobs, the runs/ folder will be transferred back in the tarball). Then, one can run
tensorboard --logdir=runs\n
to start the TensorBoard service and go to URL https://localhost:6006 to view the TensorBoard dashboard.

The below plots show the training and evaluation loss, in our standard choice with LR being 5e-3, and in the case of a small LR 2e-3 and a large LR 1e-2. Note that all tested LR values are within the basin in the LR finder plots.

We see that in the evaluated loss plot, the standard LR outperforms two variational choices. The reason may be that a larger LR finds difficulty in converging to the global minima, while a smaller LR may not be adequate to reach the minima point in a journey of 20 epochs. Overall, we see 5e-3 as a good choice as the starting LR for the Ranger optimizer.

"},{"location":"inference/particlenet.html#3-optimize-the-model","title":"3. Optimize the model","text":"

In practice, tuning the model size is also an important task. By concept, a smaller model tends to have unsatisfactory performance due to the limited ability to learn many local features. As the model size goes up, the performance will climb to some extent, but may further decrease due to the network \"degradation\" (deeper models have difficulty learning features). Besides, a heavier model may also cause the overfitting issue. In practice, it also leads to larger inference time which is the main concern when coming to real applications.

For the ParticleNet model case, we also test between a smaller and larger variation of the model size. Recall that the original model is defined by the following layer parameters.

conv_params = [\n    (16, (64, 64, 64)),\n    (16, (128, 128, 128)),\n    (16, (256, 256, 256)),\n    ]\nfc_params = [(256, 0.1)]\n
We can replace the code block with
ec_k = kwargs.get('ec_k', 16)\nec_c1 = kwargs.get('ec_c1', 64)\nec_c2 = kwargs.get('ec_c2', 128)\nec_c3 = kwargs.get('ec_c3', 256)\nfc_c, fc_p = kwargs.get('fc_c', 256), kwargs.get('fc_p', 0.1)\nconv_params = [\n    (ec_k, (ec_c1, ec_c1, ec_c1)),\n    (ec_k, (ec_c2, ec_c2, ec_c2)),\n    (ec_k, (ec_c3, ec_c3, ec_c3)),\n    ]\nfc_params = [(fc_c, fc_p)]\n
Then we have the ability to tune the model parameters from the command line. Append the extra arguments in the training command
--network-option ec_k 32 --network-option ec_c1 128 --network-option ec_c2 192 --network-option ec_c3 256\n
and the model parameters will take the new values as specified.

We test over two cases, one with the above setting to enlarge the model, and another by using

--network-option ec_c1 64 --network-option ec_c2 64 --network-option ec_c3 96\n
to adopt a lite version.

The Tensorboard monitoring plots in the training/evaluation loss is shown as follows.

We see that the \"heavy\" model reaches even smaller training loss, meaning that the model does not meet the degradation issue yet. However, the evaluation loss is not catching up with the training loss, showing some degree of overtraining in this scheme. From the evaluation result, we see no improvement by moving to a heavy model.

"},{"location":"inference/particlenet.html#4-apply-preselection-and-class-weights","title":"4. Apply preselection and class weights","text":"

In HEP applications, it is sometimes required to train a multi-class classifier. While it is simple to specify the input classes in the label section of the Weaver data config, it is sometimes ignored to set up the preselection and assign the suitable class weights for training. Using an unoptimized configuration, the trained model will not reach the best performance although no error message will result.

Since our top tagging example is a binary classification problem, there is no specific need to configure the preselection and class weights. Below we summarize some experiences that may be applicable in reader's custom multi-class training task.

The preselection should be chosen in a way that all remaining events passing the selection should fall into one and only one category. In other words, events with no labels attached should not be kept since it will confuse the training process.

Class weights (the class_weights option under weights in the data config) control the relative importance of input sample categories for training. Implementation-wise, it changes the event probability in a specific category chosen as training input events. The class weight comes into effect when one trains a multi-class classifier. Take 3-class case (denoted as [A, B, C]) as an example, the class_weights: [1, 1, 1] gives equal weights to all categories. Retraining the input with class_weights: [10, 1, 1] may result in a better discriminating power for class A vs. B or A vs. C; while the power of B separating with C will be weakened. As a trade-off between separating A vs. C and B vs. C, the class weights need to be intentionally tuned to achieve reasonable performance.

After the class weights are tuned, one can use another method to further factor out the interplay across categories, i.e., to define a \"binarized\" score between two classes only. Suppose the raw score for the three classes are P(A), P(B), and P(C) (their sum should be 1), then one can define the discriminant P(BvsC) = P(B) / (P(B)+P(C)) to separate B vs. C. In this way, the saparating power of B vs. C will remain unchanged for class_weights configured as either [1, 1, 1] or [10, 1, 1]. This strategy has been widely used in CMS to define composite tagger discrimant which are applied analysis-wise.

Above, we discuss in a very detailed manner on various attempts we can make to optimize the model. We hope the practical experiences presented here will help readers develop and deploy the complex ML model.

"},{"location":"inference/performance.html","title":"Performance of inference tools","text":""},{"location":"inference/pyg.html","title":"PyTorch Geometric","text":"

Geometric deep learning (GDL) is an emerging field focused on applying machine learning (ML) techniques to non-Euclidean domains such as graphs, point clouds, and manifolds. The PyTorch Geometric (PyG) library extends PyTorch to include GDL functionality, for example classes necessary to handle data with irregular structure. PyG is introduced at a high level in Fast Graph Representation Learning with PyTorch Geometric and in detail in the PyG docs.

"},{"location":"inference/pyg.html#gdl-with-pyg","title":"GDL with PyG","text":"

A complete reveiw of GDL is available in the following recently-published (and freely-available) textbook: Geometric Deep Learning: Grids, Groups, Graphs, Geodesics, and Gauges. The authors specify several key GDL architectures including convolutional neural networks (CNNs) operating on grids, Deep Sets architectures operating on sets, and graph neural networks (GNNs) operating on graphs, collections of nodes connected by edges. PyG is focused in particular on graph-structured data, which naturally encompases set-structured data. In fact, many state-of-the-art GNN architectures are implemented in PyG (see the docs)! A review of the landscape of GNN architectures is available in Graph Neural Networks: A Review of Methods and Applications.

"},{"location":"inference/pyg.html#the-data-class-pyg-graphs","title":"The Data Class: PyG Graphs","text":"

Graphs are data structures designed to encode data structured as a set of objects and relations. Objects are embedded as graph nodes \\(u\\in\\mathcal{V}\\), where \\(\\mathcal{V}\\) is the node set. Relations are represented by edges \\((i,j)\\in\\mathcal{E}\\) between nodes, where \\(\\mathcal{E}\\) is the edge set. Denote the sizes of the node and edge sets as \\(|\\mathcal{V}|=n_\\mathrm{nodes}\\) and \\(|\\mathcal{E}|=n_\\mathrm{edges}\\) respectively. The choice of edge connectivity determines the local structure of a graph, which has important downstream effects on graph-based learning algorithms. Graph construction is the process of embedding input data onto a graph structure. Graph-based learning algorithms are correspondingly imbued with a relational inductive bias based on the choice of graph representation; a graph's edge connectivity defines its local structure. The simplest graph construction routine is to construct no edges, yielding a permutation invariant set of objects. On the other hand, fully-connected graphs connect every node-node pair with an edge, yielding \\(n_\\mathrm{edges}=n_\\mathrm{nodes}(n_\\mathrm{nodes}-1)/2\\) edges. This representation may be feasible for small inputs like particle clouds corresponding to a jet, but is intractible for large-scale applications such as high-pileup tracking datasets. Notably, dynamic graph construction techniques operate on input point clouds, constructing edges on them dynamically during inference. For example, EdgeConv and GravNet GNN layers dynamically construct edges between nodes projected into a latent space; multiple such layers may be applied in sequence, yielding many intermediate graph representations on an input point cloud.

In general, nodes can have positions \\(\\{p_i\\}_{i=1}^{n_\\mathrm{nodes}}\\), \\(p_i\\in\\mathbb{R}^{n_\\mathrm{space\\_dim}}\\), and features (attributes) \\(\\{x_i\\}_{i=1}^{n_\\mathrm{nodes}}\\), \\(x_i\\in\\mathbb{R}^{n_\\mathrm{node\\_dim}}\\). In some applications like GNN-based particle tracking, node positions are taken to be the features. In others, e.g. jet identification, positional information may be used to seed dynamic graph consturction while kinematic features are propagated as edge features. Edges, too, can have features \\(\\{e_{ij}\\}_{(i,j)\\in\\mathcal{E}}\\), \\(e_{ij}\\in\\mathbb{R}^{n_\\mathrm{edge\\_dim}}\\), but do not have positions; instead, edges are defined by the nodes they connect, and may therefore be represented by, for example, the distance between the respective node-node pair. In PyG, graphs are stored as instances of the data class, whose fields fully specify the graph:

  • data.x: node feature matrix, \\(X\\in\\mathbb{R}^{n_\\mathrm{nodes}\\times n_\\mathrm{node\\_dim}}\\)
  • data.edge_index: node indices at each end of each edge, \\(I\\in\\mathbb{R}^{2\\times n_\\mathrm{edges}}\\)
  • data.edge_attr: edge feature matrix, \\(E\\in\\mathbb{R}^{n_\\mathrm{edges}\\times n_\\mathrm{edge\\_dim}}\\)
  • data.y: training target with arbitary shape (\\(y\\in\\mathbb{R}^{n_\\mathrm{nodes}\\times n_\\mathrm{out}}\\) for node-level targets, \\(y\\in\\mathbb{R}^{n_\\mathrm{edges}\\times n_\\mathrm{out}}\\) for edge-level targets or \\(y\\in\\mathbb{R}^{1\\times n_\\mathrm{out}}\\) for node-level targets).
  • data.pos: Node position matrix, \\(P\\in\\mathbb{R}^{n_\\mathrm{nodes}\\times n_\\mathrm{space\\_dim}}\\)

The PyG Introduction By Example tutorial covers the basics of graph creation, batching, transformation, and inference using this data class.

As an example, consider the ZINC chemical compounds dataset, which available as a built-in dataset in PyG:

from torch_geometric.datasets import ZINC\ntrain_dataset = ZINC(root='/tmp/ZINC', subset=True, split='train')\ntest_dataset =  ZINC(root='/tmp/ZINC', subset=True, split='test')\nlen(train_dataset)\n>>> 10000\nlen(test_dataset)\n>>> 1000   \n
Each graph in the dataset is a chemical compound; nodes are atoms and edges are chemical bonds. The node features x are categorical atom labels and the edge features edge_attr are categorical bond labels. The edge_index matrix lists all bonds present in the compound in COO format. The truth labels y indicate a synthetic computed property called constrained solubility; given a set of molecules represented as graphs, the task is to regress the constrained solubility. Therefore, this dataset is suitable for graph-level regression. Let's take a look at one molecule:

data = train_dataset[27]\ndata.x # node features\n>>> tensor([[0], [0], [1], [2], [0], \n            [0], [2], [0], [1], [2],\n            [4], [0], [0], [0], [0],\n            [4], [0], [0], [0], [0]])\n\ndata.pos # node positions \n>>> None\n\ndata.edge_index # COO edge indices\n>>> tensor([[ 0,  1,  1,  1,  2,  3,  3,  4,  4,  \n              5,  5,  6,  6,  7,  7,  7,  8,  9, \n              9, 10, 10, 10, 11, 11, 12, 12, 13, \n              13, 14, 14, 15, 15, 15, 16, 16, 16,\n              16, 17, 18, 19], # node indices w/ outgoing edges\n            [ 1,  0,  2,  3,  1,  1,  4,  3,  5,  \n              4,  6,  5,  7,  6,  8,  9,  7,  7,\n              10,  9, 11, 15, 10, 12, 11, 13, 12, \n              14, 13, 15, 10, 14, 16, 15, 17, 18,\n              19, 16, 16, 16]]) # node indices w/ incoming edges\n\ndata.edge_attr # edge features\n>>> tensor([1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, \n            1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1,\n            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \n            1, 1, 1, 1])\n\ndata.y # truth labels\n>>> tensor([-0.0972])\n\ndata.num_nodes\n>>> 20\n\ndata.num_edges\n>>> 40\n\ndata.num_node_features\n>>> 1 \n

We can load the full set of graphs onto an available GPU and create PyG dataloaders as follows:

import torch\nfrom torch_geometric.data import DataLoader\n\ndevice = 'cuda:0' if torch.cuda.is_available() else 'cpu'\ntest_dataset = [d.to(device) for d in test_dataset]\ntrain_dataset = [d.to(device) for d in train_dataset]\ntest_loader = DataLoader(test_dataset, batch_size=1, shuffle=False)\ntrain_loader = DataLoader(train_dataset, batch_size=1, shuffle=True)\n

"},{"location":"inference/pyg.html#the-message-passing-base-class-pyg-gnns","title":"The Message Passing Base Class: PyG GNNs","text":"

The 2017 paper Neural Message Passing for Quantum Chemistry presents a unified framework for a swath of GNN architectures known as message passing neural networks (MPNNs). MPNNs are GNNs whose feature updates are given by:

\\[x_i^{(k)} = \\gamma^{(k)} \\left(x_i^{(k-1)}, \\square_{j \\in \\mathcal{N}(i)} \\, \\phi^{(k)}\\left(x_i^{(k-1)}, x_j^{(k-1)},e_{ij}\\right) \\right)\\]

Here, \\(\\gamma\\) and \\(\\phi\\) are learnable functions (which we can approximate as multilayer perceptrons), \\(\\square\\) is a permutation-invariant function (e.g. mean, max, add), and \\(\\mathcal{N}(i)\\) is the neighborhood of node \\(i\\). In PyG, you'd write your own MPNN by using the MessagePassing base class, implementing each of the above mathematical objects as an explicit function.

  • MessagePassing.message() : define an explicit NN for \\(\\phi\\), use it to calculate \"messages\" between a node \\(x_i^{(k-1)}\\) and its neighbors \\(x_j^{(k-1)}\\), \\(j\\in\\mathcal{N}(i)\\), leveraging edge features \\(e_{ij}\\) if applicable
  • MessagePassing.propagate() : in this step, messages are calculated via the message function and aggregated across each receiving node; the keyword aggr (which can be 'add', 'max', or 'mean') is used to specify the specific permutation invariant function \\(\\square_{j\\in\\mathcal{N}(i)}\\) used for message aggregation.
  • MessagePassing.update() : the results of message passing are used to update the node features \\(x_i^{(k)}\\) through the \\(\\gamma\\) MLP

The specific implementations of message(), propagate(), and update() are up to the user. A specific example is available in the PyG Creating Message Passing Networks tutorial

"},{"location":"inference/pyg.html#message-passing-with-zinc-data","title":"Message-Passing with ZINC Data","text":"

Returning to the ZINC molecular compound dataset, we can design a message-passing layer to aggregate messages across molecular graphs. Here, we'll define a multi-layer perceptron (MLP) class and use it to build a message passing layer (MPL) the following equation:

\\[x_i' = \\gamma \\left(x_i, \\frac{1}{|\\mathcal{N}(i)|}\\sum_{j \\in \\mathcal{N}(i)} \\, \\phi\\left([x_i, x_j, e_{j,i}\\right]) \\right)\\]

Here, the MLP dimensions are constrained. Since \\(x_i, e_{i,j}\\in\\mathbb{R}\\), the \\(\\phi\\) MLP must map \\(\\mathbb{R}^3\\) to \\(\\mathbb{R}^\\mathrm{message\\_size}\\). Similarly, \\(\\gamma\\) must map \\(\\mathbb{R}^{1+\\mathrm{\\mathrm{message\\_size}}}\\) to \\(\\mathbb{R}^\\mathrm{out}\\).

from torch_geometric.nn import MessagePassing\nimport torch.nn as nn\nfrom torch.nn import Sequential as Seq, Linear, ReLU\n\nclass MLP(nn.Module):\n    def __init__(self, input_size, output_size):\n        super(MLP, self).__init__()\n\n        self.layers = nn.Sequential(\n            nn.Linear(input_size, 16),\n            nn.ReLU(),\n            nn.Linear(16, 16),\n            nn.ReLU(),\n            nn.Linear(16, output_size),\n        )\n\n    def forward(self, x):\n        return self.layers(x)\n\nclass MPLayer(MessagePassing):\n    def __init__(self, n_node_feats, n_edge_feats, message_size, output_size):\n        super(MPLayer, self).__init__(aggr='mean', \n                                      flow='source_to_target')\n        self.phi = MLP(2*n_node_feats + n_edge_feats, message_size)\n        self.gamma = MLP(message_size + n_node_feats, output_size)\n\n    def forward(self, x, edge_index, edge_attr):\n        return self.propagate(edge_index, x=x, edge_attr=edge_attr)\n\n    def message(self, x_i, x_j, edge_attr):       \n        return self.phi(torch.cat([x_i, x_j, edge_attr], dim=1))\n\n    def update(self, aggr_out, x):\n        return self.gamma(torch.cat([x, aggr_out], dim=1))\n

Let's apply this layer to one of the ZINC molecules:

molecule = train_dataset[0]\ntorch.Size([29, 1]) # 29 atoms and 1 feature (atom label)\nmpl = MPLayer(1, 1, 16, 8).to(device) # message_size = 16, output_size = 8\nxprime = mpl(graph.x.float(), graph.edge_index, graph.edge_attr.unsqueeze(1))\nxprime.shape\n>>> torch.Size([29, 8]) # 29 atoms and 8 features\n
There we have it - the message passing layer has produced 8 new features for each atom.

"},{"location":"inference/pytorch.html","title":"PyTorch Inference","text":"

PyTorch is an open source ML library developed by Facebook's AI Research lab. Initially released in late-2016, PyTorch is a relatively new tool, but has become increasingly popular among ML researchers (in fact, some analyses suggest it's becoming more popular than TensorFlow in academic communities!). PyTorch is written in idiomatic Python, so its syntax is easy to parse for experienced Python programmers. Additionally, it is highly compatible with graphics processing units (GPUs), which can substantially accelerate many deep learning workflows. To date PyTorch has not been integrated into CMSSW. Trained PyTorch models may be evaluated in CMSSW via ONNX Runtime, but model construction and training workflows must currently exist outside of CMSSW. Given the considerable interest in PyTorch within the HEP/ML community, we have reason to believe it will soon be available, so stay tuned!

"},{"location":"inference/pytorch.html#introductory-references","title":"Introductory References","text":"
  • PyTorch Install Guide
  • PyTorch Tutorials
  • LPC HATs: PyTorch
  • Deep Learning w/ PyTorch Course Repo
  • CODAS-HEP
"},{"location":"inference/pytorch.html#the-basics","title":"The Basics","text":"

The following documentation surrounds a set of code snippets designed to highlight some important ML features made available in PyTorch. In the following sections, we'll break down snippets from this script, highlighting specifically the PyTorch objects in it.

"},{"location":"inference/pytorch.html#tensors","title":"Tensors","text":"

The fundamental PyTorch object is the tensor. At a glance, tensors behave similarly to NumPy arrays. For example, they are broadcasted, concatenated, and sliced in exactly the same way. The following examples highlight some common numpy-like tensor transformations:

a = torch.randn(size=(2,2))\n>>> tensor([[ 1.3552, -0.0204],\n            [ 1.2677, -0.8926]])\na.view(-1, 1)\n>>> tensor([[ 1.3552],\n            [-0.0204],\n            [ 1.2677],\n            [-0.8926]])\na.transpose(0, 1)\n>>> tensor([[ 1.3552,  1.2677],\n            [-0.0204, -0.8926]])\na.unsqueeze(dim=0)\n>>> tensor([[[ 1.3552, -0.0204],\n             [ 1.2677, -0.8926]]])\na.squeeze(dim=0)\n>>> tensor([[ 1.3552, -0.0204],\n            [ 1.2677, -0.8926]])\n
Additionally, torch supports familiar matrix operations with various syntax options:
m1 = torch.randn(size=(2,3))\nm2 = torch.randn(size=(3,2))\nx = torch.randn(3)\n\nm1 @ m2 == m1.mm(m2) # matrix multiplication\n>>> tensor([[True, True],\n            [True, True]])\n\nm1 @ x == m1.mv(x) # matrix-vector multiplication\n>>> tensor([True, True])\n\nm1.t() == m1.transpose(0, 1) # matrix transpose\n>>> tensor([[True, True],\n            [True, True],\n            [True, True]])\n
Note that tensor.transpose(dim0, dim1) is a more general operation than tensor.t(). It is important to note that tensors have been ''upgraded'' from Numpy arrays in two key ways: 1) Tensors have native GPU support. If a GPU is available at runtime, tensors can be transferred from CPU to GPU, where computations such as matrix operations are substantially faster. Note that tensor operations must be performed on objects on the same device. PyTorch supports CUDA tensor types for GPU computation (see the PyTorch Cuda Semantics guide). 2) Tensors support automatic gradient (audograd) calculations, such that operations on tensors flagged with requires_grad=True are automatically tracked. The flow of tracked tensor operations defines a computation graph in which nodes are tensors and edges are functions mapping input tensors to output tensors. Gradients are calculated numerically via autograd by walking through this computation graph.

"},{"location":"inference/pytorch.html#gpu-support","title":"GPU Support","text":"

Tensors are created on the host CPU by default:

b = torch.zeros([2,3], dtype=torch.int32)\nb.device\n>>> cpu\n

You can also create tensors on any available GPUs:

torch.cuda.is_available() # check that a GPU is available\n>>> True \ncuda0 = torch.device('cuda:0')\nc = torch.ones([2,3], dtype=torch.int32, device=cuda0)\nc.device\n>>> cuda:0\n

You can also move tensors between devices:

b = b.to(cuda0)\nb.device\n>>> cuda:0\n

There are trade-offs between computations on the CPU and GPU. GPUs have limited memory and there is a cost associated with transfering data from CPUs to GPUs. However, GPUs perform heavy matrix operations much faster than CPUs, and are therefore often used to speed up training routines.

N = 1000 # \nfor i, N in enumerate([10, 100, 500, 1000, 5000]):\n    print(\"({},{}) Matrices:\".format(N,N))\n    M1_cpu = torch.randn(size=(N,N), device='cpu')\n    M2_cpu = torch.randn(size=(N,N), device='cpu')\n    M1_gpu = torch.randn(size=(N,N), device=cuda0)\n    M2_gpu = torch.randn(size=(N,N), device=cuda0)\n    if (i==0):\n        print('Check devices for each tensor:')\n        print('M1_cpu, M2_cpu devices:', M1_cpu.device, M2_cpu.device)\n        print('M1_gpu, M2_gpu devices:', M1_gpu.device, M2_gpu.device)\n\n    def large_matrix_multiply(M1, M2):\n        return M1 * M2.transpose(0,1)\n\n    n_iter = 1000\n    t_cpu = Timer(lambda: large_matrix_multiply(M1_cpu, M2_cpu))\n    cpu_time = t_cpu.timeit(number=n_iter)/n_iter\n    print('cpu time per call: {:.6f} s'.format(cpu_time))\n\n    t_gpu = Timer(lambda: large_matrix_multiply(M1_gpu, M2_gpu))\n    gpu_time = t_gpu.timeit(number=n_iter)/n_iter\n    print('gpu time per call: {:.6f} s'.format(gpu_time))\n    print('gpu_time/cpu_time: {:.6f}\\n'.format(gpu_time/cpu_time))\n\n>>> (10,10) Matrices:\nCheck devices for each tensor:\nM1_cpu, M2_cpu devices: cpu cpu\nM1_gpu, M2_gpu devices: cuda:0 cuda:0\ncpu time per call: 0.000008 s\ngpu time per call: 0.000015 s\ngpu_time/cpu_time: 1.904711\n\n(100,100) Matrices:\ncpu time per call: 0.000015 s\ngpu time per call: 0.000015 s\ngpu_time/cpu_time: 0.993163\n\n(500,500) Matrices:\ncpu time per call: 0.000058 s\ngpu time per call: 0.000016 s\ngpu_time/cpu_time: 0.267371\n\n(1000,1000) Matrices:\ncpu time per call: 0.000170 s\ngpu time per call: 0.000015 s\ngpu_time/cpu_time: 0.089784\n\n(5000,5000) Matrices:\ncpu time per call: 0.025083 s\ngpu time per call: 0.000011 s\ngpu_time/cpu_time: 0.000419\n

The complete list of Torch Tensor operations is available in the docs.

"},{"location":"inference/pytorch.html#autograd","title":"Autograd","text":"

Backpropagation occurs automatically through autograd. For example, consider the following function and its derivatives:

\\[\\begin{aligned} f(\\textbf{a}, \\textbf{b}) &= \\textbf{a}^T \\textbf{X} \\textbf{b} \\\\ \\frac{\\partial f}{\\partial \\textbf{a}} &= \\textbf{b}^T \\textbf{X}^T\\\\ \\frac{\\partial f}{\\partial \\textbf{b}} &= \\textbf{a}^T \\textbf{X} \\end{aligned}\\]

Given specific choices of \\(\\textbf{X}\\), \\(\\textbf{a}\\), and \\(\\textbf{b}\\), we can calculate the corresponding derivatives via autograd by requiring a gradient to be stored in each relevant tensor:

X = torch.ones((2,2), requires_grad=True)\na = torch.tensor([0.5, 1], requires_grad=True)\nb = torch.tensor([0.5, -2], requires_grad=True)\nf = a.T @ X @ b\nf\n>>> tensor(-2.2500, grad_fn=<DotBackward>) \nf.backward() # backprop \na.grad\n>>> tensor([-1.5000, -1.5000])\nb.T @ X.T \n>>> tensor([-1.5000, -1.5000], grad_fn=<SqueezeBackward3>)\nb.grad\n>>> tensor([1.5000, 1.5000])\na.T @ X\n>>> tensor([1.5000, 1.5000], grad_fn=<SqueezeBackward3>)\n
The tensor.backward() call initiates backpropagation, accumulating the gradient backward through a series of grad_fn labels tied to each tensor (e.g. <DotBackward>, indicating the dot product \\((\\textbf{a}^T\\textbf{X})\\textbf{b}\\)).

"},{"location":"inference/pytorch.html#data-utils","title":"Data Utils","text":"

PyTorch is equipped with many useful data-handling utilities. For example, the torch.utils.data package implements datasets (torch.utils.data.Dataset) and iterable data loaders (torch.utils.data.DataLoader). Additionally, various batching and sampling schemes are available.

You can create custom iterable datasets via torch.utils.data.Dataset, for example a dataset collecting the results of XOR on two binary inputs:

from torch.utils.data import Dataset\n\nclass Data(Dataset):\n    def __init__(self, device):\n        self.samples = torch.tensor([[0,0], [0,1], [1,0], [1,1]]).float().to(device)\n        self.targets = np.logical_xor(self.samples[:,0], \n                                      self.samples[:,1]).float().to(device)\n\n    def __len__(self):\n        return len(self.targets)\n\n    def __getitem__(self,idx):\n        return({'x': self.samples[idx],\n                'y': self.targets[idx]})\n
Dataloaders, from torch.utils.data.DataLoader, can generate shuffled batches of data via multiple workers. Here, we load our datasets onto the GPU:
from torch.utils.data import DataLoader\n\ndevice = 'cpu'\ntrain_data = Data(device)\ntest_data = Data(device)\ntrain_loader = DataLoader(train_data, batch_size=1, shuffle=True, num_workers=2)\ntest_loader = DataLoader(test_data, batch_size=1, shuffle=False, num_workers=2)\nfor i, batch in enumerate(train_loader):\n    print(i, batch)\n\n>>> 0 {'x': tensor([[0., 0.]]), 'y': tensor([0.])}\n    1 {'x': tensor([[1., 0.]]), 'y': tensor([1.])}\n    2 {'x': tensor([[1., 1.]]), 'y': tensor([0.])}\n    3 {'x': tensor([[0., 1.]]), 'y': tensor([1.])}\n
The full set of data utils is available in the docs.

"},{"location":"inference/pytorch.html#neural-networks","title":"Neural Networks","text":"

The PyTorch nn package specifies a set of modules that correspond to different neural network (NN) components and operations. For example, the torch.nn.Linear module defines a linear transform with learnable parameters and the torch.nn.Flatten module flattens two contiguous tensor dimensions. The torch.nn.Sequential module contains a set of modules such as torch.nn.Linear and torch.nn.Sequential, chaining them together to form the forward pass of a forward network. Furthermore, one may specify various pre-implemented loss functions, for example torch.nn.BCELoss and torch.nn.KLDivLoss. The full set of PyTorch NN building blocks is available in the docs.

As an example, we can design a simple neural network designed to reproduce the output of the XOR operation on binary inputs. To do so, we can compute a simple NN of the form:

\\[\\begin{aligned} x_{in}&\\in\\{0,1\\}^{2}\\\\ l_1 &= \\sigma(W_1^Tx_{in} + b_1); \\ W_1\\in\\mathbb{R}^{2\\times2},\\ b_1\\in\\mathbb{R}^{2}\\\\ l_2 &= \\sigma(W_2^Tx + b_2); \\ W_2\\in\\mathbb{R}^{2},\\ b_1\\in\\mathbb{R}\\\\ \\end{aligned}\\]
import torch.nn as nn\n\nclass Network(nn.Module):\n\n    def __init__(self):\n        super().__init__()\n\n        self.l1 = nn.Linear(2, 2)\n        self.l2 = nn.Linear(2, 1)\n\n    def forward(self, x):\n        x = torch.sigmoid(self.l1(x))\n        x = torch.sigmoid(self.l2(x))\n        return x\n\nmodel = Network().to(device)\nmodel(train_data['x'])\n\n>>> tensor([[0.5000],\n            [0.4814],\n            [0.5148],\n            [0.4957]], grad_fn=<SigmoidBackward>)\n
"},{"location":"inference/pytorch.html#optimizers","title":"Optimizers","text":"

Training a neural network involves minimizing a loss function; classes in the torch.optim package implement various optimization strategies for example stochastic gradient descent and Adam through torch.optim.SGD and torch.optim.Adam respectively. Optimizers are configurable through parameters such as the learning rate (configuring the optimizer's step size). The full set of optimizers and accompanying tutorials are available in the docs.

To demonstrate the use of an optimizer, let's train the NN above to produce the results of the XOR operation on binary inputs. Here we'll use the Adam optimizer:

from torch import optim\nfrom torch.optim.lr_scheduler import StepLR\nfrom matplotlib import pyplot as plt\n\n# helpful references:\n# Learning XOR: exploring the space of a classic problem\n# https://towardsdatascience.com/how-neural-networks-solve-the-xor-problem-59763136bdd7\n# https://courses.cs.washington.edu/courses/cse446/18wi/sections/section8/XOR-Pytorch.html\n\n# the training function initiates backprop and \n# steps the optimizer towards the weights that \n# optimize the loss function \ndef train(model, train_loader, optimizer, epoch):\n    model.train()\n    losses = []\n    for i, batch in enumerate(train_loader):\n        optimizer.zero_grad()\n        output = model(batch['x'])\n        y, output = batch['y'], output.squeeze(1)\n\n        # optimize binary cross entropy:\n        # https://pytorch.org/docs/stable/generated/torch.nn.BCELoss.html\n        loss = F.binary_cross_entropy(output, y, reduction='mean')\n        loss.backward()\n        optimizer.step()\n        losses.append(loss.item())\n\n    return np.mean(losses)\n\n# the test function does not adjust the model's weights\ndef test(model, test_loader):\n    model.eval()\n    losses, n_correct, n_incorrect = [], 0, 0\n    with torch.no_grad():\n        for i, batch in enumerate(test_loader):\n            output = model(batch['x'])\n            y, output = batch['y'], output.squeeze(1)\n            loss = F.binary_cross_entropy(output, y, \n                                          reduction='mean').item()\n            losses.append(loss)\n\n            # determine accuracy by thresholding model output at 0.5\n            batch_correct = torch.sum(((output>0.5) & (y==1)) |\n                                      ((output<0.5) & (y==0)))\n            batch_incorrect = len(y) - batch_correct\n            n_correct += batch_correct\n            n_incorrect += batch_incorrect\n\n    return np.mean(losses), n_correct/(n_correct+n_incorrect)\n\n\n# randomly initialize the model's weights\nfor module in model.modules():\n    if isinstance(module, nn.Linear):\n        module.weight.data.normal_(0, 1)\n\n# send weights to optimizer \nlr = 2.5e-2\noptimizer = optim.Adam(model.parameters(), lr=lr)\n\nepochs = 500\nfor epoch in range(1, epochs + 1):\n    train_loss = train(model, train_loader, optimizer, epoch)\n    test_loss, test_acc = test(model, test_loader)\n    if epoch%25==0:\n        print('epoch={}: train_loss={:.3f}, test_loss={:.3f}, test_acc={:.3f}'\n              .format(epoch, train_loss, test_loss, test_acc))\n\n>>> epoch=25: train_loss=0.683, test_loss=0.681, test_acc=0.500\n    epoch=50: train_loss=0.665, test_loss=0.664, test_acc=0.750\n    epoch=75: train_loss=0.640, test_loss=0.635, test_acc=0.750\n    epoch=100: train_loss=0.598, test_loss=0.595, test_acc=0.750\n    epoch=125: train_loss=0.554, test_loss=0.550, test_acc=0.750\n    epoch=150: train_loss=0.502, test_loss=0.498, test_acc=0.750\n    epoch=175: train_loss=0.435, test_loss=0.432, test_acc=0.750\n    epoch=200: train_loss=0.360, test_loss=0.358, test_acc=0.750\n    epoch=225: train_loss=0.290, test_loss=0.287, test_acc=1.000\n    epoch=250: train_loss=0.230, test_loss=0.228, test_acc=1.000\n    epoch=275: train_loss=0.184, test_loss=0.183, test_acc=1.000\n    epoch=300: train_loss=0.149, test_loss=0.148, test_acc=1.000\n    epoch=325: train_loss=0.122, test_loss=0.122, test_acc=1.000\n    epoch=350: train_loss=0.102, test_loss=0.101, test_acc=1.000\n    epoch=375: train_loss=0.086, test_loss=0.086, test_acc=1.000\n    epoch=400: train_loss=0.074, test_loss=0.073, test_acc=1.000\n    epoch=425: train_loss=0.064, test_loss=0.063, test_acc=1.000\n    epoch=450: train_loss=0.056, test_loss=0.055, test_acc=1.000\n    epoch=475: train_loss=0.049, test_loss=0.049, test_acc=1.000\n    epoch=500: train_loss=0.043, test_loss=0.043, test_acc=1.000\n
Here, the model has converged to 100% test accuracy, indicating that it has learned to reproduce the XOR outputs perfectly. Note that even though the test accuracy is 100%, the test loss (BCE) decreases steadily; this is because the BCE loss is nonzero when \\(y_{output}\\) is not exactly 0 or 1, while accuracy is determined by thresholding the model outputs such that each prediction is the boolean \\((y_{output} > 0.5)\\). This highlights that it is important to choose the correct performance metric for an ML problem. In the case of XOR, perfect test accuracy is sufficient. Let's check that we've recovered the XOR output by extracting the model's weights and using them to build a custom XOR function:

for name, param in model.named_parameters():\n    if param.requires_grad:\n        print(name, param.data)\n\n>>> l1.weight tensor([[ 7.2888, -6.4168],\n                      [ 7.2824, -8.1637]])\n    l1.bias tensor([ 2.6895, -3.9633])\n    l2.weight tensor([[-6.3500,  8.0990]])\n    l2.bias tensor([2.5058])\n

Because our model was built with nn.Linear modules, we have weight matrices and bias terms. Next, we'll hard-code the matrix operations into a custom XOR function based on the architecture of the NN:

def XOR(x):\n    w1 = torch.tensor([[ 7.2888, -6.4168],\n                       [ 7.2824, -8.1637]]).t()\n    b1 = torch.tensor([ 2.6895, -3.9633])\n    layer1_out = torch.tensor([x[0]*w1[0,0] + x[1]*w1[1,0] + b1[0],\n                               x[0]*w1[0,1] + x[1]*w1[1,1] + b1[1]])\n    layer1_out = torch.sigmoid(layer1_out)\n\n    w2 = torch.tensor([-6.3500,  8.0990])\n    b2 = 2.5058\n    layer2_out = layer1_out[0]*w2[0] + layer1_out[1]*w2[1] + b2\n    layer2_out = torch.sigmoid(layer2_out)\n    return layer2_out, (layer2_out > 0.5)\n\nXOR([0.,0.])\n>>> (tensor(0.0359), tensor(False))\nXOR([0.,1.])\n>>> (tensor(0.9135), tensor(True))\nXOR([1.,0.])\n>>> (tensor(0.9815), tensor(True))\nXOR([1.,1.])\n>>> (tensor(0.0265), tensor(False))\n

There we have it - the NN learned XOR!

"},{"location":"inference/pytorch.html#pytorch-in-cmssw","title":"PyTorch in CMSSW","text":""},{"location":"inference/pytorch.html#via-onnx","title":"Via ONNX","text":"

One way to incorporate your PyTorch models into CMSSW is through the Open Neural Network Exchange (ONNX) Runtime tool. In brief, ONNX supports training and inference for a variety of ML frameworks, and is currently integrated into CMSSW (see the CMS ML tutorial). PyTorch hosts an excellent tutorial on exporting a model from PyTorch to ONNX. ONNX is available in CMSSW (see a relevant discussion in the CMSSW git repo).

"},{"location":"inference/pytorch.html#example-use-cases","title":"Example Use Cases","text":"

The \\(ZZ\\rightarrow 4b\\) analysis utilizes trained PyTorch models via ONNX in CMSSW (see the corresponding repo). Briefly, they run ONNX in CMSSW_11_X via the CMSSW package PhysicsTools/ONNXRuntime, using it to define a multiClassifierONNX class. This multiclassifier is capable of loading pre-trained PyTorch models specified by a modelFile string as follows:

#include \"PhysicsTools/ONNXRuntime/interface/ONNXRuntime.h\"\n\nstd::unique_ptr<cms::Ort::ONNXRuntime> model;\nOrt::SessionOptions* session_options = new Ort::SessionOptions();\nsession_options->SetIntraOpNumThreads(1);\nmodel = std::make_unique<cms::Ort::ONNXRuntime>(modelFile, session_options);\n
"},{"location":"inference/pytorch.html#via-triton","title":"Via Triton","text":"

Coprocessors (GPUs, FPGAs, etc.) are frequently used to accelerate ML operations such as inference and training. In the 'as-a-service' paradigm, users can access cloud-based applications through lightweight client inferfaces. The Services for Optimized Network Inference on Coprocessors (SONIC) framework implements this paradigm in CMSSW, allowing the optimal integration of GPUs into event processing workflows. One powerful implementation of SONIC is the the NVIDIA Triton Inference Server, which is flexible with respect to ML framework, storage source, and hardware infrastructure. For more details, see the corresponding NVIDIA developer blog entry.

A Graph Attention Network (GAN) is available via Triton in CMSSW, and can be accessed here: https://github.com/cms-sw/cmssw/tree/master/HeterogeneousCore/SonicTriton/test

"},{"location":"inference/pytorch.html#training-tips","title":"Training Tips","text":"
  • When instantiating a DataLoader, shuffle=True should be enabled for training data but not for validation and testing data. At each training epoch, this will vary the order of data objects in each batch; accordingly, it is not efficient to load the full dataset (in its original ordering) into GPU memory before training. Instead, enable num_workers>1; this allows the DataLoader to load batches to the GPU as they're prepared. Note that this launches muliple threads on the CPU. For more information, see a corresponding discussion in the PyTorch forum.
"},{"location":"inference/sonic_triton.html","title":"Service-based inference with Triton/Sonic","text":"

This page is still under construction. For the moment, please see the Sonic+Triton tutorial given as part of the Machine Learning HATS@LPC 2021.

  • Link to Indico agenda
  • Slides
  • Exercise twiki
"},{"location":"inference/standalone.html","title":"Standalone framework","text":"

Todo.

Idea: Working w/ TF+ROOT standalone (outside of CMSSW)

"},{"location":"inference/swan_aws.html","title":"SWAN + AWS","text":"

Todo.

Ideas: best practices cost model instance priving need to log out monitoring madatory

"},{"location":"inference/tensorflow1.html","title":"Direct inference with TensorFlow 1","text":"

While it is technically still possible to use TensorFlow 1, this version of TensorFlow is quite old and is no longer supported by CMSSW. We highly recommend that you update your model to TensorFlow 2 and follow the integration guide in the Inference/Direct inference/TensorFlow 2 documentation.

"},{"location":"inference/tensorflow2.html","title":"Direct inference with TensorFlow 2","text":"

TensorFlow 2 is available since CMSSW_11_1_X (cmssw#28711, cmsdist#5525). The integration into the software stack can be found in cmsdist/tensorflow.spec and the interface is located in cmssw/PhysicsTools/TensorFlow.

"},{"location":"inference/tensorflow2.html#available-versions","title":"Available versions","text":"Python 3 on el8Python 3 on slc7Python 2 on slc7 TensorFlow el8_amd64_gcc10 el8_amd64_gcc11 v2.6.0 \u2265 CMSSW_12_3_4 - v2.6.4 \u2265 CMSSW_12_5_0 \u2265 CMSSW_12_5_0 TensorFlow slc7_amd64_gcc900 slc7_amd64_gcc10 slc7_amd64_gcc11 v2.1.0 \u2265 CMSSW_11_1_0 - - v2.3.1 \u2265 CMSSW_11_2_0 - - v2.4.1 \u2265 CMSSW_11_3_0 - - v2.5.0 \u2265 CMSSW_12_0_0 \u2265 CMSSW_12_0_0 - v2.6.0 \u2265 CMSSW_12_1_0 \u2265 CMSSW_12_1_0 \u2265 CMSSW_12_3_0 v2.6.4 - \u2265 CMSSW_12_5_0 \u2265 CMSSW_13_0_0 TensorFlow slc7_amd64_gcc900 v2.1.0 \u2265 CMSSW_11_1_0 v2.3.1 \u2265 CMSSW_11_2_0

At this time, only CPU support is provided. While GPU support is generally possible, it is currently disabled due to some interference with production workflows but will be enabled once they are resolved.

"},{"location":"inference/tensorflow2.html#software-setup","title":"Software setup","text":"

To run the examples shown below, create a mininmal inference setup with the following snippet. Adapt the SCRAM_ARCH according to your operating system and desired compiler.

export SCRAM_ARCH=\"el8_amd64_gcc11\"\nexport CMSSW_VERSION=\"CMSSW_12_6_0\"\n\nsource \"/cvmfs/cms.cern.ch/cmsset_default.sh\" \"\"\n\ncmsrel \"${CMSSW_VERSION}\"\ncd \"${CMSSW_VERSION}/src\"\n\ncmsenv\nscram b\n

Below, the cmsml Python package is used to convert models from TensorFlow objects (tf.function's or Keras models) to protobuf graph files (documentation). It should be available after executing the commands above. You can check its version via

python -c \"import cmsml; print(cmsml.__version__)\"\n

and compare to the released tags. If you want to install a newer version from either the master branch of the cmsml repository or the Python package index (PyPI), you can simply do that via pip.

masterPyPI
# into your user directory (usually ~/.local)\npip install --upgrade --user git+https://github.com/cms-ml/cmsml\n\n# _or_\n\n# into a custom directory\npip install --upgrade --prefix \"CUSTOM_DIRECTORY\" git+https://github.com/cms-ml/cmsml\n
# into your user directory (usually ~/.local)\npip install --upgrade --user cmsml\n\n# _or_\n\n# into a custom directory\npip install --upgrade --prefix \"CUSTOM_DIRECTORY\" cmsml\n
"},{"location":"inference/tensorflow2.html#saving-your-model","title":"Saving your model","text":"

After successfully training, you should save your model in a protobuf graph file which can be read by the interface in CMSSW. Naturally, you only want to save that part of your model that is required to run the network prediction, i.e., it should not contain operations related to model training or loss functions (unless explicitely required). Also, to reduce the memory footprint and to accelerate the inference, variables should be converted to constant tensors. Both of these model transformations are provided by the cmsml package.

Instructions on how to transform and save your model are shown below, depending on whether you use Keras or plain TensorFlow with tf.function's.

Kerastf.function

The code below saves a Keras Model instance as a protobuf graph file using cmsml.tensorflow.save_graph. In order for Keras to built the internal graph representation before saving, make sure to either compile the model, or pass an input_shape to the first layer:

# coding: utf-8\n\nimport tensorflow as tf\nimport tf.keras.layers as layers\nimport cmsml\n\n# define your model\nmodel = tf.keras.Sequential()\nmodel.add(layers.InputLayer(input_shape=(10,), name=\"input\"))\nmodel.add(layers.Dense(100, activation=\"tanh\"))\nmodel.add(layers.Dense(3, activation=\"softmax\", name=\"output\"))\n\n# train it\n...\n\n# convert to binary (.pb extension) protobuf\n# with variables converted to constants\ncmsml.tensorflow.save_graph(\"graph.pb\", model, variables_to_constants=True)\n

Following the Keras naming conventions for certain layers, the input will be named \"input\" while the output is named \"sequential/output/Softmax\". To cross check the names, you can save the graph in text format by using the extension \".pb.txt\".

Let's consider you write your network model in a single tf.function.

# coding: utf-8\n\nimport tensorflow as tf\nimport cmsml\n\n# define the model\n@tf.function\ndef model(x):\n    # lift variable initialization to the lowest context so they are\n    # not re-initialized on every call (eager calls or signature tracing)\n    with tf.init_scope():\n        W = tf.Variable(tf.ones([10, 1]))\n        b = tf.Variable(tf.ones([1]))\n\n    # define your \"complex\" model here\n    h = tf.add(tf.matmul(x, W), b)\n    y = tf.tanh(h, name=\"y\")\n\n    return y\n

In TensorFlow terms, the model function is polymorphic - it accepts different types of the input tensor x (tf.float32, tf.float64, ...). For each type, TensorFlow will create a concrete function with an associated tf.Graph object. This mechanism is referred to as signature tracing. For deeper insights into tf.function, the concepts of signature tracing, polymorphic and concrete functions, see the guide on Better performance with tf.function.

To save the model as a protobuf graph file, you explicitely need to create a concrete function. However, this is fairly easy once you know the exact type and shape of all input arguments.

# create a concrete function\ncmodel = model.get_concrete_function(\n    tf.TensorSpec(shape=[2, 10], dtype=tf.float32),\n)\n\n# convert to binary (.pb extension) protobuf\n# with variables converted to constants\ncmsml.tensorflow.save_graph(\"graph.pb\", cmodel, variables_to_constants=True)\n

The input will be named \"x\" while the output is named \"y\". To cross check the names, you can save the graph in text format by using the extension \".pb.txt\".

Different method: Frozen signatures

Instead of creating a polymorphic tf.function and extracting a concrete one in a second step, you can directly define an input signature upon definition.

@tf.function(input_signature=(tf.TensorSpec(shape=[2, 10], dtype=tf.float32),))\ndef model(x):\n    ...\n

This disables signature tracing since the input signature is frozen. However, you can directly pass it to cmsml.tensorflow.save_graph.

"},{"location":"inference/tensorflow2.html#inference-in-cmssw","title":"Inference in CMSSW","text":"

The inference can be implemented to run in a single thread. In general, this does not mean that the module cannot be executed with multiple threads (cmsRun --numThreads <N> <CFG_FILE>), but rather that its performance in terms of evaluation time and especially memory consumption is likely to be suboptimal. Therefore, for modules to be integrated into CMSSW, the multi-threaded implementation is strongly recommended.

"},{"location":"inference/tensorflow2.html#cmssw-module-setup","title":"CMSSW module setup","text":"

If you aim to use the TensorFlow interface in a CMSSW plugin, make sure to include

<use name=\"PhysicsTools/TensorFlow\" />\n\n<flags EDM_PLUGIN=\"1\" />\n

in your plugins/BuildFile.xml file. If you are using the interface inside the src/ or interface/ directory of your module, make sure to create a global BuildFile.xml file next to theses directories, containing (at least):

<use name=\"PhysicsTools/TensorFlow\" />\n\n<export>\n<lib name=\"1\" />\n</export>\n
"},{"location":"inference/tensorflow2.html#single-threaded-inference","title":"Single-threaded inference","text":"

Despite tf.Session being removed in the Python interface as of TensorFlow 2, the concepts of

  • Graph's, containing the constant computational structure and trained variables of your model,
  • Session's, handling execution and data exchange, and
  • the separation between them

live on in the C++ interface. Thus, the overall inference approach is 1) include the interface, 2) initialize Graph and session, 3) per event create input tensors and run the inference, and 4) cleanup.

"},{"location":"inference/tensorflow2.html#1-includes","title":"1. Includes","text":"
#include \"PhysicsTools/TensorFlow/interface/TensorFlow.h\"\n#include \"FWCore/Framework/interface/one/EDAnalyzer.h\"\n// further framework includes\n...\n
"},{"location":"inference/tensorflow2.html#2-initialize-objects","title":"2. Initialize objects","text":"
// configure logging to show warnings (see table below)\ntensorflow::setLogging(\"2\");\n\n// load the graph definition\ntensorflow::GraphDef* graphDef = tensorflow::loadGraphDef(\"/path/to/constantgraph.pb\");\n\n// create a session\ntensorflow::Session* session = tensorflow::createSession(graphDef);\n
"},{"location":"inference/tensorflow2.html#3-inference","title":"3. Inference","text":"
// create an input tensor\n// (example: single batch of 10 values)\ntensorflow::Tensor input(tensorflow::DT_FLOAT, { 1, 10 });\n\n\n// fill the tensor with your input data\n// (example: just fill consecutive values)\nfor (size_t i = 0; i < 10; i++) {\ninput.matrix<float>()(0, i) = float(i);\n}\n\n// run the evaluation\nstd::vector<tensorflow::Tensor> outputs;\ntensorflow::run(session, { { \"input\", input } }, { \"output\" }, &outputs);\n\n// process the output tensor\n// (example: print the 5th value of the 0th (the only) example)\nstd::cout << outputs[0].matrix<float>()(0, 5) << std::endl;\n// -> float\n
"},{"location":"inference/tensorflow2.html#4-cleanup","title":"4. Cleanup","text":"
tensorflow::closeSession(session);\ndelete graphDef;\n
"},{"location":"inference/tensorflow2.html#full-example","title":"Full example","text":"Click to expand

The example assumes the following directory structure:

MySubsystem/MyModule/\n\u2502\n\u251c\u2500\u2500 plugins/\n\u2502   \u251c\u2500\u2500 MyPlugin.cpp\n\u2502   \u2514\u2500\u2500 BuildFile.xml\n\u2502\n\u251c\u2500\u2500 test/\n\u2502   \u2514\u2500\u2500 my_plugin_cfg.py\n\u2502\n\u2514\u2500\u2500 data/\n    \u2514\u2500\u2500 graph.pb\n
plugins/MyPlugin.cppplugins/BuildFile.xmltest/my_plugin_cfg.py
/*\n * Example plugin to demonstrate the direct single-threaded inference with TensorFlow 2.\n */\n\n#include <memory>\n\n#include \"FWCore/Framework/interface/Event.h\"\n#include \"FWCore/Framework/interface/Frameworkfwd.h\"\n#include \"FWCore/Framework/interface/MakerMacros.h\"\n#include \"FWCore/Framework/interface/one/EDAnalyzer.h\"\n#include \"FWCore/ParameterSet/interface/ParameterSet.h\"\n#include \"PhysicsTools/TensorFlow/interface/TensorFlow.h\"\n\nclass MyPlugin : public edm::one::EDAnalyzer<> {\npublic:\nexplicit MyPlugin(const edm::ParameterSet&);\n~MyPlugin(){};\n\nstatic void fillDescriptions(edm::ConfigurationDescriptions&);\n\nprivate:\nvoid beginJob();\nvoid analyze(const edm::Event&, const edm::EventSetup&);\nvoid endJob();\n\nstd::string graphPath_;\nstd::string inputTensorName_;\nstd::string outputTensorName_;\n\ntensorflow::GraphDef* graphDef_;\ntensorflow::Session* session_;\n};\n\nvoid MyPlugin::fillDescriptions(edm::ConfigurationDescriptions& descriptions) {\n// defining this function will lead to a *_cfi file being generated when compiling\nedm::ParameterSetDescription desc;\ndesc.add<std::string>(\"graphPath\");\ndesc.add<std::string>(\"inputTensorName\");\ndesc.add<std::string>(\"outputTensorName\");\ndescriptions.addWithDefaultLabel(desc);\n}\n\nMyPlugin::MyPlugin(const edm::ParameterSet& config)\n: graphPath_(config.getParameter<std::string>(\"graphPath\")),\ninputTensorName_(config.getParameter<std::string>(\"inputTensorName\")),\noutputTensorName_(config.getParameter<std::string>(\"outputTensorName\")),\ngraphDef_(nullptr),\nsession_(nullptr) {\n// set tensorflow log level to warning\ntensorflow::setLogging(\"2\");\n}\n\nvoid MyPlugin::beginJob() {\n// load the graph\ngraphDef_ = tensorflow::loadGraphDef(graphPath_);\n\n// create a new session and add the graphDef\nsession_ = tensorflow::createSession(graphDef_);\n}\n\nvoid MyPlugin::endJob() {\n// close the session\ntensorflow::closeSession(session_);\n\n// delete the graph\ndelete graphDef_;\ngraphDef_ = nullptr;\n}\n\nvoid MyPlugin::analyze(const edm::Event& event, const edm::EventSetup& setup) {\n// define a tensor and fill it with range(10)\ntensorflow::Tensor input(tensorflow::DT_FLOAT, {1, 10});\nfor (size_t i = 0; i < 10; i++) {\ninput.matrix<float>()(0, i) = float(i);\n}\n\n// define the output and run\nstd::vector<tensorflow::Tensor> outputs;\ntensorflow::run(session_, {{inputTensorName_, input}}, {outputTensorName_}, &outputs);\n\n// print the output\nstd::cout << \" -> \" << outputs[0].matrix<float>()(0, 0) << std::endl << std::endl;\n}\n\nDEFINE_FWK_MODULE(MyPlugin);\n
<use name=\"FWCore/Framework\" />\n<use name=\"FWCore/PluginManager\" />\n<use name=\"FWCore/ParameterSet\" />\n<use name=\"PhysicsTools/TensorFlow\" />\n\n<flags EDM_PLUGIN=\"1\" />\n
# coding: utf-8\n\nimport os\n\nimport FWCore.ParameterSet.Config as cms\nfrom FWCore.ParameterSet.VarParsing import VarParsing\n\n\n# get the data/ directory\nthisdir = os.path.dirname(os.path.abspath(__file__))\ndatadir = os.path.join(os.path.dirname(thisdir), \"data\")\n\n# setup minimal options\noptions = VarParsing(\"python\")\noptions.setDefault(\"inputFiles\", \"root://xrootd-cms.infn.it//store/mc/RunIISummer20UL17MiniAODv2/TTToSemiLeptonic_TuneCP5_13TeV-powheg-pythia8/MINIAODSIM/106X_mc2017_realistic_v9-v1/00000/005708B7-331C-904E-88B9-189011E6C9DD.root\")  # noqa\noptions.parseArguments()\n\n# define the process to run\nprocess = cms.Process(\"TEST\")\n\n# minimal configuration\nprocess.load(\"FWCore.MessageService.MessageLogger_cfi\")\nprocess.MessageLogger.cerr.FwkReport.reportEvery = 1\nprocess.maxEvents = cms.untracked.PSet(\n    input=cms.untracked.int32(10),\n)\nprocess.source = cms.Source(\n    \"PoolSource\",\n    fileNames=cms.untracked.vstring(options.inputFiles),\n)\n\n# process options\nprocess.options = cms.untracked.PSet(\n    allowUnscheduled=cms.untracked.bool(True),\n    wantSummary=cms.untracked.bool(True),\n)\n\n# setup MyPlugin by loading the auto-generated cfi (see MyPlugin.fillDescriptions)\nprocess.load(\"MySubsystem.MyModule.myPlugin_cfi\")\nprocess.myPlugin.graphPath = cms.string(os.path.join(datadir, \"graph.pb\"))\nprocess.myPlugin.inputTensorName = cms.string(\"input\")\nprocess.myPlugin.outputTensorName = cms.string(\"output\")\n\n# define what to run in the path\nprocess.p = cms.Path(process.myPlugin)\n
"},{"location":"inference/tensorflow2.html#multi-threaded-inference","title":"Multi-threaded inference","text":"

Compared to the single-threaded implementation above, the multi-threaded version has one major difference: both the Graph and the Session are no longer members of a particular module instance, but rather shared between all instances in all threads. See the documentation on the C++ interface of stream modules for details.

Recommendation updated

The previous recommendation stated that the Session is not constant and thus, should not be placed in the global cache, but rather created once per stream module instance. However, it was discovered that, although not explicitely declared as constant in the tensorflow::run() / Session::run() interface, the session is actually not changed during evaluation and can be treated as being effectively constant.

As a result, it is safe to move it to the global cache, next to the Graph object. The TensorFlow interface in CMSSW was adjusted in order to accept const objects in cmssw#40161.

Thus, the overall inference approach is 1) include the interface, 2) let your plugin inherit from edm::stream::EDAnalyzerasdasd and declare the GlobalCache, 3) store in cconst Session*, pointing to the cached session, and 4) per event create input tensors and run the inference.

"},{"location":"inference/tensorflow2.html#1-includes_1","title":"1. Includes","text":"
#include \"PhysicsTools/TensorFlow/interface/TensorFlow.h\"\n#include \"FWCore/Framework/interface/stream/EDAnalyzer.h\"\n// further framework includes\n...\n

Note that stream/EDAnalyzer.h is included rather than one/EDAnalyzer.h.

"},{"location":"inference/tensorflow2.html#2-define-and-use-the-global-cache","title":"2. Define and use the global cache","text":"

The cache definition is done by declaring a simple struct. However, for the purpose of just storing a graph and a session object, a so-called tensorflow::SessionCache struct is already provided centrally. It was added in cmssw#40284 and its usage is shown in the following. In case the tensorflow::SessionCache is not (yet) available in your version of CMSSW, expand the \"Custom cache struct\" section below.

Use it in the edm::GlobalCache template argument and adjust the plugin accordingly.

class MyPlugin : public edm::stream::EDAnalyzer<edm::GlobalCache<tensorflow::SessionCache>> {\npublic:\nexplicit GraphLoadingMT(const edm::ParameterSet&, const tensorflow::SessionCache*);\n~GraphLoadingMT();\n\n// an additional static method for initializing the global cache\nstatic std::unique_ptr<tensorflow::SessionCache> initializeGlobalCache(const edm::ParameterSet&);\nstatic void globalEndJob(const CacheData*);\n...\n

Implement initializeGlobalCache to control the behavior of how the cache object is created. The destructor of tensorflow::SessionCache already handles the closing of the session itself and the deletion of all objects.

std::unique_ptr<tensorflow::SessionCache> MyPlugin::initializeGlobalCache(const edm::ParameterSet& config) {\nstd::string graphPath = edm::FileInPath(params.getParameter<std::string>(\"graphPath\")).fullPath();\nreturn std::make_unique<tensorflow::SessionCache>(graphPath);\n}\n
Custom cache struct
struct MyCache {\nMyCache() : {\n}\n\nstd::atomic<tensorflow::GraphDef*> graph;\nstd::atomic<tensorflow::Session*> session;\n};\n

Use it in the edm::GlobalCache template argument and adjust the plugin accordingly.

class MyPlugin : public edm::stream::EDAnalyzer<edm::GlobalCache<CacheData>> {\npublic:\nexplicit GraphLoadingMT(const edm::ParameterSet&, const CacheData*);\n~GraphLoadingMT();\n\n// two additional static methods for handling the global cache\nstatic std::unique_ptr<CacheData> initializeGlobalCache(const edm::ParameterSet&);\nstatic void globalEndJob(const CacheData*);\n...\n

Implement initializeGlobalCache and globalEndJob to control the behavior of how the cache object is created and destroyed.

See the full example below for more details.

"},{"location":"inference/tensorflow2.html#3-initialize-objects","title":"3. Initialize objects","text":"

In your module constructor, you can get a pointer to the constant session to perform model evaluation during the event loop.

// declaration in header\nconst tensorflow::Session* _session;\n\n// get a pointer to the const session stored in the cache in the constructor init\nMyPlugin::MyPlugin(const edm::ParameterSet& config,  const tensorflow::SessionCache* cache)\n: session_(cache->getSession()) {\n...\n}\n
"},{"location":"inference/tensorflow2.html#4-inference","title":"4. Inference","text":"
// create an input tensor\n// (example: single batch of 10 values)\ntensorflow::Tensor input(tensorflow::DT_FLOAT, { 1, 10 });\n\n\n// fill the tensor with your input data\n// (example: just fill consecutive values)\nfor (size_t i = 0; i < 10; i++) {\ninput.matrix<float>()(0, i) = float(i);\n}\n\n// define the output\nstd::vector<tensorflow::Tensor> outputs;\n\n// evaluate\n// note: in case this line causes the compiler to complain about the const'ness of the session_ in\n//       this call, your CMSSW version might not yet support passing a const session, so in this\n//       case, pass \"const_cast<tensorflow::Session*>(session_)\"\ntensorflow::run(session_, { { inputTensorName, input } }, { outputTensorName }, &outputs);\n\n// process the output tensor\n// (example: print the 5th value of the 0th (the only) example)\nstd::cout << outputs[0].matrix<float>()(0, 5) << std::endl;\n// -> float\n

Note

If the TensorFlow interface in your CMSSW release does not yet accept const sessions, line 19 in the example above will cause an error during compilation. In this case, replace session_ in that line to

const_cast<tensorflow::Session*>(session_)\n
"},{"location":"inference/tensorflow2.html#full-example_1","title":"Full example","text":"Click to expand

The example assumes the following directory structure:

MySubsystem/MyModule/\n\u2502\n\u251c\u2500\u2500 plugins/\n\u2502   \u251c\u2500\u2500 MyPlugin.cpp\n\u2502   \u2514\u2500\u2500 BuildFile.xml\n\u2502\n\u251c\u2500\u2500 test/\n\u2502   \u2514\u2500\u2500 my_plugin_cfg.py\n\u2502\n\u2514\u2500\u2500 data/\n    \u2514\u2500\u2500 graph.pb\n
plugins/MyPlugin.cppplugins/BuildFile.xmltest/my_plugin_cfg.py
/*\n * Example plugin to demonstrate the direct multi-threaded inference with TensorFlow 2.\n */\n\n#include <memory>\n\n#include \"FWCore/Framework/interface/Event.h\"\n#include \"FWCore/Framework/interface/Frameworkfwd.h\"\n#include \"FWCore/Framework/interface/MakerMacros.h\"\n#include \"FWCore/Framework/interface/stream/EDAnalyzer.h\"\n#include \"FWCore/ParameterSet/interface/ParameterSet.h\"\n#include \"PhysicsTools/TensorFlow/interface/TensorFlow.h\"\n\n// put a tensorflow::SessionCache into the global cache structure\n// the session cache wraps both a tf graph and a tf session instance and also handles their deletion\nclass MyPlugin : public edm::stream::EDAnalyzer<edm::GlobalCache<tensorflow::SessionCache>> {\npublic:\nexplicit MyPlugin(const edm::ParameterSet&, const tensorflow::SessionCache*);\n~MyPlugin(){};\n\nstatic void fillDescriptions(edm::ConfigurationDescriptions&);\n\n// an additional static method for initializing the global cache\nstatic std::unique_ptr<tensorflow::SessionCache> initializeGlobalCache(const edm::ParameterSet&);\n\nprivate:\nvoid beginJob();\nvoid analyze(const edm::Event&, const edm::EventSetup&);\nvoid endJob();\n\nstd::string inputTensorName_;\nstd::string outputTensorName_;\n\n// a pointer to the session created by the global session cache\nconst tensorflow::Session* session_;\n};\n\nstd::unique_ptr<tensorflow::SessionCache> MyPlugin::initializeGlobalCache(const edm::ParameterSet& params) {\n// this method is supposed to create, initialize and return a SessionCache instance\nstd::string graphPath = edm::FileInPath(params.getParameter<std::string>(\"graphPath\")).fullPath();\n// Setup the TF backend by configuration\nif (params.getParameter<std::string>(\"tf_backend\") == \"cuda\"){\ntensorflow::Options options { tensorflow::Backend::cuda};\n}else {\ntensorflow::Options options { tensorflow::Backend::cpu};\n}\nreturn std::make_unique<tensorflow::SessionCache>(graphPath, options);\n}\n\nvoid MyPlugin::fillDescriptions(edm::ConfigurationDescriptions& descriptions) {\n// defining this function will lead to a *_cfi file being generated when compiling\nedm::ParameterSetDescription desc;\ndesc.add<std::string>(\"graphPath\");\ndesc.add<std::string>(\"inputTensorName\");\ndesc.add<std::string>(\"outputTensorName\");\ndescriptions.addWithDefaultLabel(desc);\n}\n\nMyPlugin::MyPlugin(const edm::ParameterSet& config,  const tensorflow::SessionCache* cache)\n: inputTensorName_(config.getParameter<std::string>(\"inputTensorName\")),\noutputTensorName_(config.getParameter<std::string>(\"outputTensorName\")),\nsession_(cache->getSession()) {}\n\nvoid MyPlugin::beginJob() {}\n\nvoid MyPlugin::endJob() {\n// close the session\ntensorflow::closeSession(session_);\n}\n\nvoid MyPlugin::analyze(const edm::Event& event, const edm::EventSetup& setup) {\n// define a tensor and fill it with range(10)\ntensorflow::Tensor input(tensorflow::DT_FLOAT, {1, 10});\nfor (size_t i = 0; i < 10; i++) {\ninput.matrix<float>()(0, i) = float(i);\n}\n\n// define the output\nstd::vector<tensorflow::Tensor> outputs;\n\n// evaluate\n// note: in case this line causes the compile to complain about the const'ness of the session_ in\n//       this call, your CMSSW version might not yet support passing a const session, so in this\n//       case, pass \"const_cast<tensorflow::Session*>(session_)\"\ntensorflow::run(session_, {{inputTensorName_, input}}, {outputTensorName_}, &outputs);\n\n// print the output\nstd::cout << \" -> \" << outputs[0].matrix<float>()(0, 0) << std::endl << std::endl;\n}\n\nDEFINE_FWK_MODULE(MyPlugin);\n
<use name=\"FWCore/Framework\" />\n<use name=\"FWCore/PluginManager\" />\n<use name=\"FWCore/ParameterSet\" />\n<use name=\"PhysicsTools/TensorFlow\" />\n\n<flags EDM_PLUGIN=\"1\" />\n
# coding: utf-8\n\nimport os\n\nimport FWCore.ParameterSet.Config as cms\nfrom FWCore.ParameterSet.VarParsing import VarParsing\n\n\n# get the data/ directory\nthisdir = os.path.dirname(os.path.abspath(__file__))\ndatadir = os.path.join(os.path.dirname(thisdir), \"data\")\n\n# setup minimal options\noptions = VarParsing(\"python\")\noptions.setDefault(\"inputFiles\", \"root://xrootd-cms.infn.it//store/mc/RunIISummer20UL17MiniAODv2/TTToSemiLeptonic_TuneCP5_13TeV-powheg-pythia8/MINIAODSIM/106X_mc2017_realistic_v9-v1/00000/005708B7-331C-904E-88B9-189011E6C9DD.root\")  # noqa\noptions.parseArguments()\n\n# define the process to run\nprocess = cms.Process(\"TEST\")\n\n# minimal configuration\nprocess.load(\"FWCore.MessageService.MessageLogger_cfi\")\nprocess.MessageLogger.cerr.FwkReport.reportEvery = 1\nprocess.maxEvents = cms.untracked.PSet(\n    input=cms.untracked.int32(10),\n)\nprocess.source = cms.Source(\n    \"PoolSource\",\n    fileNames=cms.untracked.vstring(options.inputFiles),\n)\n\n# process options\nprocess.options = cms.untracked.PSet(\n    allowUnscheduled=cms.untracked.bool(True),\n    wantSummary=cms.untracked.bool(True),\n)\n\n# setup MyPlugin by loading the auto-generated cfi (see MyPlugin.fillDescriptions)\nprocess.load(\"MySubsystem.MyModule.myPlugin_cfi\")\nprocess.myPlugin.graphPath = cms.string(os.path.join(datadir, \"graph.pb\"))\nprocess.myPlugin.inputTensorName = cms.string(\"input\")\nprocess.myPlugin.outputTensorName = cms.string(\"output\")\n\n# define what to run in the path\nprocess.p = cms.Path(process.myPlugin)\n
"},{"location":"inference/tensorflow2.html#gpu-backend","title":"GPU backend","text":"

By default the TensorFlow sessions get created for CPU running. Since CMSSW_13_1_X the GPU backend for TensorFlow is available in the cmssw release.

Minimal changes are needed in the inference code to move the model on the GPU. A tensorflow::Options struct is available to setup the backend.

tensorflow::Options options { tensorflow::Backend::cuda};\n\n# Initialize the cache\ntensorflow::SessionCache cache(pbFile, options);\n# or a single session\nconst tensorflow::Session* session = tensorflow::createSession(graphDef, options);\n

CMSSW modules should add an options in the PSets of the producers and analyzers to configure on the fly the TensorFlow backend for the sessions created by the plugins.

"},{"location":"inference/tensorflow2.html#optimization","title":"Optimization","text":"

Depending on the use case, the following approaches can optimize the inference performance. It could be worth checking them out in your algorithm.

Further optimization approaches can be found in the integration checklist.

"},{"location":"inference/tensorflow2.html#reusing-tensors","title":"Reusing tensors","text":"

In some cases, instead of creating new input tensors for each inference call, you might want to store input tensors as members of your plugin. This is of course possible if you know its exact shape a-prioro and comes with the cost of keeping the tensor in memory for the lifetime of your module instance.

You can use

tensor.flat<float>().setZero();\n

to reset the values of your tensor prior to each call.

"},{"location":"inference/tensorflow2.html#tensor-data-access-via-pointers","title":"Tensor data access via pointers","text":"

As shown in the examples above, tensor data can be accessed through methods such as flat<type>() or matrix<type>() which return objects that represent the underlying data in the requested structure (tensorflow::Tensor C++ API). To read and manipulate particular elements, you can directly call this object with the coordinates of an element.

// matrix returns a 2D representation\n// set element (b,i) to f\ntensor.matrix<float>()(b, i) = float(f);\n

However, doing this for a large input tensor might entail some overhead. Since the data is actually contiguous in memory (C-style \"row-major\" memory ordering), a faster (though less explicit) way of interacting with tensor data is using a pointer.

// get the pointer to the first tensor element\nfloat* d = tensor.flat<float>().data();\n

Now, the tensor data can be filled using simple and fast pointer arithmetic.

// fill tensor data using pointer arithmethic\n// memory ordering is row-major, so the most outer loop corresponds dimension 0\nfor (size_t b = 0; b < batchSize; b++) {\nfor (size_t i = 0; i < nFeatures; i++, d++) {  // note the d++\n*d = float(i);\n}\n}\n
"},{"location":"inference/tensorflow2.html#inter-and-intra-operation-parallelism","title":"Inter- and intra-operation parallelism","text":"

Debugging and local processing only

Parallelism between (inter) and within (intra) operations can greatly improve the inference performance. However, this allows TensorFlow to manage and schedule threads on its own, possibly interfering with the thread model inherent to CMSSW. For inference code that is to be officially integrated, you should avoid inter- and intra-op parallelism and rather adhere to the examples shown above.

You can configure the amount of inter- and infra-op threads via the second argument of the tensorflow::createSession method.

SimpleVerbose
tensorflow::Session* session = tensorflow::createSession(graphDef, nThreads);\n
tensorflow::SessionOptions sessionOptions;\nsessionOptions.config.set_intra_op_parallelism_threads(nThreads);\nsessionOptions.config.set_inter_op_parallelism_threads(nThreads);\n\ntensorflow::Session* session = tensorflow::createSession(graphDef, sessionOptions);\n

Then, when calling tensorflow::run, pass the internal name of the TensorFlow threadpool, i.e. \"tensorflow\", as the last argument.

std::vector<tensorflow::Tensor> outputs;\ntensorflow::run(\nsession,\n{ { inputTensorName, input } },\n{ outputTensorName },\n&outputs,\n\"tensorflow\"\n);\n
"},{"location":"inference/tensorflow2.html#miscellaneous","title":"Miscellaneous","text":""},{"location":"inference/tensorflow2.html#logging","title":"Logging","text":"

By default, TensorFlow logging is quite verbose. This can be changed by either setting the TF_CPP_MIN_LOG_LEVEL environment varibale before calling cmsRun, or within your code through tensorflow::setLogging(level).

Verbosity level TF_CPP_MIN_LOG_LEVEL debug \"0\" info \"1\" (default) warning \"2\" error \"3\" none \"4\"

Forwarding logs to the MessageLogger service is not possible yet.

"},{"location":"inference/tensorflow2.html#links-and-further-reading","title":"Links and further reading","text":"
  • cmsml package
  • CMSSW
    • TensorFlow interface documentation
    • TensorFlow interface header
    • CMSSW process options
    • C++ interface of stream modules
  • TensorFlow
    • TensorFlow 2 tutorial
    • tf.function
    • C++ API
    • tensorflow::Tensor
    • tensorflow::Operation
    • tensorflow::ClientSession
  • Keras
    • API

Authors: Marcel Rieger

"},{"location":"inference/tfaas.html","title":"TFaaS","text":""},{"location":"inference/tfaas.html#tensorflow-as-a-service","title":"TensorFlow as a Service","text":"

TensorFlow as a Service (TFaas) was developed as a general purpose service which can be deployed on any infrastruction from personal laptop, VM, to cloud infrastructure, inculding kubernetes/docker based ones. The main repository contains all details about the service, including install, end-to-end example, and demo.

For CERN users we already deploy TFaaS on the following URL: https://cms-tfaas.cern.ch

It can be used by CMS members using any HTTP based client. For example, here is a basic access from curl client:

curl -k https://cms-tfaas.cern.ch/models\n[\n  {\n    \"name\": \"luca\",\n    \"model\": \"prova.pb\",\n    \"labels\": \"labels.csv\",\n    \"options\": null,\n    \"inputNode\": \"dense_1_input\",\n    \"outputNode\": \"output_node0\",\n    \"description\": \"\",\n    \"timestamp\": \"2021-10-22 14:04:52.890554036 +0000 UTC m=+600537.976386186\"\n  },\n  {\n    \"name\": \"test_luca_1024\",\n    \"model\": \"saved_model.pb\",\n    \"labels\": \"labels.txt\",\n    \"options\": null,\n    \"inputNode\": \"dense_input_1:0\",\n    \"outputNode\": \"dense_3/Sigmoid:0\",\n    \"description\": \"\",\n    \"timestamp\": \"2021-10-22 14:04:52.890776518 +0000 UTC m=+600537.976608672\"\n  },\n  {\n    \"name\": \"vk\",\n    \"model\": \"model.pb\",\n    \"labels\": \"labels.txt\",\n    \"options\": null,\n    \"inputNode\": \"dense_1_input\",\n    \"outputNode\": \"output_node0\",\n    \"description\": \"\",\n    \"timestamp\": \"2021-10-22 14:04:52.890903234 +0000 UTC m=+600537.976735378\"\n  }\n]\n

The following APIs are available: - /upload to push your favorite TF model to TFaaS server either for Form or as tar-ball bundle, see examples below - /delete to delete your TF model from TFaaS server - /models to view existing TF models on TFaaS server - /predict/json to serve TF model predictions in JSON data-format - /predict/proto to serve TF model predictions in ProtoBuffer data-format - /predict/image to serve TF model predictions forimages in JPG/PNG formats

"},{"location":"inference/tfaas.html#look-up-your-favorite-model","title":"\u2780 look-up your favorite model","text":"

You may easily look-up your ML model from TFaaS server, e.g.

curl https://cms-tfaas.cern.ch/models\n# possible output may looks like this\n[\n  {\n    \"name\": \"luca\",\n    \"model\": \"prova.pb\",\n    \"labels\": \"labels.csv\",\n    \"options\": null,\n    \"inputNode\": \"dense_1_input\",\n    \"outputNode\": \"output_node0\",\n    \"description\": \"\",\n    \"timestamp\": \"2021-11-08 20:07:18.397487027 +0000 UTC m=+2091094.457327022\"\n  }\n  ...\n]\n
The provided /models API will list the name of the model, its file name, labels file, possible options, input and output nodes, description and proper timestamp when it was added to TFaaS repository

"},{"location":"inference/tfaas.html#upload-your-tf-model-to-tfaas-server","title":"\u2781 upload your TF model to TFaaS server","text":"

If your model is not in TFaaS server you may easily add it as following:

# example of image based model upload\ncurl -X POST https://cms-tfaas.cern.ch/upload\n-F 'name=ImageModel' -F 'params=@/path/params.json'\n-F 'model=@/path/tf_model.pb' -F 'labels=@/path/labels.txt'\n\n# example of TF pb file upload\ncurl -s -X POST https://cms-tfaas.cern.ch/upload \\\n    -F 'name=vk' -F 'params=@/path/params.json' \\\n    -F 'model=@/path/model.pb' -F 'labels=@/path/labels.txt'\n\n# example of bundle upload produce with Keras TF\n# here is our saved model area\nls model\nassets         saved_model.pb variables\n# we can create tarball and upload it to TFaaS via bundle end-point\ntar cfz model.tar.gz model\ncurl -X POST -H \"Content-Encoding: gzip\" \\\n             -H \"content-type: application/octet-stream\" \\\n             --data-binary @/path/models.tar.gz https://cms-tfaas.cern.ch/upload\n

"},{"location":"inference/tfaas.html#get-your-predictions","title":"\u2782 get your predictions","text":"

Finally, you may obtain predictions from your favorite model by using proper API, e.g.

# obtain predictions from your ImageModel\ncurl https://cms-tfaas.cern.ch/image -F 'image=@/path/file.png' -F 'model=ImageModel'\n\n# obtain predictions from your TF based model\ncat input.json\n{\"keys\": [...], \"values\": [...], \"model\":\"model\"}\n\n# call to get predictions from /json end-point using input.json\ncurl -s -X POST -H \"Content-type: application/json\" \\\n    -d@/path/input.json https://cms-tfaas.cern.ch/json\n

Fore more information please visit curl client page.

"},{"location":"inference/tfaas.html#tfaas-interface","title":"TFaaS interface","text":"

Clients communicate with TFaaS via HTTP protocol. See examples for Curl, Python and C++ clients.

"},{"location":"inference/tfaas.html#tfaas-benchmarks","title":"TFaaS benchmarks","text":"

Benchmark results on CentOS, 24 cores, 32GB of RAM serving DL NN with 42x128x128x128x64x64x1x1 architecture (JSON and ProtoBuffer formats show similar performance): - 400 req/sec for 100 concurrent clients, 1000 requests in total - 480 req/sec for 200 concurrent clients, 5000 requests in total

For more information please visit bencmarks page.

"},{"location":"inference/xgboost.html","title":"Direct inference with XGBoost","text":""},{"location":"inference/xgboost.html#general","title":"General","text":"

XGBoost is avaliable (at least) since CMSSW_9_2_4 cmssw#19377.

In CMSSW environment, XGBoost can be used via its Python API.

For UL era, there are different verisons available for different SCRAM_ARCH:

  1. For slc7_amd64_gcc700 and above, ver.0.80 is available.

  2. For slc7_amd64_gcc900 and above, ver.1.3.3 is available.

  3. Please note that different major versions have different behavior( See Caveat Session).

"},{"location":"inference/xgboost.html#existing-examples","title":"Existing Examples","text":"

There are some existing good examples of using XGBoost under CMSSW, as listed below:

  1. Offical sample for testing the integration of XGBoost library with CMSSW.

  2. Useful codes created by Dr. Huilin Qu for inference with existing trained model.

  3. C/C++ Interface for inference with existing trained model.

We will provide examples for both C/C++ interface and python interface of XGBoost under CMSSW environment.

"},{"location":"inference/xgboost.html#example-classification-of-points-from-joint-gaussian-distribution","title":"Example: Classification of points from joint-Gaussian distribution.","text":"

In this specific example, you will use XGBoost to classify data points generated from two 8-dimension joint-Gaussian distribution.

Feature Index 0 1 2 3 4 5 6 7 \u03bc1 1 2 3 4 5 6 7 8 \u03bc2 0 1.9 3.2 4.5 4.8 6.1 8.1 11 \u03c3\u00bd = \u03c3 1 1 1 1 1 1 1 1 |\u03bc1 - \u03bc2| / \u03c3 1 0.1 0.2 0.5 0.2 0.1 1.1 3

All generated data points for train(1:10000,2:10000) and test(1:1000,2:1000) are stored as Train_data.csv/Test_data.csv.

"},{"location":"inference/xgboost.html#preparing-model","title":"Preparing Model","text":"

The training process of a XGBoost model can be done outside of CMSSW. We provide a python script for illustration.

# importing necessary models\nimport numpy as np\nimport pandas as pd \nfrom xgboost import XGBClassifier # Or XGBRegressor for Logistic Regression\nimport matplotlib.pyplot as plt\nimport pandas as pd\n\n# specify parameters via map\nparam = {'n_estimators':50}\nxgb = XGBClassifier(param)\n\n# using Pandas.DataFrame data-format, other available format are XGBoost's DMatrix and numpy.ndarray\n\ntrain_data = pd.read_csv(\"path/to/the/data\") # The training dataset is code/XGBoost/Train_data.csv\n\ntrain_Variable = train_data['0', '1', '2', '3', '4', '5', '6', '7']\ntrain_Score = train_data['Type'] # Score should be integer, 0, 1, (2 and larger for multiclass)\n\ntest_data = pd.read_csv(\"path/to/the/data\") # The testing dataset is code/XGBoost/Test_data.csv\n\ntest_Variable = test_data['0', '1', '2', '3', '4', '5', '6', '7']\ntest_Score = test_data['Type']\n\n# Now the data are well prepared and named as train_Variable, train_Score and test_Variable, test_Score.\n\nxgb.fit(train_Variable, train_Score) # Training\n\nxgb.predict(test_Variable) # Outputs are integers\n\nxgb.predict_proba(test_Variable) # Output scores , output structre: [prob for 0, prob for 1,...]\n\nxgb.save_model(\"\\Path\\To\\Where\\You\\Want\\ModelName.model\") # Saving model\n
The saved model ModelName.model is thus available for python and C/C++ api to load. Please use the XGBoost major version consistently (see Caveat).

While training with data from different datasets, proper treatment of weights are necessary for better model performance. Please refer to Official Recommendation for more details.

"},{"location":"inference/xgboost.html#cc-usage-with-cmssw","title":"C/C++ Usage with CMSSW","text":"

To use a saved XGBoost model with C/C++ code, it is convenient to use the XGBoost's offical C api. Here we provide a simple example as following.

"},{"location":"inference/xgboost.html#module-setup","title":"Module setup","text":"

There is no official CMSSW interface for XGBoost while its library are placed in cvmfs of CMSSW. Thus we have to use the raw c_api as well as setting up the library manually.

  1. To run XGBoost's c_api within CMSSW framework, in addition to the following standard setup.
    export SCRAM_ARCH=\"slc7_amd64_gcc700\" # To use higher version, please switch to slc7_amd64_900\nexport CMSSW_VERSION=\"CMSSW_X_Y_Z\"\n\nsource /cvmfs/cms.cern.ch/cmsset_default.sh\n\ncmsrel \"$CMSSW_VERSION\"\ncd \"$CMSSW_VERSION/src\"\n\ncmsenv\nscram b\n
    The addtional effort is to add corresponding xml file(s) to $CMSSW_BASE/toolbox$CMSSW_BASE/config/toolbox/$SCRAM_ARCH/tools/selected/ for setting up XGBoost.
  1. For lower version (<1), add two xml files as below.

    xgboost.xml

     <tool name=\"xgboost\" version=\"0.80\">\n<lib name=\"xgboost\"/>\n<client>\n<environment name=\"LIBDIR\" default=\"/cvmfs/cms.cern.ch/$SCRAM_ARCH/external/py2-xgboost/0.80-ikaegh/lib/python2.7/site-packages/xgboost/lib\"/>\n<environment name=\"INCLUDE\" default=\"/cvmfs/cms.cern.ch/$SCRAM_ARCH/external/py2-xgboost/0.80-ikaegh/lib/python2.7/site-packages/xgboost/include/\"/>\n</client>\n<runtime name=\"ROOT_INCLUDE_PATH\" value=\"$INCLUDE\" type=\"path\"/>\n<runtime name=\"PATH\" value=\"$INCLUDE\" type=\"path\"/>\n<use name=\"rabit\"/>\n</tool>\n
    rabit.xml
     <tool name=\"rabit\" version=\"0.80\">\n<client>\n<environment name=\"INCLUDE\" default=\"/cvmfs/cms.cern.ch/$SCRAM_ARCH/external/py2-xgboost/0.80-ikaegh/lib/python2.7/site-packages/xgboost/rabit/include/\"/>\n</client>\n<runtime name=\"ROOT_INCLUDE_PATH\" value=\"$INCLUDE\" type=\"path\"/>\n<runtime name=\"PATH\" value=\"$INCLUDE\" type=\"path\"/>  </tool>\n
    Please note that the path in cvmfs is not fixed, one can list all available versions in the py2-xgboost directory and choose one to use.

  2. For higher version (>=1), and one xml file

    xgboost.xml

    <tool name=\"xgboost\" version=\"0.80\">\n<lib name=\"xgboost\"/>\n<client>\n<environment name=\"LIBDIR\" default=\"/cvmfs/cms.cern.ch/$SCRAM_ARCH/external/xgboost/1.3.3/lib64\"/>\n<environment name=\"INCLUDE\" default=\"/cvmfs/cms.cern.ch/$SCRAM_ARCH/external/xgboost/1.3.3/include/\"/>\n</client>\n<runtime name=\"ROOT_INCLUDE_PATH\" value=\"$INCLUDE\" type=\"path\"/>\n<runtime name=\"PATH\" value=\"$INCLUDE\" type=\"path\"/>  </tool>\n
    Also one has the freedom to choose the available xgboost version inside xgboost directory.

  1. After adding xml file(s), the following commands should be executed for setting up.

    1. For lower version (<1), use
      scram setup rabit\nscram setup xgboost\n
    2. For higher version (>=1), use
      scram setup xgboost\n
  2. For using XGBoost as a plugin of CMSSW, it is necessary to add

    <use name=\"xgboost\"/>\n<flags EDM_PLUGIN=\"1\"/>\n
    in your plugins/BuildFile.xml. If you are using the interface inside the src/ or interface/ directory of your module, make sure to create a global BuildFile.xml file next to theses directories, containing (at least):
    <use name=\"xgboost\"/>\n<export>\n<lib   name=\"1\"/>\n</export>\n

  3. The libxgboost.so would be too large to load for cmsRun job, please using the following commands for pre-loading:

    export LD_PRELOAD=$CMSSW_BASE/external/$SCRAM_ARCH/lib/libxgboost.so\n

"},{"location":"inference/xgboost.html#basic-usage-of-c-api","title":"Basic Usage of C API","text":"

In order to use c_api of XGBoost to load model and operate inference, one should construct necessaries objects:

  1. Files to include

    #include <xgboost/c_api.h> 

  2. BoosterHandle: worker of XGBoost

    // Declare Object\nBoosterHandle booster_;\n// Allocate memory in C style\nXGBoosterCreate(NULL,0,&booster_);\n// Load Model\nXGBoosterLoadModel(booster_,model_path.c_str()); // second argument should be a const char *.\n

  3. DMatrixHandle: handle to dmatrix, the data format of XGBoost

    float TestData[2000][8] // Suppose 2000 data points, each data point has 8 dimension\n// Assign data to the \"TestData\" 2d array ... \n// Declare object\nDMatrixHandle data_;\n// Allocate memory and use external float array to initialize\nXGDMatrixCreateFromMat((float *)TestData,2000,8,-1,&data_); // The first argument takes in float * namely 1d float array only, 2nd & 3rd: shape of input, 4th: value to replace missing ones\n

  4. XGBoosterPredict: function for inference

    bst_ulong outlen; // bst_ulong is a typedef of unsigned long\nconst float *f; // array to store predictions\nXGBoosterPredict(booster_,data_,0,0,&out_len,&f);// lower version API\n// XGBoosterPredict(booster_,data_,0,0,0,&out_len,&f);// higher version API\n/*\nlower version (ver.<1) API\nXGB_DLL int XGBoosterPredict(   \nBoosterHandle   handle,\nDMatrixHandle   dmat,\nint     option_mask, // 0 for normal output, namely reporting scores\nint     training, // 0 for prediction\nbst_ulong *     out_len,\nconst float **  out_result \n)\n\nhigher version (ver.>=1) API\nXGB_DLL int XGBoosterPredict(   \nBoosterHandle   handle,\nDMatrixHandle   dmat,\nint     option_mask, // 0 for normal output, namely reporting scores\nint ntree_limit, // how many trees for prediction, set to 0 means no limit\nint     training, // 0 for prediction\nbst_ulong *     out_len,\nconst float **  out_result \n)\n*/\n

"},{"location":"inference/xgboost.html#full-example","title":"Full Example","text":"Click to expand full example

The example assumes the following directory structure:

MySubsystem/MyModule/\n\u2502\n\u251c\u2500\u2500 plugins/\n\u2502   \u251c\u2500\u2500 XGBoostExample.cc\n\u2502   \u2514\u2500\u2500 BuildFile.xml\n\u2502\n\u251c\u2500\u2500 python/\n\u2502   \u2514\u2500\u2500 xgboost_cfg.py\n\u2502\n\u251c\u2500\u2500 toolbox/ (storing necessary xml(s) to be copied to toolbox/ of $CMSSW_BASE)\n\u2502   \u2514\u2500\u2500 xgboost.xml\n\u2502   \u2514\u2500\u2500 rabit.xml (lower version only)\n\u2502\n\u2514\u2500\u2500 data/\n    \u2514\u2500\u2500 Test_data.csv\n    \u2514\u2500\u2500 lowVer.model / highVer.model \n
Please also note that in order to operate inference in an event-by-event way, please put XGBoosterPredict in analyze rather than beginJob.

plugins/XGBoostExample.cc for lower version XGBoostplugins/BuildFile.xml for lower version XGBoostpython/xgboost_cfg.py for lower version XGBoostplugins/XGBoostExample.cc for higher version XGBoostplugins/BuildFile.xml for higher version XGBoostpython/xgboost_cfg.py for higher version XGBoost
// -*- C++ -*-\n//\n// Package:    XGB_Example/XGBoostExample\n// Class:      XGBoostExample\n//\n/**\\class XGBoostExample XGBoostExample.cc XGB_Example/XGBoostExample/plugins/XGBoostExample.cc\n\n Description: [one line class summary]\n\n Implementation:\n     [Notes on implementation]\n*/\n//\n// Original Author:  Qian Sitian\n//         Created:  Sat, 19 Jun 2021 08:38:51 GMT\n//\n//\n\n\n// system include files\n#include <memory>\n\n// user include files\n#include \"FWCore/Framework/interface/Frameworkfwd.h\"\n#include \"FWCore/Framework/interface/one/EDAnalyzer.h\"\n\n#include \"FWCore/Framework/interface/Event.h\"\n#include \"FWCore/Framework/interface/MakerMacros.h\"\n\n#include \"FWCore/ParameterSet/interface/ParameterSet.h\"\n#include \"FWCore/Utilities/interface/InputTag.h\"\n#include \"DataFormats/TrackReco/interface/Track.h\"\n#include \"DataFormats/TrackReco/interface/TrackFwd.h\"\n\n#include <xgboost/c_api.h>\n#include <vector>\n#include <tuple>\n#include <string>\n#include <iostream>\n#include <fstream>\n#include <sstream>\n\nusing namespace std;\n\nvector<vector<double>> readinCSV(const char* name){\nauto fin = ifstream(name);\nvector<vector<double>> floatVec;\nstring strFloat;\nfloat fNum;\nint counter = 0;\ngetline(fin,strFloat);\nwhile(getline(fin,strFloat))\n{\nstd::stringstream  linestream(strFloat);\nfloatVec.push_back(std::vector<double>());\nwhile(linestream>>fNum)\n{\nfloatVec[counter].push_back(fNum);\nif (linestream.peek() == ',')\nlinestream.ignore();\n}\n++counter;\n}\nreturn floatVec;\n}\n\n//\n// class declaration\n//\n\n// If the analyzer does not use TFileService, please remove\n// the template argument to the base class so the class inherits\n// from  edm::one::EDAnalyzer<>\n// This will improve performance in multithreaded jobs.\n\n\n\nclass XGBoostExample : public edm::one::EDAnalyzer<>  {\npublic:\nexplicit XGBoostExample(const edm::ParameterSet&);\n~XGBoostExample();\n\nstatic void fillDescriptions(edm::ConfigurationDescriptions& descriptions);\n\n\nprivate:\nvirtual void beginJob() ;\nvirtual void analyze(const edm::Event&, const edm::EventSetup&) ;\nvirtual void endJob() ;\n\n// ----------member data ---------------------------\n\nstd::string test_data_path;\nstd::string model_path;\n\n\n\n\n};\n\n//\n// constants, enums and typedefs\n//\n\n//\n// static data member definitions\n//\n\n//\n// constructors and destructor\n//\nXGBoostExample::XGBoostExample(const edm::ParameterSet& config):\ntest_data_path(config.getParameter<std::string>(\"test_data_path\")),\nmodel_path(config.getParameter<std::string>(\"model_path\"))\n{\n\n}\n\n\nXGBoostExample::~XGBoostExample()\n{\n\n// do anything here that needs to be done at desctruction time\n// (e.g. close files, deallocate resources etc.)\n\n}\n\n\n//\n// member functions\n//\n\nvoid\nXGBoostExample::analyze(const edm::Event& iEvent, const edm::EventSetup& iSetup)\n{\n}\n\n\nvoid\nXGBoostExample::beginJob()\n{\nBoosterHandle booster_;\nXGBoosterCreate(NULL,0,&booster_);\ncout<<\"Hello World No.2\"<<endl;\nXGBoosterLoadModel(booster_,model_path.c_str());\nunsigned long numFeature = 0;\ncout<<\"Hello World No.3\"<<endl;\nvector<vector<double>> TestDataVector = readinCSV(test_data_path.c_str());\ncout<<\"Hello World No.4\"<<endl;\nfloat TestData[2000][8];\ncout<<\"Hello World No.5\"<<endl;\nfor(unsigned i=0; (i < 2000); i++)\n{ for(unsigned j=0; (j < 8); j++)\n{\nTestData[i][j] = TestDataVector[i][j];\n//  cout<<TestData[i][j]<<\"\\t\";\n} //cout<<endl;\n}\ncout<<\"Hello World No.6\"<<endl;\nDMatrixHandle data_;\nXGDMatrixCreateFromMat((float *)TestData,2000,8,-1,&data_);\ncout<<\"Hello World No.7\"<<endl;\nbst_ulong out_len=0;\nconst float *f;\ncout<<out_len<<endl;\nauto ret=XGBoosterPredict(booster_, data_, 0,0,&out_len,&f);\ncout<<ret<<endl;\nfor (unsigned int i=0;i<2;i++)\nstd::cout <<  i << \"\\t\"<< f[i] << std::endl;\ncout<<\"Hello World No.8\"<<endl;\n}\n\nvoid\nXGBoostExample::endJob()\n{\n}\n\nvoid\nXGBoostExample::fillDescriptions(edm::ConfigurationDescriptions& descriptions) {\n//The following says we do not know what parameters are allowed so do no validation\n// Please change this to state exactly what you do use, even if it is no parameters\nedm::ParameterSetDescription desc;\ndesc.add<std::string>(\"test_data_path\");\ndesc.add<std::string>(\"model_path\");\ndescriptions.addWithDefaultLabel(desc);\n\n//Specify that only 'tracks' is allowed\n//To use, remove the default given above and uncomment below\n//ParameterSetDescription desc;\n//desc.addUntracked<edm::InputTag>(\"tracks\",\"ctfWithMaterialTracks\");\n//descriptions.addDefault(desc);\n}\n\n//define this as a plug-in\nDEFINE_FWK_MODULE(XGBoostExample);\n
<use name=\"FWCore/Framework\"/>\n<use name=\"FWCore/PluginManager\"/>\n<use name=\"FWCore/ParameterSet\"/>\n<use name=\"DataFormats/TrackReco\"/>\n<use name=\"xgboost\"/>\n<flags EDM_PLUGIN=\"1\"/>\n
# coding: utf-8\n\nimport os\n\nimport FWCore.ParameterSet.Config as cms\nfrom FWCore.ParameterSet.VarParsing import VarParsing\n\n# setup minimal options\n#options = VarParsing(\"python\")\n#options.setDefault(\"inputFiles\", \"root://xrootd-cms.infn.it//store/mc/RunIIFall17MiniAOD/DYJetsToLL_M-10to50_TuneCP5_13TeV-madgraphMLM-pythia8/MINIAODSIM/94X_mc2017_realistic_v10-v2/00000/9A439935-1FFF-E711-AE07-D4AE5269F5FF.root\")  # noqa\n#options.parseArguments()\n\n# define the process to run\nprocess = cms.Process(\"TEST\")\n\n# minimal configuration\nprocess.load(\"FWCore.MessageService.MessageLogger_cfi\")\nprocess.MessageLogger.cerr.FwkReport.reportEvery = 1\nprocess.maxEvents = cms.untracked.PSet(input=cms.untracked.int32(1))\n#process.source = cms.Source(\"PoolSource\",\n#    fileNames=cms.untracked.vstring('file:/afs/cern.ch/cms/Tutorials/TWIKI_DATA/TTJets_8TeV_53X.root'))\nprocess.source = cms.Source(\"EmptySource\")\n# process options\nprocess.options = cms.untracked.PSet(\n    allowUnscheduled=cms.untracked.bool(True),\n    wantSummary=cms.untracked.bool(True),\n)\n\nprocess.XGBoostExample = cms.EDAnalyzer(\"XGBoostExample\")\n\n# setup MyPlugin by loading the auto-generated cfi (see MyPlugin.fillDescriptions)\n#process.load(\"XGB_Example.XGBoostExample.XGBoostExample_cfi\")\nprocess.XGBoostExample.model_path = cms.string(\"/Your/Path/data/lowVer.model\")\nprocess.XGBoostExample.test_data_path = cms.string(\"/Your/Path/data/Test_data.csv\")\n\n# define what to run in the path\nprocess.p = cms.Path(process.XGBoostExample)\n
// -*- C++ -*-\n//\n// Package:    XGB_Example/XGBoostExample\n// Class:      XGBoostExample\n//\n/**\\class XGBoostExample XGBoostExample.cc XGB_Example/XGBoostExample/plugins/XGBoostExample.cc\n\n Description: [one line class summary]\n\n Implementation:\n     [Notes on implementation]\n*/\n//\n// Original Author:  Qian Sitian\n//         Created:  Sat, 19 Jun 2021 08:38:51 GMT\n//\n//\n\n\n// system include files\n#include <memory>\n\n// user include files\n#include \"FWCore/Framework/interface/Frameworkfwd.h\"\n#include \"FWCore/Framework/interface/one/EDAnalyzer.h\"\n\n#include \"FWCore/Framework/interface/Event.h\"\n#include \"FWCore/Framework/interface/MakerMacros.h\"\n\n#include \"FWCore/ParameterSet/interface/ParameterSet.h\"\n#include \"FWCore/Utilities/interface/InputTag.h\"\n#include \"DataFormats/TrackReco/interface/Track.h\"\n#include \"DataFormats/TrackReco/interface/TrackFwd.h\"\n\n#include <xgboost/c_api.h>\n#include <vector>\n#include <tuple>\n#include <string>\n#include <iostream>\n#include <fstream>\n#include <sstream>\n\nusing namespace std;\n\nvector<vector<double>> readinCSV(const char* name){\nauto fin = ifstream(name);\nvector<vector<double>> floatVec;\nstring strFloat;\nfloat fNum;\nint counter = 0;\ngetline(fin,strFloat);\nwhile(getline(fin,strFloat))\n{\nstd::stringstream  linestream(strFloat);\nfloatVec.push_back(std::vector<double>());\nwhile(linestream>>fNum)\n{\nfloatVec[counter].push_back(fNum);\nif (linestream.peek() == ',')\nlinestream.ignore();\n}\n++counter;\n}\nreturn floatVec;\n}\n\n//\n// class declaration\n//\n\n// If the analyzer does not use TFileService, please remove\n// the template argument to the base class so the class inherits\n// from  edm::one::EDAnalyzer<>\n// This will improve performance in multithreaded jobs.\n\n\n\nclass XGBoostExample : public edm::one::EDAnalyzer<>  {\npublic:\nexplicit XGBoostExample(const edm::ParameterSet&);\n~XGBoostExample();\n\nstatic void fillDescriptions(edm::ConfigurationDescriptions& descriptions);\n\n\nprivate:\nvirtual void beginJob() ;\nvirtual void analyze(const edm::Event&, const edm::EventSetup&) ;\nvirtual void endJob() ;\n\n// ----------member data ---------------------------\n\nstd::string test_data_path;\nstd::string model_path;\n\n\n\n\n};\n\n//\n// constants, enums and typedefs\n//\n\n//\n// static data member definitions\n//\n\n//\n// constructors and destructor\n//\nXGBoostExample::XGBoostExample(const edm::ParameterSet& config):\ntest_data_path(config.getParameter<std::string>(\"test_data_path\")),\nmodel_path(config.getParameter<std::string>(\"model_path\"))\n{\n\n}\n\n\nXGBoostExample::~XGBoostExample()\n{\n\n// do anything here that needs to be done at desctruction time\n// (e.g. close files, deallocate resources etc.)\n\n}\n\n\n//\n// member functions\n//\n\nvoid\nXGBoostExample::analyze(const edm::Event& iEvent, const edm::EventSetup& iSetup)\n{\n}\n\n\nvoid\nXGBoostExample::beginJob()\n{\nBoosterHandle booster_;\nXGBoosterCreate(NULL,0,&booster_);\nXGBoosterLoadModel(booster_,model_path.c_str());\nunsigned long numFeature = 0;\nvector<vector<double>> TestDataVector = readinCSV(test_data_path.c_str());\nfloat TestData[2000][8];\nfor(unsigned i=0; (i < 2000); i++)\n{ for(unsigned j=0; (j < 8); j++)\n{\nTestData[i][j] = TestDataVector[i][j];\n//  cout<<TestData[i][j]<<\"\\t\";\n} //cout<<endl;\n}\nDMatrixHandle data_;\nXGDMatrixCreateFromMat((float *)TestData,2000,8,-1,&data_);\nbst_ulong out_len=0;\nconst float *f;\nauto ret=XGBoosterPredict(booster_, data_,0, 0,0,&out_len,&f);\nfor (unsigned int i=0;i<out_len;i++)\nstd::cout <<  i << \"\\t\"<< f[i] << std::endl;\n}\n\nvoid\nXGBoostExample::endJob()\n{\n}\n\nvoid\nXGBoostExample::fillDescriptions(edm::ConfigurationDescriptions& descriptions) {\n//The following says we do not know what parameters are allowed so do no validation\n// Please change this to state exactly what you do use, even if it is no parameters\nedm::ParameterSetDescription desc;\ndesc.add<std::string>(\"test_data_path\");\ndesc.add<std::string>(\"model_path\");\ndescriptions.addWithDefaultLabel(desc);\n\n//Specify that only 'tracks' is allowed\n//To use, remove the default given above and uncomment below\n//ParameterSetDescription desc;\n//desc.addUntracked<edm::InputTag>(\"tracks\",\"ctfWithMaterialTracks\");\n//descriptions.addDefault(desc);\n}\n\n//define this as a plug-in\nDEFINE_FWK_MODULE(XGBoostExample);\n
<use name=\"FWCore/Framework\"/>\n<use name=\"FWCore/PluginManager\"/>\n<use name=\"FWCore/ParameterSet\"/>\n<use name=\"DataFormats/TrackReco\"/>\n<use name=\"xgboost\"/>\n<flags EDM_PLUGIN=\"1\"/>\n
# coding: utf-8\n\nimport os\n\nimport FWCore.ParameterSet.Config as cms\nfrom FWCore.ParameterSet.VarParsing import VarParsing\n\n# setup minimal options\n#options = VarParsing(\"python\")\n#options.setDefault(\"inputFiles\", \"root://xrootd-cms.infn.it//store/mc/RunIIFall17MiniAOD/DYJetsToLL_M-10to50_TuneCP5_13TeV-madgraphMLM-pythia8/MINIAODSIM/94X_mc2017_realistic_v10-v2/00000/9A439935-1FFF-E711-AE07-D4AE5269F5FF.root\")  # noqa\n#options.parseArguments()\n\n# define the process to run\nprocess = cms.Process(\"TEST\")\n\n# minimal configuration\nprocess.load(\"FWCore.MessageService.MessageLogger_cfi\")\nprocess.MessageLogger.cerr.FwkReport.reportEvery = 1\nprocess.maxEvents = cms.untracked.PSet(input=cms.untracked.int32(10))\n#process.source = cms.Source(\"PoolSource\",\n#    fileNames=cms.untracked.vstring('file:/afs/cern.ch/cms/Tutorials/TWIKI_DATA/TTJets_8TeV_53X.root'))\nprocess.source = cms.Source(\"EmptySource\")\n#process.source = cms.Source(\"PoolSource\",\n#    fileNames=cms.untracked.vstring(options.inputFiles))\n# process options\nprocess.options = cms.untracked.PSet(\n    allowUnscheduled=cms.untracked.bool(True),\n    wantSummary=cms.untracked.bool(True),\n)\n\nprocess.XGBoostExample = cms.EDAnalyzer(\"XGBoostExample\")\n\n# setup MyPlugin by loading the auto-generated cfi (see MyPlugin.fillDescriptions)\n#process.load(\"XGB_Example.XGBoostExample.XGBoostExample_cfi\")\nprocess.XGBoostExample.model_path = cms.string(\"/Your/Path/data/highVer.model\")  \nprocess.XGBoostExample.test_data_path = cms.string(\"/Your/Path/data/Test_data.csv\")\n\n# define what to run in the path\nprocess.p = cms.Path(process.XGBoostExample)\n
"},{"location":"inference/xgboost.html#python-usage","title":"Python Usage","text":"

To use XGBoost's python interface, using the snippet below under CMSSW environment

# importing necessary models\nimport numpy as np\nimport pandas as pd \nfrom xgboost import XGBClassifier\nimport matplotlib.pyplot as plt\nimport pandas as pd\n\n\nxgb = XGBClassifier()\nxgb.load_model('ModelName.model')\n\n# After loading model, usage is the same as discussed in the model preparation section.\n

"},{"location":"inference/xgboost.html#caveat","title":"Caveat","text":"

It is worth mentioning that both behavior and APIs of different XGBoost version can have difference.

  1. When using c_api for C/C++ inference, for ver.<1, the API is XGB_DLL int XGBoosterPredict(BoosterHandle handle, DMatrixHandle dmat,int option_mask, int training, bst_ulong * out_len,const float ** out_result), while for ver.>=1 the API changes to XGB_DLL int XGBoosterPredict(BoosterHandle handle, DMatrixHandle dmat,int option_mask, unsigned int ntree_limit, int training, bst_ulong * out_len,const float ** out_result).

  2. Model from ver.>=1 cannot be used for ver.<1.

Other important issue for C/C++ user is that DMatrix only takes in single precision floats (float), not double precision floats (double).

"},{"location":"inference/xgboost.html#appendix-tips-for-xgboost-users","title":"Appendix: Tips for XGBoost users","text":""},{"location":"inference/xgboost.html#importance-plot","title":"Importance Plot","text":"

XGBoost uses F-score to describe feature importance quantatitively. XGBoost's python API provides a nice tool,plot_importance, to plot the feature importance conveniently after finishing train.

# Once the training is done, the plot_importance function can thus be used to plot the feature importance.\nfrom xgboost import plot_importance # Import the function\n\nplot_importance(xgb) # suppose the xgboost object is named \"xgb\"\nplt.savefig(\"importance_plot.pdf\") # plot_importance is based on matplotlib, so the plot can be saved use plt.savefig()\n
The importance plot is consistent with our expectation, as in our toy-model, the data points differ by most on the feature \"7\". (see toy model setup).

"},{"location":"inference/xgboost.html#roc-curve-and-auc","title":"ROC Curve and AUC","text":"

The receiver operating characteristic (ROC) and auccrency (AUC) are key quantities to describe the model performance. For XGBoost, ROC curve and auc score can be easily obtained with the help of sci-kit learn (sklearn) functionals, which is also in CMSSW software.

from sklearn.metrics import roc_auc_score,roc_curve,auc\n# ROC and AUC should be obtained on test set\n# Suppose the ground truth is 'y_test', and the output score is named as 'y_score'\n\nfpr, tpr, _ = roc_curve(y_test, y_score)\nroc_auc = auc(fpr, tpr)\n\nplt.figure()\nlw = 2\nplt.plot(fpr, tpr, color='darkorange',\n         lw=lw, label='ROC curve (area = %0.2f)' % roc_auc)\nplt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')\nplt.xlim([0.0, 1.0])\nplt.ylim([0.0, 1.05])\nplt.xlabel('False Positive Rate')\nplt.ylabel('True Positive Rate')\nplt.title('Receiver operating characteristic example')\nplt.legend(loc=\"lower right\")\n# plt.show() # display the figure when not using jupyter display\nplt.savefig(\"roc.png\") # resulting plot is shown below\n

"},{"location":"inference/xgboost.html#reference-of-xgboost","title":"Reference of XGBoost","text":"
  1. XGBoost Wiki: https://en.wikipedia.org/wiki/XGBoost
  2. XGBoost Github Repo.: https://github.com/dmlc/xgboost
  3. XGBoost offical api tutorial
  4. Latest, Python: https://xgboost.readthedocs.io/en/latest/python/index.html
  5. Latest, C/C++: https://xgboost.readthedocs.io/en/latest/tutorials/c_api_tutorial.html
  6. Older (0.80), Python: https://xgboost.readthedocs.io/en/release_0.80/python/index.html
  7. No Tutorial for older version C/C++ api, source code: https://github.com/dmlc/xgboost/blob/release_0.80/src/c_api/c_api.cc
"},{"location":"innovation/hackathons.html","title":"CMS Machine Learning Hackathons","text":"

Welcome to the CMS ML Hackathons! Here we encourage the exploration of cutting edge ML methods to particle physics problems through multi-day focused work. Form hackathon teams and work together with the ML Innovation group to get support with organization and announcements, hardware/software infrastructure, follow-up meetings and ML-related technical advise.

If you are interested in proposing a hackathon, please send an e-mail to the CMS ML Innovation conveners with a potential topic and we will get in touch!

Below follows a list of previous successful hackathons.

"},{"location":"innovation/hackathons.html#hgcal-ticl-reconstruction","title":"HGCAL TICL reconstruction","text":"

20 Jun 2022 - 24 Jun 2022 https://indico.cern.ch/e/ticlhack

Abstract: The HGCAL reconstruction relies on \u201cThe Iterative CLustering\u201d (TICL) framework. It follows an iterative approach, first clusters energy deposits in the same layer (layer clusters) and then connect these layer clusters to reconstruct the particle shower by forming 3-D objects, the \u201ctracksters\u201d. There are multiple areas that could benefit from advanced ML techniques to further improve the reconstruction performance.

In this project we plan to tackle the following topics using ML:

  • trackster identification (ie, identification of the type of particle initiating the shower) and energy regression linking of tracksters stemming from the same particle to reconstruct the full shower and/or use a high-purity trackster as a seed and collect 2D (ie. layer clusters) and/or 3D (ie, tracksters) energy deposits in the vicinity of the seed trackster to fully reconstruct the particle shower
  • tuning of the existing pattern recognition algorithms
  • reconstruction under HL-LHC pile-up scenarios (eg., PU=150-200)
  • trackster characterization, ie. predict if a trackster is a sound object in itself or determine if it is more likely to be a composite one.
"},{"location":"innovation/hackathons.html#material","title":"Material:","text":"

A CodiMD document has been created with an overview of the topics and to keep track of the activities during the hackathon:

https://codimd.web.cern.ch/s/hMd74Yi7J

"},{"location":"innovation/hackathons.html#jet-tagging","title":"Jet tagging","text":"

8 Nov 2021 - 11 Nov 2021 https://indico.cern.ch/e/jethack

Abstract: The identification of the initial particle (quark, gluon, W/Z boson, etc..) responsible for the formation of the jet, also known as jet tagging, provides a powerful handle in both standard model (SM) measurements and searches for physics beyond the SM (BSM). In this project we propose the development of jet tagging algorithms both for small-radius (i.e. AK4) and large-radius (i.e., AK8) jets using as inputs the PF candidates.

Two main projects are covered:

  • Jet tagging for scouting
  • Jet tagging for Level-1
"},{"location":"innovation/hackathons.html#jet-tagging-for-scouting","title":"Jet tagging for scouting","text":"

Using as inputs the PF candidates and local pixel tracks reconstructed in the scouting streams, the main goals of this project are the following:

Develop a jet-tagging baseline for scouting and compare the performance with the offline reconstruction Understand the importance of the different input variables and the impact of -various configurations (e.g., on pixel track reconstruction) in the performance Compare different jet tagging approaches with mind performance as well as inference time. Proof of concept: ggF H->bb, ggF HH->4b, VBF HH->4b

"},{"location":"innovation/hackathons.html#jet-tagging-for-level-1","title":"Jet tagging for Level-1","text":"

Using as input the newly developed particle flow candidates of Seeded Cone jets in the Level1 Correlator trigger, the following tasks will be worked on:

  • Developing a quark, gluon, b, pileup jet classifier for Seeded Cone R=0.4 jets using a combination of tt,VBF(H) and Drell-Yan Level1 samples
  • Develop tools to demonstrate the gain of such a jet tagging algorithm on a signal sample (like q vs g on VBF jets)
  • Study tagging performance as a function of the number of jet constituents
  • Study tagging performance for a \"real\" input vector (zero-paddes, perhaps unsorted)
  • Optimise jet constituent list of SeededCone Jets (N constituents, zero-removal, sorting etc)
  • Develop q/g/W/Z/t/H classifier for Seeded Cone R=0.8 jets
"},{"location":"innovation/hackathons.html#gnn-4-tracking","title":"GNN-4-tracking","text":"

27 Sept 2021 - 1 Oct 2021

https://indico.cern.ch/e/gnn4tracks

Abstract: The aim of this hackathon is to integrate graph neural nets (GNNs) for particle tracking into CMSSW.

The hackathon will make use of a GNN model reported by the paper Charged particle tracking via edge-classifying interaction networks by Gage DeZoort, Savannah Thais, et.al. They used a GNN to predict connections between detector pixel hits, and achieved accurate track building. They did this with the TrackML dataset, which uses a generic detector designed to be similar to CMS or ATLAS. Work is ongoing to apply this GNN approach to CMS data.

Tasks: The hackathon aims to create a workflow that allows graph building and GNN inference within the framework of CMSSW. This would enable accurate testing of future GNN models and comparison to existing CMSSW track building methods. The hackathon will be divided into the following subtasks:

  • Task 1: Create a package for extracting graph features and building graphs in CMSSW.
  • Task 2. GNN inference on Sonic servers
  • Task 3: Track fitting after GNN track building
  • Task 4. Performance evaluation for the new track collection
"},{"location":"innovation/hackathons.html#material_1","title":"Material:","text":"

Code is provided at this GitHub organisation. Project are listed here.

"},{"location":"innovation/hackathons.html#anomaly-detection","title":"Anomaly detection","text":"

In this four day Machine Learning Hackathon, we will develop new anomaly detection algorithms for New Physics detection, intended for deployment in the two main stages of the CMS data aquisition system: The Level-1 trigger and the High Level Trigger.

There are two main projects:

"},{"location":"innovation/hackathons.html#event-based-anomaly-detection-algorithms-for-the-level-1-trigger","title":"Event-based anomaly detection algorithms for the Level-1 Trigger","text":""},{"location":"innovation/hackathons.html#jet-based-anomaly-detection-algorithms-for-the-high-level-trigger-specifically-targeting-run-3-scouting","title":"Jet-based anomaly detection algorithms for the High Level Trigger, specifically targeting Run 3 scouting","text":""},{"location":"innovation/hackathons.html#material_2","title":"Material:","text":"

A list of projects can be found in this document. Instructions for fetching the data and example code for the two projects can be found at Level-1 Anomaly Detection.

"},{"location":"innovation/journal_club.html","title":"CMS Machine Learning Journal Club","text":"

Welcome to the CMS Machine Learning Journal Club (JC)! Here we read an discuss new cutting edge ML papers, with an emphasis on how these can be used within the collaboration. Below you can find a summary of each JC as well as some code examples demonstrating how to use the tools or methods introduced.

To vote for or to propose new papers for discussion, go to https://cms-ml-journalclub.web.cern.ch/.

Below follows a complete list of all the previous CMS ML JHournal clubs, together with relevant documentation and code examples.

"},{"location":"innovation/journal_club.html#dealing-with-nuisance-parameters-using-machine-learning-in-high-energy-physics-a-review","title":"Dealing with Nuisance Parameters using Machine Learning in High Energy Physics: a Review","text":"

Tommaso Dorigo, Pablo de Castro

Abstract: In this work we discuss the impact of nuisance parameters on the effectiveness of machine learning in high-energy physics problems, and provide a review of techniques that allow to include their effect and reduce their impact in the search for optimal selection criteria and variable transformations. The introduction of nuisance parameters complicates the supervised learning task and its correspondence with the data analysis goal, due to their contribution degrading the model performances in real data, and the necessary addition of uncertainties in the resulting statistical inference. The approaches discussed include nuisance-parameterized models, modified or adversary losses, semi-supervised learning approaches, and inference-aware techniques.

  • Indico
  • Paper
"},{"location":"innovation/journal_club.html#mapping-machine-learned-physics-into-a-human-readable-space","title":"Mapping Machine-Learned Physics into a Human-Readable Space","text":"

Taylor Faucett, Jesse Thaler, Daniel Whiteson

Abstract: We present a technique for translating a black-box machine-learned classifier operating on a high-dimensional input space into a small set of human-interpretable observables that can be combined to make the same classification decisions. We iteratively select these observables from a large space of high-level discriminants by finding those with the highest decision similarity relative to the black box, quantified via a metric we introduce that evaluates the relative ordering of pairs of inputs. Successive iterations focus only on the subset of input pairs that are misordered by the current set of observables. This method enables simplification of the machine-learning strategy, interpretation of the results in terms of well-understood physical concepts, validation of the physical model, and the potential for new insights into the nature of the problem itself. As a demonstration, we apply our approach to the benchmark task of jet classification in collider physics, where a convolutional neural network acting on calorimeter jet images outperforms a set of six well-known jet substructure observables. Our method maps the convolutional neural network into a set of observables called energy flow polynomials, and it closes the performance gap by identifying a class of observables with an interesting physical interpretation that has been previously overlooked in the jet substructure literature. - Indico - Paper

"},{"location":"innovation/journal_club.html#model-interpretability-2-papers","title":"Model Interpretability (2 papers):","text":"
  • Indico
"},{"location":"innovation/journal_club.html#identifying-the-relevant-dependencies-of-the-neural-network-response-on-characteristics-of-the-input-space","title":"Identifying the relevant dependencies of the neural network response on characteristics of the input space","text":"

Stefan Wunsch, Raphael Friese, Roger Wolf, G\u00fcnter Quast

Abstract: The relation between the input and output spaces of neural networks (NNs) is investigated to identify those characteristics of the input space that have a large influence on the output for a given task. For this purpose, the NN function is decomposed into a Taylor expansion in each element of the input space. The Taylor coefficients contain information about the sensitivity of the NN response to the inputs. A metric is introduced that allows for the identification of the characteristics that mostly determine the performance of the NN in solving a given task. Finally, the capability of this metric to analyze the performance of the NN is evaluated based on a task common to data analyses in high-energy particle physics experiments.

  • Paper
"},{"location":"innovation/journal_club.html#innvestigate-neural-networks","title":"iNNvestigate neural networks!","text":"

Maximilian Alber, Sebastian Lapuschkin, Philipp Seegerer, Miriam H\u00e4gele, Kristof T. Sch\u00fctt, Gr\u00e9goire Montavon, Wojciech Samek, Klaus-Robert M\u00fcller, Sven D\u00e4hne, Pieter-Jan Kindermans

In recent years, deep neural networks have revolutionized many application domains of machine learning and are key components of many critical decision or predictive processes. Therefore, it is crucial that domain specialists can understand and analyze actions and pre- dictions, even of the most complex neural network architectures. Despite these arguments neural networks are often treated as black boxes. In the attempt to alleviate this short- coming many analysis methods were proposed, yet the lack of reference implementations often makes a systematic comparison between the methods a major effort. The presented library iNNvestigate addresses this by providing a common interface and out-of-the- box implementation for many analysis methods, including the reference implementation for PatternNet and PatternAttribution as well as for LRP-methods. To demonstrate the versatility of iNNvestigate, we provide an analysis of image classifications for variety of state-of-the-art neural network architectures.

  • Paper
  • Code
"},{"location":"innovation/journal_club.html#simulation-based-inference-in-particle-physics-and-beyond-and-beyond","title":"Simulation-based inference in particle physics and beyond (and beyond)","text":"

Johann Brehmer, Kyle Cranmer

Abstract: Our predictions for particle physics processes are realized in a chain of complex simulators. They allow us to generate high-fidelity simulated data, but they are not well-suited for inference on the theory parameters with observed data. We explain why the likelihood function of high-dimensional LHC data cannot be explicitly evaluated, why this matters for data analysis, and reframe what the field has traditionally done to circumvent this problem. We then review new simulation-based inference methods that let us directly analyze high-dimensional data by combining machine learning techniques and information from the simulator. Initial studies indicate that these techniques have the potential to substantially improve the precision of LHC measurements. Finally, we discuss probabilistic programming, an emerging paradigm that lets us extend inference to the latent process of the simulator.

  • Indico
  • Paper
  • Code
"},{"location":"innovation/journal_club.html#efficiency-parameterization-with-neural-networks","title":"Efficiency Parameterization with Neural Networks","text":"

C. Badiali, F.A. Di Bello, G. Frattari, E. Gross, V. Ippolito, M. Kado, J. Shlomi

Abstract: Multidimensional efficiency maps are commonly used in high energy physics experiments to mitigate the limitations in the generation of large samples of simulated events. Binned multidimensional efficiency maps are however strongly limited by statistics. We propose a neural network approach to learn ratios of local densities to estimate in an optimal fashion efficiencies as a function of a set of parameters. Graph neural network techniques are used to account for the high dimensional correlations between different physics objects in the event. We show in a specific toy model how this method is applicable to produce accurate multidimensional efficiency maps for heavy flavor tagging classifiers in HEP experiments, including for processes on which it was not trained. - Indico - Paper - Code

"},{"location":"innovation/journal_club.html#a-general-framework-for-uncertainty-estimation-in-deep-learning","title":"A General Framework for Uncertainty Estimation in Deep Learning","text":"

Antonio Loquercio, Mattia Seg\u00f9, Davide Scaramuzza

Neural networks predictions are unreliable when the input sample is out of the training distribution or corrupted by noise. Being able to detect such failures automatically is fundamental to integrate deep learning algorithms into robotics. Current approaches for uncertainty estimation of neural networks require changes to the network and optimization process, typically ignore prior knowledge about the data, and tend to make over-simplifying assumptions which underestimate uncertainty. To address these limitations, we propose a novel framework for uncertainty estimation. Based on Bayesian belief networks and Monte-Carlo sampling, our framework not only fully models the different sources of prediction uncertainty, but also incorporates prior data information, e.g. sensor noise. We show theoretically that this gives us the ability to capture uncertainty better than existing methods. In addition, our framework has several desirable properties: (i) it is agnostic to the network architecture and task; (ii) it does not require changes in the optimization process; (iii) it can be applied to already trained architectures. We thoroughly validate the proposed framework through extensive experiments on both computer vision and control tasks, where we outperform previous methods by up to 23% in accuracy.

  • Indico
  • Paper
  • Code
"},{"location":"optimization/data_augmentation.html","title":"Data augmentation","text":""},{"location":"optimization/data_augmentation.html#introduction","title":"Introduction","text":"

This introduction is based on papers by Shorten & Khoshgoftaar, 2019 and Rebuffi et al., 2021 among others

With the increasing complexity and sizes of neural networks one needs huge amounts of data in order to train a state-of-the-art model. However, generating this data is often very resource and time intensive. Thus, one might either augment the existing data with more descriptive variables or combat the data scarcity problem by artificially increasing the size of the dataset by adding new instances without the resource-heavy generation process. Both processes are known in machine learning (ML) applications as data augmentation (DA) methods.

The first type of these methods is more widely known as feature generation or feature engineering and is done on instance level. Feature engineering focuses on crafting informative input features for the algorithm, often inspired or derived from first principles specific to the algorithm's application domain.

The second type of method is done on the dataset level. These types of techniques can generally be divided into two main categories: real data augmentation (RDA) and synthetic data augmentation (SDA). As the name suggests, RDA makes minor changes to the already existing data in order to generate new samples, whereas SDA generates new data from scratch. Examples of RDA include rotating (especially useful if we expect the event to be rotationally symmetric) and zooming, among a plethora of other methods detailed in this overview article. Examples of SDA include traditional sampling methods and more complex generative models like Generative Adversaial Netoworks (GANs) and Variational Autoencoders (VAE). Going further, the generative methods used for synthetic data augmentation could also be used in fast simulation, which is a notable bottleneck in the overall physics analysis workflow.

Dataset augmentation may lead to more successful algorithm outcomes. For example, introducing noise into data to form additional data points improves the learning ability of several models which otherwise performed relatively poorly, as shown by Freer & Yang, 2020. This finding implies that this form of DA creates variations that the model may see in the real world. If done right, preprocessing the data with DA will result in superior training outcomes. This improvement in performance is due to the fact that DA methods act as a regularizer, reducing overfitting during training. In addition to simulating real-world variations, DA methods can also even out categorical data with imbalanced classes.

Fig. 1: Generic pipeline of a heuristic DA (figure taken from Li, 2020)

Before diving more in depth into the various DA methods and applications in HEP, here is a list of the most notable benefits of using DA methods in your ML workflow:

  • Improvement of model prediction precision
  • More training data for the model
  • Preventing data scarcity for state-of-the-art models
  • Reduction of over overfitting and creation of data variability
  • Increased model generalization properties
  • Help in resolving class imbalance problems in datasets
  • Reduced cost of data collection and labeling
  • Enabling rare event prediction

And some words of caution:

  • There is no 'one size fits all' in DA. Each dataset and usecase should be considered separately.
  • Don't trust the augmented data blindly
  • Make sure that the augmented data is representative of the problem at hand, otherwise it will negatively affect the model performance.
  • There must be no unnecessary duplication of existing data, only by adding unique information we gain more insights.
  • Ensure the validity of the augmented data before using it in ML models.
  • If a real dataset contains biases, data augmented from it will contain biases, too. So, identification of optimal data augmentation strategy is important. So, double check your DA strategy.
"},{"location":"optimization/data_augmentation.html#feature-engineering","title":"Feature Engineering","text":"

This part is based mostly on Erdmann et al., 2018

Feature engineering (FE) is one of the key components of a machine learning workflow. This process transforms and augments training data with additional features in order to make the training more effective.

With multi-variate analyeses (MVAs), such boosted decision trees (BDTs) and neural networks, one could start with raw, \"low-level\" features, like four-momenta, and the algorithm can learn higher level patterns, correlations, metrics, etc. However, using \"high-level\" variables, in many cases, leads to outcomes superior to the use of low-level variables. As such, features used in MVAs are handcrafted from physics first principles.

Still, it is shown that a deep neural network (DNN) can perform better if it is trained with both specifically constructed variables and low-level variables. This observation suggests that the network extracts additional information from the training data.

"},{"location":"optimization/data_augmentation.html#hep-application-lorentz-boosted-network","title":"HEP Application - Lorentz Boosted Network","text":"

For the purposeses of FE in HEP, a novel ML architecture called a Lorentz Boost Network (LBN) (see Fig. 2) was proposed and implemented by Erdmann et al., 2018. It is a multipurpose method that uses Lorentz transformations to exploit and uncover structures in particle collision events. LBN is the first stage of a two-stage neural network (NN) model, that enables a fully autonomous and comprehensive characterization of collision events by exploiting exclusively the four-momenta of the final-state particles.

Within LBN, particles are combined to create rest frames representions, which enables the formation of further composite particles. These combinations are realized via linear combinations of N input four-vectors to a number of M particles and rest frames. Subsequently these composite particles are then transformed into said rest frames by Lorentz transformations in an efficient and fully vectorized implementation.

The properties of the composite, transformed particles are compiled in the form of characteristic variables like masses, angles, etc. that serve as input for a subsequent network - the second stage, which has to be configured for a specific analysis task, like classification.

The authors observed leading performance with the LBN and demonstrated that LBN forms physically meaningful particle combinations and generates suitable characteristic variables.

The usual ML workflow, employing LBN, is as follows:

Step-1: LBN(M, F)\n\n    1.0: Input hyperparameters: number of combinations M; number of features F\n    1.0: Choose: number of incoming particles, N, according to the research\n         question\n\n    1.1: Combination of input four-vectors to particles and rest frames\n\n    1.2: Lorentz transformations\n\n    1.3 Extraction of suitable high-level objects\n\n\nStep-2: NN\n\n    2.X: Train some form of a NN using an objective function that depends on\n         the analysis / research question.\n
Fig. 2: The Lorentz Boost Network architecture (figure taken from Erdmann et al., 2018)

The LBN package is also pip-installable:

pip install lbn\n
"},{"location":"optimization/data_augmentation.html#rda-techniques","title":"RDA Techniques","text":"

This section and the following subsection are based on the papers by Freer & Yang, 2020, Dolan & Ore, 2021, Barnard et al., 2016, and Bradshaw et al., 2019

RDA methods augment the existing dataset by performance some transformation on the existing data points. These transformations could include rotation, flipping, color shift (for an image), Fourier transforming (for signal processing) or some other transformation that preserves the validity of the data point and its corresponding label. As mentioned in Freer & Yang, 2020, these types of transformations augment the dataset to capture potential variations that the population of data may exhibit, allowing the network to capture a more generalized view of the sampled data.

"},{"location":"optimization/data_augmentation.html#hep-application-zooming","title":"HEP Application - Zooming","text":"

In Barnard et al., 2016, the authors investigate the effect of parton shower modelling in DNN jet taggers using images of hadronically decaying W bosons. They introduce a method known as zooming to study the scale invariance of these networks. This is the RDA strategy used by Dolan & Ore, 2021. Zooming is similar to a normalization procedure such that it standardizes features in signal data, but it aims to not create similar features in background.

After some standard data processing steps, including jet trimming and clustering via the \\(k_t\\) algorithm, and some further processing to remove spatial symmetries, the resulting jet image depicts the leading subjet and subleading subjet directly below. Barnard et al., 2016 notes that the separation between the leading and subleading subjets varies linearly as \\(2m/p_T\\) where \\(m\\) and \\(p_T\\) are the mass and transverse momentum of the jet. Standardizing this separation, or removing the linear dependence, would allow the DNN tagger to generalize to a wide range of jet \\(p_T\\). To this end, the authors construct a factor, \\(R/\\DeltaR_{act}\\), where \\(R\\) is some fixed value and \\(\\DeltaR_{act}\\) is the separation between the leading and subleading subjets. To discriminate between signal and background images with this factor, the authors enlarge the jet images by a scaling factor of \\(\\text{max}(R/s,1)\\) where \\(s = 2m_W/p_T\\) and \\(R\\) is the original jet clustering size. This process of jet image enlargement by a linear mass and \\(p_T\\) dependent factor to account for the distane between the leading and subleading jet is known as zooming. This process can be thought of as an RDA technique to augment the data in a domain-specific way.

Advantage of using the zooming technique is that it makes the construction of scale invariant taggers easier. Scale invariant searches which are able to interpolate between the boosted and resolved parts of phase space have the advantage of being applicable over a broad range of masses and kinematics, allowing a single search or analysis to be effective where previously more than one may have been necessary.

As predicted the zoomed network outperforms the unzoomed one, particularly at low signal efficiency, where the background rejection rises by around 20%. Zooming has the greatest effect at high pT.

"},{"location":"optimization/data_augmentation.html#traditional-sda-techniques","title":"Traditional SDA Techniques","text":"

Text in part based on He et al., 2010

Generally speaking, imbalanced learning occurs whenever some type of data distribution dominates the instance space compared to other data distributions. Methods for handling imbalanced learning problems can be divided into the following five major categories:

  • Sampling strategies
  • Synthetic data generation (SMOTE & ADASYN & DataBoost-IM) - aims to overcome the imbalance by artificially generating data samples.
  • Cost-sensitive learning - uses cost-matrix for different types of errors or instance to facilitate learning from imbalanced data sets. This means that cost-sensitive learning does not modify the imbalanced data distribution directly, but targets this problem by using different cost-matrices that describe the cost for misclassifying any particular data sample.
  • Active learning - conventionally used to solve problems related to unlabeled data, though recently it has been used in learning imbalanced data sets. Instead of searching the entire training space, this method effectively selects informative instances from a random set of training populations, therefore significantly reducing the computational cost when dealing with large imbalanced data sets.
  • Kernel-based methods - by integrating the regularized orthogonal weighed least squares (ROWLS) estimator, a kernel classifier construction algorithm is based on orthogonal forward selection (OFS) to optimize the model generalization for learning from two-class imbalanced data sets.
"},{"location":"optimization/data_augmentation.html#sampling","title":"Sampling","text":"

When the percentage of the minority class is less than 5%, it can be considered a rare event. When a dataset is imbalanced or when a rare event occurs, it will be difficult to get a meaningful and good predictive model due to lack of information about the rare event Au et al., 2010. In these cases, re-sampling techniques can be helpful. The re-sampling techniques are implemented in four different categories: undersampling the majority class, oversampling the minority class, combining over- and undersampling, and ensembling sampling. Oversampling and undersampling are found to work well in improving the classification for the imbalanced dataset. Yap et al., 2013

Stratified sampling (STS) This technique is used in cases where the data can be partitioned into strata (subpopulations), where each strata should be collectively exhaustive and mutually exclusive. The process of dividing the data into homogeneus subgroups before sampling is referred to as stratification. The two common strategies of STS are proportionate allocation (PA) and optimum (disproportionate) allocation (OA). The former uses a fraction in each of the stata that is proportional to that of the total population. The latter uses the standard deviation of the distribution of the variable as well, so that the larger samples are taken from the strata that has the greatest variability to generate the least possible sampling variance. The advantages of using STS include smaller error in estimation (if measurements within strata have lower standard deviation) and similarity in uncertainties across all strata in case there is high variability in a given strata.

NOTE: STS is only useful if the population can be exhaustively partitioned into subgroups. Also in case of unknown class priors (the ratio of strata to the whole population) might have deleterious effects on the classification performance.

Over- and undersampling Oversampling randomly duplicates minority class samples, while undersampling discards majority class samples in order to modify the class distribution. While oversampling might lead to overfitting, since it makes exact copies of the minority samples, undersampling may discard potentially useful majority samples.

Oversampling and undersampling are essentially opposite and roughly equivalent techniques. There are also more complex oversampling techniques, including the creation of artificial data points with algorithms like Synthetic Minority Over-sampling TEchnique (SMOTE).

It has been shown that the combination of SMOTE and undersampling performs better than only undersampling the majority class. However, over- and undersampling remain popular as it each is much easier to implement alone than in some complex hybrid approach.

Synthetic Minority Over-sampling Technique (SMOTE) Text mostly based on Chawla et al., 2002 and in part on He et al., 2010

In case of Synthetic Minority Over-sampling Technique (SMOTE), the minority class is oversampled by creating synthetic examples along the line segments joining any or all of the \\(k\\)-nearest neighbours in the minority class. The synthetic examples cause the classifier to create larger and less specific decision regions, rather than smaller and more specific regions. More general regions are now learned for the minority class samples rather than those being subsumed by the majority class samples around them. In this way SMOTE shifts the classifier learning bias toward the minority class and thus has the effect of allowing the model to generalize better.

There also exist extensions of this work like SMOTE-Boost in which the syntetic procedure was integrated with adaptive boosting techniques to change the method of updating weights to better compensate for skewed distributions.

So in general SMOTE proceeds as follows

SMOTE(N, X, k)\nInput: N - Number of synthetic samples to be generated\n       X - Underrepresented data\n       k - Hyperparameter of number of nearest neighbours to be chosen\n\nCreate an empty list SYNTHETIC_SAMPLES\nWhile N_SYNTHETIC_SAMPLES < N\n    1. Randomly choose an entry xRand from X\n    2. Find k nearest neighbours from X\n    3. Randomly choose an entry xNeighbour from the k nearest neighbours\n    4. Take difference dx between the xRand and xNeighbour\n    5. Multiply dx by a random number between 0 and 1\n    6. Append the result to SYNTHETIC_SAMPLES\nExtend X by SYNTHETIC_SAMPLES\n

Adaptive synthetic sampling approach (ADASYN) Text mostly based on He et al., 2010

Adaptive synthetic sampling approach (ADASYN) is a sampling approach for learning from imbalanced datasets. The main idea is to use a weighted distribution for different minority class examples according to their level of difficulty in learning, where more synthetic data is generated for minority class examples that are harder to learn compared to those minority examples that are easier to learn. Thus, ADASYN improves learning with respect to the data distributions by reducing the bias introduced by the class imbalance and by adaptively shifting the classification boundary toward the difficult examples.

The objectives of ADASYN are reducing bias and learning adaptively. The key idea of this algorithm is to use a density distribution as a criterion to decide the number of synthetic samples that need to be generated for each minority data example. Physically, this density distribution is a distribution of weights for different minority class examples according to their level of difficulty in learning. The resulting dataset after using ADASYN will not only provide a balanced representation of the data distribution (according to the desired balance level defined in the configuration), but it also forces the learning algorithm to focus on those difficult to learn examples. It has been shown He et al., 2010, that this algorithm improves accuracy for both minority and majority classes and does not sacrifice one class in preference for another.

ADASYN is not limited to only two-class learning, but can also be generalized to multiple-class imbalanced learning problems as well as incremental learning applications.

For more details and comparisons of ADASYN to other algorithms, please see He et al., 2010.

"},{"location":"optimization/data_augmentation.html#existing-implementations","title":"Existing implementations","text":"

Imbalanced-learn is an open-source Python library which provides a suite of algorithms for treating the class imbalance problem.

For augmentig image data, one can use of of the following:

  • Albumentations
  • ImgAug
  • Autoaugment
  • Augmentor
  • DeepAugmnent

But it is also possible to use tools directly implemented by tensorflow, keras etc. For example:

flipped_image = tf.image.flip_left_right(image)\n
"},{"location":"optimization/data_augmentation.html#deep-learning-based-sda-techniques","title":"Deep Learning-based SDA Techniques","text":"

In data science, data augmentation techniques are used to increase the amount of data by either synthetically creating data from already existing samples via a GAN or modifying the data at hand with small noise or rotation. (Rebuffi et al., 2021)

More recently, data augmentation studies have begun to focus on the field of deep learning (DL), more specifically on the ability of generative models, like Generative Adversarial Networks (GANs) and Variational Autoencoders (VAEs), to create artificial data. This synthetic data is then introduced during the classification model training process to improve performance and results.

Generative Adversarial Networks (GANs) The following text is written based on the works by Musella & Pandolfi, 2018 and Hashemi et al., 2019 and Kansal et al., 2022 and Rehm et al., 2021 and Choi & Lim, 2021 and Kansal et al., 2020

GANs have been proposed as a fast and accurate way of modeling high energy jet formation (Paganini et al., 2017a) and modeling showers throughcalorimeters of high-energy physics experiments (Paganini et al., 2017 ; Paganini et al., 2012; Erdman et al., 2020; Musella & Pandolfi, 2018) GANs have also been trained to accurately approximate bottlenecks in computationally expensive simulations of particle physics experiments. Applications in the context of present and proposed CERN experiments have demonstrated the potential of these methods for accelerating simulation and/or improving simulation fidelity (ATLAS Collaboration, 2018; SHiP Collaboration, 2019).

The generative model approximates the combined response of aparticle detecor simulation and reconstruction algorithms to hadronic jets given the latent space of uniformly distributed noise, auxiliary features and jet image at particle level (jets clustered from the list of stable particles produced by PYTHIA).

In the paper by Musella & Pandolfi, 2018, the authors apply generative models parametrized by neural networks (GANs in particular) to the simulation of particles-detector response to hadronic jets. They show that this parametrization achieves high-fidelity while increasing the processing speed by several orders of magnitude.

Their model is trained to be capable of predicting the combined effect of particle-detector simulation models and reconstruction algorithms to hadronic jets.

Generative adversarial networks (GANs) are pairs of neural networks, a generative and a discriminative one, that are trained concurrently as players of a minimax game (Musella & Pandolfi, 2018). The task of the generative network is to produce, starting from a latent space with a fixed distribution, samples that the discriminative model tries to distinguish from samples drawn from a target dataset. This kind of setup allows the distribution of the target dataset to be learned, provided that both of the networks have high enough capacity.

The input to these networks are hadronic jets, represented as \"gray-scale\" images of fixed size centered around the jet axis, with the pixel intensity corresponding to the energy fraction in a given cell. The architectures of the networks are based on the image-to-image translation. There few differences between this approach and image-to-image translation. Firstly, non-empty pixels are explicitly modelled in the generated images since these are much sparser than the natural ones. Secondly, feature matching and a dedicated adversarial classifier enforce good modelling of the total pixel intensity (energy). Lastly, the generator is conditioned on some auxiliary inputs.

By predicting directly the objects used at analysis level and thus reproducing the output of both detector simulation and reconstruction algorithms, computation time is reduced. This kind of philosophy is very similar to parametrized detector simulations, which are used in HEP for phenomenological studies. The attained accuracies are comparable to the full simulation and reconstruction chain.

"},{"location":"optimization/data_augmentation.html#variational-autoencoders-vaes","title":"Variational autoencoders (VAEs)","text":"

The following section is partly based on Otten et al., 2021

In contrast to the traditional autoencoder (AE) that outputs a single value for each encoding dimension, variational autoencoders (VAEs) provide a probabilistic interpretation for describing an observation in latent space.

In case of VAEs, the encoder model is sometimes referred to as the recognition model and the decoder model as generative model.

By constructing the encoder model to output a distribution of the values from which we randomly sample to feed into our decoder model, we are enforcing a continuous, smooth latent space representation. Thus we expect our decoder model to be able to accurately reconstruct the input for any sampling of the latent distributions, which then means that values residing close to each other in latent space should have very similar reconstructions.

"},{"location":"optimization/data_augmentation.html#ml-powered-data-generation-for-fast-simulation","title":"ML-powered Data Generation for Fast Simulation","text":"

The following text is based on this Chen et al., 2020

We rely on accurate simulation of physics processes, however currently it is very common for LHC physics to be affected by large systematic uncertanties due to the limited amount of simulated data, especially for precise measurements of SM processes for which large datasets are already available. So far the most widely used simulator is GEANT4 that provides state-of-the-art accuracy. But running this is demanding, both in terms of time and resources. Consequently, delivering synthetic data at the pace at which LHC delivers real data is one of the most challenging tasks for computing infrastructures of the LHC experiments. The typical time it takes to simulate one single event is in the ballpark of 100 seconds.

Recently, generative algorithms based on deep learning have been proposed as a possible solution to speed up GEANT4. However, one needs to work beyond the collision-as-image paradigm so that the DL-based simulation accounts for the irregular geometry of a typical detector while delivering a dataset in a format compatible with downstream reconstruction software.

One method to solve this bottleneck was proposed by Chen et al., 2020. They adopt a generative DL model to convert an analysis specific representation of collision events at generator level to the corresponding representation at reconstruction level. Thus, this novel, fast-simulation workflow starts from a large amount of generator-level events to deliver large analysis-specific samples.

They trained a neural network to model detector resolution effects as a transfer function acting on an analysis-specific set of relevant features, computed at generator level. However, their model does not sample events from a latent space (like a GAN or a plain VAE). Instead, it works as a fast simulator of a given generator-level event, preserving the correspondence between the reconstructed and the generated event, which allows us to compare event-by-event residual distributions. Furthermore, this model is much simpler than a generative model.

Step one in this workflow is generating events in their full format, which is the most resource heavy task, where, as noted before, generating one event takes roughly 100 seconds. However, with this new proposed method O(1000) events are generated per second. This would save on storage: for the full format O(1) MB/event is needed, where for the DL model only 8 MB was used to store 100000 events. To train the model, they used NVIDIA RTX2080 and it trained for 30 minutes, which in terms of overall production time is negligible. For generating N=1M events and n=10%N, one would save 90% of the CPU resources and 79% of the disk storage. Thus augmenting the centrally produced data is a viable method and could help the HEP community to face the computing challenges of the High-Luminosity LHC.

Another more extreme approach investigated the use of GANs and VAEs for generating physics quantities which are relevant to a specific analysis. In this case, one learns the N-dimensional density function of the event, in a space defined by the quantities of interest for a given analysis. So sampling from this function, one can generate new data. Trade-off between statistical precision (decreases with the increasing amount of generated events) and the systematic uncertainty that could be induced by a non accurate description of the n-dim pdf.

Qualitatively, no accuracy deterioration was observed due to scaling the dataset size for DL. This fact proves the robustness of the proposed methodology and its effectiveness for data augmentation.

"},{"location":"optimization/data_augmentation.html#open-challenges-in-data-augmentation","title":"Open challenges in Data Augmentation","text":"

Excerpts are taken from Li, 2020

The limitations of conventional data augmentation approaches reveal huge opportunities for research advances. Below we summarize a few challenges that motivate some of the works in the area of data augmentation.

  • From manual to automated search algorithms: As opposed to performing suboptimal manual search, how can we design learnable algorithms to find augmentation strategies that can outperform human-designed heuristics?
  • From practical to theoretical understanding: Despite the rapid progress of creating various augmentation approaches pragmatically, understanding their benefits remains a mystery because of a lack of analytic tools. How can we theoretically understand various data augmentations used in practice?
  • From coarse-grained to fine-grained model quality assurance: While most existing data augmentation approaches focus on improving the overall performance of a model, it is often imperative to have a finer-grained perspective on critical subpopulations of data. When a model exhibits inconsistent predictions on important subgroups of data, how can we exploit data augmentations to mitigate the performance gap in a prescribed way?
"},{"location":"optimization/data_augmentation.html#references","title":"References","text":"
  • Shorten & Khoshgoftaar, 2019, \"A survey on Image Data Augmentationfor Deep Learning\"
  • Freer & Yang, 2020, \"Data augmentation for self-paced motor imagery classification with C-LSTM\"
  • Li, 2020, \"Automating Data Augmentation: Practice, Theory and New Direction\"
  • Rebuffi et al., 2021, \"Data Augmentation Can Improve Robustness\"
  • Erdmann et al., 2018, \"Lorentz Boost Networks: Autonomous Physics-Inspired Feature Engineering\"
  • Dolan & Ore, 2021, \"Meta-learning and data augmentation for mass-generalised jet taggers\"
  • Bradshaw et al., 2019, \"Mass agnostic jet taggers\"
  • Chang et al., 2018, \"What is the Machine Learning?\"
  • Oliveira et al. 2017, \"Jet-Images \u2013 Deep Learning Edition\"
  • Barnard et al., 2016, \"Parton Shower Uncertainties in Jet Substructure Analyses with Deep Neural Networks\"
  • Chen et al., 2020, \"Data augmentation at the LHC through analysis-specific fast simulation with deep learning\"
  • Musella & Pandolfi, 2018, \"Fast and accurate simulation of particle detectors using generative adversarial networks\"
  • Hashemi et al., 2019, \"LHC analysis-specific datasets with Generative Adversarial Networks\"
  • Kansal et al., 2022, \"Particle Cloud Generation with Message Passing Generative Adversarial Networks\"
  • Rehm et al., 2021, \"Reduced Precision Strategies for Deep Learning: A High Energy Physics Generative Adversarial Network Use Case\"
  • Choi & Lim, 2021, \"A Data-driven Event Generator for Hadron Colliders using Wasserstein Generative Adversarial Network\"
  • Kansal et al., 2020, \"Graph Generative Adversarial Networks for Sparse Data Generation in High Energy Physics\"
  • Otten et al., 2021, \"Event Generation and Statistical Sampling for Physics with Deep Generative Models and a Density Information Buffer\"
  • Yap et al., 2013, \"An Application of Oversampling, Undersampling, Bagging and Boosting in Handling Imbalanced Datasets\"
  • Au et al., 2010, \"Mining Rare Events Data by Sampling and Boosting: A Case Study\"
  • Chawla et al., 2002, \"SMOTE: Synthetic Minority Over-sampling Technique\"
  • He et al., 2010, \"ADASYN: Adaptive Synthetic Sampling Approach for Imbalanced Learning\"
  • Erdman et al., 2020, \"Precise simulation of electromagnetic calorimeter showers using a Wasserstein Generative Adversarial Network\"
  • Paganini et al., 2012, \"CaloGAN: Simulating 3D High Energy Particle Showers in Multi-Layer Electromagnetic Calorimeters with Generative Adversarial Networks\"
  • Paganini et al., 2017, \"Accelerating Science with Generative Adversarial Networks: An Application to 3D Particle Showers in Multi-Layer Calorimeters\"
  • Paganini et al., 2017, \"Learning Particle Physics by Example: Location-Aware Generative Adversarial Networks for Physics Synthesis\"
  • ATLAS Collaboration, 2018, \"Deep generative models for fast shower simulation in ATLAS\"
  • SHiP Collaboration, 2019, \"Fast simulation of muons produced at the SHiP experiment using Generative Adversarial Networks\"

Content may be edited and published elsewhere by the author.

Page author: Laurits Tani, 2022

"},{"location":"optimization/importance.html","title":"Feature Importance","text":"

Feature importance is the impact a specific input field has on a prediction model's output. In general, these impacts can range from no impact (i.e. a feature with no variance) to perfect correlation with the ouput. There are several reasons to consider feature importance:

  • Important features can be used to create simplified models, e.g. to mitigate overfitting.
  • Using only important features can reduce the latency and memory requirements of the model.
  • The relative importance of a set of features can yield insight into the nature of an otherwise opaque model (improved interpretability).
  • If a model is sensitive to noise, rejecting irrelevant inputs may improve its performance.

In the following subsections, we detail several strategies for evaluating feature importance. We begin with a general discussion of feature importance at a high level before offering a code-based tutorial on some common techniques. We conclude with additional notes and comments in the last section.

"},{"location":"optimization/importance.html#general-discussion","title":"General Discussion","text":"

Most feature importance methods fall into one of three broad categories: filter methods, embedding methods, and wrapper methods. Here we give a brief overview of each category with relevant examples:

"},{"location":"optimization/importance.html#filter-methods","title":"Filter Methods","text":"

Filter methods do not rely on a specific model, instead considering features in the context of a given dataset. In this way, they may be considered to be pre-processing steps. In many cases, the goal of feature filtering is to reduce high dimensional data. However, these methods are also applicable to data exploration, wherein an analyst simply seeks to learn about a dataset without actually removing any features. This knowledge may help interpret the performance of a downstream predictive model. Relevant examples include,

  • Domain Knowledge: Perhaps the most obvious strategy is to select features relevant to the domain of interest.

  • Variance Thresholding: One basic filtering strategy is to simply remove features with low variance. In the extreme case, features with zero variance do not vary from example to example, and will therefore have no impact on the model's final prediction. Likewise, features with variance below a given threshold may not affect a model's downstream performance.

  • Fisher Scoring: Fisher scoring can be used to rank features; the analyst would then select the highest scoring features as inputs to a subsequent model.

  • Correlations: Correlated features introduce a certain degree of redundancy to a dataset, so reducing the number of strongly correlated variables may not impact a model's downstream performance.

"},{"location":"optimization/importance.html#embedded-methods","title":"Embedded Methods","text":"

Embedded methods are specific to a prediction model and independent of the dataset. Examples:

  • L1 Regularization (LASSO): L1 regularization directly penalizes large model weights. In the context of linear regression, for example, this amounts to enforcing sparsity in the output prediction; weights corresponding to less relevant features will be driven to 0, nullifying the feature's effect on the output.
"},{"location":"optimization/importance.html#wrapper-methods","title":"Wrapper Methods","text":"

Wrapper methods iterate on prediction models in the context of a given dataset. In general they may be computationally expensive when compared to filter methods. Examples:

  • Permutation Importance: Direct interpretation isn't always feasible, so other methods have been developed to inspect a feature's importance. One common and broadly-applicable method is to randomly shuffle a given feature's input values and test the degredation of model performance. This process allows us to measure permutation importance as follows. First, fit a model (\\(f\\)) to training data, yielding \\(f(X_\\mathrm{train})\\), where \\(X_\\mathrm{train}\\in\\mathbb{R}^{n\\times d}\\) for \\(n\\) input examples with \\(d\\) features. Next, measure the model's performance on testing data for some loss \\(\\mathcal{L}\\), i.e. \\(s=\\mathcal{L}\\big(f(X_\\mathrm{test}), y_\\mathrm{test}\\big)\\). For each feature \\(j\\in[1\\ ..\\ d]\\), randomly shuffle the corresponding column in \\(X_\\mathrm{test}\\) to form \\(X_\\mathrm{test}^{(j)}\\). Repeat this process \\(K\\) times, so that for \\(k\\in [1\\ ..\\ K]\\) each random shuffling of feature column \\(j\\) gives a corrupted input dataset \\(X_\\mathrm{test}^{(j,k)}\\). Finally, define the permutation importance of feature \\(j\\) as the difference between the un-corrupted validation score and average validation score over the corrupted \\(X_\\mathrm{test}^{(j,k)}\\) datasets:
\\[\\texttt{PI}_j = s - \\frac{1}{K}\\sum_{k=1}^{K} \\mathcal{L}[f(X_\\mathrm{test}^{(j,k)}), y_\\mathrm{test}]\\]
  • Recursive Feature Elimination (RFE): Given a prediction model and test/train dataset splits with \\(D\\) initial features, RFE returns the set of \\(d < D\\) features that maximize model performance. First, the model is trained on the full set of features. The importance of each feature is ranked depending on the model type (e.g. for regression, the slopes are a sufficient ranking measure; permutation importance may also be used). The least important feature is rejected and the model is retrained. This process is repeated until the most significant \\(d\\) features remain.
"},{"location":"optimization/importance.html#introduction-by-example","title":"Introduction by Example","text":""},{"location":"optimization/importance.html#direct-interpretation","title":"Direct Interpretation","text":"

Linear regression is particularly interpretable because the prediction coefficients themselves can be interpreted as a measure of feature importance. Here we will compare this direct interpretation to several model inspection techniques. In the following examples we use the Diabetes Dataset available as a Scikit-learn toy dataset. This dataset maps 10 biological markers to a 1-dimensional quantitative measure of diabetes progression:

from sklearn.datasets import load_diabetes\nfrom sklearn.model_selection import train_test_split\n\ndiabetes = load_diabetes()\nX_train, X_val, y_train, y_val = train_test_split(diabetes.data, diabetes.target, random_state=0)\nprint(X_train.shape)\n>>> (331,10)\nprint(y_train.shape)\n>>> (331,)\nprint(X_val.shape)\n>>> (111, 10)\nprint(y_val.shape)\n>>> (111,)\nprint(diabetes.feature_names)\n['age', 'sex', 'bmi', 'bp', 's1', 's2', 's3', 's4', 's5', 's6']\n
To begin, let's use Ridge Regression (L2-regularized linear regression) to model diabetes progression as a function of the input markers. The absolute value of a regression coefficient (slope) corresponding to a feature can be interpreted the impact of a feature on the final fit:

from sklearn.linear_model import Ridge\nfrom sklearn.feature_selection import RFE\n\nmodel = Ridge(alpha=1e-2).fit(X_train, y_train)\nprint(f'Initial model score: {model.score(X_val, y_val):.3f}')\n\nfor i in np.argsort(-abs(model.coef_)):\n    print(diabetes.feature_names[i], abs(model.coef_[i]))\n\n>>> Initial model score: 0.357\n>>> bmi: 592.253\n>>> s5: 580.078\n>>> bp: 297.258\n>>> s1: 252.425\n>>> sex: 203.436\n>>> s3: 145.196\n>>> s4: 97.033\n>>> age: 39.103\n>>> s6: 32.945\n>>> s2: 20.906\n
These results indicate that the bmi and s5 fields have the largest impact on the output of this regression model, while age, s6, and s2 have the smallest. Further interpretation is subject to the nature of the input data (see Common Pitfalls in the Interpretation of Coefficients of Linear Models). Note that scikit-learn has tools available to faciliate feature selections.

"},{"location":"optimization/importance.html#permutation-importance","title":"Permutation Importance","text":"

In the context of our ridge regression example, we can calculate the permutation importance of each feature as follows (based on scikit-learn docs):

from sklearn.inspection import permutation_importance\n\nmodel = Ridge(alpha=1e-2).fit(X_train, y_train)\nprint(f'Initial model score: {model.score(X_val, y_val):.3f}')\n\nr = permutation_importance(model, X_val, y_val, n_repeats=30, random_state=0)\nfor i in r.importances_mean.argsort()[::-1]:\n    print(f\"{diabetes.feature_names[i]:<8}\"\n          f\"{r.importances_mean[i]:.3f}\"\n          f\" +/- {r.importances_std[i]:.3f}\")\n\n>>> Initial model score: 0.357\n>>> s5      0.204 +/- 0.050\n>>> bmi     0.176 +/- 0.048\n>>> bp      0.088 +/- 0.033\n>>> sex     0.056 +/- 0.023\n>>> s1      0.042 +/- 0.031\n>>> s4      0.003 +/- 0.008\n>>> s6      0.003 +/- 0.003\n>>> s3      0.002 +/- 0.013\n>>> s2      0.002 +/- 0.003\n>>> age     -0.002 +/- 0.004\n
These results are roughly consistent with the direct interpretation of the linear regression parameters; s5 and bmi are the most permutation-important features. This is because both have significant permutation importance scores (0.204, 0.176) when compared to the initial model score (0.357), meaning their random permutations significantly degraded the model perforamnce. On the other hand, s2 and age have approximately no permutation importance, meaning that the model's performance was robust to random permutations of these features.

"},{"location":"optimization/importance.html#l1-enforced-sparsity","title":"L1-Enforced Sparsity","text":"

In some applications it may be useful to reject features with low importance. Models biased towards sparsity are one way to achieve this goal, as they are designed to ignore a subset of features with the least impact on the model's output. In the context of linear regression, sparsity can be enforced by imposing L1 regularization on the regression coefficients (LASSO regression):

\\[\\mathcal{L}_\\mathrm{LASSO} = \\frac{1}{2n}||y - Xw||^2_2 + \\alpha||w||_1\\]

Depending on the strength of the regularization \\((\\alpha)\\), this loss function is biased to zero-out features of low importance. In our diabetes regression example,

model = Lasso(alpha=1e-1).fit(X_train, y_train)\nprint(f'Model score: {model.score(X_val, y_val):.3f}')\n\nfor i in np.argsort(-abs(model.coef_)):\n    print(f'{diabetes.feature_names[i]}: {abs(model.coef_[i]):.3f}')\n\n>>> Model score: 0.355\n>>> bmi: 592.203\n>>> s5: 507.363\n>>> bp: 240.124\n>>> s3: 219.104\n>>> sex: 129.784\n>>> s2: 47.628\n>>> s1: 41.641\n>>> age: 0.000\n>>> s4: 0.000\n>>> s6: 0.000\n
For this value of \\(\\alpha\\), we see that the model has rejected the age, s4, and s6 features as unimportant (consistent with the permutation importance measures above) while achieving a similar model score as the previous ridge regression strategy.

"},{"location":"optimization/importance.html#recursive-feature-elimination","title":"Recursive Feature Elimination","text":"

Another common strategy is recursive feature elimination (RFE). Though RFE can be used for regression applications as well, we turn our attention to a classification task for the sake of variety. The following discussions are based on the Breast Cancer Wisconsin Diagnostic Dataset, which maps 30 numeric features corresponding to digitized breast mass images to a binary classification of benign or malignant.

from sklearn.datasets import load_breast_cancer\nfrom sklearn.svm import SVC\nfrom sklearn.model_selection import StratifiedKFold\n\ndata = load_breast_cancer()\nX_train, X_val, y_train, y_val = train_test_split(data.data, data.target, random_state=0)\nprint(X_train.shape)\n>>> (426, 30)\nprint(y_train.shape)\n>>> (426,)\nprint(X_val.shape)\n>>> (143, 30)\nprint(y_val.shape)\n>>> (143,)\nprint(breast_cancer.feature_names)\n>>> ['mean radius' 'mean texture' 'mean perimeter' 'mean area' 'mean smoothness' 'mean compactness' 'mean concavity' 'mean concave points' 'mean symmetry' 'mean fractal dimension' 'radius error' 'texture error' 'perimeter error' 'area error' 'smoothness error' 'compactness error' 'concavity error' 'concave points error' 'symmetry error' 'fractal dimension error' 'worst radius' 'worst texture' 'worst perimeter' 'worst area' 'worst smoothness' 'worst compactness' 'worst concavity' 'worst concave points' 'worst symmetry' 'worst fractal dimension']\n

Given a classifier and a classification task, recursive feature elimination (RFE, see original paper) is the process of identifying the subset of input features leading to the most performative model. Here we employ a support vector machine classifier (SVM) with a linear kernel to perform binary classification on the input data. We ask for the top \\(j\\in[1\\ .. \\ d]\\) most important features in a for loop, computing the classification accuracy when only these features are leveraged.

from sklearn.feature_selection import RFE\n\nfeatures = np.array(breast_cancer.feature_names)\nsvc = SVC(kernel='linear')\nfor n_features in np.arange(1, 30, 1):\n    rfe = RFE(estimator=svc, step=1, n_features_to_select=n_features)\n    rfe.fit(X_train, y_train)\n    print(f'n_features={n_features}, accuracy={rfe.score(X_val, y_val):.3f}')\n    print(f' - selected: {features[rfe.support_]}')\n\n>>> n_features=1, accuracy=0.881\n>>>  - selected: ['worst concave points']\n>>> n_features=2, accuracy=0.874\n>>>  - selected: ['worst concavity' 'worst concave points']\n>>> n_features=3, accuracy=0.867\n>>>  - selected: ['mean concave points' 'worst concavity' 'worst concave points']\n ...\n>>> n_features=16, accuracy=0.930\n>>> n_features=17, accuracy=0.965\n>>> n_features=18, accuracy=0.951\n...\n>>> n_features=27, accuracy=0.958\n>>> n_features=28, accuracy=0.958\n>>> n_features=29, accuracy=0.958\n
Here we've shown a subset of the output. In the first output lines, we see that the 'worst concave points' feature alone leads to 88.1% accuracy. Including the next two most important features actually degrades the classification accuracy. We then skip to the top 17 features, which in this case we observe to yield the best performance for the linear SVM classifier. The addition of more features does not lead to additional perforamnce boosts. In this way, RFE can be treated as a model wrapper introducing an additional hyperparameter, n_features_to_select, which can be used to optimize model performance. A more principled optimization using k-fold cross validation with RFE is available in the scikit-learn docs.

"},{"location":"optimization/importance.html#feature-correlations","title":"Feature Correlations","text":"

In the above, we have focused specifically on interpreting the importance of single features. However, it may be that several features are correlated, sharing the responsibility for the overall prediction of the model. In this case, some measures of feature importance may inappropriately downweight correlated features in a so-called correlation bias (see Classification with Correlated Features: Unrelability of Feature Ranking and Solutions). For example, the permutation invariance of \\(d\\) correlated features is shown to decrease (as a function of correlation strength) faster for higher \\(d\\) (see Correlation and Variable importance in Random Forests).

We can see these effects in action using the breast cancer dataset, following the corresponding scikit-learn example

from sklearn.ensemble import RandomForestClassifier\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.datasets import load_breast_cancer\n\ndata = load_breast_cancer()\nX, y = data.data, data.target\nX_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)\n\nclf = RandomForestClassifier(n_estimators=100, random_state=42)\nclf.fit(X_train, y_train)\nprint(\"Accuracy on test data: {:.2f}\".format(clf.score(X_test, y_test)))\n\n>>> Accuracy on test data: 0.97\n
Here we've implemented a random forest classifier and achieved a high accuracy (97%) on the benign vs. malignent predictions. The permutation importances for the 10 most important training features are:

r = permutation_importance(clf, X_train, y_train, n_repeats=10, random_state=42)\nfor i in r.importances_mean.argsort()[::-1][:10]:\n    print(f\"{breast_cancer.feature_names[i]:<8}\"\n          f\"  {r.importances_mean[i]:.5f}\"\n          f\" +/- {r.importances_std[i]:.5f}\")\n\n>>> worst concave points  0.00681 +/- 0.00305\n>>> mean concave points  0.00329 +/- 0.00188\n>>> worst texture  0.00258 +/- 0.00070\n>>> radius error  0.00235 +/- 0.00000\n>>> mean texture  0.00188 +/- 0.00094\n>>> mean compactness  0.00188 +/- 0.00094\n>>> area error  0.00188 +/- 0.00094\n>>> worst concavity  0.00164 +/- 0.00108\n>>> mean radius  0.00141 +/- 0.00115\n>>> compactness error  0.00141 +/- 0.00115\n

In this case, even the most permutation important features have mean importance scores \\(<0.007\\), which doesn't indicate much importance. This is surprising, because we saw via RFE that a linear SVM can achieve \\(\\approx 88\\%\\) classification accuracy with this feature alone. This indicates that worst concave points, in addition to other meaningful features, may belong to subclusters of correlated features. In the corresponding scikit-learn example, the authors show that subsets of correlated features can be extracted by calculating a dendogram and selecting representative features from each correlated subset. They achieve \\(97\\%\\) accuracy (the same as with the full dataset) by selecting only five such representative variables.

"},{"location":"optimization/importance.html#feature-importance-in-decision-trees","title":"Feature Importance in Decision Trees","text":"

Here we focus on decision trees, which are particularly interpretable classifiers that often appear as ensembles (or boosted decision tree (BDT) algorithms) in HEP. Consider a classification dataset \\(X=\\{x_n\\}_{n=1}^{N}\\), \\(x_n\\in\\mathbb{R}^{D}\\), with truth labels \\(Y=\\{y_n\\}_{n=1}^N\\), \\(y_n\\in\\{1,...,C\\}\\) corresponding \\(C\\) classes. These truth labels naturally partition \\(X\\) into subsets \\(X_c\\) with class probabilities \\(p(c)=|X_c|/|X|\\). Decision trees begin with a root node \\(t_0\\) containing all of \\(X\\). The tree is grown from the root by recursively splitting the input set \\(X\\) in a principled way; internal nodes (or branch nodes) correspond to a decision of the form

\\[\\begin{aligned} &(x_n)_d\\leq\\delta \\implies\\ \\text{sample}\\ n\\ \\text{goes to left child node}\\\\ &(x_n)_d>\\delta \\implies\\ \\text{sample}\\ n\\ \\text{goes to right child node} \\end{aligned}\\]

We emphasize that the decision boundary is drawn by considering a single feature field \\(d\\) and partitioning the \\(n^\\mathrm{th}\\) sample by the value at that feature field. Decision boundaries at each internal parent node \\(t_P\\) are formed by choosing a \"split criterion,\" which describes how to partition the set of elements at this node into left and right child nodes \\(t_L\\), \\(t_R\\) with \\(X_{t_L}\\subset X_{t_P}\\) and \\(X_{t_R}\\subset X_{t_P}\\), \\(X_{t_L}\\cup X_{t_R}=X_{t_P}\\). This partitioning is optimal if \\(X_{t_L}\\) and \\(X_{t_R}\\) are pure, each containing only members of the same class. Impurity measures are used to evaluate the degree to which the set of data points at a given tree node \\(t\\) are not pure. One common impurity measure is Gini Impurity,

\\[\\begin{aligned} I(t) = \\sum_{c=1}^C p(c|t)(1-p(c|t)) \\end{aligned}\\]

Here, \\(p(c|t)\\) is the probability of drawing a member of class \\(c\\) from the set of elements at node \\(t\\). For example, the Gini impurity at the root node (corresponding to the whole dataset) is

\\[\\begin{aligned} I(t_0) = \\sum_{c=1}^C \\frac{|X_c|}{|X|}(1-\\frac{|X_c|}{|X|}) \\end{aligned}\\]

In a balanced binary dataset, this would give \\(I(t_0)=1/2\\). If the set at node \\(t\\) is pure, i.e. class labels corresponding to \\(X_t\\) are identical, then \\(I(t)=0\\). We can use \\(I(t)\\) to produce an optimal splitting from parent \\(t_p\\) to children \\(t_L\\) and \\(t_R\\) by defining an impurity gain,

\\[\\begin{aligned} \\Delta I = I(t_P) - I(t_L) - I(t_R) \\end{aligned}\\]

This quantity describes the relative impurity between a parent node and its children. If \\(X_{t_P}\\) contains only two classes, an optimal splitting would separate them into \\(X_{p_L}\\) and \\(X_{p_R}\\), producing pure children nodes with \\(I(t_L)=I(t_R)=0\\) and, correspondingly, \\(\\Delta I(t_p) = I(t_P)\\). Accordingly, good splitting decisions should maximize impurity gain. Note that the impurity gain is often weighted, for example Scikit-Learn defines:

\\[\\begin{aligned} \\Delta I(t_p) = \\frac{|X_{t_p}|}{|X|}\\bigg(I(t_p) - \\frac{|X_{t_L}|}{|X_{t_p}|} I(t_L) - \\frac{|X_{t_R}|}{|X_{t_p}|} I(t_R) \\bigg) \\end{aligned}\\]

In general, a pure node cannot be split further and must therefore be a leaf. Likewise, a node for which there is no splitting yielding \\(\\Delta I > 0\\) must be labeled a leaf. These splitting decisions are made recursively at each node in a tree until some stopping condition is met. Stopping conditions may include maximum tree depths or leaf node counts, or threshhold on the maximum impurity gain.

Impurity gain gives us insight into the importance of a decision. In particular, larger \\(\\Delta I\\) indicates a more important decision. If some feature \\((x_n)_d\\) is the basis for several decision splits in a decision tree, the sum of impurity gains at these splits gives insight into the importance of this feature. Accordingly, one measure of the feature importance of \\(d\\) is the average (with respect to the total number of internal nodes) impurity gain imparted by decision split on \\(d\\). This method generalizes to the case of BDTs, in which case one would average this quantity across all weak learner trees in the ensemble.

Note that though decision trees are based on the feature \\(d\\) producing the best (maximum impurity gain) split at a given branch node, surrogate splits are often used to retain additional splits corresponding to features other than \\(d\\). Denote the feature maximizing the impurity gain \\(d_1\\) and producing a split boundary \\(\\delta_1\\). Surrogte splitting involves tracking secondary splits with boundaries \\(\\delta_2, \\delta_3,...\\) corresponding to \\(d_2,d_3,...\\) that have the highest correlation with the maximum impurity gain split. The upshot is that in the event that input data is missing a value at field \\(d_1\\), there are backup decision boundaries to use, mitigating the need to define multiple trees for similar data. Using this generalized notion of a decision tree, wherein each branch node contains a primary decision boundary maximizing impurity gain and several additional surrogate split boundaries, we can average the impurity gain produced at feature field \\(d\\) over all its occurances as a decision split or a surrogate split. This definition of feature importance generalizes the previous to include additional correlations.

"},{"location":"optimization/importance.html#example","title":"Example","text":"

Let us now turn to an example:

import numpy as np\nimport matplotlib.pyplot as plt\nfrom sklearn.tree import DecisionTreeClassifier\nfrom sklearn.datasets import load_wine\nfrom sklearn.inspection import DecisionBoundaryDisplay\nfrom sklearn.metrics import log_loss\nfrom sklearn.model_selection import train_test_split\n\nwine_data = load_wine() \nprint(wine_data.data.shape)\nprint(wine_data.feature_names)\nprint(np.unique(wine_data.target))\n>>> (178, 13)\n>>> ['alcohol', 'malic_acid', 'ash', 'alcalinity_of_ash', 'magnesium', 'total_phenols', 'flavanoids', 'nonflavanoid_phenols', 'proanthocyanins', 'color_intensity', 'hue', 'od280/od315_of_diluted_wines', 'proline']\n>>> [0 1 2]\n

This sklearn wine dataset has 178 entries with 13 features and truth labels corresponding to membership in one of \\(C=3\\) classes. We can train a decision tree classifier as follows:

X, y = wine_data.data, wine_data.target\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)\nclassifier = DecisionTreeClassifier(criterion='gini', splitter='best', random_state=27)\nclassifier.fit(X_train, y_train)\nX_test_pred = classifier.predict(X_test)\nprint('Test Set Performance')\nprint('Number misclassified:', sum(X_test_pred!=y_test))\nprint(f'Accuracy: {classifier.score(X_test, y_test):.3f}')\n>>> Test Set Performance\n>>> Number misclassified: 0\n>>> Accuracy: 1.000\n

In this case, the classifier has generalized perfectly, fitting the test set with \\(100\\%\\) accuracy. Let's take a look into how it makes predictions:

tree = classifier.tree_\nn_nodes = tree.node_count\nnode_features = tree.feature\nthresholds = tree.threshold\nchildren_L = tree.children_left\nchildren_R = tree.children_right\nfeature_names = np.array(wine_data.feature_names)\n\nprint(f'The tree has {n_nodes} nodes')\nfor n in range(n_nodes):\n    if children_L[n]==children_R[n]: continue # leaf node\n    print(f'Decision split at node {n}:',\n          f'{feature_names[node_features[n]]}({node_features[n]}) <=',\n          f'{thresholds[n]:.2f}')\n\n>>> The tree has 13 nodes\n>>> Decision split at node 0: color_intensity(9) <= 3.46\n>>> Decision split at node 2: od280/od315_of_diluted_wines(11) <= 2.48\n>>> Decision split at node 3: flavanoids(6) <= 1.40\n>>> Decision split at node 5: color_intensity(9) <= 7.18\n>>> Decision split at node 8: proline(12) <= 724.50\n>>> Decision split at node 9: malic_acid(1) <= 3.33\n

Here we see that several features are used to generate decision boundaries. For example, the dataset is split at the root node by a cut on the \\(\\texttt{color_intensity}\\) feature. The importance of each feature can be taken to be the average impurity gain it generates across all nodes, so we expect that one (or several) of the five unique features used at the decision splits will be the most important features by this definition. Indeed, we see,

feature_names = np.array(wine_data.feature_names)\nimportances = classifier.feature_importances_\nfor i in range(len(importances)):\n    print(f'{feature_names[i]}: {importances[i]:.3f}')\nprint('\\nMost important features', \n      feature_names[np.argsort(importances)[-3:]])\n\n>>> alcohol: 0.000\n>>> malic_acid: 0.021\n>>> ash: 0.000\n>>> alcalinity_of_ash: 0.000\n>>> magnesium: 0.000\n>>> total_phenols: 0.000\n>>> flavanoids: 0.028\n>>> nonflavanoid_phenols: 0.000\n>>> proanthocyanins: 0.000\n>>> color_intensity: 0.363\n>>> hue: 0.000\n>>> od280/od315_of_diluted_wines: 0.424\n>>> proline: 0.165\n\n>>> Most important features ['proline' 'color_intensity' 'od280/od315_of_diluted_wines']\n

This is an embedded method for generating feature importance - it's cooked right into the decision tree model. Let's verify these results using a wrapper method, permutation importance:

from sklearn.inspection import permutation_importance\n\nprint(f'Initial classifier score: {classifier.score(X_test, y_test):.3f}')\n\nr = permutation_importance(classifier, X_test, y_test, n_repeats=30, random_state=0)\nfor i in r.importances_mean.argsort()[::-1]:\n    print(f\"{feature_names[i]:<8}\"\n          f\" {r.importances_mean[i]:.3f}\"\n          f\" +/- {r.importances_std[i]:.3f}\")\n\n>>> Initial classifier score: 1.000\n\n>>> color_intensity 0.266 +/- 0.040\n>>> od280/od315_of_diluted_wines 0.237 +/- 0.049\n>>> proline  0.210 +/- 0.041\n>>> flavanoids 0.127 +/- 0.025\n>>> malic_acid 0.004 +/- 0.008\n>>> hue      0.000 +/- 0.000\n>>> proanthocyanins 0.000 +/- 0.000\n>>> nonflavanoid_phenols 0.000 +/- 0.000\n>>> total_phenols 0.000 +/- 0.000\n>>> magnesium 0.000 +/- 0.000\n>>> alcalinity_of_ash 0.000 +/- 0.000\n>>> ash      0.000 +/- 0.000\n>>> alcohol  0.000 +/- 0.000\n

The tree's performance is hurt the most if the \\(\\texttt{color_intensity}\\), \\(\\texttt{od280/od315_of_diluted_wines}\\), or \\(\\texttt{proline}\\) features are permuted, consistent with the impurity gain measure of feature importance.

"},{"location":"optimization/model_optimization.html","title":"Model optimization","text":"

This page summarizes the concepts shown in a contribution on Bayesian Optimization to the ML Forum and may be edited and published elsewhere by the author.

"},{"location":"optimization/model_optimization.html#what-we-talk-about-when-we-talk-about-model-optimization","title":"What we talk about when we talk about model optimization","text":"

Given some data \\(x\\) and a family of functionals parameterized by (a vector of) parameters \\(\\theta\\) (e.g. for DNN training weights), the problem of learning consists in finding \\(argmin_\\theta Loss(f_\\theta(x) - y_{true})\\). The treatment below focusses on gradient descent, but the formalization is completely general, i.e. it can be applied also to methods that are not explicitly formulated in terms of gradient descent (e.g. BDTs). The mathematical formalism for the problem of learning is briefly explained in a contribution on statistical learning to the ML forum: for the purposes of this documentation we will proceed through two illustrations.

The first illustration, elaborated from an image by the huawei forums shows the general idea behind learning through gradient descent in a multidimensional parameter space, where the minimum of a loss function is found by following the function's gradient until the minimum.

The cartoon illustrates the general idea behind gradient descent to find the minimum of a function in a multidimensional parameter space (figure elaborated from an image by the huawei forums).

The model to be optimized via a loss function typically is a parametric function, where the set of parameters (e.g. the network weights in neural networks) corresponds to a certain fixed structure of the network. For example, a network with two inputs, two inner layers of two neurons, and one output neuron will have six parameters whose values will be changed until the loss function reaches its minimum.

When we talk about model optimization we refer to the fact that often we are interested in finding which model structure is the best to describe our data. The main concern is to design a model that has a sufficient complexity to store all the information contained in the training data. We can therefore think of parameterizing the network structure itself, e.g. in terms of the number of inner layers and number of neurons per layer: these hyperparameters define a space where we want to again minimize a loss function. Formally, the parametric function \\(f_\\theta\\) is also a function of these hyperparameters \\(\\lambda\\): \\(f_{(\\theta, \\lambda)}\\), and the \\(\\lambda\\) can be optimized

The second illustration, also elaborated from an image by the huawei forums, broadly illustrates this concept: for each point in the hyperparameters space (that is, for each configuration of the model), the individual model is optimized as usual. The global minimum over the hyperparameters space is then sought.

The cartoon illustrates the general idea behind gradient descent to optimize the model complexity (in terms of the choice of hyperparameters) multidimensional parameter and hyperparameter space (figure elaborated from an image by the huawei forums)."},{"location":"optimization/model_optimization.html#caveat-which-data-should-you-use-to-optimize-your-model","title":"Caveat: which data should you use to optimize your model","text":"

In typical machine learning studies, you should divide your dataset into three parts. One is used for training the model (training sample), one is used for testing the performance of the model (test sample), and the third one is the one where you actually use your trained model, e.g. for inference (application sample). Sometimes you may get away with using test data as application data: Helge Voss (Chap 5 of Behnke et al.) states that this is acceptable under three conditions that must be simultaneously valid:

  • no hyperparameter optimization is performed;
  • no overtraining is found;
  • the number of training data is high enough to make statistical fluctuations negligible.

If you are doing any kind of hyperparamters optimization, thou shalt NOT use the test sample as application sample. You should have at least three distinct sets, and ideally you should use four (training, testing, hyperparameter optimization, application).

"},{"location":"optimization/model_optimization.html#grid-search","title":"Grid Search","text":"

The most simple hyperparameters optimization algorithm is the grid search, where you train all the models in the hyperparameters space to build the full landscape of the global loss function, as illustrated in Goodfellow, Bengio, Courville: \"Deep Learning\".

The cartoon illustrates the general idea behind grid search (image taken from Goodfellow, Bengio, Courville: \"Deep Learning\").

To perform a meaningful grid search, you have to provide a set of values within the acceptable range of each hyperparameters, then for each point in the cross-product space you have to train the corresponding model.

The main issue with grid search is that when there are nonimportant hyperparameters (i.e. hyperparameters whose value doesn't influence much the model performance) the algorithm spends an exponentially large time (in the number of nonimportant hyperparameters) in the noninteresting configurations: having \\(m\\) parameters and testing \\(n\\) values for each of them leads to \\(\\mathcal{O}(n^m)\\) tested configurations. While the issue may be mitigated by parallelization, when the number of hyperparameters (the dimension of hyperparameters space) surpasses a handful, even parallelization can't help.

Another issue is that the search is binned: depending on the granularity in the scan, the global minimum may be invisible.

Despite these issues, grid search is sometimes still a feasible choice, and gives its best when done iteratively. For example, if you start from the interval \\(\\{-1, 0, 1\\}\\):

  • if the best parameter is found to be at the boundary (1), then extend range (\\(\\{1, 2, 3\\}\\)) and do the search in the new range;
  • if the best parameter is e.g. at 0, then maybe zoom in and do a search in the range \\(\\{-0.1, 0, 0.1\\}\\).
"},{"location":"optimization/model_optimization.html#random-search","title":"Random search","text":"

An improvement of the grid search is the random search, which proceeds like this:

  • you provide a marginal p.d.f. for each hyperparameter;
  • you sample from the joint p.d.f. a certain number of training configurations;
  • you train for each of these configurations to build the loss function landscape.

This procedure has significant advantages over a simple grid search: random search is not binned, because you are sampling from a continuous p.d.f., so the pool of explorable hyperparameter values is larger; random search is exponentially more efficient, because it tests a unique value for each influential hyperparameter on nearly every trial.

Random search also work best when done iteratively. The differences between grid and random search are again illustrated in Goodfellow, Bengio, Courville: \"Deep Learning\".

The cartoon illustrates the general idea behind random search, as opposed to grid search (image taken from Goodfellow, Bengio, Courville: \"Deep Learning\")."},{"location":"optimization/model_optimization.html#model-based-optimization-by-gradient-descent","title":"Model-based optimization by gradient descent","text":"

Now that we have looked at the most basic model optimization techniques, we are ready to look into using gradient descent to solve a model optimization problem. We will proceed by recasting the problem as one of model selection, where the hyperparameters are the input (decision) variables, and the model selection criterion is a differentiable validation set error. The validation set error attempts to describe the complexity of the network by a single hyperparameter (details in [a contribution on statistical learning to the ML forum]) The problem may be solved with standard gradient descent, as illustrated above, if we assume that the training criterion \\(C\\) is continuous and differentiable with respect to both the parameters \\(\\theta\\) (e.g. weights) and hyperparameters \\(\\lambda\\) Unfortunately, the gradient is seldom available (either because it has a prohibitive computational cost, or because it is non-differentiable as is the case when there are discrete variables).

A diagram illustrating the way gradient-based model optimization works has been prepared by Bengio, doi:10.1162/089976600300015187.

The diagram illustrates the way model optimization can be recast as a model selection problem, where a model selection criterion involves a differentiable validation set error (image taken from Bengio, doi:10.1162/089976600300015187)."},{"location":"optimization/model_optimization.html#model-based-optimization-by-surrogates","title":"Model-based optimization by surrogates","text":"

Sequential Model-based Global Optimization (SMBO) consists in replacing the loss function with a surrogate model of it, when the loss function (i.e. the validation set error) is not available. The surrogate is typically built as a Bayesian regression model, when one estimates the expected value of the validation set error for each hyperparameter together with the uncertainty in this expectation. The pseudocode for the SMBO algorithm is illustrated by Bergstra et al.

The diagram illustrates the pseudocode for the Sequential Model-based Global Optimization (image taken from Bergstra et al).

This procedure results in a tradeoff between: exploration, i.e. proposing hyperparameters with high uncertainty, which may result in substantial improvement or not; and exploitation (propose hyperparameters that will likely perform as well as the current proposal---usually this mean close to the current ones). The disadvantage is that the whole procedure must run until completion before giving as an output any usable information. By comparison, manual or random searches tend to give hints on the location of the minimum faster.

"},{"location":"optimization/model_optimization.html#bayesian-optimization","title":"Bayesian Optimization","text":"

We are now ready to tackle in full what is referred to as Bayesian optimization.

Bayesian optimization assumes that the unknown function \\(f(\\theta, \\lambda)\\) was sampled from a Gaussian process (GP), and that after the observations it maintains the corresponding posterior. In this context, observations are the various validation set errors for different values of the hyperparameters \\(\\lambda\\). In order to pick the next value to probe, one maximizes some estimate of the expected improvement (see below). To understand the meaning of \"sampled from a Gaussian process\", we need to define what a Gaussian process is.

"},{"location":"optimization/model_optimization.html#gaussian-processes","title":"Gaussian processes","text":"

Gaussian processes (GPs) generalize the concept of Gaussian distribution over discrete random variables to the concept of Gaussian distribution over continuous functions. Given some data and an estimate of the Gaussian noise, by fitting a function one can estimate also the noise at the interpolated points. This estimate is made by similarity with contiguous points, adjusted by the distance between points. A GP is therefore fully described by its mean and its covariance function. An illustration of Gaussian processes is given in Kevin Jamieson's CSE599 lecture notes.

The diagram illustrates the evolution of a Gaussian process, when adding interpolating points (image taken from Kevin Jamieson's CSE599 lecture notes).

GPs are great for Bayesian optimization because they out-of-the-box provide the expected value (i.e. the mean of the process) and its uncertainty (covariance function).

"},{"location":"optimization/model_optimization.html#the-basic-idea-behind-bayesian-optimization","title":"The basic idea behind Bayesian optimization","text":"

Gradient descent methods are intrinsically local: the decision on the next step is taken based on the local gradient and Hessian approximations- Bayesian optimization (BO) with GP priors uses a model that uses all the information from the previous steps by encoding it in the model giving the expectation and its uncertainty. The consequence is that GP-based BO can find the minimum of difficult nonconvex functions in relatively few evaluations, at the cost of performing more computations to find the next point to try in the hyperparameters space.

The BO prior is a prior over the space of the functions. GPs are especially suited to play the role of BO prior, because marginals and conditionals can be computed in closed form (thanks to the properties of the Gaussian distribution).

There are several methods to choose the acquisition function (the function that selects the next step for the algorithm), but there is no omnipurpose recipe: the best approach is problem-dependent. The acquisition function involves an accessory optimization to maximize a certain quantity; typical choices are:

  • maximize the probability of improvement over the current best value: can be calculated analytically for a GP;
  • maximize the expected improvement over the current best value: can also be calculated analytically for a GP;
  • maximize the GP Upper confidence bound: minimize \"regret\" over the course of the optimization.
"},{"location":"optimization/model_optimization.html#historical-note","title":"Historical note","text":"

Gaussian process regression is also called kriging in geostatistics, after Daniel G. Krige (1951) who pioneered the concept later formalized by Matheron (1962)

"},{"location":"optimization/model_optimization.html#bayesian-optimization-in-practice","title":"Bayesian optimization in practice","text":"

The figure below, taken by a tutorial on BO by Martin Krasser, clarifies rather well the procedure. The task is to approximate the target function (labelled noise free objective in the figure), given some noisy samples of it (the black crosses). At the first iteration, one starts from a flat surrogate function, with a given uncertainty, and fits it to the noisy samples. To choose the next sampling location, a certain acquisition function is computed, and the value that maximizes it is chosen as the next sampling location At each iteration, more noisy samples are added, until the distance between consecutive sampling locations is minimized (or, equivalently, a measure of the value of the best selected sample is maximized).

Practical illustration of Bayesian Optimization (images taken from a tutorial on BO by Martin Krasser])."},{"location":"optimization/model_optimization.html#limitations-and-some-workaround-of-bayesian-optimization","title":"Limitations (and some workaround) of Bayesian Optimization","text":"

There are three main limitations to the BO approach. A good overview of these limitations and of possible solutions can be found in arXiv:1206.2944.

First of all, it is unclear what is an appropriate choice for the covariance function and its associated hyperparameters. In particular, the standard squared exponential kernel is often too smooth. As a workaround, alternative kernels may be used: a common choice is the Mat\u00e9rn 5/2 kernel, which is similar to the squared exponential one but allows for non-smoothness.

Another issue is that, for certain problems, the function evaluation may take very long to compute. To overcome this, often one can replace the function evaluation with the Monte Carlo integration of the expected improvement over the GP hyperparameters, which is faster.

The third main issue is that for complex problems one would ideally like to take advantage of parallel computation. The procedure is iterative, however, and it is not easy to come up with a scheme to make it parallelizable. The referenced paper proposed sampling over the expected acquisition, conditioned on all the pending evaluations: this is computationally cheap and is intrinsically parallelizable.

"},{"location":"optimization/model_optimization.html#alternatives-to-gaussian-processes-tree-based-models","title":"Alternatives to Gaussian processes: Tree-based models","text":"

Gaussian Processes model directly \\(P(hyperpar | data)\\) but are not the only suitable surrogate models for Bayesian optimization

The so-called Tree-structured Parzen Estimator (TPE), described in Bergstra et al, models separately \\(P(data | hyperpar)\\) and \\(P(hyperpar)\\), to then obtain the posterior by explicit application of the Bayes theorem TPEs exploit the fact that the choice of hyperparameters is intrinsically graph-structured, in the sense that e.g. you first choose the number of layers, then choose neurons per layer, etc. TPEs run over this generative process by replacing the hyperparameters priors with nonparametric densities. These generative nonparametric densities are built by classifying them into those that result in worse/better loss than the current proposal.

TPEs have been used in CMS already around 2017 in a VHbb analysis (see repository by Sean-Jiun Wang) and in a charged Higgs to tb search (HIG-18-004, doi:10.1007/JHEP01(2020)096).

"},{"location":"optimization/model_optimization.html#implementations-of-bayesian-optimization","title":"Implementations of Bayesian Optimization","text":"
  • Implementations in R are readily available as the R-studio tuning package;
  • Scikit-learn provides a handy implementation of Gaussian processes;
  • **scipy* provides a handy implementation of the optimization routines;
  • hyperopt provides a handy implementation of distributed hyperparameter optimization routines;
    • GPs not coded by default, hence must rely on scikit-learn;
    • Parzen tree estimators are implemented by default (together with random search);
  • Several handy tutorials online focussed on hyperparameters optimization
    • Tutorial by Martin Krasser;
    • Tutorial by Jason Brownlee;
  • Early example of hyperopt in CMS
    • VHbb analysis: repository by Sean-Jiun Wang), for optimization of a BDT;
    • Charged Higgs HIG-18-004, doi:10.1007/JHEP01(2020)096) for optimization of a DNN (no public link for the code, contact me if needed)
  • Several expansions and improvements (particularly targeted at HPC clusters) are available, see e.g. this talk by Eric Wulff.
"},{"location":"optimization/model_optimization.html#caveats-dont-get-too-obsessed-with-model-optimization","title":"Caveats: don't get too obsessed with model optimization","text":"

In general, optimizing model structure is a good thing. F. Chollet e.g. says \"If you want to get to the very limit of what can be achieved on a given task, you can't be content with arbitrary choices made by a fallible human\". On the other side, for many problems hyperparameter optimization does result in small improvements, and there is a tradeoff between improvement and time spent on the task: sometimes the time spent on optimization may not be worth, e.g. when the gradient of the loss in hyperparameters space is very flat (i.e. different hyperparameter sets give more or less the same results), particularly if you already know that small improvements will be eaten up by e.g. systematic uncertainties. On the other side, before you perform the optimization you don't know if the landscape is flat or if you can expect substantial improvements. Sometimes broad grid or random searches may give you a hint on whether the landscape of hyperparameters space is flat or not.

Sometimes you may get good (and faster) improvements by model ensembling rather than by model optimization. To do model ensembling, you first train a handful models (either different methods---BDT, SVM, NN, etc---or different hyperparameters sets): \\(pred\\_a = model\\_a.predict(x)\\), ..., \\(pred\\_d = model\\_d.predict(x)\\). You then pool the predictions: \\(pooled\\_pred = (pred\\_a + pred\\_b + pred\\_c + pred\\_d)/4.\\). THis works if all models are kind of good: if one is significantly worse than the others, then \\(pooled\\_pred\\) may not be as good as the best model of the pool.

You can also find ways of ensembling in a smarter way, e.g. by doing weighted rather than simple averages: \\(pooled\\_pred = 0.5\\cdot pred\\_a + 0.25\\cdot pred\\_b + 0.1\\cdot pred\\_c + 0.15\\cdot pred\\_d)/4.\\). Here the idea is to give more weight to better classifiers. However, you transfer the problem to having to choose the weights. These can be found empirically empirically by using random search or other algorithms like Nelder-Mead (result = scipy.optimize.minimize(objective, pt, method='nelder-mead'), where you build simplexes (polytope with N+1 vertices in N dimensions, generalization of triangle) and stretch them towards higher values of the objective. Nelder-Mead can converge to nonstationary points, but there are extensions of the algorithm that may help.

This page summarizes the concepts shown in a contribution on Bayesian Optimization to the ML Forum. Content may be edited and published elsewhere by the author. Page author: Pietro Vischia, 2022

"},{"location":"resources/cloud_resources/index.html","title":"Cloud Resources","text":"

Work in progress.

"},{"location":"resources/dataset_resources/index.html","title":"CMS-ML Dataset Tab","text":""},{"location":"resources/dataset_resources/index.html#introduction","title":"Introduction","text":"

Welcome to CMS-ML Dataset tab! Our tab is designed to provide accurate, up-to-date, and relevant data across various purposes. We strive to make this tab resourceful for your analysis and decision-making needs. We are working on benchmarking more dataset and presenting them in a user-friendly format. This tab will be continuously updated to reflect the latest developments. Explore, analyze, and derive insights with ease!

"},{"location":"resources/dataset_resources/index.html#1-jetnet","title":"1. JetNet","text":""},{"location":"resources/dataset_resources/index.html#links","title":"Links","text":"

Github Repository

Zenodo

"},{"location":"resources/dataset_resources/index.html#description","title":"Description","text":"

JetNet is a project aimed at enhancing accessibility and reproducibility in jet-based machine learning. It offers easy-to-access and standardized interfaces for several datasets, including JetNet, TopTagging, and QuarkGluon. Additionally, JetNet provides standard implementations of various generative evaluation metrics such as Fr\u00e9chet Physics Distance (FPD), Kernel Physics Distance (KPD), Wasserstein-1 (W1), Fr\u00e9chet ParticleNet Distance (FPND), coverage, and Minimum Matching Distance (MMD). Beyond these, it includes a differentiable implementation of the energy mover's distance and other general jet utilities, making it a comprehensive resource for researchers and practitioners in the field.

"},{"location":"resources/dataset_resources/index.html#nature-of-objects","title":"Nature of Objects","text":"
  • Objects: Gluon (g), Top Quark (t), Light Quark (q), W boson (w), and Z boson (z) jets of ~1 TeV transverse momentum (\\(p_T\\))
  • Number of Objects: N = 177252, 177945, 170679, 177172, 176952 for g, t, q, w, z jets respectively.
"},{"location":"resources/dataset_resources/index.html#format-of-dataset","title":"Format of Dataset","text":"
  • File Type: HDF5
  • Structure: Each file has particle_features; and jet_features; arrays, containing the list of particles' features per jet and the corresponding jet's features, respectively. Particle_features is of shape [N, 30, 4], where N is the total number of jets, 30 is the max number of particles per jet, and 4 is the number of particle features, in order: []\\eta, \\varphi, \\p_T, mask]. See Zenodo for definitions of these. jet_features is of shape [N, 4], where 4 is the number of jet features, in order: [\\(p_T\\), \\(\\eta\\), mass, # of particles].
"},{"location":"resources/dataset_resources/index.html#related-projects","title":"Related Projects","text":"
  • Top tagging benchmark
  • Particle Cloud Generation with Message Passing Generative Adversarial Networks
"},{"location":"resources/dataset_resources/index.html#2-top-tagging-benchmark-dataset","title":"2. Top Tagging Benchmark Dataset","text":""},{"location":"resources/dataset_resources/index.html#links_1","title":"Links","text":"

Zenodo

"},{"location":"resources/dataset_resources/index.html#description_1","title":"Description","text":"

A set of MC simulated training/testing events for the evaluation of top quark tagging architectures. - 14 TeV, hadronic tops for signal, qcd diets background, Delphes ATLAS detector card with Pythia8 - No MPI/pile-up included - Clustering of particle-flow entries (produced by Delphes E-flow) into anti-kT 0.8 jets in the pT range [550,650] GeV - All top jets are matched to a parton-level top within \u2206R = 0.8, and to all top decay partons within 0.8 - Jets are required to have |eta| < 2 - The leading 200 jet constituent four-momenta are stored, with zero-padding for jets with fewer than 200 - Constituents are sorted by pT, with the highest pT one first - The truth top four-momentum is stored as truth_px etc. - A flag (1 for top, 0 for QCD) is kept for each jet. It is called is_signal_new - The variable \"ttv\" (= test/train/validation) is kept for each jet. It indicates to which dataset the jet belongs. It is redundant as the different sets are already distributed as different files.

"},{"location":"resources/dataset_resources/index.html#nature-of-objects_1","title":"Nature of Objects","text":"
  • Objects: 14 TeV, hadronic tops for signal, qcd diets background, Delphes ATLAS detector card with Pythia8
  • Number of Objects: In total 1.2M training events, 400k validation events and 400k test events.
"},{"location":"resources/dataset_resources/index.html#format-of-dataset_1","title":"Format of Dataset","text":"
  • File Type: HDF5
  • Structure: Use \u201ctrain\u201d for training, \u201cval\u201d for validation during the training and \u201ctest\u201d for final testing and reporting results. For details, see the Zenodo link
"},{"location":"resources/dataset_resources/index.html#related-projects_1","title":"Related Projects","text":"
  • Butter, Anja; Kasieczka, Gregor; Plehn, Tilman and Russell, Michael (2017). Based on data from 10.21468/SciPostPhys.5.3.028 (1707.08966)
  • Kasieczka, Gregor et al (2019). Dataset used for arXiv:1902.09914 (The Machine Learning Landscape of Top Taggers)
"},{"location":"resources/dataset_resources/index.html#more-dataset-coming-in","title":"More dataset coming in!","text":"

Have any questions? Want your dataset shown on this page? Contact the ML Knowledge Subgroup!

"},{"location":"resources/fpga_resources/index.html","title":"FPGA Resource","text":"

Work in progress.

"},{"location":"resources/gpu_resources/cms_resources/lxplus_gpu.html","title":"lxplus-gpu.cern.ch","text":""},{"location":"resources/gpu_resources/cms_resources/lxplus_gpu.html#how-to-use-it","title":"How to use it?","text":"

lxplus-gpu are special lxplus nodes with GPU support. You can access these nodes by executing

ssh <your_user_name>@lxplus-gpu.cern.ch\n

The configuration of the software environment for lxplus-gpu is described in the Software Environments page.

"},{"location":"resources/gpu_resources/cms_resources/lxplus_htcondor.html","title":"HTCondor With GPU resources","text":"

In general, HTCondor supports GPU jobs if there are some worker nodes which are configured with GPU devices. CMS Connect and lxplus both have access to worker nodes equipped with GPUs.

"},{"location":"resources/gpu_resources/cms_resources/lxplus_htcondor.html#how-to-require-gpus-in-htcondor","title":"How to require GPUs in HTCondor","text":"

People can require their jobs to have GPU support by adding the following requirements to the condor submission file.

request_gpus = n # n equal to the number of GPUs required\n
"},{"location":"resources/gpu_resources/cms_resources/lxplus_htcondor.html#further-documentation","title":"Further documentation","text":"

There are good materials providing detailed documentation on how to run HTCondor jobs with GPU support at both machines.

The configuration of the software environment for lxplus-gpu and HTcondor is described in the Software Environments page. Moreover the page Using container explains step by step how to build a docker image to be run on HTCondor jobs.

"},{"location":"resources/gpu_resources/cms_resources/lxplus_htcondor.html#more-available-resources","title":"More available resources","text":"
  1. A complete documentation can be found from the GPUs section in CERN Batch Docs. Where a Tensorflow example is supplied. This documentation also contains instructions on advanced HTCondor configuration, for instance constraining GPU device or CUDA version.
  2. A good example on submitting GPU HTCondor job @ Lxplus is the weaver-benchmark project. It provides a concrete example on how to setup environment for weaver framework and operate trainning and testing process within a single job. Detailed description can be found at section ParticleNet of this documentation.

    In principle, this example can be run elsewhere as HTCondor jobs. However, paths to the datasets should be modified to meet the requirements.

  3. CMS Connect also provides a documentation on GPU job submission. In this documentation there is also a Tensorflow example.

    When submitting GPU jobs @ CMS Connect, especially for Machine Learning purpose, EOS space @ CERN are not accessible as a directory, therefore one should consider using xrootd utilities as documented in this page

"},{"location":"resources/gpu_resources/cms_resources/ml_cern_ch.html","title":"ml.cern.ch","text":"

ml.cern.ch is a Kubeflow based ML solution provided by CERN.

"},{"location":"resources/gpu_resources/cms_resources/ml_cern_ch.html#kubeflow","title":"Kubeflow","text":"

Kubeflow is a Kubernetes based ML toolkits aiming at making deployments of ML workflows simple, portable and scalable. In Kubeflow, pipeline is an important concept. Machine Learning workflows are discribed as a Kubeflow pipeline for execution.

"},{"location":"resources/gpu_resources/cms_resources/ml_cern_ch.html#how-to-access","title":"How to access","text":"

ml.cern.ch only accepts connections from within the CERN network. Therefore, if you are outside of CERN, you will need to use a network tunnel (eg. via ssh -D dynamic port forwarding as a SOCKS5 proxy)... The main website are shown below.

"},{"location":"resources/gpu_resources/cms_resources/ml_cern_ch.html#examples","title":"Examples","text":"

After logging into the main website, you can click on the Examples entry to browser a gitlab repository containing a lot of examples. For instance, below are two examples from that repository with a well-documented readme file.

  1. mnist-kfp is an example on how to use jupyter notebooks to create a Kubeflow pipeline (kfp) and how to access CERN EOS files.
  2. katib gives an example on how to use the katib to operate hyperparameter tuning for jet tagging with ParticleNet.
"},{"location":"resources/gpu_resources/cms_resources/swan.html","title":"SWAN","text":""},{"location":"resources/gpu_resources/cms_resources/swan.html#preparation","title":"Preparation","text":"
  1. Registration:

    To require GPU resources for SWAN: According to this thread, one can create a ticket through this link to ask for GPU support at SWAN, it is now in beta version and limited to a small scale. 2. Setup SWAN with GPU resources:

    1. Once the registration is done, one can login SWAN with Kerberes8 support and then create his SWAN environment.

      \ud83d\udca1 Note: When configuring the SWAN environment you will be given your choice of software stack. Be careful to use a software release with GPU support as well as an appropriate CUDA version. If you need to install additional software, it must be compatible with your chosen CUDA version.

Another important option is the environment script, which will be discussed later in this document.

"},{"location":"resources/gpu_resources/cms_resources/swan.html#working-with-swan","title":"Working with SWAN","text":"
  1. After creation, one will browse the SWAN main directory My Project where all existing projects are displayed. A new project can be created by clicking the upper right \"+\" button. After creation one will be redirected to the newly created project, at which point the \"+\" button on the upper right panel can be used for creating new notebook.

  2. It is possible to use the terminal for installing new packages or monitoring computational resources.

    1. For package installation, one can install packages with package management tools, e.g. pip for python. To use the installed packages, you will need to wrap the environment configuration in a scrip, which will be executed by SWAN. Detailed documentation can be found by clicking the upper right \"?\" button.

    2. In addition to using top and htop to monitor ordinary resources, you can use nvidia-smi to monitor GPU usage.

"},{"location":"resources/gpu_resources/cms_resources/swan.html#examples","title":"Examples","text":"

After installing package, you can then use GPU based machine learning algorithms. Two examples are supplied as an example.

  1. The first example aims at using a CNN to perform handwritten digits classification with MNIST dataset. The whole notebook can be found at pytorch_mnist. This example is modified from an official pytorch example.

  2. The second example is modified from the simple MLP example from weaver-benchmark. The whole notebook can be found at toptagging_mlp.

"},{"location":"resources/gpu_resources/cms_resources/notebooks/pytorch_mnist.html","title":"Pytorch mnist","text":"
from __future__ import print_function\nimport argparse\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torchvision import datasets, transforms\nfrom torch.optim.lr_scheduler import StepLR\n
class Net(nn.Module):\n    def __init__(self):\n        super(Net, self).__init__()\n        self.conv1 = nn.Conv2d(1, 32, 3, 1)\n        self.conv2 = nn.Conv2d(32, 64, 3, 1)\n        self.dropout1 = nn.Dropout(0.25)\n        self.dropout2 = nn.Dropout(0.5)\n        self.fc1 = nn.Linear(9216, 128)\n        self.fc2 = nn.Linear(128, 10)\n\n    def forward(self, x):\n        x = self.conv1(x)\n        x = F.relu(x)\n        x = self.conv2(x)\n        x = F.relu(x)\n        x = F.max_pool2d(x, 2)\n        x = self.dropout1(x)\n        x = torch.flatten(x, 1)\n        x = self.fc1(x)\n        x = F.relu(x)\n        x = self.dropout2(x)\n        x = self.fc2(x)\n        output = F.log_softmax(x, dim=1)\n        return output\n
def train(args, model, device, train_loader, optimizer, epoch):\n    model.train()\n    for batch_idx, (data, target) in enumerate(train_loader):\n        data, target = data.to(device), target.to(device)\n\n        optimizer.zero_grad()\n        output = model(data)\n        loss = F.nll_loss(output, target)\n        loss.backward()\n        optimizer.step()\n        if batch_idx % args[\"log_interval\"] == 0:\n            print('Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}'.format(\n                epoch, batch_idx * len(data), len(train_loader.dataset),\n                100. * batch_idx / len(train_loader), loss.item()))\n            if args[\"dry_run\"]:\n                break\n
def test(model, device, test_loader):\n    model.eval()\n    test_loss = 0\n    correct = 0\n    with torch.no_grad():\n        for data, target in test_loader:\n            data, target = data.to(device), target.to(device)\n            output = model(data)\n            test_loss += F.nll_loss(output, target, reduction='sum').item()  # sum up batch loss\n            pred = output.argmax(dim=1, keepdim=True)  # get the index of the max log-probability\n            correct += pred.eq(target.view_as(pred)).sum().item()\n\n    test_loss /= len(test_loader.dataset)\n\n    print('\\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\\n'.format(\n        test_loss, correct, len(test_loader.dataset),\n        100. * correct / len(test_loader.dataset)))\n
torch.cuda.is_available() # Check if cuda is available\n
train_kwargs = {\"batch_size\":64}\ntest_kwargs = {\"batch_size\":1000}\n
cuda_kwargs = {'num_workers': 1,\n               'pin_memory': True,\n               'shuffle': True}\ntrain_kwargs.update(cuda_kwargs)\ntest_kwargs.update(cuda_kwargs)\n
transform=transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize((0.1307,), (0.3081,))\n    ])\n
dataset1 = datasets.MNIST('./data', train=True, download=True,\n                   transform=transform)\ndataset2 = datasets.MNIST('./data', train=False,\n                   transform=transform)\ntrain_loader = torch.utils.data.DataLoader(dataset1,**train_kwargs)\ntest_loader = torch.utils.data.DataLoader(dataset2, **test_kwargs)\n
device = torch.device(\"cuda\")\nmodel = Net().to(device)\noptimizer = optim.Adadelta(model.parameters(), lr=1.0)\nscheduler = StepLR(optimizer, step_size=1, gamma=0.7)\n
args = {\"dry_run\":False, \"log_interval\":100}\nfor epoch in range(1, 14 + 1):\n    train(args, model, device, train_loader, optimizer, epoch)\n    test(model, device, test_loader)\n    scheduler.step()\n
"},{"location":"resources/gpu_resources/cms_resources/notebooks/toptagging_mlp.html","title":"Toptagging mlp","text":"

import torch\nimport torch.nn as nn\nfrom torch.utils.data.dataset import Dataset\nimport pandas as pd\nimport numpy as np\nimport uproot3\nimport torch.optim as optim\nfrom torch.optim.lr_scheduler import StepLR\nimport torch.nn.functional as F\nimport awkward0\n
class MultiLayerPerceptron(nn.Module):\nr\"\"\"Parameters\n    ----------\n    input_dims : int\n        Input feature dimensions.\n    num_classes : int\n        Number of output classes.\n    layer_params : list\n        List of the feature size for each layer.\n    \"\"\"\n\n    def __init__(self, input_dims, num_classes,\n                 layer_params=(256,64,16),\n                 **kwargs):\n\n        super(MultiLayerPerceptron, self).__init__(**kwargs)\n        channels = [input_dims] + list(layer_params) + [num_classes]\n        layers = []\n        for i in range(len(channels) - 1):\n            layers.append(nn.Sequential(nn.Linear(channels[i], channels[i + 1]),\n                                        nn.ReLU()))\n        self.mlp = nn.Sequential(*layers)\n\n    def forward(self, x):\n        # x: the feature vector initally read from the data structure, in dimension (N, C, P)\n        x = x.flatten(start_dim=1) # (N, L), where L = C * P\n        return self.mlp(x)\n\n    def predict(self,x):\n        pred = F.softmax(self.forward(x))\n        ans = []\n        for t in pred:\n            if t[0] > t[1]:\n                ans.append(1)\n            else:\n                ans.append(0)\n        return torch.tensor(ans)\n

def train(args, model, device, train_loader, optimizer, epoch):\n    model.train()\n    for batch_idx, (data, target) in enumerate(train_loader):\n        data, target = data.to(device), target.to(device)\n        optimizer.zero_grad()\n        output = model(data)\n        loss = F.nll_loss(output, target)\n        loss.backward()\n        optimizer.step()\n        if batch_idx % args[\"log_interval\"] == 0:\n            print('Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}'.format(\n                epoch, batch_idx * len(data), len(train_loader.dataset),\n                100. * batch_idx / len(train_loader), loss.item()))\n            if args[\"dry_run\"]:\n                break\n
input_branches = [\n                  'Part_Etarel',\n                  'Part_Phirel',\n                  'Part_E_log',\n                  'Part_P_log'\n                 ]\n\noutput_branches = ['is_signal_new']\n
train_dataset = uproot3.open(\"TopTaggingMLP/train.root\")[\"Events\"].arrays(input_branches+output_branches,namedecode='utf-8')\ntrain_dataset = {name:train_dataset[name].astype(\"float32\") for name in input_branches+output_branches}\ntest_dataset = uproot3.open(\"/eos/user/c/coli/public/weaver-benchmark/top_tagging/samples/prep/top_test_0.root\")[\"Events\"].arrays(input_branches+output_branches,namedecode='utf-8')\ntest_dataset = {name:test_dataset[name].astype(\"float32\") for name in input_branches+output_branches}\n
for ds in [train_dataset,test_dataset]:\n    for name in ds.keys():\n        if isinstance(ds[name],awkward0.JaggedArray):\n            ds[name] = ds[name].pad(30,clip=True).fillna(0).regular().astype(\"float32\")\n
class PF_Features(Dataset):\n    def __init__(self,mode = \"train\"):\n        if mode == \"train\":\n            self.x = {key:train_dataset[key] for key in input_branches}\n            self.y = {'is_signal_new':train_dataset['is_signal_new']}\n        elif mode == \"test\":\n            self.x = {key:test_dataset[key] for key in input_branches}\n            self.y = {'is_signal_new':test_dataset['is_signal_new']}\n        elif model == \"val\":\n            self.x = {key:test_dataset[key] for key in input_branches}\n            self.y = {'is_signal_new':test_dataset['is_signal_new']}\n\n    def __len__(self):\n        return len(self.y['is_signal_new'])\n\n    def __getitem__(self,idx):\n        X = [self.x[key][idx].copy() for key in input_branches]\n        X = np.vstack(X)\n        y = self.y['is_signal_new'][idx].copy()\n        return X,y\n
torch.cuda.is_available() # Check if cuda is available\n
True\n
device = torch.device(\"cuda\")\n
train_kwargs = {\"batch_size\":1000}\ntest_kwargs = {\"batch_size\":10}\ncuda_kwargs = {'num_workers': 1,\n               'pin_memory': True,\n               'shuffle': True}\ntrain_kwargs.update(cuda_kwargs)\ntest_kwargs.update(cuda_kwargs)\n
model = MultiLayerPerceptron(input_dims = 4 * 30, num_classes=2).to(device)\n
optimizer = optim.Adam(model.parameters(), lr=0.01)\n
train_loader = torch.utils.data.DataLoader(PF_Features(mode=\"train\"),**train_kwargs)\ntest_loader = torch.utils.data.DataLoader(PF_Features(mode=\"test\"),**test_kwargs)\n
loss_func = torch.nn.CrossEntropyLoss()\n
args = {\"dry_run\":False, \"log_interval\":500}\nfor epoch in range(1,10+1):\n    for batch_idx, (data, target) in enumerate(train_loader):\n        inputs = data.to(device)#.flatten(start_dim=1)\n        target = target.long().to(device)\n        optimizer.zero_grad()\n        output = model.forward(inputs)\n        loss = loss_func(output,target)\n        loss.backward()\n        optimizer.step()\n        if batch_idx % args[\"log_interval\"] == 0:\n            print('Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}'.format(\n                epoch, batch_idx * len(data), len(train_loader.dataset),\n                100. * batch_idx / len(train_loader), loss.item()))\n
"},{"location":"software_envs/containers.html","title":"Using containers","text":"

Containers are a great solution to isolate a software environment, especially in batch systems like lxplus. At the moment two container solutations are supported Apptainer ( previously called Singularity), and Docker.

"},{"location":"software_envs/containers.html#using-singularity","title":"Using Singularity","text":"

The unpacked.cern.ch service mounts on CVMFS contains many singularity images, some of which are suitable for machine learning applications. A description of each of the images is beyond the scope of this document. However, if you find an image which is useful for your application, you can use if by running a Singularity container with the appropriate options. For example:

singularity run --nv --bind <bind_mount_path> /cvmfs/unpacked.cern.ch/<path_to_image>\n

"},{"location":"software_envs/containers.html#examples","title":"Examples","text":"

After installing package, you can then use GPU based machine learning algorithms. Two examples are supplied as an example.

  1. The first example aims at using a CNN to perform handwritten digits classification with MNIST dataset. The whole notebook can be found at pytorch_mnist. This example is modified from an official pytorch example.

  2. The second example is modified from the simple MLP example from weaver-benchmark. The whole notebook can be found at toptagging_mlp.

"},{"location":"software_envs/containers.html#using-docker","title":"Using Docker","text":"

Docker is not supported at the moment in the interactive node of lxplus (like lxplus-gpu). However Docker is supported on HTCondor for job submission.

This option can be very handy for users, as HTCondor can pull images from any public registry, like DockerHub or GitLab registry. The user can follow this workflow: 1. Define a custom image on top of a commonly available pytorch or tensorflow image 2. Add the desidered packages and configuration 3. Push the docker image on a registry 4. Use the image in a HTCondor job

The rest of the page is a step by step tutorial for this workflow.

"},{"location":"software_envs/containers.html#define-the-image","title":"Define the image","text":"
  1. Define a file Dockerfile

    FROM pytorch/pytorch:latest\n\nADD localfolder_with_code /opt/mycode\n\n\nRUN  cd /opt/mycode && pip install -e . # or pip install requirements\n\n# Install the required Python packages\nRUN pip install \\\n    numpy \\\n    sympy \\\n    scikit-learn \\\n    numba \\\n    opt_einsum \\\n    h5py \\\n    cytoolz \\\n    tensorboardx \\\n    seaborn \\\n    rich \\\n    pytorch-lightning==1.7\n\nor \nADD requirements.txt \npip install -r requirements.txt\n
  2. Build the image

    docker build -t username/pytorch-condor-gpu:tag .\n

    and push it (after having setup the credentials with docker login hub.docker.com)

    docker push username/pytorch-condor-gpu:tag\n
  3. Setup the condor job with a submission file submitfile as:

    universe                = docker\ndocker_image            = user/pytorch-condor-gpu:tag\nexecutable              = job.sh\nwhen_to_transfer_output = ON_EXIT\noutput                  = $(ClusterId).$(ProcId).out\nerror                   = $(ClusterId).$(ProcId).err\nlog                     = $(ClusterId).$(ProcId).log\nrequest_gpus            = 1\nrequest_cpus            = 2\n+Requirements           = OpSysAndVer =?= \"CentOS7\"\n+JobFlavour = espresso\nqueue 1\n
  4. For testing purpose one can start a job interactively and debug

    condor_submit -interactive submitfile\n
"},{"location":"software_envs/lcg_environments.html","title":"LCG environments","text":""},{"location":"software_envs/lcg_environments.html#software-environment","title":"Software Environment","text":"

The software environment for ML application trainings can be setup in different ways. In this page we focus on the CERN lxplus environment.

"},{"location":"software_envs/lcg_environments.html#lcg-release-software","title":"LCG release software","text":"

Checking out an ideal software bundle with Cuda support at http://lcginfo.cern.ch/, one can set up an LCG environment by executing

source /cvmfs/sft.cern.ch/lcg/views/<name of bundle>/**x86_64-centos*-gcc11-opt**/setup.sh\n

On lxplus-gpu nodes, usually equipped with AlmaLinux 9.1 (also called Centos9), one should use the proper lcg release. At the time of writing (May 2023) the recommended environment to use GPUs is:

source /cvmfs/sft.cern.ch/lcg/views/LCG_103cuda/x86_64-centos9-gcc11-opt/setup.sh\n
"},{"location":"software_envs/lcg_environments.html#customized-environments","title":"Customized environments","text":"

One can create custom Python environment using virtualenv or venv tools, in order to avoid messing up with the global python environment.

The user has the choice of building a virtual environment from scratch or by basing on top of a LCG release.

"},{"location":"software_envs/lcg_environments.html#virtual-environment-from-scratch","title":"Virtual environment from scratch","text":"

The first approach is cleaner but requires downloading the full set of libraries needed for pytorch or TensorFlow (very heavy). Moreover the compatibility with the computing environment (usually lxplus-gpu) is not guaranteed.

  1. Create the environment in a folder of choice, usually called myenv

    python3 -m venv --system-site-packages myenv\nsource myenv/bin/activate   # activate the environment\n# Add following line to .bashrc if you want to activate this environment by default (not recommended)\n# source \"/afs/cern.ch/user/<first letter of your username>/<username>/<path-to-myenv-folder>/myenv/bin/activate\"\n
  2. To install packages properly, one should carefully check the CUDA version with nvidia-smi (as shown in figure before), and then find a proper version, pytorch is used as an example.

    # Execute the command shown in your terminal\npip install torch==1.10.0+cu113 torchvision==0.11.1+cu113 torchaudio==0.10.0+cu113 -f https://download.pytorch.org/whl/cu113/torch_stable.html\npip install jupyterlab matplotlib scikit-hep # install other packages if they are needed\n
"},{"location":"software_envs/lcg_environments.html#virtual-environment-on-top-of-lcg","title":"Virtual environment on top of LCG","text":"

Creating a virtual environment only to add packages on top of a specific LCG release can be a very effective and inexpesive way to manage the Python environment in lxplus.

N.B A caveat is that the users needs to remember to activate the lcg environment before activating his virtual environment.

  1. Activate the lcg environment of choice

    source /cvmfs/sft.cern.ch/lcg/views/LCG_103cuda/x86_64-centos9-gcc11-opt/setup.sh\n
  2. Create the enviroment as above

    python3 -m venv --system-site-packages myenv\nsource myenv/bin/activate   # activate the environment\n
  3. Now the user can work in the environment as before but Pytorch and tensorflow libraries will be available. If a single package needs to be update one can do

pip install --upgrade tensorflow=newer.version\n

This will install the package in the local environment.

At the next login, the user will need to perform these steps to get back the environment:

source /cvmfs/sft.cern.ch/lcg/views/LCG_103cuda/x86_64-centos9-gcc11-opt/setup.sh\nsource myenv/bin/activate\n
"},{"location":"software_envs/lcg_environments.html#conda-environments","title":"Conda environments","text":"

Using conda package manager: conda pacakge manager is more convenient to install and use. To begin with, obtaining an Anaconda or Miniconda installer for Linux x86_64 platform. Then execute it on Lxplus.

1. Please note that if you update your shell configuration (e.g. `.bashrc` file) by `conda init`, you may encounter failure due to inconsistent environment configuration.\n2. Installing packages via `conda` also needs special consideration on selecting proper CUDA version as discussed in `pip` part.\n
"},{"location":"training/Decorrelation.html","title":"Decorrelation","text":"

When preparing to train a machine learning algorithm, it is important to think about the correlations of the output and their impact on how the trained model is used. Generally, the goal of any training is to maximize correlations with variables of interests. For example, a classifier is trained specifically to be highly correlated with the classification categories. However, there is often another set of variables that high correlation with the ML algorithm's output is not desirable and could make the ML algorithm useless regardless of its overall performance.

There are numerous methods that achieve the goal of minimizing correlations of ML algorithms. Choosing the correct decorrelation method depends on the situation, e.g., which ML algorithm is being used and the type of the undesirable variables. Below, we detail various methods for common scenarios focusing on BDT (boosted decision tree) and neural network algorithms.

"},{"location":"training/Decorrelation.html#impartial-training-data","title":"Impartial Training Data","text":"

Generally, the best method for making a neural network's or BDT's output independent of some known variable is to remove any bias in the training dataset, which is commonly done by adding or removing information.

"},{"location":"training/Decorrelation.html#adding-information","title":"Adding Information","text":"
  • Training on a mix of signals with different masses can help prevent the BDT from learning the mass.
"},{"location":"training/Decorrelation.html#removing-information","title":"Removing Information","text":"
  • If you have any input variables that are highly correlated with the mass, you may want to omit them. There may be a loss of raw discrimination power with this approach, but the underlying interpretation will be more sound.
"},{"location":"training/Decorrelation.html#reweighting","title":"Reweighting","text":"
  • One method to achieve correlation by weighting data is reweighting the network's input samples to match a reference distribution. Examples input variables include mass, or an input to invariant mass, like the \\(p_T\\). This method is distinct from flattening the data since it is weighted to match a target distribution rather than a flat distribution. Flattening can also require very large weights that could potentially affect training. This is one way to avoid having the network sculpt, or learn, a certain kinematic quantity, like the background mass. An example of this technique is given in EXO-19-020.
    • This is what is done for the ImageTop tagger and ParticleNet group of taggers. BDT scores from EXO-10-020 where the jet \\(p_T\\) distribution is reweighted to match a reference distribution for each sample.
"},{"location":"training/Decorrelation.html#adversarial-approach","title":"Adversarial Approach","text":"

Adversarial approaches to decorrelation revolve around including a penalty, or regularization, term in the loss function in training. The loss function can be modified to enforce uniformity in the variable of interest (i.e. mass). Check out these links (1, 2, 3) for some examples of this. One way to technically implement this type of approach is using the \"flatness loss function\" (i.e. BinFlatnessLossFunction in the hep-ml package). This type of decorrelation what is done for the DeepAK8-MD taggers.

Another type of regularization one can do to acheive decorrelation is penalizing the loss function on a certain type of correlation, for example distance. In the seminal distance correlation in HEP-ML paper ((DisCo Fever: Robust Networks Through Distance Correlation)), distance is in this case is defined as distance correlation (DisCo), a measure derived from distance covariance, first introduced here. This distance correlation function calculates the non-linear correlation between the NN output and some variables that you care about, e.g. jet mass, that you can force the network to minimize which decorrelates the two variables by including it as a penalty term in the loss function. An extension of this can be found in the Double DisCo method, given below, which highlights the distance correlation term in the loss function at the bottom. The Double DisCo network leverages the ABCD method for background estimation, which is why it requires two separate discriminants. Below is the Double DisCo NN architecture used in MLG-23-003. Notice the two separate discriminant paths consisting of a Dense layer, a Dropout layer, and another Dense layer before outputting a single discriminant per path.

Source: CMS AN-22-101 for MLG-23-003.

Many thanks to Kevin Pedro for his input on this section and the previous one.

"},{"location":"training/Decorrelation.html#parametric-cut","title":"Parametric Cut","text":"

When designing jet taggers, variables of interest for discriminators include N-subjettiness derived quantities. Often, these quantities will be correlated with, for example, the \\(p_T\\) of the jet. One example of this type of correlation is called \"mass scuplting\" and happens when the distribution of the discriminating variable in background begins to exhibit a shape similar to that of the signal with successive cuts. This correlation can have confounding effects in the tagger and one way to remove these effects is to parametrically cut on the discriminant.

One such prescription to remove these correlations is described here and focuses on removing the \\(p_T\\) dependence in the soft-drop mass variable \\(\\rho\\). The authors note that there is a \\(p_T\\) dependence in the N-subjettiness ratio \\(\\tau_2/\\tau_1\\) as a function of the QCD jet scaling (soft-drop) variable, defined as \\(\\rho = log(m^2)(p_T^2)\\), which leads to mass sculpting. In order to alleviate this issue, the authors introduce a modified version of the soft-drop variable, \\(\\rho' = \\rho + log(p_T/\\mu)\\) where \\(\\mu\\) is chosen to be 1 GeV. It can also be noted that there is a linear depedence between \\(\\tau_2/\\tau_1\\) and \\(\\rho'\\). Here, the authors remedy this by modelling the linear depedence with \\(\\tau_{21}' + \\tau_2/\\tau_1 - M \\times \\rho'\\) where \\(M\\) is fit from the data. Applying both these transformations flattens out the relationship between the ratio and the soft-drop variable and removes the mass sculpting effects. It is imperative that the transformation between variables are smooth, as discontinuous functions may lead to artificial features in the data.

"},{"location":"training/Decorrelation.html#methods-for-mass-parameterized-bdts","title":"Methods for mass parameterized BDTs","text":"

Finally, when using a BDT that is parameterized by a mass quantity of interest, the output can be decorrelated from that mass by three different methods: randomization, oversampling, and variable profiling. Randomization entails randomly pairing a mass quanitity to a background training event so the BDT does not learn any meaningful associations between the mass and the output. For oversampling, this is a bootstrapping method where every input background event is paired with a potential mass point so the effective statistics for all the mass points are the same. Finally, variable profiling has the user profile each BDT input as a function of the mass quantity of interest. Examples of each of these methods is given below in the context of a di-higgs search.

A di-higgs multilepton search (HIG-21-002) made use of a BDT for signal discrimination, parameterized by the di-higgs invariant mass. In order to avoid correlations in the BDT output and invariant mass of the di-higgs system, they looked at decorrelation via randomization, oversampling, and variable profiling. All of the methods utilized a (more or less) 50/50 dataset train/test split where one iteration of the BDT was trained on \"even\" numbered events and the datacards were produced with the \"odd\" numbered events. This procedure was repeated for the opposite configuration. Then, to deteremine if the BDT was correctly interpolating the signal masses, one mass point was omitted from training and the results of this BDT were compared to a BDT trained on only this single, omitted mass point. For each train/test configuration (even/odd or odd/even), the BDT's performance gain, as well as loss, were evaluated with ROC curves with two ommitted mass points (done separately).

In the randomization method, a generator-level di-higgs invariant mass was randomly assigned to each background event the BDT was trained on. For the oversampling method, every signal mass point was assigned to a duplicate of each background event. Obviously the oversampling method leads to slower execution but the same effective statistics for all backgrounds and each signal mass. Conversely, the randomization approach is quicker, but leads to reduced effective statistics. Lastly, to improve performance over lower signal masses, each BDT input variable was profiled as a function of \\(m_{HH}\\). This profile was fit with a polynomial function, and then each point in the input distribution is divided by the fit function value. This corrected ratio is used as the new input to the BDT. The authors also found that splitting the BDT training into high and low mass regions helped.

In the end, oversampling, especially when combined with input variable corrections, provided a sizable performance gain (5.6%) over the randomization method. This gain is determined from ROC curves made for each training iteration (even/odd or odd/event) and each method. The performance loss is also a 5% improvement over the randomization method.

For more information on these methods, see the HIG-21-002. Below are some example BDT output scores for the \\(2\\ell ss\\) and \\(3 \\ell\\) channels for this analysis.

Source: HIG-21-002

So far we have seen decorrelation achieved by using inputs that are decorrelated for the classifier and regularizing the output to penalize learning correlations. Another approach can be to learn decorrelation by maximizing performance metrics that more closely align with the sensitivity of the analysis, like in this paper and their corresponding Python-based package, ThickBrick. In this case, the authors study the dependence of the event selection threshold on the signal purity in a given bin of the distribution of an observable. They demonstrate that the threshold increases with signal purity, \"implying that the threshold is stronger in the x-'bins' of higher purity.\" This parametric event selection threshold \"naturally leads to decorrelation of the event selection criteria from the event variable x.\" The failure to incorporate the dependencies on observable distributions is framed as a misalignment between the ML-based selector and the sensitivity of the physics analysis. A demo of their package, ThickBrick, was given at PyHEP2020.

"},{"location":"training/MLaaS4HEP.html","title":"MLaaS4HEP","text":""},{"location":"training/MLaaS4HEP.html#machine-learning-as-a-service-for-hep","title":"Machine Learning as a Service for HEP","text":"

MLaaS for HEP is a set of Python-based modules to support reading HEP data and stream them to the ML tool of the user's choice. It consists of three independent layers: - Data Streaming layer to handle remote data, see reader.py - Data Training layer to train ML model for given HEP data, see workflow.py - Data Inference layer, see tfaas_client.py

The MLaaS4HEP resopitory can be found here.

The general architecture of MLaaS4HEP looks like this:

Even though this architecture was originally developed for dealing with HEP ROOT files, we extend it to other data formats. As of right now, following data formats are supported: JSON, CSV, Parquet, and ROOT. All of the formats support reading files from the local file system or HDFS, while the ROOT format supports reading files via the XRootD protocol.

The pre-trained models can be easily uploaded to TFaaS inference server for serving them to clients. The TFaaS documentation can be found here.

"},{"location":"training/MLaaS4HEP.html#dependencies","title":"Dependencies","text":"

Here is a list of the dependencies: - pyarrow for reading data from HDFS file system - uproot for reading ROOT files - numpy, pandas for data representation - modin for fast panda support - numba for speeing up individual functions

"},{"location":"training/MLaaS4HEP.html#installation","title":"Installation","text":"

The easiest way to install and run MLaaS4HEP and TFaaS is to use pre-build docker images

# run MLaaS4HEP docker container\ndocker run veknet/mlaas4hep\n# run TFaaS docker container\ndocker run veknet/tfaas\n

"},{"location":"training/MLaaS4HEP.html#reading-root-files","title":"Reading ROOT files","text":"

MLaaS4HEP python repository provides the reader.py module that defines a DataReader class able to read either local or remote ROOT files (via xrootd) in chunks. It is based on the uproot framework.

Basic usage

# setup the proper environment, e.g.\n# export PYTHONPATH=/path/src/python # path to MLaaS4HEP python framework\n# export PATH=/path/bin:$PATH # path to MLaaS4HEP binaries\n\n# get help and option description\nreader --help\n\n# here is a concrete example of reading local ROOT file:\nreader --fin=/opt/cms/data/Tau_Run2017F-31Mar2018-v1_NANOAOD.root --info --verbose=1 --nevts=2000\n\n# here is an example of reading remote ROOT file:\nreader --fin=root://cms-xrd-global.cern.ch//store/data/Run2017F/Tau/NANOAOD/31Mar2018-v1/20000/6C6F7EAE-7880-E811-82C1-008CFA165F28.root --verbose=1 --nevts=2000 --info\n\n# both of aforementioned commands produce the following output\nReading root://cms-xrd-global.cern.ch//store/data/Run2017F/Tau/NANOAOD/31Mar2018-v1/20000/6C6F7EAE-7880-E811-82C1-008CFA165F28.root\n# 1000 entries, 883 branches, 4.113945007324219 MB, 0.6002757549285889 sec, 6.853425235896175 MB/sec, 1.6659010326328503 kHz\n# 1000 entries, 883 branches, 4.067909240722656 MB, 1.3497390747070312 sec, 3.0138486148558896 MB/sec, 0.740883937302516 kHz\n###total time elapsed for reading + specs computing: 2.2570559978485107 sec; number of chunks 2\n###total time elapsed for reading: 1.9500117301940918 sec; number of chunks 2\n\n--- first pass: 1131872 events, (648-flat, 232-jagged) branches, 2463 attrs\nVMEM used: 29.896704 (MB) SWAP used: 0.0 (MB)\n<__main__.RootDataReader object at 0x7fb0cdfe4a00> init is complete in 2.265552043914795 sec\nNumber of events  : 1131872\n# flat branches   : 648\nCaloMET_phi values in [-3.140625, 3.13671875] range, dim=N/A\nCaloMET_pt values in [0.783203125, 257.75] range, dim=N/A\nCaloMET_sumEt values in [820.0, 3790.0] range, dim=N/A\n

More examples about using uproot may be found here and here.

"},{"location":"training/MLaaS4HEP.html#how-to-train-ml-models-on-hep-root-data","title":"How to train ML models on HEP ROOT data","text":"

The MLaaS4HEP framework allows to train ML models in different ways: - using full dataset (i.e. the entire amount of events stored in input ROOT files) - using chunks, as subsets of a dataset, which dimension can be chosen directly by the user and can vary between 1 and the total number of events - using local or remote ROOT files.

The training phase is managed by the workflow.py module which performs the following actions: - read all input ROOT files in chunks to compute a specs file (where the main information about the ROOT files are stored: the dimension of branches, the minimum and the maximum for each branch, and the number of events for each ROOT file) - perform the training cycle (each time using a new chunk of events) - create a new chunk of events taken proportionally from the input ROOT files - extract and convert each event in a list of NumPy arrays - normalize the events - fix the Jagged Arrays dimension - create the masking vector - use the chunk to train the ML model provided by the user

A schematic representation of the steps performed in the MLaaS4HEP pipeline, in particular those inside the Data Streaming and Data Training layers, is:

If the dataset is large and exceed the amount of RAM on the training node, then the user should consider the chunk approach. This allows to train the ML model each time using a different chunk, until the entire dataset is completely read. In this case the user should pay close attention to the ML model convergence, and validate it after each chunk. For more information look at this, this and this. Using different training approach has pros and cons. For instance, training on entire dataset can guarantee the ML model convergence, but the dataset should fits into RAM of the training node. While chunk approach allows to split the dataset to fit in the hardware resources, but it requires proper model evaluation after each chunk training. In terms of training speed, this choice should be faster than training on the entire dataset, since after having used a chunk for training, that chunk is no longer read and used subsequently (this effect is prominent when remote ROOT files are used). Finally, user should be aware of potential divergence of ML model when training last chunk of the dataset and check for bias towards last chunk. For instance, user may implement a K-fold cross validation approach to train on N-1 chunks (i.e. folds in this case) and use one chunk for validation.

A detailed description of how to use the workflow.py module for training a ML model reading ROOT files from the opendata portal, can be found here. Please see how the user has to provide several information when run the workflow.py module, e.g. the definition of the ML model, and then is task of MLaaS4HEP framework to perform all the training procedure using the ML model provided by the user.

For a complete description of MLaaS4HEP see this paper.

"},{"location":"training/autoencoders.html","title":"Autoencoders","text":""},{"location":"training/autoencoders.html#introduction","title":"Introduction","text":"

Autoencoders are a powerful tool that has gained popularity in HEP and beyond recently. These types of algorithms are neural networks that learn to decompress data with minimal reconstruction error (Goodfellow, et. al.).

The idea of using neural networks for dimensionality reduction or feature learning dates back to the early 1990s. Autoencoders, or \"autoassociative neural networks,\" were originally proposed as a nonlinear generalization of principle component analysis (PCA) (Kramer). More recently, connections between autoencoders and latent variable models have brought these types of algorithms into the generative modeling space.

The two main parts of an autoencoder algorithm are the encoder function \\(f(x)\\) and the decoder function \\(g(x)\\). The learning process of an autoencoder is a minimization of a loss function, \\(L(x,g(f(x)))\\), that compares the original data to the output of the decoder, similar to that of a neural network. As such, these algorithms can be trained using the same techniques, like minibatch gradient descent with backpropagation. Below is a representation of an autoencoder from Mathworks.

"},{"location":"training/autoencoders.html#constrained-autoencoders-undercomplete-and-regularized","title":"Constrained Autoencoders (Undercomplete and Regularized)","text":"

Information in this section can be found in Goodfellow, et. al.

An autoencoder that is able to perfectly reconstruct the original data one-to-one, such that \\(g(f(x)) = x\\), is not very useful for extracting salient information from the data. There are several methods imposed on simple autoencoders to encourage them to extract useful aspects of the data.

One way of avoiding perfect data reconstruction is by constraining the dimension of the encoding function \\(f(x)\\) to be less than the data \\(x\\). These types of autoencoders are called undercomplete autoencoders, which force the imperfect copying of the data such that the encoding and decoding networks can prioritize the most useful aspects of the data.

However, if undercomplete encoders are given too much capacity, they will struggle to learn anything of importance from the data. Similarly, this problem occurs in autoencoders with encoder dimensionality greater than or equal to the data (the overcomplete case). In order to train any architecture of AE successfully, constraints based on the complexity of the target distribution must be imposed, apart from small dimensionality. These regularized autoencoders can have constraints on sparsity, robustness to noise, and robustness to changes in data (the derivative).

"},{"location":"training/autoencoders.html#sparse-autoencoders","title":"Sparse Autoencoders","text":"

Sparse autoencoders place a penalty to enforce sparsity in the encoding layer \\(\\mathbf{h} = f(\\mathbf{x})\\) such that \\(L(\\mathbf{x}, g(f(\\mathbf{x}))) + \\Omega(\\mathbf{h})\\). This penalty prevents the autoencoder from learning the identity transformation, extracting useful features of the data to be used in later tasks, such as classification. While the penalty term can be thought of as a regularizing term for a feedforward network, we can expand this view to think of the entire sparse autoencoder framework as approximating the maximum likelihood estimation of a generative model with latent variables \\(h\\). When approximating the maximum likelihood, the joint distribution \\(p_{\\text{model}}(\\mathbf{x}, \\mathbf{h})\\) can be approximated as

\\[ \\text{log} [ p_{\\text{model}}(\\mathbf{x})] = \\text{log} [p_{\\text{model}}(\\mathbf{h})] + [\\text{log} p_{\\text{model}}(\\mathbf{x} | \\mathbf{h})] \\]

where \\(p_{\\text{model}}(\\mathbf{h})\\) is the prior distribution over the latent variables, instead of the model's parameters. Here, we approximate the sum over all possible prior distribution values to be a point estimate at one highly likely value of \\(\\mathbf{h}\\). This prior term is what introduces the sparsity requirement, for example with the Laplace prior, $$ p_{\\text{model}}(h_i) = \\frac{\\lambda}{2}e^{-\\lambda|h_i|}. $$

The log-prior is then

$$ \\text{log} [p_{\\text{model}}(\\mathbf{h})] = \\sum_i (\\lambda|h_i| - \\text{log}\\frac{\\lambda}{2}) = \\Omega(\\mathbf{h}) + \\text{const}. $$ This example demonstrates how the model's distribution over latent variables (prior) gives rise to a sparsity penalty.

"},{"location":"training/autoencoders.html#penalized-autoencoders","title":"Penalized Autoencoders","text":"

Similar to sparse autoencoders, a traditional penalty term can be introduced to the cost function to regularize the autoencoder, such that the function to minimize becomes $$ L(\\mathbf{x},g(f(\\mathbf{x}))) + \\Omega(\\mathbf{h},\\mathbf{x}). $$ where $$ \\Omega(\\mathbf{h},\\mathbf{x}) = \\lambda\\sum_i ||\\nabla_{\\mathbf{x}}h_i||^2. $$ Because of the dependence on the gradient of the latent variables with respect to the input variables, if \\(\\mathbf{x}\\) changes slightly, the model is penalized for learning those slight variations. This type of regularization leads to a contractive autoencoder (CAE).

"},{"location":"training/autoencoders.html#denoising-autoencoders","title":"Denoising Autoencoders","text":"

Another way to encourage autoencoders to learn useful features of the data is training the algorithm to minimize a cost function that compares the original data (\\(\\mathbf{x}\\)) to encoded and decoded data that has been injected with noise (\\(f(g(\\mathbf{\\tilde{x}}))\\), $$ L(\\mathbf{x},g(f(\\mathbf{\\tilde{x}}))) $$ Denoising autoencoders then must learn to undo the effect of the noise in the encoded/decoded data. The autoencoder is able to learn the structure of the probability density function of the data (\\(p_{\\text{data}}\\)) as a function of the input variables (\\(x\\)) through this process (Alain, Bengio, Bengio, et. al.). With this type of cost function, even overcomplete, high-capacity autoencoders can avoid learning the identity transformation.

"},{"location":"training/autoencoders.html#variational-autoencoders","title":"Variational Autoencoders","text":"

Variational autoencoders (VAEs), introduced by Kigma and Welling, are similar to normal AEs. They are comprised of neural nets, which maps the input to latent space (encoder) and back (decoder), where the latent space is a low-dimensional, variational distribution. VAEs are bidirectional, generating data or estimating distributions, and were initially designed for unsupervised learning but can also be very useful in semi-supervised and fully supervised scenarios (Goodfellow, et. al.).

VAEs are trained by maximizing the variational lower bound associated with data point \\(\\mathbf{x}\\), which is a function of the approximate posterior (inference network, or encoder), \\(q(\\mathbf{z})\\). Latent variable \\(\\mathbf{z}\\) is drawn from this encoder distribution, with \\(p_\\text{model}(\\mathbf{x} | \\mathbf{z})\\) viewed as the decoder network. The variational lower bound (also called the evidence lower bound or ELBO) is a trade-off between the join log-likelihood of the visible and latent variables, and the KL divergence between the model prior and the approximate posterior, shown below (Goodfellow, et. al.).

$$ \\mathcal{L}(q) = E_{\\mathbf{z} \\sim q(\\mathbf{z} | \\mathbf{x})} \\text{log}p_\\text{model}(\\mathbf{x} | \\mathbf{z}) - D_\\text{KL}(q || p) $$.

Methods for optimizing the VAE by learning the variational lower bound include EM meta-algorithms like probabilistic PCA (Goodfellow, et. al.).

"},{"location":"training/autoencoders.html#applications-in-hep","title":"Applications in HEP","text":"

One of the more popular applications of AEs in HEP include anomaly detection. Because autoencoders are trained to learn latent features of a dataset, any new data that does not match those features could be classified as an anomaly and picked out by the AE. Examples of AEs for anomaly detection in HEP are listed below:

  • Anomaly detection in high-energy physics using a quantum autoencoder
  • Particle Graph Autoencoders and Differentiable, Learned Energy Mover's Distance
  • Bump Hunting in Latent Space

Another application of (V)AEs in HEP is data generation, as once the likelihood of the latent variables is approximated it can be used to generate new data. Examples of this application in HEP for simulation of various physics processes are listed below:

  • Deep generative models for fast shower simulation in ATLAS
  • Sparse Data Generation for Particle-Based Simulation of Hadronic Jets in the LHC
  • Variational Autoencoders for Jet Simulation

Finally, the latent space learned by (V)AEs give a parsimonious and information-rich phase space from which one can make inferences. Examples of using (V)AEs to learn approximate and/or compressed representations of data are given below:

  • An Exploration of Learnt Representations of W Jets
  • Machine-Learning Compression for Particle Physics Discoveries
  • Decoding Photons: Physics in the Latent Space of a BIB-AE Generative Network

More examples of (V)AEs in HEP can be found at the HEP ML Living Review.

"},{"location":"training/autoencoders.html#references","title":"References","text":"
  • Goodfellow, et. al., 2016, Deep Learning
  • Alain, Bengio, 2013, \"What Regularized Auto-Encoders Learn from the Data Generating Distribution\"
  • Bengio, et. al., 2013, \"Generalized Denoising Auto-Encoders as Generative Models\"
  • Kramer, 1991, \"Nonlinear principle component analysis using autoassociative neural networks\"
  • Kingma, Welling, 2013, \"Auto-Encoding Variational Bayes\"
"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index ba7c481..84950aa 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -210,6 +210,11 @@ 2023-12-05 daily + + https://cms-ml.github.io/documentation/training/Decorrelation.html + 2023-12-05 + daily + https://cms-ml.github.io/documentation/training/MLaaS4HEP.html 2023-12-05 diff --git a/software_envs/containers.html b/software_envs/containers.html index a73f05f..52dea51 100644 --- a/software_envs/containers.html +++ b/software_envs/containers.html @@ -1,4 +1,4 @@ - Using containers - CMS Machine Learning Documentation

Using containers

Containers are a great solution to isolate a software environment, especially in batch systems like lxplus. At the moment two container solutations are supported Apptainer ( previously called Singularity), and Docker.

Using Singularity

The unpacked.cern.ch service mounts on CVMFS contains many singularity images, some of which are suitable for machine learning applications. A description of each of the images is beyond the scope of this document. However, if you find an image which is useful for your application, you can use if by running a Singularity container with the appropriate options. For example:

singularity run --nv --bind <bind_mount_path> /cvmfs/unpacked.cern.ch/<path_to_image>
+ Using containers - CMS Machine Learning Documentation       

Using containers

Containers are a great solution to isolate a software environment, especially in batch systems like lxplus. At the moment two container solutations are supported Apptainer ( previously called Singularity), and Docker.

Using Singularity

The unpacked.cern.ch service mounts on CVMFS contains many singularity images, some of which are suitable for machine learning applications. A description of each of the images is beyond the scope of this document. However, if you find an image which is useful for your application, you can use if by running a Singularity container with the appropriate options. For example:

singularity run --nv --bind <bind_mount_path> /cvmfs/unpacked.cern.ch/<path_to_image>
 

Examples

After installing package, you can then use GPU based machine learning algorithms. Two examples are supplied as an example.

  1. The first example aims at using a CNN to perform handwritten digits classification with MNIST dataset. The whole notebook can be found at pytorch_mnist. This example is modified from an official pytorch example.

  2. The second example is modified from the simple MLP example from weaver-benchmark. The whole notebook can be found at toptagging_mlp.

Using Docker

Docker is not supported at the moment in the interactive node of lxplus (like lxplus-gpu). However Docker is supported on HTCondor for job submission.

This option can be very handy for users, as HTCondor can pull images from any public registry, like DockerHub or GitLab registry. The user can follow this workflow: 1. Define a custom image on top of a commonly available pytorch or tensorflow image 2. Add the desidered packages and configuration 3. Push the docker image on a registry 4. Use the image in a HTCondor job

The rest of the page is a step by step tutorial for this workflow.

Define the image

  1. Define a file Dockerfile

    FROM pytorch/pytorch:latest
     
     ADD localfolder_with_code /opt/mycode
    diff --git a/software_envs/lcg_environments.html b/software_envs/lcg_environments.html
    index e584a8f..6e65d2e 100644
    --- a/software_envs/lcg_environments.html
    +++ b/software_envs/lcg_environments.html
    @@ -1,4 +1,4 @@
    - LCG environments - CMS Machine Learning Documentation       

    LCG environments

    Software Environment

    The software environment for ML application trainings can be setup in different ways. In this page we focus on the CERN lxplus environment.

    LCG release software

    Checking out an ideal software bundle with Cuda support at http://lcginfo.cern.ch/, one can set up an LCG environment by executing

    source /cvmfs/sft.cern.ch/lcg/views/<name of bundle>/**x86_64-centos*-gcc11-opt**/setup.sh
    + LCG environments - CMS Machine Learning Documentation       

    LCG environments

    Software Environment

    The software environment for ML application trainings can be setup in different ways. In this page we focus on the CERN lxplus environment.

    LCG release software

    Checking out an ideal software bundle with Cuda support at http://lcginfo.cern.ch/, one can set up an LCG environment by executing

    source /cvmfs/sft.cern.ch/lcg/views/<name of bundle>/**x86_64-centos*-gcc11-opt**/setup.sh
     

    On lxplus-gpu nodes, usually equipped with AlmaLinux 9.1 (also called Centos9), one should use the proper lcg release. At the time of writing (May 2023) the recommended environment to use GPUs is:

    source /cvmfs/sft.cern.ch/lcg/views/LCG_103cuda/x86_64-centos9-gcc11-opt/setup.sh
     

    Customized environments

    One can create custom Python environment using virtualenv or venv tools, in order to avoid messing up with the global python environment.

    The user has the choice of building a virtual environment from scratch or by basing on top of a LCG release.

    Virtual environment from scratch

    The first approach is cleaner but requires downloading the full set of libraries needed for pytorch or TensorFlow (very heavy). Moreover the compatibility with the computing environment (usually lxplus-gpu) is not guaranteed.

    1. Create the environment in a folder of choice, usually called myenv

      python3 -m venv --system-site-packages myenv
       source myenv/bin/activate   # activate the environment
      diff --git a/training/Decorrelation.html b/training/Decorrelation.html
      new file mode 100644
      index 0000000..8ce3b00
      --- /dev/null
      +++ b/training/Decorrelation.html
      @@ -0,0 +1 @@
      + Decorrelation - CMS Machine Learning Documentation       

      Decorrelation

      When preparing to train a machine learning algorithm, it is important to think about the correlations of the output and their impact on how the trained model is used. Generally, the goal of any training is to maximize correlations with variables of interests. For example, a classifier is trained specifically to be highly correlated with the classification categories. However, there is often another set of variables that high correlation with the ML algorithm's output is not desirable and could make the ML algorithm useless regardless of its overall performance.

      There are numerous methods that achieve the goal of minimizing correlations of ML algorithms. Choosing the correct decorrelation method depends on the situation, e.g., which ML algorithm is being used and the type of the undesirable variables. Below, we detail various methods for common scenarios focusing on BDT (boosted decision tree) and neural network algorithms.

      Impartial Training Data

      Generally, the best method for making a neural network's or BDT's output independent of some known variable is to remove any bias in the training dataset, which is commonly done by adding or removing information.

      Adding Information

      • Training on a mix of signals with different masses can help prevent the BDT from learning the mass.

      Removing Information

      • If you have any input variables that are highly correlated with the mass, you may want to omit them. There may be a loss of raw discrimination power with this approach, but the underlying interpretation will be more sound.

      Reweighting

      • One method to achieve correlation by weighting data is reweighting the network's input samples to match a reference distribution. Examples input variables include mass, or an input to invariant mass, like the \(p_T\). This method is distinct from flattening the data since it is weighted to match a target distribution rather than a flat distribution. Flattening can also require very large weights that could potentially affect training. This is one way to avoid having the network sculpt, or learn, a certain kinematic quantity, like the background mass. An example of this technique is given in EXO-19-020.
        • This is what is done for the ImageTop tagger and ParticleNet group of taggers. reweighted_BDT_scores BDT scores from EXO-10-020 where the jet \(p_T\) distribution is reweighted to match a reference distribution for each sample.

      Adversarial Approach

      Adversarial approaches to decorrelation revolve around including a penalty, or regularization, term in the loss function in training. The loss function can be modified to enforce uniformity in the variable of interest (i.e. mass). Check out these links (1, 2, 3) for some examples of this. One way to technically implement this type of approach is using the "flatness loss function" (i.e. BinFlatnessLossFunction in the hep-ml package). This type of decorrelation what is done for the DeepAK8-MD taggers.

      Another type of regularization one can do to acheive decorrelation is penalizing the loss function on a certain type of correlation, for example distance. In the seminal distance correlation in HEP-ML paper ((DisCo Fever: Robust Networks Through Distance Correlation)), distance is in this case is defined as distance correlation (DisCo), a measure derived from distance covariance, first introduced here. This distance correlation function calculates the non-linear correlation between the NN output and some variables that you care about, e.g. jet mass, that you can force the network to minimize which decorrelates the two variables by including it as a penalty term in the loss function. An extension of this can be found in the Double DisCo method, given below, which highlights the distance correlation term in the loss function at the bottom. The Double DisCo network leverages the ABCD method for background estimation, which is why it requires two separate discriminants. Below is the Double DisCo NN architecture used in MLG-23-003. Notice the two separate discriminant paths consisting of a Dense layer, a Dropout layer, and another Dense layer before outputting a single discriminant per path.

      disco_method Source: CMS AN-22-101 for MLG-23-003.

      Many thanks to Kevin Pedro for his input on this section and the previous one.

      Parametric Cut

      When designing jet taggers, variables of interest for discriminators include N-subjettiness derived quantities. Often, these quantities will be correlated with, for example, the \(p_T\) of the jet. One example of this type of correlation is called "mass scuplting" and happens when the distribution of the discriminating variable in background begins to exhibit a shape similar to that of the signal with successive cuts. This correlation can have confounding effects in the tagger and one way to remove these effects is to parametrically cut on the discriminant.

      One such prescription to remove these correlations is described here and focuses on removing the \(p_T\) dependence in the soft-drop mass variable \(\rho\). The authors note that there is a \(p_T\) dependence in the N-subjettiness ratio \(\tau_2/\tau_1\) as a function of the QCD jet scaling (soft-drop) variable, defined as \(\rho = log(m^2)(p_T^2)\), which leads to mass sculpting. In order to alleviate this issue, the authors introduce a modified version of the soft-drop variable, \(\rho' = \rho + log(p_T/\mu)\) where \(\mu\) is chosen to be 1 GeV. It can also be noted that there is a linear depedence between \(\tau_2/\tau_1\) and \(\rho'\). Here, the authors remedy this by modelling the linear depedence with \(\tau_{21}' + \tau_2/\tau_1 - M \times \rho'\) where \(M\) is fit from the data. Applying both these transformations flattens out the relationship between the ratio and the soft-drop variable and removes the mass sculpting effects. It is imperative that the transformation between variables are smooth, as discontinuous functions may lead to artificial features in the data.

      Methods for mass parameterized BDTs

      Finally, when using a BDT that is parameterized by a mass quantity of interest, the output can be decorrelated from that mass by three different methods: randomization, oversampling, and variable profiling. Randomization entails randomly pairing a mass quanitity to a background training event so the BDT does not learn any meaningful associations between the mass and the output. For oversampling, this is a bootstrapping method where every input background event is paired with a potential mass point so the effective statistics for all the mass points are the same. Finally, variable profiling has the user profile each BDT input as a function of the mass quantity of interest. Examples of each of these methods is given below in the context of a di-higgs search.

      A di-higgs multilepton search (HIG-21-002) made use of a BDT for signal discrimination, parameterized by the di-higgs invariant mass. In order to avoid correlations in the BDT output and invariant mass of the di-higgs system, they looked at decorrelation via randomization, oversampling, and variable profiling. All of the methods utilized a (more or less) 50/50 dataset train/test split where one iteration of the BDT was trained on "even" numbered events and the datacards were produced with the "odd" numbered events. This procedure was repeated for the opposite configuration. Then, to deteremine if the BDT was correctly interpolating the signal masses, one mass point was omitted from training and the results of this BDT were compared to a BDT trained on only this single, omitted mass point. For each train/test configuration (even/odd or odd/even), the BDT's performance gain, as well as loss, were evaluated with ROC curves with two ommitted mass points (done separately).

      In the randomization method, a generator-level di-higgs invariant mass was randomly assigned to each background event the BDT was trained on. For the oversampling method, every signal mass point was assigned to a duplicate of each background event. Obviously the oversampling method leads to slower execution but the same effective statistics for all backgrounds and each signal mass. Conversely, the randomization approach is quicker, but leads to reduced effective statistics. Lastly, to improve performance over lower signal masses, each BDT input variable was profiled as a function of \(m_{HH}\). This profile was fit with a polynomial function, and then each point in the input distribution is divided by the fit function value. This corrected ratio is used as the new input to the BDT. The authors also found that splitting the BDT training into high and low mass regions helped.

      In the end, oversampling, especially when combined with input variable corrections, provided a sizable performance gain (5.6%) over the randomization method. This gain is determined from ROC curves made for each training iteration (even/odd or odd/event) and each method. The performance loss is also a 5% improvement over the randomization method.

      For more information on these methods, see the HIG-21-002. Below are some example BDT output scores for the \(2\ell ss\) and \(3 \ell\) channels for this analysis.

      mass_param_BDT Source: HIG-21-002

      So far we have seen decorrelation achieved by using inputs that are decorrelated for the classifier and regularizing the output to penalize learning correlations. Another approach can be to learn decorrelation by maximizing performance metrics that more closely align with the sensitivity of the analysis, like in this paper and their corresponding Python-based package, ThickBrick. In this case, the authors study the dependence of the event selection threshold on the signal purity in a given bin of the distribution of an observable. They demonstrate that the threshold increases with signal purity, "implying that the threshold is stronger in the x-'bins' of higher purity." This parametric event selection threshold "naturally leads to decorrelation of the event selection criteria from the event variable x." The failure to incorporate the dependencies on observable distributions is framed as a misalignment between the ML-based selector and the sensitivity of the physics analysis. A demo of their package, ThickBrick, was given at PyHEP2020.


      Last update: December 5, 2023
      \ No newline at end of file diff --git a/training/MLaaS4HEP.html b/training/MLaaS4HEP.html index 97a8b8f..bf75a9e 100644 --- a/training/MLaaS4HEP.html +++ b/training/MLaaS4HEP.html @@ -1,4 +1,4 @@ - MLaaS4HEP - CMS Machine Learning Documentation

      MLaaS4HEP

      Machine Learning as a Service for HEP

      MLaaS for HEP is a set of Python-based modules to support reading HEP data and stream them to the ML tool of the user's choice. It consists of three independent layers: - Data Streaming layer to handle remote data, see reader.py - Data Training layer to train ML model for given HEP data, see workflow.py - Data Inference layer, see tfaas_client.py

      The MLaaS4HEP resopitory can be found here.

      The general architecture of MLaaS4HEP looks like this: MLaaS4HEP-architecture

      Even though this architecture was originally developed for dealing with HEP ROOT files, we extend it to other data formats. As of right now, following data formats are supported: JSON, CSV, Parquet, and ROOT. All of the formats support reading files from the local file system or HDFS, while the ROOT format supports reading files via the XRootD protocol.

      The pre-trained models can be easily uploaded to TFaaS inference server for serving them to clients. The TFaaS documentation can be found here.

      Dependencies

      Here is a list of the dependencies: - pyarrow for reading data from HDFS file system - uproot for reading ROOT files - numpy, pandas for data representation - modin for fast panda support - numba for speeing up individual functions

      Installation

      The easiest way to install and run MLaaS4HEP and TFaaS is to use pre-build docker images

      # run MLaaS4HEP docker container
      + MLaaS4HEP - CMS Machine Learning Documentation       

      MLaaS4HEP

      Machine Learning as a Service for HEP

      MLaaS for HEP is a set of Python-based modules to support reading HEP data and stream them to the ML tool of the user's choice. It consists of three independent layers: - Data Streaming layer to handle remote data, see reader.py - Data Training layer to train ML model for given HEP data, see workflow.py - Data Inference layer, see tfaas_client.py

      The MLaaS4HEP resopitory can be found here.

      The general architecture of MLaaS4HEP looks like this: MLaaS4HEP-architecture

      Even though this architecture was originally developed for dealing with HEP ROOT files, we extend it to other data formats. As of right now, following data formats are supported: JSON, CSV, Parquet, and ROOT. All of the formats support reading files from the local file system or HDFS, while the ROOT format supports reading files via the XRootD protocol.

      The pre-trained models can be easily uploaded to TFaaS inference server for serving them to clients. The TFaaS documentation can be found here.

      Dependencies

      Here is a list of the dependencies: - pyarrow for reading data from HDFS file system - uproot for reading ROOT files - numpy, pandas for data representation - modin for fast panda support - numba for speeing up individual functions

      Installation

      The easiest way to install and run MLaaS4HEP and TFaaS is to use pre-build docker images

      # run MLaaS4HEP docker container
       docker run veknet/mlaas4hep
       # run TFaaS docker container
       docker run veknet/tfaas
      diff --git a/training/autoencoders.html b/training/autoencoders.html
      index d1f9290..63c4652 100644
      --- a/training/autoencoders.html
      +++ b/training/autoencoders.html
      @@ -1,4 +1,4 @@
      - Autoencoders - CMS Machine Learning Documentation       

      Autoencoders

      Introduction

      Autoencoders are a powerful tool that has gained popularity in HEP and beyond recently. These types of algorithms are neural networks that learn to decompress data with minimal reconstruction error (Goodfellow, et. al.).

      The idea of using neural networks for dimensionality reduction or feature learning dates back to the early 1990s. Autoencoders, or "autoassociative neural networks," were originally proposed as a nonlinear generalization of principle component analysis (PCA) (Kramer). More recently, connections between autoencoders and latent variable models have brought these types of algorithms into the generative modeling space.

      The two main parts of an autoencoder algorithm are the encoder function \(f(x)\) and the decoder function \(g(x)\). The learning process of an autoencoder is a minimization of a loss function, \(L(x,g(f(x)))\), that compares the original data to the output of the decoder, similar to that of a neural network. As such, these algorithms can be trained using the same techniques, like minibatch gradient descent with backpropagation. Below is a representation of an autoencoder from Mathworks.

      autoencoder_model

      Constrained Autoencoders (Undercomplete and Regularized)

      Information in this section can be found in Goodfellow, et. al.

      An autoencoder that is able to perfectly reconstruct the original data one-to-one, such that \(g(f(x)) = x\), is not very useful for extracting salient information from the data. There are several methods imposed on simple autoencoders to encourage them to extract useful aspects of the data.

      One way of avoiding perfect data reconstruction is by constraining the dimension of the encoding function \(f(x)\) to be less than the data \(x\). These types of autoencoders are called undercomplete autoencoders, which force the imperfect copying of the data such that the encoding and decoding networks can prioritize the most useful aspects of the data.

      However, if undercomplete encoders are given too much capacity, they will struggle to learn anything of importance from the data. Similarly, this problem occurs in autoencoders with encoder dimensionality greater than or equal to the data (the overcomplete case). In order to train any architecture of AE successfully, constraints based on the complexity of the target distribution must be imposed, apart from small dimensionality. These regularized autoencoders can have constraints on sparsity, robustness to noise, and robustness to changes in data (the derivative).

      Sparse Autoencoders

      Sparse autoencoders place a penalty to enforce sparsity in the encoding layer \(\mathbf{h} = f(\mathbf{x})\) such that \(L(\mathbf{x}, g(f(\mathbf{x}))) + \Omega(\mathbf{h})\). This penalty prevents the autoencoder from learning the identity transformation, extracting useful features of the data to be used in later tasks, such as classification. While the penalty term can be thought of as a regularizing term for a feedforward network, we can expand this view to think of the entire sparse autoencoder framework as approximating the maximum likelihood estimation of a generative model with latent variables \(h\). When approximating the maximum likelihood, the joint distribution \(p_{\text{model}}(\mathbf{x}, \mathbf{h})\) can be approximated as

      \[ \text{log} [ p_{\text{model}}(\mathbf{x})] = \text{log} [p_{\text{model}}(\mathbf{h})] + [\text{log} p_{\text{model}}(\mathbf{x} | \mathbf{h})] \]

      where \(p_{\text{model}}(\mathbf{h})\) is the prior distribution over the latent variables, instead of the model's parameters. Here, we approximate the sum over all possible prior distribution values to be a point estimate at one highly likely value of \(\mathbf{h}\). This prior term is what introduces the sparsity requirement, for example with the Laplace prior, $$ p_{\text{model}}(h_i) = \frac{\lambda}{2}e^{-\lambda|h_i|}. $$

      The log-prior is then

      $$ \text{log} [p_{\text{model}}(\mathbf{h})] = \sum_i (\lambda|h_i| - \text{log}\frac{\lambda}{2}) = \Omega(\mathbf{h}) + \text{const}. $$ This example demonstrates how the model's distribution over latent variables (prior) gives rise to a sparsity penalty.

      Penalized Autoencoders

      Similar to sparse autoencoders, a traditional penalty term can be introduced to the cost function to regularize the autoencoder, such that the function to minimize becomes $$ L(\mathbf{x},g(f(\mathbf{x}))) + \Omega(\mathbf{h},\mathbf{x}). $$ where $$ \Omega(\mathbf{h},\mathbf{x}) = \lambda\sum_i ||\nabla_{\mathbf{x}}h_i||^2. $$ Because of the dependence on the gradient of the latent variables with respect to the input variables, if \(\mathbf{x}\) changes slightly, the model is penalized for learning those slight variations. This type of regularization leads to a contractive autoencoder (CAE).

      Denoising Autoencoders

      Another way to encourage autoencoders to learn useful features of the data is training the algorithm to minimize a cost function that compares the original data (\(\mathbf{x}\)) to encoded and decoded data that has been injected with noise (\(f(g(\mathbf{\tilde{x}}))\), $$ L(\mathbf{x},g(f(\mathbf{\tilde{x}}))) $$ Denoising autoencoders then must learn to undo the effect of the noise in the encoded/decoded data. The autoencoder is able to learn the structure of the probability density function of the data (\(p_{\text{data}}\)) as a function of the input variables (\(x\)) through this process (Alain, Bengio, Bengio, et. al.). With this type of cost function, even overcomplete, high-capacity autoencoders can avoid learning the identity transformation.

      Variational Autoencoders

      Variational autoencoders (VAEs), introduced by Kigma and Welling, are similar to normal AEs. They are comprised of neural nets, which maps the input to latent space (encoder) and back (decoder), where the latent space is a low-dimensional, variational distribution. VAEs are bidirectional, generating data or estimating distributions, and were initially designed for unsupervised learning but can also be very useful in semi-supervised and fully supervised scenarios (Goodfellow, et. al.).

      VAEs are trained by maximizing the variational lower bound associated with data point \(\mathbf{x}\), which is a function of the approximate posterior (inference network, or encoder), \(q(\mathbf{z})\). Latent variable \(\mathbf{z}\) is drawn from this encoder distribution, with \(p_\text{model}(\mathbf{x} | \mathbf{z})\) viewed as the decoder network. The variational lower bound (also called the evidence lower bound or ELBO) is a trade-off between the join log-likelihood of the visible and latent variables, and the KL divergence between the model prior and the approximate posterior, shown below (Goodfellow, et. al.).

      $$ \mathcal{L}(q) = E_{\mathbf{z} \sim q(\mathbf{z} | \mathbf{x})} \text{log}p_\text{model}(\mathbf{x} | \mathbf{z}) - D_\text{KL}(q || p) $$.

      Methods for optimizing the VAE by learning the variational lower bound include EM meta-algorithms like probabilistic PCA (Goodfellow, et. al.).

      cms-ml/documentation

      Autoencoders

      Introduction

      Autoencoders are a powerful tool that has gained popularity in HEP and beyond recently. These types of algorithms are neural networks that learn to decompress data with minimal reconstruction error (Goodfellow, et. al.).

      The idea of using neural networks for dimensionality reduction or feature learning dates back to the early 1990s. Autoencoders, or "autoassociative neural networks," were originally proposed as a nonlinear generalization of principle component analysis (PCA) (Kramer). More recently, connections between autoencoders and latent variable models have brought these types of algorithms into the generative modeling space.

      The two main parts of an autoencoder algorithm are the encoder function \(f(x)\) and the decoder function \(g(x)\). The learning process of an autoencoder is a minimization of a loss function, \(L(x,g(f(x)))\), that compares the original data to the output of the decoder, similar to that of a neural network. As such, these algorithms can be trained using the same techniques, like minibatch gradient descent with backpropagation. Below is a representation of an autoencoder from Mathworks.

      autoencoder_model

      Constrained Autoencoders (Undercomplete and Regularized)

      Information in this section can be found in Goodfellow, et. al.

      An autoencoder that is able to perfectly reconstruct the original data one-to-one, such that \(g(f(x)) = x\), is not very useful for extracting salient information from the data. There are several methods imposed on simple autoencoders to encourage them to extract useful aspects of the data.

      One way of avoiding perfect data reconstruction is by constraining the dimension of the encoding function \(f(x)\) to be less than the data \(x\). These types of autoencoders are called undercomplete autoencoders, which force the imperfect copying of the data such that the encoding and decoding networks can prioritize the most useful aspects of the data.

      However, if undercomplete encoders are given too much capacity, they will struggle to learn anything of importance from the data. Similarly, this problem occurs in autoencoders with encoder dimensionality greater than or equal to the data (the overcomplete case). In order to train any architecture of AE successfully, constraints based on the complexity of the target distribution must be imposed, apart from small dimensionality. These regularized autoencoders can have constraints on sparsity, robustness to noise, and robustness to changes in data (the derivative).

      Sparse Autoencoders

      Sparse autoencoders place a penalty to enforce sparsity in the encoding layer \(\mathbf{h} = f(\mathbf{x})\) such that \(L(\mathbf{x}, g(f(\mathbf{x}))) + \Omega(\mathbf{h})\). This penalty prevents the autoencoder from learning the identity transformation, extracting useful features of the data to be used in later tasks, such as classification. While the penalty term can be thought of as a regularizing term for a feedforward network, we can expand this view to think of the entire sparse autoencoder framework as approximating the maximum likelihood estimation of a generative model with latent variables \(h\). When approximating the maximum likelihood, the joint distribution \(p_{\text{model}}(\mathbf{x}, \mathbf{h})\) can be approximated as

      \[ \text{log} [ p_{\text{model}}(\mathbf{x})] = \text{log} [p_{\text{model}}(\mathbf{h})] + [\text{log} p_{\text{model}}(\mathbf{x} | \mathbf{h})] \]

      where \(p_{\text{model}}(\mathbf{h})\) is the prior distribution over the latent variables, instead of the model's parameters. Here, we approximate the sum over all possible prior distribution values to be a point estimate at one highly likely value of \(\mathbf{h}\). This prior term is what introduces the sparsity requirement, for example with the Laplace prior, $$ p_{\text{model}}(h_i) = \frac{\lambda}{2}e^{-\lambda|h_i|}. $$

      The log-prior is then

      $$ \text{log} [p_{\text{model}}(\mathbf{h})] = \sum_i (\lambda|h_i| - \text{log}\frac{\lambda}{2}) = \Omega(\mathbf{h}) + \text{const}. $$ This example demonstrates how the model's distribution over latent variables (prior) gives rise to a sparsity penalty.

      Penalized Autoencoders

      Similar to sparse autoencoders, a traditional penalty term can be introduced to the cost function to regularize the autoencoder, such that the function to minimize becomes $$ L(\mathbf{x},g(f(\mathbf{x}))) + \Omega(\mathbf{h},\mathbf{x}). $$ where $$ \Omega(\mathbf{h},\mathbf{x}) = \lambda\sum_i ||\nabla_{\mathbf{x}}h_i||^2. $$ Because of the dependence on the gradient of the latent variables with respect to the input variables, if \(\mathbf{x}\) changes slightly, the model is penalized for learning those slight variations. This type of regularization leads to a contractive autoencoder (CAE).

      Denoising Autoencoders

      Another way to encourage autoencoders to learn useful features of the data is training the algorithm to minimize a cost function that compares the original data (\(\mathbf{x}\)) to encoded and decoded data that has been injected with noise (\(f(g(\mathbf{\tilde{x}}))\), $$ L(\mathbf{x},g(f(\mathbf{\tilde{x}}))) $$ Denoising autoencoders then must learn to undo the effect of the noise in the encoded/decoded data. The autoencoder is able to learn the structure of the probability density function of the data (\(p_{\text{data}}\)) as a function of the input variables (\(x\)) through this process (Alain, Bengio, Bengio, et. al.). With this type of cost function, even overcomplete, high-capacity autoencoders can avoid learning the identity transformation.

      Variational Autoencoders

      Variational autoencoders (VAEs), introduced by Kigma and Welling, are similar to normal AEs. They are comprised of neural nets, which maps the input to latent space (encoder) and back (decoder), where the latent space is a low-dimensional, variational distribution. VAEs are bidirectional, generating data or estimating distributions, and were initially designed for unsupervised learning but can also be very useful in semi-supervised and fully supervised scenarios (Goodfellow, et. al.).

      VAEs are trained by maximizing the variational lower bound associated with data point \(\mathbf{x}\), which is a function of the approximate posterior (inference network, or encoder), \(q(\mathbf{z})\). Latent variable \(\mathbf{z}\) is drawn from this encoder distribution, with \(p_\text{model}(\mathbf{x} | \mathbf{z})\) viewed as the decoder network. The variational lower bound (also called the evidence lower bound or ELBO) is a trade-off between the join log-likelihood of the visible and latent variables, and the KL divergence between the model prior and the approximate posterior, shown below (Goodfellow, et. al.).

      $$ \mathcal{L}(q) = E_{\mathbf{z} \sim q(\mathbf{z} | \mathbf{x})} \text{log}p_\text{model}(\mathbf{x} | \mathbf{z}) - D_\text{KL}(q || p) $$.

      Methods for optimizing the VAE by learning the variational lower bound include EM meta-algorithms like probabilistic PCA (Goodfellow, et. al.).