Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/keras/src/layers/locally_connected/locally_connected2d.py: 22%
103 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 07:57 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 07:57 +0000
1# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
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# ==============================================================================
16"""Locally-connected layer for 2D input."""
18from keras.src import activations
19from keras.src import backend
20from keras.src import constraints
21from keras.src import initializers
22from keras.src import regularizers
23from keras.src.engine.base_layer import Layer
24from keras.src.engine.input_spec import InputSpec
25from keras.src.layers.locally_connected import locally_connected_utils
26from keras.src.utils import conv_utils
27from keras.src.utils import tf_utils
29# isort: off
30from tensorflow.python.util.tf_export import keras_export
33@keras_export("keras.layers.LocallyConnected2D")
34class LocallyConnected2D(Layer):
35 """Locally-connected layer for 2D inputs.
37 The `LocallyConnected2D` layer works similarly
38 to the `Conv2D` layer, except that weights are unshared,
39 that is, a different set of filters is applied at each
40 different patch of the input.
42 Note: layer attributes cannot be modified after the layer has been called
43 once (except the `trainable` attribute).
45 Examples:
46 ```python
47 # apply a 3x3 unshared weights convolution with 64 output filters on a
48 32x32 image
49 # with `data_format="channels_last"`:
50 model = Sequential()
51 model.add(LocallyConnected2D(64, (3, 3), input_shape=(32, 32, 3)))
52 # now model.output_shape == (None, 30, 30, 64)
53 # notice that this layer will consume (30*30)*(3*3*3*64) + (30*30)*64
54 parameters
56 # add a 3x3 unshared weights convolution on top, with 32 output filters:
57 model.add(LocallyConnected2D(32, (3, 3)))
58 # now model.output_shape == (None, 28, 28, 32)
59 ```
61 Args:
62 filters: Integer, the dimensionality of the output space (i.e. the
63 number of output filters in the convolution).
64 kernel_size: An integer or tuple/list of 2 integers, specifying the
65 width and height of the 2D convolution window. Can be a single integer
66 to specify the same value for all spatial dimensions.
67 strides: An integer or tuple/list of 2 integers, specifying the strides
68 of the convolution along the width and height. Can be a single integer
69 to specify the same value for all spatial dimensions.
70 padding: Currently only support `"valid"` (case-insensitive). `"same"`
71 will be supported in future. `"valid"` means no padding.
72 data_format: A string, one of `channels_last` (default) or
73 `channels_first`. The ordering of the dimensions in the inputs.
74 `channels_last` corresponds to inputs with shape `(batch, height,
75 width, channels)` while `channels_first` corresponds to inputs with
76 shape
77 `(batch, channels, height, width)`. When unspecified, uses
78 `image_data_format` value found in your Keras config file at
79 `~/.keras/keras.json` (if exists) else 'channels_last'.
80 Defaults to 'channels_last'.
81 activation: Activation function to use. If you don't specify anything,
82 no activation is applied (ie. "linear" activation: `a(x) = x`).
83 use_bias: Boolean, whether the layer uses a bias vector.
84 kernel_initializer: Initializer for the `kernel` weights matrix.
85 bias_initializer: Initializer for the bias vector.
86 kernel_regularizer: Regularizer function applied to the `kernel` weights
87 matrix.
88 bias_regularizer: Regularizer function applied to the bias vector.
89 activity_regularizer: Regularizer function applied to the output of the
90 layer (its "activation").
91 kernel_constraint: Constraint function applied to the kernel matrix.
92 bias_constraint: Constraint function applied to the bias vector.
93 implementation: implementation mode, either `1`, `2`, or `3`. `1` loops
94 over input spatial locations to perform the forward pass. It is
95 memory-efficient but performs a lot of (small) ops. `2` stores layer
96 weights in a dense but sparsely-populated 2D matrix and implements the
97 forward pass as a single matrix-multiply. It uses a lot of RAM but
98 performs few (large) ops. `3` stores layer weights in a sparse tensor
99 and implements the forward pass as a single sparse matrix-multiply.
100 How to choose:
101 `1`: large, dense models,
102 `2`: small models,
103 `3`: large, sparse models, where "large" stands for large
104 input/output activations (i.e. many `filters`, `input_filters`,
105 large `np.prod(input_size)`, `np.prod(output_size)`), and "sparse"
106 stands for few connections between inputs and outputs, i.e. small
107 ratio `filters * input_filters * np.prod(kernel_size) /
108 (np.prod(input_size) * np.prod(strides))`, where inputs to and
109 outputs of the layer are assumed to have shapes `input_size +
110 (input_filters,)`, `output_size + (filters,)` respectively. It is
111 recommended to benchmark each in the setting of interest to pick
112 the most efficient one (in terms of speed and memory usage).
113 Correct choice of implementation can lead to dramatic speed
114 improvements (e.g. 50X), potentially at the expense of RAM. Also,
115 only `padding="valid"` is supported by `implementation=1`.
116 Input shape:
117 4D tensor with shape: `(samples, channels, rows, cols)` if
118 data_format='channels_first'
119 or 4D tensor with shape: `(samples, rows, cols, channels)` if
120 data_format='channels_last'.
121 Output shape:
122 4D tensor with shape: `(samples, filters, new_rows, new_cols)` if
123 data_format='channels_first'
124 or 4D tensor with shape: `(samples, new_rows, new_cols, filters)` if
125 data_format='channels_last'. `rows` and `cols` values might have
126 changed due to padding.
127 """
129 def __init__(
130 self,
131 filters,
132 kernel_size,
133 strides=(1, 1),
134 padding="valid",
135 data_format=None,
136 activation=None,
137 use_bias=True,
138 kernel_initializer="glorot_uniform",
139 bias_initializer="zeros",
140 kernel_regularizer=None,
141 bias_regularizer=None,
142 activity_regularizer=None,
143 kernel_constraint=None,
144 bias_constraint=None,
145 implementation=1,
146 **kwargs,
147 ):
148 super().__init__(**kwargs)
149 self.filters = filters
150 self.kernel_size = conv_utils.normalize_tuple(
151 kernel_size, 2, "kernel_size"
152 )
153 self.strides = conv_utils.normalize_tuple(
154 strides, 2, "strides", allow_zero=True
155 )
156 self.padding = conv_utils.normalize_padding(padding)
157 if self.padding != "valid" and implementation == 1:
158 raise ValueError(
159 "Invalid border mode for LocallyConnected2D "
160 '(only "valid" is supported if implementation is 1): ' + padding
161 )
162 self.data_format = conv_utils.normalize_data_format(data_format)
163 self.activation = activations.get(activation)
164 self.use_bias = use_bias
165 self.kernel_initializer = initializers.get(kernel_initializer)
166 self.bias_initializer = initializers.get(bias_initializer)
167 self.kernel_regularizer = regularizers.get(kernel_regularizer)
168 self.bias_regularizer = regularizers.get(bias_regularizer)
169 self.activity_regularizer = regularizers.get(activity_regularizer)
170 self.kernel_constraint = constraints.get(kernel_constraint)
171 self.bias_constraint = constraints.get(bias_constraint)
172 self.implementation = implementation
173 self.input_spec = InputSpec(ndim=4)
175 @property
176 def _use_input_spec_as_call_signature(self):
177 return False
179 @tf_utils.shape_type_conversion
180 def build(self, input_shape):
181 if self.data_format == "channels_last":
182 input_row, input_col = input_shape[1:-1]
183 input_filter = input_shape[3]
184 else:
185 input_row, input_col = input_shape[2:]
186 input_filter = input_shape[1]
187 if input_row is None or input_col is None:
188 raise ValueError(
189 "The spatial dimensions of the inputs to "
190 " a LocallyConnected2D layer "
191 "should be fully-defined, but layer received "
192 "the inputs shape " + str(input_shape)
193 )
194 output_row = conv_utils.conv_output_length(
195 input_row, self.kernel_size[0], self.padding, self.strides[0]
196 )
197 output_col = conv_utils.conv_output_length(
198 input_col, self.kernel_size[1], self.padding, self.strides[1]
199 )
200 self.output_row = output_row
201 self.output_col = output_col
203 if self.output_row <= 0 or self.output_col <= 0:
204 raise ValueError(
205 "One of the dimensions in the output is <= 0 "
206 f"due to downsampling in {self.name}. Consider "
207 "increasing the input size. "
208 f"Received input shape {input_shape} which would produce "
209 "output shape with a zero or negative value in a "
210 "dimension."
211 )
213 if self.implementation == 1:
214 self.kernel_shape = (
215 output_row * output_col,
216 self.kernel_size[0] * self.kernel_size[1] * input_filter,
217 self.filters,
218 )
220 self.kernel = self.add_weight(
221 shape=self.kernel_shape,
222 initializer=self.kernel_initializer,
223 name="kernel",
224 regularizer=self.kernel_regularizer,
225 constraint=self.kernel_constraint,
226 )
228 elif self.implementation == 2:
229 if self.data_format == "channels_first":
230 self.kernel_shape = (
231 input_filter,
232 input_row,
233 input_col,
234 self.filters,
235 self.output_row,
236 self.output_col,
237 )
238 else:
239 self.kernel_shape = (
240 input_row,
241 input_col,
242 input_filter,
243 self.output_row,
244 self.output_col,
245 self.filters,
246 )
248 self.kernel = self.add_weight(
249 shape=self.kernel_shape,
250 initializer=self.kernel_initializer,
251 name="kernel",
252 regularizer=self.kernel_regularizer,
253 constraint=self.kernel_constraint,
254 )
256 self.kernel_mask = (
257 locally_connected_utils.get_locallyconnected_mask(
258 input_shape=(input_row, input_col),
259 kernel_shape=self.kernel_size,
260 strides=self.strides,
261 padding=self.padding,
262 data_format=self.data_format,
263 )
264 )
266 elif self.implementation == 3:
267 self.kernel_shape = (
268 self.output_row * self.output_col * self.filters,
269 input_row * input_col * input_filter,
270 )
272 self.kernel_idxs = sorted(
273 conv_utils.conv_kernel_idxs(
274 input_shape=(input_row, input_col),
275 kernel_shape=self.kernel_size,
276 strides=self.strides,
277 padding=self.padding,
278 filters_in=input_filter,
279 filters_out=self.filters,
280 data_format=self.data_format,
281 )
282 )
284 self.kernel = self.add_weight(
285 shape=(len(self.kernel_idxs),),
286 initializer=self.kernel_initializer,
287 name="kernel",
288 regularizer=self.kernel_regularizer,
289 constraint=self.kernel_constraint,
290 )
292 else:
293 raise ValueError(
294 "Unrecognized implementation mode: %d." % self.implementation
295 )
297 if self.use_bias:
298 self.bias = self.add_weight(
299 shape=(output_row, output_col, self.filters),
300 initializer=self.bias_initializer,
301 name="bias",
302 regularizer=self.bias_regularizer,
303 constraint=self.bias_constraint,
304 )
305 else:
306 self.bias = None
307 if self.data_format == "channels_first":
308 self.input_spec = InputSpec(ndim=4, axes={1: input_filter})
309 else:
310 self.input_spec = InputSpec(ndim=4, axes={-1: input_filter})
311 self.built = True
313 @tf_utils.shape_type_conversion
314 def compute_output_shape(self, input_shape):
315 if self.data_format == "channels_first":
316 rows = input_shape[2]
317 cols = input_shape[3]
318 elif self.data_format == "channels_last":
319 rows = input_shape[1]
320 cols = input_shape[2]
322 rows = conv_utils.conv_output_length(
323 rows, self.kernel_size[0], self.padding, self.strides[0]
324 )
325 cols = conv_utils.conv_output_length(
326 cols, self.kernel_size[1], self.padding, self.strides[1]
327 )
329 if self.data_format == "channels_first":
330 return (input_shape[0], self.filters, rows, cols)
331 elif self.data_format == "channels_last":
332 return (input_shape[0], rows, cols, self.filters)
334 def call(self, inputs):
335 if self.implementation == 1:
336 output = backend.local_conv(
337 inputs,
338 self.kernel,
339 self.kernel_size,
340 self.strides,
341 (self.output_row, self.output_col),
342 self.data_format,
343 )
345 elif self.implementation == 2:
346 output = locally_connected_utils.local_conv_matmul(
347 inputs,
348 self.kernel,
349 self.kernel_mask,
350 self.compute_output_shape(inputs.shape),
351 )
353 elif self.implementation == 3:
354 output = locally_connected_utils.local_conv_sparse_matmul(
355 inputs,
356 self.kernel,
357 self.kernel_idxs,
358 self.kernel_shape,
359 self.compute_output_shape(inputs.shape),
360 )
362 else:
363 raise ValueError(
364 "Unrecognized implementation mode: %d." % self.implementation
365 )
367 if self.use_bias:
368 output = backend.bias_add(
369 output, self.bias, data_format=self.data_format
370 )
372 output = self.activation(output)
373 return output
375 def get_config(self):
376 config = {
377 "filters": self.filters,
378 "kernel_size": self.kernel_size,
379 "strides": self.strides,
380 "padding": self.padding,
381 "data_format": self.data_format,
382 "activation": activations.serialize(self.activation),
383 "use_bias": self.use_bias,
384 "kernel_initializer": initializers.serialize(
385 self.kernel_initializer
386 ),
387 "bias_initializer": initializers.serialize(self.bias_initializer),
388 "kernel_regularizer": regularizers.serialize(
389 self.kernel_regularizer
390 ),
391 "bias_regularizer": regularizers.serialize(self.bias_regularizer),
392 "activity_regularizer": regularizers.serialize(
393 self.activity_regularizer
394 ),
395 "kernel_constraint": constraints.serialize(self.kernel_constraint),
396 "bias_constraint": constraints.serialize(self.bias_constraint),
397 "implementation": self.implementation,
398 }
399 base_config = super().get_config()
400 return dict(list(base_config.items()) + list(config.items()))