Caffe2 - Python API
A deep learning, cross platform ML framework
control.py
1 # Copyright (c) 2016-present, Facebook, Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 ##############################################################################
15 
16 ## @package control
17 # Module caffe2.python.control
18 """
19 Implement functions for controlling execution of nets and steps, including
20  Do
21  DoParallel
22  For-loop
23  While-loop
24  Do-While-loop
25  Switch
26  If
27 """
28 
29 from __future__ import absolute_import
30 from __future__ import division
31 from __future__ import print_function
32 from __future__ import unicode_literals
33 
34 from caffe2.python import core
35 from future.utils import viewitems
36 
37 
38 # Used to generate names of the steps created by the control functions.
39 # It is actually the internal index of these steps.
40 _current_idx = 1
41 _used_step_names = set()
42 
43 
44 def _get_next_step_name(control_name, base_name):
45  global _current_idx, _used_step_names
46  concat_name = '%s/%s' % (base_name, control_name)
47  next_name = concat_name
48  while next_name in _used_step_names:
49  next_name = '%s_%d' % (concat_name, _current_idx)
50  _current_idx += 1
51  _used_step_names.add(next_name)
52  return next_name
53 
54 
55 def _MakeList(input):
56  """ input is a tuple.
57  Example:
58  (a, b, c) --> [a, b, c]
59  (a) --> [a]
60  ([a, b, c]) --> [a, b, c]
61  """
62  if len(input) == 0:
63  raise ValueError(
64  'input cannot be empty.')
65  elif len(input) == 1:
66  output = input[0]
67  if not isinstance(output, list):
68  output = [output]
69  else:
70  output = list(input)
71  return output
72 
73 
74 def _IsNets(nets_or_steps):
75  if isinstance(nets_or_steps, list):
76  return all(isinstance(n, core.Net) for n in nets_or_steps)
77  else:
78  return isinstance(nets_or_steps, core.Net)
79 
80 
81 def _PrependNets(nets_or_steps, *nets):
82  nets_or_steps = _MakeList((nets_or_steps,))
83  nets = _MakeList(nets)
84  if _IsNets(nets_or_steps):
85  return nets + nets_or_steps
86  else:
87  return [Do('prepend', nets)] + nets_or_steps
88 
89 
90 def _AppendNets(nets_or_steps, *nets):
91  nets_or_steps = _MakeList((nets_or_steps,))
92  nets = _MakeList(nets)
93  if _IsNets(nets_or_steps):
94  return nets_or_steps + nets
95  else:
96  return nets_or_steps + [Do('append', nets)]
97 
98 
99 def GetConditionBlobFromNet(condition_net):
100  """
101  The condition blob is the last external_output that must
102  be a single bool
103  """
104  assert len(condition_net.Proto().external_output) > 0, (
105  "Condition net %s must has at least one external output" %
106  condition_net.Proto.name)
107  # we need to use a blob reference here instead of a string
108  # otherwise, it will add another name_scope to the input later
109  # when we create new ops (such as OR of two inputs)
110  return core.BlobReference(condition_net.Proto().external_output[-1])
111 
112 
113 def BoolNet(*blobs_with_bool_value):
114  """A net assigning constant bool values to blobs. It is mainly used for
115  initializing condition blobs, for example, in multi-task learning, we
116  need to access reader_done blobs before reader_net run. In that case,
117  the reader_done blobs must be initialized.
118 
119  Args:
120  blobs_with_bool_value: one or more (blob, bool_value) pairs. The net will
121  assign each bool_value to the corresponding blob.
122 
123  returns
124  bool_net: A net assigning constant bool values to blobs.
125 
126  Examples:
127  - BoolNet((blob_1, bool_value_1), ..., (blob_n, bool_value_n))
128  - BoolNet([(blob_1, net1), ..., (blob_n, bool_value_n)])
129  - BoolNet((cond_1, bool_value_1))
130  """
131  blobs_with_bool_value = _MakeList(blobs_with_bool_value)
132  bool_net = core.Net('bool_net')
133  for blob, bool_value in blobs_with_bool_value:
134  out_blob = bool_net.ConstantFill(
135  [],
136  [blob],
137  shape=[],
138  value=bool_value,
139  dtype=core.DataType.BOOL)
140  bool_net.AddExternalOutput(out_blob)
141 
142  return bool_net
143 
144 
145 def NotNet(condition_blob_or_net):
146  """Not of a condition blob or net
147 
148  Args:
149  condition_blob_or_net can be either blob or net. If condition_blob_or_net
150  is Net, the condition is its last external_output
151  that must be a single bool.
152 
153  returns
154  not_net: the net NOT the input
155  out_blob: the output blob of the not_net
156  """
157  if isinstance(condition_blob_or_net, core.Net):
158  condition_blob = GetConditionBlobFromNet(condition_blob_or_net)
159  else:
160  condition_blob = condition_blob_or_net
161 
162  not_net = core.Net('not_net')
163  out_blob = not_net.Not(condition_blob)
164  not_net.AddExternalOutput(out_blob)
165 
166  return not_net, out_blob
167 
168 
169 def _CopyConditionBlobNet(condition_blob):
170  """Make a condition net that copies the condition_blob
171 
172  Args:
173  condition_blob is a single bool.
174 
175  returns
176  not_net: the net NOT the input
177  out_blob: the output blob of the not_net
178  """
179  condition_net = core.Net('copy_condition_blob_net')
180  out_blob = condition_net.Copy(condition_blob)
181  condition_net.AddExternalOutput(out_blob)
182 
183  return condition_net, out_blob
184 
185 
186 def MergeConditionNets(name, condition_nets, relation):
187  """
188  Merge multi condition nets into a single condition nets.
189 
190  Args:
191  name: name of the new condition net.
192  condition_nets: a list of condition nets. The last external_output
193  of each condition net must be single bool value.
194  relation: can be 'And' or 'Or'.
195 
196  Returns:
197  - A new condition net. Its last external output is relation of all
198  condition_nets.
199  """
200  if not isinstance(condition_nets, list):
201  return condition_nets
202  if len(condition_nets) <= 1:
203  return condition_nets[0] if condition_nets else None
204 
205  merged_net = core.Net(name)
206  for i in range(len(condition_nets)):
207  net_proto = condition_nets[i].Proto()
208  assert net_proto.device_option == merged_net.Proto().device_option
209  assert net_proto.type == merged_net.Proto().type
210  merged_net.Proto().op.extend(net_proto.op)
211  merged_net.Proto().external_input.extend(net_proto.external_input)
212  # discard external outputs as we're combining them together
213  curr_cond = GetConditionBlobFromNet(condition_nets[i])
214  if i == 0:
215  last_cond = curr_cond
216  else:
217  last_cond = merged_net.__getattr__(relation)([last_cond, curr_cond])
218  # merge attributes
219  for k, v in viewitems(condition_nets[i]._attr_dict):
220  merged_net._attr_dict[k] += v
221 
222  merged_net.AddExternalOutput(last_cond)
223 
224  return merged_net
225 
226 
227 def CombineConditions(name, condition_nets, relation):
228  """
229  Combine conditions of multi nets into a single condition nets. Unlike
230  MergeConditionNets, the actual body of condition_nets is not copied into
231  the combine condition net.
232 
233  One example is about multi readers. Each reader net has a reader_done
234  condition. When we want to check whether all readers are done, we can
235  use this function to build a new net.
236 
237  Args:
238  name: name of the new condition net.
239  condition_nets: a list of condition nets. The last external_output
240  of each condition net must be single bool value.
241  relation: can be 'And' or 'Or'.
242 
243  Returns:
244  - A new condition net. Its last external output is relation of all
245  condition_nets.
246  """
247  if not condition_nets:
248  return None
249  if not isinstance(condition_nets, list):
250  raise ValueError('condition_nets must be a list of nets.')
251 
252  if len(condition_nets) == 1:
253  condition_blob = GetConditionBlobFromNet(condition_nets[0])
254  condition_net, _ = _CopyConditionBlobNet(condition_blob)
255  return condition_net
256 
257  combined_net = core.Net(name)
258  for i in range(len(condition_nets)):
259  curr_cond = GetConditionBlobFromNet(condition_nets[i])
260  if i == 0:
261  last_cond = curr_cond
262  else:
263  last_cond = combined_net.__getattr__(relation)(
264  [last_cond, curr_cond])
265 
266  combined_net.AddExternalOutput(last_cond)
267 
268  return combined_net
269 
270 
271 def Do(name, *nets_or_steps):
272  """
273  Execute the sequence of nets or steps once.
274 
275  Examples:
276  - Do('myDo', net1, net2, ..., net_n)
277  - Do('myDo', list_of_nets)
278  - Do('myDo', step1, step2, ..., step_n)
279  - Do('myDo', list_of_steps)
280  """
281  nets_or_steps = _MakeList(nets_or_steps)
282  if (len(nets_or_steps) == 1 and isinstance(
283  nets_or_steps[0], core.ExecutionStep)):
284  return nets_or_steps[0]
285  else:
286  return core.scoped_execution_step(
287  _get_next_step_name('Do', name), nets_or_steps)
288 
289 
290 def DoParallel(name, *nets_or_steps):
291  """
292  Execute the nets or steps in parallel, waiting for all of them to finish
293 
294  Examples:
295  - DoParallel('pDo', net1, net2, ..., net_n)
296  - DoParallel('pDo', list_of_nets)
297  - DoParallel('pDo', step1, step2, ..., step_n)
298  - DoParallel('pDo', list_of_steps)
299  """
300  nets_or_steps = _MakeList(nets_or_steps)
301  if (len(nets_or_steps) == 1 and isinstance(
302  nets_or_steps[0], core.ExecutionStep)):
303  return nets_or_steps[0]
304  else:
305  return core.scoped_execution_step(
306  _get_next_step_name('DoParallel', name),
307  nets_or_steps,
308  concurrent_substeps=True)
309 
310 
311 def _RunOnceIf(name, condition_blob_or_net, nets_or_steps):
312  """
313  Execute nets_or_steps once if condition_blob_or_net evaluates as true.
314 
315  If condition_blob_or_net is Net, the condition is its last external_output
316  that must be a single bool. And this net will be executed before
317  nets_or_steps so as to get the condition.
318  """
319  condition_not_net, stop_blob = NotNet(condition_blob_or_net)
320  if isinstance(condition_blob_or_net, core.Net):
321  nets_or_steps = _PrependNets(
322  nets_or_steps, condition_blob_or_net, condition_not_net)
323  else:
324  nets_or_steps = _PrependNets(nets_or_steps, condition_not_net)
325 
326  def if_step(control_name):
327  return core.scoped_execution_step(
328  _get_next_step_name(control_name, name),
329  nets_or_steps,
330  should_stop_blob=stop_blob,
331  only_once=True,
332  )
333 
334  if _IsNets(nets_or_steps):
335  bool_net = BoolNet((stop_blob, False))
336  return Do(name + '/_RunOnceIf',
337  bool_net, if_step('_RunOnceIf-inner'))
338  else:
339  return if_step('_RunOnceIf')
340 
341 
342 def _RunOnceIfNot(name, condition_blob_or_net, nets_or_steps):
343  """
344  Similar to _RunOnceIf() but Execute nets_or_steps once if
345  condition_blob_or_net evaluates as false.
346  """
347  if isinstance(condition_blob_or_net, core.Net):
348  condition_blob = GetConditionBlobFromNet(condition_blob_or_net)
349  nets_or_steps = _PrependNets(nets_or_steps, condition_blob_or_net)
350  else:
351  copy_net, condition_blob = _CopyConditionBlobNet(condition_blob_or_net)
352  nets_or_steps = _PrependNets(nets_or_steps, copy_net)
353 
354  return core.scoped_execution_step(
355  _get_next_step_name('_RunOnceIfNot', name),
356  nets_or_steps,
357  should_stop_blob=condition_blob,
358  only_once=True,
359  )
360 
361 
362 def For(name, nets_or_steps, iter_num):
363  """
364  Execute nets_or_steps iter_num times.
365 
366  Args:
367  nets_or_steps: a ExecutionStep or a Net or a list of ExecutionSteps or
368  a list nets.
369  iter_num: the number times to execute the nets_or_steps.
370 
371  Returns:
372  A ExecutionStep instance.
373  """
374  init_net = core.Net('init-net')
375  iter_cnt = init_net.CreateCounter([], init_count=iter_num)
376  iter_net = core.Net('For-iter')
377  iter_done = iter_net.CountDown([iter_cnt])
378 
379  for_step = core.scoped_execution_step(
380  _get_next_step_name('For-inner', name),
381  _PrependNets(nets_or_steps, iter_net),
382  should_stop_blob=iter_done)
383  return Do(name + '/For',
384  Do(name + '/For-init-net', init_net),
385  for_step)
386 
387 
388 def While(name, condition_blob_or_net, nets_or_steps):
389  """
390  Execute nets_or_steps when condition_blob_or_net returns true.
391 
392  Args:
393  condition_blob_or_net: If it is an instance of Net, its last
394  external_output must be a single bool.
395  nets_or_steps: a ExecutionStep or a Net or a list of ExecutionSteps or
396  a list nets.
397 
398  Returns:
399  A ExecutionStep instance.
400  """
401  condition_not_net, stop_blob = NotNet(condition_blob_or_net)
402  if isinstance(condition_blob_or_net, core.Net):
403  nets_or_steps = _PrependNets(
404  nets_or_steps, condition_blob_or_net, condition_not_net)
405  else:
406  nets_or_steps = _PrependNets(nets_or_steps, condition_not_net)
407 
408  def while_step(control_name):
409  return core.scoped_execution_step(
410  _get_next_step_name(control_name, name),
411  nets_or_steps,
412  should_stop_blob=stop_blob,
413  )
414 
415  if _IsNets(nets_or_steps):
416  # In this case, while_step has sub-nets:
417  # [condition_blob_or_net, condition_not_net, nets_or_steps]
418  # If stop_blob is pre-set to True (this may happen when While() is
419  # called twice), the loop will exit after executing
420  # condition_blob_or_net. So we use BootNet to set stop_blob to
421  # False.
422  bool_net = BoolNet((stop_blob, False))
423  return Do(name + '/While', bool_net, while_step('While-inner'))
424  else:
425  return while_step('While')
426 
427 
428 def Until(name, condition_blob_or_net, nets_or_steps):
429  """
430  Similar to While() but execute nets_or_steps when
431  condition_blob_or_net returns false
432  """
433  if isinstance(condition_blob_or_net, core.Net):
434  stop_blob = GetConditionBlobFromNet(condition_blob_or_net)
435  nets_or_steps = _PrependNets(nets_or_steps, condition_blob_or_net)
436  else:
437  stop_blob = core.BlobReference(str(condition_blob_or_net))
438 
439  return core.scoped_execution_step(
440  _get_next_step_name('Until', name),
441  nets_or_steps,
442  should_stop_blob=stop_blob)
443 
444 
445 def DoWhile(name, condition_blob_or_net, nets_or_steps):
446  """
447  Execute nets_or_steps when condition_blob_or_net returns true. It will
448  execute nets_or_steps before evaluating condition_blob_or_net.
449 
450  Args:
451  condition_blob_or_net: if it is an instance of Net, tts last external_output
452  must be a single bool.
453  nets_or_steps: a ExecutionStep or a Net or a list of ExecutionSteps or
454  a list nets.
455 
456  Returns:
457  A ExecutionStep instance.
458  """
459  condition_not_net, stop_blob = NotNet(condition_blob_or_net)
460  if isinstance(condition_blob_or_net, core.Net):
461  nets_or_steps = _AppendNets(
462  nets_or_steps, condition_blob_or_net, condition_not_net)
463  else:
464  nets_or_steps = _AppendNets(nets_or_steps, condition_not_net)
465 
466  # If stop_blob is pre-set to True (this may happen when DoWhile() is
467  # called twice), the loop will exit after executing the first net/step
468  # in nets_or_steps. This is not what we want. So we use BootNet to
469  # set stop_blob to False.
470  bool_net = BoolNet((stop_blob, False))
471  return Do(name + '/DoWhile', bool_net, core.scoped_execution_step(
472  _get_next_step_name('DoWhile-inner', name),
473  nets_or_steps,
474  should_stop_blob=stop_blob,
475  ))
476 
477 
478 def DoUntil(name, condition_blob_or_net, nets_or_steps):
479  """
480  Similar to DoWhile() but execute nets_or_steps when
481  condition_blob_or_net returns false. It will execute
482  nets_or_steps before evaluating condition_blob_or_net.
483 
484  Special case: if condition_blob_or_net is a blob and is pre-set to
485  true, then only the first net/step of nets_or_steps will be executed and
486  loop is exited. So you need to be careful about the initial value the
487  condition blob when using DoUntil(), esp when DoUntil() is called twice.
488  """
489  if not isinstance(condition_blob_or_net, core.Net):
490  stop_blob = core.BlobReference(condition_blob_or_net)
491  return core.scoped_execution_step(
492  _get_next_step_name('DoUntil', name),
493  nets_or_steps,
494  should_stop_blob=stop_blob)
495 
496  nets_or_steps = _AppendNets(nets_or_steps, condition_blob_or_net)
497  stop_blob = GetConditionBlobFromNet(condition_blob_or_net)
498 
499  # If stop_blob is pre-set to True (this may happen when DoWhile() is
500  # called twice), the loop will exit after executing the first net/step
501  # in nets_or_steps. This is not what we want. So we use BootNet to
502  # set stop_blob to False.
503  bool_net = BoolNet((stop_blob, False))
504  return Do(name + '/DoUntil', bool_net, core.scoped_execution_step(
505  _get_next_step_name('DoUntil-inner', name),
506  nets_or_steps,
507  should_stop_blob=stop_blob,
508  ))
509 
510 
511 def Switch(name, *conditions):
512  """
513  Execute the steps for which the condition is true.
514  Each condition is a tuple (condition_blob_or_net, nets_or_steps).
515  Note:
516  1. Multi steps can be executed if their conditions are true.
517  2. The conditions_blob_or_net (if it is Net) of all steps will be
518  executed once.
519 
520  Examples:
521  - Switch('name', (cond_1, net_1), (cond_2, net_2), ..., (cond_n, net_n))
522  - Switch('name', [(cond_1, net1), (cond_2, net_2), ..., (cond_n, net_n)])
523  - Switch('name', (cond_1, net_1))
524  """
525  conditions = _MakeList(conditions)
526  return core.scoped_execution_step(
527  _get_next_step_name('Switch', name),
528  [_RunOnceIf(name + '/Switch', cond, step) for cond, step in conditions])
529 
530 
531 def SwitchNot(name, *conditions):
532  """
533  Similar to Switch() but execute the steps for which the condition is False.
534  """
535  conditions = _MakeList(conditions)
536  return core.scoped_execution_step(
537  _get_next_step_name('SwitchNot', name),
538  [_RunOnceIfNot(name + '/SwitchNot', cond, step)
539  for cond, step in conditions])
540 
541 
542 def If(name, condition_blob_or_net,
543  true_nets_or_steps, false_nets_or_steps=None):
544  """
545  condition_blob_or_net is first evaluated or executed. If the condition is
546  true, true_nets_or_steps is then executed, otherwise, false_nets_or_steps
547  is executed.
548 
549  If condition_blob_or_net is Net, the condition is its last external_output
550  that must be a single bool. And this Net will be executred before both
551  true/false_nets_or_steps so as to get the condition.
552  """
553  if not false_nets_or_steps:
554  return _RunOnceIf(name + '/If',
555  condition_blob_or_net, true_nets_or_steps)
556 
557  if isinstance(condition_blob_or_net, core.Net):
558  condition_blob = GetConditionBlobFromNet(condition_blob_or_net)
559  else:
560  condition_blob = condition_blob_or_net
561 
562  return Do(
563  name + '/If',
564  _RunOnceIf(name + '/If-true',
565  condition_blob_or_net, true_nets_or_steps),
566  _RunOnceIfNot(name + '/If-false', condition_blob, false_nets_or_steps)
567  )
568 
569 
570 def IfNot(name, condition_blob_or_net,
571  true_nets_or_steps, false_nets_or_steps=None):
572  """
573  If condition_blob_or_net returns false, executes true_nets_or_steps,
574  otherwise executes false_nets_or_steps
575  """
576  if not false_nets_or_steps:
577  return _RunOnceIfNot(name + '/IfNot',
578  condition_blob_or_net, true_nets_or_steps)
579 
580  if isinstance(condition_blob_or_net, core.Net):
581  condition_blob = GetConditionBlobFromNet(condition_blob_or_net)
582  else:
583  condition_blob = condition_blob_or_net
584 
585  return Do(
586  name + '/IfNot',
587  _RunOnceIfNot(name + '/IfNot-true',
588  condition_blob_or_net, true_nets_or_steps),
589  _RunOnceIf(name + '/IfNot-false', condition_blob, false_nets_or_steps)
590  )