본문 바로가기
두두 IT/딥러닝

04. 첫번째 딥러닝-MLP 구현

by DoDo's 2026. 5. 26.
반응형

1. 이론적 배경: 다층 퍼셉트론(MLP)과 이미지 데이터

① MNIST 이미지 데이터의 특징

  • 컴퓨터에게 이미지는 그저 '숫자들이 적힌 바둑판'일 뿐입니다. MNIST 데이터는 가로 28칸, 세로 28칸($28 \times 28$)으로 이루어진 흑백 이미지입니다.
  • 즉, 사진 1장에는 총 $784$개의 픽셀(점)이 있고, 각 픽셀은 0(검은색)부터 255(흰색) 사이의 밝기 값을 가집니다.

② 왜 층(Layer)을 여러 개 쌓을까요? (Deep Learning)

  • 이전처럼 입력층과 출력층만 딱 하나씩 있는 모델(단층)은 복잡한 문제를 풀지 못합니다.
  • 층을 깊게 쌓으면 모델이 점진적으로 똑똑해집니다.
    • 1번째 층: "어? 여기 직선이 있네, 저긴 곡선이 있네." (단순 선/색상 파악)
    • 2번째 층: "직선이랑 곡선이 합쳐지니 동그라미 모양이 되네!" (부분적인 형태 파악)
    • 마지막 층: "동그라미 두 개가 위아래로 붙어있으니 이건 숫자 '8'이구나!" (최종 판단)

2. 코드 분석 1: 데이터 준비 (DataLoader & ToTensor)

Python
 
trainset = datasets.MNIST(root=dataset_dir, download=True, transform=transforms.ToTensor())
  • transforms.ToTensor()의 2가지 마법:
    1. 일반적인 이미지 파일 포맷을 파이토치가 계산할 수 있는 Tensor 객체로 바꿉니다.
    2. 0~255 사이의 픽셀 값을 255로 나누어 0.0 ~ 1.0 사이의 실수로 정규화(Scaling)합니다. 이렇게 숫자의 덩치를 줄여주면 모델이 훨씬 빠르고 정확하게 학습합니다.
Python
 
train_loader = DataLoader(trainset, batch_size=batch_size, shuffle=True)
  • batch_size=256: 6만 장의 사진을 한 번에 학습하면 컴퓨터 메모리가 터집니다. 그래서 "한 번에 256장씩만 묶어서 던져줘!"라고 컨베이어 벨트를 설정하는 것입니다.

3. 코드 분석 2: 모델 조립하기 (Subclass 방식)

가장 핵심이 되는 신경망 모델 설계도(MNISTModel)입니다.

Python
 
def __init__(self):
    self.lr1 = nn.Linear(784, 128)
    self.lr2 = nn.Linear(128, 64)  
    self.lr3 = nn.Linear(64, 32)   
    self.lr4 = nn.Linear(32, 10)   # 출력층: 10개 (숫자 0~9)
  • $784$가 들어가는 이유: $28 \times 28$ 사이즈의 이미지를 한 줄로 길게 펴면 784개의 픽셀이 되기 때문입니다.
  • 처음 784개의 데이터가 128개 $\rightarrow$ 64개 $\rightarrow$ 32개로 점점 압축되며 핵심 정보만 남기고, 마지막에 0부터 9까지 10개의 확률표로 변환됩니다.
Python
 
def forward(self, X):
    X = torch.flatten(X, start_dim=1) # 2D 이미지를 1D로 쫙 펴기
    X = nn.ReLU()(self.lr1(X))        # 비선형 활성화 함수
    ...
  • torch.flatten: 바둑판(2D) 모양의 이미지를 nn.Linear에 넣기 위해 784칸짜리 긴 막대기(1D) 모양으로 납작하게 펴주는(Flatten) 작업입니다.
  • nn.ReLU() (매주 중요!): 층과 층 사이에 들어가는 필터입니다. 음수 값은 0으로 무시하고 양수만 통과시킵니다. 이 ReLU가 있어야만 신경망이 단순한 직선을 넘어 구불구불한 복잡한 패턴(비선형)을 학습할 수 있습니다.

4. 코드 분석 3: 손실 함수와 옵티마이저

Python
 
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
  • CrossEntropyLoss: 다중 분류(여러 개의 보기 중 하나를 고르는 객관식 문제)에서 오차를 계산하는 전용 채점기입니다. 이 함수 내부에는 각 숫자가 될 확률을 백분율(%)로 예쁘게 바꿔주는 Softmax 기능이 내장되어 있습니다.
  • Adam: 기존에 쓰던 SGD보다 훨씬 똑똑하게 길을 찾는 최신 네비게이션(최적화 알고리즘)입니다. 방향과 보폭을 스스로 조절해가며 정답을 빠르게 찾아냅니다.

이후 학습 루프(Train/Validation) 코드는 이전에 배우셨던 5단계(추론 $\rightarrow$ 오차 계산 $\rightarrow$ 미분 $\rightarrow$ 가중치 업데이트 $\rightarrow$ 초기화)가 그대로 적용된 것입니다. 차이점이 있다면, 실전에서는 모델의 상태를 학습 모드(model.train())와 평가 모드(model.eval())로 스위치 켜듯 바꿔주어야 한다는 점입니다.


1단계: 하드웨어 설정과 데이터 준비 (Dataloader)

1. 하드웨어(Device) 설정

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

딥러닝은 엄청난 양의 행렬 곱셈을 해야 하므로 CPU보다 GPU(그래픽카드)가 수십 배 빠릅니다. 이 코드는 "내 컴퓨터에 NVIDIA GPU(cuda)나 맥북 칩(mps)이 있으면 그걸 쓰고, 없으면 그냥 CPU로 돌려라!"라고 컴퓨터에게 똑똑하게 작업장(메모리)을 배정해 주는 코드입니다.

2. 데이터 가져오기 (datasets & ToTensor)

Python
 
trainset = datasets.MNIST(root=dataset_dir, download=True, transform=transforms.ToTensor())
  • MNIST: 0부터 9까지 흑백 손글씨 이미지 7만 장(학습용 6만, 테스트용 1만)이 모여있는 아주 유명한 데이터셋입니다.
  • transforms.ToTensor() (핵심!): 우리가 아는 이미지 파일(JPG, PNG)은 컴퓨터가 바로 계산할 수 없습니다. 이 도구는 1) 이미지를 파이토치용 Tensor로 바꾸고, 2) 0~255 사이인 픽셀 색상값을 0.0 ~ 1.0 사이의 실수로 압축(정규화)해 줍니다. 모델이 훨씬 학습을 잘하게 도와주는 필수 전처리입니다.

3. 컨베이어 벨트 만들기 (DataLoader)

Python
 
train_loader = DataLoader(trainset, batch_size=batch_size, shuffle=True)

6만 장의 시험지를 모델에게 한 번에 던져주면 컴퓨터가 과로로 터져버립니다.

  • batch_size=256: "한 번에 256장씩만 묶어서 던져줘!"라는 뜻입니다.
  • shuffle=True: "학습할 때마다 시험지 순서를 마구 섞어줘!" (답을 순서대로 외우는 꼼수를 방지합니다)

2단계: 신경망 모델 조립하기 (Subclass 방식)

이전 선형 회귀에서는 nn.Linear 딱 하나만 썼지만, 이번엔 이미지를 분석해야 하므로 층(Layer)을 여러 개 깊게 쌓습니다. (그래서 '딥' 러닝입니다!)

Python
 
class MNISTModel(nn.Module):
    def __init__(self):
        super().__init__() 
        self.lr1 = nn.Linear(784, 128) # 28x28=784 픽셀을 128개로 압축
        self.lr2 = nn.Linear(128, 64)  
        self.lr3 = nn.Linear(64, 32)   
        self.lr4 = nn.Linear(32, 10)   # 마지막은 반드시 10개! (0~9 숫자)
  • __init__ (부품 창고): 모델을 구성할 부품(nn.Linear)들을 미리 사서 창고에 넣어둡니다. 마지막 출력이 10인 이유는 정답(클래스)이 10개이기 때문입니다.
Python
 
    def forward(self, X):
        X = torch.flatten(X, start_dim=1) 
        X = nn.ReLU()(self.lr1(X))
        ...
        output = self.lr4(X)
        return output
  • forward (조립 라인): 창고에 있는 부품을 꺼내 데이터(X)가 지나갈 길을 만들어줍니다.
  • torch.flatten: 이미지는 가로x세로(28x28)인 2차원 네모 모양인데, nn.Linear 기계는 일렬로 선 1차원 데이터만 받을 수 있습니다. 그래서 네모난 이미지를 784개의 픽셀을 가진 긴 막대기 형태로 쫙 펴주는 작업입니다.
  • nn.ReLU(): 활성화 함수라고 부릅니다. 데이터가 층을 통과할 때 "음수 값이 나오면 다 0으로 무시하고, 양수만 그대로 통과시켜!"라는 똑똑한 필터입니다. 이 녀석이 들어가야 모델이 복잡한 패턴(곡선 등)을 학습할 수 있습니다.

3단계: 훈련 세팅 (Loss와 Optimizer)

Python
 
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
  • CrossEntropyLoss: 예전에는 오차를 구할 때 평균 제곱 오차(MSE)를 썼죠? 하지만 지금처럼 "이 사진이 0부터 9중에 뭐게?"라고 객관식(다중 분류) 문제를 풀 때는 반드시 이 함수를 써야 합니다.
  • Adam: 최적화 반장님을 SGD에서 Adam으로 업그레이드했습니다. 훨씬 더 빠르고 똑똑하게 가중치를 찾아가는 일종의 '네비게이션' 같은 알고리즘입니다.

4단계: 대망의 학습 루프 (Train & Validation)

코드가 가장 길지만, 자세히 보면 완전히 똑같은 패턴이 두 번 반복됩니다. 바로 학습(Train)과 모의고사 검증(Validation)입니다.

① 학습 모드 (model.train())

Python
 
for X_train, y_train in train_loader:
    X_train, y_train = X_train.to(device), y_train.to(device) # GPU로 올리기
    
    pred = model(X_train)       # 1. 추론
    loss = loss_fn(pred, y_train) # 2. 오차 계산
    loss.backward()             # 3. 미분 계산
    optimizer.step()            # 4. 가중치 업데이트
    optimizer.zero_grad()       # 5. 미분값 초기화

이전에 배우셨던 '딥러닝의 심장 5단계'가 그대로 들어있죠? DataLoader가 주는 256장(1 batch)의 이미지를 받아서, 맞히고, 혼나고(loss), 고치는(step) 과정을 끝없이 반복합니다.

② 검증 모드 (model.eval() & torch.no_grad())

Python
 
with torch.no_grad():
    for X_valid, y_valid in test_loader:
        ...
        pred_valid_class = pred_valid.argmax(dim=-1)

한 번 학습이 끝나면, 모델이 진짜 똑똑해졌는지 학습하지 않은 새로운 데이터(test_loader)로 모의고사를 봅니다.

  • model.eval(): "이제 시험 볼 거니까 학습할 때 쓰던 컨닝페이퍼 덮어!" 하고 평가 모드로 바꿉니다.
  • torch.no_grad(): 미분 엔진 끄기 (학습 안 할 거니까 메모리 절약)
  • argmax(dim=-1): 모델은 0~9번 숫자에 대해 각각의 확률을 내뱉습니다. 그중 "가장 확률이 높은(arg MAX) 번호표" 하나를 최종 정답으로 고르는 함수입니다.

5단계: 모델 저장/불러오기 및 새로운 이미지 예측

학습이 다 끝났는데 컴퓨터를 끄면 그동안 똑똑해진 지식(가중치)이 다 날아가겠죠?

  • torch.save(model, "mnist_model.pt"): 똑똑해진 뇌(모델)를 파일로 내 PC에 영구 저장합니다.
  • torch.load(...): 나중에 다시 파이썬을 켜서 그 뇌를 그대로 불러옵니다.

마지막의 load_data 함수는 정말 실무적인 코드입니다! 인터넷에서 다운받거나 직접 그림판으로 그린 숫자 사진(.jpg, .png)을 파이썬으로 불러와서,

  1. 흑백으로 바꾸고 (.convert('L'))
  2. 모델이 배운 사이즈인 28x28로 줄이고 (.resize())
  3. 텐서로 변환해서 (ToTensor()) 불러온 모델에게 "이거 무슨 숫자게?" 하고 물어보는(Predict) 최종 응용 단계입니다.
반응형

'두두 IT > 딥러닝' 카테고리의 다른 글

03. 선형회귀 pytorch_linear_regression  (0) 2026.05.26
02. tensor 다루기  (0) 2026.05.26
01. 딥러닝 개요 (Deep Learning Overview)  (1) 2026.05.22