Эффективная для памяти абсолютная разница массивов uint8 - PullRequest
3 голосов
/ 10 июля 2019

У меня есть два больших np.uint8 ndarrays, a и b . И мне нужно вычислить: c = np.sum(np.abs(a - b), axis=(-2,-1,))

Поскольку они не подписаны, я не могу просто вычесть их. Наивным способом решения этой проблемы было бы привести их к большему типу данных:

c = np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)), axis=(-2,-1,))

Что в общей сложности использует 4 * раз памяти массива. В идеальном мире я хотел бы быть чем-то вроде этого:

c = np.sum(np.abssub(a, b), axis=(-2,-1,))

Который будет использовать тот же объем памяти, что и массивы. К сожалению, я не смог найти такую ​​функцию в документации numpy. Сейчас я делаю следующее:

diff = np.empty_like(a)

mask = a > b
diff[mask] = (a - b)[mask]
# b shape is different but broadcasts to a
# That is why I use mask after substracting

mask = np.logical_not(mask, out=mask)
diff[mask] = (b - a)[mask]

c = np.sum(np.abs(diff, out=diff), axis=(-2,-1,))

Который использует всего 2,5 **, умноженный на объем памяти в качестве массивов.

Есть ли лучший способ сделать это?


*  4   times = bytes(a) + bytes(b) + bytes(a.astype(np.int16)) + bytes(b.astype(np.int16)) + bytes(a.astype(np.int16) - b.astype(np.int16))
               --------- 1 --------   ----------- 2 ----------  ----------- 3 -----------   --------------------- 4 ---------------------

** 2.5 times = bytes(a) + bytes(b) + bytes(diff) + bytes(mask) + bytes(a - b | b - a)
              --------- 1 --------   ------------ 2 ----------   ------- 2.5 -------

Ответы [ 2 ]

5 голосов
/ 10 июля 2019

numexpr модуль обеспечивает очень простую и при этом эффективную для памяти среду , которую можно использовать здесь. Это автоматически заботится о переполнении при выполнении арифметических операций. Давайте рассмотрим пример и посмотрим, как решить нашу проблему -

In [63]: a = np.array([3,252,89],dtype=np.uint8)
    ...: b = np.array([10,255,19],dtype=np.uint8)

In [64]: import numexpr as ne

In [65]: ne.evaluate('abs(a-b)')
Out[65]: array([ 7.,  3., 70.])

Следовательно, чтобы получить желаемый результат -

In [66]: int(ne.evaluate('sum(abs(a-b))'))
Out[66]: 80

Сравнение с обновленной версией NumPy -

In [67]: np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)))
Out[67]: 80

Эффективность памяти

Теперь давайте расширимся до очень большого массива и выясним суть проблемы, которая заключается в эффективности памяти. Мы будем использовать memory_profiler модуль, чтобы проверить то же самое.

Сценарий Python с версиями NumPy и numexpr, перечисленными как numpy_numexpr_memeff.py -

import numpy as np
import numexpr as ne
from memory_profiler import profile

np.random.seed(0)
a = np.random.randint(0,256,(1000000))
b = np.random.randint(0,256,(1000000))

@profile(precision=10)
def numpy1():    
    return np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)))

@profile(precision=10)
def numexpr():
    return int(ne.evaluate('sum(abs(a-b))'))

if __name__ == '__main__':
    numpy1()

if __name__ == '__main__':
    numexpr()  

Результаты запуска сценария из командной строки -

$ python -m memory_profiler numpy_numexpr_memeff.py 
Filename: numpy_numexpr_memeff.py

Line #    Mem usage    Increment   Line Contents
================================================
     9  63.0468750000 MiB   0.0000000000 MiB   @profile(precision=10)
    10                             def numpy1():    
    11  65.3437500000 MiB   2.2968750000 MiB       return np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)))


Filename: numpy_numexpr_memeff.py

Line #    Mem usage    Increment   Line Contents
================================================
    13  65.3437500000 MiB   0.0000000000 MiB   @profile(precision=10)
    14                             def numexpr():
    15  65.5859375000 MiB   0.2421875000 MiB       return int(ne.evaluate('sum(abs(a-b))'))

Итак, кажется, numexpr версия занимает 1/10 памяти по сравнению с NumPy.

Производительность

Сроки -

In [68]: np.random.seed(0)
    ...: a = np.random.randint(0,256,(1000000))
    ...: b = np.random.randint(0,256,(1000000))

In [71]: %timeit np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)))
3.99 ms ± 88.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [72]: %timeit int(ne.evaluate('sum(abs(a-b))'))
4.71 ms ± 112 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Итак, с точки зрения производительности, версия numexpr близка, но не так хороша, как версия NumPy.


Другой может быть использовать тот факт, что если мы введем один увеличенный, другой будет автоматически увеличен при выполнении арифметических операций. Итак, мы могли бы просто сделать -

np.sum(np.abs(a.astype(np.int16) - b))

Python-скрипт для проверки эффективности памяти для этого, как numpys_memeff.py -

import numpy as np
from memory_profiler import profile

np.random.seed(0)
a = np.random.randint(0,256,(1000000))
b = np.random.randint(0,256,(1000000))

@profile(precision=10)
def numpy1():    
    return np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)))

@profile(precision=10)
def numpy2():    
    return np.sum(np.abs(a.astype(np.int16) - b))

if __name__ == '__main__':
    numpy1()

if __name__ == '__main__':
    numpy2()  

Результаты -

$ python -m memory_profiler numpys_memeff.py 
Filename: numpys_memeff.py

Line #    Mem usage    Increment   Line Contents
================================================
     8  56.6015625000 MiB   0.0000000000 MiB   @profile(precision=10)
     9                             def numpy1():    
    10  59.1210937500 MiB   2.5195312500 MiB       return np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)))


Filename: numpys_memeff.py

Line #    Mem usage    Increment   Line Contents
================================================
    12  59.1210937500 MiB   0.0000000000 MiB   @profile(precision=10)
    13                             def numpy2():    
    14  59.3632812500 MiB   0.2421875000 MiB       return np.sum(np.abs(a.astype(np.int16) - b))

По производительности кажется тоже немного лучше -

In [68]: np.random.seed(0)
    ...: a = np.random.randint(0,256,(1000000))
    ...: b = np.random.randint(0,256,(1000000))

In [71]: %timeit np.sum(np.abs(a.astype(np.int16) - b.astype(np.int16)))
3.99 ms ± 88.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [73]: %timeit np.sum(np.abs(a.astype(np.int16) - b))
3.84 ms ± 29.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
0 голосов
/ 10 июля 2019

Вы можете сохранить себе несколько промежуточных массивов с помощью

# sizeof(a)
diff = a - b
# sizeof(a)
mask = b > a
np.negative(diff, where=mask, out=diff)

c = np.sum(diff, axis=(-2,-1,))

или записать другим способом:

def abssub(a, b):
    diff = a - b
    mask = b > a
    return np.negative(diff, where=mask, out=diff)

c = np.sum(abssub(a, b), axis=(-2,-1,))
...