導語:本文受 JayAlammar 的文章“ A Visual Intro to NumPy”的啓發,並對其做了更詳細豐富的介紹。
譯者:AI研習社(季一帆)
雙語原文鏈接:NumPy Illustrated: The Visual Guide to NumPy
NumPy是一個廣泛適用的Python數據處理庫,pandas, OpenCV等庫都基於numpy。同時,在PyTorch、TensorFlow、Keras等深度許欸小框架中,瞭解numpy將顯著提高數據共享和處理能力,甚至無需過多更改就可以在GPU運行計算。
n維數組是NumPy的核心概念,這樣的好處,儘管一維和而爲數組的處理方式有些差異,但多數不同維數組的操作是一樣的。本文將對以下三個部分展開介紹:
向量——一維數組
矩陣——二維數組
3維及更高維數組
本文受JayAlammar的文章“ A Visual Intro to NumPy”的啓發,並對其做了更詳細豐富的介紹。
numpy數組 vs. Python列表
乍看上去,NumPy數組與Python列表極其相似。它們都用來裝載數據,都能夠快速添加或獲取元素,插入和移除元素則比較慢。
當然相比python列表,numpy數組可以直接進行算術運算:
除此之外,numpy數組還具有以下特點:
更緊湊,高維時尤爲明顯
向量化後運算速度比列表更快
在末尾添加元素時不如列表高效
元素類型一般比較固定
其中,O(N)表示完成操作所需的時間與數組大小成正比(請見Big-O Cheat Sheet),O(1)表示操作時間與數組大小無關(詳見Time Complexity)。
1.向量與1維數組
向量初始化
通過Python列表可以創建NumPy數組,如下將列表元素轉化爲一維數組:
注意,確保列表元素類型相同,否則dtype=’object',將影響運算甚至產生語法錯誤。
由於在數組末尾沒有預留空間以快速添加新元素,NumPy數組無法像Python列表那樣增長,因此,通常的做法是在變長Python列表中準備好數據,然後將其轉換爲NumPy數組,或是使用np.zeros或np.empty預先分配必要的空間:
通過以下方法可以創建一個與某一變量形狀一致的空數組:
不止是空數組,通過上述方法還可以將數組填充爲特定值:
在NumPy中,還可以通過單調序列初始化數組:
如果您需要[0., 1., 2.]這樣的浮點數組,可以更改arange輸出的類型,即arange(3).astype(float),但有更好的方法:由於arange函數對類型敏感,因此參數爲整數類型,它生成的也是整數類型,如果輸入float類型arange(3.),則會生成浮點數。
arange浮點類型數據不是非常友好:
上圖中,0.1對我們來說是一個有限的十進制數,但對計算機而言,它是一個二進制無窮小數,必須四捨五入爲一個近似值。因此,將小數作爲arange的步長可能導致一些錯誤。可以通過以下兩種方式避免如上錯誤:一是使間隔末尾落入非整數步數,但這會降低可讀性和可維護性;二是使用linspace,這樣可以避免四捨五入的錯誤影響,並始終生成要求數量的元素。但使用linspace時尤其需要注意最後一個的數量參數設置,由於它計算點數量,而不是間隔數量,因此上圖中數量參數是11,而不是10。
隨機數組的生成如下:
向量索引
對於數組數據的訪問,numpy提供了便捷的訪問方式:
上圖中,除“fancy indexing”外,其他所有索引方法本質上都是views
:它們並不存儲數據,如果原數組在被索引後發生更改,則會反映出原始數組中的更改。
上述所有這些方法都可以改變原始數組,即允許通過分配新值改變原數組的內容。這導致無法通過切片來複制數組:
此外,還可以通過布爾索引從NumPy數組中獲取數據,這意味着可以使用各種邏輯運算符:
注意,不可以使用3 <= a <= 5
這樣的Python“三元”比較。
如上所述,布爾索引是可寫的。如下圖np.where和np.clip兩個專有函數。
向量操作
NumPy的計算速度是其亮點之一,其向量運算操作接近C++級別,避免了Python循環耗時較多的問題。NumPy允許像普通數字一樣操作整個數組:
在python中,a//b表示a div b(除法的商),x**n表示 xⁿ
浮點數的計算也是如此,numpy能夠將標量廣播到數組:
numpy提供了許多數學函數來處理矢量:
向量點乘(內積)和叉乘(外積、向量積)如下:
numpy也提供瞭如下三角函數運算:
數組整體進行四捨五入:
floor向上取整,ceil向下取整,round四捨五入
np.around與np.round是等效的,這樣做只是爲了避免 from numpy import *時與Python aroun的衝突(但一般的使用方式是import numpy as np)。當然,你也可以使用a.round()。
numpy還可以實現以下功能:
以上功能都存在相應的nan-resistant變體:例如nansum,nanmax等
在numpy中,排序函數功能有所閹割:
對於一維數組,可以通過反轉結果來解決reversed函數缺失的不足,但在2維數組中該問題變得棘手。
查找向量中的元素
不同於Python列表,NumPy數組沒有索引方法。
index()中的方括號表示j或i&j可以省略
可以通過np.where(a==x)[0] [0]查找元素,但這種方法很不pythonic,哪怕需要查找的項在數組開頭,該方法也需要遍歷整個數組。
使用Numba實現加速查找,next((i[0] for i, v in np.ndenumerate(a) if v==x), -1),在最壞的情況下,它的速度要比where慢。
如果數組是排好序的,使用v = np.searchsorted(a, x); return v if a[v]==x else -1時間複雜度爲O(log N),但在這之前,排序的時間複雜度爲O(N log N)。
實際上,通過C實現加速搜索並不是困難,問題是浮點數據比較。
浮點數比較
np.[allclose](https://numpy.org/doc/stable/reference/generated/numpy.isclose.html)(a, b)
用於容忍誤差之內的浮點數比較。
np.allclose假定所有比較數字的尺度爲1。如果在納秒級別上,則需要將默認atol參數除以1e9:np.allclose(1e-9,2e-9, atol=1e-17)==False。
math.isclose不對要比較的數字做任何假設,而是需要用戶提供一個合理的abs_tol值(np.allclose默認的atol值1e-8足以滿足小數位數爲1的浮點數比較,即math.isclose(0.1+0.2–0.3, abs_tol=1e-8)==True。
此外,對於絕隊偏差和相對偏差,np.allclose依然存在一些問題。例如,對於某些值a、b, allclose(a,b)!=allclose(b,a),而在math.isclose中則不存在這些問題。查看GitHub上的浮點數據指南和相應的NumPy問題瞭解更多信息。
2.矩陣和二維數組
過去,NumPy中曾有一個專用的matrix類,但現在已被棄用,因此在下文中矩陣和2維數組表示同一含義。
矩陣的初始化語法與向量類似:
如上要使用雙括號,因爲第二個位置參數(可選)是爲dtype(也接受整數)保留的。
隨機矩陣的生成也與向量類似:
二維數組的索引語法要比嵌套列表更方便:
“view”表示數組切片時並未進行任何複製,在修改數組後,相應更改也將反映在切片中。
軸參數
在求和等操作中,NumPy可以實現跨行或跨列的操作。爲了適用任意維數的數組,NumPy引入了axis的概念。axis參數的值實際上就是維度數量,如第一個維是axis=0 ,第二維是axis=1,依此類推。因此,在2維數組中,axis=0指列方向,axis=1指行方向。
矩陣運算
除了+,-,,/,//和*等數組元素的運算符外,numpy提供了@運算符計算矩陣乘積:
類似前文介紹的標量廣播機制,numpy同樣可以通過廣播機制實現向量與矩陣,或兩個向量之間的混合運算:
注意,上圖最後一個示例是對稱的逐元素乘法。使用矩陣乘法@可以計算非對稱線性代數外積,兩個矩陣互換位置後計算內積:
行向量與列向量
根據前文可知,在2維數組中,行向量和列向量被區別對待。通常NumPy會盡可能使用單一類型的1維數組(例如,2維數組a的第j列a[:, j]是1維數組)。默認情況下,一維數組在2維操作中被視爲行向量,因此,將矩陣乘行向量時,使用形狀(n,)或(1,n)的向量結果一致。有多種方法可以從一維數組中得到列向量,但並不包括transpose:
使用newaxis更新數組形狀和索引可以將1維數組轉化爲2維列向量:
其中,-1表示在reshape是該維度自動決定,方括號中的None等同於np.newaxis,表示在指定位置添加一個空軸。
因此,NumPy中共有三種類型的向量:1維數組,2維行向量和2維列向量。以下是兩兩類型轉換圖:
根據廣播規則,一維數組被隱式解釋爲二維行向量,因此通常不必在這兩個數組之間進行轉換,對應圖中陰影化區域。
嚴格來說,除一維外的所有數組的大小都是一個向量(如a.shape == [1,1,1,5,1,1]),因此numpy的輸入類型是任意的,但上述三種最爲常用。可以使用np.reshape將一維矢量轉換爲這種形式,使用np.squeeze可將其恢復。這兩個功能都通過view發揮作用。
矩陣操作
矩陣的拼接有以下兩種方式:
圖示操作僅適用於矩陣堆疊或向量堆疊,而一維數組和矩陣的混合堆疊只有通過vstack纔可實現,hstack會導致維度不匹配錯誤。因爲前文提到將一維數組作爲行向量,而不是列向量。爲此,可以將其轉換爲行向量,或使用專門的column_stack函數執行此操作:
與stack對應的是split:
矩陣複製有兩種方式:tile類似粘貼複製;repeat相當於分頁打印。
delete可以刪除特定的行或列:
相應插入操作爲insert:
與hstack一樣,append函數無法自動轉置1D數組,因此需要重新調整向量形狀或添加維數,或者使用column_stack:
如果僅僅是向數組的邊界添加常量值,pad函數是足夠的:
Meshgrids
廣播機制使得meshgrids變得容易。例如需要下圖所示(但尺寸大得多)的矩陣:
上述兩種方法由於使用了循環,因此都比較慢。MATLAB通過構建meshgrid處理這種問題。
meshgrid函數接受任意一組索引,通過mgrid切片和indices索引生成完整的索引範圍,然後,fromfunction函數根據I和J實現運算。
在NumPy中有一種更好的方法,無需在內存中存儲整個I和J矩陣(雖然meshgrid已足夠優秀,僅存儲對原始向量的引用),僅存儲形狀矢量,然後通過廣播規實現其餘內容的處理:
如果沒有indexing ='ij'參數,那麼meshgrid將更改參數的順序,即J,I=np.meshgrid(j,i)——一種用於可視化3D繪圖的“ xy”模式(祥見該文檔)。
除了在二維或三維網格上初始化函數外,網格還可以用於索引數組:
以上方法在稀疏網格中同樣適用。
矩陣統計
就像sum函數,numpy提供了矩陣不同軸上的[min](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.min.html)/[max](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.max.html)
, [argmin](https://numpy.org/doc/stable/reference/generated/numpy.argmin.html)/[argmax](https://numpy.org/doc/stable/reference/generated/numpy.argmax.html)
, [mean](https://numpy.org/doc/stable/reference/generated/numpy.mean.html)/[median](https://numpy.org/doc/stable/reference/generated/numpy.median.html)/[percentile](https://numpy.org/doc/stable/reference/generated/numpy.percentile.html)
, [std](https://numpy.org/doc/stable/reference/generated/numpy.std.html)/[var](https://numpy.org/doc/stable/reference/generated/numpy.var.html)
等函數。
np.amin等同於np.min,這樣做同樣是爲了避免from numpy import *可能的歧義。
2維及更高維中的argmin和argmax函數分別返回最小和最大值的索引,通過unravel_index函數可以將其轉換爲二維座標:
矩陣排序
雖然在前文中,axis參數適用於不同函數,但在二維數組排序中影響較小:
你通常不需要上述這樣的排序矩陣,axis不是key參數的替代。但好在NumPy提供了其他功能,這些功能允許按一列或幾列進行排序:
1、a[a [:,0] .argsort()]表示按第一列對數組進行排序:
其中,argsort返回排序後的原始數組的索引數組。
可以重複使用該方法,但千萬不要搞混:
a = a[a[:,2].argsort()]
a = a[a[:,1].argsort(kind='stable')]
a = a[a[:,0].argsort(kind='stable')]
2、函數lexsort可以像上述這樣對所有列進行排序,但是它總是按行執行,並且排序的行是顛倒的(即從下到上),其用法如下:
a[np.lexsort(np.flipud(a[2,5].T))],首先按第2列排序,然後按第5列排序;a[np.lexsort(np.flipud(a.T))],從左到右依次排序各列。
其中,flipud沿上下方向翻轉矩陣(沿axis = 0方向,與a [::-1,...]等效,其中...表示“其他所有維度”),注意區分它與fliplr,fliplr用於1維數組。
3、sort函數還有一個order參數,但該方法極不友好,不推薦學習。
4、在pandas中排序也是不錯的選擇,因爲在pandas中操作位置確定,可讀性好且不易出錯:
- pd.DataFrame(a).sort_values(by=[2,5]).to_numpy(),先按第2列排序,再按第5列排序。
-pd.DataFrame(a).sort_values().to_numpy(),按從左到右的順序對所有列進行排序。
3、3維及更高維數組
通過重塑1維向量或轉換嵌套Python列表來創建3維數組時,索引分別對應(z,y,x)。索引z是平面編號,(y,x)座標在該平面上移動:
通過上述索引順序,可以方便的保留灰度圖像,a[i]表示第i個圖像。
但這樣的索引順序並不具有廣泛性,例如在處理RGB圖像時,通常使用(y,x,z)順序:首先是兩個像素座標,然後纔是顏色座標(Matplotlib中的RGB,OpenCV中的BGR):
這樣可以方便地定位特定像素,如a[i,j]給出像素(i,j)的RGB元組。
因此,幾何形狀的創建實際取決於你對域的約定:
顯然,hstack,vstack或dstack之類的NumPy函數並不一定滿足這些約定,其默認的索引順序是(y,x,z),RGB圖像順序如下:
如果數據不是這樣的佈局,使用concatenate命令可以方便的堆疊圖像,並通過axis參數提供索引號:
如果不考慮軸數,可以將數組轉換hstack和相應形式:
這種轉換非常方便,該過程只是混合索引的順序重排,並沒有實際的複製操作。
通過混合索引順序可實現數組轉置,掌握該方法將加深你對3維數據的瞭解。根據確定的軸順序,轉置數組平面的命令有所不同:對於通用數組,交換索引1和2,對於RGB圖像交換0和1:
注意,transpose(a.T)的默認軸參數會顛倒索引順序,這不同於上述述兩種索引順序。
廣播機制同樣適用多維數組,更多詳細信息可參閱筆記“ NumPy中的廣播”。
最後介紹einsum(Einstein summation)函數,這將使你在處理多維數組時避免很多Python循環,代碼更爲簡潔:
該函數對重複索引的數組求和。在一般情況下,使用np.tensordot(a,b,axis=1)就可以,但在更復雜的情況下,einsum速度更快,讀寫更容易。
如果你想看看自己的NumPy水平到底如何,可以在GitHub上進行練習——例如100個NumPy練習。
對於本文未介紹到的NumPy常用功能,歡迎各位讀者通過reddi、hackernews給我留言,我將進一步完善本文!
參考
Scott Sievert, NumPy GPU acceleration
Jay Alammar, A Visual Intro to NumPy and Data Representation
NumPy Issue #14989, Reverse param in ordering functions
NumPy Issue #2269, First nonzero element
NumPy Issue #10161, numpy.isclose vs math.isclose
AI研習社是AI學術青年和AI開發者技術交流的在線社區。我們與高校、學術機構和產業界合作,通過提供學習、實戰和求職服務,爲AI學術青年和開發者的交流互助和職業發展打造一站式平臺,致力成爲中國最大的科技創新人才聚集地。
如果,你也是位熱愛分享的AI愛好者。歡迎與譯站一起,學習新知,分享成長。