층 1이 y = W₁ x + b₁이고 층 2가 z = W₂ y + b₂라면, 결과는 z = W₂ W₁ x + (W₂ b₁ + b₂)가 돼. 결국 x에 대한 하나의 선형 함수일 뿐이야. 선형층을 10개 쌓아도 표현력은 1개일 때와 정확히 같아. 순수한 선형층에 깊이를 더하는 건 낭비야. 단일 행렬 하나로 계산할 수 있는 일을 10배 많은 매개변수로 계산하는 셈이거든.
해결책은 비선형성이야. 모든 선형층 사이에 비선형 활성화 함수인 ReLU, GELU, tanh를 넣으면 합성한 결과가 더 이상 선형 함수가 아니야. 그때부터 깊이가 의미를 가져. 각 층이 입력 공간을 여러 영역으로 나누고, 그 영역들을 다양한 방식으로 다시 조합할 수 있어.
원칙: 비선형성이 없는 선형층은 깊이만 낭비해. 현대적인 직관으로 보면 층은 언제나 두 연산이 한 쌍으로 움직여. 선형 투영과 비선형 게이트야.
XOR이라는 대표 사례
XOR은 단일 선형 분류기로 풀 수 없는 가장 작은 함수야. 입력 2개와 출력 1개가 있고, 단위 정사각형의 4개 모서리에 (0,0)→0, (0,1)→1, (1,0)→1, (1,1)→0이 놓여 있어. 1인 점들을 한쪽에 두고 0인 점들을 다른 쪽에 두는 직선은 존재하지 않아. 하지만 ReLU 유닛 2개가 있는 은닉층을 추가하면 간단히 풀 수 있어.
깊이가 의미를 가지려면
깊이는 마법이 아니야. 비선형 활성화가 실제로 쓸 수 있게 해 주는 표현 복잡성의 예산이야. 초기 층에서 만든 표현인 에지와 문자 n-gram은 중간 수준 개념인 질감과 음절로 합쳐지고, 다시 상위 수준 개념인 객체와 단어로 합쳐져. 활성화를 빼면 이 합성은 하나의 선형 함수로 무너져.
Code
XOR: 선형 모델은 실패, ReLU는 성공·python
import torch, torch.nn as nn
X = torch.tensor([[0,0],[0,1],[1,0],[1,1]], dtype=torch.float32)
y = torch.tensor([0., 1., 1., 0.])
class LinearOnly(nn.Module):
def __init__(self):
super().__init__()
self.l1 = nn.Linear(2, 4); self.l2 = nn.Linear(4, 1)
def forward(self, x):
return self.l2(self.l1(x)).squeeze(-1)
class WithReLU(nn.Module):
def __init__(self):
super().__init__()
self.l1 = nn.Linear(2, 4); self.l2 = nn.Linear(4, 1)
def forward(self, x):
return self.l2(torch.relu(self.l1(x))).squeeze(-1)
def train(model, steps=2000, lr=0.1):
opt = torch.optim.SGD(model.parameters(), lr=lr)
for _ in range(steps):
opt.zero_grad()
loss = ((model(X) - y) ** 2).mean()
loss.backward(); opt.step()
return loss.item()
print("LinearOnly final loss:", train(LinearOnly())) # ~0.25
print("WithReLU final loss:", train(WithReLU())) # ~0.0