寻找使用 numpy 根据出现次数对 3d 数组进行下采样的最快方法
Looking for fastest method to downsample a 3d array based on occurences using numpy
给定类型为 'uint8' 的大型 3d numpy 数组(不会太大而无法放入内存),我想在每个维度中使用给定的比例因子缩小此数组。您可以假设数组的形状可以被比例因子整除。
数组的值在 [0, 1, ... max] 中,其中 max 总是小于 6。我想按比例缩小它,这样每个形状为 "scale_factor" 的 3d 块将return 在此块中出现次数最多的数字。当等于 return 第一个(我不在乎)。
我试过以下有效的方法
import numpy as np
array = np.random.randint(0, 4, ((128, 128, 128)), dtype='uint8')
scale_factor = (4, 4, 4)
bincount = 3
# Reshape to free dimension of size scale_factor to apply scaledown method to
m, n, r = np.array(array.shape) // scale_factor
array = array.reshape((m, scale_factor[0], n, scale_factor[1], r, scale_factor[2]))
# Making histogram, first over last axis, then sum over other two
array = np.apply_along_axis(lambda x: np.bincount(x, minlength=bincount),
axis=5, arr=array)
array = np.apply_along_axis(lambda x: np.sum(x), axis=3, arr=array)
array = np.apply_along_axis(lambda x: np.sum(x), axis=1, arr=array).astype('uint8')
array = np.argmax(array , axis=3)
这有效,但是 bincount 非常慢。也让 np.histogram 工作,但也很慢。我确实认为我尝试的这两种方法都不完全是为我的目的而设计的,它们提供了更多的功能来减慢方法的速度。
我的问题是,有谁知道更快的方法吗?如果有人能指出深度学习库中的一种方法可以做到这一点,我也会很高兴,但这不是正式的问题。
好吧,这是一个类似的方法,但速度更快。它仅根据您的用例将 bincount 函数替换为更简单的实现:lambda x: max(set(x), key=lambda y: list(x).count(y))
首先对数组进行整形,以便可以直接在一维上使用该方法。
在我的 128x128x128 笔记本电脑上,速度快了大约 4 倍:
import time
import numpy as np
array = np.random.randint(0, 4, ((128, 128, 128)), dtype='uint8')
scale_factor = (4, 4, 4)
bincount = 4
start_time = time.time()
N = 10
for i in range(N):
# Reshape to free dimension of size scale_factor to apply scaledown method to
m, n, r = np.array(array.shape) // scale_factor
arr = array.reshape((m, scale_factor[0], n, scale_factor[1], r, scale_factor[2]))
arr = np.swapaxes(arr, 1, 2).swapaxes(2, 4)
arr = arr.reshape((m, n, r, np.prod(scale_factor)))
# Obtain the element that occurred the most
arr = np.apply_along_axis(lambda x: max(set(x), key=lambda y: list(x).count(y)),
axis=3, arr=arr)
print((time.time() - start_time) / N)
与例如 np.mean()
等内置方法仍有很大差距
@F.Wessels 正在朝着正确的方向思考,但答案还不完全存在。如果自己动手而不是沿轴应用,速度可以提高一百倍以上。
首先,当您将 3D 数组 space 分成块时,您的尺寸从 128x128x128 变为 32x4x32x4x32x4。试着直观地理解这一点:你实际上有 32x32x32 个大小为 4x4x4 的块。不是将块保持为 4x4x4,而是将它们压缩为 64 大小,从那里可以找到最常见的项目。这就是诀窍:如果您的块不是排列为 32x32x32x64 而是排列为 32768x64 也没有关系。基本上,我们已经回到了二维维度,在那里一切都变得更容易了。
现在使用大小为 32768x64 的二维数组,您可以使用列表理解和 numpy ops 自己进行 bin 计数;它会快10倍。
import time
import numpy as np
array = np.random.randint(0, 4, ((128, 128, 128)), dtype='uint8')
scale_factor = (4, 4, 4)
bincount = 4
def prev_func(array):
# Reshape to free dimension of size scale_factor to apply scaledown method to
m, n, r = np.array(array.shape) // scale_factor
arr = array.reshape((m, scale_factor[0], n, scale_factor[1], r, scale_factor[2]))
arr = np.swapaxes(arr, 1, 2).swapaxes(2, 4)
arr = arr.reshape((m, n, r, np.prod(scale_factor)))
# Obtain the element that occurred the most
arr = np.apply_along_axis(lambda x: max(set(x), key=lambda y: list(x).count(y)),
axis=3, arr=arr)
return arr
def new_func(array):
# Reshape to free dimension of size scale_factor to apply scaledown method to
m, n, r = np.array(array.shape) // scale_factor
arr = array.reshape((m, scale_factor[0], n, scale_factor[1], r, scale_factor[2]))
arr = np.swapaxes(arr, 1, 2).swapaxes(2, 4)
arr = arr.reshape((m, n, r, np.prod(scale_factor)))
# Collapse dimensions
arr = arr.reshape(-1,np.prod(scale_factor))
# Get blockwise frequencies -> Get most frequent items
arr = np.array([(arr==b).sum(axis=1) for b in range(bincount)]).argmax(axis=0)
arr = arr.reshape((m,n,r))
return arr
N = 10
start1 = time.time()
for i in range(N):
out1 = prev_func(array)
end1 = time.time()
print('Prev:',(end1-start1)/N)
start2 = time.time()
for i in range(N):
out2 = new_func(array)
end2 = time.time()
print('New:',(end2-start2)/N)
print('Difference:',(out1-out2).sum())
输出:
Prev: 1.4244404077529906
New: 0.01667332649230957
Difference: 0
如您所见,即使我调整了尺寸,结果也没有差异。当我转到 2D 时,Numpy 的 reshape 函数保持了值的顺序,因为保留了最后一个维度 64。当我重塑回 m、n、r 时,这个顺序被重新建立。你给出的原始方法在我的机器上花了大约 5 秒 运行,所以根据经验,速度提高了五百倍。
给定类型为 'uint8' 的大型 3d numpy 数组(不会太大而无法放入内存),我想在每个维度中使用给定的比例因子缩小此数组。您可以假设数组的形状可以被比例因子整除。
数组的值在 [0, 1, ... max] 中,其中 max 总是小于 6。我想按比例缩小它,这样每个形状为 "scale_factor" 的 3d 块将return 在此块中出现次数最多的数字。当等于 return 第一个(我不在乎)。
我试过以下有效的方法
import numpy as np
array = np.random.randint(0, 4, ((128, 128, 128)), dtype='uint8')
scale_factor = (4, 4, 4)
bincount = 3
# Reshape to free dimension of size scale_factor to apply scaledown method to
m, n, r = np.array(array.shape) // scale_factor
array = array.reshape((m, scale_factor[0], n, scale_factor[1], r, scale_factor[2]))
# Making histogram, first over last axis, then sum over other two
array = np.apply_along_axis(lambda x: np.bincount(x, minlength=bincount),
axis=5, arr=array)
array = np.apply_along_axis(lambda x: np.sum(x), axis=3, arr=array)
array = np.apply_along_axis(lambda x: np.sum(x), axis=1, arr=array).astype('uint8')
array = np.argmax(array , axis=3)
这有效,但是 bincount 非常慢。也让 np.histogram 工作,但也很慢。我确实认为我尝试的这两种方法都不完全是为我的目的而设计的,它们提供了更多的功能来减慢方法的速度。
我的问题是,有谁知道更快的方法吗?如果有人能指出深度学习库中的一种方法可以做到这一点,我也会很高兴,但这不是正式的问题。
好吧,这是一个类似的方法,但速度更快。它仅根据您的用例将 bincount 函数替换为更简单的实现:lambda x: max(set(x), key=lambda y: list(x).count(y))
首先对数组进行整形,以便可以直接在一维上使用该方法。
在我的 128x128x128 笔记本电脑上,速度快了大约 4 倍:
import time
import numpy as np
array = np.random.randint(0, 4, ((128, 128, 128)), dtype='uint8')
scale_factor = (4, 4, 4)
bincount = 4
start_time = time.time()
N = 10
for i in range(N):
# Reshape to free dimension of size scale_factor to apply scaledown method to
m, n, r = np.array(array.shape) // scale_factor
arr = array.reshape((m, scale_factor[0], n, scale_factor[1], r, scale_factor[2]))
arr = np.swapaxes(arr, 1, 2).swapaxes(2, 4)
arr = arr.reshape((m, n, r, np.prod(scale_factor)))
# Obtain the element that occurred the most
arr = np.apply_along_axis(lambda x: max(set(x), key=lambda y: list(x).count(y)),
axis=3, arr=arr)
print((time.time() - start_time) / N)
与例如 np.mean()
等内置方法仍有很大差距@F.Wessels 正在朝着正确的方向思考,但答案还不完全存在。如果自己动手而不是沿轴应用,速度可以提高一百倍以上。
首先,当您将 3D 数组 space 分成块时,您的尺寸从 128x128x128 变为 32x4x32x4x32x4。试着直观地理解这一点:你实际上有 32x32x32 个大小为 4x4x4 的块。不是将块保持为 4x4x4,而是将它们压缩为 64 大小,从那里可以找到最常见的项目。这就是诀窍:如果您的块不是排列为 32x32x32x64 而是排列为 32768x64 也没有关系。基本上,我们已经回到了二维维度,在那里一切都变得更容易了。
现在使用大小为 32768x64 的二维数组,您可以使用列表理解和 numpy ops 自己进行 bin 计数;它会快10倍。
import time
import numpy as np
array = np.random.randint(0, 4, ((128, 128, 128)), dtype='uint8')
scale_factor = (4, 4, 4)
bincount = 4
def prev_func(array):
# Reshape to free dimension of size scale_factor to apply scaledown method to
m, n, r = np.array(array.shape) // scale_factor
arr = array.reshape((m, scale_factor[0], n, scale_factor[1], r, scale_factor[2]))
arr = np.swapaxes(arr, 1, 2).swapaxes(2, 4)
arr = arr.reshape((m, n, r, np.prod(scale_factor)))
# Obtain the element that occurred the most
arr = np.apply_along_axis(lambda x: max(set(x), key=lambda y: list(x).count(y)),
axis=3, arr=arr)
return arr
def new_func(array):
# Reshape to free dimension of size scale_factor to apply scaledown method to
m, n, r = np.array(array.shape) // scale_factor
arr = array.reshape((m, scale_factor[0], n, scale_factor[1], r, scale_factor[2]))
arr = np.swapaxes(arr, 1, 2).swapaxes(2, 4)
arr = arr.reshape((m, n, r, np.prod(scale_factor)))
# Collapse dimensions
arr = arr.reshape(-1,np.prod(scale_factor))
# Get blockwise frequencies -> Get most frequent items
arr = np.array([(arr==b).sum(axis=1) for b in range(bincount)]).argmax(axis=0)
arr = arr.reshape((m,n,r))
return arr
N = 10
start1 = time.time()
for i in range(N):
out1 = prev_func(array)
end1 = time.time()
print('Prev:',(end1-start1)/N)
start2 = time.time()
for i in range(N):
out2 = new_func(array)
end2 = time.time()
print('New:',(end2-start2)/N)
print('Difference:',(out1-out2).sum())
输出:
Prev: 1.4244404077529906
New: 0.01667332649230957
Difference: 0
如您所见,即使我调整了尺寸,结果也没有差异。当我转到 2D 时,Numpy 的 reshape 函数保持了值的顺序,因为保留了最后一个维度 64。当我重塑回 m、n、r 时,这个顺序被重新建立。你给出的原始方法在我的机器上花了大约 5 秒 运行,所以根据经验,速度提高了五百倍。