Instead of making “quadrants,” as Elliott’s answer showed, we could pad it to make it evenly divisible, and then execute either max or middle pool.
Because combining in CNN is often used, the input array is usually 3D. Thus, I created a function that works on two-dimensional or three-dimensional arrays.
def pooling(mat,ksize,method='max',pad=False): '''Non-overlapping pooling on 2D or 3D data. <mat>: ndarray, input array to pool. <ksize>: tuple of 2, kernel size in (ky, kx). <method>: str, 'max for max-pooling, 'mean' for mean-pooling. <pad>: bool, pad <mat> or not. If no pad, output has size n//f, n being <mat> size, f being kernel size. if pad, output has size ceil(n/f). Return <result>: pooled matrix. ''' m, n = mat.shape[:2] ky,kx=ksize _ceil=lambda x,y: int(numpy.ceil(x/float(y))) if pad: ny=_ceil(m,ky) nx=_ceil(n,kx) size=(ny*ky, nx*kx)+mat.shape[2:] mat_pad=numpy.full(size,numpy.nan) mat_pad[:m,:n,...]=mat else: ny=m//ky nx=n//kx mat_pad=mat[:ny*ky, :nx*kx, ...] new_shape=(ny,ky,nx,kx)+mat.shape[2:] if method=='max': result=numpy.nanmax(mat_pad.reshape(new_shape),axis=(1,3)) else: result=numpy.nanmean(mat_pad.reshape(new_shape),axis=(1,3)) return result
Sometimes you may need to execute an overlapping pool in increments not equal to the size of the kernel. Here is a function that does this with or without adding:
def asStride(arr,sub_shape,stride): '''Get a strided sub-matrices view of an ndarray. See also skimage.util.shape.view_as_windows() ''' s0,s1=arr.strides[:2] m1,n1=arr.shape[:2] m2,n2=sub_shape view_shape=(1+(m1-m2)//stride[0],1+(n1-n2)//stride[1],m2,n2)+arr.shape[2:] strides=(stride[0]*s0,stride[1]*s1,s0,s1)+arr.strides[2:] subs=numpy.lib.stride_tricks.as_strided(arr,view_shape,strides=strides) return subs def poolingOverlap(mat,ksize,stride=None,method='max',pad=False): '''Overlapping pooling on 2D or 3D data. <mat>: ndarray, input array to pool. <ksize>: tuple of 2, kernel size in (ky, kx). <stride>: tuple of 2 or None, stride of pooling window. If None, same as <ksize> (non-overlapping pooling). <method>: str, 'max for max-pooling, 'mean' for mean-pooling. <pad>: bool, pad <mat> or not. If no pad, output has size (nf)//s+1, n being <mat> size, f being kernel size, s stride. if pad, output has size ceil(n/s). Return <result>: pooled matrix. ''' m, n = mat.shape[:2] ky,kx=ksize if stride is None: stride=(ky,kx) sy,sx=stride _ceil=lambda x,y: int(numpy.ceil(x/float(y))) if pad: ny=_ceil(m,sy) nx=_ceil(n,sx) size=((ny-1)*sy+ky, (nx-1)*sx+kx) + mat.shape[2:] mat_pad=numpy.full(size,numpy.nan) mat_pad[:m,:n,...]=mat else: mat_pad=mat[:(m-ky)//sy*sy+ky, :(n-kx)//sx*sx+kx, ...] view=asStride(mat_pad,ksize,stride) if method=='max': result=numpy.nanmax(view,axis=(2,3)) else: result=numpy.nanmean(view,axis=(2,3)) return result