NumPy индексирование и срезы: как свободно обрезать тензоры

1. Зачем так важно индексирование/срезы?



При работе с глубоким обучением вы часто будете манипулировать тензорами.

  • Берёте только первые несколько образцов из батча
  • Выбираете конкретный канал (R/G/B) изображения
  • Обрезаете часть временных шагов в последовательных данных
  • Отбираете только нужный класс из меток

Все эти операции в итоге сводятся к "индексированию (indexing) и срезам (slicing)".

И поскольку синтаксис индексирования/срезов в Tensor PyTorch почти идентичен NumPy, хорошо освоить его в NumPy, и код на PyTorch станет намного проще.


2. Базовое индексирование: от 1‑мерных массивов

2.1 Индексирование 1‑мерного массива

import numpy as np

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

print(x[0])  # 10
print(x[1])  # 20
print(x[4])  # 50
  • Индексы начинаются с 0
  • x[i] – i‑й элемент

2.2 Отрицательные индексы

Если нужно посчитать от конца, используйте отрицательные индексы.

print(x[-1])  # 50 (последний)
print(x[-2])  # 40

В PyTorch работает точно так же.


3. Основы срезов: start:stop:step



Срезы применяются как x[start:stop:step].

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

print(x[1:4])    # [20 30 40], 1 ≤ i < 4
print(x[:3])     # [10 20 30], от начала до 3
print(x[2:])     # [30 40 50], от 2 до конца
print(x[:])      # копия всего

Если задать step, можно пропускать элементы.

print(x[0:5:2])  # [10 30 50], от 0 до 4 с шагом 2
print(x[::2])    # [10 30 50], то же

В глубоких сетях удобно, например, пропускать каждый второй временной шаг или выбирать образцы с заданным интервалом.


4. Индексирование массивов 2‑мерных и выше: работа с строками и столбцами

2‑мерные массивы – это уже матрицы/батчи, поэтому сразу ощущается дух глубокого обучения.

import numpy as np

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

4.1 Индексирование одной строки/столбца

print(X[0])      # первая строка: [1 2 3]
print(X[1])      # вторая строка: [4 5 6]

print(X[0, 0])   # 1‑й ряд, 1‑й столбец: 1
print(X[1, 2])   # 2‑й ряд, 3‑й столбец: 6
  • X[i] – i‑й ряд (1‑мерный массив)
  • X[i, j] – значение в i‑м ряду и j‑м столбце (скаляр)

4.2 Срезы строк

print(X[0:2])    # строки 0 и 1
# [[1 2 3]
#  [4 5 6]]

print(X[1:])     # от строки 1 до конца
# [[4 5 6]
#  [7 8 9]]

Это типичный паттерн в PyTorch: обрезка батча.

4.3 Срезы столбцов

print(X[:, 0])   # все строки, 0‑й столбец → [1 4 7]
print(X[:, 1])   # все строки, 1‑й столбец → [2 5 8]
  • : означает «весь размерность»
  • X[:, 0] – «все строки, столбец 0»

В глубоких сетях:

  • В массиве (batch_size, feature_dim) можно взять конкретный признак: X[:, k]

5. 3‑мерные и более: батч × канал × высота × ширина

Рассмотрим пример с изображениями.

# Предположим (batch, height, width)
images = np.random.randn(32, 28, 28)  # 32 изображения 28×28

5.1 Выбор одного образца

img0 = images[0]        # первое изображение, shape: (28, 28)
img_last = images[-1]   # последнее

5.2 Использование части батча

first_8 = images[:8]    # первые 8, shape: (8, 28, 28)

5.3 Обрезка части изображения (crop)

# Центр 20×20
crop = images[:, 4:24, 4:24]  # shape: (32, 20, 20)

В PyTorch аналогично:

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

Индексирование/срезы почти идентичны.


6. Срезы обычно являются «просмотром» (view)

Ключевой момент:

Результат среза обычно является просмотром (view) исходного массива. То есть данные не копируются, а просто «видят» оригинал.

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]  ← оригинал изменился!

Преимущества: экономия памяти и скорость. Недостаток: случайное изменение оригинала.

Если нужен полностью независимый массив, используйте copy().

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

y[0] = 999
print(x)  # [10 20 30 40 50], оригинал сохранён

В PyTorch аналогично, поэтому понимание «view vs copy» облегчает отладку.


7. Логическое индексирование: выбор по условию

Логическое индексирование позволяет выбрать элементы, удовлетворяющие условию.

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 – массив True/False
  • x[mask] – берёт только True позиции

Пример с 2‑мерным массивом:

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

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

В глубоких сетях часто используют:

  • Отбор образцов по условию (например, метка = 1)
  • Маскирование при расчёте потерь – усреднение только нужных значений

В PyTorch аналогично:

import torch

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

8. Индексирование массивом/списком целых чисел (Fancy Indexing)

Можно сразу выбрать несколько позиций, передав массив или список индексов.

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

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

В 2‑мерном массиве тоже работает.

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

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

В глубоких сетях:

  • Случайный выбор индексов для батча
  • Сбор конкретных лейблов/предсказаний

PyTorch поддерживает такой стиль.


9. Часто используемые паттерны индексирования

Соберём типичные паттерны из точки зрения глубокого обучения:

import numpy as np

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

# 1) первые 8 образцов
X_head = X[:8]              # (8, 10)

# 2) конкретный признак (например, 3‑й столбец)
f3 = X[:, 3]                # (32,)

# 3) только чётные индексы
X_even = X[::2]             # (16, 10)

# 4) только лейбл 1
labels = np.random.randint(0, 3, size=(32,))
mask = labels == 1
X_cls1 = X[mask]            # только лейбл 1

# 5) случайный перетасовка, первые 24 – train, остальные 8 – 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]

Эти паттерны почти идентичны в PyTorch. Освоив NumPy‑индексирование, вы получите навык «свободного разрезания, перемешивания и выбора» тензоров.


10. Итоги

В итоге:

  • Индексирование: x[i], x[i, j], отрицательные индексы
  • Срезы: start:stop:step, :, многомерные индексы (X[:, 0], X[:8], X[:, 4:8])
  • Срезы обычно являются просмотром (view), поэтому оригинал может измениться → при необходимости используйте copy()
  • Логическое индексирование: фильтрация по условию (x[x > 0], X[labels == 1])
  • Индексирование массивом целых чисел: выбор конкретных позиций (x[[0,2,4]])

Уверенное владение этими техниками позволяет легко выполнять операции с батчами, каналами, масками и перемешиванием данных в PyTorch.

image