Подтвердить что ты не робот

Пикторизация вектора вложенных циклов

Я был бы признателен за помощь в поиске и понимании питонического способа оптимизации следующих манипуляций с массивами в вложенных циклах:

def _func(a, b, radius):
    "Return 0 if a>b, otherwise return 1"
    if distance.euclidean(a, b) < radius:
        return 1
    else:
        return 0

def _make_mask(volume, roi, radius):
    mask = numpy.zeros(volume.shape)
    for x in range(volume.shape[0]):
        for y in range(volume.shape[1]):
            for z in range(volume.shape[2]):
                mask[x, y, z] = _func((x, y, z), roi, radius)
    return mask

Где volume.shape (182, 218, 200) и roi.shape (3) - оба типа ndarray; и radius является int

4b9b3361

Ответ 1

Подход № 1

Здесь векторный подход -

m,n,r = volume.shape
x,y,z = np.mgrid[0:m,0:n,0:r]
X = x - roi[0]
Y = y - roi[1]
Z = z - roi[2]
mask = X**2 + Y**2 + Z**2 < radius**2

Возможное улучшение: возможно, мы сможем ускорить последний шаг с помощью модуля numexpr -

import numexpr as ne

mask = ne.evaluate('X**2 + Y**2 + Z**2 < radius**2')

Подход # 2

Мы также можем постепенно строить три диапазона, соответствующие параметрам формы, и выполнять вычитание по трем элементам roi "на лету", фактически не создавая сетки, как было сделано ранее с помощью np.mgrid. Для достижения этой цели было бы полезно использовать broadcasting. Реализация будет выглядеть так:

m,n,r = volume.shape
vals = ((np.arange(m)-roi[0])**2)[:,None,None] + \
       ((np.arange(n)-roi[1])**2)[:,None] + ((np.arange(r)-roi[2])**2)
mask = vals < radius**2

Упрощенная версия: благодаря @Bi Rico для того, чтобы предложить здесь улучшение, поскольку мы можем использовать np.ogrid для выполнения этих операций немного более кратким образом, например:

m,n,r = volume.shape    
x,y,z = np.ogrid[0:m,0:n,0:r]-roi
mask = (x**2+y**2+z**2) < radius**2

Тест времени выполнения

Определения функций -

def vectorized_app1(volume, roi, radius):
    m,n,r = volume.shape
    x,y,z = np.mgrid[0:m,0:n,0:r]
    X = x - roi[0]
    Y = y - roi[1]
    Z = z - roi[2]
    return X**2 + Y**2 + Z**2 < radius**2

def vectorized_app1_improved(volume, roi, radius):
    m,n,r = volume.shape
    x,y,z = np.mgrid[0:m,0:n,0:r]
    X = x - roi[0]
    Y = y - roi[1]
    Z = z - roi[2]
    return ne.evaluate('X**2 + Y**2 + Z**2 < radius**2')

def vectorized_app2(volume, roi, radius):
    m,n,r = volume.shape
    vals = ((np.arange(m)-roi[0])**2)[:,None,None] + \
           ((np.arange(n)-roi[1])**2)[:,None] + ((np.arange(r)-roi[2])**2)
    return vals < radius**2

def vectorized_app2_simplified(volume, roi, radius):
    m,n,r = volume.shape    
    x,y,z = np.ogrid[0:m,0:n,0:r]-roi
    return (x**2+y**2+z**2) < radius**2

Сроки -

In [106]: # Setup input arrays  
     ...: volume = np.random.rand(90,110,100) # Half of original input sizes 
     ...: roi = np.random.rand(3)
     ...: radius = 3.4
     ...: 

In [107]: %timeit _make_mask(volume, roi, radius)
1 loops, best of 3: 41.4 s per loop

In [108]: %timeit vectorized_app1(volume, roi, radius)
10 loops, best of 3: 62.3 ms per loop

In [109]: %timeit vectorized_app1_improved(volume, roi, radius)
10 loops, best of 3: 47 ms per loop

In [110]: %timeit vectorized_app2(volume, roi, radius)
100 loops, best of 3: 4.26 ms per loop

In [139]: %timeit vectorized_app2_simplified(volume, roi, radius)
100 loops, best of 3: 4.36 ms per loop

Итак, как всегда broadcasting демонстрирует свою магию для сумасшедшего почти 10,000x ускорения по сравнению с исходным кодом и более 10x лучше, чем создание мешей используя на лету транслируемые операции!

Ответ 2

Предположим, вы сначала создали массив xyzy:

import itertools

xyz = [np.array(p) for p in itertools.product(range(volume.shape[0]), range(volume.shape[1]), range(volume.shape[2]))]

Теперь, используя numpy.linalg.norm,

np.linalg.norm(xyz - roi, axis=1) < radius

проверяет, меньше ли расстояние для каждого кортежа от roi.

Наконец, просто reshape результат к требуемым размерам.