【发布时间】:2018-11-16 03:15:36
【问题描述】:
关于重复消息的注意事项:
类似的主题,不完全是重复的。特别是。因为循环仍然是最快的方法。谢谢。
目标:
快速将数组从 [small,small] 升级到 [big,big] 一个因子,不要使用图像库。非常简单的缩放,一个小的值会变成几个大的值,经过归一化后变成几个大的值。换句话说,这是天文措辞中的“通量守恒”——从小阵列展开到大阵列的 4 个值(因子 2)的 16 值将是 4 4,因此保留了值的数量。
问题:
我有一些工作代码可以进行放大,但与缩小相比,它们的工作速度并不快。放大实际上比缩小更容易(在这种基本情况下,这需要很多总和) - 放大只需要将已知数据放入预分配数组的大块中。
对于一个工作示例,[16,24;8,16] 的 [2,2] 数组:
16、24
8、16
对 [4,4] 数组乘以 2 的值:
4、4、6、6
4、4、6、6
2、2、4、4
2、2、4、4
最快的实现是由 numba 的 jit & prange 加速的 for 循环。我想更好地利用 Numpy 的预编译函数来完成这项工作。我还将娱乐 Scipy 的东西 - 但不是它的大小调整功能。
对于强大的矩阵操作函数来说,这似乎是一个完美的问题,但我只是没有设法让它很快发生。
此外,单行 numpy 调用是 方式 时髦的,所以不要感到惊讶。但这就是让它正确对齐所需要的。
代码示例:
在下面检查更优化的调用请注意,我这里的情况是一个 20480x20480 float64 数组,它可能会占用相当多的内存 - 但如果方法太占用内存(可以是矩阵)。
环境:Python 3、Windows、i5-4960K @ 4.5 GHz。在所示示例中,循环代码的运行时间约为 18.9 秒,numpy 代码的运行时间约为 52.5 秒。
% MAIN:运行这些
import timeit
timeitSetup = '''
from Regridder1 import Regridder1
import numpy as np
factor = 10;
inArrayX = np.float64(np.arange(0,2048,1));
inArrayY = np.float64(np.arange(0,2048,1));
[inArray, _] = np.meshgrid(inArrayX,inArrayY);
''';
print("Time to run 1: {}".format( timeit.timeit(setup=timeitSetup,stmt="Regridder1(inArray, factor,)", number = 10) ));
timeitSetup = '''
from Regridder2 import Regridder2
import numpy as np
factor = 10;
inArrayX = np.float64(np.arange(0,2048,1));
inArrayY = np.float64(np.arange(0,2048,1));
[inArray, _] = np.meshgrid(inArrayX,inArrayY);
''';
print("Time to run 2: {}".format( timeit.timeit(setup=timeitSetup,stmt="Regridder2(inArray, factor,)", number = 10) ));
% FUN: Regridder 1 - for 循环
import numpy as np
from numba import prange, jit
@jit(nogil=True)
def Regridder1(inArray,factor):
inSize = np.shape(inArray);
outSize = [np.int64(np.round(inSize[0] * factor)), np.int64(np.round(inSize[1] * factor))];
outBlockSize = factor*factor; #the block size where 1 inArray pixel is spread across # outArray pixels
outArray = np.zeros(outSize); #preallcoate
outBlocks = inArray/outBlockSize; #precalc the resized blocks to go faster
for i in prange(0,inSize[0]):
for j in prange(0,inSize[1]):
outArray[i*factor:(i*factor+factor),j*factor:(j*factor+factor)] = outBlocks[i,j]; #puts normalized value in a bunch of places
return outArray;
% FUN: Regridder 2 - numpy
import numpy as np
def Regridder2(inArray,factor):
inSize = np.shape(inArray);
outSize = [np.int64(np.round(inSize[0] * factor)), np.int64(np.round(inSize[1] * factor))];
outBlockSize = factor*factor; #the block size where 1 inArray pixel is spread across # outArray pixels
outArray = inArray.repeat(factor).reshape(inSize[0],factor*inSize[1]).T.repeat(factor).reshape(inSize[0]*factor,inSize[1]*factor).T/outBlockSize;
return outArray;
非常感谢您对加快这一进程的洞察力。希望代码是好的,在文本框中制定。
当前最佳解决方案:
在我的比较中,numba 的 jit for 循环实现 (Regridder1) 仅将 jit 应用于需要它的地方,它可以在 18.0 秒运行 timeit 测试,而仅 numpy 的实现 (Regridder2) 在 18.5 秒运行 timeit 测试。好处是在第一次调用时,仅 numpy 的实现不需要等待 jit 编译代码。 Jit 的 cache=True 让它不会在后续运行中编译。其他电话(nogil、nopython、prange)似乎没有帮助,但似乎也没有伤害。也许在未来的 numba 更新中,他们会做得更好或其他什么。
为了简单和便携,Regridder2 是最佳选择。它几乎一样快,并且不需要安装 numba(我的 Anaconda 安装需要我去安装它) - 所以它有助于便携性。
% FUN: Regridder 1 - for 循环
import numpy as np
def Regridder1(inArray,factor):
inSize = np.shape(inArray);
outSize = [np.int64(np.round(inSize[0] * factor)), np.int64(np.round(inSize[1] * factor))];
outBlockSize = factor*factor #the block size where 1 inArray pixel is spread across # outArray pixels
outArray = np.empty(outSize) #preallcoate
outBlocks = inArray/outBlockSize #precalc the resized blocks to go faster
factor = np.int64(factor) #convert to an integer to be safe (in case it's a 1.0 float)
outArray = RegridderUpscale(inSize, factor, outArray, outBlocks) #call a function that has just the loop
return outArray;
#END def Regridder1
from numba import jit, prange
@jit(nogil=True, nopython=True, cache=True) #nopython=True, nogil=True, parallel=True, cache=True
def RegridderUpscale(inSize, factor, outArray, outBlocks ):
for i in prange(0,inSize[0]):
for j in prange(0,inSize[1]):
outArray[i*factor:(i*factor+factor),j*factor:(j*factor+factor)] = outBlocks[i,j];
#END for j
#END for i
#scales the original data up, note for other languages you need i*factor+factor-1 because slicing
return outArray; #return success
#END def RegridderUpscale
% FUN: Regridder 2 - numpy 基于@ZisIsNotZis 的回答
import numpy as np
def Regridder2(inArray,factor):
inSize = np.shape(inArray);
#outSize = [np.int64(np.round(inSize[0] * factor)), np.int64(np.round(inSize[1] * factor))]; #whoops
outBlockSize = factor*factor; #the block size where 1 inArray pixel is spread across # outArray pixels
outArray = np.broadcast_to( inArray[:,None,:,None]/outBlockSize, (inSize[0], factor, inSize[1], factor)).reshape(np.int64(factor*inSize[0]), np.int64(factor*inSize[1])); #single line call that gets the job done
return outArray;
#END def Regridder2
【问题讨论】:
-
[4,4]数组示例是所需的输出吗?还是……我很困惑? -
它可能是一个输出(我把它作为一个可视化的例子来说明所需的缩放类型),但是代码中的 2048 -> 20480 更好地显示了现实世界的速度限制。
-
在 Regridder2 中调用
repeat之前先进行除法会快一点(就像您在 Regridder1 中所做的那样)。即outArray = (inArray/outBlockSize).repeat(...)... -
无需在 Regridder2 中计算
outSize。 -
outArray = ((inArray/outBlockSize).repeat(outBlockSize).reshape(inSize[0],inSize[1],factor,factor).swapaxes(1,2).reshape(inSize[0]*factor,inSize[1]*factor))在 Regridder2 中计算outArray的速度略快,但远不及 Regridder1。
标签: python arrays numpy scaling