파이토치 프레임워크 정리 문서
이 문서는 파이토치의 핵심 개념을 처음부터 차근차근 이해할 수 있도록 정리한 문서이다.
파이토치의 기본 자료구조인 텐서, 차원 개념, GPU 사용, 텐서 연산, 차원 조작, 자동 미분과 기울기 계산까지 한 흐름으로 연결해서 설명한다.
1. 파이토치란 무엇인가
파이토치는 파이썬 기반의 딥러닝 프레임워크이다.
딥러닝 모델을 만들 때 필요한 텐서 연산, GPU 가속, 자동 미분 기능을 제공하며, 연구와 실무 양쪽에서 매우 널리 사용된다.
파이토치의 가장 큰 특징은 다음과 같다.
- 텐서를 중심으로 연산을 구성한다.
- GPU를 이용해 대규모 행렬 연산을 빠르게 수행할 수 있다.
- 자동 미분 기능으로 역전파 계산을 직접 구현하지 않아도 된다.
- 동적 계산 그래프를 사용해 코드 흐름이 직관적이다.
설치는 다음처럼 진행한다.
pip install torch
2. 동적 계산 그래프
파이토치는 계산 그래프를 미리 고정하지 않고, 코드를 실행하는 시점에 연산 흐름에 따라 그래프를 만든다.
이 방식을 동적 계산 그래프라고 한다.
이 구조의 장점은 다음과 같다.
- 조건문과 반복문을 자연스럽게 모델 안에 넣을 수 있다.
- 중간 결과를 바로 확인하기 쉽다.
- 디버깅과 구조 수정이 편하다.
즉, 파이토치는 수학 연산을 단순히 계산하는 데서 끝나지 않고, 어떤 연산이 어떤 연산으로 이어졌는지까지 함께 추적한다.
3. 텐서의 개념
파이토치의 핵심 자료구조는 텐서이다.
텐서는 다차원 배열이며, NumPy 배열과 비슷하지만 딥러닝에 필요한 기능이 추가되어 있다.
텐서는 다음처럼 이해할 수 있다.
- 0차원 텐서: 스칼라
- 1차원 텐서: 벡터
- 2차원 텐서: 행렬
- 3차원 이상 텐서: 다차원 배열
딥러닝에서는 입력 데이터, 모델의 가중치, 출력값, 손실값까지 거의 모든 것을 텐서로 다룬다.
4. torch.tensor()
torch.tensor()는 파이썬 숫자, 리스트, 중첩 리스트를 파이토치 텐서로 만드는 가장 기본적인 함수이다.
import torch
x = torch.tensor(5)
print(x)
print(x.shape)
# 결과
# tensor(5)
# torch.Size([])
숫자 하나만 넣으면 0차원 텐서가 만들어진다.
x = torch.tensor([1.0, 2.0, 3.0])
print(x)
print(x.shape)
# 결과
# tensor([1., 2., 3.])
# torch.Size([3])
리스트를 넣으면 1차원 텐서가 만들어진다.
x = torch.tensor([[1, 2], [3, 4]])
print(x)
print(x.shape)
# 결과
# tensor([[1, 2],
# [3, 4]])
# torch.Size([2, 2])
중첩 리스트를 넣으면 2차원 텐서가 된다.
5. 스칼라와 item()
스칼라는 숫자 하나만 담는 값이며, 파이토치에서는 0차원 텐서로 표현된다.
x = torch.tensor(8)
print(x)
print(x.shape)
# 결과
# tensor(8)
# torch.Size([])
원소가 하나뿐인 텐서는 item()을 사용해 파이썬 숫자로 꺼낼 수 있다.
print(x.item())
# 결과
# 8
손실값처럼 스칼라 텐서를 로그로 출력할 때 자주 사용한다.
6. 1차원 텐서와 벡터
벡터는 여러 원소가 일렬로 나열된 1차원 텐서이다.
shape는 보통 다음처럼 표현된다.
x = torch.tensor([1.0, 2.0, 3.0])
print(x)
print(x.shape)
# 결과
# tensor([1., 2., 3.])
# torch.Size([3])
벡터는 스칼라와도 연산할 수 있다.
x = torch.tensor([1.0, 2.0, 3.0])
y = x + 10
print(y)
# 결과
# tensor([11., 12., 13.])
벡터끼리는 같은 shape일 때 원소별 연산이 수행된다.
a = torch.tensor([1.0, 2.0, 3.0])
b = torch.tensor([4.0, 5.0, 6.0])
print(a + b)
print(a * b)
# 결과
# tensor([5., 7., 9.])
# tensor([ 4., 10., 18.])
이때 숫자 하나가 모든 원소에 자동으로 적용되는 현상은 브로드캐스팅의 기본 형태로 이해할 수 있다.
7. 2차원 텐서와 행렬
행렬은 2차원 텐서이며, 보통 shape가 행 개수와 열 개수 형태로 나타난다.
a = torch.tensor([[1, 2],
[3, 4]])
b = torch.tensor([[5, 6],
[7, 8]])
print(a)
print(a.shape)
# 결과
# tensor([[1, 2],
# [3, 4]])
# torch.Size([2, 2])
같은 shape의 행렬끼리는 원소별 연산이 가능하다.
print(a + b)
print(a * b)
# 결과
# tensor([[ 6, 8],
# [10, 12]])
# tensor([[ 5, 12],
# [21, 32]])
이 곱셈은 행렬 곱셈이 아니라 원소별 곱셈이다.
행렬 곱셈은 별도의 연산을 사용해야 한다.
8. 행렬 곱셈과 torch.mm(), torch.matmul(), @
행렬 곱셈은 일반 사칙연산과 다르며, torch.mm(), torch.matmul(), 또는 @ 연산자를 사용한다.
a = torch.tensor([[1, 2],
[3, 4]])
b = torch.tensor([[5, 6],
[7, 8]])
print(torch.mm(a, b))
print(torch.matmul(a, b))
print(a @ b)
# 결과
# tensor([[19, 22],
# [43, 50]])
# tensor([[19, 22],
# [43, 50]])
# tensor([[19, 22],
# [43, 50]])
딥러닝의 선형층 연산은 결국 이런 행렬 곱셈을 기반으로 동작한다.
차이점은 다음처럼 이해하면 된다.
- torch.mm()는 2차원 행렬 곱셈에 집중된 함수
- torch.matmul()은 더 일반적인 텐서 곱셈까지 다룰 수 있는 함수
- @는 파이썬 연산자 문법
9. 다차원 텐서
3차원 이상을 가지는 텐서를 다차원 텐서라고 부른다.
t = torch.tensor([
[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]],
[[5, 6],
[7, 8]]
])
print(t)
print(t.shape)
# 결과
# tensor([[[1, 2],
# [3, 4]],
#
# [[5, 6],
# [7, 8]],
#
# [[5, 6],
# [7, 8]]])
# torch.Size([3, 2, 2])
이 텐서는 shape가 3, 2, 2인 3차원 텐서이다.
이미지, 영상, 시계열, 배치 데이터는 보통 이런 다차원 구조를 가진다.
예를 들어 이미지 데이터는 다음처럼 표현될 수 있다.
- 흑백 이미지 한 장: 높이, 너비
- 컬러 이미지 한 장: 채널, 높이, 너비
- 여러 장의 이미지 배치: 배치, 채널, 높이, 너비
10. NumPy와의 변환
파이토치는 NumPy와 쉽게 변환할 수 있다.
텐서를 NumPy 배열로 바꾸는 방법은 다음과 같다.
x = torch.tensor([1.0, 2.0, 3.0])
arr = x.numpy()
print(arr)
print(type(arr))
# 결과
# [1. 2. 3.]
# <class 'numpy.ndarray'>
NumPy 배열을 텐서로 바꾸는 방법은 다음과 같다.
import numpy as np
arr = np.array([4.0, 5.0, 6.0])
t = torch.from_numpy(arr)
print(t)
print(type(t))
# 결과
# tensor([4., 5., 6.], dtype=torch.float64)
# <class 'torch.Tensor'>
전처리는 NumPy나 Pandas로 하고, 모델 연산은 파이토치로 하는 경우가 많기 때문에 이 변환은 매우 자주 사용한다.
주의할 점은 GPU 텐서는 바로 numpy()를 사용할 수 없다는 점이다.
GPU 텐서는 먼저 CPU로 옮겨야 한다.
11. 텐서 인덱싱과 슬라이싱
파이토치 텐서는 NumPy처럼 인덱싱과 슬라이싱이 가능하다.
t = torch.tensor([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
])
print(t[0])
print(t[:, 0])
print(t[:, -1])
print(t[..., -1])
# 결과
# tensor([1, 2, 3, 4])
# tensor([1, 5, 9])
# tensor([ 4, 8, 12])
# tensor([ 4, 8, 12])
각 표현은 다음 의미를 가진다.
- t[0] : 첫 번째 행
- t[:, 0] : 첫 번째 열
- t[:, -1] : 마지막 열
- t[..., -1] : 마지막 차원의 마지막 원소들
3차원 텐서에서 ... 은 특히 유용하다.
t = torch.tensor([
[[1, 2, 3],
[4, 5, 6]],
[[7, 8, 9],
[10, 11, 12]]
])
print(t.shape)
print(t[..., -1])
# 결과
# torch.Size([2, 2, 3])
# tensor([[ 3, 6],
# [ 9, 12]])
... 은 앞쪽 차원들을 모두 유지한 채, 마지막 축 기준으로 선택할 때 편리하다.
12. GPU 사용과 디바이스 개념
딥러닝에서는 대규모 행렬 연산이 많기 때문에 GPU 사용이 매우 중요하다.
파이토치는 텐서를 GPU로 옮겨 연산할 수 있도록 지원한다.
x = torch.tensor([[1., 2.], [3., 4.]])
print(x.is_cuda)
# 결과
# False
GPU로 옮기면 다음처럼 바뀐다.
x = x.cuda()
print(x.is_cuda)
# 결과
# True
다시 CPU로 옮길 수도 있다.
x = x.cpu()
print(x.is_cuda)
# 결과
# False
현재 텐서가 어느 장치에 있는지는 device로 확인할 수 있다.
x = torch.tensor([[1., 2.], [3., 4.]])
print(x.device)
# 결과
# cpu
GPU가 있다면 다음처럼 장치를 더 안전하게 선택할 수 있다.
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
x = torch.tensor([[1., 2.], [3., 4.]]).to(device)
print(x.device)
# 결과
# cuda:0 또는 cpu
가장 중요한 점은 연산하려는 텐서들이 반드시 같은 디바이스에 있어야 한다는 것이다.
CPU 텐서와 GPU 텐서는 바로 함께 연산할 수 없다.
13. 기본 사칙연산
파이토치 텐서는 덧셈, 뺄셈, 곱셈, 나눗셈을 지원한다.
a = torch.tensor([[1., 2.],
[3., 4.]])
b = torch.tensor([[5., 6.],
[7., 8.]])
print(a + b)
print(a - b)
print(a * b)
print(a / b)
# 결과
# tensor([[ 6., 8.],
# [10., 12.]])
# tensor([[-4., -4.],
# [-4., -4.]])
# tensor([[ 5., 12.],
# [21., 32.]])
# tensor([[0.2000, 0.3333],
# [0.4286, 0.5000]])
이 연산들은 모두 원소별로 수행된다.
14. mean(), sum(), argmax()
딥러닝에서는 평균, 합계, 최댓값 위치를 계산하는 일이 매우 많다.
x = torch.tensor([[1., 2., 3.],
[4., 5., 6.]])
print(x.mean())
print(x.mean(dim=0))
print(x.mean(dim=1))
# 결과
# tensor(3.5000)
# tensor([2.5000, 3.5000, 4.5000])
# tensor([2., 5.])
mean은 평균을 계산한다.
- mean()은 전체 평균
- mean(dim=0)은 열 방향 평균
- mean(dim=1)은 행 방향 평균
합계도 같은 방식이다.
print(x.sum())
print(x.sum(dim=0))
print(x.sum(dim=1))
# 결과
# tensor(21.)
# tensor([5., 7., 9.])
# tensor([ 6., 15.])
argmax는 최댓값의 위치를 찾는다.
print(x.argmax())
print(x.argmax(dim=0))
print(x.argmax(dim=1))
# 결과
# tensor(5)
# tensor([1, 1, 1])
# tensor([2, 2])
분류 문제에서는 보통 클래스별 점수 중 가장 큰 위치를 argmax로 선택해 예측 클래스를 구한다.
scores = torch.tensor([
[0.1, 0.9, 0.0],
[0.3, 0.2, 0.5]
])
print(scores.argmax(dim=1))
# 결과
# tensor([1, 2])
15. cat()
cat은 여러 텐서를 특정 축 기준으로 이어 붙이는 함수이다.
a = torch.tensor([[1, 2],
[3, 4]])
b = torch.tensor([[5, 6],
[7, 8]])
print(torch.cat([a, b], dim=0))
print(torch.cat([a, b], dim=1))
# 결과
# tensor([[1, 2],
# [3, 4],
# [5, 6],
# [7, 8]])
# tensor([[1, 2, 5, 6],
# [3, 4, 7, 8]])
- dim=0은 행 방향으로 이어 붙인다.
- dim=1은 열 방향으로 이어 붙인다.
딥러닝에서는 여러 배치를 합치거나, feature를 결합할 때 자주 사용한다.
16. dtype와 형변환
텐서는 데이터 타입을 가진다.
정수형과 실수형이 섞여 있으면 연산 과정에서 형이 자동으로 맞춰질 수 있다.
a = torch.tensor([2], dtype=torch.int32)
b = torch.tensor([5.0])
print(a.dtype)
print(b.dtype)
print(a + b)
# 결과
# torch.int32
# torch.float32
# tensor([7.])
필요하면 명시적으로 형변환할 수도 있다.
x = torch.tensor([1, 2, 3], dtype=torch.int32)
y = x.float()
print(x.dtype)
print(y.dtype)
# 결과
# torch.int32
# torch.float32
딥러닝에서는 보통 float32를 가장 많이 사용한다.
17. view() 와 clone()
view는 텐서의 shape를 바꾸는 메서드이다.
데이터를 복사하는 것이 아니라, 같은 데이터를 다른 모양으로 해석하는 방식이다.
x = torch.tensor([1, 2, 3, 4, 5, 6])
y = x.view(2, 3)
print(y)
# 결과
# tensor([[1, 2, 3],
# [4, 5, 6]])
더 많은 차원으로도 바꿀 수 있다.
x = torch.tensor([1, 2, 3, 4, 5, 6, 7, 8])
y = x.view(2, 2, 2)
print(y)
# 결과
# tensor([[[1, 2],
# [3, 4]],
#
# [[5, 6],
# [7, 8]]])
view는 원본과 메모리를 공유할 수 있으므로 원본 변경이 영향을 줄 수 있다.
이 영향을 피하고 싶다면 clone으로 복사한 뒤 view를 사용한다.
x = torch.tensor([1, 2, 3, 4])
y = x.clone().view(2, 2)
print(y)
# 결과
# tensor([[1, 2],
# [3, 4]])
18. permute()
permute는 차원의 순서를 바꾸는 메서드이다.
view가 shape를 다시 해석하는 데 가깝다면,
permute는 축 자체의 순서를 재배치한다는 점이 중요하다.
x = torch.rand((32, 32, 3))
y = x.permute(2, 0, 1)
print(x.shape)
print(y.shape)
# 결과
# torch.Size([32, 32, 3])
# torch.Size([3, 32, 32])
이미지 데이터는 종종 높이, 너비, 채널 순서로 들어오는데,
딥러닝 모델은 채널, 높이, 너비 순서를 요구하는 경우가 많다.
이럴 때 permute를 사용한다.
19. unsqueeze() 와 squeeze()
딥러닝에서는 batch 차원이나 channel 차원을 맞추기 위해 크기 1인 축을 추가하거나 제거해야 할 때가 많다.
이때 unsqueeze와 squeeze를 사용한다.
unsqueeze는 크기 1인 차원을 추가한다.
x = torch.tensor([1, 2, 3, 4])
print(x.shape)
x = x.unsqueeze(0)
print(x.shape)
# 결과
# torch.Size([4])
# torch.Size([1, 4])
squeeze는 크기 1인 차원을 제거한다.
x = x.squeeze()
print(x.shape)
# 결과
# torch.Size([4])
2차원 텐서에도 차원을 추가할 수 있다.
x = torch.tensor([[1, 2, 3, 4],
[5, 6, 7, 8]])
print(x.shape)
x = x.unsqueeze(0)
print(x.shape)
# 결과
# torch.Size([2, 4])
# torch.Size([1, 2, 4])
이런 연산은 모델 입력 형식을 맞출 때 매우 자주 사용한다.
20. 자동 미분과 requires_grad
파이토치의 가장 중요한 기능 중 하나는 자동 미분이다.
딥러닝 모델 학습에서는 손실 함수를 파라미터에 대해 미분한 값, 즉 기울기가 필요하다.
파이토치는 연산 과정을 추적한 뒤 backward를 호출하면 자동으로 기울기를 계산해 준다.
기울기를 추적할 텐서에는 requires_grad=True를 설정한다.
x = torch.tensor([3.0, 16.0], requires_grad=True)
y = torch.tensor([10.0, 4.0], requires_grad=True)
print(x.requires_grad, y.requires_grad)
# 결과
# True True
이제 x와 y를 사용한 연산은 계산 그래프에 기록된다.
21. 계산 그래프와 mean()
다음처럼 텐서 연산을 수행하면 파이토치는 연산 관계를 자동으로 추적한다.
x = torch.tensor([3.0, 16.0], requires_grad=True)
y = torch.tensor([10.0, 4.0], requires_grad=True)
z = x + 2 * y
out = z.mean()
print(z)
print(out)
# 결과
# tensor([23., 24.], grad_fn=<AddBackward0>)
# tensor(23.5000, grad_fn=<MeanBackward0>)
수식으로 보면 다음과 같다.
[
z = x + 2y
]
[
out = \frac{z_1 + z_2}{2}
]
즉 out은 x와 y의 함수이므로, out을 x와 y에 대해 미분할 수 있다.
22. backward()
backward는 현재 텐서에서 시작해 계산 그래프를 따라 역전파를 수행하는 메서드이다.
out.backward()
out은 스칼라 텐서이므로 바로 backward를 호출할 수 있다.
이후 requires_grad=True로 설정된 텐서들의 grad에 기울기가 저장된다.
23. grad 와 leaf tensor
역전파가 끝나면 미분 대상 텐서의 grad 속성에 기울기가 저장된다.
print("x.grad:", x.grad)
print("y.grad:", y.grad)
# 결과
# x.grad: tensor([0.5000, 0.5000])
# y.grad: tensor([1., 1.])
이 값을 해석해 보면 다음과 같다.
[
z = x + 2y
]
[
out = mean(z)
]
x는 평균 연산 때문에 각 원소가 절반씩 기여하므로 기울기가 0.5가 된다.
y는 먼저 2배가 곱해지고, 그 뒤 평균에서 절반이 적용되므로 최종 기울기가 1이 된다.
중간 연산 결과인 z의 grad는 기본적으로 저장되지 않는다.
print(z.grad)
# 결과
# None
이유는 z가 leaf tensor가 아니기 때문이다.
leaf tensor는 사용자가 직접 만든 텐서 중에서 requires_grad=True를 가진 텐서를 뜻한다.
x와 y는 leaf tensor이고, z는 연산을 통해 만들어진 중간 텐서이다.
24. 자동 미분의 간단한 예시
자동 미분은 조금 더 단순한 예시로도 이해할 수 있다.
x = torch.tensor([2.0], requires_grad=True)
y = x ** 2
y.backward()
print(x.grad)
# 결과
# tensor([4.])
수식으로 보면
[
y = x^2
]
따라서
[
\frac{dy}{dx} = 2x
]
x가 2일 때 기울기는 4가 되므로 결과와 일치한다.
25. 핵심 메서드 요약
| 메서드 / 속성 | 역할 |
|---|---|
| torch.tensor() | 파이썬 데이터를 텐서로 생성 |
| item() | 스칼라 텐서를 파이썬 숫자로 추출 |
| numpy() | 텐서를 NumPy 배열로 변환 |
| torch.from_numpy() | NumPy 배열을 텐서로 변환 |
| cuda() | 텐서를 GPU로 이동 |
| cpu() | 텐서를 CPU로 이동 |
| is_cuda | GPU 여부 확인 |
| device | 현재 텐서의 디바이스 확인 |
| torch.mm() | 2차원 행렬 곱셈 |
| torch.matmul() | 일반적인 행렬 곱셈 |
| @ | 행렬 곱셈 연산자 |
| mean() | 평균 계산 |
| sum() | 합계 계산 |
| argmax() | 최댓값 위치 반환 |
| cat() | 텐서 이어 붙이기 |
| view() | shape 재구성 |
| clone() | 텐서 복사 |
| permute() | 차원 순서 변경 |
| unsqueeze() | 크기 1인 차원 추가 |
| squeeze() | 크기 1인 차원 제거 |
| requires_grad | 자동 미분 추적 여부 |
| backward() | 역전파 수행 |
| grad | 계산된 기울기 저장 |
26. 전체 흐름 요약
torch.tensor() 로 텐서 생성
↓
스칼라, 벡터, 행렬, 다차원 텐서 이해
↓
item(), numpy(), from_numpy() 확인
↓
인덱싱과 슬라이싱 학습
↓
GPU 이동과 디바이스 개념 확인
↓
사칙연산, 행렬 곱셈, 평균, 합계, argmax 확인
↓
cat(), view(), clone(), permute() 로 차원 조작
↓
unsqueeze(), squeeze() 로 크기 1인 차원 다루기
↓
requires_grad=True 설정
↓
연산을 통해 계산 그래프 생성
↓
backward() 호출
↓
grad 로 기울기 확인
27. 최종 정리
파이토치를 이해할 때 가장 중요한 축은 다음 다섯 가지이다.
- 텐서를 만들고 shape를 이해하는 것
- 텐서 연산과 행렬 곱셈을 이해하는 것
- GPU와 디바이스 개념을 이해하는 것
- view, permute, unsqueeze, squeeze 같은 차원 조작을 익히는 것
- requires_grad, backward, grad를 통해 자동 미분을 이해하는 것
파이토치는 결국 텐서를 중심으로 움직이는 프레임워크이고,
딥러닝 모델 학습은 텐서 연산, 자동 미분, 파라미터 업데이트의 반복이라고 볼 수 있다.
한 문장으로 정리하면 다음과 같다.
파이토치는 텐서를 기반으로 연산을 수행하고, 자동 미분과 GPU 가속을 통해 딥러닝 모델을 효율적으로 학습시키는 프레임워크이다.
'KDT 수업 > 파이썬 공부' 카테고리의 다른 글
| 내가 보려고 정리한 OpenCV 정리 (2) with GPT (0) | 2026.04.01 |
|---|---|
| 내가 보려고 정리한 OpenCV 정리 with GPT (0) | 2026.03.27 |
| 내가 보려고 만든 MatPlotLib 정리 (0) | 2026.03.12 |
| 내가 보려고 만든 크롤링 정리 (0) | 2026.03.12 |
| 내가 보려고 만든 판다스 정리 (0) | 2026.03.12 |