1. 程式人生 > 程式設計 >numpy.array shape (R, 1) and (R,) 的區別

numpy.array shape (R, 1) and (R,) 的區別

翻譯自:stackoverflow 回答 By Gareth Rees

原問題

在 numpy 中,有些運算返回 shape 為 (R,1) 而有些返回 (R,)。由於需要顯式呼叫 reshape,這會讓矩陣乘法變得更加繁瑣。舉例來說,假設有一個矩陣 M,如果我們想執行 numpy.dot(M[:,0],numpy.ones((1,R))),其中 R 是行數(當然,換成列會有同樣的問題)。我們會得到 matrices are not aligned 異常,因為 M[:,0] 的 shape 是 (R,) 但是 numpy.ones((1,R)) 的 shape 是 (1,R)

所以我的問題是:

  1. shape (R,1)

    and (R,) 有什麼區別。我知道從字面意思上一個是數的 list,另一個是隻包含一個數的 list 組成的 list。只是好奇為什麼不把 numpy 設計成更傾向於 shape (R,1) 而不是 (R,) 以讓矩陣乘法更簡單。

  2. 上面的例子有更好的方法嗎?而不用顯式地像這樣呼叫 reshapenumpy.dot(M[:,0].reshape(R,1),R)))

回答

1. NumPy 中 shape 的含義

你寫到,“我知道從字面意思上一個是數的 list,另一個是隻包含一個數的 list 組成的 list”,但是用這種方式去想有點沒啥用。

考慮 NumPy 陣列的最好方式是它們包含兩個部分,一個 資料緩衝區

(data buffer),它只是一個原始資料塊(a block of raw elements), 以及一個描述如何解釋資料緩衝區的 檢視(view)。

比如,如果我們建立一個包含 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))
複製程式碼

陣列 ba 有相同的資料緩衝區,但是現在被 兩個 索引索引,其中分別從 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
複製程式碼

現在應該清楚了陣列具有一個或多個維度(dimensions)為 1 的 shape 意味著什麼。經過執行:

>>> 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,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,0,0]
6
複製程式碼

有關如何實現陣列的更多詳細資訊,請參見 NumPy 內部檔案

2. 該怎麼辦?

既然 numpy.reshape 只是建立了一個新檢視,那麼在有必要去使用它的時候不要感到害怕。當你想要以不同的方式索引陣列時,這是正確的工具。

然而,在長時間的計算中,通常可以首先安排去構造具有“正確”形狀的陣列,從而最大限度地減少 reshape 和 transpose 的次數。但在沒有看到導致有 reshape 需求的實際背景之前,很難說應該改變什麼。

你的問題中的例子是:

numpy.dot(M[:,0],numpy.ones((1,R)))
複製程式碼

但這是不現實的。首先,這個表示式:

M[:,0].sum()
複製程式碼

可以更簡單地計算結果。其次,第 0 列真的有什麼特別的嗎?也許你真正需要的是:

M.sum(axis=0)
複製程式碼