NumPy‑Indexierung & Slicing: Tensoren frei schneiden und auswählen

1. Warum ist Indexierung/Slicing so wichtig?



Beim Deep‑Learning arbeitet man häufig mit Tensoren.

  • Aus einer Batch nur die ersten paar Samples auswählen
  • In einem Bild nur einen bestimmten Kanal (R/G/B) nutzen
  • In Sequenzdaten nur einige Zeitschritte extrahieren
  • Aus Labels nur bestimmte Klassen herausfiltern

All diese Aufgaben sind letztlich „Indexierung (indexing) & Slicing“.

Da die Indexierungs‑/Slicing‑Syntax von PyTorch fast identisch mit NumPy ist, lohnt es sich, NumPy zunächst gut zu beherrschen – so wird das Schreiben von Deep‑Learning‑Code deutlich einfacher.


2. Grundlegende Indexierung: von 1‑D bis hin

2.1 Indexierung eines eindimensionalen Arrays

import numpy as np

x = np.array([10, 20, 30, 40, 50])

print(x[0])  # 10
print(x[1])  # 20
print(x[4])  # 50
  • Indizes beginnen bei 0
  • x[i] gibt das i‑te Element

2.2 Negative Indizes

Wenn man von hinten zählen möchte, verwendet man negative Indizes.

print(x[-1])  # 50 (letztes Element)
print(x[-2])  # 40

PyTorch verhält sich genauso.


3. Grundlegendes Slicing: start:stop:step



Slicing wird in der Form x[start:stop:step] verwendet.

x = np.array([10, 20, 30, 40, 50])

print(x[1:4])    # [20 30 40], 1 ≤ i < 4
print(x[:3])     # [10 20 30], von Anfang bis 3
print(x[2:])     # [30 40 50], von 2 bis Ende
print(x[:])      # Kopie des gesamten Arrays

Mit step kann man einen Abstand festlegen.

print(x[0:5:2])  # [10 30 50], 0 bis 4 in Schritten von 2
print(x[::2])    # [10 30 50], gleich

Im Deep‑Learning ist das z. B. nützlich, wenn man Zeitschritte überspringt oder Stichproben in bestimmten Abständen auswählt.


4. Indexierung ab 2‑D: Zeilen und Spalten

Ab 2‑D wird man quasi mit Matrizen/Batchs arbeiten.

import numpy as np

X = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])  # shape: (3, 3)

4.1 Einzelne Zeilen/Spalten

print(X[0])      # erste Zeile: [1 2 3]
print(X[1])      # zweite Zeile: [4 5 6]

print(X[0, 0])   # Zeile 1, Spalte 1: 1
print(X[1, 2])   # Zeile 2, Spalte 3: 6
  • X[i] → i‑te Zeile (1‑D‑Array)
  • X[i, j] → Wert in Zeile i, Spalte j (Skalar)

4.2 Zeilen‑Slicing

print(X[0:2])    # Zeilen 0 und 1
# [[1 2 3]
#  [4 5 6]]

print(X[1:])     # ab Zeile 1 bis Ende
# [[4 5 6]
#  [7 8 9]]

Das ist ein typisches Muster, wenn man in PyTorch nur einen Teil der Batch nutzt.

4.3 Spalten‑Slicing

print(X[:, 0])   # alle Zeilen, Spalte 0 → [1 4 7]
print(X[:, 1])   # alle Zeilen, Spalte 1 → [2 5 8]
  • : bedeutet „alle Elemente in dieser Dimension“
  • X[:, 0] bedeutet „alle Zeilen, Spalte 0“

Im Deep‑Learning:

  • Bei einem Array (batch_size, feature_dim) kann man mit X[:, k] einen bestimmten Feature‑Kanal extrahieren.

5. 3‑D und mehr: Batch × Kanal × Höhe × Breite

Betrachten wir Bilddaten als Beispiel.

# (batch, height, width) annehmen
images = np.random.randn(32, 28, 28)  # 32 Bilder, 28×28

5.1 Ein einzelnes Sample

img0 = images[0]        # erstes Bild, shape: (28, 28)
img_last = images[-1]   # letztes Bild

5.2 Teil einer Batch

first_8 = images[:8]    # die ersten 8, shape: (8, 28, 28)

5.3 Bild‑Crop (nur ein Teilbereich)

# mittleres 20×20‑Bereich
crop = images[:, 4:24, 4:24]  # shape: (32, 20, 20)

In PyTorch sieht das ähnlich aus:

# images_torch: (32, 1, 28, 28)
center_crop = images_torch[:, :, 4:24, 4:24]

Die Indexierungs‑/Slicing‑Prinzipien sind also nahezu identisch.


6. Slicing erzeugt meist einen View

Ein wichtiger Punkt:

Das Ergebnis eines Slicings ist in der Regel ein View des Original‑Arrays. Das bedeutet, es wird nicht kopiert, sondern man sieht nur einen Teil des Originals.

x = np.array([10, 20, 30, 40, 50])
y = x[1:4]   # View

print(y)     # [20 30 40]
y[0] = 999

print(y)     # [999  30  40]
print(x)     # [ 10 999  30  40  50]  ← Original geändert!

Vorteil: Speicher‑ und Zeitersparnis. Nachteil: Unabsichtliche Änderungen am Original.

Um einen echten, unabhängigen Array zu erhalten, nutzt man copy().

x = np.array([10, 20, 30, 40, 50])
y = x[1:4].copy()

y[0] = 999
print(x)  # [10 20 30 40 50], Original bleibt unverändert

PyTorch hat ein ähnliches Konzept, daher ist es hilfreich, den Unterschied zwischen View und Copy zu kennen.


7. Boolesche Indexierung: Elemente nach Bedingung auswählen

Boolesche Indexierung wählt nur die Elemente, die eine Bedingung erfüllen.

import numpy as np

x = np.array([1, -2, 3, 0, -5, 6])

mask = x > 0
print(mask)      # [ True False  True False False  True]

pos = x[mask]
print(pos)       # [1 3 6]
  • x > 0 erzeugt ein Bool‑Array
  • x[mask] wählt die True‑Positionen

Beispiel mit 2‑D:

X = np.array([[1, 2, 3],
              [4, 5, 6],
              [-1, -2, -3]])

pos = X[X > 0]
print(pos)  # [1 2 3 4 5 6]

Im Deep‑Learning wird das häufig genutzt, z. B. um:

  • Nur Samples mit einer bestimmten Klasse zu extrahieren
  • Beim Verlustrechnen einen Masken‑Filter anzuwenden

PyTorch verhält sich analog:

import torch

x = torch.tensor([1, -2, 3, 0, -5, 6])
mask = x > 0
pos = x[mask]

8. Integer‑Array / List‑Indexierung (Fancy Indexing)

Man kann auch ein Array oder eine Liste von Ganzzahlen als Index verwenden, um mehrere Positionen gleichzeitig zu holen.

x = np.array([10, 20, 30, 40, 50])

idx = [0, 2, 4]
print(x[idx])  # [10 30 50]

Auch in 2‑D funktioniert das.

X = np.array([[1, 2],
              [3, 4],
              [5, 6]])  # shape: (3, 2)

rows = [0, 2]
print(X[rows])  
# [[1 2]
#  [5 6]]

Im Deep‑Learning kann man z. B. mit einer zufälligen Index‑Liste Batch‑Samples ziehen oder nur bestimmte Labels/Predictions sammeln.


9. Häufige Indexierungs‑Patterns zusammengefasst

Hier ein Überblick über typische Muster aus der Deep‑Learning‑Praxis:

import numpy as np

# (batch, feature)
X = np.random.randn(32, 10)

# 1) Nur die ersten 8 Samples
X_head = X[:8]              # (8, 10)

# 2) Nur ein bestimmtes Feature (z. B. Spalte 3)
f3 = X[:, 3]                # (32,)

# 3) Nur gerade Indizes
X_even = X[::2]             # (16, 10)

# 4) Nur Samples mit Label 1
labels = np.random.randint(0, 3, size=(32,))
mask = labels == 1
X_cls1 = X[mask]            # Samples mit Label 1

# 5) Zufälliges Shuffeln, dann 24 für train, 8 für val
indices = np.random.permutation(len(X))
train_idx = indices[:24]
val_idx = indices[24:]

X_train = X[train_idx]
X_val = X[val_idx]

Diese Muster gelten auch für PyTorch‑Tensoren. Im Grunde bedeutet NumPy‑Indexierung beherrschen: man kann Tensoren frei „schneiden, mischen, auswählen“.


10. Fazit

Zusammengefasst:

  • Indexierung: x[i], x[i, j], negative Indizes
  • Slicing: start:stop:step, :, multidimensional (X[:, 0], X[:8], X[:, 4:8])
  • Slicing erzeugt meist einen View – bei Bedarf copy() nutzen
  • Boolesche Indexierung: Filter mit Bedingungen (x[x > 0], X[labels == 1])
  • Integer‑Array‑Indexierung: Mehrere Positionen gleichzeitig (x[[0,2,4]])

Wenn man diese Techniken beherrscht, kann man mit PyTorch‑Tensoren Batch‑Slicing, Kanal‑Auswahl, Masken‑Anwendung und Sample‑Mischung viel einfacher durchführen.

image