Dada una matriz 2D (M x N) y un kernel 2D (K x L), ¿cómo devuelvo una matriz que es el resultado de la agrupación máxima o media usando el kernel dado sobre la imagen?
Me gustaría usar numpy si es posible.
Nota: M, N, K, L pueden ser pares o impares y no es necesario que sean perfectamente divisibles entre sí, por ejemplo: matriz 7x5 y núcleo 2x2.
por ejemplo, de agrupación máxima:
matrix: array([[ 20, 200, -5, 23], [ -13, 134, 119, 100], [ 120, 32, 49, 25], [-120, 12, 09, 23]]) kernel: 2 x 2 soln: array([[ 200, 119], [ 120, 49]])Podrías usar scikit-image block_reduce :
import numpy as np import skimage.measure a = np.array([ [ 20, 200, -5, 23], [ -13, 134, 119, 100], [ 120, 32, 49, 25], [-120, 12, 9, 23] ]) skimage.measure.block_reduce(a, (2,2), np.max)Da:
array([[200, 119], [120, 49]])Si el tamaño de la imagen es divisible uniformemente por el tamaño del kernel, puede remodelar la matriz y usar el max o mean como mejor le parezca.
import numpy as np mat = np.array([[ 20, 200, -5, 23], [ -13, 134, 119, 100], [ 120, 32, 49, 25], [-120, 12, 9, 23]]) M, N = mat.shape K = 2 L = 2 MK = M // K NL = N // L print(mat[:MK*K, :NL*L].reshape(MK, K, NL, L).max(axis=(1, 3))) # [[200, 119], [120, 49]]Si no tiene un número par de kernels, tendrá que manejar los límites por separado. (Como se señaló en los comentarios, esto da como resultado que se copie la matriz, lo que afectará el rendimiento).
mat = np.array([[20, 200, -5, 23, 7], [-13, 134, 119, 100, 8], [120, 32, 49, 25, 12], [-120, 12, 9, 23, 15], [-57, 84, 19, 17, 82], ]) # soln # [200, 119, 8] # [120, 49, 15] # [84, 19, 82] M, N = mat.shape K = 2 L = 2 MK = M // K NL = N // L # split the matrix into 'quadrants' Q1 = mat[:MK * K, :NL * L].reshape(MK, K, NL, L).max(axis=(1, 3)) Q2 = mat[MK * K:, :NL * L].reshape(-1, NL, L).max(axis=2) Q3 = mat[:MK * K, NL * L:].reshape(MK, K, -1).max(axis=1) Q4 = mat[MK * K:, NL * L:].max() # compose the individual quadrants into one new matrix soln = np.vstack([np.c_[Q1, Q3], np.c_[Q2, Q4]]) print(soln) # [[200 119 8] # [120 49 15] # [ 84 19 82]]En lugar de hacer "cuadrantes" como se muestra en la respuesta de Elliot, podríamos rellenarlo para que sea divisible de manera uniforme y luego realizar una agrupación máxima o media.
Como la agrupación se usa a menudo en CNN, la matriz de entrada suele ser 3D. Así que hice una función que funciona en matrices 2D o 3D.
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 resultA veces, es posible que desee realizar una agrupación superpuesta, a un ritmo que no es igual al tamaño del núcleo. Aquí hay una función que hace eso, con o sin relleno:
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