Numpy 是 Python 專門處理高維數組 (high dimensional array) 的計算的包,每次使用它遇到問題都會它的官網 (www.numpy.org). 去找答案。 在使用 numpy 以前,須要引進它,語法以下:javascript
import numpy
這樣你就能夠用 numpy 裏面全部的內置方法 (build-in methods) 了,好比求和與均值。php
numpy.sum()numpy.mean()
可是每次寫 numpy 字數有點多,一般咱們給 numpy 起個別名 np,用如下語法,這樣全部出現 numpy 的地方均可以用 np 替代。css
import numpy as np
爲何要專門學習數組呢?看下面「numpy 數組」和「列表」之間的計算效率對比:兩個大小都是 1000000,把每一個元素翻倍,運行 10 次用 %time 記時。html
my_arr = np.arange(1000000)my_list = list(range(1000000))
%time for _ in range(10): my_arr2 = my_arr * 2
Wall time: 48.9 ms
%time for _ in range(10): my_list2 = [x * 2 for x in my_list]
Wall time: 1.33 s
咱們發現「numpy 數組」效率是「列表」效率的 27 (1.33*1000/48.9) 倍左右。若是元素全是數值型變量 (numerical variable),那麼 numpy 數組明顯是個很好的數據結構。java
學習 numpy 仍是遵循的 Python 裏「萬物皆對象」的原則,既然把數組當對象,咱們就按着數組的建立、數組的存載、數組的獲取、數組的變形、和數組的計算來盤一盤 NumPy,目錄以下:web
有些讀者可能會說,NumPy 都什麼好學的,數組都弄不懂的人還能幹什麼,那我來問你個問題,知道「轉置操做」吧,那麼下面這個二維數組轉置後是什麼?算法
arr = np.array([[1,2,3],[4,5,6]])arr
array([[1, 2, 3], [4, 5, 6]])
太簡單了,是 [[1,4], [2,5], [3,6]],來看看是否是。shell
arr.T
array([[1, 4], [2, 5], [3, 6]])
答對了,你牛,再看一道轉置的題 apache
arr = np.arange(16).reshape((2, 2, 4))arr
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
等等,如今有三維,轉置一般不是轉兩個維度嗎?轉三個維度也能夠?固然,好比把第 1, 2, 3 維度轉置到第 2, 1, 3 維度,能夠用 transpose 函數。swift
arr.transpose(1,0,2)
array([[[ 0, 1, 2, 3],
[ 8, 9, 10, 11]],
[[ 4, 5, 6, 7],
[12, 13, 14, 15]]])
若是不知道上面答案怎麼來的,我以爲你仍是有必要看看本帖的。因爲篇幅緣由,NumPy 系列也分兩貼,上貼講前三節的內容,下帖講後兩節的內容。
1.1
初次印象
數組 (array) 是相同類型的元素 (element) 的集合所組成數據結構 (data structure)。numpy 數組中的元素用的最可能是「數值型」元素,平時咱們說的一維、二維、三維數組長下面這個樣子 (對應着線、面、體)。四維數組很難被可視化。
注意一個關鍵字 axis,中文叫「軸」,一個數組是多少維度就有多少根軸。因爲 Python 計數都是從 0 開始的,那麼
第 1 維度 = axis 0
第 2 維度 = axis 1
第 3 維度 = axis 2
但這些數組只可能在平面上打印出來,那麼它們 (高於二維的數組) 的表現形式稍微有些不一樣。
分析上圖各個數組的在不一樣維度上的元素:
一維數組:軸 0 有 3 個元素
二維數組:軸 0 有 2 個元素,軸 1 有 3 個元素
三維數組:軸 0 有 2 個元素 (2 塊),軸 1 有 2 個元素,軸 2 有 3 個元素
四維數組:軸 0 有 2 個元素 (2 塊),軸 1 有 2 個元素 (2 塊),軸 2 有 2 個元素,軸 3 有 3 個元素
2.1
建立數組
帶着上面這個對軸的認識,接下來咱們用代碼來建立 numpy 數組,有三種方式:
按步就班的 np.array() 用在列表和元組上
定隔定點的 np.arange() 和 np.linspace()
一步登天的 np.ones(), np.zeros(), np.eye() 和 np.random.random()
給了「列表」和「元組」原材料,用 np.array() 包裝一下便獲得 numpy 數組。
l = [3.5, 5, 2, 8, 4.2]np.array(l)
array([3.5, 5. , 2. , 8. , 4.2])
t = (3.5, 5, 2, 8, 4.2)np.array(t)
array([3.5, 5. , 2. , 8. , 4.2])
注意,numpy 數組的輸出都帶有 array() 的字樣,裏面的元素用「中括號 []」框住。
更常見的兩種建立 numpy 數組方法:
定隔的 arange:固定元素大小間隔
定點的 linspace:固定元素個數
先看 arange 例子:
print( np.arange(8) )print( np.arange(2,8) )print( np.arange(2,8,2))
[0 1 2 3 4 5 6 7]
[2 3 4 5 6 7]
[2 4 6]
函數 arange 的參數爲起點 , 終點 , 間隔
arange(start , stop , step)
其中 stop 必需要有,start 和 step 沒有的話默認爲 1。對着這個規則看看上面各類狀況的輸出。
注:用函數 print 打印 numpy 數組就沒有 array() 的字樣了,只用其內容,並且元素之間的「逗號」也沒有了。
再看 linspace 的例子:
print( np.linspace(2,6,3) )print( np.linspace(3,8,11) )
[2. 4. 6.]
[3. 3.5 4. 4.5 5. 5.5 6. 6.5 7. 7.5 8. ]
函數 linspace 的參數爲起點 , 終點 , 點數
linspace (start , stop , num)
其中 start 和 stop 必需要有,num 沒有的話默認爲 50。對着這個規則看看上面各類狀況的輸出。
NumPy 還提供一次性
用 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.15684866 0.33684519 0.85095027 0.67827412]
[0.58041935 0.12639616 0.33509142 0.99561644]
[0.59581471 0.92043399 0.56731046 0.76811703]]
[[0.74276133 0.85278489 0.32392871 0.40553182]
[0.7718898 0.35496469 0.20061144 0.00351225]
[0.49957334 0.48449498 0.62835324 0.29610557]]]
對於函數 eye(),它的參數就是一個標量,控制矩陣的行數或列數:
np.eye(4)
array([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]])
此外還能夠設定 eye() 裏面的參數 k
默認設置 k = 0 表明 1 落在對角線上
k = 1 表明 1 落在對角線右上方
k = -1 表明 1 落在對角線左下方
np.eye(4, k=1)
array([[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.],
[0., 0., 0., 0.]])
1.3
數組性質
還記得 Python 裏面「萬物皆對象」麼?numpy 數組也不例外,那麼咱們來看看數組有什麼屬性 (attributes) 和方法 (methods)。
用按步就班的 np.array() 帶列表生成數組 arr
arr = np.array([3.5, 5, 2, 8, 4.2])arr
如今你應該會用 dir(arr) 來查看數組的屬性了吧,看完以後咱們對 type, ndim, len(), size, shape, stride, dtype 幾個感興趣,一把梭打印出來看看:
print( 'The type is', type(arr) )print( 'The dimension is', arr.ndim )print( 'The length of array is', len(arr) )print( 'The number of elements is', arr.size )print( 'The shape of array is', arr.shape )print( 'The stride of array is', arr.strides )print( 'The type of elements is', arr.dtype )
The type is <class 'numpy.ndarray'>
The dimension is 1
The length of array is 5
The number of elements is 5
The shape of array is (5,)
The stride of array is (8,)
The type of elements is float64
根據結果咱們來看看上面屬性究竟是啥:
type:數組類型,固然是 numpy.ndarray
ndim:維度個數是 1
len():數組長度爲 5 (注意這個說法只對一維數組有意義)
size:數組元素個數爲 5
shape:數組形狀,即每一個維度的元素個數 (用元組來表示),只有一維,元素個數爲 5,寫成元組形式是 (5,)
strides:跨度,即在某一維度下爲了獲取到下一個元素須要「跨過」的字節數 (用元組來表示),float64 是 8 個字節數 (bytes),所以跨度爲 8
dtype:數組元素類型,是雙精度浮點 (注意和 type 區分)
注意我黃色高亮了 strides,這個概念對於解決引言的「轉置高維數組」問題很重要。一圖勝千言。
咦,爲何有個 Python View 和 Memory Block 啊?這兩個不是同樣的麼?對一維數組來講,「Python 視圖」看它和「內存塊」存儲它的形式是同樣的,但對二維數組甚至高維數組呢?
仍是用按步就班的 np.array() 帶二維列表生成二維數組 arr2d
l2 = [[1, 2, 3], [4, 5, 6]]arr2d = np.array(l2)arr2d
array([[1, 2, 3],
[4, 5, 6]])
一把梭打印屬性出來看看:
print( 'The type is', type(arr2d) )print( 'The dimension is', arr2d.ndim )print( 'The length of array is', len(arr2d) )print( 'The number of elements is', arr2d.size )print( 'The shape of array is', arr2d.shape )print( 'The stride of array is', arr2d.strides )print( 'The type of elements is', arr2d.dtype )
The type is <class 'numpy.ndarray'>
The dimension is 2
The length of array is 2
The number of elements is 6
The shape of array is (2, 3)
The stride of array is (12, 4)
The type of elements is int32
一樣,咱們來分析一下上面屬性:
type:數組類型 numpy.ndarray
ndim:維度個數是 2
len():數組長度爲 2 (嚴格定義 len 是數組在「軸 0」的元素個數)
size:數組元素個數爲 6
shape:數組形狀 (2, 3)
strides:跨度 (12, 4) 看完下圖再解釋
dtype:數組元素類型 int32
對於二維數組,Python 視圖」看它和「內存塊」存儲它的形式是不同的,以下圖所示:
在 numpy 數組中,默認的是行主序 (row-major order),意思就是每行的元素在內存塊中彼此相鄰,而列主序 (column-major order) 就是每列的元素在內存塊中彼此相鄰。
回顧跨度 (stride) 的定義,即在某一維度下爲了獲取到下一個元素須要「跨過」的字節數。注:每個 int32 元素是 4 個字節數。對着上圖:
第一維度 (軸 0):沿着它獲取下一個元素須要跨過 3 個元素,即 12 = 3×4 個字節
第二維度 (軸 1):沿着它獲取下一個元素須要跨過 1 個元素,即 4 = 1×4 個字節
所以該二維數組的跨度爲 (12, 4)。
用 np.random.random() 來生成一個多維數組:
arr4d = np.random.random( (2,2,2,3) )
裏面具體元素是什麼不重要,一把梭 arr4d 的屬性比較重要:
print( 'The type is', type(arr4d) )print( 'The dimension is', arr4d.ndim )print( 'The length of array is', len(arr4d) )print( 'The number of elements is', arr4d.size )print( 'The shape of array is', arr4d.shape )print( 'The stride of array is', arr4d.strides )print( 'The type of elements is', arr4d.dtype )
The type is <class 'numpy.ndarray'>
The dimension is 4
The length of array is 2
The number of elements is 24
The shape of array is (2, 2, 2, 3)
The stride of array is (96, 48, 24, 8)
The type of elements is float64
除了 stride,都好理解,請根據下圖好好想一想爲何 stride 是 (96, 48, 24, 8)?[Hint: 一個 float64 的元素佔 8 個字節]
算了仍是分析一下吧 (省得掉粉 )。回顧跨度 (stride) 的定義,即在某一維度下爲了獲取到下一個元素須要「跨過」的字節數。注:每個 float64 元素是 8 個字節數
第一維度 (軸 0):沿着它獲取下一個元素須要跨過 12 個元素,即 96 = 12×8 個字節
第二維度 (軸 1):沿着它獲取下一個元素須要跨過 6 個元素,即 48 = 6×8 個字節
第三維度 (軸 2):沿着它獲取下一個元素須要跨過 3 個元素,即 24 = 3×8 個字節
第四維度 (軸 3):沿着它獲取下一個元素須要跨過 1 個元素,即 8 = 1×8 個字節
所以該四維數組的跨度爲 (96, 48, 24, 8)。
留一道思考題,strides 和 shape 有什麼關係?
strides = (96, 48, 24, 8)
shape = (2, 2, 2, 3)
總不能每一個高維數組都用可視化的方法來算 strides 把。
本節講數組的「保存」和「加載」,我知道它們沒什麼技術含量,可是很重要。假設你已經訓練完一個深度神經網絡,該網絡就是用無數參數來表示的。好比權重都是 numpy 數組,爲了下次不用訓練而重複使用,將其保存成 .npy 格式或者 .csv 格式是很是重要的。
用 np.save 函數將 numpy 數組保存爲 .npy 格式,具體寫法以下:
np.save( ‘’文件名」,數組 )
arr_disk = np.arange(8)np.save("arr_disk", arr_disk)arr_disk
arr_disk.npy 保存在 Jupyter Notebook 所在的根目錄下。要加載它也很簡單,用 np.load( "文件名" ) 便可:
np.load("arr_disk.npy")
array([0, 1, 2, 3, 4, 5, 6, 7])
用 np.savetxt 函數將 numpy 數組保存爲 .txt 格式,具體寫法以下:
np.save( ‘’文件名」,數組 )
arr_text = np.array([[1., 2., 3.], [4., 5., 6.]])np.savetxt("arr_from_text.txt", arr_text)
arr_from_text.txt 保存在 Jupyter Notebook 所在的根目錄下,用 Notepad 打開看裏面確實存儲着 [[1,2,3], [4,5,6]]。
用 np.loadtxt( "文件名" ) 便可加載該文件
np.loadtxt("arr_from_text.txt")
array([[1., 2., 3.],
[4., 5., 6.]])
另外,假設咱們已經在 arr_from_csv 的 csv 文件裏寫進去了 [[1,2,3], [4,5,6]],每行的元素是由「分號 ;」來分隔的,展現以下:
用 np.genfromtxt( "文件名" ) 便可加載該文件
np.genfromtxt("arr_from_csv.csv")
array([nan, nan])
奇怪的是數組裏面都是 nan,緣由是沒有設定好「分隔符 ;」,那麼函數 genfromtxt 讀取的兩個元素是
1;2;3
4;5;6
它們固然不是數字拉,Numpy 只能用兩個 nan (Not a Number) 來表明上面的四不像了。
帶上「分隔符 ;」再用 np.genfromtxt( "文件名",分隔符 ) 便可加載該文件
np.genfromtxt("arr_from_csv.csv", delimiter=";")
array([[1., 2., 3.],
[4., 5., 6.]])
獲取數組是經過索引 (indexing) 和切片 (slicing) 來完成的,
切片是獲取一段特定位置的元素
索引是獲取一個特定位置的元素
索引和切片的方式和列表如出一轍,參考 Python 入門篇 (上) 的 2.3 節。對於一維數組 arr,
切片寫法是 arr[start : stop : step]
索引寫法是 arr[index]
所以,切片的操做是能夠用索引操做來實現的 (一個一個總能湊成一段),只是不必罷了。爲了簡化,咱們在本章三節標題裏把切片和索引都叫作索引。
索引數組有三種形式,正規索引 (normal indexing)、布爾索引 (boolean indexing) 和花式索引 (fancy indexing)。
3.1
正規索引
雖然切片操做能夠由屢次索引操做替代,但二者最大的區別在於
切片獲得的是原數組的一個視圖 (view) ,修改切片中的內容會改變原數組
索引獲得的是原數組的一個複製 (copy),修改索引中的內容不會改變原數組
請看下面一維數組的例子來講明上述二者的不一樣。
arr = np.arange(10)arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
用 arr[6] 索引第 7 個元素 (記住 Python 是從 0 開始記錄位置的)
arr[6]
6
把它賦給變量 a,並從新給 a 賦值 1000,可是元數組 arr 第 7 個元素的值仍是 6,並無改爲 1000。
a = arr[6]a = 1000arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
用 arr[5:8] 切片第 6 到 8 元素 (記住 Python 切片包頭不包尾)
arr[5:8]
array([5, 6, 7])
把它賦給變量 b,並從新給 b 的第二個元素賦值 12,再看發現元數組 arr 第 7 個元素的值已經變成 12 了。
b = arr[5:8]b[1] = 12arr
array([ 0, 1, 2, 3, 4, 5, 12, 7, 8, 9])
這就證明了切片獲得原數組的視圖 (view),更改切片數據會更改原數組,而索引獲得原數組的複製 (copy), 更改索引數據不會更改原數組。但願用下面一張圖能夠明晰 view 和 copy 的關係。
瞭解完一維數組的切片和索引,類比到二維和多維數組上很是簡單。
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])arr2d
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
狀況一:用 arr2d[2] 來索引第三行,更嚴格的說法是索引「軸 0」上的第三個元素。
arr2d[2]
array([7, 8, 9])
狀況二:用 arr2d[0][2] 來索引第一行第三列
arr2d[0][2]
3
索引二維數組打了兩個中括號好麻煩,索引五維數組不是要打了五個中括號?還有一個簡易方法,用 arr2d[0, 2] 也能夠索引第一行第三列
arr2d[0,2]
3
狀況一:用 arr2d[:2] 切片前兩行,更嚴格的說法是索引「軸 0」上的前兩個元素。
arr2d[:2]
array([[1, 2, 3],
[4, 5, 6]])
狀況二:用 arr2d[:, [0,2]] 切片第一列和第三列
arr2d[:,[0,2]]
array([[1, 3],
[4, 6],
[7, 9]])
狀況三:用 arr2d[1, :2] 切片第二行的前兩個元素
arr2d[1, :2]
array([4, 5])
狀況四:用 arr2d[:2, 2] 切片第三列的前兩個元素
arr2d[:2, 2]
array([3, 6])
3.2
布爾索引
布爾索引,就是用一個由布爾 (boolean) 類型值組成的數組來選擇元素的方法。
假設咱們有阿里巴巴 (BABA),臉書 (FB) 和京東 (JD) 的
股票代碼 code 數組
股票價格 price 數組:每行記錄一天開盤,最高和收盤價格。
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]])price
array([[170, 177, 169],
[150, 159, 153],
[ 24, 27, 26],
[165, 170, 167],
[ 22, 23, 20],
[155, 161, 157]])
假設咱們想找出 BABA 對應的股價,首先找到 code 裏面是 'BABA' 對應的索引 (布爾索引),即一個值爲 True 和 False 的布爾數組。
code == 'BABA'
array([ True, False, False, True, False, False])
用該索引能夠獲取 BABA 的股價:
price[ code == 'BABA' ]
array([[170, 177, 169],
[165, 170, 167]])
用該索引還能夠獲取 BABA 的最高和收盤價格:
price[ code == 'BABA', 1: ]
array([[177, 169],
[170, 167]])
再試試獲取 JD 和 FB 的股價:
price[ (code == 'FB')|(code == 'JD') ]
array([[150, 159, 153],
[ 24, 27, 26],
[ 22, 23, 20],
[155, 161, 157]])
雖然下面操做沒有實際意義,試試把股價小於 25 的清零。
price[ price < 25 ] = 0price
array([[170, 177, 169],
[150, 159, 153],
[ 0, 27, 26],
[165, 170, 167],
[ 0, 0, 0],
[155, 161, 157]])
注:這種布爾索引的操做在 Pandas 更經常使用也更方便,看完 pandas 那帖後就能夠忽略這一節了。
3.3
花式索引
花式索引是獲取數組中想要的特定元素的有效方法。考慮下面數組:
arr = np.arange(32).reshape(8,4)arr
array([[ 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]])
假設你想按特定順序來獲取第 5, 4 和 7 行時,用 arr[ [4,3,6] ]
arr[
array([[16, 17, 18, 19],
[12, 13, 14, 15],
[24, 25, 26, 27]])
假設你想按特定順序來獲取倒數第 4, 3 和 6 行時 (即正數第 4, 5 和 2 行),用 arr[ [-4,-3,-6] ]
arr[
array([[16, 17, 18, 19],
[20, 21, 22, 23],
[ 8, 9, 10, 11]])
此外,你還能更靈活的設定「行」和「列」中不一樣的索引,以下
arr[ [1,5,7,2], [0,3,1,2] ]
array([ 4, 23, 29, 10])
檢查一下,上行代碼獲取的分別是第二行第一列、第六行第四列、第八行第二列、第三行第三列的元素,它們確實是 4, 23, 29 和 10。若是不用花式索引,就要寫下面繁瑣但等價的代碼:
np.array( [ arr[1,0], arr[5,3], arr[7,1], arr[2,2] ] )
array([ 4, 23, 29, 10])
最後,咱們能夠把交換列,把原先的 [0,1,2,3] 的列換成 [0,3,1,2]。
arr[:,[0,3,1,2]]
array([[ 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]])
本帖討論了 NumPy 的前三節,數組建立、數組存載和數組獲取。一樣把 numpy 數組當成一個對象,要學習它,無非就是學習怎麼
建立它:按步就班法、定隔定點法、一步登天法
存載它:保存成 .npy, .txt 和 .csv 格式,下次加載即用
獲取它:一段用切片,一個用索引;有正規法、布爾法、花式法
等等,你好像還沒教什麼 numpy 數組硬核的東西呢,下帖討論 NumPy 的後兩節就教怎麼
變形它:重塑和打平,合併和分裂,元素重複和數組重複
計算它:元素層面計算,線性代數計算,廣播機制計算
回到引言的「數組轉置」問題:
arr = np.arange(16).reshape((2, 2, 4))arr
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
將第 1, 2, 3 維度轉置到第 2, 1, 3 維度,即將軸 0, 1, 2 轉置到軸 1, 0, 2。
解答:
數組轉置的本質:交換每一個軸 (axis) 的形狀 (shape) 和跨度 (stride)。
四幅圖解決問題:
用代碼驗證一下:
arr.transpose(1,0,2)
array([[[ 0, 1, 2, 3],
[ 8, 9, 10, 11]],
[[ 4, 5, 6, 7],
[12, 13, 14, 15]]])
歐了!下篇討論 NumPy 系列的「數組的變性」和「數組的計算」。Stay Tuned!
本文分享自微信公衆號 - Python與算法之美(Python_Ai_Road)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。