목록으로

프로그래밍 · Python

Python 완전정복 시리즈 #30: PyTorch 완벽 이해하기

BeanCon
Python PyTorch로 유연한 딥러닝 모델을 직접 구현하는 방법을 설명하는 대표 이미지

Python 완전정복 시리즈 30편입니다. PyTorch로 텐서 생성, 자동 미분, Dataset과 DataLoader, nn.Module 기반 신경망, 손실 함수, 옵티마이저, 직접 작성하는 학습 루프를 실습 중심으로 정리했습니다. TensorFlow와 Keras 비교, CPU, CUDA, Apple Silicon MPS 장치 선택, FashionMNIST CNN 프로젝트, 모델 학습과 평가, 예측, 오답 분석, 혼동행렬, state_dict와 체크포인트 저장, AMP, torch.compile, 재현 가능한 실험, 과적합 완화, 프로젝트 구조와 자주 발생하는 오류까지 다룹니다.

목차

메타 설명
PyTorch는 TensorFlow와 무엇이 다를까요? 텐서 생성부터 자동 미분, Dataset과 DataLoader, nn.Module 기반 신경망, 손실 함수, 옵티마이저, 직접 작성하는 학습 루프, GPU·Apple Silicon 가속, 모델 저장, 혼합 정밀도까지 PyTorch 딥러닝의 핵심을 실습 중심으로 알아봅니다.

지난 시간에는 TensorFlow와 Keras를 이용해 첫 번째 인공신경망을 만들었습니다.

Keras에서는 다음과 같은 코드로 모델을 학습할 수 있었습니다.

model.compile(
    optimizer="adam",
    loss="sparse_categorical_crossentropy",
    metrics=["accuracy"]
)

model.fit(
    train_images,
    train_labels,
    epochs=10
)

데이터를 넣고 `fit()`을 호출하면 Keras가 다음 과정을 대부분 알아서 처리했습니다.

데이터를 배치로 나누기
→ 순전파
→ 손실 계산
→ 역전파
→ 가중치 수정
→ 학습 기록 출력

자동차로 비유하면 목적지를 입력하고 출발 버튼을 누른 셈입니다.

그런데 딥러닝을 깊이 공부하거나 새로운 모델 구조를 실험하다 보면 이런 생각이 들 수 있습니다.

배치마다 어떤 계산이 수행되는지 직접 보고 싶다.

손실을 두 개 조합하고 싶다.

특정 조건에서만 역전파하고 싶다.

여러 모델을 동시에 학습하고 싶다.

학습 도중 가중치를 특별한 방식으로 수정하고 싶다.

논문에 나온 새로운 구조를 직접 구현하고 싶다.

자동 조종석은 편리하지만, 가끔은 운전대와 기어를 직접 잡고 싶은 순간이 찾아옵니다.

이번 시간의 주인공 PyTorch는 텐서 계산부터 역전파, 학습 루프까지 Python 코드로 직접 제어할 수 있는 딥러닝 프레임워크입니다.

Keras:
“학습 과정은 제가 처리하겠습니다.”

PyTorch:
“좋습니다. 그런데 직접 운전해 보시겠습니까?”

이제 딥러닝 엔진룸의 문을 열어 보겠습니다. 🔥🧠

1. PyTorch란?

PyTorch는 텐서 연산과 자동 미분을 기반으로 신경망을 만들고 학습할 수 있는 오픈소스 딥러닝 프레임워크입니다.

CPU뿐 아니라 다양한 가속 장치를 활용할 수 있으며, Python 코드와 자연스럽게 결합되는 유연한 구조를 제공합니다. 공식 프로젝트 소개에서는 PyTorch를 연구에 필요한 유연성과 모듈성, 실제 서비스에 필요한 안정성을 함께 제공하는 오픈소스 딥러닝 프레임워크로 설명합니다.

PyTorch의 대표적인 활용 분야는 다음과 같습니다.

이미지 분류
객체 탐지
자연어 처리
음성 인식
추천 시스템
생성형 AI
시계열 예측
강화학습
그래프 신경망
과학적 머신러닝

2026년 7월 28일 기준 PyPI에 공개된 최신 안정 버전은 PyTorch 2.13.0이며, 해당 버전은 2026년 7월 8일 공개되었습니다.

버전은 빠르게 바뀔 수 있으므로 실제 설치 시점에는 공식 설치 페이지에서 운영체제와 가속 환경에 맞는 명령어를 확인해야 합니다.

2. PyTorch가 인기 있는 이유

Python과 자연스럽게 연결된다

PyTorch 모델은 일반적인 Python 클래스와 제어문을 사용해 작성합니다.

if training:
    output = self.training_layer(x)
else:
    output = self.inference_layer(x)

반복문과 조건문을 모델 계산에 자연스럽게 포함할 수 있습니다.

for layer in self.layers:
    x = layer(x)

이러한 특성 덕분에 새로운 아이디어를 빠르게 실험하기 좋습니다.

학습 과정을 직접 제어할 수 있다

PyTorch에서는 일반적으로 학습 과정을 직접 작성합니다.

optimizer.zero_grad()

predictions = model(inputs)

loss = loss_function(
    predictions,
    targets
)

loss.backward()

optimizer.step()

각 줄이 무엇을 담당하는지 눈으로 확인할 수 있습니다.

자동 미분을 제공한다

복잡한 신경망의 미분을 직접 계산하지 않아도 됩니다.

loss.backward()

이 한 줄을 실행하면 PyTorch가 계산 그래프를 따라 각 가중치의 기울기를 계산합니다.

PyTorch의 `torch.autograd`는 계산 그래프에 대한 기울기를 자동으로 계산하는 내장 미분 엔진입니다.

연구 코드와 실험에 유연하다

새로운 레이어, 손실 함수, 학습 전략을 Python 코드로 직접 만들 수 있습니다.

기존 블록 조립
→ 빠른 모델 구현

새 계산 직접 작성
→ 새로운 연구 실험

PyTorch는 조립식 가구이면서 동시에 목공 작업실입니다.

설명서를 따라 조립할 수도 있고, 필요하면 직접 나무를 깎을 수도 있습니다. 🪚

3. TensorFlow·Keras와 PyTorch 비교

두 프레임워크 모두 강력하며 많은 딥러닝 문제를 해결할 수 있습니다.

핵심 차이는 주로 사용 방식과 추상화 수준에서 나타납니다.

구분TensorFlow·KerasPyTorch
입문 방식고수준 API 중심직접 작성하는 흐름
모델 생성Sequential·Functional API`nn.Module` 클래스
기본 학습`model.fit()`학습 루프 직접 작성
역전파내부에서 처리`loss.backward()` 호출
가중치 수정내부에서 처리`optimizer.step()` 호출
데이터 로딩Dataset APIDataset·DataLoader
실험 제어고수준 기능 제공세부 흐름 직접 제어
코드 성격선언적 구조가 많음일반 Python 코드에 가까움

Keras 방식

model.fit(
    train_images,
    train_labels,
    epochs=10
)

PyTorch 방식

for epoch in range(10):
    for inputs, targets in train_loader:
        optimizer.zero_grad()

        outputs = model(inputs)

        loss = criterion(
            outputs,
            targets
        )

        loss.backward()

        optimizer.step()

PyTorch 코드가 더 길어 보입니다.

하지만 길어진 코드 속에 학습의 각 단계가 그대로 드러납니다.

Keras:
요리 완성 버튼

PyTorch:
칼질, 불 조절, 간 맞추기까지 직접

어느 방식이 무조건 우월한 것은 아닙니다.

빠르게 기준 모델을 만들 때는 Keras가 편리할 수 있고, 복잡한 학습 과정을 실험할 때는 PyTorch의 직접 제어가 유리할 수 있습니다.

4. PyTorch 설치하기

PyTorch 설치 명령은 다음 조건에 따라 달라질 수 있습니다.

Windows·Linux·macOS

CPU만 사용하는지

NVIDIA CUDA를 사용하는지

AMD ROCm을 사용하는지

사용할 Python 버전

따라서 GPU 환경에서는 공식 설치 선택기에서 운영체제와 가속 플랫폼을 고른 뒤 생성되는 명령을 사용하는 것이 안전합니다. 공식 설치 페이지도 CPU, CUDA, ROCm 등의 환경에 따라 서로 다른 설치 명령을 제공합니다.

CPU 중심 기본 설치

일반적인 환경에서는 다음과 같이 설치할 수 있습니다.

python -m pip install torch torchvision

`torchvision`은 이미지 데이터셋, 이미지 변환, 사전 학습 모델 등 컴퓨터 비전 기능을 제공합니다.

가상환경 만들기

Windows

python -m venv venv
venv\Scripts\activate

python -m pip install --upgrade pip
python -m pip install torch torchvision matplotlib

macOS 또는 Linux

python3 -m venv venv
source venv/bin/activate

python -m pip install --upgrade pip
python -m pip install torch torchvision matplotlib

NVIDIA GPU 환경

CUDA 버전에 따라 별도 패키지 저장소 주소가 사용될 수 있습니다.

GPU 설치 명령은 시스템마다 다르므로 임의의 CUDA 명령을 복사하기보다 공식 설치 선택기에서 다음 항목을 선택합니다.

운영체제
패키지 관리자
Python
CUDA 버전

GPU 드라이버와 CUDA 관련 구성은 꽤 예민합니다.

PyTorch:
“CUDA 준비됐습니까?”

운영체제:
“아마도요.”

드라이버:
“저는 처음 듣는 이야기입니다.”

이 삼자대화를 피하려면 버전 호환성을 먼저 확인해야 합니다.

5. 설치 및 가속 장치 확인하기

PyTorch 버전 확인

import torch

print(
    torch.__version__
)

CUDA 사용 가능 여부

print(
    torch.cuda.is_available()
)

CUDA 장치 개수

print(
    torch.cuda.device_count()
)

첫 번째 GPU 이름

if torch.cuda.is_available():
    print(
        torch.cuda.get_device_name(0)
    )

Apple Silicon MPS 확인

Apple Silicon Mac에서는 Metal Performance Shaders 기반 MPS 장치를 확인할 수 있습니다.

print(
    torch.backends.mps.is_available()
)

현재 사용할 장치 선택

def select_device() -> torch.device:
    if torch.cuda.is_available():
        return torch.device("cuda")

    if torch.backends.mps.is_available():
        return torch.device("mps")

    return torch.device("cpu")


device = select_device()

print(
    f"사용 장치: {device}"
)

이제 Windows의 NVIDIA GPU, Apple Silicon의 MPS, 일반 CPU 환경을 하나의 코드로 처리할 수 있습니다.

6. 텐서란?

텐서는 다차원 숫자 배열입니다.

NumPy의 `ndarray`와 비슷하지만 PyTorch 텐서는 다음 기능을 추가로 제공합니다.

GPU·MPS 같은 가속 장치 연산

자동 미분

신경망 모듈과 직접 연결

분산 연산

0차원 텐서

import torch

scalar = torch.tensor(7)

print(scalar)
print(scalar.ndim)

1차원 텐서

vector = torch.tensor([
    10,
    20,
    30
])

print(vector)
print(vector.shape)

2차원 텐서

matrix = torch.tensor([
    [1, 2, 3],
    [4, 5, 6]
])

print(matrix)
print(matrix.shape)

3차원 텐서

tensor_3d = torch.tensor([
    [
        [1, 2],
        [3, 4]
    ],
    [
        [5, 6],
        [7, 8]
    ]
])

print(tensor_3d.shape)

결과:

torch.Size([2, 2, 2])

이미지는 보통 다음 형태로 표현됩니다.

채널 × 높이 × 너비

예:

RGB 이미지:
3 × 224 × 224

여러 이미지를 배치로 묶으면 차원이 하나 더 추가됩니다.

배치 × 채널 × 높이 × 너비

예:

32 × 3 × 224 × 224

텐서의 `shape`를 읽을 줄 알면 딥러닝 오류의 절반은 이미 용의자를 확보한 셈입니다.

7. 텐서 생성하기

직접 값 입력

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

0으로 채우기

zeros = torch.zeros(
    2,
    3
)

print(zeros)

1로 채우기

ones = torch.ones(
    2,
    3
)

print(ones)

무작위 값

random_values = torch.rand(
    2,
    3
)

print(random_values)

`torch.rand()`는 0 이상 1 미만 범위의 균등분포 난수를 생성합니다.

정규분포 난수

normal_values = torch.randn(
    2,
    3
)

print(normal_values)

일정한 간격

numbers = torch.arange(
    start=0,
    end=10,
    step=2
)

print(numbers)

결과:

tensor([0, 2, 4, 6, 8])

균등하게 나눈 값

numbers = torch.linspace(
    start=0,
    end=10,
    steps=5
)

print(numbers)

결과:

tensor([ 0.0000,  2.5000,  5.0000,  7.5000, 10.0000])

기존 텐서와 같은 형태

original = torch.tensor([
    [1, 2],
    [3, 4]
])

zeros = torch.zeros_like(
    original
)

ones = torch.ones_like(
    original
)

8. 텐서의 핵심 속성

tensor = torch.tensor(
    [
        [1, 2, 3],
        [4, 5, 6]
    ],
    dtype=torch.float32
)

shape

print(
    tensor.shape
)

결과:

torch.Size([2, 3])

ndim

print(
    tensor.ndim
)

dtype

print(
    tensor.dtype
)

결과:

torch.float32

device

print(
    tensor.device
)

결과 예:

cpu

전체 요소 수

print(
    tensor.numel()
)

결과:

6

대표 데이터 타입

타입설명
`torch.float32`일반적인 실수 연산
`torch.float64`높은 정밀도의 실수
`torch.float16`반정밀도 실수
`torch.bfloat16`넓은 지수 범위의 저정밀도 실수
`torch.int64`정수 레이블 등에 사용
`torch.int32`32비트 정수
`torch.bool`참·거짓

딥러닝 모델의 입력값은 보통 `float32`, 다중 분류 정답은 보통 `int64` 형식으로 사용합니다.

inputs = inputs.float()
targets = targets.long()

자료형이 맞지 않으면 PyTorch는 꽤 직설적으로 불만을 표시합니다.

PyTorch:
“Float를 기대했는데 Long이 왔습니다.”

개발자:
“숫자는 다 같은 숫자 아닌가요?”

PyTorch:
“그 말씀은 회계팀 앞에서 다시 해보시죠.”

9. NumPy와 텐서 변환하기

NumPy 배열을 텐서로 변환

import numpy as np
import torch

array = np.array(
    [1, 2, 3],
    dtype=np.float32
)

tensor = torch.from_numpy(
    array
)

print(tensor)

텐서를 NumPy 배열로 변환

array = tensor.numpy()

print(array)

CPU 텐서만 바로 NumPy 배열로 변환할 수 있습니다.

GPU 텐서는 먼저 CPU로 이동해야 합니다.

array = (
    gpu_tensor
    .detach()
    .cpu()
    .numpy()
)

메모리 공유 주의

`torch.from_numpy()`로 생성한 텐서와 원본 NumPy 배열은 메모리를 공유할 수 있습니다.

array = np.array(
    [1, 2, 3],
    dtype=np.float32
)

tensor = torch.from_numpy(
    array
)

array[0] = 999

print(tensor)

결과:

tensor([999.,   2.,   3.])

독립된 복사본이 필요하면 `clone()`이나 `copy()`를 사용합니다.

tensor = torch.from_numpy(
    array.copy()
)

10. 텐서 인덱싱과 슬라이싱

NumPy와 매우 비슷한 방식으로 접근할 수 있습니다.

tensor = torch.tensor([
    [10, 20, 30],
    [40, 50, 60],
    [70, 80, 90]
])

특정 값

print(
    tensor[0, 1]
)

결과:

tensor(20)

Python 숫자로 가져오려면 `item()`을 사용합니다.

value = tensor[0, 1].item()

print(value)

행 선택

print(
    tensor[1]
)

열 선택

print(
    tensor[:, 1]
)

부분 영역

print(
    tensor[0:2, 1:3]
)

조건 필터링

result = tensor[
    tensor >= 50
]

print(result)

결과:

tensor([50, 60, 70, 80, 90])

11. 텐서 연산

사칙연산

a = torch.tensor([
    1.0,
    2.0,
    3.0
])

b = torch.tensor([
    10.0,
    20.0,
    30.0
])

print(a + b)
print(a - b)
print(a * b)
print(a / b)

스칼라 연산

print(
    a * 10
)

제곱

print(
    a ** 2
)

합계와 평균

print(
    a.sum()
)

print(
    a.mean()
)

최댓값 위치

scores = torch.tensor([
    0.1,
    0.7,
    0.2
])

predicted_class = scores.argmax()

print(
    predicted_class.item()
)

형태 변경

numbers = torch.arange(
    1,
    13
)

matrix = numbers.reshape(
    3,
    4
)

print(matrix)

차원 추가

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

row = vector.unsqueeze(0)
column = vector.unsqueeze(1)

print(row.shape)
print(column.shape)

결과:

torch.Size([1, 3])
torch.Size([3, 1])

크기가 1인 차원 제거

restored = row.squeeze(0)

print(
    restored.shape
)

행렬 곱셈

a = torch.tensor([
    [1.0, 2.0],
    [3.0, 4.0]
])

b = torch.tensor([
    [5.0, 6.0],
    [7.0, 8.0]
])

result = a @ b

print(result)

다음 방식도 사용할 수 있습니다.

result = torch.matmul(
    a,
    b
)

12. 브로드캐스팅

크기가 다른 텐서라도 일정한 규칙을 만족하면 연산할 수 있습니다.

matrix = torch.tensor([
    [10, 20, 30],
    [40, 50, 60]
])

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

print(
    matrix + offset
)

결과:

tensor([
    [11, 22, 33],
    [41, 52, 63]
])

작은 텐서가 각 행에 적용됩니다.

matrix:
(2, 3)

offset:
(3,)

→ 마지막 차원의 크기가 같아 연산 가능

브로드캐스팅 오류가 발생하면 두 텐서의 형태부터 확인합니다.

print(a.shape)
print(b.shape)

딥러닝의 많은 사건은 복잡한 수학이 아니라 잘못 배치된 괄호와 `shape`에서 시작됩니다.

13. 텐서를 장치로 이동하기

GPU나 MPS를 이용하려면 텐서와 모델을 같은 장치로 이동해야 합니다.

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

tensor = torch.tensor([
    1.0,
    2.0,
    3.0
])

tensor = tensor.to(
    device
)

print(
    tensor.device
)

모델 이동

model = model.to(
    device
)

배치 이동

inputs = inputs.to(
    device
)

targets = targets.to(
    device
)

중요한 규칙:

모델:
cuda

입력:
cpu

→ 함께 계산할 수 없음

모델과 입력은 같은 회의실에 있어야 합니다.

GPU 모델에게 CPU 데이터를 전달하면 PyTorch는 원격 회의를 자동으로 연결해 주지 않습니다.

14. 자동 미분 Autograd

다음 함수를 생각해 보겠습니다.

y = x² + 3x + 1

`x=2`일 때 미분값은 다음과 같습니다.

dy/dx = 2x + 3

x=2
→ 7

PyTorch로 계산해 보겠습니다.

import torch

x = torch.tensor(
    2.0,
    requires_grad=True
)

y = (
    x ** 2
    + 3 * x
    + 1
)

y.backward()

print(
    x.grad
)

결과:

tensor(7.)

requires_grad

requires_grad=True

PyTorch에게 이 텐서와 관련된 연산을 추적해 기울기를 계산할 준비를 하라고 지시합니다.

backward

y.backward()

현재 결과에서 시작해 계산 그래프를 역방향으로 이동하며 기울기를 계산합니다.

grad

x.grad

계산된 기울기가 저장됩니다.

Autograd는 미분 계산을 대신해 주는 회계 감사팀과 같습니다.

최종 손실이 발생했을 때 각 가중치가 손실에 얼마나 기여했는지 추적합니다.

15. 계산 그래프 이해하기

PyTorch는 텐서 연산이 수행될 때 계산 관계를 기록합니다.

x = torch.tensor(
    2.0,
    requires_grad=True
)

a = x * 3
b = a ** 2
loss = b + 1

개념적인 계산 그래프:

x
→ × 3
→ 제곱
→ + 1
→ loss

역전파:

loss
→ +1의 미분
→ 제곱의 미분
→ ×3의 미분
→ x의 기울기

PyTorch는 실행된 연산을 기반으로 동적인 계산 그래프를 구성합니다.

일반적인 Python 조건문도 모델 계산에 포함할 수 있습니다.

def calculate(x):
    if x.mean() > 0:
        return x ** 2

    return x ** 3

입력에 따라 실제 실행 경로가 달라질 수 있습니다.

16. 기울기 누적과 초기화

PyTorch에서는 기본적으로 기울기가 누적됩니다.

x = torch.tensor(
    2.0,
    requires_grad=True
)

y = x ** 2
y.backward()

print(x.grad)

결과:

tensor(4.)

다시 역전파하면 기존 값에 더해질 수 있습니다.

y = x ** 2
y.backward()

print(x.grad)

결과:

tensor(8.)

따라서 학습 루프에서는 새로운 배치를 처리하기 전에 기존 기울기를 초기화합니다.

optimizer.zero_grad()

전체 순서:

optimizer.zero_grad()

predictions = model(inputs)

loss = criterion(
    predictions,
    targets
)

loss.backward()

optimizer.step()

기울기를 초기화하지 않으면 전날 회의록에 오늘 회의 내용을 계속 덧붙이는 상황이 됩니다.

가끔은 의도적으로 누적하기도 하지만, 기본 학습에서는 매 배치마다 깨끗하게 지웁니다.

17. 신경망 모듈 nn.Module

PyTorch 신경망은 일반적으로 `torch.nn.Module`을 상속해 만듭니다.

`nn.Module`은 레이어와 학습 가능한 파라미터를 관리하는 기본 클래스입니다. 공식 튜토리얼에서도 모든 PyTorch 신경망 모듈은 `nn.Module`을 상속하며, 신경망은 다른 레이어 모듈을 포함하는 중첩 구조로 구성된다고 설명합니다.

기본 형태:

import torch
from torch import nn


class NeuralNetwork(nn.Module):
    def __init__(self) -> None:
        super().__init__()

        self.layer = nn.Linear(
            in_features=10,
            out_features=1
        )

    def forward(
        self,
        x: torch.Tensor
    ) -> torch.Tensor:
        return self.layer(x)

init

사용할 레이어를 정의합니다.

self.layer = nn.Linear(
    10,
    1
)

forward

입력 데이터가 레이어를 어떤 순서로 통과할지 정의합니다.

return self.layer(x)

모델 생성

model = NeuralNetwork()

print(model)

`nn.Module`은 단순한 클래스 상속 이상의 역할을 합니다.

레이어 등록

파라미터 추적

장치 이동

학습·평가 모드 전환

state_dict 생성

중첩 모듈 관리

18. 첫 번째 PyTorch 모델

`y = 3x + 5` 관계를 학습하는 선형 모델을 만들어 보겠습니다.

데이터

import torch

X = torch.arange(
    -10,
    11,
    dtype=torch.float32
).reshape(
    -1,
    1
)

y = (
    3 * X
    + 5
)

모델

from torch import nn


class LinearModel(nn.Module):
    def __init__(self) -> None:
        super().__init__()

        self.linear = nn.Linear(
            in_features=1,
            out_features=1
        )

    def forward(
        self,
        x: torch.Tensor
    ) -> torch.Tensor:
        return self.linear(x)

생성

model = LinearModel()

print(model)

초기 가중치

print(
    model.linear.weight
)

print(
    model.linear.bias
)

처음에는 무작위에 가까운 값입니다.

모델은 아직 `3`과 `5`의 존재를 모릅니다.

첫 출근한 신입사원에게 회사의 비밀 공식을 기대하면 안 됩니다.

19. forward 메서드

`forward()`는 입력 텐서가 모델 내부에서 어떻게 계산되는지 정의합니다.

def forward(
    self,
    x: torch.Tensor
) -> torch.Tensor:
    return self.linear(x)

모델을 사용할 때는 `forward()`를 직접 호출하기보다 모델 객체를 함수처럼 호출합니다.

predictions = model(X)

다음처럼 직접 호출하지 않는 것이 일반적입니다.

predictions = model.forward(X)

`model(X)`를 사용하면 PyTorch가 내부적으로 모듈 호출과 관련된 여러 기능을 함께 처리할 수 있습니다.

여러 층 연결

class MultiLayerModel(nn.Module):
    def __init__(self) -> None:
        super().__init__()

        self.network = nn.Sequential(
            nn.Linear(10, 64),
            nn.ReLU(),
            nn.Linear(64, 32),
            nn.ReLU(),
            nn.Linear(32, 1)
        )

    def forward(
        self,
        x: torch.Tensor
    ) -> torch.Tensor:
        return self.network(x)

`nn.Sequential`은 레이어를 순서대로 연결할 때 편리합니다.

복잡한 연결이 필요하면 `forward()`에서 직접 계산 흐름을 작성할 수 있습니다.

20. 손실 함수

PyTorch의 손실 함수는 `torch.nn`에서 제공합니다.

회귀

criterion = nn.MSELoss()

평균제곱오차를 계산합니다.

criterion = nn.L1Loss()

평균절대오차를 계산합니다.

이진 분류

criterion = nn.BCEWithLogitsLoss()

`BCEWithLogitsLoss`는 Sigmoid와 이진 교차 엔트로피 계산을 결합합니다.

따라서 모델의 마지막 층에 Sigmoid를 별도로 넣지 않는 방식이 일반적입니다.

self.output = nn.Linear(
    32,
    1
)

예측 확률이 필요할 때만 Sigmoid를 적용합니다.

probabilities = torch.sigmoid(
    logits
)

다중 분류

criterion = nn.CrossEntropyLoss()

중요한 특징:

모델 출력:
Softmax 적용 전 logits

정답:
정수 클래스 번호

손실 함수:
CrossEntropyLoss

모델 출력층:

nn.Linear(
    128,
    10
)

Softmax를 직접 넣지 않습니다.

# 다중 분류 학습에서는 보통 불필요
nn.Softmax(dim=1)

`CrossEntropyLoss`가 내부적으로 클래스 점수를 적절히 처리합니다.

문제별 조합

문제출력층손실 함수
회귀Linear 1개MSELoss
이진 분류Linear 1개BCEWithLogitsLoss
다중 분류클래스 수만큼 LinearCrossEntropyLoss

21. 옵티마이저

옵티마이저는 계산된 기울기를 이용해 모델 파라미터를 수정합니다.

PyTorch의 `torch.optim`은 파라미터와 현재 옵티마이저 상태를 관리하며, 계산된 기울기를 기반으로 파라미터를 갱신합니다.

확률적 경사하강법

optimizer = torch.optim.SGD(
    model.parameters(),
    lr=0.01
)

Adam

optimizer = torch.optim.Adam(
    model.parameters(),
    lr=0.001
)

가중치 감쇠

optimizer = torch.optim.AdamW(
    model.parameters(),
    lr=0.001,
    weight_decay=0.01
)

모델 파라미터 전달

model.parameters()

모델이 학습해야 할 가중치와 편향을 옵티마이저에 전달합니다.

옵티마이저에게 파라미터를 전달하지 않으면 열심히 기울기를 계산한 뒤 아무것도 수정하지 않는 철학적인 학습이 완성됩니다.

22. PyTorch 학습 루프

PyTorch 학습의 핵심입니다.

epochs = 300

for epoch in range(epochs):
    model.train()

    predictions = model(X)

    loss = criterion(
        predictions,
        y
    )

    optimizer.zero_grad()

    loss.backward()

    optimizer.step()

    if (epoch + 1) % 50 == 0:
        print(
            f"Epoch {epoch + 1:03d} | "
            f"Loss {loss.item():.6f}"
        )

각 단계의 역할을 살펴보겠습니다.

model.train()

model.train()

모델을 학습 모드로 전환합니다.

Dropout과 Batch Normalization처럼 학습과 평가 시 동작이 다른 레이어에 영향을 줍니다.

순전파

predictions = model(X)

현재 가중치로 예측합니다.

손실 계산

loss = criterion(
    predictions,
    y
)

예측과 정답의 차이를 계산합니다.

기울기 초기화

optimizer.zero_grad()

이전 배치에서 계산된 기울기를 지웁니다.

역전파

loss.backward()

각 파라미터의 기울기를 계산합니다.

파라미터 수정

optimizer.step()

옵티마이저가 가중치와 편향을 수정합니다.

학습된 값 확인

print(
    model.linear.weight.item()
)

print(
    model.linear.bias.item()
)

충분히 학습되었다면 다음 값에 가까워집니다.

가중치:
3

편향:
5

23. train과 eval 모드

PyTorch 모델에는 학습 모드와 평가 모드가 있습니다.

학습 모드

model.train()

다음 레이어가 학습 방식으로 동작합니다.

Dropout
BatchNorm

평가 모드

model.eval()

Dropout이 비활성화되고 Batch Normalization이 학습된 통계를 사용합니다.

평가 기본 구조

model.eval()

with torch.inference_mode():
    predictions = model(
        X_test
    )

`model.eval()`과 기울기 비활성화는 서로 다른 역할입니다.

model.eval()
→ 레이어 동작 모드 변경

inference_mode()
→ 자동 미분 기록 비활성화

둘 중 하나만 실행하면 완전한 평가 코드가 아닐 수 있습니다.

24. 추론 시 기울기 비활성화

평가와 예측에서는 보통 기울기가 필요하지 않습니다.

inference_mode

with torch.inference_mode():
    predictions = model(
        inputs
    )

기울기 추적을 비활성화해 메모리와 계산량을 줄일 수 있습니다.

no_grad

with torch.no_grad():
    predictions = model(
        inputs
    )

`torch.inference_mode()`는 추론 전용 상황에서 더 강한 최적화를 적용할 수 있습니다.

일반적인 평가에서는 다음 패턴을 사용할 수 있습니다.

model.eval()

with torch.inference_mode():
    outputs = model(inputs)

추론 시간에 역전파용 기록을 계속 남기는 것은 퇴근한 직원의 모든 움직임을 회의록에 적는 것과 비슷합니다.

필요하지 않다면 기록하지 않는 편이 낫습니다.

25. Dataset이란?

Dataset은 데이터 한 건을 어떻게 가져올지 정의하는 객체입니다.

일반적으로 다음 두 메서드를 구현합니다.

__len__()
__getitem__()

len

전체 데이터 개수를 반환합니다.

def __len__(self) -> int:
    return len(self.labels)

getitem

특정 인덱스의 입력과 정답을 반환합니다.

def __getitem__(
    self,
    index: int
):
    return (
        self.features[index],
        self.labels[index]
    )

개념:

Dataset:
창고 관리대장

DataLoader:
창고에서 상자를 배치로 가져오는 운반 시스템

26. 사용자 정의 Dataset 만들기

간단한 표 데이터를 위한 Dataset을 만들어 보겠습니다.

import torch
from torch.utils.data import Dataset


class CustomerDataset(Dataset):
    def __init__(
        self,
        features,
        labels
    ) -> None:
        self.features = torch.tensor(
            features,
            dtype=torch.float32
        )

        self.labels = torch.tensor(
            labels,
            dtype=torch.long
        )

    def __len__(self) -> int:
        return len(self.labels)

    def __getitem__(
        self,
        index: int
    ):
        return (
            self.features[index],
            self.labels[index]
        )

사용

features = [
    [25, 50000],
    [42, 90000],
    [31, 65000]
]

labels = [
    0,
    1,
    0
]

dataset = CustomerDataset(
    features,
    labels
)

print(
    len(dataset)
)

print(
    dataset[0]
)

실제 프로젝트에서는 CSV, 이미지 경로, 음성 파일, 데이터베이스 결과 등을 `Dataset`으로 감쌀 수 있습니다.

27. DataLoader란?

DataLoader는 Dataset에서 데이터를 배치 단위로 가져오는 반복 가능한 객체입니다.

공식 문서에 따르면 DataLoader는 자동 배치, 데이터 순서 조절, 단일·다중 프로세스 로딩, 메모리 고정 등의 기능을 지원합니다.

from torch.utils.data import DataLoader

data_loader = DataLoader(
    dataset,
    batch_size=2,
    shuffle=True
)

반복

for features, labels in data_loader:
    print(features)
    print(labels)

주요 인수

DataLoader(
    dataset,
    batch_size=32,
    shuffle=True,
    num_workers=0
)

batch_size

한 번에 가져올 데이터 개수입니다.

shuffle

매 에포크마다 데이터 순서를 섞습니다.

학습:

shuffle=True

평가:

shuffle=False

num_workers

데이터 로딩에 사용할 하위 프로세스 수입니다.

Windows나 일부 실행 환경에서는 `num_workers=0`부터 시작하는 편이 안전합니다.

데이터 로딩이 느리면 GPU가 데이터를 기다리며 놀 수 있습니다.

고성능 GPU를 구매해 놓고 데이터 담당 직원 한 명이 손수 상자를 나르는 상황이 될 수 있습니다. 📦

28. 데이터 변환 Transform

이미지 모델은 데이터를 불러올 때 크기 조정, 텐서 변환, 정규화 등의 전처리를 적용합니다.

`torchvision.transforms`를 사용할 수 있습니다.

from torchvision import transforms

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(
        mean=(0.5,),
        std=(0.5,)
    )
])

ToTensor

이미지를 PyTorch 텐서로 변환하고 일반적인 이미지의 픽셀 범위를 조정합니다.

Normalize

평균과 표준편차를 기준으로 값을 변환합니다.

변환값
=
(원래값 - 평균)
÷ 표준편차

이미지 크기 변경:

transform = transforms.Compose([
    transforms.Resize(
        (224, 224)
    ),
    transforms.ToTensor()
])

데이터 증강:

train_transform = transforms.Compose([
    transforms.RandomHorizontalFlip(),
    transforms.RandomRotation(10),
    transforms.ToTensor()
])

평가 데이터에는 무작위 증강을 일반적으로 적용하지 않습니다.

학습 이미지:
다양한 변형으로 연습

평가 이미지:
원래 조건에서 공정하게 시험

29. FashionMNIST 프로젝트

이번에는 의류 이미지를 분류하는 CNN 모델을 만들겠습니다.

FashionMNIST는 다음과 같은 10개 의류 클래스를 포함합니다.

0: T-shirt/top
1: Trouser
2: Pullover
3: Dress
4: Coat
5: Sandal
6: Shirt
7: Sneaker
8: Bag
9: Ankle boot

이미지 크기:

1 × 28 × 28
채널:
1개, 회색조

높이:
28

너비:
28

Dataset 불러오기

from torchvision import datasets
from torchvision import transforms


transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(
        mean=(0.5,),
        std=(0.5,)
    )
])


train_dataset = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=transform
)


test_dataset = datasets.FashionMNIST(
    root="data",
    train=False,
    download=True,
    transform=transform
)

DataLoader

from torch.utils.data import DataLoader


train_loader = DataLoader(
    train_dataset,
    batch_size=128,
    shuffle=True,
    num_workers=0
)


test_loader = DataLoader(
    test_dataset,
    batch_size=256,
    shuffle=False,
    num_workers=0
)

배치 형태 확인

images, labels = next(
    iter(train_loader)
)

print(
    images.shape
)

print(
    labels.shape
)

결과:

torch.Size([128, 1, 28, 28])
torch.Size([128])

30. CNN 모델 만들기

CNN은 이미지의 공간적인 특징을 학습하는 데 활용되는 신경망입니다.

import torch
from torch import nn


class FashionCNN(nn.Module):
    def __init__(self) -> None:
        super().__init__()

        self.features = nn.Sequential(
            nn.Conv2d(
                in_channels=1,
                out_channels=32,
                kernel_size=3,
                padding=1
            ),
            nn.ReLU(),

            nn.MaxPool2d(
                kernel_size=2
            ),

            nn.Conv2d(
                in_channels=32,
                out_channels=64,
                kernel_size=3,
                padding=1
            ),
            nn.ReLU(),

            nn.MaxPool2d(
                kernel_size=2
            )
        )

        self.classifier = nn.Sequential(
            nn.Flatten(),

            nn.Linear(
                64 * 7 * 7,
                128
            ),
            nn.ReLU(),

            nn.Dropout(
                p=0.3
            ),

            nn.Linear(
                128,
                10
            )
        )

    def forward(
        self,
        x: torch.Tensor
    ) -> torch.Tensor:
        x = self.features(x)
        x = self.classifier(x)

        return x

첫 번째 합성곱

입력:
1 × 28 × 28

출력:
32 × 28 × 28

32개의 필터가 서로 다른 특징을 찾습니다.

첫 번째 풀링

32 × 28 × 28
→ 32 × 14 × 14

두 번째 합성곱

32 × 14 × 14
→ 64 × 14 × 14

두 번째 풀링

64 × 14 × 14
→ 64 × 7 × 7

Flatten

64 × 7 × 7
→ 3,136

출력층

128
→ 10개 클래스 점수

출력에는 Softmax를 넣지 않습니다.

nn.Linear(
    128,
    10
)

학습에 사용할 `CrossEntropyLoss`가 클래스 점수를 처리하기 때문입니다.

31. 모델 학습 함수

def train_one_epoch(
    model: nn.Module,
    data_loader: DataLoader,
    criterion: nn.Module,
    optimizer: torch.optim.Optimizer,
    device: torch.device
) -> tuple[float, float]:
    model.train()

    total_loss = 0.0
    correct_count = 0
    sample_count = 0

    for images, labels in data_loader:
        images = images.to(
            device
        )

        labels = labels.to(
            device
        )

        optimizer.zero_grad()

        logits = model(
            images
        )

        loss = criterion(
            logits,
            labels
        )

        loss.backward()

        optimizer.step()

        batch_size = images.size(0)

        total_loss += (
            loss.item()
            * batch_size
        )

        predictions = logits.argmax(
            dim=1
        )

        correct_count += (
            predictions
            == labels
        ).sum().item()

        sample_count += batch_size

    average_loss = (
        total_loss
        / sample_count
    )

    accuracy = (
        correct_count
        / sample_count
    )

    return (
        average_loss,
        accuracy
    )

중요한 순서

모델을 학습 모드로 전환

→ 데이터를 장치로 이동

→ 기울기 초기화

→ 순전파

→ 손실 계산

→ 역전파

→ 가중치 수정

→ 통계 누적

logits.argmax

predictions = logits.argmax(
    dim=1
)

각 이미지에서 가장 높은 점수를 가진 클래스 번호를 선택합니다.

32. 모델 평가 함수

def evaluate(
    model: nn.Module,
    data_loader: DataLoader,
    criterion: nn.Module,
    device: torch.device
) -> tuple[float, float]:
    model.eval()

    total_loss = 0.0
    correct_count = 0
    sample_count = 0

    with torch.inference_mode():
        for images, labels in data_loader:
            images = images.to(
                device
            )

            labels = labels.to(
                device
            )

            logits = model(
                images
            )

            loss = criterion(
                logits,
                labels
            )

            batch_size = images.size(0)

            total_loss += (
                loss.item()
                * batch_size
            )

            predictions = logits.argmax(
                dim=1
            )

            correct_count += (
                predictions
                == labels
            ).sum().item()

            sample_count += batch_size

    average_loss = (
        total_loss
        / sample_count
    )

    accuracy = (
        correct_count
        / sample_count
    )

    return (
        average_loss,
        accuracy
    )

평가 함수에는 다음 코드가 없습니다.

optimizer.zero_grad()
loss.backward()
optimizer.step()

평가 중에는 가중치를 수정하지 않기 때문입니다.

학습:
문제를 풀고 오답을 수정

평가:
문제를 풀지만 답안지는 수정하지 않음

33. 전체 학습 코드

from pathlib import Path
import random

import matplotlib.pyplot as plt
import numpy as np
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision import transforms


MODEL_FILE = Path(
    "fashion_cnn_state.pt"
)

BEST_MODEL_FILE = Path(
    "fashion_cnn_best.pt"
)


CLASS_NAMES = [
    "T-shirt/top",
    "Trouser",
    "Pullover",
    "Dress",
    "Coat",
    "Sandal",
    "Shirt",
    "Sneaker",
    "Bag",
    "Ankle boot"
]


def set_seed(
    seed: int = 2026
) -> None:
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)

    if torch.cuda.is_available():
        torch.cuda.manual_seed_all(
            seed
        )


def select_device() -> torch.device:
    if torch.cuda.is_available():
        return torch.device(
            "cuda"
        )

    if torch.backends.mps.is_available():
        return torch.device(
            "mps"
        )

    return torch.device(
        "cpu"
    )


class FashionCNN(nn.Module):
    def __init__(self) -> None:
        super().__init__()

        self.features = nn.Sequential(
            nn.Conv2d(
                1,
                32,
                kernel_size=3,
                padding=1
            ),
            nn.ReLU(),
            nn.MaxPool2d(2),

            nn.Conv2d(
                32,
                64,
                kernel_size=3,
                padding=1
            ),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )

        self.classifier = nn.Sequential(
            nn.Flatten(),

            nn.Linear(
                64 * 7 * 7,
                128
            ),
            nn.ReLU(),

            nn.Dropout(
                0.3
            ),

            nn.Linear(
                128,
                10
            )
        )

    def forward(
        self,
        x: torch.Tensor
    ) -> torch.Tensor:
        x = self.features(x)
        x = self.classifier(x)

        return x


def create_data_loaders(
    batch_size: int = 128
) -> tuple[
    DataLoader,
    DataLoader
]:
    transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize(
            mean=(0.5,),
            std=(0.5,)
        )
    ])

    train_dataset = (
        datasets.FashionMNIST(
            root="data",
            train=True,
            download=True,
            transform=transform
        )
    )

    test_dataset = (
        datasets.FashionMNIST(
            root="data",
            train=False,
            download=True,
            transform=transform
        )
    )

    train_loader = DataLoader(
        train_dataset,
        batch_size=batch_size,
        shuffle=True,
        num_workers=0
    )

    test_loader = DataLoader(
        test_dataset,
        batch_size=batch_size * 2,
        shuffle=False,
        num_workers=0
    )

    return (
        train_loader,
        test_loader
    )


def train_one_epoch(
    model: nn.Module,
    data_loader: DataLoader,
    criterion: nn.Module,
    optimizer: torch.optim.Optimizer,
    device: torch.device
) -> tuple[float, float]:
    model.train()

    total_loss = 0.0
    correct_count = 0
    sample_count = 0

    for images, labels in data_loader:
        images = images.to(
            device
        )

        labels = labels.to(
            device
        )

        optimizer.zero_grad()

        logits = model(
            images
        )

        loss = criterion(
            logits,
            labels
        )

        loss.backward()

        optimizer.step()

        batch_size = images.size(0)

        total_loss += (
            loss.item()
            * batch_size
        )

        predictions = logits.argmax(
            dim=1
        )

        correct_count += (
            predictions
            == labels
        ).sum().item()

        sample_count += batch_size

    return (
        total_loss / sample_count,
        correct_count / sample_count
    )


def evaluate(
    model: nn.Module,
    data_loader: DataLoader,
    criterion: nn.Module,
    device: torch.device
) -> tuple[float, float]:
    model.eval()

    total_loss = 0.0
    correct_count = 0
    sample_count = 0

    with torch.inference_mode():
        for images, labels in data_loader:
            images = images.to(
                device
            )

            labels = labels.to(
                device
            )

            logits = model(
                images
            )

            loss = criterion(
                logits,
                labels
            )

            batch_size = images.size(0)

            total_loss += (
                loss.item()
                * batch_size
            )

            predictions = logits.argmax(
                dim=1
            )

            correct_count += (
                predictions
                == labels
            ).sum().item()

            sample_count += batch_size

    return (
        total_loss / sample_count,
        correct_count / sample_count
    )


def plot_history(
    history: dict[str, list[float]]
) -> None:
    epochs = range(
        1,
        len(history["train_loss"]) + 1
    )

    plt.figure(
        figsize=(10, 5)
    )

    plt.plot(
        epochs,
        history["train_loss"],
        marker="o",
        label="Train loss"
    )

    plt.plot(
        epochs,
        history["test_loss"],
        marker="o",
        label="Test loss"
    )

    plt.xlabel("Epoch")
    plt.ylabel("Loss")
    plt.title(
        "Training and Test Loss"
    )

    plt.legend()
    plt.grid(alpha=0.3)
    plt.tight_layout()
    plt.show()

    plt.figure(
        figsize=(10, 5)
    )

    plt.plot(
        epochs,
        history["train_accuracy"],
        marker="o",
        label="Train accuracy"
    )

    plt.plot(
        epochs,
        history["test_accuracy"],
        marker="o",
        label="Test accuracy"
    )

    plt.xlabel("Epoch")
    plt.ylabel("Accuracy")
    plt.title(
        "Training and Test Accuracy"
    )

    plt.legend()
    plt.grid(alpha=0.3)
    plt.tight_layout()
    plt.show()


def main() -> None:
    set_seed()

    device = select_device()

    print(
        f"사용 장치: {device}"
    )

    train_loader, test_loader = (
        create_data_loaders(
            batch_size=128
        )
    )

    model = FashionCNN().to(
        device
    )

    criterion = nn.CrossEntropyLoss()

    optimizer = torch.optim.AdamW(
        model.parameters(),
        lr=0.001,
        weight_decay=0.0001
    )

    history = {
        "train_loss": [],
        "train_accuracy": [],
        "test_loss": [],
        "test_accuracy": []
    }

    best_accuracy = 0.0
    epoch_count = 10

    for epoch in range(
        1,
        epoch_count + 1
    ):
        train_loss, train_accuracy = (
            train_one_epoch(
                model,
                train_loader,
                criterion,
                optimizer,
                device
            )
        )

        test_loss, test_accuracy = (
            evaluate(
                model,
                test_loader,
                criterion,
                device
            )
        )

        history["train_loss"].append(
            train_loss
        )

        history["train_accuracy"].append(
            train_accuracy
        )

        history["test_loss"].append(
            test_loss
        )

        history["test_accuracy"].append(
            test_accuracy
        )

        print(
            f"Epoch {epoch:02d}/{epoch_count} | "
            f"Train loss {train_loss:.4f} | "
            f"Train acc {train_accuracy:.2%} | "
            f"Test loss {test_loss:.4f} | "
            f"Test acc {test_accuracy:.2%}"
        )

        if test_accuracy > best_accuracy:
            best_accuracy = test_accuracy

            torch.save(
                model.state_dict(),
                BEST_MODEL_FILE
            )

            print(
                "  ↳ 최고 성능 모델 저장"
            )

    torch.save(
        model.state_dict(),
        MODEL_FILE
    )

    print()
    print(
        f"최고 평가 정확도: "
        f"{best_accuracy:.2%}"
    )

    print(
        f"최종 모델 저장: "
        f"{MODEL_FILE.resolve()}"
    )

    print(
        f"최고 모델 저장: "
        f"{BEST_MODEL_FILE.resolve()}"
    )

    plot_history(
        history
    )


if __name__ == "__main__":
    main()

주의

학습 과정에서 평가 데이터를 반복적으로 확인해 모델을 선택하면 해당 데이터는 사실상 검증 데이터 역할을 하게 됩니다.

실제 프로젝트에서는 다음처럼 나누는 것이 좋습니다.

학습 데이터
→ 가중치 학습

검증 데이터
→ 모델 선택과 조기 종료

최종 평가 데이터
→ 마지막 한 번의 성능 확인

이번 코드는 학습 루프를 이해하기 위한 입문 예제이므로 구조를 단순화했습니다.

34. 학습 기록 시각화

학습 중 기록한 값을 그래프로 보면 과적합과 학습 상태를 확인할 수 있습니다.

학습 손실:
계속 감소

평가 손실:
감소하다가 다시 증가

→ 과적합 가능성
학습 정확도:
낮음

평가 정확도:
낮음

→ 과소적합 가능성

그래프는 모델의 건강검진표입니다.

정확도 숫자 하나만 보면 멀쩡해 보이던 모델도 손실 그래프에서 슬그머니 이상 징후를 드러낼 수 있습니다.

35. 이미지 예측하기

저장한 최고 성능 모델을 불러와 이미지 한 장을 예측해 보겠습니다.

model = FashionCNN().to(
    device
)

state_dict = torch.load(
    "fashion_cnn_best.pt",
    map_location=device,
    weights_only=True
)

model.load_state_dict(
    state_dict
)

model.eval()

이미지 한 장 가져오기

image, actual_label = (
    test_dataset[0]
)

배치 차원을 추가합니다.

input_tensor = (
    image
    .unsqueeze(0)
    .to(device)
)

print(
    input_tensor.shape
)

결과:

torch.Size([1, 1, 28, 28])

예측

with torch.inference_mode():
    logits = model(
        input_tensor
    )

    probabilities = torch.softmax(
        logits,
        dim=1
    )

    predicted_label = (
        probabilities
        .argmax(dim=1)
        .item()
    )

    confidence = (
        probabilities[0, predicted_label]
        .item()
    )

결과 출력

print(
    f"실제 클래스: "
    f"{CLASS_NAMES[actual_label]}"
)

print(
    f"예측 클래스: "
    f"{CLASS_NAMES[predicted_label]}"
)

print(
    f"예측 확률: "
    f"{confidence:.2%}"
)

`CrossEntropyLoss` 학습에서는 Softmax를 모델에 넣지 않았지만, 사람이 읽을 확률을 만들 때는 `torch.softmax()`를 적용할 수 있습니다.

36. 오답 이미지 분석하기

전체 평가 데이터에서 틀린 이미지를 수집해 보겠습니다.

wrong_predictions = []

model.eval()

with torch.inference_mode():
    for images, labels in test_loader:
        images = images.to(
            device
        )

        labels = labels.to(
            device
        )

        logits = model(
            images
        )

        probabilities = torch.softmax(
            logits,
            dim=1
        )

        predictions = logits.argmax(
            dim=1
        )

        wrong_mask = (
            predictions
            != labels
        )

        wrong_indices = (
            wrong_mask
            .nonzero(
                as_tuple=True
            )[0]
        )

        for index in wrong_indices:
            predicted_label = (
                predictions[index]
                .item()
            )

            actual_label = (
                labels[index]
                .item()
            )

            confidence = (
                probabilities[
                    index,
                    predicted_label
                ]
                .item()
            )

            wrong_predictions.append({
                "image": (
                    images[index]
                    .detach()
                    .cpu()
                ),
                "actual": actual_label,
                "predicted": predicted_label,
                "confidence": confidence
            })

오답 표시

plt.figure(
    figsize=(12, 8)
)

for plot_index, item in enumerate(
    wrong_predictions[:12]
):
    image = (
        item["image"]
        .squeeze(0)
        .numpy()
    )

    image = (
        image * 0.5
        + 0.5
    )

    plt.subplot(
        3,
        4,
        plot_index + 1
    )

    plt.imshow(
        image,
        cmap="gray"
    )

    plt.title(
        f"True: "
        f"{CLASS_NAMES[item['actual']]}\n"
        f"Pred: "
        f"{CLASS_NAMES[item['predicted']]}\n"
        f"{item['confidence']:.1%}"
    )

    plt.axis("off")

plt.tight_layout()
plt.show()

분석할 내용

Shirt와 T-shirt를 자주 혼동하는가?

Pullover와 Coat가 비슷한가?

이미지가 흐리거나 잘렸는가?

배경에 특이한 패턴이 있는가?

높은 확률로 틀린 사례가 있는가?

모델이 90% 정확하다는 말보다 나머지 10%에서 어떤 실수를 하는지가 더 중요한 경우가 많습니다.

오답은 모델이 남긴 익명의 건의함입니다.

37. 혼동행렬 만들기

confusion_matrix = torch.zeros(
    10,
    10,
    dtype=torch.int64
)

model.eval()

with torch.inference_mode():
    for images, labels in test_loader:
        images = images.to(
            device
        )

        labels = labels.to(
            device
        )

        logits = model(
            images
        )

        predictions = logits.argmax(
            dim=1
        )

        for actual, predicted in zip(
            labels.cpu(),
            predictions.cpu()
        ):
            confusion_matrix[
                actual,
                predicted
            ] += 1

시각화

matrix = confusion_matrix.numpy()

plt.figure(
    figsize=(10, 9)
)

plt.imshow(
    matrix,
    cmap="Blues"
)

plt.title(
    "FashionMNIST Confusion Matrix"
)

plt.xlabel(
    "Predicted class"
)

plt.ylabel(
    "Actual class"
)

plt.xticks(
    range(10),
    CLASS_NAMES,
    rotation=45,
    ha="right"
)

plt.yticks(
    range(10),
    CLASS_NAMES
)

plt.colorbar()

for row in range(10):
    for column in range(10):
        value = matrix[
            row,
            column
        ]

        if value > 0:
            plt.text(
                column,
                row,
                str(value),
                ha="center",
                va="center",
                fontsize=8
            )

plt.tight_layout()
plt.show()

대각선은 정답입니다.

대각선 밖에 큰 숫자가 있다면 특정 클래스 조합을 자주 혼동한다는 뜻입니다.

38. state_dict 저장하기

PyTorch에서는 모델 전체보다 `state_dict`를 저장하는 방식이 널리 사용됩니다.

torch.save(
    model.state_dict(),
    "fashion_model.pt"
)

`state_dict`에는 학습된 파라미터가 이름과 함께 저장됩니다.

for name, parameter in (
    model.state_dict().items()
):
    print(
        name,
        parameter.shape
    )

결과 예:

features.0.weight
features.0.bias
features.3.weight
features.3.bias
classifier.1.weight
classifier.1.bias
...

PyTorch에서는 텐서나 상태를 `.pt` 또는 `.pth` 확장자로 저장하는 관례가 널리 사용됩니다.

39. 체크포인트 저장하기

학습을 중단한 뒤 이어서 진행하려면 모델뿐 아니라 옵티마이저와 현재 에포크도 저장해야 합니다.

checkpoint = {
    "epoch": epoch,
    "model_state_dict": (
        model.state_dict()
    ),
    "optimizer_state_dict": (
        optimizer.state_dict()
    ),
    "best_accuracy": best_accuracy,
    "history": history
}

torch.save(
    checkpoint,
    "training_checkpoint.pt"
)

저장 항목

현재 에포크

모델 가중치

옵티마이저 상태

최고 평가 점수

학습 기록

Adam 같은 옵티마이저는 단순히 현재 가중치만 사용하는 것이 아니라 내부 통계도 관리합니다.

옵티마이저 상태를 저장하지 않으면 이어달리기 선수가 배턴 대신 경기 규칙 설명서만 건네받는 셈입니다.

40. 모델 다시 불러오기

state_dict 불러오기

model = FashionCNN()

state_dict = torch.load(
    "fashion_model.pt",
    map_location="cpu",
    weights_only=True
)

model.load_state_dict(
    state_dict
)

model.eval()

`map_location="cpu"`를 지정하면 GPU에서 저장된 상태를 CPU 환경에서 불러오는 데 도움이 됩니다. 공식 `torch.load()` 문서도 GPU 메모리 급증을 피하려면 CPU로 매핑해 불러온 뒤 `load_state_dict()`를 사용하는 방법을 안내합니다.

체크포인트 불러오기

checkpoint = torch.load(
    "training_checkpoint.pt",
    map_location=device,
    weights_only=True
)

model = FashionCNN().to(
    device
)

optimizer = torch.optim.AdamW(
    model.parameters(),
    lr=0.001
)

model.load_state_dict(
    checkpoint[
        "model_state_dict"
    ]
)

optimizer.load_state_dict(
    checkpoint[
        "optimizer_state_dict"
    ]
)

start_epoch = (
    checkpoint["epoch"]
    + 1
)

best_accuracy = checkpoint[
    "best_accuracy"
]

보안 주의

신뢰할 수 없는 모델 파일을 무심코 불러오면 안 됩니다.

모델 파일도 실행 환경에 영향을 줄 수 있는 외부 입력으로 취급해야 합니다.

인터넷에서 받은 정체불명 모델

출처가 불분명한 체크포인트

임의로 수정된 직렬화 파일

신뢰할 수 있는 출처와 파일 무결성을 확인해야 합니다.

41. CPU·CUDA·MPS 대응

장치 선택 함수를 다시 살펴보겠습니다.

def select_device() -> torch.device:
    if torch.cuda.is_available():
        return torch.device("cuda")

    if torch.backends.mps.is_available():
        return torch.device("mps")

    return torch.device("cpu")

모델 이동

model = model.to(
    device
)

배치 이동

images = images.to(
    device
)

labels = labels.to(
    device
)

CPU로 결과 가져오기

predictions = (
    predictions
    .detach()
    .cpu()
)

NumPy로 변환

array = (
    predictions
    .numpy()
)

장치를 여러 번 오가는 작업은 비용이 발생할 수 있습니다.

학습 루프 안에서 불필요하게 CPU와 GPU 사이를 왕복하지 않는 것이 좋습니다.

GPU:
“계산 준비 완료!”

코드:
“잠깐 CPU로 갔다가 다시 올게요.”

GPU:
“그럴 거면 왜 불렀습니까?”

42. 자동 혼합 정밀도 AMP

일반적인 모델 학습은 `float32`를 많이 사용합니다.

자동 혼합 정밀도는 일부 연산을 `float16`이나 `bfloat16` 같은 낮은 정밀도로 수행하고, 수치 범위가 중요한 연산은 `float32`로 처리합니다.

PyTorch의 AMP는 연산 특성에 따라 정밀도를 선택하는 Autocast와 작은 기울기의 언더플로를 줄이는 Gradient Scaling을 제공합니다.

GPU 환경에서 학습 속도와 메모리 사용량을 개선할 수 있지만 하드웨어와 모델에 따라 효과가 다릅니다.

기본 개념 코드

scaler = torch.amp.GradScaler(
    "cuda"
)

for images, labels in train_loader:
    images = images.to(
        device
    )

    labels = labels.to(
        device
    )

    optimizer.zero_grad()

    with torch.autocast(
        device_type="cuda",
        dtype=torch.float16
    ):
        logits = model(
            images
        )

        loss = criterion(
            logits,
            labels
        )

    scaler.scale(
        loss
    ).backward()

    scaler.step(
        optimizer
    )

    scaler.update()

흐름

Autocast
→ 적절한 연산을 낮은 정밀도로 수행

GradScaler
→ 작은 기울기가 0으로 사라지는 현상 완화

step
→ 안전하게 옵티마이저 갱신

CPU나 MPS 환경에서는 지원 범위와 권장 데이터 타입이 달라질 수 있으므로 공식 문서와 현재 장치 지원 상태를 확인해야 합니다.

AMP는 모든 숫자의 소수점을 무작정 잘라내는 기능이 아닙니다.

속도와 안정성 사이에서 계산마다 적절한 신발을 골라 신기는 코디네이터입니다.

43. torch.compile

PyTorch 2 계열은 `torch.compile()`을 이용해 모델이나 함수를 최적화할 수 있습니다.

compiled_model = torch.compile(
    model
)

이후 일반 모델처럼 사용할 수 있습니다.

outputs = compiled_model(
    inputs
)

`torch.compile()`은 TorchDynamo와 선택한 백엔드를 이용해 함수나 모델을 최적화합니다. `nn.Module`에는 구조를 유지한 채 컴파일하는 방식도 제공됩니다.

주의사항

첫 실행 시 컴파일 비용이 발생할 수 있음

모든 모델이 같은 속도 향상을 얻지는 않음

동적인 Python 코드가 그래프 분리를 일으킬 수 있음

환경과 장치에 따라 지원 수준이 다름

따라서 다음 순서가 좋습니다.

일반 eager 모드에서 정확성 확인

→ 성능 측정

→ torch.compile 적용

→ 다시 정확성과 속도 측정

컴파일을 붙였다는 이유만으로 자동으로 빨라졌다고 선언하면 안 됩니다.

성능 최적화는 느낌이 아니라 측정의 영역입니다.

44. 재현 가능한 실험

딥러닝에는 여러 무작위 요소가 포함됩니다.

가중치 초기화

데이터 순서 섞기

Dropout

데이터 증강

일부 GPU 연산

시드 설정

import random

import numpy as np
import torch


def set_seed(
    seed: int = 2026
) -> None:
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)

    if torch.cuda.is_available():
        torch.cuda.manual_seed_all(
            seed
        )

결정적 알고리즘

torch.use_deterministic_algorithms(
    True
)

일부 연산에서는 오류가 발생하거나 성능이 낮아질 수 있습니다.

PyTorch 공식 재현성 문서는 완전히 동일한 결과가 릴리스, 플랫폼, CPU와 GPU 사이에서 항상 보장되는 것은 아니며, 결정적 연산은 비결정적 연산보다 느릴 수 있다고 설명합니다.

기록할 항목

Python 버전

PyTorch 버전

torchvision 버전

운영체제

GPU 모델

드라이버 버전

무작위 시드

데이터 버전

모델 코드

학습률

배치 크기

에포크 수

평가 결과

시드 하나만 저장하고 재현성을 확보했다고 생각하면 안 됩니다.

같은 레시피라도 오븐과 재료가 달라지면 쿠키의 표정이 달라질 수 있습니다. 🍪

45. 과적합 줄이기

Dropout

nn.Dropout(
    p=0.3
)

가중치 감쇠

optimizer = torch.optim.AdamW(
    model.parameters(),
    lr=0.001,
    weight_decay=0.01
)

데이터 증강

train_transform = (
    transforms.Compose([
        transforms.RandomHorizontalFlip(),
        transforms.RandomRotation(10),
        transforms.ToTensor()
    ])
)

의류 이미지에서는 좌우 반전이 자연스러울 수 있지만 숫자 이미지에서는 의미가 달라질 수 있습니다.

문제 특성에 맞는 증강을 선택해야 합니다.

모델 단순화

레이어 수 줄이기

뉴런 수 줄이기

필터 수 줄이기

조기 종료

PyTorch에서는 검증 손실을 직접 확인해 조기 종료 로직을 만들 수 있습니다.

best_loss = float("inf")
patience = 3
waiting_count = 0

for epoch in range(epoch_count):
    validation_loss = ...

    if validation_loss < best_loss:
        best_loss = validation_loss
        waiting_count = 0

        torch.save(
            model.state_dict(),
            "best_model.pt"
        )
    else:
        waiting_count += 1

    if waiting_count >= patience:
        print(
            "조기 종료"
        )
        break

PyTorch에서는 자동 기능이 부족하다기보다 필요한 동작을 직접 코드로 구성하는 방식이 중심입니다.

46. PyTorch 프로젝트 구조

코드가 길어지면 파일을 역할별로 분리하는 것이 좋습니다.

pytorch_project/
├─ data/
├─ models/
│  └─ fashion_cnn.py
├─ datasets/
│  └─ fashion_dataset.py
├─ utils/
│  ├─ device.py
│  └─ seed.py
├─ checkpoints/
├─ train.py
├─ evaluate.py
├─ predict.py
├─ requirements.txt
└─ README.md

models

신경망 클래스를 관리합니다.

datasets

Dataset과 데이터 변환을 관리합니다.

train.py

학습 루프와 체크포인트 저장을 담당합니다.

evaluate.py

평가 지표와 혼동행렬을 생성합니다.

predict.py

새로운 데이터 예측을 수행합니다.

requirements.txt

torch
torchvision
numpy
matplotlib

재현 가능한 환경을 위해 실제 프로젝트에서는 버전을 고정할 수 있습니다.

torch==검증된버전
torchvision==호환버전

무조건 최신 버전으로 갱신하기보다 프로젝트에서 검증한 조합을 관리하는 것이 중요합니다.

47. 자주 발생하는 오류

오류 1. torch를 찾을 수 없음

ModuleNotFoundError:
No module named 'torch'

해결:

python -m pip install torch torchvision

확인:

python -m pip show torch

오류 2. CPU와 GPU 텐서가 섞임

Expected all tensors to be
on the same device

확인:

print(
    next(
        model.parameters()
    ).device
)

print(
    inputs.device
)

수정:

model = model.to(
    device
)

inputs = inputs.to(
    device
)

targets = targets.to(
    device
)

오류 3. 자료형이 맞지 않음

expected scalar type Long
but found Float

다중 분류 정답:

labels = labels.long()

모델 입력:

images = images.float()

오류 4. 행렬 형태가 맞지 않음

mat1 and mat2 shapes
cannot be multiplied

Linear 레이어 입력 크기와 실제 Flatten 결과를 확인합니다.

print(
    x.shape
)

CNN에서는 임시 텐서로 형태를 확인할 수 있습니다.

sample = torch.randn(
    1,
    1,
    28,
    28
)

with torch.inference_mode():
    features = model.features(
        sample
    )

print(
    features.shape
)

오류 5. CrossEntropyLoss 전에 Softmax 적용

잘못된 예:

self.output = nn.Sequential(
    nn.Linear(
        128,
        10
    ),
    nn.Softmax(
        dim=1
    )
)

수정:

self.output = nn.Linear(
    128,
    10
)

학습 손실:

criterion = nn.CrossEntropyLoss()

확률이 필요할 때만 적용합니다.

probabilities = torch.softmax(
    logits,
    dim=1
)

오류 6. 기울기를 초기화하지 않음

잘못된 예:

loss.backward()
optimizer.step()

수정:

optimizer.zero_grad()
loss.backward()
optimizer.step()

오류 7. 평가 중 train 모드 유지

평가 전:

model.eval()

학습 시작 전:

model.train()

Dropout과 Batch Normalization이 포함된 모델에서는 특히 중요합니다.

오류 8. 평가 중 기울기를 계속 기록함

수정:

model.eval()

with torch.inference_mode():
    outputs = model(inputs)

오류 9. DataLoader가 Windows에서 멈춤

다중 프로세스 DataLoader를 사용할 때 메인 진입점을 보호합니다.

def main() -> None:
    ...


if __name__ == "__main__":
    main()

처음에는 다음 설정으로 확인합니다.

num_workers=0

오류 10. 메모리 부족

CUDA out of memory

대응:

batch_size 줄이기

이미지 크기 줄이기

모델 크기 줄이기

불필요한 텐서 참조 제거

AMP 사용 검토

기울기 누적 사용 검토
batch_size = 32

필요하지 않은 텐서는 CPU로 이동하거나 참조를 제거합니다.

오류 11. numpy 변환 오류

GPU 텐서를 바로 NumPy로 변환할 수 없습니다.

잘못된 예:

array = gpu_tensor.numpy()

수정:

array = (
    gpu_tensor
    .detach()
    .cpu()
    .numpy()
)

오류 12. 모델 파일 구조가 맞지 않음

저장 당시 모델 구조와 불러올 모델 구조가 다르면 오류가 발생합니다.

Missing key(s) in state_dict

Unexpected key(s) in state_dict

size mismatch

모델 클래스, 출력 클래스 수, 레이어 이름을 확인합니다.

오류 13. 학습 손실이 NaN

확인할 내용:

입력에 NaN이 있는가?

학습률이 너무 큰가?

잘못된 나눗셈이 있는가?

로그에 0 이하 값이 들어가는가?

기울기가 폭발하는가?

확인:

print(
    torch.isnan(inputs).any()
)

print(
    torch.isinf(inputs).any()
)

학습률 낮추기:

optimizer = torch.optim.Adam(
    model.parameters(),
    lr=0.0001
)

기울기 제한:

torch.nn.utils.clip_grad_norm_(
    model.parameters(),
    max_norm=1.0
)

오류 14. 다운로드가 실패함

FashionMNIST 다운로드 과정에서 네트워크나 파일 권한 문제가 발생할 수 있습니다.

download=True

확인할 내용:

인터넷 연결

data 폴더 쓰기 권한

프록시와 방화벽

디스크 여유 공간

한 번 다운로드되면 다음 실행부터 로컬 파일을 사용할 수 있습니다.

48. 연습 문제

문제 1

다음 텐서를 생성하고 형태와 자료형을 출력하세요.

2행 3열
float32

문제 2

1부터 12까지 값을 가진 텐서를 3행 4열로 변경하세요.

torch.arange(
    1,
    13
).reshape(
    3,
    4
)

문제 3

다음 함수의 `x=3`에서 미분값을 Autograd로 계산하세요.

y = x³ + 2x

문제 4

입력 특성 10개, 은닉층 32개, 출력 2개인 모델을 `nn.Module`로 작성하세요.

문제 5

다중 분류 모델에 사용할 손실 함수와 출력층을 작성하세요.

output = nn.Linear(
    32,
    class_count
)

criterion = nn.CrossEntropyLoss()

문제 6

PyTorch 학습 루프의 빈칸을 채우세요.

optimizer.________()

outputs = model(inputs)

loss = criterion(
    outputs,
    targets
)

loss.________()

optimizer.________()

정답:

zero_grad
backward
step

문제 7

평가 시 필요한 두 가지 코드를 작성하세요.

model.eval()

with torch.inference_mode():
    ...

문제 8

사용자 정의 Dataset에서 반드시 구현할 두 메서드를 작성하세요.

__len__
__getitem__

문제 9

DataLoader를 배치 크기 64, 데이터 섞기 활성화로 생성하세요.

문제 10

GPU, MPS, CPU 순서로 사용 가능한 장치를 선택하는 함수를 작성하세요.

문제 11

FashionCNN의 첫 번째 합성곱 출력 형태를 계산하세요.

입력:
1 × 28 × 28

필터:
32개

kernel_size:
3

padding:
1

문제 12

모델의 `state_dict`를 `model.pt` 파일로 저장하세요.

문제 13

GPU에서 저장한 모델을 CPU로 불러오는 코드를 작성하세요.

state_dict = torch.load(
    "model.pt",
    map_location="cpu",
    weights_only=True
)

문제 14

학습 정확도는 계속 상승하지만 검증 손실이 증가하는 이유와 대응 방법을 설명하세요.

문제 15

Keras의 `model.fit()`과 PyTorch 학습 루프의 차이를 설명하세요.

49. 핵심 요약

PyTorch 불러오기

import torch

from torch import nn

텐서 생성

tensor = torch.tensor(
    [1, 2, 3],
    dtype=torch.float32
)

무작위 텐서

tensor = torch.randn(
    2,
    3
)

장치 선택

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

장치 이동

tensor = tensor.to(
    device
)

model = model.to(
    device
)

자동 미분

x = torch.tensor(
    2.0,
    requires_grad=True
)

y = x ** 2

y.backward()

print(
    x.grad
)

모델 정의

class Model(nn.Module):
    def __init__(self) -> None:
        super().__init__()

        self.layer = nn.Linear(
            10,
            1
        )

    def forward(
        self,
        x
    ):
        return self.layer(x)

손실 함수

criterion = nn.CrossEntropyLoss()

옵티마이저

optimizer = torch.optim.Adam(
    model.parameters(),
    lr=0.001
)

학습 루프

model.train()

for inputs, targets in train_loader:
    inputs = inputs.to(
        device
    )

    targets = targets.to(
        device
    )

    optimizer.zero_grad()

    outputs = model(
        inputs
    )

    loss = criterion(
        outputs,
        targets
    )

    loss.backward()

    optimizer.step()

평가

model.eval()

with torch.inference_mode():
    outputs = model(
        inputs
    )

Dataset

class CustomDataset(Dataset):
    def __len__(self):
        ...

    def __getitem__(
        self,
        index
    ):
        ...

DataLoader

loader = DataLoader(
    dataset,
    batch_size=64,
    shuffle=True
)

모델 저장

torch.save(
    model.state_dict(),
    "model.pt"
)

모델 불러오기

model.load_state_dict(
    torch.load(
        "model.pt",
        map_location=device,
        weights_only=True
    )
)

model.eval()

50. 마무리

이번 시간에는 PyTorch를 이용해 딥러닝 모델의 내부 학습 과정을 직접 제어하는 방법을 배웠습니다.

PyTorch의 기본 구성 요소를 다시 정리해 보겠습니다.

Tensor
→ 데이터를 저장하고 계산

Autograd
→ 기울기 자동 계산

nn.Module
→ 신경망 구조 정의

Dataset
→ 데이터 한 건을 가져오는 규칙

DataLoader
→ 데이터를 배치로 공급

Loss
→ 예측 오류 계산

Optimizer
→ 모델 파라미터 수정

state_dict
→ 학습된 상태 저장

PyTorch 학습의 핵심은 다음 여섯 단계입니다.

기울기 초기화

→ 순전파

→ 손실 계산

→ 역전파

→ 가중치 수정

→ 반복

코드로 표현하면 다음과 같습니다.

optimizer.zero_grad()

outputs = model(inputs)

loss = criterion(
    outputs,
    targets
)

loss.backward()

optimizer.step()

처음 보면 반복되는 의식처럼 보일 수 있습니다.

하지만 각 줄의 역할을 이해하면 PyTorch의 학습 코드는 신비한 주문이 아니라 매우 논리적인 작업 순서가 됩니다.

zero_grad:
지난 실수 기록 지우기

model:
현재 실력으로 답하기

criterion:
얼마나 틀렸는지 확인하기

backward:
누가 얼마나 책임졌는지 계산하기

step:
다음에는 덜 틀리도록 수정하기

TensorFlow와 Keras에서는 많은 과정이 `fit()` 안에 들어 있었습니다.

PyTorch에서는 그 과정을 개발자가 직접 펼쳐 놓습니다.

코드가 길어진 대신 다음과 같은 자유를 얻습니다.

손실 함수를 자유롭게 조합

여러 모델을 동시에 학습

조건에 따라 역전파 제어

특별한 가중치 갱신 적용

복잡한 데이터 흐름 구현

새로운 논문 아이디어 실험

PyTorch는 모든 것을 자동으로 처리해 주는 딥러닝 자판기가 아닙니다.

대신 필요한 부품을 꺼내 직접 모델을 조립할 수 있는 작업실입니다.

처음에는 공구가 많아 당황할 수 있습니다.

Tensor
Autograd
Module
Dataset
DataLoader
Optimizer
Scheduler
Checkpoint

하지만 중심축은 언제나 같습니다.

“데이터가 어디에서 들어오고, 어떤 계산을 거쳐, 어떤 손실로 평가되며, 가중치가 어떻게 수정되는가?”

이 네 가지를 추적할 수 있다면 복잡한 PyTorch 코드도 단계별로 해석할 수 있습니다.

이제 딥러닝 모델이 학습되지 않을 때 무작정 에포크만 늘리지 마세요.

먼저 장치, 자료형, 텐서 형태, 손실 함수, 기울기 흐름을 확인해야 합니다.

모델:
“성능이 오르지 않습니다.”

개발자:
“에포크를 두 배로 늘리자!”

shape:
“저는 처음부터 틀려 있었습니다.”

딥러닝에서 가장 무서운 오류는 프로그램을 멈추는 오류가 아닙니다.

코드는 정상 실행되지만 잘못된 방식으로 학습되는 오류입니다.

그러므로 PyTorch를 배운다는 것은 신경망 클래스를 외우는 일이 아니라 학습 과정 전체를 직접 관찰하고 통제하는 능력을 기르는 일입니다. 🔥

다음 편 예고

[Python 완전정복 시리즈 #31] OpenCV 완벽 이해하기 | Python으로 이미지와 영상을 분석하는 컴퓨터 비전 첫걸음

다음 시간에는 컴퓨터가 이미지와 영상을 읽고 처리하는 방법을 알아봅니다.

이미지 불러오기, 크기 변경, 자르기, 색상 공간 변환, 그리기, 임계처리, 윤곽선 검출, 얼굴 영역 처리, 웹캠 영상 분석까지 OpenCV를 이용해 실습해 보겠습니다.

#Python #파이썬 #Python강좌 #파이썬기초 #PyTorch #파이토치 #딥러닝 #DeepLearning #머신러닝 #인공지능 #Tensor #텐서 #Autograd #자동미분 #신경망 #CNN #Dataset #DataLoader #nnModule #GPU #CUDA #MPS #FashionMNIST #이미지분류 #모델학습 #역전파 #Optimizer #코딩공부 #프로그래밍

댓글

0

댓글을 불러오는 중입니다.