PyTorch 기초 (1) - 텐서: NumPy 배열에서 넘어오기, 그리고 GPU
PyTorch의 텐서가 NumPy 배열과 무엇이 같고 무엇이 다른지, 생성·인덱싱·연산·모양 바꾸기, NumPy와의 상호 변환(메모리 공유 주의), 그리고 GPU로 옮기는 device 개념까지 정리한 PyTorch 기초 (1)편입니다.
PyTorch 기초 시리즈(6편)의 1편입니다. 전체 목차는 0편에 있습니다. 이 편은 PyTorch의 기본 자료구조인 텐서를, 이미 익숙한 NumPy 배열과 견주며 정리합니다.
텐서는 “미분되고 GPU에서 도는 ndarray”
NumPy를 아는 사람에게 텐서를 설명하는 가장 빠른 방법은 이렇습니다. 텐서는 NumPy의 ndarray에 두 가지가 더해진 것입니다. 하나는 GPU에서 연산할 수 있다는 것이고, 다른 하나는 연산 과정을 기록해 자동으로 미분할 수 있다는 것(다음 편의 autograd)입니다. 그 둘을 빼면, 텐서를 다루는 문법은 NumPy와 거의 같습니다.
그래서 이 편의 대부분은 “NumPy와 같다”이고, 중요한 것은 “다른 지점”입니다.
텐서 만들기
기본적인 생성 방법은 NumPy와 판박이입니다.
1
2
3
4
5
6
7
import torch
torch.tensor([[1, 2], [3, 4]]) # 파이썬 리스트에서
torch.zeros(2, 3) # 0으로 채운 (2, 3)
torch.ones(2, 3) # 1로 채운 (2, 3)
torch.randn(2, 3) # 표준정규분포 난수
torch.arange(6).reshape(2, 3) # 0~5를 (2, 3)으로
텐서의 정체성도 NumPy처럼 모양(shape)과 dtype으로 규정됩니다.
1
2
3
4
t = torch.randn(2, 3)
t.shape # torch.Size([2, 3])
t.dtype # torch.float32 — randn의 기본 실수 타입
t.ndim # 2
한 가지 차이가 dtype 기본값입니다. torch.tensor([1, 2, 3])은 int64, torch.tensor([1., 2., 3.])은 float32가 됩니다. 딥러닝의 기본 실수 타입이 float64가 아니라 float32라는 점을 기억해두면 좋습니다. 모델 파라미터와 입력은 대부분 float32여야 하고, 정수 텐서를 실수로 바꿀 일이 잦습니다.
1
2
3
x = torch.tensor([1, 2, 3])
x.float() # float32로 변환
x.to(torch.float32) # 같은 의미
인덱싱·연산·모양 바꾸기
인덱싱, 슬라이싱, 마스크, 브로드캐스팅은 NumPy와 사실상 동일합니다.
1
2
3
t[0] # 첫 행
t[:, 1] # 두 번째 열
t[t > 0] # 양수만 (불리언 마스크)
연산도 마찬가지입니다. 원소별 연산과 행렬 곱을 구분하는 것만 익히면 됩니다.
1
2
3
4
a * b # 원소별 곱
a @ b # 행렬 곱 (matmul)
t.sum(dim=0) # 열 방향 합 — NumPy의 axis=0에 해당
t.mean(dim=1) # 행 방향 평균
NumPy의 axis가 PyTorch에서는 dim으로 이름만 바뀝니다. 모양을 바꾸는 것도 비슷하지만, 여기에 주의할 지점이 하나 있습니다.
1
2
t.reshape(3, 2) # 모양 바꾸기 (필요하면 복사)
t.view(3, 2) # 모양 바꾸기 (메모리 공유, 연속 텐서에만)
view는 데이터를 복사하지 않고 같은 메모리를 다른 모양으로 보는 것이라 빠르지만, 텐서가 메모리에 연속으로 놓여 있어야만 됩니다. 애매하면 reshape을 쓰면 알아서 처리합니다.
NumPy와 오가기: 메모리를 공유한다
텐서와 NumPy 배열은 서로 변환됩니다. 여기에 꼭 알아야 할 함정이 있습니다. CPU에서 이 변환은 데이터를 복사하지 않고 메모리를 공유합니다.
1
2
3
4
5
6
7
import numpy as np
a = np.array([1., 2., 3.])
t = torch.from_numpy(a) # NumPy → 텐서 (메모리 공유)
a[0] = 99
t # tensor([99., 2., 3.]) — 한쪽을 바꾸면 다른 쪽도 바뀐다
반대 방향인 t.numpy()도 마찬가지로 메모리를 공유합니다. 의도치 않은 값 변경을 피하려면 t.clone()이나 a.copy()로 명시적으로 복사해야 합니다. NumPy와의 이 다리 덕분에, 앞선 시리즈에서 익힌 pandas·NumPy로 데이터를 준비한 뒤 텐서로 넘겨 학습하는 흐름이 자연스럽게 이어집니다.
device: CPU와 GPU
여기가 NumPy에 없는 부분입니다. 텐서는 어느 장치(device)에 놓일지를 가집니다. 기본은 CPU이고, GPU로 옮기면 그 위에서 연산이 병렬로 빠르게 돕니다.
1
2
3
4
torch.cuda.is_available() # GPU(CUDA)를 쓸 수 있는가
device = "cuda" if torch.cuda.is_available() else "cpu"
t = torch.randn(2, 3).to(device) # 텐서를 GPU로
규칙은 단순합니다. 연산에 참여하는 텐서들은 같은 device에 있어야 합니다. CPU 텐서와 GPU 텐서를 바로 더하면 에러가 납니다. 그래서 실무에서는 모델과 입력 데이터를 같은 device로 옮기는 코드가 항상 따라붙는데, 이건 (5)편에서 자세히 다룹니다. (애플 실리콘 맥이라면 "cuda" 대신 "mps"를 쓸 수 있습니다.)
정리
| 개념 | NumPy와 비교 | 한 줄 요약 |
|---|---|---|
| 생성 | 거의 동일 | tensor, zeros, randn, arange |
| 기본 dtype | 다름 | 실수는 float32(NumPy는 float64) |
| 인덱싱·연산 | 동일 | 마스크·브로드캐스팅 그대로, axis→dim |
| view/reshape | PyTorch 특유 | view는 메모리 공유(연속 필요), reshape은 안전 |
| NumPy 변환 | 주의 | from_numpy·numpy()는 메모리 공유 → 필요시 복사 |
| device | NumPy에 없음 | 텐서는 CPU/GPU에 놓임. 연산은 같은 device에서만 |
텐서는 “무엇을 담는가”입니다. 다음 편은 PyTorch를 NumPy와 결정적으로 다르게 만드는 것 — 이 텐서들의 연산을 기록해 자동으로 미분하는 autograd입니다. 1편의 딥러닝 기초에서 손으로 짰던 역전파가 여기서 backward() 한 줄이 됩니다.