哈达玛在阵列列表上的效率

时间:2018-12-30 01:45:24

标签: python arrays numpy

numpy是在1D数组的垂直堆栈上的Hadmard产品,其循环速度比遍历1D数组列表并在每个数组上执行Hadamard(基于元素的)产品的速度要快得多(这很有意义,无论如何我都对其进行了测试) 。

在这种情况下,我需要在一组numpy数组与另一组numpy数组之间执行Hadamard乘积:

stacked_arrays = np.vstack([1D-arrays...])
stacked_arrays *= np.power(factor, np.arange(1, num_arrays))

但是,我需要执行此操作来更改列表中的每个组件1D数组,并且此操作需要进行很多操作。我知道这听起来像是一个奇怪的功能,但是有没有办法做到这一点而无需循环:

factors = factor ** np.arange(1, num_arrays)
for array, f in zip([1D..arrays], factors):
     array *= f

还是没有取消操作结果?

也无法使用map,因为map会这样创建numpy数组的副本:

result = map(lambda x, y: x * y, zip([1D..arrays], factors))

由于您无法使用*=lambda,因此会返回一个numpy数组列表,而原始数组将保持不变。

是否有一种方法可以使np.vstack仍然以某种方式引用旧的组件数组,或者可以通过另一种方法来实现stacked的数组之间的Hadamard乘积速度,同时可以对未堆叠的数组进行突变?由于不需要进行堆叠(np.split),因此可以节省一些时间。

TimeIt结果:

m = []
for _ in range(100):
    m.append(np.array([1, 2, 4, 5], dtype=np.float64))
factors = np.expand_dims(np.power(2, np.arange(100, dtype=np.float64)), axis=1)

def split_and_unstack():
    l = np.vstack(m)
    l *= factors
    result = np.split(l, 100)

def split_only():
    l = np.vstack(m)
    l *= factors

print(timeit.timeit(split_and_unstack, number=10000))
# 1.8569015570101328

print(timeit.timeit(split_only, number=10000))
# 0.9328480050317012

# makes sense that with unstacking it is about double the time

说明:     上面提到的[1D数组]列表是较大的1D数组列表的子列表。     此较大的列表是collections.deque。而这deque需要     在提取子列表之前进行洗牌(即,这是用于随机梯度下降的体验重播缓冲区)。

缓冲区popappend的速度:

times = int(1e4)
tgt = np.array([1, 2, 3, 4])

queue = collections.deque([tgt] * times, maxlen=times)
reg_list = [tgt] * times
numpy_list = np.array([tgt] * times)

def pop():
    queue.pop()
def pop_list():
    reg_list.pop()
def pop_np():
    global numpy_list
    numpy_list = numpy_list[1:]

print(timeit.timeit(pop, number=times))
# 0.0008135469979606569
print(timeit.timeit(pop_list, number=times))
# 0.000994370027910918
print(timeit.timeit(pop_np, number=times))
# 0.0016436030273325741

def push():
    queue.append(tgt)
def push_list():
    reg_list.append(tgt)
numpy_list = np.array([tgt] * 1)
def push_np():
    numpy_list[0] = tgt

print(timeit.timeit(push, number=times))
# 0.0008797429618425667
print(timeit.timeit(push_list, number=times))
# 0.00097957398975268
print(timeit.timeit(push_np, number=times))
# 0.003331452957354486

1 个答案:

答案 0 :(得分:1)

让我们分解问题。您希望有一个对所有可变数组的引用列表,但希望能够对它们作为一个块执行操作。

我认为您的做法是落后的。与其尝试将数组打包和解包到单独的缓冲区中,不如将视图维护在单个缓冲区中。

替换当前循环

m = [np.array([1, 2, 4, 5], dtype=np.float64) for _ in range(100)]

具有单个缓冲区,并查看每一行:

buf = np.vstack([np.array([1, 2, 4, 5], dtype=np.float64) for _ in range(100)])
m = list(buf)  # check that m[0].base is b

现在您有了一个数组m的列表,您可以分别修改每个数组。只要您将修改保留在原位并且不重新分配列表元素,所有更改将直接显示在buf中。同时,您可以在buf上执行批量计算,只要就地进行,m就会反映所有更改。

实际上,您甚至不需要m。注意list(buf)是如何在块的每一行中创建视图的。您可以轻松地直接索引到缓冲区中。例如,m[3][8]通常以buf的形式写为buf[3, 8],但是您也可以使用buf是序列的事实,并写成buf[3][8] 。这种方法效率较低,因为它每次都会创建一个新视图(buf[3]),但是创建的次数很少。

要提取随机的行子集,还可以使用序列索引。假设您的缓冲区保留了最后M行,您希望对其进行改组并从中提取N行的子序列。您可以通过创建索引数组并反复遍历索引来完成此操作:

 indices = np.arange(M)

 # inside the loop:

 np.random.shuffle(indices)
 chunk = buf[indices[:N]]
 # do your math on `chunk`

只要indices不变,并且您认为随机播放足够随机,就不需要重新分配M或对其进行重新排序。