iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 26
0
Software Development

從零開始學Python系列 第 26

[Day 26] 從零開始學Python - 科學運算NumPy:人間用多少滄桑,換多少人的瘋狂

註:本文同步刊載在Medium,若習慣Medium的話亦可去那邊看呦!

我們今天要來介紹一下NumPy。
一般來說,讀者可能常常會聽到有些人說Python的執行效率相對比較低,
這是真的,因為動態語言會比妥善先編譯過的語言執行速度還慢,
因而我們會看到在像是LeetCode的答案繳交時,
用C/C++/Java可能是執行5 ms(毫秒)的時間,
用Python寫相同邏輯的答案可能要30~100ms。
既然這樣,為什麼現在Python會成為機器學習或AI發展的主流語言呢?
原因就是Python有很多方便的程式庫,
尤其科學運算方面,有不少經過最佳化以及妥善編譯後的程式庫,
使用起來的效率相當的好,NumPy就是其中一個。

通常狀況下NumPy會搭配到後面的Matplotlib及Keras(TensorFlow),
用來輸入大量的矩陣及陣列,可以迅速進行運算。

如果讀者沒有使用其他如Anaconda的方式安裝的話,
要安裝numpy最簡單的方式就是使用pip。

# 一般安裝指令
pip install numpy
# 如果發生安裝不起來的狀況,可以嘗試看看使用--user
pip install --user

我們先來介紹NumPy的最基本單位:陣列。
在Python中我們一般使用串列,也有陣列的模組,但一般不會用。
NumPy的陣列叫做ndarray,
它是一系列的相同資料型態的元素組成的,和串列不同,
串列可以這個放字串,另一個放int,但ndarray不允許這麼做喲!
nd是n-dimension(n維)的意思,
Numpy會將維度稱為rank。
單維的陣列就像一個row一樣;
二維的話就像Excel的表格有row/column;
三維就會像一個立方體(但不一定要正立方體)!

ndarray這麼設定是因為,
當我們在計算資料時,會用到一些矩陣運算或一些其他的科學運算方式,
在這當中會容易同時處理到多個資料,
因此使用比較方整的格式及固定資料型態,
可以讓配置每個元素的記憶體大小比較固定,
同時也便於取用和運算。
(所以會比list和Tuple等效率來得好)

接下來我們簡單介紹一下numpy的幾個基本指令:
np.array():產生一個ndarray
ndim維度數(rank)
shape:每個維度的元素個數(以Tuple型式呈現)
size總個數

>>> import numpy as np # 大家習慣的縮寫方式
>>> a = np.array([1, 5, 3, -1]) # 一維陣列
>>> a
array([1,  5,  3, -1])
>>> a.ndim # rank
1
>>> a.size # 陣列的元素總個數
4
>>> a.shape # 回傳每個rank有幾個值
(4,)
>>> b = np.array([[1, 3, 2, 4], [5, 4, 3]]) # 長度要符合阿大大!
<stdin>:1: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray
>>> b = np.array([[1, 3, 2, 4], [5, 4, 3, 3]]) # 正確的二維陣列
>>> b.ndim, b.size, b.shape # 所以我們會看到一個二維陣列,總數8個,2個rows,4個columns
(2, 8, (2, 4))

再來我們介紹另一個產生陣列的方式arange():
arange基本上很像range()的用法,
舉例來說:
np.arange(10) -> 生成一個一維陣列,元素從0~9
np.arange(5, 10) -> 生成一個一維陣列,元素從5~9
np.arange(5, 11, 2) -> 生成一個一維陣列,元素為5, 7, 9
此外,還有比較特別的是,它可以接受帶小數點的輸入。

>>> np.arange(2, 8.8, 1.3)
array([2. , 3.3, 4.6, 5.9, 7.2, 8.5])

除此以外,也可以用以下幾個來初始化陣列:
zeros() -> 建立全是0的陣列

>>> import numpy as np
>>> np.zeros((3))
array([0., 0., 0.])
>>> np.zeros((3,)) # 因為傳入是Tuple,所以多寫一個逗號主要是提醒不要把外面的括號省略
array([0., 0., 0.])
>>> np.zeros((1, 5))
array([[0., 0., 0., 0., 0.]])
>>> np.zeros((3, 4))
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])

ones() -> 建立全是1的陣列
(範例就不浪費大家字數了,意思一樣)

random.random() -> 從0.0~1.0的隨機值來建立一個陣列(每個元素都是獨立的隨機值)

>>> np.random.random((2,6))
array([[0.01287523, 0.53233704, 0.79855996, 0.74661588, 0.53556373,
        0.01158943],
       [0.69197322, 0.90871892, 0.24175071, 0.73861581, 0.90259941,
        0.74164385]])

np.random這個系列底下其實也有很多常用的東西,
比如說如果我們想要一個最常見的常態分布的陣列,
我們可以使用random.normal(),
用法是np.random.normal(平均值, 標準差, size)

>>> mu, sigma = 0, 0.2
>>> c = np.random.normal(mu, sigma, (2, 8))
>>> c
array([[ 0.00521883, -0.04220576,  0.00590997, -0.06231083,  0.07088372,
         0.16073115,  0.30269475, -0.02136197],
       [-0.2599142 , -0.03247713, -0.00156353, -0.43750613, -0.01930995,
        -0.00546659,  0.18611712,  0.12076204]])

後續怎麼畫出來呢?讓我們留待到下一篇再告訴大家吧XD!

在介紹完初始化陣列後,
我們來講一個numpy的特異功能:reshape()
reshape顧名思義,就是將陣列重新捏成你想要的形狀。
比如假設我們做了一個一維的陣列,其size為9。

>>> s = np.random.normal(mu, sigma, 9)
>>> s
array([ 0.04298177, -0.14931128,  0.01522315,  0.10384426,  0.06392715,
       -0.02892813,  0.22175294,  0.06630997, -0.09279769])

使用reshape的方式,就是直接指定維度形狀,
只要reshape前後的size算起來是一致即可。

>>> s.reshape(3, 3) # reshape完後若沒有回頭存起來,並不會修改到s呦!
array([[ 0.04298177, -0.14931128,  0.01522315],
       [ 0.10384426,  0.06392715, -0.02892813],
       [ 0.22175294,  0.06630997, -0.09279769]])
>>> s.reshape(1, 9)
array([[ 0.04298177, -0.14931128,  0.01522315,  0.10384426,  0.06392715,
        -0.02892813,  0.22175294,  0.06630997, -0.09279769]])
>>> s.reshape(9, 1)
array([[ 0.04298177],
       [-0.14931128],
       [ 0.01522315],
       [ 0.10384426],
       [ 0.06392715],
       [-0.02892813],
       [ 0.22175294],
       [ 0.06630997],
       [-0.09279769]])
>>> s.shape = (3, 3) # 直接對shape使用Tuple指定也可以呦!
>>> s
array([[ 0.04298177, -0.14931128,  0.01522315],
       [ 0.10384426,  0.06392715, -0.02892813],
       [ 0.22175294,  0.06630997, -0.09279769]])

在取得單一或範圍元素的時候,
陣列基本上跟串列蠻像的:

>>> d = np.arange(24)
>>> d
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])
>>> d[7]
7
>>> d[-1]
23
>>> d[10:18]
array([10, 11, 12, 13, 14, 15, 16, 17])
>>> d[10:18:2]
array([10, 12, 14, 16])

但如果是多維陣列的話,則必須要用逗號分隔不同的維度:
(Python的串列則是用中括號分隔)

>>> d.shape = 3,2,4
>>> d
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]]])
>>> d[1]
array([[ 8,  9, 10, 11],
       [12, 13, 14, 15]])
>>> d[1:,1, 3]
array([15, 23])
>>> d[1:,1, 3:]
array([[15],
       [23]])
>>> d[1, 1]
array([12, 13, 14, 15])
>>> d[1, 1, 2:]
array([14, 15])

另一個功能是可以將值給定到陣列指定的範圍:

>>> d[1, 1, 2:] = 99999 # 將指定範圍的值全數換成99999
>>> d
array([[[    0,     1,     2,     3],
        [    4,     5,     6,     7]],

       [[    8,     9,    10,    11],
        [   12,    13, 99999, 99999]],

       [[   16,    17,    18,    19],
        [   20,    21,    22,    23]]])

在陣列計算時,我們使用陣列對數字加減乘除時,
可以將其一口氣全部作相同的事情:

>>> d + 1
array([[[     1,      2,      3,      4],
        [     5,      6,      7,      8]],

       [[     9,     10,     11,     12],
        [    13,     14, 100000, 100000]],

       [[    17,     18,     19,     20],
        [    21,     22,     23,     24]]])
>>> d * 3 + 2
array([[[     2,      5,      8,     11],
        [    14,     17,     20,     23]],

       [[    26,     29,     32,     35],
        [    38,     41, 299999, 299999]],

       [[    50,     53,     56,     59],
        [    62,     65,     68,     71]]])
>>> d / 3 + 2
array([[[2.00000000e+00, 2.33333333e+00, 2.66666667e+00, 3.00000000e+00],
        [3.33333333e+00, 3.66666667e+00, 4.00000000e+00, 4.33333333e+00]],

       [[4.66666667e+00, 5.00000000e+00, 5.33333333e+00, 5.66666667e+00],
        [6.00000000e+00, 6.33333333e+00, 3.33350000e+04, 3.33350000e+04]],

       [[7.33333333e+00, 7.66666667e+00, 8.00000000e+00, 8.33333333e+00],
        [8.66666667e+00, 9.00000000e+00, 9.33333333e+00, 9.66666667e+00]]])

除此以外,常用的矩陣運算numpy也都有所支援,
舉例來說,假設想要算兩個二維矩陣的相乘(內積),
可以使用np.dot(x, y)或x.dot(y):

>>> x = np.arange(1, 5)
>>> x.shape=2,2
>>> y = np.arange(5, 9)
>>> y.shape=2,2
>>> x
array([[1, 2],
       [3, 4]])
>>> y
array([[5, 6],
       [7, 8]])
>>> x.dot(y)
array([[19, 22],
       [43, 50]])
>>> np.dot(x,y) # 跟x.dot(y)意思是一樣的
array([[19, 22],
       [43, 50]])

(註:如果是算外積的話,則要用outer())

如果是想要對應的位置兩兩相乘,則要用np.multiply():

>>> np.multiply(x, y)
array([[ 5, 12],
       [21, 32]])

以上簡單介紹了一下numpy的用法,
當然只是蠻簡略的部分,
如果有更多對於詳細函式需求理解的部分,可以直接參考看看numpy的官方文件:
https://numpy.org/doc

那麼,我們就明天見囉!


上一篇
[Day 25] 從零開始學Python - 二元搜尋法模組bisect:我走回從前你往未來飛,遇見對的人錯過交叉點
下一篇
[Day 27] 從零開始學Python - 科學繪圖Matplotlib:畫著你,畫不出你的骨骼
系列文
從零開始學Python30

尚未有邦友留言

立即登入留言