Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Hoofdstuk 11 — Interoperabiliteit: NumPy met Python collections, buffers/memory‑views en PyTorch tensors

Let op: De tekst is in het Nederlands; alle Python codevoorbeelden zijn in het Engels.

Doel van dit hoofdstuk:

  • Begrijpen hoe NumPy samenwerkt met standaard Python‑collecties (list/tuple), array module en collections.abc sequenties.
  • Zero‑copy interoperabiliteit via buffer‑protocol en memory‑views (memoryview, np.frombuffer, ndarray.view) benutten.
  • Efficiënt data uitwisselen met PyTorch tensors in beide richtingen, inclusief device/dtype/contiguïteit en zero‑copy waar mogelijk.
  • Praktische patronen, valkuilen, performance‑implicaties en testbare code‑snippets.

Aanbevolen voorkennis: Hoofdstuk 6–10 (arrays, dtypes, views/kopieën, broadcasting, prestaties), basiskennis van PyTorch is nuttig maar niet noodzakelijk.

11.1 Python‑collecties naar en van NumPy

Standaard Python‑collecties zijn heterogeen en niet geoptimaliseerd voor vectorisatie, maar NumPy kan er goed mee werken door te converteren naar homogene arrays.

import numpy as np

# list/tuple → ndarray (copy of data)
x_list = [1, 2, 3]
x_arr = np.array(x_list)            # dtype inferred (usually int64 or int32)

y_tuple = (1.0, 2.5, 3.14)
y_arr = np.array(y_tuple, dtype=np.float32)  # explicit dtype cast

# ndarray → Python list (copy)
back_to_list = x_arr.tolist()

Opmerkingen:

  • np.array(obj) maakt doorgaans een kopie; voor nested lists zal NumPy proberen te inferren naar 2D/ND shape. Inconsistente lengtes geven dtype=object — ongunstig voor rekenen.
  • .tolist() maakt altijd een kopie en kan langzaam zijn op grote arrays; gebruik alleen voor IO of weergave.

11.1.1 De array module en collections.abc sequenties

De standaardbibliotheek heeft array('f'), array('d'), array('i'), etc. Deze ondersteunen het buffer‑protocol, waardoor zero‑copy uitwisseling met NumPy mogelijk is.

import numpy as np
from array import array

arr_mod = array('d', [1.0, 2.0, 3.0])   # double precision
# Zero-copy view of the same memory (shape (3,))
view_np = np.frombuffer(arr_mod, dtype=np.float64)
view_np[0] = 99.0
assert arr_mod[0] == 99.0               # shares memory!

# Opposite direction: create array('d') from ndarray bytes (copy)
x = np.array([10.0, 20.0], dtype=np.float64)
arr_copy = array('d', x)                # this copies the data

collections.abc beschrijft sequentie‑interfaces; NumPy accepteert elk object dat zich als een sequentie gedraagt (iterable van elementaire waarden) voor np.array of ufuncs die Python iterables nemen, maar dat leidt vrijwel altijd tot een kopie.

11.2 Buffer‑protocol en memory‑views

Voor performance‑kritische interoperabiliteit is het buffer‑protocol essentieel. Het stelt Python‑objecten in staat hun ruwe geheugen te delen zonder kopiëren.

Belangrijkste bouwstenen:

  • memoryview(obj): generieke manier om een buffer van obj te bekijken.
  • np.frombuffer(buffer, dtype, count, offset): maak een 1D‑array die het buffer deelt (zero‑copy) — let op dtype en uitlijning.
  • ndarray.tobytes(), ndarray.data, ndarray.view(new_dtype): lage‑niveau toegang/manipulatie.
import numpy as np

# bytes/bytearray → NumPy zonder kopie (view)
buf = bytearray(b"\x00\x00\x80?\x00\x00\x00@")  # two float32: 1.0, 2.0
mv = memoryview(buf)
a = np.frombuffer(mv, dtype=np.float32)
assert np.shares_memory(a, np.frombuffer(buf, dtype=np.float32))

# dtype‑re‑interpretatie met .view (geen byte‑kopie, wel herinterpretatie)
x = np.arange(4, dtype=np.uint8)
y = x.view(np.int32)  # requires correct length and alignment; platform dependent

Valkuilen en richtlijnen:

  • Zero‑copy werkt alleen bij compatibele dtypes, endianness en uitlijning; anders moet je kopiëren (.astype(new_dtype, copy=True)).
  • Veel buffer‑bronnen zijn 1D; voor multi‑dimensies moet je np.ndarray(shape, dtype, buffer=..., strides=...) of a = np.frombuffer(...).reshape(...) gebruiken.
  • Gebruik np.shares_memory(a, b) om te controleren of je echt geheugen deelt.

11.3 Memory‑views van NumPy naar ander Python‑code

Je kunt een NumPy‑array als buffer aanbieden aan andere libraries, of direct via memoryview gebruiken.

import numpy as np

A = np.arange(12, dtype=np.int16).reshape(3, 4)
# Expose a read‑only memoryview of A's data (C‑order assumed)
mv = memoryview(A)
# mv can be passed to any buffer‑consumer (e.g., file IO, struct unpacking)

Let op:

  • Niet alle consumers begrijpen striding/ND‑vorm; vaak moet je een contigue view aanleveren (np.ascontiguousarray(A)).
  • Negatieve strides (zoals slices met omkering) zijn vaak onverenigbaar met C‑extensions; maak dan een kopie of ascontiguousarray.

11.4 PyTorch tensors ↔ NumPy arrays

PyTorch en NumPy zijn nauw verweven. Je kunt vaak zero‑copy views delen tussen CPU‑tensors en NumPy arrays. GPU‑tensors (CUDA/MPS) vereisen kopiëren naar host, en terug.

Voorbeelden (CPU):

import numpy as np
import torch

# NumPy → Torch (zero‑copy view when possible)
a_np = np.arange(6, dtype=np.float32).reshape(2, 3)
t_torch = torch.from_numpy(a_np)  # shares memory if a_np is writable and contiguous

# Mutations reflect both ways
t_torch[0, 0] = -1.0
assert a_np[0, 0] == -1.0

# Torch → NumPy (CPU)
t2 = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float64)
a2 = t2.numpy()  # shares memory if t2 is a CPU tensor

# If non‑contiguous, make it contiguous first
u = torch.arange(12, dtype=torch.float32).reshape(3, 4).t()  # transpose -> non‑contiguous
u_contig = u.contiguous()
a_from_u = u_contig.numpy()

Belangrijk:

  • Zero‑copy geldt alleen voor CPU‑tensors en NumPy arrays; op GPU moet je .cpu() gebruiken (kopie): a_gpu = t_cuda.detach().cpu().numpy().
  • Dtype‑mapping: float32 ↔ torch.float32, float64 ↔ torch.float64, int64 ↔ torch.int64 (PyTorch’s default int), bool ↔ torch.bool, etc.
  • Writability/ownership: torch.from_numpy vereist dat de NumPy‑array writeable is; anders krijg je een tensor die mogelijk beperkingen heeft. Omgekeerd deelt tensor.numpy() geheugen en respecteert requires_grad=False. Voor autograd: gebruik .detach().numpy() om grad‑history te verbreken.

11.4.1 Device transfers en pinned memory

# CPU tensor to GPU, then back to NumPy
x_cpu = torch.arange(6, dtype=torch.float32).reshape(2, 3)
x_gpu = x_cpu.to('cuda')        # requires CUDA‑capable setup
x_back = x_gpu.detach().cpu().numpy()   # copy to host

Pinned memory kan host↔device transfers versnellen, maar vereist geavanceerde setup (pin_memory=True bij DataLoader, of tensor.pin_memory() op CPU‑buffers).

11.4.2 Non‑contiguïteit, strides en views

PyTorch en NumPy hebben beide strides; zero‑copy gedeelde views vereisen compatibele strides en contiguïteit. Na transposes/slices is .contiguous() of np.ascontiguousarray vaak nodig.

# Non‑contiguous example
w = torch.arange(12, dtype=torch.float32).reshape(3, 4)
wT = w.t()                         # transpose, non‑contiguous
# a direct wT.numpy() raises an error in older versions; in modern PyTorch it may copy
wT_c = wT.contiguous()
a = wT_c.numpy()

11.5 Seriële uitwisseling: DLPack en __array__ protocol

Voor breder ecosysteem‑interop bestaan standaarden:

  • __array__/__array_priority__: Python protocol waarmee objecten zich als NumPy‑array kunnen presenteren. np.asarray(obj) kan dan een view of kopie verkrijgen.
  • DLPack: een zero‑copy tensor overdrachtsformaat ondersteund door PyTorch, CuPy, JAX, TensorFlow e.a.
# PyTorch ↔ NumPy via __array__ protocol (NumPy side)
# Many torch tensors implement __array__ returning a NumPy view for CPU tensors.

# PyTorch ↔ andere frameworks via DLPack
import torch

# Torch to DLPack capsule
x = torch.arange(6, dtype=torch.float32)
capsule = torch.utils.dlpack.to_dlpack(x)
# Another framework (e.g., cupy) could import it zero‑copy on the same device
# cupy_arr = cupy.fromDlpack(capsule)

Opmerking: NumPy zelf consumeert DLPack niet standaard (v1.26); interop met GPU frameworks loopt dan via die frameworks of via kopiëren naar CPU.

11.6 Praktische patronen

  • Data inlezen als Python list/iterable → éénmalig casten naar np.array met gewenst dtype en shape; daarna overal arrays gebruiken.
  • Gebruik het buffer‑protocol voor zero‑copy tussen array module, bytearray, memoryview en NumPy.
  • Voor PyTorch training/inferentie op CPU: gebruik torch.from_numpy om datasets/tensors te maken zonder kopie; zorg dat arrays contigu en van het juiste dtype zijn.
  • Voor GPU: verzamel in NumPy, zet om naar torch CPU tensor met from_numpy, en roep .to('cuda') één keer vóór de training/inferentie.
  • Let op eigendom en levensduur: als een tensor/array een view is op andermans geheugen, houd dat bron‑object in scope.

11.7 Veelgemaakte valkuilen

  • Onbedoelde kopieën bij .astype, np.array(list) herhaald in een loop, of tensor.numpy() op GPU (maakt impliciet een CPU kopie via .cpu()).
  • Endianness/dtype mismatch bij frombuffer geeft corrupte waarden; specificeer dtype correct.
  • Niet‑contigue layouts breken zero‑copy; materialiseer contigu.
  • Schrijven naar gedeeld geheugen kan onverwachte bijwerkingen veroorzaken; gebruik .copy() als isolatie gewenst is.

11.8 Korte oefeningen

  1. Maak een array('d',[1,2,3]) en construeer er een NumPy‑view bovenop met np.frombuffer. Pas een element aan en toon dat het originele array‑object mee verandert (zero‑copy).
  2. Converteer een geneste Python‑list naar een 2D‑NumPy‑array met een expliciet dtype. Laat zien wat er gebeurt als de sub‑lijsten verschillende lengtes hebben.
  3. Maak een bytearray met twee little‑endian float32’s en lees ze in met np.frombuffer en de juiste dtype. Reshape naar (1,2) zonder kopie en controleer np.shares_memory.
  4. Zet een NumPy array float32 om naar een PyTorch CPU‑tensor met torch.from_numpy en toon bidirectionele mutaties. Maak daarna een transpose in PyTorch en converteer naar NumPy; los contiguïteit op.
  5. Demonstreer de roundtrip CPU→GPU→CPU met PyTorch (.to('cuda') en .cpu()) en converteer terug naar NumPy, met aandacht voor waar kopieën plaatsvinden.

Werkboek en uitwerkingen:

  • Oefeningen bij dit hoofdstuk kunnen later toegevoegd worden in ../workbook/hoofdstuk-11/oefeningen.md.
  • Uitwerkingen kunnen later toegevoegd worden in ../solutions/hoofdstuk-11/uitwerkingen.md.

Verder lezen en vervolg:

  • Hoofdstuk 6–7: views/kopieën, contiguïteit en broadcasting.
  • Hoofdstuk 10: prestaties en geheugenbeheer; zero‑copy patronen.
  • PyTorch docs: torch.from_numpy, Tensor.numpy, DLPack utils; Python docs: buffer protocol en memoryview.