如何 select numpy 张量点轴

How to select numpy tensordot axes

我有两个形状为 (436, 1024, 2) 的 numpy 数组。最后一个维度 (2) 表示二维向量。我想按元素比较两个 numpy 数组的二维向量,以便找到平均 angular 错误。

为此,我想使用点积,它在数组的前两个维度上循环时效果非常好(python 中的for 循环可能很慢)。因此我想使用一个 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

下面是我正确计算平均 angular 误差的函数:

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)

这个问题可能比你做的要简单得多。如果沿最后一个轴将 np.tensordot 应用于一对形状为 (w, h, 2) 的数组,您将得到形状为 (w, h, w, h) 的结果。这不是你想要的。这里有三种简单的方法。除了展示选项之外,我还展示了一些在不改变任何基本功能的情况下简化代码的提示和技巧:

  1. 手动执行 sum-reduction(使用 +*):

    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 (a.k.a. @) 适当广播的维度:

    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.matmulnp.dot 都要求第一个数组的最后一个维度与第二个数组的倒数第二个维度匹配,就像普通矩阵乘法一样。 Nonenp.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.dotnp.tensordot。如前所述,dottensordot 保持两个数组的维度不变。 matmul一起播,就是你想要的。