Python.numpy極簡入門

Numpy庫一直在用,但從沒有去了解過numpy到底是個什麼東西,屬於知其然但不知其所以然的境界,雖然也沒什麼大礙,但今天看到某本書裏有介紹,看了一下,覺得還不錯,可以算是個簡單入門吧,所以依照書上的框架複述一遍,寫了這篇博文。

目錄

1. Numpy簡介

2. ndarray對象

2.1 數組的創建

2.2 數組的形狀獲取和改變

2.3 數組元素類型的指定

3. ufunc對象

3.1 ufunc示例-sin()

3.2 性能比較

3.3 ufunc示例-add()

3.4 ufunc的廣播機制

3.5 ogrid對象


1. Numpy簡介

Numpy爲Python帶來了多維數組功能,並提供豐富的函數庫來處理這些數組。他將常用的數學函數進行了數組化,使得這些數學函數能夠直接對數組進行操作,將本來需要在Python級別進行的循環,放到C語言的運算中,明顯的提高了程序的運行速度。這點是很重要的,因爲Python最大的每種不足就是性能問題。

在Python中自帶有list(列表)對象,用來保存一組“值”,我們可以將list近似的當作數組來使用,但是列表中的元素可以是任何對象,比如['a',0.01,{"age":18}],因此列表中所保存的是對象的指針,這樣爲了保存一個簡單的[1,2,3],需要3個指針和三個整數對象,在時間和空間上都造成了極大浪費。

爲了克服list的弊端,就可以使用Numpy庫所帶來的數組。

Numpy庫提供了兩種基本的對象,一是ndarray(N-dimensional array object)是存儲單一數據類型的多維數組二是ufunc(universal function object)是能夠對數組進行處理的函數

2. ndarray對象

2.1 數組的創建

通過給array函數傳遞Python的序列對象來創建數組,如果傳遞的是多層嵌套序列,將創建多維數組。示例如下:

import numpy as np

a = np.array( [1,2,3,4] )
b = np.array( (5,6,7,8) )
c = np.array( [[1,2,3,4],[4,5,6,7],[7,8,9,10]] )

print(a) #[1 2 3 4]
print(b)  #[5 6 7 8]
print(c)
'''
[[ 1  2  3  4]
 [ 4  5  6  7]
 [ 7  8  9 10]]
'''

2.2 數組的形狀獲取和改變

數組的大小可以通過數組的shape屬性獲得。示例如下:

#緊接上面的代碼
print(a.shape) #(4,)
print(c.shape) #(3, 4)

數組a的shape只有一個元素,因此可以判定爲一維數組,而數組c的第0軸長度爲3,第1軸的長度爲4,是3*4的數組。

可以通過修改數組的shape屬性,在保持元素個數不變的情況下,改變數組每個軸的長度。相當於做了重排。

c.shape=4,3
print(c)
'''
[[ 1  2  3]
 [ 4  4  5]
 [ 6  7  7]
 [ 8  9 10]]
'''

當某個軸的元素爲-1時,將根據元素的個數自動計算此軸的長度,因此下面的程序將c的shape改爲了(2,6)。

c.shape=2,-1
print(c)
'''
[[ 1  2  3  4  4  5]
 [ 6  7  7  8  9 10]]
'''

使用數組的reshape方法,可以創建一個改變了尺寸的新數組,原數組的shape保持不變

此處要注意的是,reshape方法和shape屬性的區別。

d=a.reshape((2,2))
e=a.reshape(2,2)  #兩種方式都可以
print(d)
print(e)
print(a)
'''
[[1 2]
 [3 4]]

[[1 2]
 [3 4]]

[1 2 3 4]
'''

數組a和數組b,共享數據存儲內存區域,因此修改其中任何一個數組的元素都會同時修改另一個。

print(a) #[1 2 3 4]
a[1]=100 #將數組a的第二個元素給爲100
print(a)  #[  1 100   3   4]
print(d)
'''
[[  1 100]   數組d中也被改變了
 [  3   4]]
'''

2.3 數組元素類型的指定

數組內部元素的類型可以通過dtype屬性獲得。上面例子中,參數序列的元素都是整數類型,因此數組內元素類型也是整形。還可以在具體創建數組時,顯示地指定元素類型,通過dtype參數。

temp01=np.array( [[1,2,3,4],[4,5,6,7],[7,8,9,10]],dtype=np.float)
print(temp01)  #浮點數類型
'''
[[ 1.  2.  3.  4.]
 [ 4.  5.  6.  7.]
 [ 7.  8.  9. 10.]]
'''
temp02=np.array( [[1,2,3,4],[4,5,6,7],[7,8,9,10]],dtype=np.complex)
print(temp02)  #複數類型
'''
[[ 1.+0.j  2.+0.j  3.+0.j  4.+0.j]
 [ 4.+0.j  5.+0.j  6.+0.j  7.+0.j]
 [ 7.+0.j  8.+0.j  9.+0.j 10.+0.j]]
'''

array方法裏面的參數是一個Python序列,也就是說,是先創建了一個Python序列,然後用array函數將其轉換爲數組。這樣效率不高,Numpy還提供了,許多特定的方法來創建數組,例如linspace。

3. ufunc對象

Python裏面提供了許多universal function ,可以直接對數組的每個元素都進行操作。並且由於Numpy內置的許多ufunc函數都是基於C語言編寫的,因此有較好的性能。

3.1 ufunc示例-sin()

import numpy as np
x=np.linspace(0,2*np.pi,10)
print(type(x))  #<class 'numpy.ndarray'> x是數組類型
print(x)
'''
[0.         0.6981317  1.3962634  2.0943951  2.7925268  3.4906585
 4.1887902  4.88692191 5.58505361 6.28318531]
'''
y=np.sin(x)
print(type(y))  #<class 'numpy.ndarray'>  結果y也是數組類型
print(y)
'''
[ 0.00000000e+00  6.42787610e-01  9.84807753e-01  8.66025404e-01
  3.42020143e-01 -3.42020143e-01 -8.66025404e-01 -9.84807753e-01
 -6.42787610e-01 -2.44929360e-16]
'''

3.2 性能比較

用下面的程序比較numpy.sin和math.sin的計算速度。

import time
import math

import numpy as np
x = [i * 0.001 for i in range(10000000)]
start = time.clock()
for i,t in enumerate(x):
    x[i] = math.sin(t)
print("math.sin: ",time.clock() - start)
#math.sin:  2.5702852179138964

x = [i * 0.001 for i in range(10000000)]
x=np.array(x)
start = time.clock()
t=np.sin(x)
print("numpy.sin: ",time.clock() - start)

#math.sin:  2.526883316363891
#numpy.sin:  0.10765761889063619

運行了10000000次正弦運算,numpy.sin速度比math.sin的速度快上一個數量級,這是因爲numpy.sin可以一次性對一個數組進行計算。也就是說,numpy.sin是在C語言級別上進行循環的,而math.sin是在Python級別進行循環,衆所周知,Python的一大弊端就是性能不佳,因此numpy.sin的運行速度自然遠勝math.sin。

當然numpy.sin同樣也支持對單個數值求正弦。

3.3 ufunc示例-add()

import numpy as np
a=np.arange(0,4)
print(a,type(a))  #[0 1 2 3] <class 'numpy.ndarray'>
#arange([start,] stop[, step,], dtype=None)根據start與stop指定的範圍以及step設定的步長,生成一個 ndarray
b=np.arange(1,5)
print(b,type(b))
c=np.add(a,b)
print(c)  #[1 3 5 7]

3.4 ufunc的廣播機制

使用ufunc函數對兩個數組進行計算時,ufunc函數會對這兩個數組的對應元素進行計算,因此要求着兩個函數的形狀相同。如果形狀不同,會進行如下廣播處理:

1).讓所有輸入數組都向其中維數最多的數組看齊,shape屬性中不足的部分通過在前面加1補齊。

2).輸出數組的shape屬性是輸入數組的shape屬性在各個軸上的最大值。

3).如果輸入數組的某個軸長度爲1或與輸出數組對應軸的長度相等,這個數組就能夠用來計算,否則出錯。

4).當輸入數組的某個軸長度爲1時,沿着這條軸運算時,都用此軸上的第一組值。

示例如下:

首先創建形狀爲(6,1)的二維數組a:

import numpy as np
a=np.arange(0,60,10).reshape(-1,1)
print(a)
"""
[[ 0]
 [10]
 [20]
 [30]
 [40]
 [50]]
"""
print(a.shape)  #(6, 1)

再創建形狀爲(5,)的一維數組b。

b=np.arange(0,5)
print(b)  #[0 1 2 3 4]
print(b.shape)  #(5,)

計算數組a和b的和,得到一個加法表,相當於計算兩個數組中所有元素組的和,得到形狀爲(6,5)的數組。

c=a+b
print(c.shape)  #(6, 5)
print(c)
'''
[[ 0  1  2  3  4]
 [10 11 12 13 14]
 [20 21 22 23 24]
 [30 31 32 33 34]
 [40 41 42 43 44]
 [50 51 52 53 54]]
'''

a是2維數組,b是1維數組,根據規則1),需要讓數組b的shape屬性向數組a對齊,在數組b的shape屬性前面+1,補齊後爲(1,5),相當於做如下運算。

b.shape=1,5
print(b)  #[[0 1 2 3 4]]

這樣一來,做加法運算的兩個輸入數組的shape屬性分別爲(6,1)和(1,5),根據規則2),可知輸出數組的shape屬性爲(6,5)。由於數組b的第0軸長度爲1,而數組a的第0軸長度爲6,因此,爲了能夠讓他們在第0軸上相加,需要將數組b第0軸的長度拓展爲6,這相當於:

b=b.repeat(6,axis=0)
print(b)
'''
[[0 1 2 3 4]
 [0 1 2 3 4]
 [0 1 2 3 4]
 [0 1 2 3 4]
 [0 1 2 3 4]
 [0 1 2 3 4]]
'''

#同理把a的第1軸拓展爲5
a = a.repeat(5,axis=1)
print(a)
"""
[[ 0  0  0  0  0]
 [10 10 10 10 10]
 [20 20 20 20 20]
 [30 30 30 30 30]
 [40 40 40 40 40]
 [50 50 50 50 50]]
"""

經過上述處理後,數組a和數組b就可以按其對應元素進行相加運算。當然,在執行“a+b"時,Numpy內部並不會真正將長度爲1的軸用repeat()進行拓展,這樣太浪費空間。由於這樣的廣播計算很常用,Numpy提供了ogrid對象,用以快速產生能進行廣播運算的數組。

3.5 ogrid對象

x,y=np.ogrid[0:5,0:5]
print(x)
"""
[[0]
 [1]
 [2]
 [3]
 [4]]
"""
print(y)
"""
[[0]
 [1]
 [2]
 [3]
 [4]]
"""

ogrid對象和多維數組一樣,用切片元組作爲下標,返回的是一組可以用來廣播計算的數組。其切片下標有兩種形式:

開始值,結束值,步長和“np.arange(開始值,結束值,步長)”類似。

開始值,結束值,長度j,當第三個參數爲虛數時,他表示所返回數組的長度,其和np.linspace(開始值,結束值,長度)”類似。

x,y=np.ogrid[0:1:4j,0:1:3j]
print(x)
"""
[[0.        ]
 [0.33333333]
 [0.66666667]
 [1.        ]]
"""
print(y)
"""
[[0.  0.5 1. ]]
"""

本文完。如果錯誤,歡迎指出。如有想法,歡迎交流。但不接受批評,畢竟沒有錢拿。

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