Belle II Software  release-06-01-15
relations.py
1 #!/usr/bin/env python3
2 
3 
10 
11 # This example serves as a basic example of implementing Relational networks into basf2 with tensorflow.
12 # As a toy example it will try to tell if 2 out of multiple lines are hitting each other in three dimensional space.
13 # Relevant Paper: https://arxiv.org/abs/1706.01427
14 
15 import tensorflow as tf
17 import numpy as np
18 
19 
21  """ Using class to stop training early if it's not getting better"""
22 
23  def __init__(self):
24  """ init class """
25 
26  self.countercounter = 0
27 
28  self.best_resultbest_result = np.inf
29 
30  def check(self, cost):
31  """
32  Check if validation result is better than the best validation result.
33  Decide if training should be continued.
34  """
35  if cost < self.best_resultbest_result:
36  self.countercounter = 0
37  self.best_resultbest_result = cost
38  else:
39  self.countercounter += 1
40  if self.countercounter >= 20:
41  return False
42  return True
43 
44 
45 EARLY_STOPPER = early_stopping()
46 
47 
48 def get_model(number_of_features, number_of_spectators, number_of_events, training_fraction, parameters):
49  """Building Graph inside tensorflow"""
50  tf.reset_default_graph()
51  x = tf.placeholder(tf.float32, [None, number_of_features])
52  y = tf.placeholder(tf.float32, [None, 1])
53  # Used as input for pre training data set.
54  z = tf.placeholder(tf.float32, [None, number_of_spectators])
55 
56  def layer(x, shape, name, unit=tf.sigmoid):
57  """Build one hidden layer in feed forward net"""
58  with tf.name_scope(name):
59  weights = tf.Variable(tf.truncated_normal(shape, stddev=1.0 / np.sqrt(float(shape[0]))), name='weights')
60  biases = tf.Variable(tf.constant(0.0, shape=[shape[1]]), name='biases')
61  layer = unit(tf.matmul(x, weights) + biases)
62  return layer
63 
64  def build_relation_net_variables(shape, name):
65  """Build the variables(not the net itself), who will be shared between multiple relations"""
66  variables = []
67  with tf.name_scope(name), tf.variable_scope(name):
68  for i in range(len(shape) - 1):
69  weights = tf.get_variable('weights_{}'.format(i),
70  initializer=tf.truncated_normal(shape[i:i + 2],
71  stddev=1.0 / np.sqrt(float(shape[0]))))
72  biases = tf.get_variable('biases_{}'.format(i), initializer=tf.constant(0.0, shape=[shape[i + 1]]))
73  variables.append([weights, biases])
74  return variables
75 
76  def relation_net(x, variables):
77  """Build one relation net between 2 object using pre-build variables"""
78  net = [x]
79  for layer in variables:
80  if len(variables) != len(net):
81  net.append(tf.nn.tanh(tf.matmul(net[-1], layer[0]) + layer[1]))
82  else:
83  return tf.nn.sigmoid(tf.matmul(net[-1], layer[0]) + layer[1])
84 
85  if parameters['use_relations']:
86  # Group input according to relations.
87  tracks = []
88  [tracks.append(tf.slice(x, [0, i * 6], [-1, 6])) for i in range(int(number_of_features / 6))]
89 
90  # Number of features per relation. Each feature is a net with shared variables across all combinations.
91  # Number of Features is also the number of different set of variables for relational nets.
92  number_of_features_per_relation = 1
93  relations = []
94  pre_training_relations = []
95  for feature_number in range(number_of_features_per_relation):
96  # Build the variables, which will be shared across all combinations
97  relational_variables = build_relation_net_variables([12, 50, 50, 1],
98  'tracks_relational_{}'.format(feature_number))
99  # Loop over every combination of input groups.
100  for counter, track1 in enumerate(tracks):
101  for track2 in tracks[counter + 1:]:
102  # Build the net with pre-build variables.
103  relations.append(relation_net(tf.concat([track1, track2], 1), relational_variables))
104 
105  if parameters['pre_training_epochs'] > 0:
106  # build net for pre-training with the same shared variables.
107  pre_training_relations.append(relation_net(z, relational_variables))
108 
109  new_x = tf.concat(relations, 1)
110 
111  else:
112  new_x = x
113 
114  if parameters['use_feed_forward']:
115  print('Number of variables going into feed_forward:', int(new_x.get_shape()[1]))
116  inference_hidden1 = layer(new_x, [int(new_x.get_shape()[1]), 50], 'inference_hidden1')
117  inference_hidden2 = layer(inference_hidden1, [50, 50], 'inference_hidden2')
118  inference_activation = layer(inference_hidden2, [50, 1], 'inference_sigmoid', unit=tf.sigmoid)
119  else:
120  print('Number of variables going into reduce_max:', int(new_x.get_shape()[1]))
121  inference_activation = layer(new_x, [int(new_x.get_shape()[1]), 1], 'inference_sigmoid', unit=tf.sigmoid)
122  print(inference_activation.get_shape())
123 
124  epsilon = 1e-5
125  inference_loss = -tf.reduce_sum(y * tf.log(inference_activation + epsilon) +
126  (1.0 - y) * tf.log(1 - inference_activation + epsilon))
127 
128  inference_optimizer = tf.train.AdamOptimizer(learning_rate=0.01)
129  inference_minimize = inference_optimizer.minimize(inference_loss)
130 
131  config = tf.ConfigProto()
132  config.gpu_options.allow_growth = True
133  session = tf.Session(config=config)
134 
135  state = State(x, y, inference_activation, inference_loss, inference_minimize, session)
136 
137  if parameters['pre_training_epochs'] > 0:
138  # define training ops for pre-training and save them into state
139  new_z = tf.concat(pre_training_relations, 1)
140  pre_activation = layer(new_z, [int(new_z.get_shape()[1]), 1], 'pre_output')
141  state.pre_loss = -tf.reduce_sum(y * tf.log(pre_activation + epsilon) +
142  (1.0 - y) * tf.log(1 - pre_activation + epsilon))
143  pre_optimizer = tf.train.AdamOptimizer(learning_rate=0.01)
144  state.pre_minimize = pre_optimizer.minimize(state.pre_loss)
145 
146  state.pre_training_epochs = parameters['pre_training_epochs']
147  state.z = z
148  init = tf.global_variables_initializer()
149  session.run(init)
150 
151  return state
152 
153 
154 def begin_fit(state, Xtest, Stest, ytest, wtest):
155  """Saves the training validation set for monitoring."""
156  state.val_x = Xtest
157  state.val_y = ytest
158  state.val_z = Stest
159 
160  return state
161 
162 
163 def partial_fit(state, X, S, y, w, epoch):
164  """Pass received data to tensorflow session"""
165  feed_dict = {state.x: X, state.y: y, state.z: S}
166 
167  # pre training trains shared variables on only 2 lines.
168  # In this case there is no relation net which have to compare two lines not hitting each other in a signal event.
169  if state.pre_training_epochs > epoch:
170  state.session.run(state.pre_minimize, feed_dict=feed_dict)
171  if epoch % 1000 == 0:
172  avg_cost = state.session.run(state.pre_loss, feed_dict={state.y: state.val_y, state.z: state.val_z})
173  print("Pre-Training: Epoch:", '%04d' % (epoch), "cost=", "{:.9f}".format(avg_cost))
174  return True
175 
176  # Training of the whole network.
177  else:
178  state.session.run(state.optimizer, feed_dict=feed_dict)
179  if epoch % 1000 == 0:
180  avg_cost = state.session.run(state.cost, feed_dict={state.y: state.val_y, state.x: state.val_x})
181  print("Epoch:", '%04d' % (epoch), "cost=", "{:.9f}".format(avg_cost))
182  return EARLY_STOPPER.check(avg_cost)
183 
184  return True
185 
186 
187 if __name__ == "__main__":
188  import os
189  import pandas
190  from root_pandas import to_root
191  import tempfile
192  import json
193 
194  import basf2_mva
195  import basf2_mva_util
196  # ##############Building Data samples ###########################
197  # This is a dataset for testing relational nets.
198  # It consists of number_total_lines lines in 3 dimensional space.
199  # Each line has 6 variables.
200  # In apprx. half of the cases, two lines are hitting each other.
201  # This is considered a signal event.
202  # Training results differs from the number of total lines.
203 
204  variables = []
205  # try using 10 lines and see what happens
206  number_total_lines = 5
207  # Names for the training data set
208  for i in range(number_total_lines):
209  variables += ['px_' + str(i), 'py_' + str(i), 'pz_' + str(i), 'dx_' + str(i), 'dy_' + str(i),
210  'dz_' + str(i)]
211  # Names for the spectator variables.
212  # Used as input variables for pre-training.
213  spectators = ['Spx1', 'Spy1', 'Spz1', 'Sdx1', 'Sdy1', 'Sdz1', 'Spx2', 'Spy2', 'Spz2', 'Sdx2', 'Sdy2', 'Sdz2']
214  # Number of events in training and test root file.
215  number_of_events = 1000000
216 
217  def build_signal_event():
218  """Building two lines which are hitting each other"""
219  p_vec1, p_vec2 = np.random.normal(size=3), np.random.normal(size=3)
220  v_cross = np.random.normal(size=3)
221  epsilon1, epsilon2 = np.random.rand() * 2 - 1, np.random.rand() * 2 - 1
222  v_vec1 = v_cross + (p_vec1 * epsilon1)
223  v_vec2 = v_cross + (p_vec2 * epsilon2)
224  return np.concatenate([p_vec1, v_vec1]), np.concatenate([p_vec2, v_vec2])
225 
226  # This path will delete itself with all data in it after end of program.
227  with tempfile.TemporaryDirectory() as path:
228  for filename in ['train.root', 'test.root']:
229  print('Building ' + filename)
230  # Use random numbers to build all training and spectator variables.
231  data = np.random.normal(size=[number_of_events, number_total_lines * 6 + 12])
232  target = np.zeros([number_of_events], dtype=bool)
233 
234  # Overwrite for half of the variables some lines so that they are hitting each other.
235  # Write them also at the end for the spectators.
236  for index, sample in enumerate(data):
237  if np.random.rand() > 0.5:
238  target[index] = True
239  i1, i2 = int(np.random.rand() * number_total_lines), int(np.random.rand() * (number_total_lines - 1))
240  i2 = (i1 + i2) % number_total_lines
241  track1, track2 = build_signal_event()
242  data[index, i1 * 6:(i1 + 1) * 6] = track1
243  data[index, i2 * 6:(i2 + 1) * 6] = track2
244  data[index, number_total_lines * 6:] = np.append(track1, track2)
245 
246  # Saving all variables in root files
247  dic = {}
248  for i, name in enumerate(variables + spectators):
249  dic.update({name: data[:, i]})
250  dic.update({'isSignal': target})
251 
252  df = pandas.DataFrame(dic, dtype=np.float32)
253  to_root(df, os.path.join(path, filename), key='variables')
254 
255  # ##########################Do Training#################################
256  # Do a comparison of different Nets for this task.
257 
258  general_options = basf2_mva.GeneralOptions()
259  general_options.m_datafiles = basf2_mva.vector(os.path.join(path, 'train.root'))
260  general_options.m_treename = "variables"
261  general_options.m_variables = basf2_mva.vector(*variables)
262  general_options.m_target_variable = "isSignal"
263  general_options.m_spectators = basf2_mva.vector(*spectators)
264 
265  specific_options = basf2_mva.PythonOptions()
266  specific_options.m_framework = "tensorflow"
267  specific_options.m_steering_file = 'toy_relations.py'
268  specific_options.m_nIterations = 100
269  specific_options.m_mini_batch_size = 100
270  specific_options.m_training_fraction = 0.999
271 
272  print('Train relational net with pre-training')
273  general_options.m_identifier = os.path.join(path, 'relation_2.xml')
274  specific_options.m_config = json.dumps({'use_relations': True, 'use_feed_forward': False, 'pre_training_epochs': 30000})
275  basf2_mva.teacher(general_options, specific_options)
276 
277  # Train normal feed forward Net:
278  print('Train feed forward net')
279  general_options.m_identifier = os.path.join(path, 'feed_forward.xml')
280  specific_options.m_config = json.dumps({'use_relations': False, 'use_feed_forward': True, 'pre_training_epochs': 0})
281  basf2_mva.teacher(general_options, specific_options)
282 
283  # Train Relation Net:
284  print('Train relational net')
285  general_options.m_identifier = os.path.join(path, 'relation.xml')
286  specific_options.m_config = json.dumps({'use_relations': True, 'use_feed_forward': True, 'pre_training_epochs': 0})
287  basf2_mva.teacher(general_options, specific_options)
288 
289  # ########################Compare Results####################################
290 
291  method1 = basf2_mva_util.Method(os.path.join(path, 'feed_forward.xml'))
292  method2 = basf2_mva_util.Method(os.path.join(path, 'relation.xml'))
293  method3 = basf2_mva_util.Method(os.path.join(path, 'relation_2.xml'))
294 
295  test_data = basf2_mva.vector(os.path.join(path, 'test.root'))
296  print('Apply feed forward net')
297  p1, t1 = method1.apply_expert(test_data, general_options.m_treename)
298  print('Apply relational net')
299  p2, t2 = method2.apply_expert(test_data, general_options.m_treename)
300  print('Apply special relational net')
301  p3, t3 = method3.apply_expert(test_data, general_options.m_treename)
302 
303  print('Feed Forward Net AUC: ', basf2_mva_util.calculate_roc_auc(p1, t1))
304  print('Relational Net AUC: ', basf2_mva_util.calculate_roc_auc(p2, t2))
305  print('Relational Net with pre-training AUC: ', basf2_mva_util.calculate_roc_auc(p3, t3))
def calculate_roc_auc(p, t)
best_result
saves best training result
Definition: relations.py:28
def check(self, cost)
Definition: relations.py:30
counter
counts how many times training is not getting better
Definition: relations.py:26