딥러닝 학습은 결국 이 루프를 반복하는 것과 같다.

입력층(Input Layer) + 은닉층(Hidden Layer, 1개 이상) + 출력층(Output Layer) 로 이루어진 신경망이다.
| 층 | 역할 | 예시(헬스케어) |
|---|---|---|
| 입력층 | 데이터를 받는 곳 | 나이, 혈압, 혈당… |
| 은닉층 | 특징을 추출/변환/조합 | 패턴 인식, 조합 |
| 출력층 | 최종 결과를 내는 곳 | 질병 확률, 분류 결과 |
깊어질수록 은닉층은 더 복잡한 특징을 학습한다.
순전파 = 입력값이 레이어를 통과하면서 예측값 를 만드는 과정
각 층에서 기본 형태는 동일하다:


여기서 는 은닉층 출력(중간 표현) 이라고 생각하면 된다.
import torch
import torch.nn as nn
class MLP(nn.Module): # nn.Module의 모든 성질을 이어받겠다!
def __init__(self, input_size, hidden_size, output_size):
super(MLP, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size) # fc = fully connected
self.fc2 = nn.Linear(hidden_size, hidden_size)
self.fc3 = nn.Linear(hidden_size, output_size)
self.relu = nn.ReLU()
def forward(self, x): # 실제로 모델이 순전파를 할 때 작동하는 함수
h = self.relu(self.fc1(x)) # -> self.fc1 의 forward함수를 작동 -> weight @ x + bias
h = self.relu(self.fc2(h))
out = self.fc3(h)
return out
model = MLP(input_size=4, hidden_size=16, output_size=2) # 레이어가 생성
X = torch.tensor([1, 2, 3, 4])
pred = model(X) # -> forward가 실행되는 것 model.forward(X)랑 같음
model = nn.Sequential(
nn.Linear(4, 16),
nn.ReLU(),
nn.Linear(16, 16),
nn.ReLU(),
nn.Linear(16, 2)
)
=Error Function = Cost Function
손실함수는 예측 와 정답 의 차이를 하나의 점수로 만든다.

import torch.nn as nn
criterion = nn.MSELoss()
loss = criterion(prediction, target)


nn.BCEWithLogitsLoss()nn.BCE()를 사용한다면 Sigmoid 붙여야 함 
nn.CrossEntropyLoss()logit이란?
신경망(MLP)의 마지막 출력층에서 활성화 함수(Softmax나 Sigmoid)를 거치기 전에 나오는 순수한 날것의 숫자들
수학적인 정석대로라면 예측 결과에 Softmax를 씌워서 확률로 만든 다음,
정답(Target)과 비교해서 Cross-Entropy Loss 계산 공식에 넣는 게 맞다.
하지만 컴퓨터가 이 계산을 순서대로 하면 수치적 불안정성(Numerical Instability) 이라는 치명적인 문제가 발생한다.
문제점: 크로스 엔트로피 수식에는 로그() 계산이 들어가는데, 만약 모델이 예측을 아주 잘못해서 Softmax 결과값이 0.000000000001처럼 0에 극도로 가까워졌다고 가정해 보자. 여기에 Log를 씌우면 컴퓨터는 처리 한계를 벗어나 마이너스 무한대()나 NaN(Not a Number, 계산 불가) 에러를 뱉어버린다.
파이토치의 해결책: 파이토치 개발자들은 "Softmax 계산과 Log 계산을 분리하지 말고, 수학적으로 식을 하나로 합쳐서(LogSoftmax) 한 번에 계산하자!" 라고 결정했다. 수식을 하나로 합쳐서 최적화하면 연산 속도도 훨씬 빠르고, 값이 0에 가까워져도 에러가 나지 않게 안전한 처리가 가능하기 때문이다.
역전파는 손실 𝐿을 각 가중치 𝑤로 미분해서, 를 계산하는 과정이다.
이 값이 “가중치를 조금 바꿨을 때 손실이 얼마나 변하는지”를 알려준다.
신경망은 레이어를 통과하며 함수가 연속해서 적용된다.
그래서 미분도 합성함수 미분법(연쇄법칙)을 쓴다.

경사하강법 업데이트 식:

너무 작으면
적절하면
너무 크면
학습률 선택 팁
역전파가 기울기()를 계산해주면, 옵티마이저는 그걸 이용해 실제로 𝑤를 업데이트한다.
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
| 옵티마이저 | 핵심 키워드 | 특징 | 실무 팁 |
|---|---|---|---|
| SGD | 기본 | 기울기 방향으로만 이동 | 성능의 기준점(Baseline)으로 주로 사용 |
| Momentum | 관성 (방향 유지) | 이전 이동 방향을 기억해 가속도 부여 | 지그재그 흔들림 완화 |
| RMSprop | 적응형 보폭 | 상황에 따라 스텝 크기 조절 | RNN 등 시계열 데이터에서 종종 사용 |
| Adam | 관성 + 보폭 | Momentum + RMSprop의 장점 결합 | 현업 표준. 무조건 1순위로 시도할 것 |
기울기 초기화 코드는 loss.backward() 이전 or optimizer.step() 이후에만 사용하면 된다.
하지만 가장 안전한건 가장 첫 줄에 위치하는 것이다.
그 이유는? → 가독성 및 안전성 때문
outputs = model(X_batch)
loss = criterion(outputs, y_batch)
# 만약 이런 코드가 중간에 끼어들면?
if loss.item() > 1000:
continue # backward 안 하고 넘어감
optimizer.zero_grad()
loss.backward()

루프 밖 seed 1번 고정은 언제 “괜찮다”고 말할 수 있나?
왜 그런가?
torch.manual_seed(42)는
카드를 특정 순서로 섞어놓는 것
예를 들어:
덱 = [7, 3, 9, 1, 4, 8, 2, ...]
이 순서는 항상 똑같이 시작한다.
모델 초기화는 “카드를 뽑는 것.”
model = MLP(...) 이 순간:
덱을 한 번만 섞음
→ 모델1: 앞 카드 사용
→ 모델2: 그 다음 카드 사용
→ 모델3: 그 다음 카드 사용
👉 실행을 다시 하면 같은 결과는 나오지만
👉 모델1, 모델2, 모델3는 서로 다른 초기값
for ...
torch.manual_seed(42)
model = MLP(...)
이건 모델 만들기 전에 덱을 다시 처음 상태로 돌린다는 것. 따라서?
모델1 = [7,3,9]
모델2 = [7,3,9]
모델3 = [7,3,9]
👉 완전히 같은 초기값
| 목적 | seed 위치 |
|---|---|
| 실행을 다시 해도 같은 결과 | 맨 위에 한 번 |
| 모든 실험이 같은 초기값 | 루프 안에서 매번 |
실제로 완벽한 재현을 위해서는 torch.manual_seed와 np.random.seed 외에도
다음과 같은 설정이 추가로 필요할 수 있다:
random.seed(seed) (파이썬 기본 난수 라이브러리)torch.cuda.manual_seed_all(seed) (GPU 사용 시)torch.backends.cudnn.deterministic = True (연산 결과의 결정론적 보장)