ChatGPT解决这个技术问题 Extra ChatGPT

numpy.array 形状 (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,),但 numpy.ones((1, R)) 的形状为 (1, R)

所以我的问题是:

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

This 可能会有所帮助。虽然不是找到一个实际的解决方案。
正确的解决方案: numpy.ravel( M[ : , 0] ) -- 将形状从 (R, 1) 转换为 (R,)
一个元组不是由括号决定的,它们不是它的一部分,而是由逗号决定的。 x=4, 分配一个元组,x=(4) 分配一个 int,造成通常的混淆。形状 n, 表示具有 n 个项目的一维数组的形状,而 n, 1 表示 n 行 x 1 列数组的形状。 (R,)(R,1) 只是添加(无用的)括号,但仍分别表示 1D 和 2D 数组形状,元组周围的括号强制评估顺序并防止将其作为值列表读取(例如在函数调用中)。记住这个元组的奇怪之处,事情变得更清楚了,NumPy 返回了有意义的形状。

G
Gareth Rees

一、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,)

这里的 shape (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 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

如果我们 reshape 一个数组,这不会更改数据缓冲区。相反,它会创建一个新视图来描述解释数据的不同方式。所以之后:

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

数组 b 具有与 a 相同的数据缓冲区,但现在它由 两个 索引进行索引,这些索引分别从 0 to 2 和 0 to 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 internals documentation

2. 怎么办?

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

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

您问题中的示例是:

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

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

M[:,0].sum()

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

M.sum(axis=0)

这对于思考如何存储数组非常有帮助。谢谢!访问(2-d)矩阵的列(或行)以进行进一步的矩阵计算是不方便的,因为我总是必须适当地重塑列。每次我必须将形状从 (n,) 更改为 (n,1)。
@SammyLee:如果您需要另一个轴,请使用 newaxis,例如 a[:, j, np.newaxis]a 的第 j 列,a[np.newaxis, i] 是第 i 行。
我正在尝试绘制索引以通过该模型在纸上获得更好的理解,但我似乎没有得到它,如果我有一个 2 x 2 x 4 的形状,我理解前 2 可以理解为 0000000011111111,最后 4 可以是理解为 0123012301230123 中间那个怎么回事?
想到这一点的一个简单方法是 numpy 在这里完全按照预期工作,但 Python 的元组打印可能会产生误导。在 (R, ) 的情况下,ndarray 的形状是具有单个元素的元组,因此由 Python 打印并带有尾随逗号。如果没有额外的逗号,它将是 ambiguous with an expression in parenthesis。具有一维的 ndarray 可以看作是长度为 R 的列向量。在 (R, 1) 的情况下,元组有两个元素,因此可以被认为是一个行向量(或一个长度为 R 的 1 行的矩阵。
@Alex-droidAD:见 this question 及其答案。
E
Eypros

(R,)(1,R) 之间的区别实际上是您需要使用的索引数量。 ones((1,R)) 是一个二维数组,恰好只有一行。 ones(R) 是一个向量。通常,如果变量具有多个行/列没有意义,则应该使用向量,而不是具有单维的矩阵。

对于您的具体情况,有几个选项:

1)只需将第二个参数设为向量。以下工作正常:

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

2) 如果您想要类似 matlab 的矩阵运算,请使用类 matrix 而不是 ndarray。所有矩阵都被强制为二维数组,运算符 * 进行矩阵乘法而不是逐元素(所以你不需要点)。以我的经验,这是值得的,但如果你习惯了matlab,它可能会很好。


是的。我期待一个更像 matlab 的行为。我来看看 matrix 类。 matrix 类 BTW 有什么问题?
matrix 的问题在于它只是二维的,而且因为它重载了运算符“*”,如果在 matrix 上使用,为 ndarray 编写的函数可能会失败。
K
Katie Jergens

形状是一个元组。如果只有 1 个维度,则形状将是一个数字,并且在逗号后只是空白。对于 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)


经典的。这么多复杂的答案,然后我发现这完全解释了它。谢谢!
h
hpaulj

对于其基本数组类,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 一开始只使用二维数组。较新的版本允许更多维度,但保留 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 最近才添加它。


P
Palak Bansal

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


您的直觉是由线性代数和/或类似 MATLAB 的语言所塑造的,这些语言主要用于二维数组、矩阵。在 MATLAB 中,一切都是二维的,甚至是“标量”。我们将 Python 和 numpy 用于比 dot 更多的产品:)
我同意。点积帮助我更好地理解了结构,我出于同样的原因提到它:)
M
Mikhail_Sam

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

所以这是一个:

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)

这工作正常!


TensorFlow 2.4 参见例如 stackoverflow.com/questions/67662727/…
b
bogatron

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

2) 对于您的示例,您正在计算一个外部产品,因此您可以使用 np.outer 在没有 reshape 调用的情况下执行此操作:

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

感谢你的回答。 1) M[:,0] 本质上是获取包含第一个元素的所有行,因此使用 (R, 1) 比使用 (1, R) 更有意义。 2) 它并不总是可以被 np.outer 替换,例如,点表示形状为 (1, R) 的矩阵,然后是 (R, 1)。
1) 是的,这 可能 是惯例,但这在其他情况下不太方便。约定也可以让 M[1, 1] 返回一个形状 (1, 1) 数组,但这通常也不如标量方便。如果您真的想要类似矩阵的行为,那么您最好使用 matrix 对象。 2) 实际上,无论形状是 (1, R)(R, 1) 还是两者的组合,np.outer 都有效。
J
Jesse H.

为了清楚起见,我们正在谈论:

一个 NumPy 数组,也称为 numpy.ndarray

numpy.ndarray.shape 已知的数组的形状

问题假设一些未知的 numpy.ndarray 形状为 (R,) 其中 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]
    ]
)

所以,第一个问题的答案:

形状(R,1)和(R,)有什么区别?

答案:它们有不同的维度。 a 是一个维度的长度,b 是另一个维度的长度,.shape 分别是 (a, b)(a,)b 恰好是 1。考虑这一点的一种方法是,如果 a = 1 那么行的长度为 1,因此它是一个行向量。如果 b = 1 则该列的长度为 1,因此它表示的 numpy.ndarray 是一个列向量。

上面的例子有更好的方法吗?

答案:假设我们有一个数组,我在上面的示例中使用了 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)

资源

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