Skip to main content
Summary

NumPy 배열과 텐서는 서로 오갈 수 있다

PyTorch를 쓰다 보면 NumPy 배열을 텐서로 바꾸거나, 텐서를 다시 NumPy 배열로 바꿔야 할 때가 많습니다. 예를 들어 데이터 전처리는 NumPy로 하고, 모델 학습은 PyTorch로 하는 식입니다.

NumPy 배열을 텐서로 바꿀 때는 torch.from_numpy()를 사용합니다.

import numpy as np
import torch

arr = np.array([1, 2, 3])
tensor = torch.from_numpy(arr)

print(tensor)

반대로 CPU에 있는 텐서를 NumPy 배열로 바꿀 때는 tensor.numpy()를 사용합니다.

tensor = torch.tensor([1, 2, 3])
arr = tensor.numpy()

print(arr)

여기서 중요한 점이 있습니다. CPU에 있는 텐서와 NumPy 배열은 메모리를 공유할 수 있습니다. 즉, 한쪽 값을 바꾸면 다른 쪽 값도 바뀔 수 있습니다.

arr = np.array([1, 2, 3])
tensor = torch.from_numpy(arr)

arr[0] = 100
print(tensor)  # tensor([100, 2, 3])

이 동작은 편리하지만, 초보자에게는 예상 밖의 버그가 되기도 합니다. 원본과 독립된 복사본이 필요하다면 NumPy 쪽에서는 copy(), PyTorch 쪽에서는 clone()을 사용하세요.

tensor = torch.from_numpy(arr.copy())
new_tensor = tensor.clone()

NumPy 변환과 장치 이동 이해하기

NumPy로 바꾸려면 CPU 텐서여야 한다

tensor.numpy()는 CPU 텐서에서만 바로 사용할 수 있습니다. GPU에 있는 텐서를 NumPy로 바꾸려면 먼저 CPU로 옮겨야 합니다.

# GPU 텐서라고 가정
# arr = tensor.numpy()  # 오류 가능

arr = tensor.cpu().numpy()

이 규칙은 NumPy가 PyTorch의 CUDA나 MPS 장치를 직접 다루지 않기 때문입니다. 따라서 "NumPy로 바꿀 때는 CPU로 먼저 이동한다"라고 기억하면 됩니다.

나중에 자동 미분을 배우고 나면 또 하나의 상황을 만나게 됩니다. 기울기 계산을 추적 중인 텐서는 바로 NumPy 배열로 바꿀 수 없습니다. 그때는 계산 그래프에서 분리한 뒤 CPU로 옮기고 NumPy로 변환합니다.

arr = tensor.detach().cpu().numpy()

지금 당장 detach()의 원리를 모두 이해할 필요는 없습니다. 우선은 "학습 중 만들어진 텐서를 분석이나 시각화를 위해 NumPy로 바꿀 때 자주 붙는 안전장치" 정도로 기억해 두면 충분합니다.

장치는 데이터가 놓인 위치다

PyTorch에서 device는 텐서가 어디에 있는지를 뜻합니다. 보통 세 가지를 자주 봅니다.

  • cpu: 기본 장치

  • cuda: NVIDIA GPU

  • mps: Apple Silicon Mac의 GPU 가속 장치

사용할 장치는 보통 다음처럼 정합니다.

device = torch.device(
    "cuda" if torch.cuda.is_available()
    else "mps" if torch.backends.mps.is_available()
    else "cpu"
)

print(device)

텐서를 해당 장치로 옮길 때는 to(device)를 사용합니다.

x = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
x = x.to(device)

print(x.device)

to()는 원본을 직접 바꾸는 함수가 아닙니다. 이동한 결과를 반환하므로, 반환값을 다시 변수에 담아야 합니다.

x.to(device)      # 이동한 결과를 사용하지 않음
x = x.to(device)  # 올바른 사용

모델과 입력은 같은 장치에 있어야 한다

학습할 때는 입력 텐서만 GPU로 옮기면 충분하지 않습니다. 모델도 같은 장치로 옮겨야 합니다.

model = MyModel()
model = model.to(device)

x = x.to(device)
output = model(x)

모델은 내부에 가중치 텐서를 가지고 있습니다. 입력은 GPU에 있는데 모델 가중치는 CPU에 있으면 PyTorch는 둘을 함께 계산할 수 없습니다. 이때 흔히 Expected all tensors to be on the same device 같은 오류가 발생합니다. 이것을 보통 device mismatch 오류라고 부릅니다.

기본 원칙은 간단합니다.

model = model.to(device)
inputs = inputs.to(device)
labels = labels.to(device)

모델, 입력, 정답 라벨이 모두 같은 장치에 있어야 합니다. 코드 안에서 새 텐서를 직접 만들 때도 같은 원칙이 적용됩니다. 예를 들어 torch.zeros(...)처럼 만든 텐서는 기본적으로 CPU에 생기므로, 필요하면 device=device를 지정하거나 .to(device)로 옮겨야 합니다.

dtype도 함께 확인해야 한다

장치만큼 중요한 것이 자료형, 즉 dtype입니다. 텐서가 숫자를 어떤 형식으로 저장하는지 나타냅니다.

모델 입력은 보통 float32를 사용합니다.

x = x.float()

이미지 데이터는 처음에 0~255 범위의 정수일 수 있습니다. 모델에 넣기 전에는 보통 실수형으로 바꾸고, 필요하면 0~1 범위로 나눕니다.

x = x.float() / 255.0

반면 분류 문제의 라벨은 보통 int64, 즉 long 타입이어야 합니다. 특히 nn.CrossEntropyLoss를 사용할 때 정답 라벨은 클래스 번호 형태의 long 텐서여야 합니다.

labels = labels.long()

예를 들어 고양이, 강아지, 새를 분류한다면 라벨은 [0, 1, 2] 같은 클래스 번호입니다. 이 값을 float로 바꾸면 손실 함수가 기대하는 형식과 맞지 않아 오류가 날 수 있습니다.

정리하면 다음과 같습니다.

  • 모델 입력: 보통 float32

  • 분류 라벨: 보통 int64, PyTorch에서는 long

  • 모델과 입력과 라벨: 같은 device

변환과 이동은 학습 코드의 기본 습관이다

앞으로 학습 루프를 작성할 때는 매번 비슷한 코드가 등장합니다.

inputs = inputs.to(device).float()
labels = labels.to(device).long()

outputs = model(inputs)
loss = loss_fn(outputs, labels)

이 몇 줄은 단순해 보이지만 매우 중요합니다. 데이터는 모델이 계산할 수 있는 장치에 있어야 하고, 손실 함수가 이해할 수 있는 자료형이어야 합니다. NumPy 변환, 장치 이동, dtype 확인은 따로 떨어진 기술이 아니라 PyTorch 학습 코드를 안정적으로 만드는 기본 규칙입니다.

책 목록

초보자를 위한 PyTorch 사용법: 텐서부터 이미지 분류 프로젝트까지

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기