【Python庫系列】超詳細的NumPy入門

目錄

一、數組的創建

1.1 創建數組

1.2 數組的性質

二、數組的存載

2.1 numpy 自身的 .npy 格式

2.2 文本 .txt 格式

2.3 文本 .csv 格式

三、數組的獲取

3.1 正規索引

3.2 布爾索引

3.3 花式索引

四、數組的變形

4.1 重塑和打平

4.2 合併和分裂

4.3 重複和拼接

4.4 其他操作

五、數組的計算

5.1 元素層面計算

5.2 線性代數計算

5.3 元素整合計算

5.4 廣播機制計算


前提:導入numpy庫。

import numpy as np

一、數組的創建

1.1 創建數組

創建numpy數組的三種方式:

  • 按步就班法:np.array() 用在列表和元組上 
  • 定隔定點法:np.arange() 和 np.linspace()
  • 一步登天法:np.ones(),np.zeros(),np.eye() 和 np.random.random()

1. 按步就班法

list = [1, 2, 3.5, 5.5, 8]
np.array(list)
array([1. , 2. , 3.5, 5.5, 8. ])
tuple = (1, 2, 3.5, 5.5, 8)
print(np.array(tuple))
np.array(tuple)
[1.  2.  3.5 5.5 8. ]
array([1. , 2. , 3.5, 5.5, 8. ])

注意,numpy數組的輸出都帶有 array() 的字樣,裏面的元素用“中括號 []”框住。用函數 print 打印 numpy 數組就沒有 array() 的字樣了。

2. 定隔定點法

  • 定隔的 arange():  固定元素大小間隔
  • 定點的 linspace():固定元素個數

函數 arange 的參數爲起點,終點,間隔:arange(start , stop , step)。其中 stop 必須要有,start 和 step 沒有的話默認爲 1。

print(np.arange(10))
print(np.arange(2, 10))
print(np.arange(2, 10, 2))
[0 1 2 3 4 5 6 7 8 9]
[2 3 4 5 6 7 8 9]
[2 4 6 8]

函數 linspace 的參數爲起點,終點,點數:linspace (start , stop , num)。其中 start 和 stop 必須要有,num 沒有的話默認爲 50。

print(np.linspace(2, 6, 3))
print(np.linspace(2, 8, 4))
print(np.linspace(0, 49))
[2. 4. 6.]
[2. 4. 6. 8.]
[ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9. 10. 11. 12. 13. 14. 15. 16. 17.
 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35.
 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49.]

3. 一步登天法

  • zeros():    創建全是 0 的 n 維數組
  • ones():     創建全是 1 的 n 維數組
  • random():創建隨機 n 維數組
  • eye():       創建對角矩陣

前三種,由於輸出是 n 爲數組,它們的參數是一個「標量」或「元組類型的形狀」。

print(np.zeros(5)) # 標量5代表形狀(5,)
print(np.ones((2, 3)))
print(np.random.random((2, 3, 4)))
[0. 0. 0. 0. 0.]
[[1. 1. 1.]
 [1. 1. 1.]]
[[[0.42180299 0.84854733 0.115058   0.65318331]
  [0.03357884 0.05289622 0.19615066 0.22481889]
  [0.05550249 0.2390295  0.24496322 0.83995308]]

 [[0.73575253 0.19963972 0.3201794  0.75923466]
  [0.54934324 0.6029062  0.39607958 0.9521938 ]
  [0.10393022 0.71187835 0.04202606 0.85566354]]]

函數 eye(),它的參數就是一個標量,控制矩陣的行數或列數。

eye() 裏面的參數 k

  • k = 0: 默認設置,代表 1 落在對角線上
  • k = 1: 代表 1 落在對角線右上方
  • k = -1:代表 1 落在對角線左下方
print(np.eye(4))
print(np.eye(4, k=1))
[[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]]
[[0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]
 [0. 0. 0. 0.]]

1.2 數組的性質

先用按部就班的 np.array() 帶列表生成二維數組 arr。

arr = np.array([1, 2, 3.5, 5.5, 8])

再用 dir(arr) 來查看數組的屬性,之後我們對 type,ndim,len(),size,shape,stride,dtype 這幾個常用函數分析一波。

  • type:    數組類型,當然是 numpy.ndarray
  • ndim:   數組維度個數
  • len():   數組長度 
  • size:    數組元素個數
  • shape: 數組形狀,即每個維度的元素個數 (用元組來表示),只有一維,元素個數爲 5,寫成元組形式是 (5,)
  • strides:數組跨度,即在某一維度下爲了獲取到下一個元素需要「跨過」的字節數 (用元組來表示),float64 是 8 個字節數 (bytes),因此跨度爲 8
  • dtype:  數組元素類型 (注意和 type 區分)
print('類型:', type(arr))
print('維度:', arr.ndim)
print('長度:', len(arr))
print('個數:', arr.size)
print('形狀:', arr.shape)
print('跨度:', arr.strides)
print('類型:', arr.dtype)
類型: <class 'numpy.ndarray'>
維度: 
長度: 5
個數: 5
形狀: (5,)
跨度: (8,)
類型: float64

二維數組,甚至多維數組,大家可自行測試體會。

二、數組的存載

2.1 numpy 自身的 .npy 格式

  • save:np.save( ‘’文件名”,數組 ) 即可保存爲 .npy 格式
  • load:np.load( "文件名" ) 即可加載該文件
arr_disk = np.arange(8)
np.save("arr_disk", arr_disk) # 保存在當前目錄下

np.load("arr_disk.npy")
array([0, 1, 2, 3, 4, 5, 6, 7])

2.2 文本 .txt 格式

  • savetxt:用 np.savetxt( ‘’文件名”,數組 ) 即可保存爲 .txt 格式
  • loadtxt: 用 np.loadtxt( "文件名" ) 即可加載該文件
arr_text = np.array([[1., 2., 3.], [4., 5., 6.]])
np.savetxt("arr_text.txt", arr_text) # 保存爲當前目錄下

np.loadtxt("arr_text.txt")
array([[1., 2., 3.],
       [4., 5., 6.]])

2.3 文本 .csv 格式

arr_csv = np.array([[1, 2, 3], [4, 5, 6]])
np.savetxt("arr_csv.csv", arr_csv) # 保存爲當前目錄下

np.loadtxt("arr_csv.csv")

# 或 np.genfromtxt("arr_csv.csv")
array([[1., 2., 3.],
       [4., 5., 6.]])

三、數組的獲取

獲取數組是通過索引 (indexing) 和切片 (slicing) 來完成的。

  • 索引:獲取一個特定位置的元素,寫法是 arr[index]
  • 切片:獲取一段特定位置的元素,寫法是 arr[start : stop : step]

切片的操作是可以用索引操作來實現的 (一個一個總能湊成一段),只是沒必要罷了。爲了簡化,我們在本章三節標題裏把切片和索引都叫做索引。索引數組有三種形式,正規索引 (normal indexing)、布爾索引 (boolean indexing) 和花式索引 (fancy indexing)。

3.1 正規索引

一維數組:

arr1 = np.arange(10) # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print(arr1[6])   # 索引
print(arr1[1:5]) # 切片
6
[1 2 3 4]

二維數組:

arr2 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(arr2)
print(arr2[2])    # 索引第三行
print(arr2[0][2]) # 索引第一行第三列
print(arr2[0, 2]) # 索引第一行第三列
[[1 2 3]
 [4 5 6]
 [7 8 9]]
[7 8 9]
3
3
print(arr2[:2])         # 切片前兩行
print(arr2[:, [0, 2]])  # 切片第一列和第三列
print(arr2[1, :2])      # 切片第二行的前兩個元素    
print(arr2[:2, 2])      # 切片第三列的前兩個元素
[[1 2 3]
 [4 5 6]]
[[1 3]
 [4 6]
 [7 9]]
[4 5]
[3 6]

3.2 布爾索引

布爾索引,就是用一個由布爾 (boolean) 類型值組成的數組來選擇元素的方法。

code = np.array(['BABA', 'FB', 'JD', 'BABA', 'JD', 'FB'])
price = np.array([[170,177,169], [150,159,153],
                  [24,27,26], [165,170,167],
                  [22,23,20], [155,116,157]])
print(code == 'BABA')
print(price[code == 'BABA'])
print(price[code == 'BABA', :1])

注:這種布爾索引的操作在 Pandas 更常用也更方便。

3.3 花式索引

花式索引是獲取數組中想要的特定元素的有效方法。

arr = np.arange(32).reshape(8,4)
print(arr)
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]
 [20 21 22 23]
 [24 25 26 27]
 [28 29 30 31]]
print(arr[[4,3,6]], '\n')   # 按特定順序來獲取第 5,4 和 7 行
print(arr[[-4,-3,-6]], '\n')  # 按特定順序來獲取倒數第 4,3 和 6 行
print(arr[[1,5,7,2], [0,3,1,2]]) # 獲取第二行第一列、第六行第四列、第八行第二列、第三行第三列的元素
# print(np.array([arr[1,0], arr[5,3], arr[7,1], arr[2,2]]))
[[16 17 18 19]
 [12 13 14 15]
 [24 25 26 27]] 

[[16 17 18 19]
 [20 21 22 23]
 [ 8  9 10 11]] 

[ 4 23 29 10]
print(arr[:,[0,3,1,2]]) # 把原先的 [0,1,2,3] 的列換成 [0,3,1,2]
[[ 0  3  1  2]
 [ 4  7  5  6]
 [ 8 11  9 10]
 [12 15 13 14]
 [16 19 17 18]
 [20 23 21 22]
 [24 27 25 26]
 [28 31 29 30]]

四、數組的變形

本節介紹四大類數組層面上的操作,具體有:

  1. 重塑 (reshape) 和打平 (ravel, flatten)
  2. 合併 (concatenate, stack) 和分裂 (split)
  3. 重複 (repeat) 和拼接 (tile)
  4. 其他操作 (sort, insert, delete, copy)

4.1 重塑和打平

重塑 (reshape) 和打平 (ravel, flatten) 這兩個操作僅僅只改變數組的維度:

  • 重塑:是從低維到高維
  • 打平:是從高維到低維

1. 重塑

用reshape()函數將一維數組 arr 重塑成二維數組。

arr = np.arange(12)
print(arr)
print(arr.reshape((4, 3)))
[ 0  1  2  3  4  5  6  7  8  9 10 11]
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]

當你重塑高維矩陣時,不想花時間算某一維度的元素個數時,可以用「-1」取代,程序會自動幫你計算出來。比如把 12 個元素重塑成 (2,  6),你可以寫成 (2, -1) 或者 (-1, 6)。

print(arr.reshape((2, -1)))
print(arr.reshape((-1, 6)))
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]]
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]]

注意:

在衆多計算機語言中,

  • 默認行主序的有 C 語言( order=‘C’ 等價於行主序)

  • 默認列主序的有 Fortran 語言( order=‘F’ 等價於列主序)

在 numpy 數組中,默認的是行主序,即 order ='C'。如果你真的想在「重塑」和「打平」時用列主序,只用把 order 設爲 'F',以重塑舉例:

arr = np.arange(12)
print(arr.reshape((4, 3)))
print(arr.reshape((4,3), order='F'))
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
[[ 0  4  8]
 [ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]]

2. 打平

用 ravel() 或flatten() 函數將二維數組 arr 打平成一維數組。

arr = np.arange(12).reshape((4,3))
print(arr)

ravel_arr = arr.ravel()
print(ravel_arr)

flatten_arr = arr.flatten()
print(flatten_arr)
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
[ 0  1  2  3  4  5  6  7  8  9 10 11]
[ 0  1  2  3  4  5  6  7  8  9 10 11]

函數 ravel() 或 flatten() 的不同之處是:

  1. ravel() 按「行主序」打平時沒有複製原數組,按「列主序」在打平時複製了原數組
  2. flatten() 在打平時複製了原數組

4.2 合併和分裂

合併 (concatenate, stack) 和分裂 (split) 這兩個操作僅僅只改變數組的分合:

  • 合併:是多合一
  • 分裂:是一分多

1. 合併

使用「合併」函數有三種選擇:

  1. 有通用的 concatenate
  2. 有專門的 vstack, hstack, dstack
  3. 有極簡的 r_, c_

用下面兩個數組來舉例:

arr1 = np.array([[1, 2, 3], [4, 5, 6]])
arr2 = np.array([[7, 8, 9], [10, 11, 12]])

在 concatenate() 函數裏通過設定軸,來對數組進行豎直方向合併 (軸 0) 和水平方向合併 (軸 1)。  

print(np.concatenate([arr1, arr2], axis=0))
print(np.concatenate([arr1, arr2], axis=1))
[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]]
[[ 1  2  3  7  8  9]
 [ 4  5  6 10 11 12]]

在 NumPy 裏還有專門合併的函數

  • vstack:v 代表 vertical,豎直合併,等價於 concatenate(axis=0)
  • hstack:h 代表 horizontal,水平合併,等價於 concatenate(axis=1)
  • dstack:d 代表 depth-wise,按深度合併,深度有點像彩色照片的 RGB 通道

print(np.vstack((arr1, arr2)))
print(np.hstack((arr1, arr2)))
print(np.dstack((arr1, arr2)))
[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]]
[[ 1  2  3  7  8  9]
 [ 4  5  6 10 11 12]]
[[[ 1  7]
  [ 2  8]
  [ 3  9]]

 [[ 4 10]
  [ 5 11]
  [ 6 12]]]

和 vstack, hstack 不同,dstack 將原數組的維度增加了一維。

np.dstack((arr1, arr2)).shape
(2, 3, 2)

此外,還有一種更簡單的在豎直和水平方向合併的函數,r_() 和 c_()。

print(np.r_[arr1,arr2])
print(np.c_[arr1,arr2])
[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]]
[[ 1  2  3  7  8  9]
 [ 4  5  6 10 11 12]]

2. 分裂

使用「分裂」函數有兩種選擇

  1. 有通用的 split
  2. 有專門的 hsplit, vsplit

用下面數組來舉例:

arr = np.arange(25).reshape((5,5))
print(arr)
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]
 [20 21 22 23 24]]

和 concatenate() 函數一樣,我們可以在 split() 函數裏通過設定軸,來對數組沿着豎直方向分裂 (軸 0) 和沿着水平方向分裂 (軸 1)。 

first, second, third = np.split(arr, [1,3])
print('The first split is', first)    # 第 1 行
print('The second split is', second)  # 第 2 到 3 行
print('The third split is', third)    # 第 4 到 5 行
The first split is [[0 1 2 3 4]]
The second split is [[ 5  6  7  8  9]
 [10 11 12 13 14]]
The third split is [[15 16 17 18 19]
 [20 21 22 23 24]]

split() 默認沿着軸 0 分裂,其第二個參數 [1, 3] 相當於是個切片操作,將數組分成三部分。

vsplit() 和 split(axis=0) 等價,hsplit() 和 split(axis=1) 等價。

first, second, third = np.hsplit(arr, [1, 3])
print('The first split is', first)
print('The second split is', second)
print('The third split is', third)
The first split is [[ 0]
 [ 5]
 [10]
 [15]
 [20]]
The second split is [[ 1  2]
 [ 6  7]
 [11 12]
 [16 17]
 [21 22]]
The third split is [[ 3  4]
 [ 8  9]
 [13 14]
 [18 19]
 [23 24]]

4.3 重複和拼接

重複 (repeat) 和拼接 (tile) 這兩個操作本質都是複製。

  • 重複:是在元素層面複製
  • 拼接:是在數組層面複製

1. 重複

函數 repeat() 複製的是數組的每一個元素,參數有幾種設定方法:

  • 一維數組:用標量和列表來複制元素的個數
  • 多維數組:用標量和列表來複制元素的個數,用軸來控制複製的行和列
arr = np.arange(5)
print(arr)
print(arr.repeat(3))   # 數組 arr 中每個元素複製 3 遍
print(arr.repeat([1, 2, 3, 4, 5]))  # 數組 arr 中每個元素分別複製 1, 2, 3, 4, 5 遍
[0 1 2 3 4]
[0 0 0 1 1 1 2 2 2 3 3 3 4 4 4]
[0 1 1 2 2 2 3 3 3 3 4 4 4 4 4]
arr2 = np.arange(6).reshape((2,3))
print(arr2)
print(arr2.repeat(2, axis=0))        # 數組 arr2 中每個元素沿着軸 0 複製 2 遍
print(arr2.repeat([2,3,4], axis=1))  # 數組 arr2 中每個元素沿着軸 1 分別複製 2, 3, 4 遍
[[0 1 2]
 [3 4 5]]
[[0 1 2]
 [0 1 2]
 [3 4 5]
 [3 4 5]]
[[0 0 1 1 1 2 2 2 2]
 [3 3 4 4 4 5 5 5 5]]

2. 拼接

函數 tile() 複製的是數組本身,參數有幾種設定方法:

  • 標量:把數組當成一個元素,一列一列複製
  • 形狀:把數組當成一個元素,按形狀複製
arr2 = np.arange(6).reshape((2,3))
print(arr2)
print(np.tile(arr2, 2))       # 數組 arr2 按列複製 2 遍
print(np.tile(arr2, (2, 3)))  # 數組 arr2 按形狀複製 6 (2×3) 遍,並以 (2,3) 的形式展現
[[0 1 2]
 [3 4 5]]
[[0 1 2 0 1 2]
 [3 4 5 3 4 5]]
[[0 1 2 0 1 2 0 1 2]
 [3 4 5 3 4 5 3 4 5]
 [0 1 2 0 1 2 0 1 2]
 [3 4 5 3 4 5 3 4 5]]

4.4 其他操作

本節討論數組的其他操作,包括排序 (sort),插入 (insert),刪除 (delete) 和複製 (copy)。

1. 排序

排序包括直接排序 (direct sort) 和間接排序 (indirect sort)。

直接排序

arr = np.array([5, 3, 2, 6, 1, 4])
arr.sort()
print(arr)
[1 2 3 4 5 6]

sort()函數是按升序 (ascending order) 排列的,該函數裏沒有參數可以控制 order,因此你想要按降序排列的數組,只需:

print(arr[::-1])
[6 5 4 3 2 1]

注意:

用來排序 numpy 用兩種方式:

  1. arr.sort():sort 會改變 arr
  2. np.sort(arr):sort 在排序時創建了 arr 的一個複製品,不會改變 arr

間接排序

有時候我們不僅僅只想排序數組,還想在排序過程中提取每個元素在原數組對應的索引(index),這時 argsort() 就派上用場了。

score = np.array([100, 60, 99, 80, 91])
idx = score.argsort()
print(idx)  # 打印數據由小到大的地址
print(score[idx])
[1 3 4 2 0]
[ 60  80  91  99 100]
arr = np.random.randint(40, size=(3,4))
print(arr)
print(arr[:, arr[0].argsort()]) # 對其第一行 arr[0] 排序,獲取索引,在應用到所用行上
[[24 16 39 34]
 [10  5 29 35]
 [39 22 17 15]]
[[16 24 34 39]
 [ 5 10 35 29]
 [22 39 15 17]]

2. 插入和刪除

和列表一樣,我們可以給 numpy 數組:

  • 用insert()函數在某個特定位置之前插入元素
  • 用delete()函數刪除某些特定元素
a = np.arange(6)
print(a)
print(np.insert(a, 1, 100))
print(np.delete(a, [1, 3]))
[0 1 2 3 4 5]
[  0 100   1   2   3   4   5]
[0 2 4 5]

3. 複製

用copy()函數來複制數組 a 得到 a_copy,很明顯,改變 a_copy 裏面的元素不會改變 a。

a = np.arange(6)
a_copy = a.copy()
print('Before changing value, a is', a)
print('Before changing value, a_copy is', a_copy)
a_copy[-1] = 99
print('After changing value, a_copy is', a_copy)
print('After changing value, a is', a)
Before changing value, a is [0 1 2 3 4 5]
Before changing value, a_copy is [0 1 2 3 4 5]
After changing value, a_copy is [ 0  1  2  3  4 99]
After changing value, a is [0 1 2 3 4 5]

五、數組的計算

本節介紹四大類數組計算,具體有:

  1. 元素層面 (element-wise) 計算
  2. 線性代數 (linear algebra) 計算
  3. 元素整合 (element aggregation) 計算
  4. 廣播機制 (broadcasting) 計算

5.1 元素層面計算

Numpy 數組元素層面計算包括:

  1. 二元運算 (binary operation):加減乘除
  2. 數學函數:倒數、平方、指數、對數
  3. 比較運算 (comparison)

先定義兩個數組 arr1 和 arr2。

arr1 = np.array([[1., 2., 3.], [4., 5., 6.]])
arr2 = np.ones((2,3)) * 2
print(arr1)
print(arr2)
[[1. 2. 3.]
 [4. 5. 6.]]
[[2. 2. 2.]
 [2. 2. 2.]]
# 加減乘除
print(arr1 + arr2 + 1)
print(arr1 - arr2)
print(arr1 * arr2)
print(arr1 / arr2)
# 倒數、平方、指數、對數
print(1 / arr1)
print(arr1 ** 2)
print(np.exp(arr1))
print(np.log(arr1))
# 比較
print(arr1 > arr2)
print(arr1 > 3)
[[4. 5. 6.]
 [7. 8. 9.]]
[[-1.  0.  1.]
 [ 2.  3.  4.]]
[[ 2.  4.  6.]
 [ 8. 10. 12.]]
[[0.5 1.  1.5]
 [2.  2.5 3. ]]
[[1.         0.5        0.33333333]
 [0.25       0.2        0.16666667]]
[[ 1.  4.  9.]
 [16. 25. 36.]]
[[  2.71828183   7.3890561   20.08553692]
 [ 54.59815003 148.4131591  403.42879349]]
[[0.         0.69314718 1.09861229]
 [1.38629436 1.60943791 1.79175947]]
[[False False  True]
 [ True  True  True]]
[[False False False]
 [ True  True  True]]

5.2 線性代數計算

在 NumPy 默認不採用矩陣運算,而是數組 (ndarray) 運算。矩陣只是二維,而數組可以是任何維度,因此數組運算更通用些。

注:arr 的形狀是 (2,),只含一個元素的元組只說明 arr 是一維,數組是不分行數組或列數組的。

下面我們分別對「數組」和「矩陣」從創建、轉置、求逆和相乘四個方面看看它們的同異。

1. 創建

創建數組 arr2d 和矩陣 A,注意它們的輸出有 array 和 matrix 的關鍵詞。

arr2 = np.array([[1, 2], [3, 1]])
arr2
array([[1, 2],
       [3, 1]])
A = np.asmatrix(arr2)
matrix([[1, 2],
        [3, 1]])

2. 轉置

數組用 arr2.T 操作或 arr.tranpose() 函數,而矩陣用 A.T 操作。主要原因就是 .T 只適合二維數據。

print(arr2.T)
print(arr2.transpose())
print(A.T)
[[1 3]
 [2 1]]
[[1 3]
 [2 1]]
[[1 3]
 [2 1]]

3. 求逆

數組用 np.linalg.inv() 函數,而矩陣用 A.I 和 A**-1 操作。

print(np.linalg.inv(arr2))
print(A.I)
print(A**-1)
[[-0.2  0.4]
 [ 0.6 -0.2]]
[[-0.2  0.4]
 [ 0.6 -0.2]]
[[-0.2  0.4]
 [ 0.6 -0.2]]

4. 相乘

相乘是個很模棱兩可的概念

  • 數組相乘是在元素層面進行,
  • 矩陣相乘要就是數學定義的矩陣相乘 (比如第一個矩陣的列要和第二個矩陣的行一樣)
arr2 = np.array([[1, 2], [3, 1]])
A = np.asmatrix(arr2)
print(arr2*arr2)  # 數組相乘
print(A*A)        # 矩陣相乘
[[1 4]
 [9 1]]
[[7 4]
 [6 7]]

點乘函數 dot()

點乘左右兩邊最常見的數組就是:

  • 向量 (1D) 和向量 (1D)
  • 矩陣 (2D) 和向量 (1D)
  • 矩陣 (2D) 和矩陣 (2D)

分別看看三個簡單例子。

例一:np.dot(向量, 向量) 實際上做的就是內積,即把兩個向量每個元素相乘,最後再加總。點乘結果 10 是個標量 (0D 數組),形狀 = ()。

x = np.array([1, 2, 3])
y = np.array([3, 2, 1])
z = np.dot(x, y)
print(z.shape)
print(z)
()
10

例二:np.dot(矩陣, 向量) 實際上做的就是普通的矩陣乘以向量。點乘結果是個向量 (1D 數組),形狀 = (2, )。

x = np.array([1, 2, 3])
y = np.array([[3, 2, 1], [1, 1, 1]])
z = np.dot(y, x)
print(z.shape)
print(z)
(2,)
[10  6]

例三:np.dot(矩陣, 矩陣) 實際上做的就是普通的矩陣乘以矩陣。點乘結果是個矩陣 (2D 數組),形狀 = (2, 3)。

x = np.array([[1, 2, 3], [1, 2, 3], [1, 2, 3]])
y = np.array([[3, 2, 1], [1, 1, 1]])
z = np.dot(y, x)
print(z.shape)
print(z)
(2, 3)
[[ 6 12 18]
 [ 3  6  9]]

當 x 第二個維度的元素 (x.shape[1]) 和 y 第一個維度的元素 (y.shape[0]) 個數相等時,np.dot(X, Y) 纔有意義。

5.3 元素整合計算

在數組中,元素可以以不同方式整合 (aggregation)。拿求和 (sum) 函數來說,我們可以對數組:

  • 所有的元素求和
  • 在某個軸 (axis) 上的元素求和
arr = np.arange(1, 7).reshape((2, 3))
print(arr)
print( '總和:', arr.sum() )
print( '跨行求和', arr.sum(axis=0) )
print( '跨列求和', arr.sum(axis=1) )
[[1 2 3]
 [4 5 6]]
總和: 21
跨行求和 [5 7 9]
跨列求和 [ 6 15]

行和列這些概念對矩陣 (二維矩陣) 才適用,高維矩陣還是要用軸 (axis) 來區分每個維度。

除了 sum 函數,整合函數還包括 min, max, mean, std 和 cumsum,分別是求最小值、最大值、均值、標準差和累加,這些函數對數組裏的元素整合方式和 sum 函數相同,就不多講了。

5.4 廣播機制計算

當對兩個形狀不同的數組按元素操作時,可能會觸發「廣播機制」。具體做法,先適當複製元素使得這兩個數組形狀相同後再按元素操作,兩個步驟:

  1. 廣播軸 (broadcast axis):比對兩個數組的維度,將形狀小的數組的維度 (軸) 補齊
  2. 複製元素:順着補齊的軸,將形狀小的數組裏的元素複製,使得最終形狀和另一個數組吻合
arr = np.arange(12).reshape((4,3))
print(arr)
print(arr.mean(axis=0))
print(arr - arr.mean(axis=0))
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
[4.5 5.5 6.5]
[[-4.5 -4.5 -4.5]
 [-1.5 -1.5 -1.5]
 [ 1.5  1.5  1.5]
 [ 4.5  4.5  4.5]]

沿軸 0 的均值的一維數組被廣播到數組 arr 的所有的行上。

廣播機制的規則

當我們對兩個數組操作時,如果它們的形狀

  • 不相容 (incompatible),廣播機制不能進行
  • 相容 (compatible),廣播機制可以進行

因此,進行廣播機制分兩步

  1. 檢查兩個數組形狀是否兼容,即從兩個形狀元組最後一個元素,來檢查(它們是否相等,是否有一個等於 1)。
  2. 一旦它們形狀兼容,確定兩個數組的最終形狀。

例一:維度一樣,形狀不一樣

a = np.array([[1, 2, 3]])
b = np.array([[4], [5], [6]])
print('The shape of a is', a.shape)
print('The shape of b is', b.shape)
The shape of a is (1, 3)
The shape of b is (3, 1)

回顧進行廣播機制的兩步

  1. 檢查數組 a 和 b 形狀是否兼容,從兩個形狀元組 (1, 3) 和 (3, 1)最後一個元素開始檢查,發現它們都滿足『有一個等於 1』的條件。
  2. 因此它們形狀兼容,兩個數組的最終形狀爲 (max(1,3), max(3,1)) = (3, 3)

到此,a 和 b 被擴展成 (3, 3) 的數組,讓我們看看 a + b 等於多少?

c = a + b
print('The shape of c is', c.shape)
print('a is', a)
print('b is', b)
print('c = a + b =', c)
The shape of c is (3, 3)
a is [[1 2 3]]
b is [[4]
 [5]
 [6]]
c = a + b = [[5 6 7]
 [6 7 8]
 [7 8 9]]

例二:維度不一樣

a = np.arange(5)
b = np.array(2)
print('The dimension of a is', a.ndim, 'and the shape of a is', a.shape)
print('The dimension of b is', b.ndim, 'and the shape of b is', b.shape)
The dimension of a is 1 and the shape of a is (5,)
The dimension of b is 0 and the shape of b is ()

數組 a 和 b 形狀分別爲 (5,) 和 (),首先我們把缺失的維度用 1 補齊得到 (5,) 和 (1,),再根據廣播機制那套流程得到這兩個形狀是兼容的,而且最終形狀爲 (5,)。

c = a + b
print('The dimension of c is', c.ndim, 'and the shape of c is', c.shape, '\n')
print('a is', a)
print('b is', b)
print('c = a + b =', c)
The dimension of c is 1 and the shape of c is (5,) 

a is [0 1 2 3 4]
b is 2
c = a + b = [2 3 4 5 6]

例五:

a = np.array([[[1,2,3], [4,5,6]]])
b1 = np.array([[1,1,1], [2,2,2], [3,3,3]])
b2 = np.arange(3).reshape((1, 3))
b3 = np.arange(6).reshape((2, 3))
b4 = np.arange(12).reshape((2, 2, 3))
b5 = np.arange(6).reshape((2, 1, 3))

print('a的維度:', a.ndim, 'a的形狀:', a.shape)
print('b1的維度:', b1.ndim, 'b1的形狀:', b1.shape)
print('b2的維度:', b2.ndim, 'b2的形狀:', b2.shape)
print('b3的維度:', b3.ndim, 'b3的形狀:', b3.shape)
print('b4的維度:', b4.ndim, 'b4的形狀:', b4.shape)
print('b5的維度:', b5.ndim, 'b5的形狀:', b5.shape)
a的維度: 3 a的形狀: (1, 2, 3)
b1的維度: 2 b1的形狀: (3, 3)
b2的維度: 2 b2的形狀: (1, 3)
b3的維度: 2 b3的形狀: (2, 3)
b4的維度: 3 b4的形狀: (2, 2, 3)
b5的維度: 3 b5的形狀: (2, 1, 3)

對於數組 a 和 b1,它們形狀是 (1, 2, 3) 和 (3, 3)。元組最後一個都是 3,兼容;倒數第二個是 3 和 2,即不相等,也沒有一個是 1,不兼容a 和 b1 不能進行廣播機制。不行就看看下面代碼:

c1 = a + b1
print(c1)
print(c1.shape)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-159-224e02cbb1e0> in <module>
----> 1 c1 = a + b1
      2 print(c1)
      3 print(c1.shape)

ValueError: operands could not be broadcast together with shapes (1,2,3) (3,3) 

a 和其他 b2, b3, b4, b5 都可以進行廣播機制。

 

c2 = a + b2
print(c2)
print(c2.shape)

c3 = a + b3
print(c3)
print(c3.shape)

c4 = a + b4
print(c4)
print(c4.shape)

c5 = a + b5
print(c5)
print(c5.shape)
[[[1 3 5]
  [4 6 8]]]
(1, 2, 3)
[[[ 1  3  5]
  [ 7  9 11]]]
(1, 2, 3)
[[[ 1  3  5]
  [ 7  9 11]]

 [[ 7  9 11]
  [13 15 17]]]
(2, 2, 3)
[[[ 1  3  5]
  [ 4  6  8]]

 [[ 4  6  8]
  [ 7  9 11]]]
(2, 2, 3)

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章