Caffe2 - Python API
A deep learning, cross platform ML framework
layers.py
1 ## @package layers
2 # Module caffe2.python.layers.layers
3 from __future__ import absolute_import
4 from __future__ import division
5 from __future__ import print_function
6 from __future__ import unicode_literals
7 
8 import logging
9 
10 from caffe2.python import core, schema, scope, utils, workspace
11 from caffe2.python.layers.tags import TagContext
12 from caffe2.proto import caffe2_pb2
13 
14 from collections import namedtuple
15 import numpy as np
16 logger = logging.getLogger(__name__)
17 logger.setLevel(logging.INFO)
18 
19 # Some types to simplify descriptions of things traveling between ops
20 IdList = schema.List(np.int64)
21 IdScoreList = schema.Map(np.int64, np.float32)
22 
23 
24 def get_key(record):
25  if schema.equal_schemas(record, IdList):
26  key = 'values'
27  elif schema.equal_schemas(record, IdScoreList, check_field_types=False):
28  key = 'values:keys'
29  else:
30  raise NotImplementedError('Not implemented for {}'.format(record))
31  assert record[key].metadata is not None, (
32  "Blob {} doesn't have metadata".format(str(record[key]())))
33  return record[key]
34 
35 
36 def get_categorical_limit(record):
37  key = get_key(record)
38  return key.metadata.categorical_limit
39 
40 
41 def get_avg_length(record):
42  return record['lengths'].metadata.expected_value
43 
44 
45 def set_request_only(field):
46  for f in field.all_scalars():
47  categorical_limit, expected_value = None, None
48  if not f.metadata:
49  feature_specs = schema.FeatureSpec(
50  feature_is_request_only=True,
51  )
52  elif not f.metadata.feature_specs:
53  categorical_limit = f.metadata.categorical_limit
54  expected_value = f.metadata.expected_value
55  feature_specs = schema.FeatureSpec(
56  feature_is_request_only=True,
57  )
58  else:
59  categorical_limit = f.metadata.categorical_limit
60  expected_value = f.metadata.expected_value
61  feature_specs = schema.FeatureSpec(
62  feature_type=f.metadata.feature_specs.feature_type,
63  feature_names=f.metadata.feature_specs.feature_names,
64  feature_ids=f.metadata.feature_specs.feature_ids,
65  feature_is_request_only=True,
66  desired_hash_size=f.metadata.feature_specs.desired_hash_size,
67  )
68 
69  # make sure not to set categorical_limit for a non-integer field
70  if not np.issubdtype(f.field_type(), np.integer):
71  assert categorical_limit is None, \
72  "categorical_limit shouldn't be set for no-integer field"
73 
74  f.set_metadata(
75  schema.Metadata(
76  categorical_limit=categorical_limit,
77  expected_value=expected_value,
78  feature_specs=feature_specs,
79  )
80  )
81 
82 
83 class InstantiationContext(object):
84  """
85  List of contexts where layer could be instantitated
86  """
87  # The layers support this context will accumulate predictions, labels,
88  # weights. The accumulated data can later be used to compute
89  # calibration or for other
90  # purpose.
91  ACCUMULATE_PRED = 'accumulate_pred'
92  EVAL = 'eval'
93  PREDICTION = 'prediction'
94  TRAINING = 'training'
95 
96 
97 _LAYER_REGISTRY = {}
98 
99 
100 def register_layer(name, layer):
101  assert name not in _LAYER_REGISTRY, "{0} already exists".format(name)
102  _LAYER_REGISTRY[name] = layer
103 
104 
105 def layer_exists(name):
106  return name in _LAYER_REGISTRY
107 
108 
109 def get_layer_class(name):
110  return _LAYER_REGISTRY[name]
111 
112 
113 def create_layer(layer_name, *args, **kwargs):
114  return _LAYER_REGISTRY[layer_name](*args, **kwargs)
115 
116 
117 LayerPsParam = namedtuple('LayerPsParam', ['sparse_key', 'average_length'])
118 
119 
120 class LayerParameter(object):
121 
122  def __init__(self, parameter=None, optimizer=None, initializer=None,
123  ps_param=None, regularizer=None):
124  assert isinstance(parameter, core.BlobReference), \
125  "expect {0} to be a blob reference".format(str(parameter))
126  # need to put the following line (shape) before initialier
127  # shape will be updated once initializer is (re)set
128  self._shape = None
129  self.parameter = parameter
130  self.optimizer = optimizer
131  self.initializer = initializer
132  self.ps_param = ps_param
133  self.regularizer = regularizer
134 
135  @property
136  def initializer(self):
137  return self._initializer
138 
139  @initializer.setter
140  def initializer(self, op):
141  assert op is None or core.IsOperator(getattr(op, 'type', None)), \
142  "initializer expects an operator, got type: {}".format(type(op))
143  self._initializer = op
144  if op is not None:
146 
147  @property
148  def shape(self):
149  return self._shape
150 
151  @shape.setter
152  def shape(self, shape):
153  assert self.shape is None or self.shape == shape, \
154  "inconsistent shape for layer parameter:"\
155  " {}, expect: {}, but got {}".format(self, self.shape, shape)
156  self._shape = shape
157 
158  def _infer_shape_from_initializer(self):
159  for arg in self.initializer.arg:
160  if arg.name == 'shape':
161  return list(arg.ints)
162  with workspace.WorkspaceGuard("model_init_by_loading_params"):
163  try:
164  net = core.Net("shape_checker")
165  net._net.op.extend([self.initializer])
166  shape_blob = net.NextScopedBlob(self.parameter + "_shape")
167  net.Shape([self.parameter], shape_blob)
168  workspace.RunNetOnce(net)
169  shape = workspace.FetchBlob(shape_blob).tolist()
170  # ResetWorkspace to save memory
171  workspace.ResetWorkspace()
172  return shape
173  except RuntimeError as exp:
174  logger.warning(
175  "Cannot infer the shape of blob {} from operator {}: {}".format(
176  self.parameter, self.initializer.type, exp)
177  )
178  workspace.ResetWorkspace()
179  return None
180 
181  def __str__(self):
182  return str(self.parameter)
183 
184 
185 def is_request_only_scalar(scalar):
186  if len(scalar.field_metadata()) == 0:
187  return False
188  for metadata in scalar.field_metadata():
189  if not (metadata and metadata.feature_specs and getattr(
190  metadata.feature_specs, 'feature_is_request_only', False)):
191  return False
192  return True
193 
194 
195 class ModelLayer(object):
196 
197  def __init__(self, model, prefix, input_record,
198  predict_input_record_fields=None, tags=None, **kwargs):
199  """
200  Base class for model layers. Layer is an abstraction that allows to
201  provide model description in terms of meta-operators, where each of the
202  meta-operators can have different implementations for training,
203  evaluation and prediction, that are instantiated later. As an example
204  SampledSoftmax can do something related to sampling depending on
205  supervision during the training and just apply softmax if it's used for
206  prediction/evaluation.
207 
208  All inputs/outputs from layers are represented as a record (instance of
209  schema bounded to blobs) and are accessible through input_record and
210  output_schema. If Layer needs to have only a subset of inputs/provides
211  subset of outputs during the inference - it should provide
212  predict_input_record and predict_output_schema correspondingly (those
213  records are expected to be a subset of input_record/output_schema).
214 
215  Each layer has a list of Tags associated with it, that depends on
216  current context and arguments. It's possible to use those tags during
217  the instantiation time.
218 
219  """
220  self.name = model.next_layer_name(prefix)
221  self.model = model
222  self.kwargs = kwargs
223  self._input_record = input_record
224  if predict_input_record_fields:
225  if not isinstance(predict_input_record_fields, list):
226  predict_input_record_fields = [predict_input_record_fields]
228  predict_input_record_fields]
229  else:
230  self._predict_input_record = None
231 
232  self.request_only = True
233  if len(input_record.all_scalars()) == 0:
234  self.request_only = False
235  for scalar in input_record.all_scalars():
236  if not is_request_only_scalar(scalar):
237  self.request_only = False
238  break
239 
240  self.precomputation_request_only = False
241  self.precomputation_object_only = False
242 
243  self._output_schema = None
244  self._predict_output_schema = None
245  self.eval_output_schema = None
246  self.tags = set(tags or [])
247  self.tags.update(TagContext.current().tags)
248  self.params = []
249  self._export_output_for_metrics = False
250  self._export_params_for_metrics = False
251 
252  def get_type(self):
253  return self.__class__.__name__
254 
255  def _check_output_schema(self):
256  assert self._output_schema is not None, "Schema is not initialized"
257  assert (self._predict_output_schema is None or
258  schema.is_schema_subset(self._predict_output_schema,
259  self._output_schema)), (
260  "predict_output_schema is not a subset of the output_schema")
261 
262  @property
263  def predict_input_record(self):
264  return self._predict_input_record or self._input_record
265 
266  @property
267  def input_record(self):
268  return self._input_record
269 
270  @property
271  def predict_output_schema(self):
272  self._check_output_schema()
273  return self._predict_output_schema or self._output_schema
274 
275  @predict_output_schema.setter
276  def predict_output_schema(self, output_schema):
277  assert self._predict_output_schema is None
278  self._predict_output_schema = output_schema
279 
280  @property
281  def output_schema(self):
282  if self.request_only:
283  set_request_only(self._output_schema)
284  self._check_output_schema()
285  return self._output_schema
286 
287  @output_schema.setter
288  def output_schema(self, output_schema):
289  assert self._output_schema is None
290  self._output_schema = output_schema
291 
292  def get_parameters(self):
293  return self.params
294 
296  """Return a subset of parameters which can be converted to fp16"""
297  return []
298 
299  def get_memory_usage(self):
300  return 0
301 
302  def add_init_params(self, init_net):
303  '''
304  Adds layer initialization operators to passed net.
305  '''
306  for param in self.params:
307  # TODO(amalevich): Either return back to lambdas, that add
308  # all params (looks a bit safer and breaking less
309  # abstractions) or extend Net interface to this type of
310  # operations better
311  # TODO(xlwang) init_net._net.op has type google.protobuf.\
312  # internal.containers.RepeatedCompositeFieldContainer, but
313  # the version of protobuf in fbcode does not support append
314  # so extend is used
315  init_op = param.initializer
316  current_device_scope = scope.CurrentDeviceScope()
317  if not init_op:
318  continue
319 
320  if not init_op.HasField('device_option') and\
321  current_device_scope:
322  init_op = caffe2_pb2.OperatorDef()
323  init_op.CopyFrom(param.initializer)
324  init_op.device_option.CopyFrom(current_device_scope)
325 
326  # do not add duplicated init ops
327  if any(utils.OpAlmostEqual(op, init_op, 'debug_info')
328  for op in init_net._net.op):
329  continue
330 
331  init_net._net.op.extend([init_op])
332 
333  def create_param(self, param_name, shape, initializer, optimizer,
334  ps_param=None, regularizer=None):
335  with scope.NameScope(self.name, reset=True):
336  param = self.model.create_param(param_name=param_name,
337  shape=shape,
338  initializer=initializer,
339  optimizer=optimizer,
340  ps_param=ps_param,
341  regularizer=regularizer)
342 
343  # make sure we don't share parameters in the same layer
344  assert all(param.parameter != p.parameter for p in self.params)
345 
346  self.params.append(param)
347  return param.parameter
348 
349  def get_next_blob_reference(self, name):
350  with scope.NameScope(self.name, reset=True):
351  return self.model.net.NextScopedBlob(name)
352 
353  def add_operators(self, net, init_net=None,
354  context=InstantiationContext.TRAINING):
355  '''
356  Adds layer trainig or initialization operators to the passed in net.
357  init_net can be None and can be called independently from add_init_params
358  '''
359  # Namescope below should warranty that all intermediate blobs will be
360  # assiciated with the layer that produces them
361  with scope.NameScope(self.name):
362  if context not in {InstantiationContext.PREDICTION,
363  InstantiationContext.EVAL,
364  InstantiationContext.ACCUMULATE_PRED}:
365  assert init_net, (
366  "Only prediction and eval context don't need init_net")
367  if init_net:
368  self.add_init_params(init_net)
369  if context == InstantiationContext.TRAINING:
370  self.add_train_ops(net)
371  elif context == InstantiationContext.EVAL:
372  self.add_eval_ops(net)
373  elif context == InstantiationContext.ACCUMULATE_PRED:
375  else:
376  self.add_ops(net)
377 
378  if context in {InstantiationContext.TRAINING,
379  InstantiationContext.EVAL} \
381  self.add_param_copy_operators(net)
382 
383  def add_ops(self, net):
384  # Predict layer implementation.
385  raise NotImplementedError
386 
387  def add_eval_ops(self, net):
388  # Default eval layer implementation is completely matching
389  # predict layer implementation.
390  self.add_ops(net)
391 
392  def add_train_ops(self, net):
393  # Default train layer implementation is completely matching
394  # eval layer implementation.
395  self.add_eval_ops(net)
396 
397  def add_ops_to_accumulate_pred(self, net):
398  # This adds operators to accumulate predictions/labels/weights. The
399  # accumulated data can later be used to compute calibration or for other
400  # purpose. Default layer implementation is completely matching eval
401  # layer implementation.
402  self.add_eval_ops(net)
403 
404  def add_param_copy_operators(self, net):
405  for param in self.params:
406  param_copy_ref = self.model.metrics_schema[str(param.parameter)]
407  net.Copy([param.parameter], param_copy_ref.field_blobs())
408 
409  def export_output_for_metrics(self):
410  self._export_output_for_metrics = True
411 
412  # Export output of the layer directly
413  export_name = self.name + "/output"
414  self.model.add_metric_field(export_name, self.output_schema)
415 
416  def export_params_for_metrics(self):
417  self._export_params_for_metrics = True
418 
419  # Export copies of parameters
420  for param in self.params:
421  param_copy_ref = self.get_next_blob_reference(
422  str(param).split("/")[-1] + "_copy")
423  self.model.add_metric_field(str(param.parameter), param_copy_ref)
Module caffe2.python.layers.split.
def add_operators(self, net, init_net=None, context=InstantiationContext.TRAINING)
Definition: layers.py:354
def __init__(self, model, prefix, input_record, predict_input_record_fields=None, tags=None, kwargs)
Definition: layers.py:198
def get_next_blob_reference(self, name)
Definition: layers.py:349
def add_param_copy_operators(self, net)
Definition: layers.py:404
def add_ops_to_accumulate_pred(self, net)
Definition: layers.py:397
def add_init_params(self, init_net)
Definition: layers.py:302