Belle II Software  release-06-02-00
CDCTriggerNeuroTrainerModule.cc
1 /**************************************************************************
2  * basf2 (Belle II Analysis Software Framework) *
3  * Author: The Belle II Collaboration *
4  * *
5  * See git log for contributors and copyright holders. *
6  * This file is licensed under LGPL-3.0, see LICENSE.md. *
7  **************************************************************************/
8 #include "trg/cdc/modules/neurotrigger/CDCTriggerNeuroTrainerModule.h"
9 #ifdef HAS_OPENMP
10 #include <parallel_fann.hpp>
11 #else
12 #include <fann.h>
13 #endif
14 
15 #include <framework/datastore/StoreArray.h>
16 #include <mdst/dataobjects/MCParticle.h>
17 #include <tracking/dataobjects/RecoTrack.h>
18 #include <trg/cdc/dataobjects/CDCTriggerSegmentHit.h>
19 #include <trg/cdc/dataobjects/CDCTriggerTrack.h>
20 #include <framework/datastore/StoreObjPtr.h>
21 #include <framework/dataobjects/EventMetaData.h>
22 #include <framework/core/ModuleParam.templateDetails.h>
23 
24 #include <cdc/geometry/CDCGeometryPar.h>
25 #include <framework/gearbox/Unit.h>
26 
27 #include <fstream>
28 #include <cmath>
29 #include <TFile.h>
30 
31 using namespace Belle2;
32 using namespace std;
33 
34 //this line registers the module with the framework and actually makes it available
35 //in steering files or the the module list (basf2 -m).
36 REG_MODULE(CDCTriggerNeuroTrainer)
37 
39 {
40  setDescription(
41  "The NeuroTriggerTrainer module of the CDC trigger.\n"
42  "Takes track segments and 2D track estimates to prepare input data\n"
43  "for the training of a neural network.\n"
44  "Networks are trained after the event loop and saved.\n\n"
45  "Data preparation is done in two steps:\n"
46  "1. The MLP uses hits from a limited range around the 2D track. "
47  "To find this range, a histogram with the distance of hits to the 2D track "
48  "is prepared. The relevant ID range is determined by a threshold on "
49  "the hit counters or on the sum of the hit counters over the relevant range.\n"
50  "2. Input data is calculated from the hits, the 2D tracks and the ID ranges. "
51  "Target data is collected from a MCParticle or RecoTrack related to the 2D track."
52  );
53  // parameters for saving / loading
54  addParam("hitCollectionName", m_hitCollectionName,
55  "Name of the input StoreArray of CDCTriggerSegmentHits.",
56  string(""));
57  addParam("EventTimeName", m_EventTimeName,
58  "Name of the event time object.",
59  string(""));
60  addParam("inputCollectionName", m_inputCollectionName,
61  "Name of the StoreArray holding the 2D input tracks.",
62  string("TRGCDC2DFinderTracks"));
63  addParam("trainOnRecoTracks", m_trainOnRecoTracks,
64  "If true, use RecoTracks as targets instead of MCParticles.",
65  false);
66  addParam("targetCollectionName", m_targetCollectionName,
67  "Name of the MCParticle/RecoTrack collection used as target values.",
68  string("MCParticles"));
69  addParam("filename", m_filename,
70  "Name of the root file where the NeuroTrigger parameters will be saved.",
71  string("NeuroTrigger.root"));
72  addParam("trainFilename", m_trainFilename,
73  "Name of the root file where the generated training samples will be saved.",
74  string("NeuroTrigger.root"));
75  addParam("logFilename", m_logFilename,
76  "Base name of the text files where the training logs will be saved "
77  "(two for each sector, named logFilename_BestRun_i.log "
78  "and logFilename_AllOptima_i.log).",
79  string("NeuroTrigger"));
80  addParam("arrayname", m_arrayname,
81  "Name of the TObjArray to hold the NeuroTrigger parameters.",
82  string("MLPs"));
83  addParam("trainArrayname", m_trainArrayname,
84  "Name of the TObjArray to hold the training samples.",
85  string("trainSets"));
86  addParam("saveDebug", m_saveDebug,
87  "If true, save parameter distribution of training data "
88  "in train file and training curve in log file.", true);
89  addParam("load", m_load,
90  "Switch to load saved parameters if existing. "
91  "Take care not to duplicate training sets!", false);
92  // NeuroTrigger parameters
93  addParam("nMLP", m_parameters.nMLP,
94  "Number of expert MLPs.", m_parameters.nMLP);
95  addParam("nHidden", m_parameters.nHidden,
96  "Number of nodes in each hidden layer for all networks "
97  "or factor to multiply with number of inputs (1 list or nMLP lists). "
98  "The number of layers is derived from the shape.", m_parameters.nHidden);
99  addParam("multiplyHidden", m_parameters.multiplyHidden,
100  "If true, multiply nHidden with number of input nodes.",
101  m_parameters.multiplyHidden);
102  addParam("targetZ", m_parameters.targetZ,
103  "Train one output of MLP to give z.", m_parameters.targetZ);
104  addParam("targetTheta", m_parameters.targetTheta,
105  "Train one output of MLP to give theta.", m_parameters.targetTheta);
106  addParam("outputScale", m_parameters.outputScale,
107  "Output scale for all networks (1 value list or nMLP value lists). "
108  "Output[i] of the MLP is scaled from [-1, 1] "
109  "to [outputScale[2*i], outputScale[2*i+1]]. "
110  "(units: z[cm] / theta[degree])", m_parameters.outputScale);
111  addParam("phiRange", m_parameters.phiRange,
112  "Phi region in degree for which experts are trained. "
113  "1 value pair, nMLP value pairs or nPhi value pairs "
114  "with nPhi * nPt * nTheta * nPattern = nMLP.", m_parameters.phiRange);
115  addParam("invptRange", m_parameters.invptRange,
116  "Charge / Pt region in 1/GeV for which experts are trained. "
117  "1 value pair, nMLP value pairs or nPt value pairs "
118  "with nPhi * nPt * nTheta * nPattern = nMLP.", m_parameters.invptRange);
119  addParam("thetaRange", m_parameters.thetaRange,
120  "Theta region in degree for which experts are trained. "
121  "1 value pair, nMLP value pairs or nTheta value pairs "
122  "with nPhi * nPt * nTheta * nPattern = nMLP.", m_parameters.thetaRange);
123  addParam("phiRangeTrain", m_parameters.phiRangeTrain,
124  "Phi region in degree from which training events are taken. "
125  "Can be larger than phiRange to avoid edge effect.", m_parameters.phiRangeTrain);
126  addParam("invptRangeTrain", m_parameters.invptRangeTrain,
127  "Charge / Pt region in 1/GeV from which training events are taken. "
128  "Can be larger than phiRange to avoid edge effect.", m_parameters.invptRangeTrain);
129  addParam("thetaRangeTrain", m_parameters.thetaRangeTrain,
130  "Theta region in degree from which training events are taken. "
131  "Can be larger than phiRange to avoid edge effect.", m_parameters.thetaRangeTrain);
132  addParam("maxHitsPerSL", m_parameters.maxHitsPerSL,
133  "Maximum number of hits in a single SL. "
134  "1 value or same as SLpattern.", m_parameters.maxHitsPerSL);
135  addParam("SLpattern", m_parameters.SLpattern,
136  "Super layer pattern for which experts are trained. "
137  "1 value, nMLP values or nPattern values "
138  "with nPhi * nPt * nTheta * nPattern = nMLP.", m_parameters.SLpattern);
139  addParam("SLpatternMask", m_parameters.SLpatternMask,
140  "Super layer pattern mask for which experts are trained. "
141  "1 value or same as SLpattern.", m_parameters.SLpatternMask);
142  addParam("tMax", m_parameters.tMax,
143  "Maximal drift time (for scaling, unit: trigger timing bins).", m_parameters.tMax);
144  addParam("et_option", m_parameters.et_option,
145  "option on how to obtain the event time. Possibilities are: "
146  "'etf_only', 'fastestpriority', 'zero', 'etf_or_fastestpriority', 'etf_or_zero', 'etf_or_fastest2d', 'fastest2d'.",
147  m_parameters.et_option);
148  addParam("T0fromHits", m_parameters.T0fromHits,
149  "Deprecated, kept for backward compatibility. If true, the event time is "
150  "determined from all relevant hits in a sector, if there is no valid event "
151  "time from the event time finder. If false, no drift times are used if "
152  "there is no valid event time.",
153  m_parameters.T0fromHits);
154  addParam("selectSectorByMC", m_selectSectorByMC,
155  "If true, track parameters for sector selection are taken "
156  "from MCParticle instead of CDCTriggerTrack.", false);
157  // parameters for training data preparation
158  addParam("nTrainPrepare", m_nTrainPrepare,
159  "Number of samples for preparation of relevant ID ranges "
160  "(0: use default ranges).", 1000);
161  addParam("IDranges", m_IDranges,
162  "If list is not empty, it will replace the default ranges. "
163  "1 list or nMLP lists. Set nTrainPrepare to 0 if you use this option.",
164  {});
165  addParam("relevantCut", m_relevantCut,
166  "Cut for preparation of relevant ID ranges.", 0.02);
167  addParam("cutSum", m_cutSum,
168  "If true, relevantCut is applied to the sum over hit counters, "
169  "otherwise directly on the hit counters.", false);
170  addParam("nTrainMin", m_nTrainMin,
171  "Minimal number of training samples "
172  "or factor to multiply with number of weights. "
173  "If the minimal number of samples is not reached, "
174  "all samples are saved but no training is started.", 10.);
175  addParam("nTrainMax", m_nTrainMax,
176  "Maximal number of training samples "
177  "or factor to multiply with number of weights. "
178  "When the maximal number of samples is reached, "
179  "no further samples are added.", 10.);
180  addParam("multiplyNTrain", m_multiplyNTrain,
181  "If true, multiply nTrainMin and nTrainMax with number of weights.",
182  true);
183  addParam("nValid", m_nValid,
184  "Number of validation samples for training.", 1000);
185  addParam("nTest", m_nTest,
186  "Number of test samples to get resolution after training.", 5000);
187  addParam("stopLoop", m_stopLoop,
188  "If true, stop event loop when maximal number of samples "
189  "is reached for all sectors.", true);
190  addParam("rescaleTarget", m_rescaleTarget,
191  "If true, set target values > outputScale to 1, "
192  "else skip them.", true);
193  // parameters for training
194  addParam("wMax", m_wMax,
195  "Weights are limited to [-wMax, wMax] after each training epoch "
196  "(for convenience of the FPGA implementation).",
197  63.);
198  addParam("nThreads", m_nThreads,
199  "Number of threads for parallel training.", 1);
200  addParam("checkInterval", m_checkInterval,
201  "Training is stopped if validation error is higher than "
202  "checkInterval epochs ago, i.e. either the validation error is increasing "
203  "or the gain is less than the fluctuations.", 500);
204  addParam("maxEpochs", m_maxEpochs,
205  "Maximum number of training epochs.", 10000);
206  addParam("repeatTrain", m_repeatTrain,
207  "If >1, training is repeated several times with different start weights. "
208  "The weights which give the best resolution on the test samples are kept.", 1);
209  addParam("NeuroTrackInputMode", m_neuroTrackInputMode,
210  "When using real tracks, use neurotracks instead of 2dtracks as input to the neurotrigger",
211  false);
212 }
213 
214 
215 void
217 {
218  // register store objects
219  m_tracks.isRequired(m_inputCollectionName);
220  if (m_trainOnRecoTracks) {
221  StoreArray<RecoTrack> targets(m_targetCollectionName);
222  targets.isRequired(m_targetCollectionName);
223  } else {
224  StoreArray<MCParticle> targets(m_targetCollectionName);
225  targets.isRequired(m_targetCollectionName);
226  }
227  // load or initialize neurotrigger
228  if (!m_load ||
229  !loadTraindata(m_trainFilename, m_trainArrayname) ||
230  !m_NeuroTrigger.load(m_filename, m_arrayname)) {
231  m_NeuroTrigger.initialize(m_parameters);
232  m_trainSets.clear();
234  for (unsigned iMLP = 0; iMLP < m_NeuroTrigger.nSectors(); ++iMLP) {
235  m_trainSets.push_back(CDCTriggerMLPData());
236  int layerId = 3;
237  for (int iSL = 0; iSL < 9; ++iSL) {
238  m_trainSets[iMLP].addCounters(cdc.nWiresInLayer(layerId));
239  layerId += (iSL > 0 ? 6 : 7);
240  }
241  }
242  }
243  m_NeuroTrigger.initializeCollections(m_hitCollectionName, m_EventTimeName, m_parameters.et_option);
244  // consistency check of training parameters
245  if (m_NeuroTrigger.nSectors() != m_trainSets.size())
246  B2ERROR("Number of training sets (" << m_trainSets.size() << ") should match " <<
247  "number of sectors (" << m_NeuroTrigger.nSectors() << ")");
248  if (m_nTrainMin > m_nTrainMax) {
249  m_nTrainMin = m_nTrainMax;
250  B2WARNING("nTrainMin set to " << m_nTrainMin << " (was larger than nTrainMax)");
251  }
252  // set IDranges if they were given in the parameters
253  if (m_IDranges.size() > 0) {
254  if (m_IDranges.size() == 1 || m_IDranges.size() == m_NeuroTrigger.nSectors()) {
255  B2DEBUG(50, "Setting relevant ID ranges from parameters.");
256  for (unsigned isector = 0; isector < m_NeuroTrigger.nSectors(); ++isector) {
257  unsigned iranges = (m_IDranges.size() == 1) ? 0 : isector;
258  if (m_IDranges[iranges].size() == 18)
259  m_NeuroTrigger[isector].relevantID = m_IDranges[iranges];
260  else
261  B2ERROR("IDranges must contain 18 values (sector " << isector
262  << " has " << m_IDranges[iranges].size() << ")");
263  }
264  if (m_nTrainPrepare > 0)
265  B2WARNING("Given ID ranges will be replaced during training. "
266  "Set nTrainPrepare = 0 if you want to give ID ranges by hand.");
267  } else {
268  B2ERROR("Number of IDranges should be 0, 1, or " << m_NeuroTrigger.nSectors());
269  }
270  }
271 
272  // initialize monitoring histograms
273  if (m_saveDebug) {
274  for (unsigned iMLP = 0; iMLP < m_NeuroTrigger.nSectors(); ++iMLP) {
275  phiHistsMC.push_back(
276  new TH1D(("phiMC" + to_string(iMLP)).c_str(),
277  ("MC phi in sector " + to_string(iMLP)).c_str(),
278  100, -2 * M_PI, 2 * M_PI));
279  ptHistsMC.push_back(
280  new TH1D(("ptMC" + to_string(iMLP)).c_str(),
281  ("MC charge / pt in sector " + to_string(iMLP)).c_str(),
282  100, -5., 5.));
283  thetaHistsMC.push_back(
284  new TH1D(("thetaMC" + to_string(iMLP)).c_str(),
285  ("MC theta in sector " + to_string(iMLP)).c_str(),
286  100, 0., M_PI));
287  zHistsMC.push_back(
288  new TH1D(("zMC" + to_string(iMLP)).c_str(),
289  ("MC z in sector " + to_string(iMLP)).c_str(),
290  200, -100., 100.));
291  phiHists2D.push_back(
292  new TH1D(("phi2D" + to_string(iMLP)).c_str(),
293  ("2D phi in sector " + to_string(iMLP)).c_str(),
294  100, -2 * M_PI, 2 * M_PI));
295  ptHists2D.push_back(
296  new TH1D(("pt2D" + to_string(iMLP)).c_str(),
297  ("2D charge / pt in sector " + to_string(iMLP)).c_str(),
298  100, -5., 5.));
299  }
300  }
301 }
302 
303 void
305 {
306  for (int itrack = 0; itrack < m_tracks.getEntries(); ++itrack) {
307  // get related MCParticle/RecoTrack for target
308  // and retrieve track parameters
309  float phi0Target = 0;
310  float invptTarget = 0;
311  float thetaTarget = 0;
312  float zTarget = 0;
313  if (m_trainOnRecoTracks) {
314  RecoTrack* recoTrack =
315  m_tracks[itrack]->getRelatedTo<RecoTrack>(m_targetCollectionName);
316  if (!recoTrack) {
317  B2DEBUG(150, "Skipping CDCTriggerTrack without relation to RecoTrack.");
318  continue;
319  }
320  // a RecoTrack has multiple representations for different particle hypothesis
321  // -> just take the first one that does not give errors.
322  const vector<genfit::AbsTrackRep*>& reps = recoTrack->getRepresentations();
323  bool foundValidRep = false;
324  for (unsigned irep = 0; irep < reps.size() && !foundValidRep; ++irep) {
325  if (!recoTrack->wasFitSuccessful(reps[irep]))
326  continue;
327  // get state (position, momentum etc.) from hit closest to IP and
328  // extrapolate to z-axis (may throw an exception -> continue to next representation)
329  try {
331  recoTrack->getMeasuredStateOnPlaneClosestTo(TVector3(0, 0, 0), reps[irep]);
332  reps[irep]->extrapolateToLine(state, TVector3(0, 0, -1000), TVector3(0, 0, 2000));
333  // flip tracks if necessary, such that trigger tracks and reco tracks
334  // point in the same direction
335  if (state.getMom().Dot(m_tracks[itrack]->getDirection()) < 0) {
336  state.setPosMom(state.getPos(), -state.getMom());
337  state.setChargeSign(-state.getCharge());
338  }
339  // get track parameters
340  phi0Target = state.getMom().Phi();
341  invptTarget = state.getCharge() / state.getMom().Pt();
342  thetaTarget = state.getMom().Theta();
343  zTarget = state.getPos().Z();
344  } catch (...) {
345  continue;
346  }
347  // break loop
348  foundValidRep = true;
349  }
350  if (!foundValidRep) {
351  B2DEBUG(150, "No valid representation found for RecoTrack, skipping.");
352  continue;
353  }
354  } else {
355  MCParticle* mcTrack =
356  m_tracks[itrack]->getRelatedTo<MCParticle>(m_targetCollectionName);
357  if (!mcTrack) {
358  B2DEBUG(150, "Skipping CDCTriggerTrack without relation to MCParticle.");
359  continue;
360  }
361  phi0Target = mcTrack->getMomentum().Phi();
362  invptTarget = mcTrack->getCharge() / mcTrack->getMomentum().Pt();
363  thetaTarget = mcTrack->getMomentum().Theta();
364  zTarget = mcTrack->getProductionVertex().Z();
365  }
366 
367  // update 2D track variables
368  m_NeuroTrigger.updateTrack(*m_tracks[itrack]);
369 
370  // find all matching sectors
371  float phi0 = m_tracks[itrack]->getPhi0();
372  float invpt = m_tracks[itrack]->getKappa(1.5);
373  float theta = atan2(1., m_tracks[itrack]->getCotTheta());
374  if (m_selectSectorByMC) {
375  phi0 = phi0Target;
376  invpt = invptTarget;
377  theta = thetaTarget;
378  }
379  vector<int> sectors = m_NeuroTrigger.selectMLPs(phi0, invpt, theta);
380  if (sectors.size() == 0) continue;
381  // get target values
382  vector<float> targetRaw = {};
383  if (m_parameters.targetZ)
384  targetRaw.push_back(zTarget);
385  if (m_parameters.targetTheta)
386  targetRaw.push_back(thetaTarget);
387  for (unsigned i = 0; i < sectors.size(); ++i) {
388  int isector = sectors[i];
389  vector<float> target = m_NeuroTrigger[isector].scaleTarget(targetRaw);
390  // skip out of range targets or rescale them
391  bool outOfRange = false;
392  for (unsigned itarget = 0; itarget < target.size(); ++itarget) {
393  if (fabs(target[itarget]) > 1.) {
394  outOfRange = true;
395  target[itarget] /= fabs(target[itarget]);
396  }
397  }
398  if (!m_rescaleTarget && outOfRange) continue;
399 
400  if (m_nTrainPrepare > 0 &&
401  m_trainSets[isector].getTrackCounter() < m_nTrainPrepare) {
402  // get relative ids for all hits related to the MCParticle / RecoTrack
403  // and count them to find relevant id range
404  // using only related hits suppresses background EXCEPT for curling tracks
405  if (m_trainOnRecoTracks) {
406  RecoTrack* recoTrack =
407  m_tracks[itrack]->getRelatedTo<RecoTrack>(m_targetCollectionName);
408  for (const CDCTriggerSegmentHit& hit :
409  recoTrack->getRelationsTo<CDCTriggerSegmentHit>(m_hitCollectionName)) {
410  // get relative id
411  double relId = m_NeuroTrigger.getRelId(hit);
412  m_trainSets[isector].addHit(hit.getISuperLayer(), round(relId));
413  }
414  } else {
415  MCParticle* mcTrack =
416  m_tracks[itrack]->getRelatedTo<MCParticle>(m_targetCollectionName);
417  for (const CDCTriggerSegmentHit& hit :
418  mcTrack->getRelationsTo<CDCTriggerSegmentHit>(m_hitCollectionName)) {
419  // get relative id
420  double relId = m_NeuroTrigger.getRelId(hit);
421  m_trainSets[isector].addHit(hit.getISuperLayer(), round(relId));
422  }
423  }
424  m_trainSets[isector].countTrack();
425  // if required hit number is reached, get relevant ids
426  if (m_trainSets[isector].getTrackCounter() >= m_nTrainPrepare) {
427  updateRelevantID(isector);
428  }
429  } else {
430  // check whether we already have enough samples
431  float nTrainMax = m_multiplyNTrain ? m_nTrainMax * m_NeuroTrigger[isector].nWeights() : m_nTrainMax;
432  if (m_trainSets[isector].nSamples() > (nTrainMax + m_nValid + m_nTest)) {
433  continue;
434  }
435  // read out or determine event time
436  m_NeuroTrigger.getEventTime(isector, *m_tracks[itrack], m_parameters.et_option, m_neuroTrackInputMode);
437  // check hit pattern
438  unsigned long hitPattern = m_NeuroTrigger.getInputPattern(isector, *m_tracks[itrack], m_neuroTrackInputMode);
439  unsigned long sectorPattern = m_NeuroTrigger[isector].getSLpattern();
440  B2DEBUG(250, "hitPattern " << hitPattern << " sectorPattern " << sectorPattern);
441  if (sectorPattern > 0 && (sectorPattern & hitPattern) != sectorPattern) {
442  B2DEBUG(250, "hitPattern not matching " << (sectorPattern & hitPattern));
443  continue;
444  }
445  // get training data
446  vector<unsigned> hitIds;
447  if (m_neuroTrackInputMode) {
448  hitIds = m_NeuroTrigger.selectHitsHWSim(isector, *m_tracks[itrack]);
449  } else {
450  hitIds = m_NeuroTrigger.selectHits(isector, *m_tracks[itrack]);
451  }
452  m_trainSets[isector].addSample(m_NeuroTrigger.getInputVector(isector, hitIds), target);
453  if (m_saveDebug) {
454  phiHistsMC[isector]->Fill(phi0Target);
455  ptHistsMC[isector]->Fill(invptTarget);
456  thetaHistsMC[isector]->Fill(thetaTarget);
457  zHistsMC[isector]->Fill(zTarget);
458  phiHists2D[isector]->Fill(m_tracks[itrack]->getPhi0());
459  ptHists2D[isector]->Fill(m_tracks[itrack]->getKappa(1.5));
460  }
461  if (m_trainSets[isector].nSamples() % 1000 == 0) {
462  B2DEBUG(50, m_trainSets[isector].nSamples() << " samples collected for sector " << isector);
463  }
464  }
465  }
466  }
467  // check number of samples for all sectors
468  if (m_stopLoop) {
469  bool stop = true;
470  for (unsigned isector = 0; isector < m_trainSets.size(); ++isector) {
471  float nTrainMax = m_multiplyNTrain ? m_nTrainMax * m_NeuroTrigger[isector].nWeights() : m_nTrainMax;
472  if (m_trainSets[isector].nSamples() < (nTrainMax + m_nValid + m_nTest)) {
473  stop = false;
474  break;
475  }
476  }
477  if (stop) {
478  B2INFO("Training sample preparation for NeuroTrigger finished, stopping event loop.");
479  StoreObjPtr<EventMetaData> eventMetaData;
480  eventMetaData->setEndOfData();
481  }
482  }
483 }
484 
485 void
487 {
488  // save the training data
489  saveTraindata(m_trainFilename, m_trainArrayname);
490  // do training for all sectors with sufficient training samples
491  for (unsigned isector = 0; isector < m_NeuroTrigger.nSectors(); ++isector) {
492  // skip sectors that have already been trained
493  if (m_NeuroTrigger[isector].isTrained())
494  continue;
495  float nTrainMin = m_multiplyNTrain ? m_nTrainMin * m_NeuroTrigger[isector].nWeights() : m_nTrainMin;
496  if (m_trainSets[isector].nSamples() < (nTrainMin + m_nValid + m_nTest)) {
497  B2WARNING("Not enough training samples for sector " << isector << " (" << (nTrainMin + m_nValid + m_nTest)
498  << " requested, " << m_trainSets[isector].nSamples() << " found)");
499  continue;
500  }
501  train(isector);
502  m_NeuroTrigger[isector].trained = true;
503  // set sector ranges
504  vector<unsigned> indices = m_NeuroTrigger.getRangeIndices(m_parameters, isector);
505  vector<float> phiRange = m_parameters.phiRange[indices[0]];
506  vector<float> invptRange = m_parameters.invptRange[indices[1]];
507  vector<float> thetaRange = m_parameters.thetaRange[indices[2]];
508  //convert phi and theta from degree to radian
509  phiRange[0] *= Unit::deg;
510  phiRange[1] *= Unit::deg;
511  thetaRange[0] *= Unit::deg;
512  thetaRange[1] *= Unit::deg;
513  m_NeuroTrigger[isector].phiRange = phiRange;
514  m_NeuroTrigger[isector].invptRange = invptRange;
515  m_NeuroTrigger[isector].thetaRange = thetaRange;
516  // save all networks (including the newly trained)
517  m_NeuroTrigger.save(m_filename, m_arrayname);
518  }
519 }
520 
521 void
523 {
524  B2DEBUG(50, "Setting relevant ID ranges for sector " << isector);
525  vector<float> relevantID;
526  relevantID.assign(18, 0.);
528  int layerId = 3;
529  for (unsigned iSL = 0; iSL < 9; ++iSL) {
530  int nWires = cdc.nWiresInLayer(layerId);
531  layerId += (iSL > 0 ? 6 : 7);
532  B2DEBUG(90, "SL " << iSL << " (" << nWires << " wires)");
533  // get maximum hit counter
534  unsigned maxCounter = 0;
535  int maxId = 0;
536  unsigned counterSum = 0;
537  for (int iTS = 0; iTS < nWires; ++iTS) {
538  if (m_trainSets[isector].getHitCounter(iSL, iTS) > 0)
539  B2DEBUG(90, iTS << " " << m_trainSets[isector].getHitCounter(iSL, iTS));
540  if (m_trainSets[isector].getHitCounter(iSL, iTS) > maxCounter) {
541  maxCounter = m_trainSets[isector].getHitCounter(iSL, iTS);
542  maxId = iTS;
543  }
544  counterSum += m_trainSets[isector].getHitCounter(iSL, iTS);
545  }
546  // use maximum as starting range
547  if (maxId > nWires / 2) maxId -= nWires;
548  relevantID[2 * iSL] = maxId;
549  relevantID[2 * iSL + 1] = maxId;
550  if (m_cutSum) {
551  // add neighboring wire with higher hit count
552  // until sum over unused wires is less than relevantCut * sum over all wires
553  double cut = m_relevantCut * counterSum;
554  B2DEBUG(50, "Threshold on counterSum: " << cut);
555  unsigned relevantSum = maxCounter;
556  while (counterSum - relevantSum > cut) {
557  int prev = m_trainSets[isector].getHitCounter(iSL, relevantID[2 * iSL] - 1);
558  int next = m_trainSets[isector].getHitCounter(iSL, relevantID[2 * iSL + 1] + 1);
559  if (prev > next ||
560  (prev == next &&
561  (relevantID[2 * iSL + 1] - maxId) > (maxId - relevantID[2 * iSL]))) {
562  --relevantID[2 * iSL];
563  relevantSum += prev;
564  if (relevantID[2 * iSL] <= -nWires) break;
565  } else {
566  ++relevantID[2 * iSL + 1];
567  relevantSum += next;
568  if (relevantID[2 * iSL + 1] >= nWires - 1) break;
569  }
570  }
571  } else {
572  // add wires from both sides until hit counter drops below relevantCut * track counter
573  double cut = m_relevantCut * m_trainSets[isector].getTrackCounter();
574  B2DEBUG(50, "Threshold on counter: " << cut);
575  while (m_trainSets[isector].getHitCounter(iSL, relevantID[2 * iSL] - 1) > cut) {
576  --relevantID[2 * iSL];
577  if (relevantID[2 * iSL] <= -nWires) break;
578  }
579  while (m_trainSets[isector].getHitCounter(iSL, relevantID[2 * iSL + 1] + 1) > cut) {
580  ++relevantID[2 * iSL + 1];
581  if (relevantID[2 * iSL + 1] >= nWires - 1) break;
582  }
583  }
584  // add +-0.5 to account for rounding during preparation
585  relevantID[2 * iSL] -= 0.5;
586  relevantID[2 * iSL + 1] += 0.5;
587  B2DEBUG(50, "SL " << iSL << ": "
588  << relevantID[2 * iSL] << " " << relevantID[2 * iSL + 1]);
589  }
590  m_NeuroTrigger[isector].relevantID = relevantID;
591 }
592 
593 void
595 {
596 #ifdef HAS_OPENMP
597  B2INFO("Training network for sector " << isector << " with OpenMP");
598 #else
599  B2INFO("Training network for sector " << isector << " without OpenMP");
600 #endif
601  // initialize network
602  unsigned nLayers = m_NeuroTrigger[isector].nLayers();
603  unsigned* nNodes = new unsigned[nLayers];
604  for (unsigned il = 0; il < nLayers; ++il) {
605  nNodes[il] = m_NeuroTrigger[isector].nNodesLayer(il);
606  }
607  struct fann* ann = fann_create_standard_array(nLayers, nNodes);
608  // initialize training and validation data
609  CDCTriggerMLPData currentData = m_trainSets[isector];
610  // train set
611  unsigned nTrain = m_trainSets[isector].nSamples() - m_nValid - m_nTest;
612  struct fann_train_data* train_data =
613  fann_create_train(nTrain, nNodes[0], nNodes[nLayers - 1]);
614  for (unsigned i = 0; i < nTrain; ++i) {
615  vector<float> input = currentData.getInput(i);
616  for (unsigned j = 0; j < input.size(); ++j) {
617  train_data->input[i][j] = input[j];
618  }
619  vector<float> target = currentData.getTarget(i);
620  for (unsigned j = 0; j < target.size(); ++j) {
621  train_data->output[i][j] = target[j];
622  }
623  }
624  // validation set
625  struct fann_train_data* valid_data =
626  fann_create_train(m_nValid, nNodes[0], nNodes[nLayers - 1]);
627  for (unsigned i = nTrain; i < nTrain + m_nValid; ++i) {
628  vector<float> input = currentData.getInput(i);
629  for (unsigned j = 0; j < input.size(); ++j) {
630  valid_data->input[i - nTrain][j] = input[j];
631  }
632  vector<float> target = currentData.getTarget(i);
633  for (unsigned j = 0; j < target.size(); ++j) {
634  valid_data->output[i - nTrain][j] = target[j];
635  }
636  }
637  // set network parameters
638  fann_set_activation_function_hidden(ann, FANN_SIGMOID_SYMMETRIC);
639  fann_set_activation_function_output(ann, FANN_SIGMOID_SYMMETRIC);
640  fann_set_training_algorithm(ann, FANN_TRAIN_RPROP);
641  double bestRMS = 999.;
642  // keep full train error curve for best run
643  vector<double> bestTrainLog = {};
644  vector<double> bestValidLog = {};
645  // keep train error of optimum for all runs
646  vector<double> trainOptLog = {};
647  vector<double> validOptLog = {};
648  // repeat training several times with different random start weights
649  for (int irun = 0; irun < m_repeatTrain; ++irun) {
650  double bestValid = 999.;
651  vector<double> trainLog = {};
652  vector<double> validLog = {};
653  trainLog.assign(m_maxEpochs, 0.);
654  validLog.assign(m_maxEpochs, 0.);
655  int breakEpoch = 0;
656  int bestEpoch = 0;
657  vector<fann_type> bestWeights = {};
658  bestWeights.assign(m_NeuroTrigger[isector].nWeights(), 0.);
659  fann_randomize_weights(ann, -0.1, 0.1);
660  // train and save the network
661  for (int epoch = 1; epoch <= m_maxEpochs; ++epoch) {
662 #ifdef HAS_OPENMP
663  double mse = parallel_fann::train_epoch_irpropm_parallel(ann, train_data, m_nThreads);
664 #else
665  double mse = fann_train_epoch(ann, train_data);
666 #endif
667  trainLog[epoch - 1] = mse;
668  // reduce weights that got too large
669  for (unsigned iw = 0; iw < ann->total_connections; ++iw) {
670  if (ann->weights[iw] > m_wMax)
671  ann->weights[iw] = m_wMax;
672  else if (ann->weights[iw] < -m_wMax)
673  ann->weights[iw] = -m_wMax;
674  }
675  // evaluate validation set
676  fann_reset_MSE(ann);
677 #ifdef HAS_OPENMP
678  double valid_mse = parallel_fann::test_data_parallel(ann, valid_data, m_nThreads);
679 #else
680  double valid_mse = fann_test_data(ann, valid_data);
681 #endif
682  validLog[epoch - 1] = valid_mse;
683  // keep weights for lowest validation error
684  if (valid_mse < bestValid) {
685  bestValid = valid_mse;
686  for (unsigned iw = 0; iw < ann->total_connections; ++iw) {
687  bestWeights[iw] = ann->weights[iw];
688  }
689  bestEpoch = epoch;
690  }
691  // break when validation error increases
692  if (epoch > m_checkInterval && valid_mse > validLog[epoch - m_checkInterval]) {
693  B2INFO("Training run " << irun << " stopped in epoch " << epoch);
694  B2INFO("Train error: " << mse << ", valid error: " << valid_mse <<
695  ", best valid: " << bestValid);
696  breakEpoch = epoch;
697  break;
698  }
699  // print current status
700  if (epoch == 1 || (epoch < 100 && epoch % 10 == 0) || epoch % 100 == 0) {
701  B2INFO("Epoch " << epoch << ": Train error = " << mse <<
702  ", valid error = " << valid_mse << ", best valid = " << bestValid);
703  }
704  }
705  if (breakEpoch == 0) {
706  B2INFO("Training run " << irun << " finished in epoch " << m_maxEpochs);
707  breakEpoch = m_maxEpochs;
708  }
709  trainOptLog.push_back(trainLog[bestEpoch - 1]);
710  validOptLog.push_back(validLog[bestEpoch - 1]);
711  // test trained network
712  vector<float> oldWeights = m_NeuroTrigger[isector].getWeights();
713  m_NeuroTrigger[isector].weights = bestWeights;
714  vector<double> sumSqr;
715  sumSqr.assign(nNodes[nLayers - 1], 0.);
716  for (unsigned i = nTrain + m_nValid; i < m_trainSets[isector].nSamples(); ++i) {
717  vector<float> output = m_NeuroTrigger.runMLP(isector, m_trainSets[isector].getInput(i));
718  vector<float> target = m_trainSets[isector].getTarget(i);
719  for (unsigned iout = 0; iout < output.size(); ++iout) {
720  float diff = output[iout] - m_NeuroTrigger[isector].unscaleTarget(target)[iout];
721  sumSqr[iout] += diff * diff;
722  }
723  }
724  double sumSqrTotal = 0;
725  if (m_parameters.targetZ) {
726  sumSqrTotal += sumSqr[m_NeuroTrigger[isector].zIndex()];
727  B2INFO("RMS z: " << sqrt(sumSqr[m_NeuroTrigger[isector].zIndex()] / m_nTest) << "cm");
728  }
729  if (m_parameters.targetTheta) {
730  sumSqr[m_NeuroTrigger[isector].thetaIndex()] /= (Unit::deg * Unit::deg);
731  sumSqrTotal += sumSqr[m_NeuroTrigger[isector].thetaIndex()];
732  B2INFO("RMS theta: " << sqrt(sumSqr[m_NeuroTrigger[isector].thetaIndex()] / m_nTest) << "deg");
733  }
734  double RMS = sqrt(sumSqrTotal / m_nTest / sumSqr.size());
735  B2INFO("RMS on test samples: " << RMS << " (best: " << bestRMS << ")");
736  if (RMS < bestRMS) {
737  bestRMS = RMS;
738  bestTrainLog.assign(trainLog.begin(), trainLog.begin() + breakEpoch);
739  bestValidLog.assign(validLog.begin(), validLog.begin() + breakEpoch);
740  } else {
741  m_NeuroTrigger[isector].weights = oldWeights;
742  }
743  }
744  // save training log
745  if (m_saveDebug) {
746  // full error curve for best run
747  ofstream logstream(m_logFilename + "_BestRun_" + to_string(isector) + ".log");
748  for (unsigned i = 0; i < bestTrainLog.size(); ++i) {
749  logstream << bestTrainLog[i] << " " << bestValidLog[i] << endl;
750  }
751  logstream.close();
752  // training optimum for all runs
753  ofstream logstreamOpt(m_logFilename + "_AllOptima_" + to_string(isector) + ".log");
754  for (unsigned i = 0; i < trainOptLog.size(); ++i) {
755  logstreamOpt << trainOptLog[i] << " " << validOptLog[i] << endl;
756  }
757  logstreamOpt.close();
758  }
759  // free memory
760  fann_destroy_train(train_data);
761  fann_destroy_train(valid_data);
762  fann_destroy(ann);
763  delete[] nNodes;
764 }
765 
766 void
767 CDCTriggerNeuroTrainerModule::saveTraindata(const string& filename, const string& arrayname)
768 {
769  B2INFO("Saving traindata to file " << filename << ", array " << arrayname);
770  TFile datafile(filename.c_str(), "UPDATE");
771  TObjArray* trainSets = new TObjArray(m_trainSets.size());
772  for (unsigned isector = 0; isector < m_trainSets.size(); ++isector) {
773  trainSets->Add(&m_trainSets[isector]);
774  if (m_saveDebug) {
775  phiHistsMC[isector]->Write(phiHistsMC[isector]->GetName(), TObject::kOverwrite);
776  ptHistsMC[isector]->Write(ptHistsMC[isector]->GetName(), TObject::kOverwrite);
777  thetaHistsMC[isector]->Write(thetaHistsMC[isector]->GetName(), TObject::kOverwrite);
778  zHistsMC[isector]->Write(zHistsMC[isector]->GetName(), TObject::kOverwrite);
779  phiHists2D[isector]->Write(phiHists2D[isector]->GetName(), TObject::kOverwrite);
780  ptHists2D[isector]->Write(ptHists2D[isector]->GetName(), TObject::kOverwrite);
781  }
782  }
783  trainSets->Write(arrayname.c_str(), TObject::kSingleKey | TObject::kOverwrite);
784  datafile.Close();
785  trainSets->Clear();
786  delete trainSets;
787  for (unsigned isector = 0; isector < phiHistsMC.size(); ++ isector) {
788  delete phiHistsMC[isector];
789  delete ptHistsMC[isector];
790  delete thetaHistsMC[isector];
791  delete zHistsMC[isector];
792  delete phiHists2D[isector];
793  delete ptHists2D[isector];
794  }
795  phiHistsMC.clear();
796  ptHistsMC.clear();
797  thetaHistsMC.clear();
798  zHistsMC.clear();
799  phiHists2D.clear();
800  ptHists2D.clear();
801 }
802 
803 bool
804 CDCTriggerNeuroTrainerModule::loadTraindata(const string& filename, const string& arrayname)
805 {
806  TFile datafile(filename.c_str(), "READ");
807  if (!datafile.IsOpen()) {
808  B2WARNING("Could not open file " << filename);
809  return false;
810  }
811  TObjArray* trainSets = (TObjArray*)datafile.Get(arrayname.c_str());
812  if (!trainSets) {
813  datafile.Close();
814  B2WARNING("File " << filename << " does not contain key " << arrayname);
815  return false;
816  }
817  m_trainSets.clear();
818  for (int isector = 0; isector < trainSets->GetEntriesFast(); ++isector) {
819  CDCTriggerMLPData* samples = dynamic_cast<CDCTriggerMLPData*>(trainSets->At(isector));
820  if (samples) m_trainSets.push_back(*samples);
821  else B2WARNING("Wrong type " << trainSets->At(isector)->ClassName() << ", ignoring this entry.");
822  }
823  trainSets->Clear();
824  delete trainSets;
825  datafile.Close();
826  B2DEBUG(100, "loaded " << m_trainSets.size() << " training sets");
827  return true;
828 }
Struct for training data of a single MLP for the neuro trigger.
unsigned nSamples() const
get number of samples (same for input and target)
const std::vector< float > & getInput(unsigned i) const
get input vector of sample i
const std::vector< float > & getTarget(unsigned i) const
get target value of sample i
The trainer module for the neural networks of the CDC trigger.
bool loadTraindata(const std::string &filename, const std::string &arrayname="trainSets")
Load saved training samples.
virtual void initialize() override
Initialize the module.
virtual void event() override
Called once for each event.
void updateRelevantID(unsigned isector)
calculate and set the relevant id range for given sector based on hit counters of the track segments.
virtual void terminate() override
Do the training for all sectors.
void train(unsigned isector)
Train a single MLP.
void saveTraindata(const std::string &filename, const std::string &arrayname="trainSets")
Save all training samples.
Combination of several CDCHits to a track segment hit for the trigger.
The Class for CDC Geometry Parameters.
static CDCGeometryPar & Instance(const CDCGeometry *=nullptr)
Static method to get a reference to the CDCGeometryPar instance.
A Class to store the Monte Carlo particle information.
Definition: MCParticle.h:32
TVector3 getMomentum() const
Return momentum.
Definition: MCParticle.h:198
float getCharge() const
Return the particle charge defined in TDatabasePDG.
Definition: MCParticle.cc:34
TVector3 getProductionVertex() const
Return production vertex position.
Definition: MCParticle.h:189
Base class for Modules.
Definition: Module.h:72
This is the Reconstruction Event-Data Model Track.
Definition: RecoTrack.h:76
bool wasFitSuccessful(const genfit::AbsTrackRep *representation=nullptr) const
Returns true if the last fit with the given representation was successful.
Definition: RecoTrack.cc:333
const std::vector< genfit::AbsTrackRep * > & getRepresentations() const
Return a list of track representations. You are not allowed to modify or delete them!
Definition: RecoTrack.h:553
bool addHit(const HitType *hit, Args &&... params)
Add a generic hit with the given parameters for the reco hit information.
Definition: RecoTrack.h:793
const genfit::MeasuredStateOnPlane & getMeasuredStateOnPlaneClosestTo(const TVector3 &closestPoint, const genfit::AbsTrackRep *representation=nullptr)
Return genfit's MasuredStateOnPlane, that is closest to the given point useful for extrapolation of m...
Definition: RecoTrack.cc:418
RelationVector< TO > getRelationsTo(const std::string &name="", const std::string &namedRelation="") const
Get the relations that point from this object to another store array.
TO * getRelatedTo(const std::string &name="", const std::string &namedRelation="") const
Get the object to which this object has a relation.
Accessor to arrays stored in the data store.
Definition: StoreArray.h:113
Type-safe access to single objects in the data store.
Definition: StoreObjPtr.h:95
static const double deg
degree to radians
Definition: Unit.h:109
#StateOnPlane with additional covariance matrix.
#define REG_MODULE(moduleName)
Register the given module (without 'Module' suffix) with the framework.
Definition: Module.h:650
Abstract base class for different kinds of events.