使用 Numpy,计算大小为 10 的向量与大小为 (5, 10) 的矩阵中的每一行的内积的最佳方法是什么?
问题相关的 Numpy
这里有几种方法,使用一些虚拟数据:
In [1]: import numpy as np
In [2]: a = np.random.randint(0, 10, (10,))
In [3]: b = np.random.randint(0, 10, (5, 10))
In [4]: a
Out[4]: array([4, 1, 0, 6, 3, 3, 6, 6, 1, 8])
In [5]: b
Out[5]:
array([[9, 0, 6, 1, 1, 1, 4, 7, 4, 7],
[5, 8, 8, 3, 4, 8, 7, 3, 0, 4],
[2, 2, 5, 3, 9, 6, 1, 5, 8, 3],
[2, 0, 4, 3, 5, 3, 3, 4, 3, 3],
[3, 3, 6, 4, 7, 5, 8, 6, 7, 3]])
由于您要求的维度,为了计算内积(也称为标量积和点积),我们需要转置矩阵b以计算出维度。对于长度为 10 的向量,numpy 赋予它 shape (10,)。所以它似乎有 10 行,没有列,但它有点模棱两可。Numpy 基本上会做它必须做的事情以使维度工作。我们可以通过使用将其强制转换为(10, 1)向量a.reshape((10, 1)),但这不是必需的。矩阵有一个定义的第二维,所以我们有一个 shape (5, 10)。为了将这两个形状相乘,我们需要在中间匹配相同的尺寸。这意味着制作(10,) * (10, 5). 对矩阵执行转置会反转维度给我们(10, 5)。那些内心10然后 s 将消失并给我们留下一个(1, 5)向量。
话虽如此,我们可以使用以下任何方法来获得等效的答案:
标准标准点积:
In [7]: a.dot(b.T) Out[7]: array([174, 174, 141, 119, 190])方便的 numpy 表示法:
In [6]: a @ b.T Out[6]: array([174, 174, 141, 119, 190])高效的“爱因斯坦符号”,Ricci 微积分的一个子集(我让感兴趣的读者在线搜索以获取更多信息):
In [8]: np.einsum('i,ij->j', a, b.T) Out[8]: array([174, 174, 141, 119, 190])就像shadowstalker的评论一样:
In [9]: np.array([np.dot(a, r) for r in b]) Out[9]: array([174, 174, 141, 119, 190])
如果您的矩阵的维度(100, 100)或更小,那么该@方法可能是最快和最优雅的。然而,一旦你开始进入让你怀疑你的笔记本电脑是否会处理它的矩阵(例如使用 shape (10000, 10000)) - 那么是时候阅读关于爱因斯坦符号和numpy 中的惊人模块的文档和这个博客了!einsum
好的,让我们进行一个 numpy 测试!
使用 Numpy,计算大小为 10 的向量与大小为 (5, 10) 的矩阵中的每一行的内积的最佳方法是什么?
- np.dot(vector1, matrix1)
- np.array([np.dot(row, vector1) for row in matrix1])
- np.matmul(vector1, matrix1)
- np.dot(矩阵 1,向量 1)
- np.sum(matrix1 * vector1, axis=1)
回答
简而言之,
最佳答案是 4,这是因为它是计算成本最低的方法,只需一步即可完成。
但是,如果您想知道原因,请继续阅读……
要回答这个问题,让我们看看哪些有效,哪些无效。记住问题要求“计算内积的最佳方法”,所以应该有不止一种方法有效,对吧?
现在让我们用 numpy 实际创建一个向量和一个矩阵
np.random.seed(2)
vector1 = np.random.randint(10, size=(1,10))[0]
print('Vector 1\n', vector1)
matrix1 = np.random.randint(10, size=(5,10))
print('\nMatrix 1\n', matrix1)
我们将得到这个输出
Vector 1
[8 8 6 2 8 7 2 1 5 4]
Matrix 1
[[4 5 7 3 6 4 3 7 6 1]
[3 5 8 4 6 3 9 2 0 4]
[2 4 1 7 8 2 9 8 7 1]
[6 8 5 9 9 9 3 0 0 2]
[8 8 2 9 6 5 6 6 6 3]]
惊人的!
现在让我们玩一下每个可能的答案,看看哪些适合,哪些不适合!
但在我们这样做之前,让我们看看矩阵乘法是如何工作的。
要将两个矩阵与 [ROWS x COLUMNS] 相乘,第一个矩阵的列的大小必须与第二个矩阵的行大小匹配,因此我们需要如下所示的场景。
[A,B] * [B,N]
生成的矩阵将具有两个矩阵的外部维度的维度因此从上面的示例中,我们可以相乘,因为两个矩阵的内部维度匹配,我们将获得一个具有两个外部维度的新矩阵矩阵。
在这种情况下,我们得到的矩阵将是 [A,N] 维。
1. np.dot(vector1, matrix1)
OUTPUT: (EXCEPTION)
shapes (10,) and (5,10) not aligned: 10 (dim 0) != 5 (dim 0)
我们这里有一个 **exception **,所以 (1) 不可能是正确的。我们不能有点积,因为 [1,10] * [5,10] 不符合矩阵乘法的内部要求。
2. np.array([np.dot(row, vector1) for row in matrix1])
OUTPUT:
[243 225 211 309 301]
在这里它似乎工作。
对于每一行,我们的矩阵都有一个“for”循环。所以 5 次,我们将矩阵的每个元素乘以向量的等效元素,然后将它们相加。
每次运行我们都会得到一个数字,所以在 5 次之后,我们将用我们的产品创建一个大小为 [1,5] 的向量。
3. np.matmul(vector1, matrix1)
OUTPUT:
matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 5 is different from 10)
我们这里有一个例外,所以(3)不可能是正确的。
在这里,我们尝试在 [1,10]*[5,10] 处进行矩阵乘法。内部尺寸不匹配,我们遇到与 (1) 相同的问题。
4. np.dot(matrix1, vector1)
OUTPUT:
[243 225 211 309 301]
在这里它似乎工作。
我们乘以 [5,10]*[10,1] 矩阵!生成的矩阵是 [5,1] 矩阵,并给出与答案 (2) 的情况相同的结果。
然而,这个答案比答案 (2) 更好,因为它在计算上更便宜,因为在答案 2 中我们创建了一个 for 循环,而这里我们通过一次代数计算得到结果。
5. np.sum(matrix1 * vector1, axis=1)
OUTPUT:
[243 225 211 309 301]
在这里它似乎工作。
我们再次得到与 (2) 和 (4) 相同的结果。
但是我们再次在这里执行多个步骤。
首先通过做 我们最终得到了这个矩阵
[[32 40 42 6 48 28 6 7 30 4]
[24 40 48 8 48 21 18 2 0 16]
[16 32 6 14 64 14 18 8 35 4]
[48 64 30 18 72 63 6 0 0 8]
[64 64 12 18 48 35 12 6 30 12]]
然后通过在轴 1(y 轴)求和,我们将每个行号添加到单个数字(很像 for 循环的解决方案 )。
那么最好的解决方案是什么?
您还记得,问题不只是询问哪些选项会产生一些结果,而是最好使用哪个选项。到目前为止的答案,, 和似乎工作。
最佳答案是 4,这是因为它是计算成本最低的方法,只需一步即可完成。
为自己运行!
import numpy as np
print("With Numpy, what’s the best way to compute the inner product of a vector of size 10 with each row in a matrix of size (5, 10)?")
np.random.seed(2)
print('\nSamples:')
vector1 = np.random.randint(10, size=(1,10))[0]
print('\nVector 1\n', vector1)
matrix1 = np.random.randint(10, size=(5,10))
print('\nMatrix 1\n', matrix1)
print('\n\nPossible Answers:')
#========================
print('\nA) np.dot(vector1, matrix1)', end='')
try: print(' - WORKING \n', np.dot(vector1, matrix1))
except Exception as e: print(' - EXCEPTION \n', e)
#========================
print('\nB) np.array([np.dot(row, vector1) for row in matrix1])', end='')
try:
print(' - WORKING \n', np.array([np.dot(row, vector1) for row in matrix1]))
except Exception as e:
print(' - EXCEPTION \n', e)
#========================
print('\nC) np.matmul(vector1, matrix1)', end='')
try:
print(' - WORKING \n', np.matmul(vector1, matrix1))
except Exception as e:
print(' - EXCEPTION \n', e)
#========================
print('\nD) np.dot(matrix1, vector1)', end='')
try:
print(' - WORKING \n', np.dot(matrix1, vector1))
except Exception as e:
print(' - EXCEPTION \n', e)
#========================
print('\nE) np.sum(matrix1 * vector1, axis=1)', end='')
try:
print(' - WORKING \n', np.sum(matrix1 * vector1, axis=1))
except Exception as e:
print(' - EXCEPTION \n', e)
print('\n=======================================')
print("As the question asks, we shouldn't just look for a working result, but for the best result, and it seems to be (D) as it gets the best result with a single math step, thus computationally more efficient ")
据我所知,最快的方法是:
np.dot(matrix, vector)
这是一个专门为此编写和优化的函数。不建议一个一个相乘再自己加,显然会花更多的时间。