只用 PyTorch 就行嗎?

對於深度學習開發者來說,這個關係往往會讓人困惑。

在許多微調程式碼中,總會同時出現兩個名字:

import numpy as np
import torch

「不,所有張量運算都在 PyTorch 裡完成…為什麼還需要 NumPy?」 「兩者概念重疊,還是必須單獨學習 NumPy?」

我也曾好奇這個問題,並在一個清晰的回答基礎上整理了以下內容。


1. NumPy 與 PyTorch,究竟有什麼不同?



粗略來說,PyTorch 可以被視為:

「GPU 上的 NumPy + 自動微分(autograd)」

拆解角色:

  • NumPy
  • 在 CPU 上運行的陣列/矩陣庫
  • 數值計算、線性代數、統計運算的「Python 標準」
  • ndarray 為核心資料結構

  • PyTorch

  • 與 NumPy 風格相近的 API
  • 不僅能在 CPU 上運行,還能在 GPU(CUDA)上運行
  • 支援張量運算的自動微分 → 可進行深度學習訓練

PyTorch 的開發者有意地模仿了 NumPy 的語法

  • np.array([...])torch.tensor([...])
  • x_np.shapex_torch.shape
  • np.reshape(x, ...)x_torch.view(...)x_torch.reshape(...)

因此,學習 PyTorch 的張量運算時,自然也會同時習得 NumPy 的思維與語法


2. 「先學 NumPy 再學 PyTorch」的實際答案

許多教學會先從 NumPy 開始,容易讓人混淆。從實務角度來看,答案是:

不必先學 NumPy,直接從 PyTorch 的張量運算開始即可。

原因很簡單:

  • 張量的形狀、廣播、索引等概念在 NumPy 與 PyTorch 中幾乎相同。
  • 在深度學習中,我們真正需要的是「張量運算 + 自動微分」,這完全在 PyTorch 內部完成。

所以,如果你的目標是深度學習:

  1. 先熟悉 PyTorch 的張量操作
  2. 當程式碼中出現 np.xxx 時,理解為「CPU 陣列版本的張量操作」即可。

3. 為什麼 NumPy 還會頻繁出現?——「生態系統的通用語言」



這裡會產生疑問:

「PyTorch 更強大,為什麼還要用 NumPy?」

原因是:Python 數據/科學生態系統大多以 NumPy 為基礎設計

3-1. 數據預處理階段:NumPy 的世界

常用的庫:

  • OpenCVPillow → 讀取圖像時通常返回 NumPy 陣列
  • pandasDataFrame.values / to_numpy() 等使用 NumPy 陣列
  • 各種統計/數值套件 → 以 NumPy 為輸入/輸出標準

也就是說,「送進模型之前的數據操作」大多使用 NumPy。

3-2. 模型訓練/推論階段:PyTorch 的世界

import numpy as np
import torch

x_np = ...  # 預處理後的 NumPy 陣列
x_tensor = torch.from_numpy(x_np)  # 轉成張量
x_tensor = x_tensor.to("cuda")     # 移到 GPU

out = model(x_tensor)

從這裡開始,就是 PyTorch 的張量領域。

3-3. 視覺化・儲存・後處理階段:再次回到 NumPy

模型預測結果:

  • matplotlib 畫圖
  • 再放回 pandas 儲存為 CSV
  • 做簡單統計

這些庫大多以 NumPy 陣列 為基礎運作。

因此,往往需要將張量轉回 NumPy:

out_cpu = out.detach().cpu().numpy()

常見錯誤:

TypeError: can't convert cuda:0 device type tensor to numpy

這是因為 GPU 張量不能直接轉成 NumPy,必須先 .cpu()


4. 系統層面的關鍵點:Zero‑Copy 共享

從更低層面來看,有一個有趣的點:

x_np = np.random.randn(3, 3)
x_tensor = torch.from_numpy(x_np)  # 這裡!

實際上並未複製資料

  • torch.from_numpy(...) 會共享 NumPy 使用的記憶體。
  • 因此,即使是巨大的陣列,不會額外佔用記憶體

相反:

x_np2 = x_tensor.numpy()

同樣(若是 CPU 張量)會得到共享同一記憶體的 NumPy 陣列。

示例:

x_np = np.zeros((2, 2))
x_tensor = torch.from_numpy(x_np)

x_tensor[0, 0] = 1
print(x_np)
# [[1. 0.]
#  [0. 0.]]  ← NumPy 陣列也跟著改變

兩者就像「同一扇窗」看同一個資料。

總結: * PyTorch 與 NumPy 可以在 CPU 上共享記憶體。 * 這使得「預處理(NumPy)↔訓練(PyTorch)↔後處理(NumPy)」能在不產生大量複製成本的情況下流暢切換。


5. 一個完整流程的簡易範例

假設一個極簡的圖像分類例子:

import cv2
import numpy as np
import torch

# 1. 圖像載入 & NumPy 預處理(NumPy 區域)
img = cv2.imread("cat.png")          # shape: (H, W, C), BGR, NumPy array
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, (224, 224))
img = img.astype(np.float32) / 255.0
img = np.transpose(img, (2, 0, 1))   # (C, H, W)

# 2. NumPy → PyTorch 張量轉換 & GPU 移動(PyTorch 區域)
x = torch.from_numpy(img)            # shape: (3, 224, 224)
x = x.unsqueeze(0)                   # 加 batch 維度: (1, 3, 224, 224)
x = x.to("cuda")

# 3. 模型推論
with torch.no_grad():
    logits = model(x)
    probs = torch.softmax(logits, dim=1)

# 4. 再轉回 NumPy 進行後處理/視覺化(NumPy 區域)
probs_np = probs.cpu().numpy()
pred_class = np.argmax(probs_np, axis=1)[0]

從程式碼流程可見:

  • 輸入/輸出邊界使用 NumPy
  • 中間訓練/推論使用 PyTorch

6. 最終總結:這樣理解就夠了

總結如下:

  1. 學習 PyTorch 就是學習「未來版的 NumPy」。掌握張量操作與廣播感覺,實際上已經掌握了 NumPy 的核心概念。
  2. 不需要深入學習 NumPy。若目標是深度學習,先學 PyTorch,遇到 np.xxx 時只需把它視為「CPU 陣列版的張量操作」。
  3. 但在實務中,數據預處理/後處理/視覺化仍離不開 NumPy,尤其是與 pandasmatplotlib 等庫交互。
  4. PyTorch 與 NumPy 能共享記憶體(Zero‑Copy),使得大規模資料在兩者之間高效切換。

當你看到深度學習程式碼時,這樣思考即可:

  • 張量在 GPU 上、需要微分 → PyTorch
  • 與其他庫交互、簡單數值/統計/視覺化 → NumPy

這兩個世界自然流暢地往返,成為現代 Python 深度學習開發者的基本技能。

image