У меня есть два множества массива формы (436, 1024, 2). Последнее измерение (2) представляет двумерные векторы. Я хочу сравнить 2D-вектор двух массивов numpy поэлементно, чтобы найти среднюю угловую ошибку.

Для этого я хочу использовать точечный продукт, который отлично работает при цикле по двум первым измерениям массивов (цикл for в python может быть медленным). Поэтому я хотел бы использовать функцию numpy.

Я обнаружил, что np.tensordot позволяет выполнять поэлементное скалярное произведение. Однако мне не удается использовать его аргумент axes:

import numpy as np

def average_angular_error_vec(estimated_oc : np.array, target_oc : np.array):
    estimated_oc = np.float64(estimated_oc)
    target_oc = np.float64(target_oc)

    norm1 = np.linalg.norm(estimated_oc, axis=2)
    norm2 = np.linalg.norm(target_oc, axis=2)
    norm1 = norm1[..., np.newaxis]
    norm2 = norm2[..., np.newaxis]

    unit_vector1 = np.divide(estimated_oc, norm1)
    unit_vector2 = np.divide(target_oc, norm2)

    dot_product = np.tensordot(unit_vector1, unit_vector2, axes=2)
    angle = np.arccos(dot_product)

    return np.mean(angle)

У меня следующая ошибка:

ValueError: shape-mismatch for sum

Ниже приведена моя функция, которая правильно вычисляет среднюю угловую ошибку:

def average_angular_error(estimated_oc : np.array, target_oc : np.array):
    h, w, c = target_oc.shape
    r = np.zeros((h, w), dtype="float64")

    estimated_oc = np.float64(estimated_oc)
    target_oc = np.float64(target_oc)

    for i in range(h):
        for j in range(w):

            unit_vector_1 = estimated_oc[i][j] / np.linalg.norm(estimated_oc[i][j])
            unit_vector_2 = target_oc[i][j] / np.linalg.norm(target_oc[i][j])
            dot_product = np.dot(unit_vector_1, unit_vector_2)

            angle = np.arccos(dot_product)

            r[i][j] = angle
       
    return np.mean(r)
1
AlixL 9 Окт 2020 в 18:51

1 ответ

Лучший ответ

Проблема, вероятно, намного проще, чем вы ее решаете. Если вы примените np.tensordot к паре массивов формы (w, h, 2) вдоль последней оси, вы получите результат формы (w, h, w, h). Это не то, что вам нужно. Здесь есть три простых подхода. Помимо отображения параметров, я показал несколько советов и приемов, позволяющих упростить код без изменения каких-либо основных функций:

  1. Выполните уменьшение суммы вручную (используя + и *):

    def average_angular_error(estimated_oc : np.ndarray, target_oc : np.ndarray):
        # If you want to do in-place normalization, do x /= ... instead of x = x / ...
        estimated_oc = estimated_oc / np.linalg.norm(estimated_oc, axis=-1, keepdims=True)
        target_oc = target_oc / np.linalg.norm(target_oc, axis=-1, keepdims=True)
        # Use plain element-wise multiplication
        dots = np.sum(estimated_oc * target_oc, axis=-1)
        return np.arccos(dots).mean()
    
  2. Используйте np.matmul (он же @) с правильно транслируемыми размерами:

    def average_angular_error(estimated_oc : np.ndarray, target_oc : np.ndarray):
        estimated_oc = estimated_oc / np.linalg.norm(estimated_oc, axis=-1, keepdims=True)
        target_oc = target_oc / np.linalg.norm(target_oc, axis=-1, keepdims=True)
        # Matrix multiplication needs two dimensions to operate on
        dots = estimated_oc[..., None, :] @ target_oc[..., :, None]
        return np.arccos(dots).mean()
    

    np.matmul и np.dot оба требуют, чтобы последнее измерение первого массива соответствовало второму и последнему из второго, как при обычном умножении матриц. None - это псевдоним для np.newaxis, который вводит новую ось размера 1 в выбранном вами месте. В этом случае я сделал первый массив (w, h, 1, 2) и второй (w, h, 2, 1). Это гарантирует, что последние два измерения умножаются как транспонированный вектор и обычный вектор для каждого соответствующего элемента.

  3. Используйте np.einsum:

    def average_angular_error(estimated_oc : np.ndarray, target_oc : np.ndarray):
        estimated_oc = estimated_oc / np.linalg.norm(estimated_oc, axis=-1, keepdims=True)
        target_oc = target_oc / np.linalg.norm(target_oc, axis=-1, keepdims=True)
        # Matrix multiplication needs two dimensions to operate on
        dots = np.einsum('ijk,ijk->ik', estimated_oc, target_oc)
        return np.arccos(dots).mean()
    

Вы не можете использовать для этого np.dot или np.tensordot. dot и tensordot сохраняют нетронутые размеры обоих массивов, как объяснялось ранее. matmul транслирует их вместе, а вы этого и хотите.

1
Mad Physicist 9 Окт 2020 в 23:00