Мост с numpy

Связываем PyTorch с numpy: туда и обратно, с важным нюансом об общей памяти.

Мост с numpy — это переход между torch.Tensor и numpy.ndarray; на CPU они могут делить одну и ту же память.

Зачем это нужно

Половина инструментов для данных (загрузка CSV через pandas, картинки через OpenCV, графики через matplotlib) говорит на языке numpy-массивов. PyTorch создан так, чтобы переход был дешёвым: API тензоров намеренно повторяет numpy. Поэтому в реальном проекте вы постоянно переводите данные туда-сюда: получили numpy-массив из файла, превратили в тензор, обучили модель, результат вернули в numpy для визуализации.

Из numpy в тензор

Есть два пути: torch.from_numpy(arr) и torch.tensor(arr). Разница принципиальна.

import numpy as np
import torch

arr = np.array([1.0, 2.0, 3.0])

t1 = torch.from_numpy(arr)   # ОБЩАЯ память с arr
t2 = torch.tensor(arr)       # КОПИЯ данных

arr[0] = 99.0
print(t1)   # tensor([99.,  2.,  3.])  — изменился вслед за arr!
print(t2)   # tensor([1., 2., 3.])     — копия, не тронут

from_numpy не копирует данные — тензор и массив указывают на одну и ту же область памяти. Изменили массив — изменился тензор, и наоборот. Это быстро и экономно, но опасно, если вы не ждёте такой связи. torch.tensor всегда делает независимую копию.

Из тензора в numpy

Обратно — метод .numpy(). Он тоже разделяет память с тензором (для CPU-тензоров):

t = torch.ones(3)
arr = t.numpy()      # общая память

t[0] = 5.0
print(arr)           # [5. 1. 1.]  — массив увидел изменение

Подводные камни

Два случая, когда .numpy() не сработает напрямую и нужно подготовить тензор:

ПроблемаРешение
Тензор на GPU — у numpy нет GPUсначала .cpu(): t.cpu().numpy()
Тензор с requires_grad=True (в графе)сначала .detach(): t.detach().numpy()

На практике безопасная универсальная формула, которую вы будете писать постоянно при выводе результатов модели:

# превратить любой тензор (GPU, в графе) в numpy
result = some_tensor.detach().cpu().numpy()

Порядок важен: сперва detach() (выйти из графа), потом cpu() (перенести на процессор), потом numpy(). Что такое detach и граф — подробно в следующем разделе про autograd.

Чистый Python для интуиции

Идея «общей памяти» знакома и в обычном Python: список, на который ссылаются две переменные, меняется через любую из них. Этот пример запускается:

# аналогия общей памяти из чистого Python
a = [1, 2, 3]
b = a            # b ссылается на тот же список (как from_numpy)
c = a.copy()     # c — независимая копия (как torch.tensor)

a[0] = 99
print("b:", b)   # b увидел изменение
print("c:", c)   # c не тронут

Вывод:

b: [99, 2, 3]
c: [1, 2, 3]

Та же логика: from_numpy/.numpy() ведут себя как b = a (общая память), а torch.tensor(arr) — как a.copy().

Итог

  • torch.from_numpy(arr) — общая память; torch.tensor(arr) — независимая копия.
  • .numpy() переводит тензор в массив, тоже разделяя память (на CPU).
  • Перед .numpy() часто нужны .detach() (выйти из графа) и .cpu() (с GPU).
  • Универсальная формула вывода результата: tensor.detach().cpu().numpy().
Проверьте себя
1. Чем отличается torch.from_numpy(arr) от torch.tensor(arr)?
AНичем, это синонимы
Bfrom_numpy разделяет память с массивом, tensor делает копию
Cfrom_numpy делает копию, tensor разделяет память
Dfrom_numpy работает только на GPU
2. Какая формула безопасно переведёт тензор модели (возможно на GPU и в графе) в numpy?
Atensor.numpy()
Btensor.cpu().detach()
Ctensor.detach().cpu().numpy()
Dnumpy(tensor)
3. Почему нельзя вызвать .numpy() прямо на тензоре, лежащем на GPU?
Anumpy не поддерживает float32
BУ numpy нет понятия GPU, данные сперва нужно перенести на CPU
CGPU-тензоры всегда целочисленные
DЭто можно, ошибки не будет
Поддержать проект