跳轉到

1.2 NumPy 陣列基礎#

本節目標#

  • 會用多種方式建立 ndarray,理解 shape 與 dtype
  • 熟練 indexing 與 slicing,看懂 reshape
  • 建立 broadcasting 的直覺,知道為什麼不用 Python list

ndarray:本書的主角#

NumPy 的核心是 ndarray(n-dimensional array,n 維陣列)。 在本書中它扮演兩種角色:

  • 一維陣列 = 向量 \(\mathbf{v} = \begin{bmatrix} 1 & 3 & 2 \end{bmatrix}\)
  • 二維陣列 = 矩陣 \(A\) (每一行可以是一個向量,也可以是一個變換)

建立陣列的常用方法#

import numpy as np

# 1. 從 Python list 建立
v = np.array([1, 2, 3])
A = np.array([[1, 2],
              [3, 4]])

# 2. 全 0、全 1(先給 shape)
z = np.zeros((2, 3))     # 2x3 的零矩陣
o = np.ones(4)           # 長度 4 的全 1 向量

# 3. 等差數列
a = np.arange(0, 10, 2)      # [0 2 4 6 8],同 range
l = np.linspace(0, 1, 5)     # [0.  0.25 0.5 0.75 1.],等分 5 點

print(v, A, z, a, l, sep="\n")

畫函數圖形時 linspace 特別好用:第 3 章起我們會不斷用它產生座標點。

shape 與 dtype:陣列的兩張身分證#

A = np.array([[1.0, 2.0],
              [3.0, 4.0]])

print(A.shape)   # (2, 2)  —— 2 列 2 行
print(A.dtype)   # float64 —— 元素型別
print(A.ndim)    # 2       —— 維度數

注意:shape 是列在前、行在後。一個 \(m \times n\) 矩陣的 shape 是 (m, n)。


indexing 與 slicing#

A = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])

A[0, 0]      # 1      第 0 列第 0 行
A[1]         # [4 5 6] 整個第 1 列
A[:, 2]      # [3 6 9] 整個第 2 行(冒號 = 全部)
A[0:2, 1:3]  # [[2 3]
             #  [5 6]] 子矩陣
A[::-1]      # 列順序倒過來(上下翻轉)

切片是視圖(view):修改切片會改到原陣列,這是 list 沒有的行為。

row = A[0]
row[0] = 99
print(A[0, 0])   # 99 —— 原陣列也被改了!

reshape:不改資料,只改形狀#

v = np.arange(6)          # [0 1 2 3 4 5]
M = v.reshape(2, 3)       # [[0 1 2]
                          #  [3 4 5]]
M2 = v.reshape(3, 2)
print(v.reshape(2, -1))   # -1 = 「你幫我算」→ (2, 3)

reshape 常用來把向量「立起來」變行向量,broadcasting 時很常見:

col = np.arange(3).reshape(3, 1)   # [[0]
                                   #  [1]
                                   #  [2]]

broadcasting:不同形狀也能運算#

Broadcasting 是 NumPy 的魔法:當兩個陣列形狀不同時, NumPy 會自動「複製延伸」較小的那個,讓運算成立。規則:由尾端對齊,每個維度要相等或其中一邊是 1。

例子 1:向量 + 純量#

v = np.array([1, 2, 3])
print(v + 10)        # [11 12 13]

純量 10 被廣播成 [10 10 10]。幾何意義:整個向量平移。

例子 2:矩陣 + 行向量#

A = np.array([[1, 2, 3],
              [4, 5, 6]])
b = np.array([100, 200, 300])   # shape (3,)
print(A + b)
# [[101 202 303]
#  [104 205 306]]

\(b\) 的 shape (3,) 對上 \(A\) 的 shape (2, 3) 尾端,被廣播成兩列。 幾何意義:矩陣裡的每個(列)向量都做同樣的平移。

例子 3:刻意用 reshape 觸發廣播#

c = np.array([10, 20]).reshape(2, 1)   # shape (2, 1)
print(A + c)
# [[ 11  12  13]     ← 第一列 +10
#  [ 24  25  26]]    ← 第二列 +20

第 3 章畫線性組合網格、第 4 章畫變換後的網格,都靠 broadcasting 一次算出幾百個點。


為什麼用 array 不用 list:速度對照小實驗#

list 每個元素是完整 Python 物件,運算要逐個解譯; ndarray 是連續記憶體 + C 語言迴圈,還能平行化。

import numpy as np
import time

N = 10_000_000
py_list = list(range(N))
np_arr = np.arange(N)

t0 = time.time()
py_result = [x * 2 for x in py_list]
t1 = time.time() - t0

t0 = time.time()
np_result = np_arr * 2
t2 = time.time() - t0

print(f"list : {t1:.3f} 秒")
print(f"numpy: {t2:.3f} 秒")
print(f"快了約 {t1 / t2:.0f} 倍")

典型輸出(機器不同略有差異):

list : 0.52 秒
numpy: 0.015 秒
快了約 35 倍

除了速度,還有兩個理由:語法簡潔(不用寫 for 迴圈)、 以及背後的 BLAS 函式庫把矩陣乘法做到極致——這是本書所有運算的基礎。


小結與練習#

本節重點 1. np.array / zeros / ones / arange / linspace 五種建法要滾瓜爛熟。 2. shape 列在前行在後;dtype 决定精度。 3. Broadcasting:尾端對齊,維度相等或為 1 就能算。 4. ndarray 比 list 快數十倍,且支援向量化語法。

練習 1. 用 linspace 產生 \(0\) 到 \(2\pi\) 的 100 個點,算出每點的 \(\sin\) 值(一行程式)。 2. 建立 4x4 矩陣,只取出四個角落的元素組成 2x2 矩陣(提示:fancy indexing)。 3. np.arange(12).reshape(3, 4) 的結果,加上 np.array([1, 2, 3, 4]) 會發生什麼?畫圖(或手畫)說明 broadcasting 的方向。 4. 把 np.array([1, 2, 3]) reshape 成 (3, 1),再和自身相加,結果是 3x3 矩陣——解釋為什麼。