【问题标题】:How to select numpy tensordot axes如何选择 numpy 张量点轴
【发布时间】:2021-01-24 17:30:32
【问题描述】:

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

为此,我想使用点积,它在遍历数组的第一个维度时工作得非常好(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

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

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.ndarraynp.array 是一个静态构造函数。如果你想使用鸭子类型,请将x = np.asanyarray(x) 左右粘贴到函数的开头。
  • @JavaiMaster。链接的问题不是重复的。它准确地解释了为什么tensordot 绝对是不是这里的答案。
  • @JavaiMaster。话虽如此,我觉得我以前回答过类似的问题。既然我已经回答了,我会试着找到骗子:)
  • @AlixL 你到底在哪里得到错误?它看起来更像是输入中的形状不匹配而不是任何东西。

标签: python numpy dot-product


【解决方案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.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.tensordotdottensordot 保持两个数组的未触及维度,如前所述。 matmul 一起广播,这就是你想要的。

【讨论】:

  • 确实其他选项效果很好,我找到了使用 np.einsum 的方法。但是我仍然想知道如何使用 np.tensordot 来获得相同的结果。您介意解释一下 np.matmul 选项吗?我不确定 [..., None, :] 是什么意思。你在添加维度吗?如果是,为什么在 之后使用一列?
  • @AlixL。我已经充实了 #2 并添加了一些后记来解决您的问题。
猜你喜欢
  • 2019-11-12
  • 2016-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-05
  • 2020-09-05
相关资源
最近更新 更多