본문 바로가기

개발 공부/딥러닝

[Deep Learning] PyTorch 신경망 모델 직접 만들기

1. 신경망은 무엇을 쌓은 구조일까

신경망의 기본 연산은 여러 입력에 Weight를 곱하고 Bias를 더하는 선형 연산이다.
여기에 ReLU 같은 Activation Function을 적용하면 단순한 직선 관계만이 아니라 복잡한 비선형 패턴을 표현할 수 있다.

y = f(b + w1*x1 + w2*x2 + ... + wn*xn)

기호 의미
x 입력 feature
w 각 입력이 결과에 미치는 영향인 Weight
b 기준점을 이동시키는 Bias
f 비선형성을 더하는 Activation Function

 

입력 feature가 여러 Hidden Layer를 거쳐 출력으로 전달되는 신경망 구조

⠀
그림을 볼 때는 Layer별 입력과 출력부터 읽는다

한 Layer의 출력 개수는 다음 Layer의 입력 개수가 된다.
첫 Layer는 feature 개수의 영향을 받고, 마지막 Layer는 target 클래스 개수의 영향을 받는다.
그 사이 Hidden Layer의 크기는 모델 설계자가 정할 수 있다.

위치 input size output size
첫 Layer feature size hidden size
중간 Layer 이전 Layer의 output 다음 hidden size
마지막 Layer 이전 Layer의 output target size

📌 이해 보충 | FashionMNIST는 28×28 흑백 이미지이므로 한 장을 펼치면 feature size는 `1×28×28=784`다.
클래스가 10개이므로 마지막 output size는 10이어야 한다.

⠀

2. PyTorch 모델 클래스에 필요한 두 메서드

from torch import nn

class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
# 사용할 Layer를 준비

def forward(self, features):
# features가 Layer를 통과하는 순서를 정의
return prediction

메서드 역할
__init__ Layer와 모델 구성 요소를 객체 변수로 준비
forward 입력 feature가 각 Layer를 통과하는 계산 순서를 정의

`nn.Module`을 상속하면 PyTorch가 모델의 Parameter를 추적하고 CPU와 GPU 이동, 저장, 학습 모드 전환 같은 기능을 사용할 수 있다. `super().__init__()`은 부모 클래스인 `nn.Module`을 초기화하는 코드다.

📌 이해 보충 | 모델을 사용할 때 `model(features)`처럼 호출하면 PyTorch가 내부적으로 `forward(features)`를 실행한다. 따라서 보통 `model.forward(features)`를 직접 호출하지 않는다.

⠀
Model과 입력 Tensor는 같은 Device에 둔다

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

model = NeuralNetwork().to(device)
features = features.to(device)

GPU 모델의 Weight는 GPU 메모리에 있고 CPU Tensor는 CPU 메모리에 있다.
서로 다른 메모리에 있는 값은 바로 행렬곱을 할 수 없으므로 모델과 입력을 모두 같은 Device로 이동해야 한다.

⠀

3. 이미지가 Linear Layer에 들어가기 전에 Flatten이 필요한 이유

이미지 Batch의 Shape은 보통 `(B, C, H, W)`지만 `nn.Linear`는 한 샘플을 길이 하나인 Vector로 받는다.
따라서 Batch 차원은 유지하고 나머지 차원을 펼친다.

features.shape
# (10, 1, 28, 28)

flatten = nn.Flatten()
out = flatten(features)
out.shape
# (10, 784)

📌 이해 보충 | `10`은 Batch 크기이므로 유지된다.
한 이미지의 `1×28×28`만 곱해져 784가 된다.
전체 원소 수는 `10×1×28×28 = 10×784`로 그대로다.

📌 이해 보충 | `nn.Flatten()`의 기본값은 `start_dim=1`이다.
0번 차원인 Batch는 그대로 두고 1번 차원부터 마지막 차원까지만 펼치기 때문에 이미지 10장이 한 덩어리로 섞이지 않는다.

⠀

4. Linear와 ReLU는 Shape에 어떤 영향을 줄까

linear = nn.Linear(in_features=784, out_features=512)
out = linear(flattened_features)
# (batch, 512)

relu = nn.ReLU()
activated = relu(out)
# (batch, 512)

Layer 값 변화 Shape 변화
nn.Linear Weight와 Bias로 선형 계산 out_features에 맞춰 바뀜
nn.ReLU 음수는 0, 양수는 유지 바뀌지 않음

`nn.Linear(784, 512)`의 512는 첫 Hidden Layer에 있는 출력 뉴런 수다.
다음 Linear Layer는 이 512개 값을 입력으로 받아야 하므로 `nn.Linear(512, ...)`로 연결한다.

⠀
Weight와 Bias Shape은 왜 이렇게 생길까

layer = nn.Linear(in_features=784, out_features=512)

layer.weight.shape
# torch.Size([512, 784])

layer.bias.shape
# torch.Size([512])

출력값 512개는 각각 입력값 784개를 모두 사용해 계산된다.
따라서 Weight는 `출력 개수 × 입력 개수`, 즉 `(512, 784)`이고,
출력 뉴런마다 Bias가 하나씩 필요하므로 Bias는 `(512,)`다.

Parameter Shape 의미
Weight (512, 784) 512개 출력마다 784개 입력 Weight
Bias (512,) 512개 출력마다 Bias 1개

⠀
ReLU는 값을 바꾸지만 Shape은 바꾸지 않는다

Linear 출력 ReLU 출력
0.2826 0.2826
-0.4782 0
-0.0358 0
0.4889 0.4889

ReLU는 음수를 0으로 바꾸고 양수는 그대로 둔다.
값은 달라지지만 원소를 추가하거나 삭제하지 않으므로 `(batch, 512)`라는 Shape은 유지된다.

Linear 출력의 음수만 0으로 바꾸고 Shape은 유지하는 ReLU

⠀

5. Model V1은 계산 과정을 한 단계씩 보여준다

V1은 각 중간 결과를 변수로 저장하므로 Shape이 어떻게 변하는지 따라가기 쉽다.
대신 Layer가 많아지면 변수와 코드가 빠르게 늘어난다.

⠀
실행 결과

Model V1에서 Layer를 지날 때 변하는 출력 Shape

단계 Shape 해석
입력 (10, 1, 28, 28) 28×28 이미지 10장
Flatten 후 첫 Layer (10, 512) 이미지마다 512개 출력
두 번째 Layer (10, 1024) 이미지마다 1024개 출력
마지막 Layer (10, 10) 이미지마다 클래스 10개 점수

 

6. 핵심 정리

코드 역할
nn.Module PyTorch 모델의 부모 클래스
__init__ Layer를 생성하고 저장
forward 입력이 통과할 계산 순서 정의
nn.Flatten Batch를 유지하고 이미지 차원을 Vector로 펼침
nn.Linear 입력과 출력 크기를 바꾸는 선형 계산
nn.ReLU 음수를 0으로 바꾸어 비선형성 추가
.to(device) 모델과 데이터를 같은 연산 장치로 이동

모델을 만들 때 마음대로 정할 수 없는 것은 첫 Layer의 입력 크기와 마지막 Layer의 출력 크기다.
중간 Hidden Layer의 크기는 설계할 수 있지만, 앞 Layer의 출력이 다음 Layer의 입력과 정확히 이어져야 한다.

반응형