1. 程式人生 > >手把手numpy教程【二】——陣列與切片

手把手numpy教程【二】——陣列與切片

本文始發於個人公眾號:**TechFlow**,原創不易,求個關注

今天是Numpy專題的第二篇,我們來進入正題,來看看Numpy的運算。

上一篇文章當中曾經提到過,同樣大小的資料,使用Numpy的運算速度會是我們自己寫迴圈來計算的上百倍甚至更多。並且Numpy的API非常簡單,通常只要簡單幾行程式碼就可以完成非常複雜的操作。

計算與廣播

在Python中的陣列無論是什麼型別,我們是無法直接對其中所有的元素進行計算的。想要做到這一點,必須要通過map這樣的方式操作。而Numpy當中,我們可以很方便地對一整個陣列或者是矩陣進行各式的計算。

首先,我們先定義一個Numpy的陣列:

arr = np.array([[1,2,3],[2,2,3]])
image-20200516161939969

首先而我們來看一下基本的四則運算:

image-20200516162021455

這張圖中我們可以看出兩點,首先是Numpy當中的陣列過載了四則運算子,我們可以直接通過加減乘除進行計算。第二點是Numpy自動替我們做了對映,雖然我們運算操作的物件是陣列本身,但是Numpy自動替我們對映到了其中的每一個元素。

如果你不喜歡直接運算,想要使用Numpy的api進行呼叫,也是一樣可以的。Numpy當中也為加減乘除提供了api。

image-20200516162427254

我們甚至還可以比較兩個陣列的大小,得到的結果是一個bool型的陣列,代表其中每一個元素的大小關係。

image-20200516162534018

除了列舉的這些之外,Numpy當中還提供了許多其他的api來進行各種計算,幾乎囊括了所有常見的數學計算公式。比如log、exp、pow、開方、三角函式等等計算,基本上api的名稱和math當中的一樣,大家也沒有必要都記住,基本上可以根據英文猜出來,一般來說記住常用的,其他的可以等到使用的時候再查閱。

廣播

理解了Numpy中的基本操作之後,接下來要介紹一個非常重要的概念,叫做廣播。如果這個概念理解不到位,那麼後來在使用的過程當中,會遇到很多頭疼的問題,或者是總是看不懂別人的程式碼。

廣播的英文叫做broadcasting,這個思想應用的範圍很廣,比如分散式訊息中介軟體等很多領域都有化用。在Numpy計算當中,廣播指的是將一個小的資料應用在大資料的計算上。這個概念其實很形象,我們來看個例子。

比如我們想要對Numpy中的陣列每一位的元素都加上3,我們當然可以創造出一個同樣大小的陣列來,然後再把它們相加。但是大可不必這麼麻煩,我們直接用原陣列加上3即可,Numpy內部會發現3和我們的資料大小不一致,然後自動幫我們把3拓充到和我們的資料一樣大小的陣列再進行計算:

image-20200516162846508

它其實等價於:

np.full_like(arr, 3) + arr

如果你能理解了上面這個操作,那麼同樣的,我們要對所有的元素平方或者是開方也都不在話下了:

image-20200516163141477

廣播並不是只可以用在陣列和一個整數之間,還可以用在陣列和另外一個規模更小的陣列當中,但是會對兩者的shape有所要求。Numpy規定,兩個陣列的shape必須相等或者其中一個為1才可以執行廣播操作。

比如說剛才我們建立的arry陣列的shape是(3, 2),我們可以讓它和一個大小是(1, 2)或者是(3, 1)的“小陣列”進行運算,這同樣是支援的。

如果你看不明白上面的計算過程, 我下面用一張圖做一下演示。

從圖中可以看到左邊的陣列shape是(2, 3),右邊的陣列shape是(2, 1),滿足Numpy對於廣播機制的要求。Numpy會自動對右邊陣列shape為1的維度進行廣播,也就是將它複製若干份使得它們的shape相等。如果你把左邊的陣列看成是若干個聽廣播的人,右側的陣列看成是訊息的話,那麼廣播機制就是把訊息複製若干份,讓每一個聽廣播的人聽到同樣的內容。所以這個名字還是很形象的。

切片

Python中陣列為人稱道的很重要的一點就是它的切片操作非常方便,Numpy作為依託於Python的計算包,自然也繼承了這一點,所以在Numpy當中,我們也可以很方便地使用切片功能。切片的使用方法和Python基本是一樣的。

我們用上下標加上冒號來表示我們想要切片的範圍, 和Python一樣,這是一個左閉右開的區間。

我們也可以省略其中的一個範圍,只提供上界或者是下界:

image-20200516165031942

我們還可以上下界都省略,表示全部都要,以及倒序切片的方法也和Python是一樣的。

image-20200516165127699

但是有一點不太一樣,Numpy中的切片和golang中的切片比較像,它代表原陣列一段區間的引用,而不是拷貝。也就是說我們修改切片中的內容是會影響原陣列的,我們對一個切片賦值,明顯可以發現原陣列的對應位置發生了改變。

image-20200516165245162

這麼設計的原因和golang是一樣的,因為Numpy是為了大資料計算而誕生的,大資料計算顯然效能是一個非常重要的考量指標。如果這裡不是設計成引用,而是拷貝的話,那麼當一個大的切片產生的時候,必然會涉及到大量拷貝的操作。不僅非常消耗記憶體,並且也會佔用大量計算資源。如果使用引用可以非常快速地返回結果。

golang當中如此設計,也是一樣的道理。

那問題來了,如果我們想要拷貝出一份切片出來,而不是獲得一個切片應該怎麼辦?答案也很簡單,我們可以呼叫copy方法,獲取一份拷貝。

arr[3:10].copy()

索引

理解了切片的用法之後,我們接下來看看索引。索引也是Numpy當中非常重要的概念,應用也非常普遍。

Numpy當中的索引對應陣列中的維度,比如一個二維的陣列,當我們用下標訪問的時候,獲得的其實是一個一維的陣列。所以如果我們想要訪問一個具體的元素的時候,能做的就是繼續往下指定下標:

image-20200516171154055

這個很好理解,和Python當中的多維陣列的用法是一樣的。上面我們用了兩個方括號去鎖定一個元素的位置,為了寫起來方便,我們還可以用逗號分隔查詢。友情提醒,Python原生的陣列並不支援這樣的操作,不要搞混哦。

同樣的道理,如果是多維的陣列也是一樣的,我們依次寫出從0到k維的座標來獲取一個固定的元素。如果我們給出的座標資訊較少,那麼則會獲得一個數組。

拿3維陣列舉例,如果我們訪問的時候只用一個下標,那麼我們獲得的是一個二維陣列。如果使用兩個下標,則獲得的是一個一維陣列。對於更高的維度也是同樣。

結尾

今天的文章我們一起了解了Numpy當中常見的計算api以及廣播和索引機制,關於索引的使用今天只是開了個頭,還有很多非常靈活的用法,由於篇幅的限制,我們分成了多篇文章,會在之後的文章當中一一介紹。

今天介紹的也是Numpy的基礎內容,除了廣播機制稍稍需要思考一下之外,其餘的應該都非常簡單,我相信大家都能看明白。Numpy之所以普及,除了速度快之外,api簡單易用,學習成本低也是很大的特點。

關注我,獲取更多精彩文章。

![](https://user-gold-cdn.xitu.io/2020/5/18/172253008a83371c?w=258&h=258&f=png&