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