학습 루프의 빈칸을 채우는 일
앞 장에서 우리는 PyTorch가 계산 그래프를 따라 기울기를 자동으로 구해 준다는 사실을 배웠습니다. 이제 학습의 기본 리듬은 꽤 익숙해졌습니다.
optimizer.zero_grad()
pred = model(x)
loss = loss_fn(pred, y)
loss.backward()
optimizer.step()여기서 아직 제대로 설명하지 않은 주인공이 있습니다. 바로 x와 y입니다. 모델에 넣을 입력 x와 비교할 정답 y는 어디서 오는 걸까요?
처음에는 이렇게 직접 텐서를 만들어 넣을 수 있습니다.
x = torch.tensor([[1.0, 2.0]])
y = torch.tensor([[1.0]])하지만 실제 학습에서는 데이터가 수백 개, 수만 개, 때로는 수십억 개에 이릅니다. 이미지 파일은 폴더에 흩어져 있고, 텍스트는 문장 길이가 제각각이며, 정답 라벨은 별도의 표 파일에 들어 있기도 합니다. 이런 데이터를 매번 직접 꺼내서 모델에 넣는다면 학습 코드는 금세 엉망이 됩니다.
그래서 PyTorch는 데이터를 다루는 역할을 두 가지로 나누었습니다. 하나는 데이터셋이고, 다른 하나는 데이터로더입니다.
데이터셋은 "전체 데이터 중에서 하나를 꺼내면 무엇을 돌려줄 것인가?"를 정의합니다. 데이터로더는 "그 하나하나를 모아 배치로 만들고, 순서를 섞고, 학습 루프에 안정적으로 전달하는 일"을 맡습니다.
음식점으로 비유하면 이렇습니다. 데이터셋은 냉장고와 조리대입니다. 주문이 들어오면 재료 하나를 꺼내 손질해 내놓습니다. 데이터로더는 서빙 담당입니다. 한 접시씩 가져갈 수도 있고, 여러 접시를 한 쟁반에 담아 가져갈 수도 있으며, 바쁜 시간에는 여러 사람이 나누어 서빙할 수도 있습니다.
좋은 학습 루프는 모델 코드만으로 만들어지지 않습니다. 모델이 먹을 데이터를 일정한 모양으로, 적절한 단위로, 흔들림 없이 공급하는 구조에서 시작됩니다.

Dataset은 샘플 하나를 꺼내는 약속이다
PyTorch에서 Dataset은 보통 다음 두 가지 질문에 답하는 객체입니다.
"데이터가 총 몇 개인가?"
"특정 번호의 데이터를 달라고 하면 입력과 정답을 어떻게 돌려줄 것인가?"
이 두 질문은 코드에서 각각 __len__과 __getitem__으로 표현됩니다.
from torch.utils.data import Dataset
class MyDataset(Dataset):
def __len__(self):
return 데이터_개수
def __getitem__(self, idx):
x = idx번째_입력
y = idx번째_정답
return x, y__len__은 데이터셋의 길이를 알려 줍니다. len(dataset)을 호출했을 때 이 함수가 실행됩니다.
__getitem__은 하나의 샘플을 꺼냅니다. dataset[0], dataset[1]처럼 인덱스로 접근했을 때 이 함수가 실행됩니다. 중요한 점은 __getitem__이 보통 샘플 하나를 반환한다는 것입니다. 배치를 만드는 일은 아직 하지 않습니다. 배치는 뒤에서 만날 DataLoader가 담당합니다.
가장 단순한 예제를 보겠습니다. 공부 시간으로 시험 합격 여부를 예측하는 작은 데이터셋을 만든다고 해 봅시다.
import torch
from torch.utils.data import Dataset
class StudyDataset(Dataset):
def __init__(self):
self.x = torch.tensor([
[1.0],
[2.0],
[3.0],
[4.0],
[5.0],
])
self.y = torch.tensor([
[0.0],
[0.0],
[1.0],
[1.0],
[1.0],
])
def __len__(self):
return len(self.x)
def __getitem__(self, idx):
return self.x[idx], self.y[idx]이제 데이터셋을 만들어 직접 하나를 꺼내 볼 수 있습니다.
dataset = StudyDataset()
print(len(dataset)) # 5
print(dataset[0]) # (tensor([1.]), tensor([0.]))
print(dataset[3]) # (tensor([4.]), tensor([1.]))dataset[0]은 첫 번째 입력과 첫 번째 정답을 함께 반환합니다. 모델 입장에서는 x만 있으면 예측할 수 있지만, 학습하려면 예측이 맞았는지 비교할 y도 필요합니다. 그래서 지도 학습에서는 대개 데이터셋이 (입력, 정답) 쌍을 반환합니다.
이 구조가 익숙해지면 이미지, 텍스트, 음성 데이터도 결국 같은 흐름으로 볼 수 있습니다. 파일에서 이미지를 읽든, 문장을 숫자로 바꾸든, 최종적으로는 __getitem__이 "이 인덱스의 입력과 정답은 이것입니다"라고 돌려주면 됩니다.
TensorDataset으로 빠르게 묶기
모든 데이터가 이미 텐서로 준비되어 있다면 직접 클래스를 만들지 않아도 됩니다. PyTorch에는 여러 텐서를 같은 인덱스 기준으로 묶어 주는 TensorDataset이 있습니다.
from torch.utils.data import TensorDataset
x = torch.tensor([
[1.0],
[2.0],
[3.0],
[4.0],
[5.0],
])
y = torch.tensor([
[0.0],
[0.0],
[1.0],
[1.0],
[1.0],
])
dataset = TensorDataset(x, y)
print(dataset[2])TensorDataset은 첫 번째 텐서의 idx번째 값과 두 번째 텐서의 idx번째 값을 함께 반환합니다. 위 예제에서 dataset[2]는 x[2]와 y[2]를 묶어서 돌려줍니다.
데이터가 작고 이미 숫자로 정리되어 있을 때는 TensorDataset이 편합니다. 하지만 실제 프로젝트에서는 파일을 읽거나, 이미지를 변환하거나, 텍스트를 토큰으로 바꾸는 과정이 필요합니다. 그때는 직접 Dataset 클래스를 만드는 편이 훨씬 유연합니다.
DataLoader는 샘플을 배치로 묶는다
이제 데이터셋에서 샘플 하나를 꺼낼 수 있게 되었습니다. 하지만 학습할 때는 보통 하나씩 넣지 않습니다. 앞에서 배운 것처럼 딥러닝은 데이터를 몇 개씩 묶은 배치 단위로 학습하는 경우가 많습니다.
배치 크기가 2라면 다음과 같은 일이 일어납니다.
첫 번째 배치에는 2개의 샘플이 들어갑니다. 두 번째 배치에도 2개의 샘플이 들어갑니다. 데이터가 5개라면 마지막 배치에는 1개의 샘플만 남을 수 있습니다.
이 일을 대신해 주는 도구가 DataLoader입니다.
from torch.utils.data import DataLoader
loader = DataLoader(dataset, batch_size=2, shuffle=True)
for batch_x, batch_y in loader:
print(batch_x, batch_y)DataLoader는 데이터셋에서 샘플을 하나씩 꺼낸 뒤, 지정한 batch_size만큼 모아 하나의 배치 텐서로 만들어 줍니다. 앞에서 dataset[0]이 tensor([1.])처럼 샘플 하나를 반환했다면, DataLoader는 여러 샘플을 쌓아서 tensor([[1.], [4.]]) 같은 배치를 만들어 줍니다.
여기서 중요한 변화가 있습니다. 샘플 하나의 모양이 [1]이었다면, 배치의 모양은 [배치_크기, 1]이 됩니다. 즉, 배치 차원이 맨 앞에 생깁니다.
for batch_x, batch_y in loader:
print(batch_x.shape, batch_y.shape)출력은 대략 다음과 비슷합니다.
torch.Size([2, 1]) torch.Size([2, 1])
torch.Size([2, 1]) torch.Size([2, 1])
torch.Size([1, 1]) torch.Size([1, 1])마지막 배치의 크기가 1인 이유는 전체 데이터가 5개이고, 배치 크기가 2이기 때문입니다. 마지막에 하나가 남는 것이 싫다면 drop_last=True를 사용할 수 있습니다.
loader = DataLoader(
dataset,
batch_size=2,
shuffle=True,
drop_last=True,
)이렇게 하면 마지막에 남는 불완전한 배치를 버립니다. 배치 정규화처럼 배치 크기에 민감한 기법을 쓸 때 유용할 수 있지만, 데이터가 적을 때는 샘플을 버리는 것이 아까울 수 있습니다.
셔플은 모델의 암기를 방해한다
DataLoader에서 자주 사용하는 옵션 중 하나가 shuffle=True입니다. 셔플은 데이터를 학습할 때마다 섞는다는 뜻입니다.
왜 섞어야 할까요?
데이터가 정렬되어 있다고 상상해 봅시다. 고양이 이미지 5,000장 다음에 강아지 이미지 5,000장이 이어져 있습니다. 모델이 앞부분만 보면서 한동안 "세상에는 고양이만 있구나"라고 학습하다가, 나중에는 갑자기 "이제 강아지만 있네?"라고 혼란스러워질 수 있습니다.
물론 모델이 실제로 그렇게 말하지는 않습니다. 하지만 경사하강법 관점에서 보면 데이터의 순서가 기울기의 방향을 불필요하게 흔들 수 있습니다. 배치마다 비슷한 샘플만 들어가면, 모델은 전체 데이터의 평균적인 방향이 아니라 특정 구간에 치우친 방향으로 움직이게 됩니다.
그래서 학습 데이터는 보통 섞습니다.
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)반대로 검증 데이터는 보통 섞지 않아도 됩니다.
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)검증은 모델을 업데이트하지 않고 성능만 확인하는 과정입니다. 순서가 성능 계산에 영향을 주지 않는다면 굳이 섞을 필요가 없습니다. 특히 예측 결과를 원본 데이터 순서와 맞춰 분석하고 싶다면 검증 데이터는 섞지 않는 편이 좋습니다.
DataLoader는 이 밖에도 데이터를 더 빠르게 공급하기 위한 옵션을 제공합니다.
train_loader = DataLoader(
train_dataset,
batch_size=32,
shuffle=True,
num_workers=2,
pin_memory=True,
)num_workers는 데이터를 불러오는 작업을 몇 개의 별도 프로세스로 나누어 처리할지 정합니다. 이미지 파일을 디스크에서 읽고 변환하는 작업이 느릴 때 도움이 됩니다. pin_memory=True는 GPU로 데이터를 옮길 때 전송을 더 효율적으로 만들 수 있는 옵션입니다. 다만 환경에 따라 효과가 다르고, 특히 운영체제나 노트북 환경에서는 num_workers를 너무 크게 잡으면 오히려 문제가 생길 수도 있습니다. 처음에는 0 또는 2 정도에서 시작해 보는 편이 안전합니다.
학습 데이터와 검증 데이터는 왜 나누어야 할까
모델을 학습시키는 목적은 학습 데이터의 정답을 외우는 것이 아닙니다. 우리가 원하는 것은 처음 보는 데이터에도 그럴듯한 예측을 하는 능력입니다. 이것을 일반화 성능이라고 부릅니다.
일반화 성능을 확인하려면 모델이 학습 중에 직접 보지 않은 데이터가 필요합니다. 그래서 전체 데이터를 보통 학습 데이터와 검증 데이터로 나눕니다.
학습 데이터는 모델의 가중치를 업데이트하는 데 사용합니다. 검증 데이터는 학습된 모델이 처음 보는 데이터에 얼마나 잘 맞는지 확인하는 데 사용합니다.
PyTorch에서는 random_split을 사용해 데이터셋을 나눌 수 있습니다.
from torch.utils.data import random_split
full_dataset = StudyDataset()
train_size = int(len(full_dataset) * 0.8)
val_size = len(full_dataset) - train_size
train_dataset, val_dataset = random_split(
full_dataset,
[train_size, val_size],
)그 다음 각각의 데이터로더를 만듭니다.
train_loader = DataLoader(
train_dataset,
batch_size=2,
shuffle=True,
)
val_loader = DataLoader(
val_dataset,
batch_size=2,
shuffle=False,
)이제 학습 루프에서는 train_loader로 모델을 업데이트하고, 검증 루프에서는 val_loader로 성능을 측정할 수 있습니다. 다음 장에서 이 두 흐름을 하나의 학습 루프로 묶어 보겠습니다.
데이터를 나눌 때 한 가지 조심할 점이 있습니다. 분류 문제에서는 각 클래스가 학습 데이터와 검증 데이터에 적절히 섞여 있어야 합니다. 예를 들어 숫자 분류 문제에서 검증 데이터에 숫자 9가 하나도 없다면, 모델이 9를 잘 맞히는지 확인할 방법이 없습니다. 데이터가 작거나 클래스 불균형이 심할 때는 단순히 무작위로 나누는 것만으로 충분하지 않을 수 있습니다.
이미지 데이터는 픽셀을 텐서로 바꾼다
이미지는 사람에게는 그림이지만, 모델에게는 숫자 배열입니다. 흑백 이미지는 보통 높이와 너비를 가진 2차원 숫자 배열로 볼 수 있습니다. 컬러 이미지는 여기에 빨강, 초록, 파랑 채널이 더해져 3차원 숫자 배열이 됩니다.
예를 들어 32×32 크기의 컬러 이미지는 개념적으로 다음과 같은 모양을 가집니다.
[채널, 높이, 너비] = [3, 32, 32]PyTorch의 이미지 모델은 보통 채널이 앞에 오는 형태를 사용합니다. 반면 이미지 파일을 읽는 라이브러리 중에는 [높이, 너비, 채널] 형태로 이미지를 다루는 경우가 많습니다. 그래서 이미지 전처리에서는 차원 순서를 바꾸는 일이 자주 등장합니다.
이미지 전처리에서 흔히 하는 작업은 세 가지입니다.
이미지를 텐서로 변환한다.
픽셀 값을 적절한 범위로 조정한다.
모든 이미지의 크기를 모델이 받을 수 있는 일정한 크기로 맞춘다.
픽셀 값은 보통 0부터 255 사이의 정수입니다. 하지만 신경망에는 0부터 1 사이의 실수나 평균과 표준편차로 정규화된 값이 더 다루기 좋습니다. 값의 범위가 너무 크면 학습이 불안정해질 수 있기 때문입니다.
아주 단순한 이미지 데이터셋의 형태를 상상해 보겠습니다. 여기서는 실제 이미지 파일을 읽는 대신, 구조만 확인하는 예제를 보겠습니다.
class SimpleImageDataset(Dataset):
def __init__(self, images, labels, transform=None):
self.images = images
self.labels = labels
self.transform = transform
def __len__(self):
return len(self.images)
def __getitem__(self, idx):
image = self.images[idx]
label = self.labels[idx]
if self.transform is not None:
image = self.transform(image)
return image, label여기서 transform은 이미지를 모델에 넣기 전에 바꾸는 함수입니다. 이미지 크기를 바꾸거나, 텐서로 변환하거나, 픽셀 값을 정규화하는 작업을 이곳에 넣을 수 있습니다.
핵심은 Dataset 안에서 모든 전처리를 끝내도 된다는 점입니다. 학습 루프는 굳이 "이미지를 어떻게 읽고 어떻게 바꾸는지" 알 필요가 없습니다. 학습 루프는 그저 for x, y in train_loader:로 배치를 받아 모델에 넣으면 됩니다. 데이터 준비의 복잡함을 데이터셋 안에 가두는 것이 좋은 구조입니다.
텍스트 데이터는 먼저 숫자가 되어야 한다
텍스트는 이미지보다 한 단계 더 낯설 수 있습니다. 이미지는 픽셀이라는 숫자로 출발하지만, 텍스트는 문자와 단어로 출발합니다. 신경망은 문자열을 그대로 계산할 수 없습니다. 그래서 텍스트는 먼저 숫자 ID로 바뀌어야 합니다.
예를 들어 다음 문장이 있다고 해 봅시다.
나는 딥러닝을 배운다이 문장을 단어 단위로 나누고, 각 단어에 번호를 붙이면 다음처럼 표현할 수 있습니다.
vocab = {
"<pad>": 0,
"<unk>": 1,
"나는": 2,
"딥러닝을": 3,
"배운다": 4,
}
sentence = "나는 딥러닝을 배운다"
token_ids = [2, 3, 4]여기서 vocab은 단어와 숫자 ID를 연결하는 사전입니다. <pad>는 길이를 맞추기 위한 특별한 토큰이고, <unk>는 사전에 없는 단어를 뜻합니다.
간단한 텍스트 분류 데이터셋을 만들어 보겠습니다.
class TextDataset(Dataset):
def __init__(self, texts, labels, vocab, max_length):
self.texts = texts
self.labels = labels
self.vocab = vocab
self.max_length = max_length
def __len__(self):
return len(self.texts)
def encode(self, text):
tokens = text.split()
ids = [self.vocab.get(token, self.vocab["<unk>"]) for token in tokens]
ids = ids[:self.max_length]
pad_len = self.max_length - len(ids)
ids = ids + [self.vocab["<pad>"]] * pad_len
return torch.tensor(ids, dtype=torch.long)
def __getitem__(self, idx):
x = self.encode(self.texts[idx])
y = torch.tensor(self.labels[idx], dtype=torch.long)
return x, y여기서는 문장을 공백 기준으로 나누었습니다. 실제 자연어 처리에서는 더 정교한 토큰화가 필요할 수 있지만, 기본 아이디어는 같습니다. 문장을 토큰으로 나누고, 토큰을 숫자 ID로 바꿉니다.
중요한 점은 텍스트 ID의 자료형입니다. 임베딩 레이어는 보통 단어 ID를 입력으로 받습니다. 이때 ID는 실수가 아니라 정수여야 하므로 dtype=torch.long을 사용합니다. 나중에 임베딩 레이어를 배울 때 이 숫자 ID가 어떻게 촘촘한 벡터 표현으로 바뀌는지 다시 만나게 됩니다.
길이가 다른 데이터와 collate_fn
이미지는 크기를 맞추면 대부분 같은 모양으로 배치를 만들 수 있습니다. 하지만 텍스트는 문장마다 길이가 다릅니다. "좋아요"처럼 짧은 문장도 있고, 여러 문장으로 이루어진 긴 리뷰도 있습니다.
DataLoader는 기본적으로 샘플들을 같은 차원으로 쌓으려고 합니다. 그런데 샘플마다 길이가 다르면 그대로는 배치를 만들 수 없습니다.
예를 들어 한 문장은 길이가 3이고 다른 문장은 길이가 7이라면, 단순히 하나의 텐서로 쌓기 어렵습니다.
[2, 5, 9]
[4, 8, 1, 3, 6, 7, 10]이 문제를 해결하는 방법 중 하나는 모든 문장을 미리 같은 길이로 자르거나 패딩하는 것입니다. 앞의 TextDataset 예제에서는 max_length를 정해 모든 문장을 같은 길이로 만들었습니다.
또 다른 방법은 배치를 만드는 순간에 길이를 맞추는 것입니다. 이때 사용하는 것이 collate_fn입니다. collate는 여러 샘플을 하나의 배치로 모으는 과정을 뜻합니다.
from torch.nn.utils.rnn import pad_sequence
def text_collate_fn(batch):
xs, ys = zip(*batch)
xs = [torch.tensor(x, dtype=torch.long) for x in xs]
ys = torch.tensor(ys, dtype=torch.long)
padded_xs = pad_sequence(
xs,
batch_first=True,
padding_value=0,
)
return padded_xs, ys그리고 DataLoader에 넘깁니다.
loader = DataLoader(
dataset,
batch_size=4,
shuffle=True,
collate_fn=text_collate_fn,
)collate_fn은 데이터셋이 반환한 샘플 여러 개를 받아 배치로 만드는 사용자 정의 함수입니다. 텍스트처럼 길이가 들쭉날쭉한 데이터를 다룰 때 매우 유용합니다.
처음 딥러닝을 배울 때는 collate_fn까지 바로 능숙하게 쓸 필요는 없습니다. 다만 기억해 두면 좋습니다. Dataset은 샘플 하나를 만들고, DataLoader는 배치를 만들며, 배치를 만드는 방식이 특별해야 할 때 collate_fn이 끼어듭니다.
데이터 파이프라인을 하나로 연결하기
이제 작은 예제를 통해 전체 흐름을 연결해 보겠습니다. 데이터셋을 만들고, 학습용과 검증용으로 나누고, 각각 데이터로더를 만드는 코드입니다.
import torch
from torch.utils.data import Dataset, DataLoader, random_split
class StudyDataset(Dataset):
def __init__(self):
self.x = torch.tensor([
[1.0], [2.0], [3.0], [4.0], [5.0],
[6.0], [7.0], [8.0], [9.0], [10.0],
])
self.y = torch.tensor([
[0.0], [0.0], [0.0], [0.0], [1.0],
[1.0], [1.0], [1.0], [1.0], [1.0],
])
def __len__(self):
return len(self.x)
def __getitem__(self, idx):
return self.x[idx], self.y[idx]
full_dataset = StudyDataset()
train_size = int(len(full_dataset) * 0.8)
val_size = len(full_dataset) - train_size
train_dataset, val_dataset = random_split(
full_dataset,
[train_size, val_size],
generator=torch.Generator().manual_seed(42),
)
train_loader = DataLoader(
train_dataset,
batch_size=4,
shuffle=True,
)
val_loader = DataLoader(
val_dataset,
batch_size=4,
shuffle=False,
)마지막으로 데이터로더가 실제로 어떤 배치를 내놓는지 확인해 봅시다.
for batch_x, batch_y in train_loader:
print("입력 모양:", batch_x.shape)
print("정답 모양:", batch_y.shape)
print(batch_x)
print(batch_y)
break이 확인 과정은 생각보다 중요합니다. 학습이 잘 안 될 때 원인은 모델이 아니라 데이터 모양인 경우가 많습니다. 입력의 차원이 모델이 기대한 것과 다르거나, 정답의 자료형이 손실 함수와 맞지 않거나, 이미지 값의 범위가 이상한 경우가 흔합니다.
딥러닝 디버깅의 첫 번째 습관은 "배치 하나를 직접 눈으로 확인하기"입니다. 모델을 의심하기 전에 데이터가 제대로 들어오는지 먼저 봐야 합니다.
손실 함수와 정답 자료형 맞추기
데이터로더가 반환하는 y는 손실 함수와 잘 맞아야 합니다. 이 부분은 초보자가 자주 막히는 지점입니다.
이진 분류에서 BCEWithLogitsLoss를 쓴다면 모델 출력과 정답이 비슷한 모양의 실수 텐서인 경우가 많습니다.
# pred.shape == [batch_size, 1]
# y.shape == [batch_size, 1]
y = y.float()반면 여러 클래스 중 하나를 맞히는 다중 분류에서 CrossEntropyLoss를 쓴다면 정답은 원-핫 벡터가 아니라 클래스 번호인 경우가 일반적입니다.
# pred.shape == [batch_size, num_classes]
# y.shape == [batch_size]
y = y.long()예를 들어 숫자 0부터 9까지 분류하는 문제에서 정답은 [0, 3, 9, 1] 같은 형태입니다. [1, 0, 0, 0, ...]처럼 원-핫으로 직접 바꾸지 않아도 됩니다.
데이터셋을 만들 때는 입력뿐 아니라 정답의 모양과 자료형도 함께 설계해야 합니다. 데이터로더는 있는 그대로 묶어 줄 뿐입니다. 잘못된 모양의 정답을 좋은 모양으로 추측해서 바꿔 주지는 않습니다.
좋은 데이터 공급 구조의 조건
모델 학습이 하나의 공장이라면, 데이터 파이프라인은 컨베이어 벨트입니다. 컨베이어 벨트가 멈추거나, 재료가 뒤섞이거나, 엉뚱한 크기의 부품이 들어오면 아무리 좋은 기계도 제대로 작동할 수 없습니다.
좋은 데이터 공급 구조는 대체로 다음 조건을 만족합니다.
Dataset이 샘플 하나의 입력과 정답을 일관된 형식으로 반환한다.DataLoader가 적절한 배치 크기와 셔플 설정으로 데이터를 공급한다.학습 데이터와 검증 데이터가 분리되어 일반화 성능을 확인할 수 있다.
이미지와 텍스트가 모델이 계산할 수 있는 텐서 형태로 변환된다.
배치 하나를 출력해 보았을 때 입력 모양, 정답 모양, 자료형이 예상과 맞다.
이 조건들이 갖추어지면 학습 루프는 훨씬 단순해집니다. 학습 루프는 더 이상 파일을 읽거나, 문장을 자르거나, 이미지를 변환하는 세부 작업에 신경 쓰지 않아도 됩니다. 그저 데이터로더에서 배치를 받고, 모델에 넣고, 손실을 계산하고, 역전파하면 됩니다.
이 분리는 나중에 모델이 커질수록 더 큰 힘을 발휘합니다. MLP로 숫자를 분류할 때도, CNN으로 이미지를 분류할 때도, RNN으로 문장을 읽을 때도, Transformer로 토큰 시퀀스를 처리할 때도 기본 구조는 같습니다.
데이터셋은 하나를 꺼냅니다. 데이터로더는 여러 개를 묶습니다. 모델은 배치를 보고 예측합니다. 손실 함수는 틀린 정도를 계산합니다. 역전파는 어느 가중치를 얼마나 고쳐야 할지 알려 줍니다.
이제 퍼즐 조각이 거의 모였습니다. 다음 장에서는 이 조각들을 하나의 반복 구조로 묶어 실제 학습 루프를 완성합니다. 모델은 배치를 받고, 예측하고, 틀리고, 고치고, 다시 예측할 것입니다. 딥러닝이 살아 움직이는 장면이 드디어 코드 한가운데 펼쳐집니다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.