지난 글에서 단일 퍼셉트론이 XOR을 풀지 못하는 한계를 확인했다. 이 한계를 깨기 위해 퍼셉트론을 여러 층으로 쌓은 것이 다층 퍼셉트론(Multi-Layer Perceptron, MLP), 즉 현대적 의미의 신경망(Neural Network)이다. 이번 글에서는 신경망의 기본 구성 요소를 하나씩 해부한다. 층이 무엇인지, 파라미터가 어디서 나오는지, 데이터가 입력에서 출력까지 어떤 계산을 거치는지를 수식과 코드로 완전히 이해한다.
신경망의 세 가지 층 유형
신경망은 크게 세 종류의 층으로 구성된다.
입력층(Input Layer): 원시 데이터를 받는 층. 실제 계산은 수행하지 않고 데이터를 네트워크에 전달하는 역할만 한다. 노드 수 = 특성(feature) 수.
은닉층(Hidden Layer): 입력층과 출력층 사이의 모든 층. 이 층들이 데이터의 복잡한 패턴을 학습한다. 층의 수와 각 층의 노드 수가 신경망의 "용량(capacity)"을 결정한다.
출력층(Output Layer): 최종 예측값을 생성하는 층. 문제 유형에 따라 출력 노드 수가 달라진다.
- 이진 분류: 노드 1개 (시그모이드 출력)
- 다중 분류: 클래스 수만큼 노드 (소프트맥스)
- 회귀: 노드 1개 (선형 출력)
층의 수학: 행렬 곱과 편향
하나의 완전 연결 층(Fully Connected Layer, Dense Layer)이 하는 연산을 수식으로 표현하면:
z⁽ˡ⁾ = W⁽ˡ⁾ · a⁽ˡ⁻¹⁾ + b⁽ˡ⁾
a⁽ˡ⁾ = f(z⁽ˡ⁾)
W⁽ˡ⁾: l번째 층의 가중치 행렬 (형상: n_out × n_in)b⁽ˡ⁾: l번째 층의 편향 벡터 (형상: n_out)a⁽ˡ⁻¹⁾: 이전 층의 출력 (형상: n_in)f: 활성화 함수
배치(batch) 단위로 처리할 때는 X ∈ ℝ^(B×n_in)이 되고, 결과는 Z ∈ ℝ^(B×n_out)이 된다.
파라미터 수 계산
신경망의 학습 가능한 파라미터(가중치 + 편향)의 수를 계산하는 방법:
import torch
import torch.nn as nn
model = nn.Sequential(
nn.Linear(784, 256), # 784*256 + 256 = 201,216
nn.ReLU(),
nn.Linear(256, 128), # 256*128 + 128 = 32,896
nn.ReLU(),
nn.Linear(128, 10), # 128*10 + 10 = 1,290
)
# 파라미터 수 계산
total = sum(p.numel() for p in model.parameters())
trainable = sum(
p.numel() for p in model.parameters()
if p.requires_grad
)
print(f"전체 파라미터: {total:,}") # 235,402
print(f"학습 가능: {trainable:,}") # 235,402
# 각 층별 파라미터 확인
for name, param in model.named_parameters():
print(f"{name:15s}: {param.shape} = {param.numel():,}")
순전파(Forward Pass) 상세
순전파는 입력 데이터가 층을 통과하며 예측값을 생성하는 과정이다.
import torch
import torch.nn as nn
import torch.nn.functional as F
class ThreeLayerNet(nn.Module):
def __init__(self, d_in, d_h1, d_h2, d_out):
super().__init__()
self.fc1 = nn.Linear(d_in, d_h1)
self.fc2 = nn.Linear(d_h1, d_h2)
self.fc3 = nn.Linear(d_h2, d_out)
def forward(self, x):
# 각 층: 선형 변환 → 활성화
z1 = self.fc1(x) # (B, d_h1)
a1 = F.relu(z1) # 비선형 변환
z2 = self.fc2(a1) # (B, d_h2)
a2 = F.relu(z2)
z3 = self.fc3(a2) # (B, d_out)
return z3 # 로짓(logit) 반환
model = ThreeLayerNet(4, 5, 4, 3)
x = torch.randn(8, 4) # 배치 크기 8, 입력 차원 4
output = model(x) # (8, 3) 출력
print(output.shape) # torch.Size([8, 3])
활성화 함수가 필요한 이유
활성화 함수 없이 층을 쌓으면 어떻게 될까? 선형 변환을 아무리 많이 쌓아도 결국 하나의 선형 변환과 같다.
# 활성화 없이 두 층을 쌓으면
# y = W2(W1 x + b1) + b2
# = (W2W1)x + (W2b1 + b2)
# = W_eff x + b_eff
# → 결국 하나의 선형 변환과 동일!
W1 = torch.tensor([[2.0, 1.0], [0.0, 3.0]])
W2 = torch.tensor([[1.0, -1.0]])
W_eff = W2 @ W1 # [[2, -2]] — 단일 행렬로 표현 가능
print(W_eff)
비선형 활성화 함수가 있어야 신경망이 선형 변환 이상의 복잡한 함수를 표현할 수 있다. 이것이 활성화 함수의 존재 이유다.
완전 연결층 vs 다른 층 종류
신경망에는 완전 연결층 외에도 다양한 층이 있다.
| 층 유형 | 특징 | 주로 쓰이는 곳 |
|---|---|---|
| Linear (FC) | 모든 입력-출력 연결 | MLP, 분류 헤드 |
| Conv2d | 지역적 패턴 학습, 가중치 공유 | 이미지 처리 |
| LSTM/GRU | 시퀀스 상태 유지 | 자연어, 시계열 |
| Attention | 위치별 중요도 가중합 | 트랜스포머 |
| Embedding | 정수 → 벡터 룩업 | NLP 토큰 |
신경망 학습 흐름
신경망 학습의 전체 사이클은 4단계로 이루어진다.
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()
for epoch in range(100):
for X_batch, y_batch in dataloader:
# 1. 순전파 → 예측
pred = model(X_batch)
# 2. 손실 계산
loss = criterion(pred, y_batch)
# 3. 역전파 → 그래디언트 계산
optimizer.zero_grad()
loss.backward()
# 4. 파라미터 업데이트
optimizer.step()
이 사이클에서 순전파는 예측을 만들고, 역전파(backpropagation)는 각 파라미터에 대한 손실의 기울기를 계산한다. 파라미터 업데이트는 옵티마이저가 담당한다. 역전파는 다음 글에서 자세히 다룬다.
정리
신경망의 핵심 구성 요소를 정리하면: 층(Layer)은 선형 변환(가중치 행렬 × 입력 + 편향)과 비선형 활성화 함수로 이루어진다. 여러 층을 쌓으면 복잡한 함수를 근사할 수 있는 능력이 생긴다. 파라미터 수는 층간 연결 수 + 편향 수로 계산한다. 순전파는 입력에서 출력으로 데이터가 흐르며 예측을 생성하는 과정이다.
지난 글: 퍼셉트론: 딥러닝의 기원이 된 인공 뉴런
다음 글: 활성화 함수: ReLU, Sigmoid, GELU의 모든 것
읽어주셔서 감사합니다. 😊

