# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # pylint: disable=invalid-name, line-too-long, unused-variable, too-many-locals, too-many-branches # ruff: noqa: F841 """Convolution in python""" import numpy as np import scipy from tvm.topi.nn.utils import get_pad_tuple def _conv2d_nchw_python(a_np, w_np, stride, padding): """Convolution operator in NCHW layout. Parameters ---------- a_np : numpy.ndarray 4-D with shape [batch, in_channel, in_height, in_width] w_np : numpy.ndarray 4-D with shape [num_filter, in_channel, filter_height, filter_width] stride : int or a list/tuple of two ints Stride size, or [stride_height, stride_width] padding : int or str or a list/tuple of 2 or 4 ints Padding size, or ['VALID', 'SAME'], or [pad_height, pad_width] for 2 ints, or [pad_top, pad_left, pad_bottom, pad_right] for 2 ints Returns ------- b_np : np.ndarray 4-D with shape [batch, out_channel, out_height, out_width] """ batch, in_channel, in_height, in_width = a_np.shape num_filter, _, kernel_h, kernel_w = w_np.shape if isinstance(stride, int): stride_h = stride_w = stride else: stride_h, stride_w = stride pad_top, pad_left, pad_bottom, pad_right = get_pad_tuple(padding, (kernel_h, kernel_w)) pad_h = pad_top + pad_bottom pad_w = pad_left + pad_right # compute the output shape out_channel = num_filter out_height = (in_height - kernel_h + pad_h) // stride_h + 1 out_width = (in_width - kernel_w + pad_w) // stride_w + 1 b_np = np.zeros((batch, out_channel, out_height, out_width), dtype=a_np.dtype) # computation for n in range(batch): for f in range(out_channel): for c in range(in_channel): if pad_h > 0 or pad_w > 0: apad = np.zeros((in_height + pad_h, in_width + pad_w), dtype=a_np.dtype) apad[pad_top : pad_top + in_height, pad_left : pad_left + in_width] = a_np[n, c] else: apad = a_np[n, c] out = _conv2d_hw(apad, w_np[f, c]) b_np[n, f] += out[::stride_h, ::stride_w] return b_np def _conv2d_hw(apad, w_np_fc): """2d convolution operator in HW layout. This is intended to be used as a subroutine from _conv2d_nchw_python. Using scipy.signal.convolve2d directly does not work for all dtypes (e.g. float16). Where possible, this function uses scipy.signal.convolve2d to take advantage of compiled scipy routines, falling back to an explicit loop only where needed Parameters ---------- a_np : numpy.ndarray 2-D with shape [in_height, in_width] w_np : numpy.ndarray 2-D with shape [filter_height, filter_width]. Returns ------- b_np : np.ndarray 2-D with shape [out_height, out_width] """ try: return scipy.signal.convolve2d(apad, np.rot90(np.rot90(w_np_fc)), mode="valid") except ValueError: pass assert len(apad.shape) == len(w_np_fc.shape) == 2 dtype = apad.dtype in_height, in_width = apad.shape kernel_h, kernel_w = w_np_fc.shape output_shape = [a_dim - w_dim + 1 for a_dim, w_dim in zip(apad.shape, w_np_fc.shape)] output = np.zeros(output_shape, dtype=apad.dtype) for y in range(output_shape[0]): for x in range(output_shape[1]): output[y][x] = np.sum(apad[y : y + kernel_h, x : x + kernel_w] * w_np_fc) return output def conv2d_nchw_python(a_np, w_np, stride, padding, groups=1): """Convolution operator in NCHW layout. Parameters ---------- a_np : numpy.ndarray 4-D with shape [batch, in_channel, in_height, in_width] w_np : numpy.ndarray 4-D with shape [num_filter, in_channel // groups, filter_height, filter_width] stride : int or a list/tuple of two ints Stride size, or [stride_height, stride_width] padding : int or str or a list/tuple of 2 or 4 ints Padding size, or ['VALID', 'SAME'], or [pad_height, pad_width] for 2 ints, or [pad_top, pad_left, pad_bottom, pad_right] for 2 ints groups : int Number of groups Returns ------- b_np : np.ndarray 4-D with shape [batch, out_channel, out_height, out_width] """ a_slices = np.array_split(a_np, groups, axis=1) w_slices = np.array_split(w_np, groups, axis=0) b_slices = [ _conv2d_nchw_python(a_slice, w_slice, stride, padding) for a_slice, w_slice in zip(a_slices, w_slices) ] b_np = np.concatenate(b_slices, axis=1) return b_np