Caffe2 - Python API
A deep learning, cross platform ML framework
fc.py
1 ## @package fc
2 # Module caffe2.python.layers.fc
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 from caffe2.python import schema
9 from caffe2.python.layers.layers import ModelLayer
10 from caffe2.python.layers.sampling_trainable_mixin import SamplingTrainableMixin
11 import math
12 import numpy as np
13 
14 
16 
17  def __init__(self, model, input_record, output_dims, weight_init=None,
18  bias_init=None, weight_optim=None, bias_optim=None, name='fc',
19  weight_reg=None, bias_reg=None, clip_param=None,
20  max_fc_size=None, axis=1,
21  **kwargs):
22  super(FC, self).__init__(model, name, input_record, **kwargs)
23  assert isinstance(input_record, schema.Scalar), (
24  "Incorrect input type {}".format(input_record))
25  assert len(input_record.field_types()[0].shape) > 0, (
26  "FC expects limited dimensions of the input tensor")
27  assert axis >= 1, "axis {} should >= 1.".format(axis)
28  self.axis = axis
29  input_dims = np.prod(input_record.field_types()[0].shape[axis - 1:])
30 
31  assert input_dims > 0, (
32  "FC expects input dimensions > 0, got {}".format(input_dims))
33 
34  self.clip_args = None
35  if (clip_param is not None):
36  assert len(clip_param) == 2, (
37  'clip_param must be a tuple / list '
38  'of length 2 and in the form of (clip_min, clip max)'
39  )
40  clip_min, clip_max = clip_param
41  assert clip_min is not None or clip_max is not None, (
42  'clip_min, and clip_max in clip_param cannot both be None'
43  )
44  assert (
45  (clip_min is None or clip_max is None) or clip_min < clip_max
46  ), (
47  'clip_param = [clip_min, clip_max] must have clip_min < clip_max'
48  )
49  self.clip_args = {}
50  if clip_min is not None:
51  self.clip_args['min'] = clip_min
52  if clip_max is not None:
53  self.clip_args['max'] = clip_max
54 
55  scale = math.sqrt(1.0 / input_dims)
56  weight_init = weight_init if weight_init else (
57  'UniformFill', {'min': -scale, 'max': scale})
58  bias_init = bias_init if bias_init else (
59  'UniformFill', {'min': -scale, 'max': scale})
60 
61  self.output_dim_vec = FC.calculate_fc_output_dims(
62  max_fc_size, input_dims, output_dims)
63 
64  if self.output_dim_vec is None or len(self.output_dim_vec) == 1:
65  self.w = self.create_param(param_name='w',
66  shape=[output_dims, input_dims],
67  initializer=weight_init,
68  optimizer=weight_optim,
69  regularizer=weight_reg)
70 
71  self.b = self.create_param(param_name='b',
72  shape=[output_dims, ],
73  initializer=bias_init,
74  optimizer=bias_optim,
75  regularizer=bias_reg)
76  else:
77  self.w_vec = []
78  self.b_vec = []
79 
80  for idx, output_dim in enumerate(self.output_dim_vec):
81  self.w_vec.append(self.create_param(param_name='w_sub_{}'.format(idx),
82  shape=[output_dim, input_dims],
83  initializer=weight_init,
84  optimizer=weight_optim,
85  regularizer=weight_reg))
86 
87  self.b_vec.append(self.create_param(param_name='b_sub_{}'.format(idx),
88  shape=[output_dim, ],
89  initializer=weight_init,
90  optimizer=weight_optim,
91  regularizer=weight_reg))
92  if axis == 1:
93  output_shape = (output_dims, )
94  else:
95  output_shape = list(input_record.field_types()[0].shape)[0: axis - 1]
96  output_shape = tuple(output_shape + [output_dims])
97 
99  (np.float32, output_shape),
100  self.get_next_blob_reference('output')
101  )
102 
103  @staticmethod
104  def calculate_fc_output_dims(max_fc_size, input_dim, output_dim):
105 
106  if not max_fc_size or max_fc_size < 0:
107  return None
108 
109  assert max_fc_size >= input_dim, "Currently we split along the output " \
110  "dimension. So we need max_fc_size >= input_dim. But, max_fc_size: " \
111  "{}, input_dim: {}".format(max_fc_size, input_dim)
112 
113  output_dim_allowed = int(np.floor(max_fc_size / input_dim))
114  num_fc = int(np.floor((output_dim - 1) / output_dim_allowed) + 1)
115 
116  output_dim_vec = [output_dim_allowed] * (num_fc - 1)
117 
118  output_dim_vec.append(output_dim - sum(output_dim_vec))
119 
120  return output_dim_vec
121 
122  def _add_ops(self, net, params):
123  if self.clip_args is not None:
124  clipped_params = [net.NextScopedBlob(
125  'clipped_%s' % str(p)) for p in params]
126  for p, cp in zip(params, clipped_params):
127  net.Clip([p], [cp], **self.clip_args)
128 
129  params = clipped_params
130 
131  if self.output_dim_vec is None or len(self.output_dim_vec) == 1:
132  net.FC(self.input_record.field_blobs() + params,
133  self.output_schema.field_blobs(), axis=self.axis, **self.kwargs)
134  else:
135  w_vec = params[:int(len(params) / 2)]
136  b_vec = params[int(len(params) / 2):]
137 
138  assert len(w_vec) == len(b_vec)
139 
140  output_blob_vec = []
141 
142  for i in range(len(self.output_dim_vec)):
143  output_blob = net.NextScopedBlob(
144  'output_sub_{}'.format(i))
145  output_blob_vec.append(
146  net.FC(self.input_record.field_blobs() +
147  [w_vec[i], b_vec[i]],
148  [output_blob], axis=self.axis, **self.kwargs))
149 
150  net.Concat(output_blob_vec,
151  self.output_schema.field_blobs() +
152  [self.output_schema.field_blobs()[0] + "_concat_dims"])
153 
154  @property
155  def param_blobs(self):
156  if self.output_dim_vec is None or len(self.output_dim_vec) == 1:
157  return [self.w, self.b]
158  else:
159  return self.w_vec + self.b_vec
def get_next_blob_reference(self, name)
Definition: layers.py:349
def create_param(self, param_name, shape, initializer, optimizer, ps_param=None, regularizer=None)
Definition: layers.py:334