Belle II Software  release-08-01-10
SVDTimeNet_Learn.py
1 
8 
9 # coding: utf-8
10 
11 # ## Train and save the SVDTime Neural Network
12 #
13 # The SVDTimeNN is a MultilayerPerceptron estimator of
14 # The truth data in this case are bin numbers in a series of time shift
15 # bins. The result of such a training is a distribution function for a
16 # time shift value. From these, it is easy to calculate mean value and
17 # standard deviation, but also do a range of approximate probabilistic
18 # calculations.
19 
20 # ##### Required Python packages
21 #
22 # The following python packages are used:
23 # - math (basic python math functions)
24 # - numpy (Vectors and matrices for numerics)
25 # - pandas (Python analogue of Excel tables)
26 # - scipy (Scientific computing package)
27 # - scikit-learn (machine learning)
28 #
29 # Only sklear2pmml is missing in the current basf2 distribution.
30 # Install it with
31 #
32 # pip3 install --user git+https://github.com/jpmml/sklearn2pmml.git
33 #
34 # ##### Other pre-requisites:
35 #
36 # A sample of training data, plus binning and bounds information in pickle (*.pkl) files.
37 
38 import pickle
39 import numpy as np
40 import pandas as pd
41 from sklearn.neural_network import MLPClassifier
42 from sklearn2pmml import sklearn2pmml, PMMLPipeline
43 from svd.SVDSimBase import dt, betaprime_wave, tau_encoder
44 from lxml import etree as ET
45 import argparse
46 
47 # ### Retrieve training sample
48 
49 parser = argparse.ArgumentParser(description="Train the SVD hit time estimator")
50 
51 parser.add_argument(
52  '--nsamples',
53  dest='n_samples',
54  action='store',
55  default=1000000,
56  type=int,
57  help='Global tag to use at central DB in PNNL')
58 
59 args = parser.parse_args()
60 
61 pkl_name = 'SVDTime_Training{0}_{1}.pkl'
62 
63 print('Reading data...')
64 
65 stockdata = pd.read_pickle(pkl_name.format('Sample', args.n_samples))
66 bounds = pd.read_pickle(pkl_name.format('Bounds', args.n_samples))
67 bins = pd.read_pickle(pkl_name.format('Bins', args.n_samples))
68 
69 timearray = bins['midpoint']
70 timebins = np.unique(bins[['lower', 'upper']])
71 
72 print('Done.')
73 
74 # ### Split the data into training and test samples
75 
76 test_fraction = 0.2
77 X = stockdata[['s' + str(i) for i in range(1, 7)] + ['normed_tau']]
78 Y = stockdata['t0_bin']
79 X_train = X[stockdata.test > test_fraction]
80 X_test = X[stockdata.test < test_fraction]
81 Y_train = Y[stockdata.test > test_fraction]
82 Y_test = Y[stockdata.test < test_fraction]
83 
84 classifier = MLPClassifier(
85  hidden_layer_sizes=(len(timearray) - 1, len(timearray) + 1),
86  activation='relu',
87  solver='adam',
88  tol=1.0e-6,
89  alpha=0.005,
90  verbose=True
91 )
92 
93 print('Fitting the neural network...')
94 
95 nntime_fitter = PMMLPipeline([('claasifier', classifier)])
96 nntime_fitter.fit(X_train, Y_train)
97 
98 test_score = nntime_fitter.score(X_test, Y_test)
99 train_score = nntime_fitter.score(X_train, Y_train)
100 print('Test: {}'.format(test_score))
101 print('Train: {}'.format(train_score))
102 print('Fitting done.')
103 
104 print('Writing output...')
105 
106 pmml_name = 'SVDTimeNet.pmml'
107 xml_name = pmml_name.replace('pmml', 'xml')
108 sklearn2pmml(nntime_fitter, pmml_name, with_repr=True)
109 
110 parser = ET.XMLParser(remove_blank_text=True)
111 net = ET.parse(pmml_name, parser)
112 root = net.getroot()
113 # namespace hassle
114 namespace = root.nsmap[None]
115 nsprefix = '{' + namespace + '}'
116 procinfo = root.find(nsprefix + 'MiningBuildTask')
117 
118 # Save some metadata
119 name = ET.SubElement(procinfo, nsprefix + 'Title')
120 name.text = 'Neural network for time shift estimation'
121 
122 # Information on use of the classifier
123 target = ET.SubElement(procinfo, nsprefix + 'IntendedUse')
124 basf2proc = ET.SubElement(target, nsprefix + 'basf2process')
125 basf2simulation = ET.SubElement(basf2proc, nsprefix + 'Simulation')
126 basf2simulation.text = 'yes'
127 basf2reconstruction = ET.SubElement(basf2proc, nsprefix + 'Reconstruction')
128 basf2reconstruction.text = 'yes'
129 sensorType = ET.SubElement(target, nsprefix + 'SensorType')
130 sensorType.text = 'all'
131 sensorSide = ET.SubElement(target, nsprefix + 'SensorSide')
132 sensorSide.text = 'all'
133 
134 # information on training
135 training = ET.SubElement(procinfo, nsprefix + 'Training')
136 source = ET.SubElement(training, nsprefix + 'SampleSource')
137 source.text = 'Toy simulation'
138 genfunc = ET.SubElement(training, nsprefix + 'Waveform')
139 genfunc.text = 'beta-prime'
140 num_samples = ET.SubElement(training, nsprefix + 'SampleSize')
141 train_samples = ET.SubElement(num_samples, nsprefix + 'Training', {'n': str(int((1 - test_fraction) * args.n_samples))})
142 test_samples = ET.SubElement(num_samples, nsprefix + 'Test', {'n': str(int(test_fraction * args.n_samples))})
143 bounds.apply(
144  lambda row: ET.SubElement(training, nsprefix + 'Parameter', **{u: str(v) for u, v in row.items()}), axis=1
145 )
146 
147 netparams = ET.SubElement(procinfo, nsprefix + 'NetworkParameters')
148 inputLayer = ET.SubElement(netparams, nsprefix + 'NetworkLayer')
149 inputLayer.attrib['number'] = str(0)
150 inputLayer.attrib['kind'] = 'input'
151 inputLayer.attrib['size'] = str(7) # 7 as in 6 APV samples + tau
152 n_hidden = len(classifier.hidden_layer_sizes)
153 for (iLayer, sz) in zip(range(1, 1 + n_hidden), classifier.hidden_layer_sizes):
154  layer = ET.SubElement(netparams, nsprefix + 'NetworkLayer')
155  layer.attrib['number'] = str(iLayer)
156  layer.attrib['kind'] = 'hidden'
157  layer.attrib['size'] = str(sz)
158 outputLayer = ET.SubElement(netparams, nsprefix + 'NetworkLayer')
159 outputLayer.attrib['number'] = str(n_hidden + 1)
160 outputLayer.attrib['kind'] = 'output'
161 outputLayer.attrib['size'] = str(len(timearray))
162 
163 for field in root.find(nsprefix + 'DataDictionary'):
164  if field.attrib['name'] == 't0_bin':
165  for child in field:
166  i = int(child.attrib['value'])
167  child.attrib['lower'] = '{0:.3f}'.format(bins.loc[i, 'lower'])
168  child.attrib['upper'] = '{0:.3f}'.format(bins.loc[i, 'upper'])
169  child.attrib['midpoint'] = '{0:.3f}'.format(bins.loc[i, 'midpoint'])
170 
171 net.write(xml_name, xml_declaration=True, pretty_print=True, encoding='utf-8')
172 
173 print('Output saved.')
174 
175 print('Saving fits...')
176 
177 # #### Set up tau en/decoder
178 amp_index = bounds[bounds.value == 'amplitude'].index[0]
179 amp_range = (bounds.ix[amp_index, 'low'], bounds.ix[amp_index, 'high'])
180 tau_index = bounds[bounds.value == 'tau'].index[0]
181 tau_range = (bounds.ix[tau_index, 'low'], bounds.ix[tau_index, 'high'])
182 coder = tau_encoder(amp_range, tau_range)
183 
184 # #### True values
185 
186 Trues_test = stockdata[stockdata.test < test_fraction][['t0', 'amplitude', 'tau', 't0_bin', 'abin']]
187 
188 # #### Predicted probabilities.
189 
190 probs = nntime_fitter.predict_proba(X_test)
191 
192 # ### Calculate time shifts and amplitudes from probabilities
193 
194 
195 def fitFromProb(fw, signals, p, tau, timearray):
196  t_fit = np.average(timearray, weights=p)
197  t_sigma = np.sqrt(np.average((timearray - t_fit)**2, weights=p))
198  weights = fw(-t_fit + np.linspace(-dt, 4 * dt, 6, endpoint=True), tau=tau)
199  weights[signals.values == 0.0] = 0.0
200  norm = 1.0 / np.inner(weights, weights)
201  a_fit = np.inner(signals, weights) * norm
202  a_sigma = np.sqrt(norm)
203  residuals = signals - a_fit * weights
204  ndf = np.sum(np.ones_like(signals[signals > 0])) - 2 # Can't be less than 1
205  chi2_ndf = np.inner(residuals, residuals) / ndf
206  return pd.Series({
207  't_fit': t_fit,
208  't_sigma': t_sigma,
209  'a_fit': a_fit,
210  'a_sigma': a_sigma,
211  'chi2_ndf': chi2_ndf
212  })
213 
214 
215 probdf = pd.DataFrame(probs)
216 probdf.index = X_test.index
217 probdf.to_pickle('SVDTime_TrainingProbs_{0}.pkl'.format(args.n_samples))
218 
219 fits = X_test.apply(
220  lambda row: fitFromProb(
221  betaprime_wave,
222  row[['s' + str(i) for i in range(1, 7)]],
223  probdf.ix[row.name],
224  coder.decode(row['normed_tau']),
225  timearray),
226  axis=1
227 )
228 fits['t_true'] = Trues_test['t0']
229 fits['tau'] = Trues_test['tau']
230 fits['a_true'] = Trues_test['amplitude']
231 fits['t_bin'] = Trues_test['t0_bin']
232 fits['a_bin'] = Trues_test['abin']
233 
234 fits.to_pickle('SVDTime_TrainingFits_{0}.pkl'.format(args.n_samples))
235 
236 print('Writing classifier...')
237 
238 with open('classifier.pkl', 'wb') as f:
239  pickle.dump(classifier, f)
240 
241 with open('classifier.txt', 'w') as cdump:
242  cdump.write("Classifier coefficients:\n")
243  for iLayer in range(len(classifier.coefs_)):
244  cdump.write('Layer: {0}\n'.format(iLayer))
245  nrows = classifier.coefs_[iLayer].shape[0]
246  ncols = classifier.coefs_[iLayer].shape[1]
247  cdump.write('Weights:\n')
248  for col in range(ncols):
249  s = " ".join([str(classifier.coefs_[iLayer][row, col]) for row in range(nrows)])
250  s += "\n"
251  cdump.write(s)
252  # intercepts should have nrows dimension
253  cdump.write('Intercepts:\n')
254  s = " ".join([str(classifier.intercepts_[iLayer][col]) for col in range(ncols)])
255  s += "\n"
256  cdump.write(s)
257 
258 print("Learning phase completed.")