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._output_schema = None
241  self._predict_output_schema = None
242  self.eval_output_schema = None
243  self.tags = set(tags or [])
244  self.tags.update(TagContext.current().tags)
245  self.params = []
246  self._export_output_for_metrics = False
247  self._export_params_for_metrics = False
248 
249  def get_type(self):
250  return self.__class__.__name__
251 
252  def _check_output_schema(self):
253  assert self._output_schema is not None, "Schema is not initialized"
254  assert (self._predict_output_schema is None or
255  schema.is_schema_subset(self._predict_output_schema,
256  self._output_schema)), (
257  "predict_output_schema is not a subset of the output_schema")
258 
259  @property
260  def predict_input_record(self):
261  return self._predict_input_record or self._input_record
262 
263  @property
264  def input_record(self):
265  return self._input_record
266 
267  @property
268  def predict_output_schema(self):
269  self._check_output_schema()
270  return self._predict_output_schema or self._output_schema
271 
272  @predict_output_schema.setter
273  def predict_output_schema(self, output_schema):
274  assert self._predict_output_schema is None
275  self._predict_output_schema = output_schema
276 
277  @property
278  def output_schema(self):
279  if self.request_only:
280  set_request_only(self._output_schema)
281  self._check_output_schema()
282  return self._output_schema
283 
284  @output_schema.setter
285  def output_schema(self, output_schema):
286  assert self._output_schema is None
287  self._output_schema = output_schema
288 
289  def get_parameters(self):
290  return self.params
291 
293  """Return a subset of parameters which can be converted to fp16"""
294  return []
295 
296  def get_memory_usage(self):
297  return 0
298 
299  def add_init_params(self, init_net):
300  '''
301  Adds layer initialization operators to passed net.
302  '''
303  for param in self.params:
304  # TODO(amalevich): Either return back to lambdas, that add
305  # all params (looks a bit safer and breaking less
306  # abstractions) or extend Net interface to this type of
307  # operations better
308  # TODO(xlwang) init_net._net.op has type google.protobuf.\
309  # internal.containers.RepeatedCompositeFieldContainer, but
310  # the version of protobuf in fbcode does not support append
311  # so extend is used
312  init_op = param.initializer
313  current_device_scope = scope.CurrentDeviceScope()
314  if not init_op:
315  continue
316 
317  if not init_op.HasField('device_option') and\
318  current_device_scope:
319  init_op = caffe2_pb2.OperatorDef()
320  init_op.CopyFrom(param.initializer)
321  init_op.device_option.CopyFrom(current_device_scope)
322 
323  # do not add duplicated init ops
324  if any(utils.OpAlmostEqual(op, init_op, 'debug_info')
325  for op in init_net._net.op):
326  continue
327 
328  init_net._net.op.extend([init_op])
329 
330  def create_param(self, param_name, shape, initializer, optimizer,
331  ps_param=None, regularizer=None):
332  with scope.NameScope(self.name, reset=True):
333  param = self.model.create_param(param_name=param_name,
334  shape=shape,
335  initializer=initializer,
336  optimizer=optimizer,
337  ps_param=ps_param,
338  regularizer=regularizer)
339 
340  # make sure we don't share parameters in the same layer
341  assert all(param.parameter != p.parameter for p in self.params)
342 
343  self.params.append(param)
344  return param.parameter
345 
346  def get_next_blob_reference(self, name):
347  with scope.NameScope(self.name, reset=True):
348  return self.model.net.NextScopedBlob(name)
349 
350  def add_operators(self, net, init_net=None,
351  context=InstantiationContext.TRAINING):
352  '''
353  Adds layer trainig or initialization operators to the passed in net.
354  init_net can be None and can be called independently from add_init_params
355  '''
356  # Namescope below should warranty that all intermediate blobs will be
357  # assiciated with the layer that produces them
358  with scope.NameScope(self.name):
359  if context not in {InstantiationContext.PREDICTION,
360  InstantiationContext.EVAL,
361  InstantiationContext.ACCUMULATE_PRED}:
362  assert init_net, (
363  "Only prediction and eval context don't need init_net")
364  if init_net:
365  self.add_init_params(init_net)
366  if context == InstantiationContext.TRAINING:
367  self.add_train_ops(net)
368  elif context == InstantiationContext.EVAL:
369  self.add_eval_ops(net)
370  elif context == InstantiationContext.ACCUMULATE_PRED:
372  else:
373  self.add_ops(net)
374 
375  if context in {InstantiationContext.TRAINING,
376  InstantiationContext.EVAL} \
378  self.add_param_copy_operators(net)
379 
380  def add_ops(self, net):
381  # Predict layer implementation.
382  raise NotImplementedError
383 
384  def add_eval_ops(self, net):
385  # Default eval layer implementation is completely matching
386  # predict layer implementation.
387  self.add_ops(net)
388 
389  def add_train_ops(self, net):
390  # Default train layer implementation is completely matching
391  # eval layer implementation.
392  self.add_eval_ops(net)
393 
394  def add_ops_to_accumulate_pred(self, net):
395  # This adds operators to accumulate predictions/labels/weights. The
396  # accumulated data can later be used to compute calibration or for other
397  # purpose. Default layer implementation is completely matching eval
398  # layer implementation.
399  self.add_eval_ops(net)
400 
401  def add_param_copy_operators(self, net):
402  for param in self.params:
403  param_copy_ref = self.model.metrics_schema[str(param.parameter)]
404  net.Copy([param.parameter], param_copy_ref.field_blobs())
405 
406  def export_output_for_metrics(self):
407  self._export_output_for_metrics = True
408 
409  # Export output of the layer directly
410  export_name = self.name + "/output"
411  self.model.add_metric_field(export_name, self.output_schema)
412 
413  def export_params_for_metrics(self):
414  self._export_params_for_metrics = True
415 
416  # Export copies of parameters
417  for param in self.params:
418  param_copy_ref = self.get_next_blob_reference(
419  str(param).split("/")[-1] + "_copy")
420  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:351
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:346
def add_param_copy_operators(self, net)
Definition: layers.py:401
def add_ops_to_accumulate_pred(self, net)
Definition: layers.py:394
def add_init_params(self, init_net)
Definition: layers.py:299