就地映射NumPy数组
有没有办法直接在 NumPy 数组上进行映射?如果有,怎么做?
给定一个 a_values
- 2D 数组 - 目前我用的这段代码可以实现这个功能:
for row in range(len(a_values)):
for col in range(len(a_values[0])):
a_values[row][col] = dim(a_values[row][col])
但是这段代码看起来太丑了,我怀疑 NumPy 里面应该有一个函数可以做到类似的事情,像这样:
a_values.map_in_place(dim)
不过如果有像上面那样的东西,我一直找不到。
5 个回答
为什么不使用numpy的实现,还有那个out_技巧呢?
from numpy import array, arange, vectorize, rint, multiply, round as np_round
def fmilo(array_):
np_round(multiply(array_ ,0.67328, array_), out=array_)
得到的结果是:
===== ...AND THE WINNER IS... =========================
mac (as in question) : 80.8470ms [130422%]
mac (optimised) : 80.2400ms [129443%]
mac (slice-assignment) : 75.5181ms [121825%]
senderle : 78.9380ms [127342%]
eryksun : 11.0800ms [17874%]
slice-assignment w/ ufunc : 0.0899ms [145%]
fmilo : 0.0620ms [100%]
=======================================================
这段文字是我在接受了问题的答案后,整理出来的关于不同回答和评论的贡献。大家给这个回答点赞是非常欢迎的,但如果你点赞了这个回答,请别忘了也给senderle和(如果他/她写了的话)eryksun的回答点赞,他们提出了下面的方法。
问:有没有办法在原地修改一个numpy数组?
答:可以,但不能用单一的数组方法。你需要自己写代码。
下面是一个脚本,用来比较这个讨论中提到的各种实现方式:
import timeit
from numpy import array, arange, vectorize, rint
# SETUP
get_array = lambda side : arange(side**2).reshape(side, side) * 30
dim = lambda x : int(round(x * 0.67328))
# TIMER
def best(fname, reps, side):
global a
a = get_array(side)
t = timeit.Timer('%s(a)' % fname,
setup='from __main__ import %s, a' % fname)
return min(t.repeat(reps, 3)) #low num as in place --> converge to 1
# FUNCTIONS
def mac(array_):
for row in range(len(array_)):
for col in range(len(array_[0])):
array_[row][col] = dim(array_[row][col])
def mac_two(array_):
li = range(len(array_[0]))
for row in range(len(array_)):
for col in li:
array_[row][col] = int(round(array_[row][col] * 0.67328))
def mac_three(array_):
for i, row in enumerate(array_):
array_[i][:] = [int(round(v * 0.67328)) for v in row]
def senderle(array_):
array_ = array_.reshape(-1)
for i, v in enumerate(array_):
array_[i] = dim(v)
def eryksun(array_):
array_[:] = vectorize(dim)(array_)
def ufunc_ed(array_):
multiplied = array_ * 0.67328
array_[:] = rint(multiplied)
# MAIN
r = []
for fname in ('mac', 'mac_two', 'mac_three', 'senderle', 'eryksun', 'ufunc_ed'):
print('\nTesting `%s`...' % fname)
r.append(best(fname, reps=50, side=50))
# The following is for visually checking the functions returns same results
tmp = get_array(3)
eval('%s(tmp)' % fname)
print tmp
tmp = min(r)/100
print('\n===== ...AND THE WINNER IS... =========================')
print(' mac (as in question) : %.4fms [%.0f%%]') % (r[0]*1000,r[0]/tmp)
print(' mac (optimised) : %.4fms [%.0f%%]') % (r[1]*1000,r[1]/tmp)
print(' mac (slice-assignment) : %.4fms [%.0f%%]') % (r[2]*1000,r[2]/tmp)
print(' senderle : %.4fms [%.0f%%]') % (r[3]*1000,r[3]/tmp)
print(' eryksun : %.4fms [%.0f%%]') % (r[4]*1000,r[4]/tmp)
print(' slice-assignment w/ ufunc : %.4fms [%.0f%%]') % (r[5]*1000,r[5]/tmp)
print('=======================================================\n')
上面脚本的输出结果 - 至少在我的系统上 - 是:
mac (as in question) : 88.7411ms [74591%]
mac (optimised) : 86.4639ms [72677%]
mac (slice-assignment) : 79.8671ms [67132%]
senderle : 85.4590ms [71832%]
eryksun : 13.8662ms [11655%]
slice-assignment w/ ufunc : 0.1190ms [100%]
可以看到,使用numpy的ufunc
可以提高速度,提升幅度超过2倍,甚至接近3倍,相比于第二好的和最差的替代方案。
如果不能使用ufunc
,这里有其他替代方案的比较:
mac (as in question) : 91.5761ms [672%]
mac (optimised) : 88.9449ms [653%]
mac (slice-assignment) : 80.1032ms [588%]
senderle : 86.3919ms [634%]
eryksun : 13.6259ms [100%]
希望这对你有帮助!
如果你在空间上有很大的限制,才值得尝试在原地进行这个操作。这样做的话,可以通过遍历数组的扁平视图来稍微加快你的代码。因为 reshape
在可能的情况下会返回一个新的视图,数据本身不会被复制(除非原始数据有些特殊的结构)。
我不知道还有什么更好的方法可以真正实现任意 Python 函数的原地应用。
>>> def flat_for(a, f):
... a = a.reshape(-1)
... for i, v in enumerate(a):
... a[i] = f(v)
...
>>> a = numpy.arange(25).reshape(5, 5)
>>> flat_for(a, lambda x: x + 5)
>>> a
array([[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19],
[20, 21, 22, 23, 24],
[25, 26, 27, 28, 29]])
一些时间测试:
>>> a = numpy.arange(2500).reshape(50, 50)
>>> f = lambda x: x + 5
>>> %timeit flat_for(a, f)
1000 loops, best of 3: 1.86 ms per loop
这个方法大约是嵌套循环版本的两倍快:
>>> a = numpy.arange(2500).reshape(50, 50)
>>> def nested_for(a, f):
... for i in range(len(a)):
... for j in range(len(a[0])):
... a[i][j] = f(a[i][j])
...
>>> %timeit nested_for(a, f)
100 loops, best of 3: 3.79 ms per loop
当然,使用向量化的方法仍然更快,所以如果你可以复制数据,就用这个方法:
>>> a = numpy.arange(2500).reshape(50, 50)
>>> g = numpy.vectorize(lambda x: x + 5)
>>> %timeit g(a)
1000 loops, best of 3: 584 us per loop
如果你能用内置的 ufuncs 重写 dim
,那就请不要使用 vectorize
:
>>> a = numpy.arange(2500).reshape(50, 50)
>>> %timeit a + 5
100000 loops, best of 3: 4.66 us per loop
numpy
进行像 +=
这样的操作时是原地进行的,正如你所期待的那样——所以你可以在不增加额外开销的情况下,享受到 ufunc 的速度。有时候甚至会更快!可以在 这里查看一个例子。
顺便说一下,我对这个问题的最初回答(可以在编辑历史中查看)是非常荒谬的,涉及到对 a
的索引进行向量化。不仅需要做一些奇怪的操作来绕过 vectorize
的 类型检测机制,结果发现它和嵌套循环版本一样慢。真是聪明反被聪明误!