Chcę porównać różne obszary dwuwymiarowej tablicy $ A $ z podaną tablicą $ b $ o mniejszym rozmiarze. Ponieważ muszę to robić wiele razy, ważne jest, aby było to wykonywane bardzo szybko. Mam rozwiązanie, które działa dobrze, ale miałem nadzieję, że da się to zrobić ładniej i szybciej.Jaki jest najszybszy sposób porównywania poprawek z tablicy?
W szczegółach:
Powiedzmy mamy duży wachlarz i małą tablicę. Pętlę na wszystkie możliwe "łaty" w dużej tablicy, które mają ten sam rozmiar co mała tablica i porównuję te poprawki z podaną małą tablicą.
def get_best_fit(big_array, small_array):
# we assume the small array is square
patch_size = small_array.shape[0]
min_value = np.inf
for x in range(patch_size, big_array.shape[0] - patch_size):
for y in range(patch_size, big_array.shape[1] - patch_size):
p = get_patch_term(x, y, patch_size, big_array)
tmp = some_metric(p, small_array)
if min_value > tmp:
min_value = tmp
min_patch = p
return min_patch, min_value
W celu uzyskania poprawki Mam ten bezpośrednią realizację dostępu do tablicy:
def get_patch_term(x, y, patch_size, data):
"""
a patch has the size (patch_size)^^2
"""
patch = data[(x - (patch_size-1)/2): (x + (patch_size-1)/2 + 1),
(y - (patch_size-1)/2): (y + (patch_size-1)/2 + 1)]
return patch
myślę, że jest to najważniejsze zadanie i może być wykonywane szybciej, ale nie jestem pewien o tym.
Zajrzałem do Cythona, ale może zrobiłem to źle, nie jestem do końca obeznany z tym.
Moja pierwsza próba była bezpośrednim tłumaczeniem na Cython:
def get_patch_term_fast(Py_ssize_t x_i, Py_ssize_t y_i,
Py_ssize_t patch_size,
np.ndarray[DTYPE_t, ndim=2] big_array):
assert big_array.dtype == DTYPE
patch_size = (patch_size - 1)/2
cdef np.ndarray[DTYPE_t, ndim=2] patch = <np.ndarray[DTYPE_t, ndim=2]>big_array[(x_i - patch_size):(x_i + patch_size + 1), (y_i - patch_size): (y_i + patch_size + 1)]
return patch
I to wydaje się być szybszy (patrz poniżej), ale myślałem, że równoległe podejście powinno być lepiej, więc wymyśliłem tego
def get_patch_term_fast_parallel(Py_ssize_t x_i, Py_ssize_t y_i,
Py_ssize_t patch_size,
np.ndarray[DTYPE_t, ndim=2] big_array):
assert big_array.dtype == DTYPE
patch_size = (patch_size - 1)/2
assert big_array.dtype == DTYPE
cdef Py_ssize_t x
cdef Py_ssize_t y
cdef np.ndarray[DTYPE_t, ndim=1] patch = np.empty(np.power((2 * patch_size) + 1, 2))
with nogil, parallel():
for x in prange(x_i - patch_size, x_i + patch_size + 1):
for y in prange(y_i - patch_size, y_i + patch_size + 1):
patch[((x - (x_i - patch_size)) * (2 * patch_size + 1)) + (y - (y_i - patch_size))] = big_array[x, y]
#cdef np.ndarray[DTYPE_t, ndim=2] patch = <np.ndarray[DTYPE_t, ndim=2]>big_array[(x_i - patch_size):(x_i + patch_size + 1), (y_i - patch_size): (y_i + patch_size + 1)]
return patch
Co niestety nie jest szybsze. Do testów użyłem:
A = np.array(range(1200), dtype=np.float).reshape(30, 40)
b = np.array([41, 42, 81, 84]).reshape(2, 2)
x = 7
y = 7
print(timeit.timeit(lambda:get_patch_term_fast(x, y, 3, A), number=300))
print(timeit.timeit(lambda:get_patch_term_fast_parallel(x, y, 3, A).reshape(3,3), number=300))
print(timeit.timeit(lambda:get_patch_term(x, y, 3, A), number=300))
Który daje
0.0008792859734967351
0.0029909340664744377
0.0029337930027395487
Tak, moje pierwsze pytanie brzmi, czy to możliwe, aby zrobić to szybciej? Drugie pytanie brzmi: dlaczego równoległe podejście nie jest szybsze niż oryginalna implementacja numpy?
Edit:
próbowałem dalej parallelize funkcję get_best_fit ale niestety mam dużo błędów, stwierdzające, że nie można przypisać obiekt Pythona bez gil.
Oto kod:
def get_best_fit_fast(np.ndarray[DTYPE_t, ndim=2] big_array,
np.ndarray[DTYPE_t, ndim=2] small_array):
# we assume the small array is square
cdef Py_ssize_t patch_size = small_array.shape[0]
cdef Py_ssize_t x
cdef Py_ssize_t y
cdef Py_ssize_t x_range = big_array.shape[0] - patch_size
cdef Py_ssize_t y_range = big_array.shape[1] - patch_size
cdef np.ndarray[DTYPE_t, ndim=2] p
cdef np.ndarray[DTYPE_t, ndim=2] weights = np.empty((x_range - patch_size)*(y_range - patch_size)).reshape((x_range - patch_size), (y_range - patch_size))
with nogil, parallel():
for x in prange(patch_size, x_range):
for y in prange(patch_size, y_range):
p = get_patch_term_fast(x, y, patch_size, big_array)
weights[x - patch_size, y - patch_size] = np.linalg.norm(np.abs(p - small_array))
return np.min(weights)
PS: I pominął część zwracając najmniejszej łatę ...
W zależności od korelacji krzyżowej 'some_metric' (lub czegoś podobnego, w zależności od metryki) przy użyciu szybkich transformacji Fouriera, może być szybsza. – DavidW
W każdym razie, przyjmowanie prostych plasterków w numpy jest naprawdę bardzo wydajne (nie wymaga nawet kopiowania czegokolwiek), więc prawdopodobnie nie pobijesz go w Cython. Możesz mieć więcej szczęścia stosując Cython do pętli w 'get_best_fit'. – DavidW
Niestety, tłumaczenie jeden-do-jednego funkcji get_best_fit w Cython nie daje żadnej przewagi prędkości. I nie sprawiam, żeby działała równolegle. Choć teoretycznie powinno to działać, przypisanie obiektów w pętli równoległej sprawia mi kłopoty. – mijc