小能豆

numpy.array 形状 (R, 1) 和 (R,) 之间的区别

javascript

在 中numpy,一些操作返回形状为(R, 1),但一些操作返回(R,)。这将使矩阵乘法更加繁琐,因为reshape需要显式地 。例如,给定一个矩阵M,如果我们想做numpy.dot(M[:,0], numpy.ones((1, R)))其中R是行数(当然,同样的问题也发生在列上)。我们会得到matrices are not aligned错误,因为M[:,0]是形状(R,),但numpy.ones((1, R))是形状(1, R)

我的问题是:

  1. (R, 1)形状和之间有什么区别(R,)。我知道字面上它是数字列表和列表列表,其中所有列表都只包含一个数字。只是想知道为什么不设计成numpy有利于形状(R, 1)而不是(R,)更容易进行矩阵乘法。
  2. 上面的例子有没有更好的方法?没有像这样明确地重塑:numpy.dot(M[:,0].reshape(R, 1), numpy.ones((1, R)))

阅读 48

收藏
2024-07-02

共1个答案

小能豆

1. NumPy 中形状的含义

你写道,“我知道它实际上是数字列表和列表的列表,其中所有列表都只包含一个数字”,但这是一种无益的思考方式。

思考 NumPy 数组的最佳方式是它们由两部分组成,一个是数据缓冲区(只是一块原始元素),另一个是描述如何解释数据缓冲区的视图。

例如,如果我们创建一个包含 12 个整数的数组:

>>> a = numpy.arange(12)
>>> a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

然后a由一个数据缓冲区组成,其排列方式如下:

┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

以及描述如何解释数据的观点:

>>> a.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : True
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False
>>> a.dtype
dtype('int64')
>>> a.itemsize
8
>>> a.strides
(8,)
>>> a.shape
(12,)

这里的形状 (12,)表示数组由从 0 到 11 的单个索引进行索引。从概念上讲,如果我们标记这个单个索引i,则数组a如下所示:

i= 0    1    2    3    4    5    6    7    8    9   10   11
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

如果我们重塑数组,这不会改变数据缓冲区。相反,它会创建一个新的视图,描述解释数据的不同方式。因此之后:

>>> b = a.reshape((3, 4))

该数组b具有与 相同的数据缓冲区,但现在它由两个a索引进行索引,这两个索引分别从 0 到 2 和 0 到 3。如果我们将这两个索引标记为和,则数组如下所示:i``j``b

i= 0    0    0    0    1    1    1    1    2    2    2    2
j= 0    1    2    3    0    1    2    3    0    1    2    3
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

意思就是:

>>> b[2,1]
9

您可以看到第二个索引变化很快,而第一个索引变化很慢。如果您希望反过来,可以指定参数order

>>> c = a.reshape((3, 4), order='F')

其结果是一个如下索引数组:

i= 0    1    2    0    1    2    0    1    2    0    1    2
j= 0    0    0    1    1    1    2    2    2    3    3    3
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

意思就是:

>>> c[2,1]
5

现在应该清楚数组具有一个或多个尺寸为 1 的形状意味着什么。之后:

>>> d = a.reshape((12, 1))

该数组d由两个索引索引,其中第一个索引从 0 到 11,第二个索引始终为 0:

i= 0    1    2    3    4    5    6    7    8    9   10   11
j= 0    0    0    0    0    0    0    0    0    0    0    0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

所以:

>>> d[10,0]
10

长度为 1 的维度是“免费的”(在某种意义上),因此没有什么可以阻止你去城镇:

>>> e = a.reshape((1, 2, 1, 6, 1))

给出一个像这样索引的数组:

i= 0    0    0    0    0    0    0    0    0    0    0    0
j= 0    0    0    0    0    0    1    1    1    1    1    1
k= 0    0    0    0    0    0    0    0    0    0    0    0
l= 0    1    2    3    4    5    0    1    2    3    4    5
m= 0    0    0    0    0    0    0    0    0    0    0    0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

所以:

>>> e[0,1,0,0,0]
6

有关如何实现数组的更多详细信息,请参阅NumPy 内部文档。

2. 该怎么办?

由于numpy.reshape只是创建了一个新视图,因此您不必担心在必要时使用它。当您想要以不同的方式索引数组时,它是正确的工具。

然而,在长时间计算中,通常可以首先安排构建具有“正确”形状的数组,从而最大限度地减少重塑和转置的次数。但如果不了解导致需要重塑的实际背景,就很难说应该改变什么。

你问题中的例子是:

numpy.dot(M[:,0], numpy.ones((1, R)))

但这不现实。首先,这个表达式:

M[:,0].sum()

计算结果更简单。其次,第 0 列真的有什么特别之处吗?也许你真正需要的是:

M.sum(axis=0)
2024-07-02