Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tensorflow/python/keras/utils/conv_utils.py: 10%
182 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# ==============================================================================
15"""Utilities used by convolution layers."""
17import itertools
19import numpy as np
21from tensorflow.python.framework import ops
22from tensorflow.python.framework import tensor_shape
23from tensorflow.python.keras import backend
24from tensorflow.python.ops import array_ops
27def convert_data_format(data_format, ndim):
28 if data_format == 'channels_last':
29 if ndim == 3:
30 return 'NWC'
31 elif ndim == 4:
32 return 'NHWC'
33 elif ndim == 5:
34 return 'NDHWC'
35 else:
36 raise ValueError('Input rank not supported:', ndim)
37 elif data_format == 'channels_first':
38 if ndim == 3:
39 return 'NCW'
40 elif ndim == 4:
41 return 'NCHW'
42 elif ndim == 5:
43 return 'NCDHW'
44 else:
45 raise ValueError('Input rank not supported:', ndim)
46 else:
47 raise ValueError('Invalid data_format:', data_format)
50def normalize_tuple(value, n, name):
51 """Transforms a single integer or iterable of integers into an integer tuple.
53 Args:
54 value: The value to validate and convert. Could an int, or any iterable of
55 ints.
56 n: The size of the tuple to be returned.
57 name: The name of the argument being validated, e.g. "strides" or
58 "kernel_size". This is only used to format error messages.
60 Returns:
61 A tuple of n integers.
63 Raises:
64 ValueError: If something else than an int/long or iterable thereof was
65 passed.
66 """
67 if isinstance(value, int):
68 return (value,) * n
69 else:
70 try:
71 value_tuple = tuple(value)
72 except TypeError:
73 raise ValueError('The `' + name + '` argument must be a tuple of ' +
74 str(n) + ' integers. Received: ' + str(value))
75 if len(value_tuple) != n:
76 raise ValueError('The `' + name + '` argument must be a tuple of ' +
77 str(n) + ' integers. Received: ' + str(value))
78 for single_value in value_tuple:
79 try:
80 int(single_value)
81 except (ValueError, TypeError):
82 raise ValueError('The `' + name + '` argument must be a tuple of ' +
83 str(n) + ' integers. Received: ' + str(value) + ' '
84 'including element ' + str(single_value) + ' of type' +
85 ' ' + str(type(single_value)))
86 return value_tuple
89def conv_output_length(input_length, filter_size, padding, stride, dilation=1):
90 """Determines output length of a convolution given input length.
92 Args:
93 input_length: integer.
94 filter_size: integer.
95 padding: one of "same", "valid", "full", "causal"
96 stride: integer.
97 dilation: dilation rate, integer.
99 Returns:
100 The output length (integer).
101 """
102 if input_length is None:
103 return None
104 assert padding in {'same', 'valid', 'full', 'causal'}
105 dilated_filter_size = filter_size + (filter_size - 1) * (dilation - 1)
106 if padding in ['same', 'causal']:
107 output_length = input_length
108 elif padding == 'valid':
109 output_length = input_length - dilated_filter_size + 1
110 elif padding == 'full':
111 output_length = input_length + dilated_filter_size - 1
112 return (output_length + stride - 1) // stride
115def conv_input_length(output_length, filter_size, padding, stride):
116 """Determines input length of a convolution given output length.
118 Args:
119 output_length: integer.
120 filter_size: integer.
121 padding: one of "same", "valid", "full".
122 stride: integer.
124 Returns:
125 The input length (integer).
126 """
127 if output_length is None:
128 return None
129 assert padding in {'same', 'valid', 'full'}
130 if padding == 'same':
131 pad = filter_size // 2
132 elif padding == 'valid':
133 pad = 0
134 elif padding == 'full':
135 pad = filter_size - 1
136 return (output_length - 1) * stride - 2 * pad + filter_size
139def deconv_output_length(input_length,
140 filter_size,
141 padding,
142 output_padding=None,
143 stride=0,
144 dilation=1):
145 """Determines output length of a transposed convolution given input length.
147 Args:
148 input_length: Integer.
149 filter_size: Integer.
150 padding: one of `"same"`, `"valid"`, `"full"`.
151 output_padding: Integer, amount of padding along the output dimension. Can
152 be set to `None` in which case the output length is inferred.
153 stride: Integer.
154 dilation: Integer.
156 Returns:
157 The output length (integer).
158 """
159 assert padding in {'same', 'valid', 'full'}
160 if input_length is None:
161 return None
163 # Get the dilated kernel size
164 filter_size = filter_size + (filter_size - 1) * (dilation - 1)
166 # Infer length if output padding is None, else compute the exact length
167 if output_padding is None:
168 if padding == 'valid':
169 length = input_length * stride + max(filter_size - stride, 0)
170 elif padding == 'full':
171 length = input_length * stride - (stride + filter_size - 2)
172 elif padding == 'same':
173 length = input_length * stride
175 else:
176 if padding == 'same':
177 pad = filter_size // 2
178 elif padding == 'valid':
179 pad = 0
180 elif padding == 'full':
181 pad = filter_size - 1
183 length = ((input_length - 1) * stride + filter_size - 2 * pad +
184 output_padding)
185 return length
188def normalize_data_format(value):
189 if value is None:
190 value = backend.image_data_format()
191 data_format = value.lower()
192 if data_format not in {'channels_first', 'channels_last'}:
193 raise ValueError('The `data_format` argument must be one of '
194 '"channels_first", "channels_last". Received: ' +
195 str(value))
196 return data_format
199def normalize_padding(value):
200 if isinstance(value, (list, tuple)):
201 return value
202 padding = value.lower()
203 if padding not in {'valid', 'same', 'causal'}:
204 raise ValueError('The `padding` argument must be a list/tuple or one of '
205 '"valid", "same" (or "causal", only for `Conv1D). '
206 'Received: ' + str(padding))
207 return padding
210def conv_kernel_mask(input_shape, kernel_shape, strides, padding):
211 """Compute a mask representing the connectivity of a convolution operation.
213 Assume a convolution with given parameters is applied to an input having N
214 spatial dimensions with `input_shape = (d_in1, ..., d_inN)` to produce an
215 output with shape `(d_out1, ..., d_outN)`. This method returns a boolean array
216 of shape `(d_in1, ..., d_inN, d_out1, ..., d_outN)` with `True` entries
217 indicating pairs of input and output locations that are connected by a weight.
219 Example:
221 >>> input_shape = (4,)
222 >>> kernel_shape = (2,)
223 >>> strides = (1,)
224 >>> padding = "valid"
225 >>> conv_kernel_mask(input_shape, kernel_shape, strides, padding)
226 array([[ True, False, False],
227 [ True, True, False],
228 [False, True, True],
229 [False, False, True]])
231 where rows and columns correspond to inputs and outputs respectively.
234 Args:
235 input_shape: tuple of size N: `(d_in1, ..., d_inN)`, spatial shape of the
236 input.
237 kernel_shape: tuple of size N, spatial shape of the convolutional kernel /
238 receptive field.
239 strides: tuple of size N, strides along each spatial dimension.
240 padding: type of padding, string `"same"` or `"valid"`.
241 `"valid"` means no padding. `"same"` results in padding evenly to
242 the left/right or up/down of the input such that output has the same
243 height/width dimension as the input.
245 Returns:
246 A boolean 2N-D `np.ndarray` of shape
247 `(d_in1, ..., d_inN, d_out1, ..., d_outN)`, where `(d_out1, ..., d_outN)`
248 is the spatial shape of the output. `True` entries in the mask represent
249 pairs of input-output locations that are connected by a weight.
251 Raises:
252 ValueError: if `input_shape`, `kernel_shape` and `strides` don't have the
253 same number of dimensions.
254 NotImplementedError: if `padding` is not in {`"same"`, `"valid"`}.
255 """
256 if padding not in {'same', 'valid'}:
257 raise NotImplementedError('Padding type %s not supported. '
258 'Only "valid" and "same" '
259 'are implemented.' % padding)
261 in_dims = len(input_shape)
262 if isinstance(kernel_shape, int):
263 kernel_shape = (kernel_shape,) * in_dims
264 if isinstance(strides, int):
265 strides = (strides,) * in_dims
267 kernel_dims = len(kernel_shape)
268 stride_dims = len(strides)
269 if kernel_dims != in_dims or stride_dims != in_dims:
270 raise ValueError('Number of strides, input and kernel dimensions must all '
271 'match. Received: %d, %d, %d.' %
272 (stride_dims, in_dims, kernel_dims))
274 output_shape = conv_output_shape(input_shape, kernel_shape, strides, padding)
276 mask_shape = input_shape + output_shape
277 mask = np.zeros(mask_shape, np.bool_)
279 output_axes_ticks = [range(dim) for dim in output_shape]
280 for output_position in itertools.product(*output_axes_ticks):
281 input_axes_ticks = conv_connected_inputs(input_shape, kernel_shape,
282 output_position, strides, padding)
283 for input_position in itertools.product(*input_axes_ticks):
284 mask[input_position + output_position] = True
286 return mask
289def conv_kernel_idxs(input_shape, kernel_shape, strides, padding, filters_in,
290 filters_out, data_format):
291 """Yields output-input tuples of indices in a CNN layer.
293 The generator iterates over all `(output_idx, input_idx)` tuples, where
294 `output_idx` is an integer index in a flattened tensor representing a single
295 output image of a convolutional layer that is connected (via the layer
296 weights) to the respective single input image at `input_idx`
298 Example:
300 >>> input_shape = (2, 2)
301 >>> kernel_shape = (2, 1)
302 >>> strides = (1, 1)
303 >>> padding = "valid"
304 >>> filters_in = 1
305 >>> filters_out = 1
306 >>> data_format = "channels_last"
307 >>> list(conv_kernel_idxs(input_shape, kernel_shape, strides, padding,
308 ... filters_in, filters_out, data_format))
309 [(0, 0), (0, 2), (1, 1), (1, 3)]
311 Args:
312 input_shape: tuple of size N: `(d_in1, ..., d_inN)`, spatial shape of the
313 input.
314 kernel_shape: tuple of size N, spatial shape of the convolutional kernel /
315 receptive field.
316 strides: tuple of size N, strides along each spatial dimension.
317 padding: type of padding, string `"same"` or `"valid"`.
318 `"valid"` means no padding. `"same"` results in padding evenly to
319 the left/right or up/down of the input such that output has the same
320 height/width dimension as the input.
321 filters_in: `int`, number if filters in the input to the layer.
322 filters_out: `int', number if filters in the output of the layer.
323 data_format: string, "channels_first" or "channels_last".
325 Yields:
326 The next tuple `(output_idx, input_idx)`, where
327 `output_idx` is an integer index in a flattened tensor representing a single
328 output image of a convolutional layer that is connected (via the layer
329 weights) to the respective single input image at `input_idx`.
331 Raises:
332 ValueError: if `data_format` is neither
333 `"channels_last"` nor `"channels_first"`, or if number of strides, input,
334 and kernel number of dimensions do not match.
336 NotImplementedError: if `padding` is neither `"same"` nor `"valid"`.
337 """
338 if padding not in ('same', 'valid'):
339 raise NotImplementedError('Padding type %s not supported. '
340 'Only "valid" and "same" '
341 'are implemented.' % padding)
343 in_dims = len(input_shape)
344 if isinstance(kernel_shape, int):
345 kernel_shape = (kernel_shape,) * in_dims
346 if isinstance(strides, int):
347 strides = (strides,) * in_dims
349 kernel_dims = len(kernel_shape)
350 stride_dims = len(strides)
351 if kernel_dims != in_dims or stride_dims != in_dims:
352 raise ValueError('Number of strides, input and kernel dimensions must all '
353 'match. Received: %d, %d, %d.' %
354 (stride_dims, in_dims, kernel_dims))
356 output_shape = conv_output_shape(input_shape, kernel_shape, strides, padding)
357 output_axes_ticks = [range(dim) for dim in output_shape]
359 if data_format == 'channels_first':
360 concat_idxs = lambda spatial_idx, filter_idx: (filter_idx,) + spatial_idx
361 elif data_format == 'channels_last':
362 concat_idxs = lambda spatial_idx, filter_idx: spatial_idx + (filter_idx,)
363 else:
364 raise ValueError('Data format %s not recognized.'
365 '`data_format` must be "channels_first" or '
366 '"channels_last".' % data_format)
368 for output_position in itertools.product(*output_axes_ticks):
369 input_axes_ticks = conv_connected_inputs(input_shape, kernel_shape,
370 output_position, strides, padding)
371 for input_position in itertools.product(*input_axes_ticks):
372 for f_in in range(filters_in):
373 for f_out in range(filters_out):
374 out_idx = np.ravel_multi_index(
375 multi_index=concat_idxs(output_position, f_out),
376 dims=concat_idxs(output_shape, filters_out))
377 in_idx = np.ravel_multi_index(
378 multi_index=concat_idxs(input_position, f_in),
379 dims=concat_idxs(input_shape, filters_in))
380 yield (out_idx, in_idx)
383def conv_connected_inputs(input_shape, kernel_shape, output_position, strides,
384 padding):
385 """Return locations of the input connected to an output position.
387 Assume a convolution with given parameters is applied to an input having N
388 spatial dimensions with `input_shape = (d_in1, ..., d_inN)`. This method
389 returns N ranges specifying the input region that was convolved with the
390 kernel to produce the output at position
391 `output_position = (p_out1, ..., p_outN)`.
393 Example:
395 >>> input_shape = (4, 4)
396 >>> kernel_shape = (2, 1)
397 >>> output_position = (1, 1)
398 >>> strides = (1, 1)
399 >>> padding = "valid"
400 >>> conv_connected_inputs(input_shape, kernel_shape, output_position,
401 ... strides, padding)
402 [range(1, 3), range(1, 2)]
404 Args:
405 input_shape: tuple of size N: `(d_in1, ..., d_inN)`, spatial shape of the
406 input.
407 kernel_shape: tuple of size N, spatial shape of the convolutional kernel /
408 receptive field.
409 output_position: tuple of size N: `(p_out1, ..., p_outN)`, a single position
410 in the output of the convolution.
411 strides: tuple of size N, strides along each spatial dimension.
412 padding: type of padding, string `"same"` or `"valid"`.
413 `"valid"` means no padding. `"same"` results in padding evenly to
414 the left/right or up/down of the input such that output has the same
415 height/width dimension as the input.
417 Returns:
418 N ranges `[[p_in_left1, ..., p_in_right1], ...,
419 [p_in_leftN, ..., p_in_rightN]]` specifying the region in the
420 input connected to output_position.
421 """
422 ranges = []
424 ndims = len(input_shape)
425 for d in range(ndims):
426 left_shift = int(kernel_shape[d] / 2)
427 right_shift = kernel_shape[d] - left_shift
429 center = output_position[d] * strides[d]
431 if padding == 'valid':
432 center += left_shift
434 start = max(0, center - left_shift)
435 end = min(input_shape[d], center + right_shift)
437 ranges.append(range(start, end))
439 return ranges
442def conv_output_shape(input_shape, kernel_shape, strides, padding):
443 """Return the output shape of an N-D convolution.
445 Forces dimensions where input is empty (size 0) to remain empty.
447 Args:
448 input_shape: tuple of size N: `(d_in1, ..., d_inN)`, spatial shape of the
449 input.
450 kernel_shape: tuple of size N, spatial shape of the convolutional kernel /
451 receptive field.
452 strides: tuple of size N, strides along each spatial dimension.
453 padding: type of padding, string `"same"` or `"valid"`.
454 `"valid"` means no padding. `"same"` results in padding evenly to
455 the left/right or up/down of the input such that output has the same
456 height/width dimension as the input.
458 Returns:
459 tuple of size N: `(d_out1, ..., d_outN)`, spatial shape of the output.
460 """
461 dims = range(len(kernel_shape))
462 output_shape = [
463 conv_output_length(input_shape[d], kernel_shape[d], padding, strides[d])
464 for d in dims
465 ]
466 output_shape = tuple(
467 [0 if input_shape[d] == 0 else output_shape[d] for d in dims])
468 return output_shape
471def squeeze_batch_dims(inp, op, inner_rank):
472 """Returns `unsqueeze_batch(op(squeeze_batch(inp)))`.
474 Where `squeeze_batch` reshapes `inp` to shape
475 `[prod(inp.shape[:-inner_rank])] + inp.shape[-inner_rank:]`
476 and `unsqueeze_batch` does the reverse reshape but on the output.
478 Args:
479 inp: A tensor with dims `batch_shape + inner_shape` where `inner_shape`
480 is length `inner_rank`.
481 op: A callable that takes a single input tensor and returns a single.
482 output tensor.
483 inner_rank: A python integer.
485 Returns:
486 `unsqueeze_batch_op(squeeze_batch(inp))`.
487 """
488 with ops.name_scope_v2('squeeze_batch_dims'):
489 shape = inp.shape
491 inner_shape = shape[-inner_rank:]
492 if not inner_shape.is_fully_defined():
493 inner_shape = array_ops.shape(inp)[-inner_rank:]
495 batch_shape = shape[:-inner_rank]
496 if not batch_shape.is_fully_defined():
497 batch_shape = array_ops.shape(inp)[:-inner_rank]
499 if isinstance(inner_shape, tensor_shape.TensorShape):
500 inp_reshaped = array_ops.reshape(inp, [-1] + inner_shape.as_list())
501 else:
502 inp_reshaped = array_ops.reshape(
503 inp, array_ops.concat(([-1], inner_shape), axis=-1))
505 out_reshaped = op(inp_reshaped)
507 out_inner_shape = out_reshaped.shape[-inner_rank:]
508 if not out_inner_shape.is_fully_defined():
509 out_inner_shape = array_ops.shape(out_reshaped)[-inner_rank:]
511 out = array_ops.reshape(
512 out_reshaped, array_ops.concat((batch_shape, out_inner_shape), axis=-1))
514 out.set_shape(inp.shape[:-inner_rank] + out.shape[-inner_rank:])
515 return out