Python 如何使用numpy在2d阵列上执行最大/平均池

Python 如何使用numpy在2d阵列上执行最大/平均池,python,arrays,numpy,matrix,max-pooling,Python,Arrays,Numpy,Matrix,Max Pooling,给定一个2D(M x N)矩阵和一个2D内核(K x L),如何返回一个矩阵,该矩阵是使用给定内核对图像进行最大或平均池化的结果 如果可能的话,我想用numpy 注:M,N,K,L可以是偶数或奇数,它们不需要彼此完全可除,例如:7x5矩阵和2x2内核 最大池的示例: matrix: array([[ 20, 200, -5, 23], [ -13, 134, 119, 100], [ 120, 32, 49, 25], [-

给定一个2D(M x N)矩阵和一个2D内核(K x L),如何返回一个矩阵,该矩阵是使用给定内核对图像进行最大或平均池化的结果

如果可能的话,我想用numpy

注:M,N,K,L可以是偶数或奇数,它们不需要彼此完全可除,例如:7x5矩阵和2x2内核

最大池的示例:

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]])

您可以使用scikit图像:

将numpy导入为np
进口量
a=np.array([
[  20,  200,   -5,   23],
[ -13,  134,  119,  100],
[ 120,   32,   49,   25],
[-120,   12,    9,   23]
])
撇渣.测量.块减少(a,(2,2),np.最大值)
给出:

数组([[200119],
[120,  49]])

如果图像大小可以被内核大小平均整除,则可以重新调整数组的形状,并根据需要使用
max
mean

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]] 
如果内核数不是偶数,则必须分别处理边界。(正如评论中指出的,这会导致复制矩阵,从而影响性能)

我们不需要像Elliot的答案那样制作“象限”,我们可以填充它使其均匀可除,然后执行最大或平均池

由于CNN中经常使用池,因此输入阵列通常是3D的。所以我制作了一个可以在2D或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 result
def池(mat,ksize,method='max',pad=False):
''二维或三维数据的非重叠池。
:ndarray,池的输入数组。
:2的元组,内核大小为(ky,kx)。
:str,'最大值用于最大池,
“平均”表示平均池。
:bool,pad或not。如果没有焊盘,则输出大小为
n//f,n表示大小,f表示内核大小。
如果是pad,则输出的大小为ceil(n/f)。
返回:合并矩阵。
'''
m、 n=材料形状[:2]
ky,kx=ksize
_ceil=lambda x,y:int(numpy.ceil(x/float(y)))
如果pad:
纽约州=_ceil(米,肯塔基州)
nx=单元(n,kx)
尺寸=(ny*ky,nx*kx)+材料形状[2:]
mat_pad=numpy.full(大小,numpy.nan)
垫块[:m,:n,…]=垫块
其他:
ny=m//ky
nx=n//kx
垫=垫[:ny*ky,:nx*kx,…]
新的形状=(纽约州,肯塔基州,nx州,kx)+材料形状[2:]
如果方法=='max':
结果=numpy.nanmax(材料垫重塑(新形状),轴=(1,3))
其他:
结果=numpy.nanmean(材料垫重塑(新形状),轴=(1,3))
返回结果
有时,您可能希望以不等于内核大小的步幅执行重叠池。这里有一个函数可以实现这一点,无论有无填充:

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
           (n-f)//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
def asStride(arr、亚U形、跨步):
''获取数据阵列的跨步子矩阵视图。
另请参见skimage.util.shape.view_as_windows()
'''
s0,s1=arr.strips[:2]
m1,n1=arr.shape[:2]
m2,n2=亚_形
视图形状=(1+(m1-m2)//步幅[0],1+(n1-n2)//步幅[1],m2,n2)+arr.shape[2:]
步幅=(步幅[0]*s0,步幅[1]*s1,s0,s1)+arr.strips[2:]
subs=numpy.lib.strides\u技巧。当跨步时(arr,view\u shape,strides=strides)
返回接头
def POOLOGOVERLAP(mat、ksize、stride=None、method='max',pad=False):
''二维或三维数据的重叠池。
:ndarray,池的输入数组。
:2的元组,内核大小为(ky,kx)。
:2元组或无元组,池窗口的步长。
如果没有,则与(非重叠池)相同。
:str,'最大值用于最大池,
“平均”表示平均池。
:bool,pad或not。如果没有焊盘,则输出大小为
(n-f)//s+1,n表示大小,f表示内核大小,s步长。
如果是pad,则输出的大小为ceil(n/s)。
返回:合并矩阵。
'''
m、 n=材料形状[:2]
ky,kx=ksize
如果“跨步”为“无”:
步幅=(ky,kx)
sy,sx=步幅
_ceil=lambda x,y:int(numpy.ceil(x/float(y)))
如果pad:
ny=_ceil(m,sy)
nx=单元(n,sx)
尺寸=((ny-1)*sy+ky,(nx-1)*sx+kx)+材料形状[2:]
mat_pad=numpy.full(大小,numpy.nan)
垫块[:m,:n,…]=垫块
其他:
mat_pad=mat[:(m-ky)//sy*sy+ky,:(n-kx)//sx*sx+kx,…]
视图=关联(垫、大小、步幅)
如果方法=='max':
结果=numpy.nanmax(视图,轴=(2,3))
其他:
结果=numpy.nanmean(视图,轴=(2,3))
返回结果

由于numpy文档中说要使用“numpy.lib.stride”技巧和“极度小心”的“as_strided”,这里是另一个没有它的2D/3D池解决方案

如果步幅=1,则使用相同的填充。对于跨步>1,我不能100%确定如何定义相同的填充

def pool3D(arr,
           kernel=(2, 2, 2),
           stride=(1, 1, 1),
           func=np.nanmax,
           ):
    # check inputs
    assert arr.ndim == 3
    assert len(kernel) == 3

    # create array with lots of padding around it, from which we grab stuff (could be more efficient, yes)
    arr_padded_shape = arr.shape + 2 * np.array(kernel)
    arr_padded = np.zeros(arr_padded_shape, dtype=arr.dtype) * np.nan
    arr_padded[
    kernel[0]:kernel[0] + arr.shape[0],
    kernel[1]:kernel[1] + arr.shape[1],
    kernel[2]:kernel[2] + arr.shape[2],
    ] = arr

    # create temporary array, which aggregates kernel elements in last axis
    size_x = 1 + (arr.shape[0]-1) // stride[0]
    size_y = 1 + (arr.shape[1]-1) // stride[1]
    size_z = 1 + (arr.shape[2]-1) // stride[2]
    size_kernel = np.prod(kernel)
    arr_tmp = np.empty((size_x, size_y, size_z, size_kernel), dtype=arr.dtype)

    # fill temporary array
    kx_center = (kernel[0] - 1) // 2
    ky_center = (kernel[1] - 1) // 2
    kz_center = (kernel[2] - 1) // 2
    idx_kernel = 0
    for kx in range(kernel[0]):
        dx = kernel[0] + kx - kx_center
        for ky in range(kernel[1]):
            dy = kernel[1] + ky - ky_center
            for kz in range(kernel[2]):
                dz = kernel[2] + kz - kz_center
                arr_tmp[:, :, :, idx_kernel] = arr_padded[
                                               dx:dx + arr.shape[0]:stride[0],
                                               dy:dy + arr.shape[1]:stride[1],
                                               dz:dz + arr.shape[2]:stride[2],
                                               ]
                idx_kernel += 1

    # perform pool function
    arr_final = func(arr_tmp, axis=-1)
    return arr_final


def pool2D(arr,
           kernel=(2, 2),
           stride=(1, 1),
           func=np.nanmax,
           ):
    # check inputs
    assert arr.ndim == 2
    assert len(kernel) == 2

    # transform into 3D array with empty dimension?
    arr3D = arr[..., np.newaxis]
    kernel3D = kernel + (1,)
    stride3D = stride + (1,)
    arr3D_final = pool3D(arr3D, kernel3D, stride3D, func)
    arr2D_final = arr3D_final[:, :, 0]

    return arr2D_final

另一种解决方案使用了鲜为人知的魔法
np.maximum.at
(或者您可以将其调整为使用np.add.at和divising进行池化)

用法示例:

img = np.array([[20, 200, -5, 23],
                [-13, 134, 119, 100],
                [120, 32, 49, 25],
                [-120, 12, 9, 23]])

print(f'Input: \n{img}')

print(f"Output: \n{max_pool(img, factor=2)}")

印刷品

Input: 
[[  20  200   -5   23]
 [ -13  134  119  100]
 [ 120   32   49   25]
 [-120   12    9   23]]
Output: 
[[200 119]
 [120  49]]

不幸的是,它看起来有点慢,所以我仍然使用mdh提供的解决方案,N=mat。您还应该指出,即使内核没有划分源代码,但放弃了边界(并产生了一个副本),您的答案仍然有效。这比scikit的block_reduce快30倍
block_reduce
0.035秒内9093个函数调用
pooling
0.001秒内10个函数调用
@Tyathalae我可能在您的评论中缺少了一些分析上下文,但在我看来,如果在0.035秒内有9093个函数调用到Scikit的
block_reduce
(每~3.85μs调用1次)并且在0.001秒内仅对上述池函数调用10次(每~0.1ms调用1次),这难道不意味着Scikit的
block_reduce
实际上比上述实现快约26倍吗?此外,如果我正确阅读了这一点,样本大小的差异(即函数调用)也会增加非常大。您能澄清一下吗?谢谢!您好@Greenstick,您是对的,我的评论有点含糊不清。它显示了完成该操作所需的函数(子调用)数量。因此,scikit总共调用了
9093
sub
img = np.array([[20, 200, -5, 23],
                [-13, 134, 119, 100],
                [120, 32, 49, 25],
                [-120, 12, 9, 23]])

print(f'Input: \n{img}')

print(f"Output: \n{max_pool(img, factor=2)}")

Input: 
[[  20  200   -5   23]
 [ -13  134  119  100]
 [ 120   32   49   25]
 [-120   12    9   23]]
Output: 
[[200 119]
 [120  49]]