Идеальная numpy реализация для этой функции - PullRequest
2 голосов
/ 11 февраля 2020

Это кажется более прямым вопросом. Я немного обобщу его в конце.

Я пытаюсь использовать эту функцию в numpy. Я успешно использовал вложенные циклы for, но я не могу придумать numpy способ сделать это.

function to implement

Мой способ реализации:

bs = 10 # batch_size
nb = 8 # number of bounding boxes
nc = 15 # number of classes

bbox = np.random.random(size=(bs, nb, 4)) # model output bounding boxes

p = np.random.random(size=(bs, nb, nc)) # model output probability
p = softmax(p, axis=-1)

s_rand = np.random.random(size=(nc, nc))
s = (s_rand + s_rand.T)/2 # similarity matrix

pp = np.random.random(size=(bs, nb, nc)) # proposed probability
pp = softmax(pp, axis=-1)

first_term = 0
for b in range(nb):
    for b_1 in range(nb):
        if b_1 == b:
            continue
        for l in range(nc):
            for l_1 in range(nc):
                first_term += (s[l, l_1] * (pp[:, b, l] - pp[:, b_1, l_1])**2)
second_term = 0
for b in range(nb):
    for l in range(nc):
        second_term += (np.linalg.norm(s[l, :], ord=1) * (pp[:, b, l] - p[:, b, l])**2)
second_term *= nb

epsilon = 0.5
output = ((1 - epsilon) * first_term) + (epsilon * second_term)

Я очень старался удалить петли и вместо этого использовать np.tile и np.repeat, чтобы выполнить задачу. Но не могу придумать возможного пути.

Я пытался найти в Google поиск упражнений, подобных тем, которые могут помочь мне выучить такие преобразования в numpy, но не увенчался успехом.

Ответы [ 3 ]

1 голос
/ 11 февраля 2020

P_hat.shape - это (B, L), S.shape - это (L, L), P.shape - это (B, L).

array_before_sum = S[None,:,None,:]*(P_hat[:,:,None,None]- P_hat[None,None,:,:])**2
array_after_sum = array_before_sum.sum(axis=(1,3))
array_sum_again = (array_after_sum*(1-np.ones((B,B)))).sum()
first_term = (1-epsilon)*array_sum_again

second_term = epsilon*(B*np.abs(S).sum(axis=1)[None,:]*(P_hat - P)**2).sum()

0 голосов
/ 11 февраля 2020

Максимально оптимизированный код: (удаление первых двух циклов основано на ответе L.Iridium)

squared_diff = (pp[:, :, None, :, None] - pp[:, None, :, None, :]) ** 2
weighted_diff = s * squared_diff
b_eq_b_1_removed = b.sum(axis=(3,4)) * (1 - np.eye(nb))
first_term = b_eq_b_1_removed.sum(axis=(1,2))

normalized_s = np.linalg.norm(s, ord=1, axis=1)
squared_diff = (pp - p)**2
second_term = nb * (normalized_s * squared_diff).sum(axis=(1,2))

loss = ((1 - epsilon) * first_term) + (epsilon * second_term)

Отслеживание времени: 512 µs ± 13 µs per loop

Отслеживание времени кода, размещенного в вопросе: 62.5 ms ± 197 µs per loop

Это огромное улучшение.

0 голосов
/ 11 февраля 2020

Я думаю, что вы можете сделать оба с einsum

first_term = np.einsum('km, ijklm -> i', s, (pp[..., None, None] - pp[:, None, None, ...])**2 )
second_term = np.einsum('k, ijk -> i', np.linalg.norm(s, axis = 1), (pp - p)**2 )

Теперь есть проблема: тензор ijklm в first_term станет огромным, если nb и nc получат большой. Вам, вероятно, следует распределить его так, чтобы вы получили 3 меньших тензора:

first_term =    np.einsum('km, ijk, ijk -> i', s, pp, pp)  +\
                np.einsum('km, ilm, ilm -> i', s, pp, pp)  -\
            2 * np.einsum('km, ijk, ilm -> i', s, pp, pp)

. Это использует тот факт, что (a-b)**2 = a**2 + b**2 - 2ab позволяет разбить задачу на три части, каждая из которых может быть выполнена за один шаг. с точечным произведением

...