PyTorch adalah pustaka deep learning yang menjadi penerus semangat NumPy: array-nya disebut tensor, operasinya vectorized, dan antarmukanya terasa akrab. Namun pustaka ini tidak terpasang di lingkungan penulisan bab ini, jadi kita menetapkan aturan yang sama seperti bab NumPy dan TensorFlow: setiap blok yang diuji membuktikan konsepnya hanya dengan NumPy, dan kode PyTorch muncul sebagai ilustrasi # notest yang memerlukan pip install torch untuk dijalankan sendiri.

Meski demikian, inti interoperabilitas NumPy–PyTorch justru bisa dibuktikan tanpa PyTorch: aturan zero-copy (berbagi memori), pemetaan dtype, dan migrasi antar perangkat. Kita mulai dari yang pertama.

Zero-copy: Berbagi Memori ala from_numpy #

torch.from_numpy(a) tidak menyalin data; tensor yang dihasilkan menunjuk ke buffer array NumPy yang sama, sehingga perubahan satu sisi langsung terlihat di sisi lain. Perilaku ini identik dengan view NumPy — dan view bisa kita buktikan sekarang juga:

python
import numpy as np

a = np.array([1.0, 2.0, 3.0])
v = a.view()
print("view berbagi memori ->", np.shares_memory(a, v))
a[0] = 99.0
print("isi view:", v)

Output yang Dihasilkan:

text
view berbagi memori -> True
isi view: [99.  2.  3.]

Bedah singkat:

  1. Baris 3-4 (a.view()): view adalah ndarray baru yang berbagi buffer dengan a; np.shares_memory membuktikan True.
  2. Baris 5-6: menulisi a[0] otomatis mengubah v — dua nama, satu memori. Inilah semantik yang diterapkan torch.from_numpy: mutasi array NumPy terlihat oleh tensor, dan sebaliknya.

Membedah Semantik Konversi lewat Simulasi #

PyTorch menyediakan dua pintu masuk dengan semantik berbeda: from_numpy (zero-copy) dan torch.tensor (salinan penuh). Untuk membuktikan perbedaannya tanpa pustaka torch, kita buat tensor tiruan — kelas kecil yang membungkus array dan lapor status memorinya:

python
import numpy as np

class TensorMock:
    """Meniru tensor yang membungkus array NumPy."""

    def __init__(self, arr):
        self._data = arr

    def berbagi_dengan(self, arr):
        return np.shares_memory(self._data, arr)

a = np.array([10, 20, 30])
t = TensorMock(a)
print("zero-copy   ->", t.berbagi_dengan(a))
c = TensorMock(a.copy())
print("salinan penuh ->", c.berbagi_dengan(a))
a[0] = 99
print("isi t:", t._data)

Output yang Dihasilkan:

text
zero-copy   -> True
salinan penuh -> False
isi t: [99 20 30]

Mari kita baca:

  1. Baris 3-8 (class TensorMock): kelas menyimpan referensi ke array, persis seperti from_numpy menyimpan referensi ke buffer NumPy; method berbagi_dengan memeriksa status memori.
  2. Baris 10-11 (t = TensorMock(a)): membungkus tanpa menyalin — analogi torch.from_numpy(a) — laporannya True.
  3. Baris 12-13 (TensorMock(a.copy())): membungkus salinan — analogi torch.tensor(a) — laporannya False.
  4. Baris 14-15: mutasi a[0] terbawa ke t tetapi tidak ke c; dua pintu masuk, dua konsekuensi.

Tabel berikut merangkum matriks konversi antar kedua pustaka:

ArahAPIMemori
ndarray → tensortorch.from_numpy(a)zero-copy, berbagi buffer
ndarray → tensortorch.tensor(a)salinan baru
tensor → ndarrayt.numpy()zero-copy (tensor CPU)
tensor GPU → ndarrayt.cpu().numpy()salinan (harus pindah ke CPU dulu)

Pemetaan dtype NumPy ke torch #

Konversi juga menuntut kesesuaian dtype. Detail yang sering mengejutkan: ndarray bawaan NumPy bertipe float64, sedangkan tensor bawaan PyTorch bertipe float32. Konversi eksplisit dikerjakan di sisi NumPy dengan astype:

python
import numpy as np

a64 = np.array([1.5, 2.5])
print(a64.dtype)
a32 = a64.astype(np.float32)
print(a32.dtype, a32)
print(np.array([1, 2, 3]).dtype, "vs", np.array([1, 2, 3], dtype=np.int32).dtype)

Output yang Dihasilkan:

text
float64
float32 [1.5 2.5]
int64 vs int32

Penjelasan:

  1. Baris 3-4: array default NumPy adalah float64 (8 byte per nilai).
  2. Baris 5-6 (astype(np.float32)): konversi menurunkan presisi menjadi 4 byte — ukuran yang biasa dipakai pelatihan model; nilainya masih [1.5 2.5].
  3. Baris 7: bilangan bulat bawaan NumPy adalah int64, sementara sebagian API model mengharapkan int32 — pemetaannya harus diketahui di muka.
NumPyPyTorchUkuran
np.float64torch.float64 (torch.double)8 byte
np.float32torch.float32 (torch.float)4 byte
np.int64torch.int64 (torch.long)8 byte
np.int32torch.int32 (torch.int)4 byte
np.uint8torch.uint81 byte
np.bool_torch.bool1 byte

Kode Integrasi (Ilustratif) #

Berikut pola lengkapnya — blok ini tidak diuji karena memerlukan pustaka torch yang terpasang:

python
# notest
import numpy as np
import torch  # perlu dipasang sendiri: pip install torch

a = np.array([1.0, 2.0, 3.0])
t = torch.from_numpy(a)      # zero-copy: berbagi memori dengan a
c = torch.tensor(a)          # salinan penuh
kembali = t.numpy()          # tensor CPU -> ndarray, juga zero-copy
device = "cuda" if torch.cuda.is_available() else "cpu"
t_pindah = t.to(device)      # migrasi antar perangkat
print(t_pindah.device)

Baris demi baris:

  1. Baris 5 (torch.from_numpy(a)): tensor CPU berbagi buffer dengan a — semantik yang telah dibuktikan lewat view dan TensorMock di atas.
  2. Baris 6-7: torch.tensor(a) menyalin; t.numpy() mengembalikan ndarray tanpa salinan selama tensor berada di CPU.
  3. Baris 8-9 (t.to(device)): migrasi perangkat memilih "cuda" bila GPU tersedia, jika tidak tetap di "cpu"; tensor hasil GPU memakai memori kartu grafis dan tidak bisa langsung diserahkan ke .numpy() — ia harus dikembalikan dengan t_pindah.cpu() lebih dulu.

Dua kalimat penutup: untuk data masuk, susun array NumPy dengan bentuk dan dtype yang benar, lalu serahkan dengan from_numpy atau torch.tensor sesuai kebutuhan salin; untuk data keluar, panggil .numpy() setelah tensor berada di CPU. Pola pemeriksaan ketersediaan pustaka (try/except ImportError) dibahas serupa pada bab NumPy dan TensorFlow.