numpy的区别。数组形状(R, 1)和(R,)

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

所以我的问题是:

  1. 形状(R, 1)(R,)之间的区别是什么。我知道字面上它是一个数字的列表和列表的列表所有的列表都只包含一个数字。只是想知道为什么不设计numpy,这样它更喜欢形状(R, 1),而不是(R,),以便于矩阵乘法。

  2. 对于上面的例子有没有更好的方法?不需要像这样显式地重塑:numpy.dot(M[:,0].reshape(R, 1), numpy.ones((1, R)))

207345 次浏览

(R,)(1,R)之间的区别实际上是你需要使用的索引的数量。ones((1,R))是一个恰好只有一行的2-D数组。ones(R)是一个向量。一般来说,如果变量有多行/多列是没有意义的,你应该使用一个向量,而不是一个单维矩阵。

对于您的特定情况,有几个选项:

1)将第二个参数设为向量。以下工作很好:

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

2)如果你想要像matlab一样的矩阵运算,使用类matrix而不是ndarray。所有矩阵都被强制转换为2-D数组,操作符*执行矩阵乘法而不是元素乘法(因此不需要dot)。根据我的经验,这比它值得的麻烦多了,但如果你习惯了matlab,它可能会很好。

1)不喜欢(R, 1)形状而不是(R,)形状的原因是,它不必要地使事情复杂化。此外,为什么将形状(R, 1)默认为长度- r向量而不是(1, R)更可取?当您需要额外的维度时,最好保持简单和明确。

2)对于你的例子,你正在计算一个外部乘积,所以你可以通过使用np.outer在不调用reshape的情况下做到这一点:

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

对于其基数组类,2d数组并不比1d或3d数组更特殊。有些操作保留维度,有些操作减少维度,有些操作合并甚至扩展维度。

M=np.arange(9).reshape(3,3)
M[:,0].shape # (3,) selects one column, returns a 1d array
M[0,:].shape # same, one row, 1d array
M[:,[0]].shape # (3,1), index with a list (or array), returns 2d
M[:,[0,1]].shape # (3,2)


In [20]: np.dot(M[:,0].reshape(3,1),np.ones((1,3)))


Out[20]:
array([[ 0.,  0.,  0.],
[ 3.,  3.,  3.],
[ 6.,  6.,  6.]])


In [21]: np.dot(M[:,[0]],np.ones((1,3)))
Out[21]:
array([[ 0.,  0.,  0.],
[ 3.,  3.,  3.],
[ 6.,  6.,  6.]])

给出相同数组的其他表达式

np.dot(M[:,0][:,np.newaxis],np.ones((1,3)))
np.dot(np.atleast_2d(M[:,0]).T,np.ones((1,3)))
np.einsum('i,j',M[:,0],np.ones((3)))
M1=M[:,0]; R=np.ones((3)); np.dot(M1[:,None], R[None,:])

MATLAB从2D数组开始。新版本允许更多维度,但保留了2的下界。但是你仍然需要注意行矩阵和列矩阵之间的区别,即形状为(1,3) v (3,1)的行矩阵。你多久写一次[1,2,3].'?我打算写row vectorcolumn vector,但是有了2d的约束,MATLAB中没有任何向量——至少在数学意义上的向量是1d的。

你看过np.atleast_2d(也是_1d和_3d版本)吗?

在较新的Python/numpy中,有matmul操作符

In [358]: M[:,0,np.newaxis]@np.ones((1,3))
Out[358]:
array([[0., 0., 0.],
[3., 3., 3.],
[6., 6., 6.]])

numpy中,元素乘法在某种意义上比矩阵乘法更基本。对于大小为1的维度上的乘积和,不需要使用dot/matmul:

In [360]: M[:,0,np.newaxis]*np.ones((1,3))
Out[360]:
array([[0., 0., 0.],
[3., 3., 3.],
[6., 6., 6.]])

它使用了broadcasting,这是numpy一直拥有的强大功能。MATLAB最近才添加了它。

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。如果将两个下标标记为ij,则数组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)

形状是一个元组。如果只有一个维度,形状将是一个数字,逗号后是空白。对于2+维度,所有的逗号后面会有一个数字。

# 1 dimension with 2 elements, shape = (2,).
# Note there's nothing after the comma.
z=np.array([  # start dimension
10,       # not a dimension
20        # not a dimension
])            # end dimension
print(z.shape)

(2)

# 2 dimensions, each with 1 element, shape = (2,1)
w=np.array([  # start outer dimension
[10],     # element is in an inner dimension
[20]      # element is in an inner dimension
])            # end outer dimension
print(w.shape)

(2, 1)

这里已经有很多好的答案了。但对我来说,很难找到一些例子,形状或数组可以破坏所有的程序。

下面是一个例子:

import numpy as np
a = np.array([1,2,3,4])
b = np.array([10,20,30,40])




from sklearn.linear_model import LinearRegression
regr = LinearRegression()
regr.fit(a,b)

这将失败并产生错误:

ValueError:期望的2D数组,得到的是1D数组

但如果我们将reshape添加到a:

a = np.array([1,2,3,4]).reshape(-1,1)

这是正确的!

shape (n,)的数据结构称为秩1数组。它的行为不像行向量或列向量那样一致,这使得它的一些操作和效果不直观。如果你对这个(n,)数据结构求转置,它看起来是一样的点积会给你一个数字而不是一个矩阵。 形状为(n,1)或(1,n)的行向量或列向量更直观和一致

需要明确的是,我们谈论的是:

  • NumPy数组,也称为numpy.ndarray
  • numpy.ndarray.shape所知的数组的形状
  • 这个问题假设了一些形状为(R,)的未知numpy.ndarray,其中R应该被理解为其各自维度的长度

NumPy数组有一个形状。.shape由一个元组表示,其中元组中的每个元素都告诉我们该维度的长度。为了保持简单,让我们坚持行和列。虽然在下面的例子中numpy.ndarray的值不会改变,但形状会改变。

让我们考虑一个值为1、2、3和4的数组。

我们的例子将包括以下.shape表示:

(4,)  # 1-dimensional array with length 4
(1,4) # 2-dimensional array with row length 1, column length 4
(4,1) # 2-dimensional array with row length 4, column length 1

我们可以用变量ab来更抽象地思考这个问题。

(a,)  # 1-dimensional array with length a
(b,a) # 2-dimensional array with row length b, column length a
(a,b) # 2-dimensional array with row length a, column length b

对我来说,“手动”构建它们有助于更好地理解它们的维度含义。

>> # (4,)
>> one_dimensional_vector = np.array(
[1, 2, 3, 4]
)


>> # (1,4)
>> row_vector = np.array(
[
[1, 2, 3, 4]
]
)


>> # (4,1)
>> column_vector = np.array(
[
[1],
[2],
[3],
[4]
]
)

第一个问题的答案是

  1. 形状(R, 1)和形状(R,)的区别是什么?

他们有不同的尺寸。a是一个维度的长度,b是另一个维度的长度,.shape分别是(a, b)(a,)b恰好是1。一种思考方法是,如果a = 1,那么行长度为1,因此它是一个行向量。如果b = 1,则列的长度为1,因此它所表示的numpy.ndarray是一个列向量。

  1. 对于上面的例子有没有更好的方法?

回答:让我们假设我们有上面示例中使用的数组,其中1、2、3和4作为值。让(R,)(R, 1)的一个方便方法是:

>> one_dimensional_array = np.array([1,2,3,4])
>> one_dimensional_array.shape
(4,)
>> row_vector = one_dimensional_array[:, None]
>> row_vector.shape
(4, 1)

资源

  1. NumPy - ndarrays - https://numpy.org/doc/stable/reference/arrays.ndarray.html
  2. 交叉验证@unutbu -维技巧- https://stats.stackexchange.com/a/285005